From 197eedf40105a76d92eb170d4a5edd79f7af5d23 Mon Sep 17 00:00:00 2001 From: Ezekiel Leslie Omire Date: Wed, 3 Dec 2025 09:52:14 +0300 Subject: [PATCH] Complete Phase 3 Freebie Tracker with working models, seed, and debug scripts --- alembic.ini | 119 + lib/__pycache__/db.cpython-38.pyc | Bin 0 -> 360 bytes lib/__pycache__/debug.cpython-38.pyc | Bin 0 -> 352 bytes lib/__pycache__/models.cpython-38.pyc | Bin 0 -> 2806 bytes lib/__pycache__/seed.cpython-38.pyc | Bin 0 -> 1036 bytes lib/db.py | 12 + lib/debug.py | 4 + lib/freebies.db | Bin 0 -> 24576 bytes lib/migrations/README | 1 + lib/migrations/__pycache__/env.cpython-38.pyc | Bin 0 -> 1679 bytes lib/migrations/env.py | 80 + lib/migrations/script.py.mako | 26 + ...a5f8be6737c8_initial_tables.cpython-38.pyc | Bin 0 -> 1439 bytes .../versions/a5f8be6737c8_initial_tables.py | 52 + lib/models.py | 66 + lib/seed.py | 36 + venv/bin/Activate.ps1 | 241 + venv/bin/activate | 76 + venv/bin/activate.csh | 37 + venv/bin/activate.fish | 75 + venv/bin/alembic | 8 + venv/bin/mako-render | 8 + venv/bin/pip | 8 + venv/bin/pip3 | 8 + venv/bin/pip3.8 | 8 + venv/bin/python | 1 + venv/bin/python3 | 1 + .../site/python3.8/greenlet/greenlet.h | 164 + .../MarkupSafe-2.1.5.dist-info/INSTALLER | 1 + .../MarkupSafe-2.1.5.dist-info/LICENSE.rst | 28 + .../MarkupSafe-2.1.5.dist-info/METADATA | 93 + .../MarkupSafe-2.1.5.dist-info/RECORD | 14 + .../MarkupSafe-2.1.5.dist-info/WHEEL | 6 + .../MarkupSafe-2.1.5.dist-info/top_level.txt | 1 + .../typing_extensions.cpython-38.pyc | Bin 0 -> 122543 bytes .../site-packages/_distutils_hack/__init__.py | 128 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 5147 bytes .../__pycache__/override.cpython-38.pyc | Bin 0 -> 259 bytes .../site-packages/_distutils_hack/override.py | 1 + .../alembic-1.14.1.dist-info/INSTALLER | 1 + .../alembic-1.14.1.dist-info/LICENSE | 19 + .../alembic-1.14.1.dist-info/METADATA | 143 + .../alembic-1.14.1.dist-info/RECORD | 151 + .../alembic-1.14.1.dist-info/REQUESTED | 0 .../alembic-1.14.1.dist-info/WHEEL | 5 + .../alembic-1.14.1.dist-info/entry_points.txt | 2 + .../alembic-1.14.1.dist-info/top_level.txt | 1 + .../site-packages/alembic/__init__.py | 4 + .../site-packages/alembic/__main__.py | 4 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 284 bytes .../__pycache__/__main__.cpython-38.pyc | Bin 0 -> 294 bytes .../__pycache__/command.cpython-38.pyc | Bin 0 -> 18135 bytes .../alembic/__pycache__/config.cpython-38.pyc | Bin 0 -> 17677 bytes .../__pycache__/context.cpython-38.pyc | Bin 0 -> 323 bytes .../__pycache__/environment.cpython-38.pyc | Bin 0 -> 239 bytes .../__pycache__/migration.cpython-38.pyc | Bin 0 -> 235 bytes .../alembic/__pycache__/op.cpython-38.pyc | Bin 0 -> 306 bytes .../alembic/autogenerate/__init__.py | 10 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 627 bytes .../__pycache__/api.cpython-38.pyc | Bin 0 -> 16841 bytes .../__pycache__/compare.cpython-38.pyc | Bin 0 -> 27097 bytes .../__pycache__/render.cpython-38.pyc | Bin 0 -> 27365 bytes .../__pycache__/rewriter.cpython-38.pyc | Bin 0 -> 6996 bytes .../site-packages/alembic/autogenerate/api.py | 650 ++ .../alembic/autogenerate/compare.py | 1329 +++ .../alembic/autogenerate/render.py | 1119 +++ .../alembic/autogenerate/rewriter.py | 240 + .../site-packages/alembic/command.py | 758 ++ .../python3.8/site-packages/alembic/config.py | 640 ++ .../site-packages/alembic/context.py | 5 + .../site-packages/alembic/context.pyi | 853 ++ .../site-packages/alembic/ddl/__init__.py | 6 + .../ddl/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 393 bytes .../ddl/__pycache__/_autogen.cpython-38.pyc | Bin 0 -> 11117 bytes .../ddl/__pycache__/base.cpython-38.pyc | Bin 0 -> 10711 bytes .../ddl/__pycache__/impl.cpython-38.pyc | Bin 0 -> 23982 bytes .../ddl/__pycache__/mssql.cpython-38.pyc | Bin 0 -> 10716 bytes .../ddl/__pycache__/mysql.cpython-38.pyc | Bin 0 -> 9933 bytes .../ddl/__pycache__/oracle.cpython-38.pyc | Bin 0 -> 5533 bytes .../ddl/__pycache__/postgresql.cpython-38.pyc | Bin 0 -> 21733 bytes .../ddl/__pycache__/sqlite.cpython-38.pyc | Bin 0 -> 5973 bytes .../site-packages/alembic/ddl/_autogen.py | 329 + .../site-packages/alembic/ddl/base.py | 336 + .../site-packages/alembic/ddl/impl.py | 886 ++ .../site-packages/alembic/ddl/mssql.py | 419 + .../site-packages/alembic/ddl/mysql.py | 492 + .../site-packages/alembic/ddl/oracle.py | 202 + .../site-packages/alembic/ddl/postgresql.py | 850 ++ .../site-packages/alembic/ddl/sqlite.py | 236 + .../site-packages/alembic/environment.py | 1 + .../site-packages/alembic/migration.py | 1 + .../lib/python3.8/site-packages/alembic/op.py | 5 + .../python3.8/site-packages/alembic/op.pyi | 1338 +++ .../alembic/operations/__init__.py | 15 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 502 bytes .../__pycache__/base.cpython-38.pyc | Bin 0 -> 71986 bytes .../__pycache__/batch.cpython-38.pyc | Bin 0 -> 19208 bytes .../operations/__pycache__/ops.cpython-38.pyc | Bin 0 -> 84033 bytes .../__pycache__/schemaobj.cpython-38.pyc | Bin 0 -> 8436 bytes .../__pycache__/toimpl.cpython-38.pyc | Bin 0 -> 5740 bytes .../site-packages/alembic/operations/base.py | 1907 ++++ .../site-packages/alembic/operations/batch.py | 717 ++ .../site-packages/alembic/operations/ops.py | 2800 ++++++ .../alembic/operations/schemaobj.py | 290 + .../alembic/operations/toimpl.py | 238 + .../python3.8/site-packages/alembic/py.typed | 0 .../site-packages/alembic/runtime/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 208 bytes .../__pycache__/environment.cpython-38.pyc | Bin 0 -> 40044 bytes .../__pycache__/migration.cpython-38.pyc | Bin 0 -> 39551 bytes .../alembic/runtime/environment.py | 1051 ++ .../alembic/runtime/migration.py | 1391 +++ .../site-packages/alembic/script/__init__.py | 4 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 309 bytes .../script/__pycache__/base.cpython-38.pyc | Bin 0 -> 27349 bytes .../__pycache__/revision.cpython-38.pyc | Bin 0 -> 42426 bytes .../__pycache__/write_hooks.cpython-38.pyc | Bin 0 -> 4816 bytes .../site-packages/alembic/script/base.py | 1066 ++ .../site-packages/alembic/script/revision.py | 1728 ++++ .../alembic/script/write_hooks.py | 179 + .../alembic/templates/async/README | 1 + .../async/__pycache__/env.cpython-38.pyc | Bin 0 -> 2164 bytes .../alembic/templates/async/alembic.ini.mako | 117 + .../alembic/templates/async/env.py | 89 + .../alembic/templates/async/script.py.mako | 26 + .../alembic/templates/generic/README | 1 + .../generic/__pycache__/env.cpython-38.pyc | Bin 0 -> 1729 bytes .../templates/generic/alembic.ini.mako | 119 + .../alembic/templates/generic/env.py | 78 + .../alembic/templates/generic/script.py.mako | 26 + .../alembic/templates/multidb/README | 12 + .../multidb/__pycache__/env.cpython-38.pyc | Bin 0 -> 2714 bytes .../templates/multidb/alembic.ini.mako | 124 + .../alembic/templates/multidb/env.py | 140 + .../alembic/templates/multidb/script.py.mako | 47 + .../site-packages/alembic/testing/__init__.py | 29 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1298 bytes .../__pycache__/assertions.cpython-38.pyc | Bin 0 -> 5532 bytes .../testing/__pycache__/env.cpython-38.pyc | Bin 0 -> 9657 bytes .../__pycache__/fixtures.cpython-38.pyc | Bin 0 -> 9704 bytes .../__pycache__/requirements.cpython-38.pyc | Bin 0 -> 8774 bytes .../__pycache__/schemacompare.cpython-38.pyc | Bin 0 -> 5543 bytes .../testing/__pycache__/util.cpython-38.pyc | Bin 0 -> 4004 bytes .../__pycache__/warnings.cpython-38.pyc | Bin 0 -> 850 bytes .../alembic/testing/assertions.py | 176 + .../site-packages/alembic/testing/env.py | 518 + .../site-packages/alembic/testing/fixtures.py | 318 + .../alembic/testing/plugin/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 215 bytes .../__pycache__/bootstrap.cpython-38.pyc | Bin 0 -> 271 bytes .../alembic/testing/plugin/bootstrap.py | 4 + .../alembic/testing/requirements.py | 210 + .../alembic/testing/schemacompare.py | 169 + .../alembic/testing/suite/__init__.py | 7 + .../suite/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 423 bytes .../_autogen_fixtures.cpython-38.pyc | Bin 0 -> 8089 bytes .../test_autogen_comments.cpython-38.pyc | Bin 0 -> 4132 bytes .../test_autogen_computed.cpython-38.pyc | Bin 0 -> 5644 bytes .../test_autogen_diffs.cpython-38.pyc | Bin 0 -> 7156 bytes .../test_autogen_fks.cpython-38.pyc | Bin 0 -> 19496 bytes .../test_autogen_identity.cpython-38.pyc | Bin 0 -> 5389 bytes .../test_environment.cpython-38.pyc | Bin 0 -> 8598 bytes .../suite/__pycache__/test_op.cpython-38.pyc | Bin 0 -> 2308 bytes .../testing/suite/_autogen_fixtures.py | 335 + .../testing/suite/test_autogen_comments.py | 242 + .../testing/suite/test_autogen_computed.py | 204 + .../testing/suite/test_autogen_diffs.py | 273 + .../alembic/testing/suite/test_autogen_fks.py | 1190 +++ .../testing/suite/test_autogen_identity.py | 226 + .../alembic/testing/suite/test_environment.py | 364 + .../alembic/testing/suite/test_op.py | 42 + .../site-packages/alembic/testing/util.py | 126 + .../site-packages/alembic/testing/warnings.py | 40 + .../site-packages/alembic/util/__init__.py | 35 + .../util/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1399 bytes .../util/__pycache__/compat.cpython-38.pyc | Bin 0 -> 2406 bytes .../util/__pycache__/editor.cpython-38.pyc | Bin 0 -> 2525 bytes .../util/__pycache__/exc.cpython-38.pyc | Bin 0 -> 508 bytes .../__pycache__/langhelpers.cpython-38.pyc | Bin 0 -> 9943 bytes .../util/__pycache__/messaging.cpython-38.pyc | Bin 0 -> 3213 bytes .../util/__pycache__/pyfiles.cpython-38.pyc | Bin 0 -> 3213 bytes .../__pycache__/sqla_compat.cpython-38.pyc | Bin 0 -> 18013 bytes .../site-packages/alembic/util/compat.py | 90 + .../site-packages/alembic/util/editor.py | 81 + .../site-packages/alembic/util/exc.py | 6 + .../site-packages/alembic/util/langhelpers.py | 332 + .../site-packages/alembic/util/messaging.py | 121 + .../site-packages/alembic/util/pyfiles.py | 114 + .../site-packages/alembic/util/sqla_compat.py | 663 ++ .../site-packages/distutils-precedence.pth | 1 + .../greenlet-3.1.1.dist-info/AUTHORS | 51 + .../greenlet-3.1.1.dist-info/INSTALLER | 1 + .../greenlet-3.1.1.dist-info/LICENSE | 30 + .../greenlet-3.1.1.dist-info/LICENSE.PSF | 47 + .../greenlet-3.1.1.dist-info/METADATA | 103 + .../greenlet-3.1.1.dist-info/RECORD | 122 + .../greenlet-3.1.1.dist-info/WHEEL | 6 + .../greenlet-3.1.1.dist-info/top_level.txt | 1 + .../site-packages/greenlet/CObjects.cpp | 157 + .../site-packages/greenlet/PyGreenlet.cpp | 738 ++ .../site-packages/greenlet/PyGreenlet.hpp | 35 + .../greenlet/PyGreenletUnswitchable.cpp | 147 + .../site-packages/greenlet/PyModule.cpp | 292 + .../greenlet/TBrokenGreenlet.cpp | 45 + .../greenlet/TExceptionState.cpp | 62 + .../site-packages/greenlet/TGreenlet.cpp | 718 ++ .../site-packages/greenlet/TGreenlet.hpp | 813 ++ .../greenlet/TGreenletGlobals.cpp | 94 + .../site-packages/greenlet/TMainGreenlet.cpp | 153 + .../site-packages/greenlet/TPythonState.cpp | 393 + .../site-packages/greenlet/TStackState.cpp | 265 + .../site-packages/greenlet/TThreadState.hpp | 497 + .../greenlet/TThreadStateCreator.hpp | 102 + .../greenlet/TThreadStateDestroy.cpp | 258 + .../site-packages/greenlet/TUserGreenlet.cpp | 662 ++ .../site-packages/greenlet/__init__.py | 71 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1036 bytes .../_greenlet.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 1420664 bytes .../site-packages/greenlet/greenlet.cpp | 320 + .../site-packages/greenlet/greenlet.h | 164 + .../greenlet/greenlet_allocator.hpp | 63 + .../greenlet/greenlet_compiler_compat.hpp | 98 + .../greenlet/greenlet_cpython_add_pending.hpp | 172 + .../greenlet/greenlet_cpython_compat.hpp | 142 + .../greenlet/greenlet_exceptions.hpp | 171 + .../greenlet/greenlet_internal.hpp | 107 + .../site-packages/greenlet/greenlet_refs.hpp | 1118 +++ .../greenlet/greenlet_slp_switch.hpp | 99 + .../greenlet/greenlet_thread_support.hpp | 31 + .../greenlet/platform/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 210 bytes .../platform/setup_switch_x64_masm.cmd | 2 + .../greenlet/platform/switch_aarch64_gcc.h | 124 + .../greenlet/platform/switch_alpha_unix.h | 30 + .../greenlet/platform/switch_amd64_unix.h | 87 + .../greenlet/platform/switch_arm32_gcc.h | 79 + .../greenlet/platform/switch_arm32_ios.h | 67 + .../greenlet/platform/switch_arm64_masm.asm | 53 + .../greenlet/platform/switch_arm64_masm.obj | Bin 0 -> 746 bytes .../greenlet/platform/switch_arm64_msvc.h | 17 + .../greenlet/platform/switch_csky_gcc.h | 48 + .../platform/switch_loongarch64_linux.h | 31 + .../greenlet/platform/switch_m68k_gcc.h | 38 + .../greenlet/platform/switch_mips_unix.h | 64 + .../greenlet/platform/switch_ppc64_aix.h | 103 + .../greenlet/platform/switch_ppc64_linux.h | 105 + .../greenlet/platform/switch_ppc_aix.h | 87 + .../greenlet/platform/switch_ppc_linux.h | 84 + .../greenlet/platform/switch_ppc_macosx.h | 82 + .../greenlet/platform/switch_ppc_unix.h | 82 + .../greenlet/platform/switch_riscv_unix.h | 36 + .../greenlet/platform/switch_s390_unix.h | 87 + .../greenlet/platform/switch_sh_gcc.h | 36 + .../greenlet/platform/switch_sparc_sun_gcc.h | 92 + .../greenlet/platform/switch_x32_unix.h | 63 + .../greenlet/platform/switch_x64_masm.asm | 111 + .../greenlet/platform/switch_x64_masm.obj | Bin 0 -> 1078 bytes .../greenlet/platform/switch_x64_msvc.h | 60 + .../greenlet/platform/switch_x86_msvc.h | 326 + .../greenlet/platform/switch_x86_unix.h | 105 + .../greenlet/slp_platformselect.h | 75 + .../site-packages/greenlet/tests/__init__.py | 240 + .../tests/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 5765 bytes .../fail_clearing_run_switches.cpython-38.pyc | Bin 0 -> 1555 bytes .../fail_cpp_exception.cpython-38.pyc | Bin 0 -> 1195 bytes ...initialstub_already_started.cpython-38.pyc | Bin 0 -> 2077 bytes .../fail_slp_switch.cpython-38.pyc | Bin 0 -> 786 bytes ...fail_switch_three_greenlets.cpython-38.pyc | Bin 0 -> 1166 bytes ...ail_switch_three_greenlets2.cpython-38.pyc | Bin 0 -> 1541 bytes .../fail_switch_two_greenlets.cpython-38.pyc | Bin 0 -> 1185 bytes .../__pycache__/leakcheck.cpython-38.pyc | Bin 0 -> 7336 bytes .../test_contextvars.cpython-38.pyc | Bin 0 -> 7839 bytes .../tests/__pycache__/test_cpp.cpython-38.pyc | Bin 0 -> 2778 bytes .../test_extension_interface.cpython-38.pyc | Bin 0 -> 4651 bytes .../tests/__pycache__/test_gc.cpython-38.pyc | Bin 0 -> 3154 bytes .../__pycache__/test_generator.cpython-38.pyc | Bin 0 -> 2252 bytes .../test_generator_nested.cpython-38.pyc | Bin 0 -> 5261 bytes .../__pycache__/test_greenlet.cpython-38.pyc | Bin 0 -> 46477 bytes .../test_greenlet_trash.cpython-38.pyc | Bin 0 -> 4911 bytes .../__pycache__/test_leaks.cpython-38.pyc | Bin 0 -> 11396 bytes .../test_stack_saved.cpython-38.pyc | Bin 0 -> 890 bytes .../__pycache__/test_throw.cpython-38.pyc | Bin 0 -> 3984 bytes .../__pycache__/test_tracing.cpython-38.pyc | Bin 0 -> 9549 bytes .../__pycache__/test_version.cpython-38.pyc | Bin 0 -> 1559 bytes .../__pycache__/test_weakref.cpython-38.pyc | Bin 0 -> 1870 bytes .../greenlet/tests/_test_extension.c | 231 + ...t_extension.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 35904 bytes .../greenlet/tests/_test_extension_cpp.cpp | 226 + ...tension_cpp.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 57640 bytes .../tests/fail_clearing_run_switches.py | 47 + .../greenlet/tests/fail_cpp_exception.py | 33 + .../tests/fail_initialstub_already_started.py | 78 + .../greenlet/tests/fail_slp_switch.py | 29 + .../tests/fail_switch_three_greenlets.py | 44 + .../tests/fail_switch_three_greenlets2.py | 55 + .../tests/fail_switch_two_greenlets.py | 41 + .../site-packages/greenlet/tests/leakcheck.py | 319 + .../greenlet/tests/test_contextvars.py | 310 + .../site-packages/greenlet/tests/test_cpp.py | 73 + .../tests/test_extension_interface.py | 115 + .../site-packages/greenlet/tests/test_gc.py | 86 + .../greenlet/tests/test_generator.py | 59 + .../greenlet/tests/test_generator_nested.py | 168 + .../greenlet/tests/test_greenlet.py | 1324 +++ .../greenlet/tests/test_greenlet_trash.py | 187 + .../greenlet/tests/test_leaks.py | 443 + .../greenlet/tests/test_stack_saved.py | 19 + .../greenlet/tests/test_throw.py | 128 + .../greenlet/tests/test_tracing.py | 291 + .../greenlet/tests/test_version.py | 41 + .../greenlet/tests/test_weakref.py | 35 + .../INSTALLER | 1 + .../LICENSE | 202 + .../METADATA | 133 + .../importlib_metadata-8.5.0.dist-info/RECORD | 31 + .../importlib_metadata-8.5.0.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../importlib_metadata/__init__.py | 1132 +++ .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 41898 bytes .../__pycache__/_adapters.cpython-38.pyc | Bin 0 -> 2929 bytes .../__pycache__/_collections.cpython-38.pyc | Bin 0 -> 1581 bytes .../__pycache__/_compat.cpython-38.pyc | Bin 0 -> 1919 bytes .../__pycache__/_functools.cpython-38.pyc | Bin 0 -> 3161 bytes .../__pycache__/_itertools.cpython-38.pyc | Bin 0 -> 5054 bytes .../__pycache__/_meta.cpython-38.pyc | Bin 0 -> 3259 bytes .../__pycache__/_text.cpython-38.pyc | Bin 0 -> 3114 bytes .../__pycache__/diagnose.cpython-38.pyc | Bin 0 -> 869 bytes .../importlib_metadata/_adapters.py | 83 + .../importlib_metadata/_collections.py | 30 + .../importlib_metadata/_compat.py | 56 + .../importlib_metadata/_functools.py | 104 + .../importlib_metadata/_itertools.py | 171 + .../site-packages/importlib_metadata/_meta.py | 75 + .../site-packages/importlib_metadata/_text.py | 99 + .../importlib_metadata/compat/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 218 bytes .../compat/__pycache__/py311.cpython-38.pyc | Bin 0 -> 1054 bytes .../compat/__pycache__/py39.cpython-38.pyc | Bin 0 -> 1213 bytes .../importlib_metadata/compat/py311.py | 22 + .../importlib_metadata/compat/py39.py | 36 + .../importlib_metadata/diagnose.py | 21 + .../site-packages/importlib_metadata/py.typed | 0 .../INSTALLER | 1 + .../LICENSE | 202 + .../METADATA | 103 + .../RECORD | 67 + .../importlib_resources-6.4.5.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../importlib_resources/__init__.py | 43 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 888 bytes .../__pycache__/_adapters.cpython-38.pyc | Bin 0 -> 7453 bytes .../__pycache__/_common.cpython-38.pyc | Bin 0 -> 5644 bytes .../__pycache__/_functional.cpython-38.pyc | Bin 0 -> 2778 bytes .../__pycache__/_itertools.cpython-38.pyc | Bin 0 -> 1410 bytes .../__pycache__/abc.cpython-38.pyc | Bin 0 -> 6891 bytes .../__pycache__/readers.cpython-38.pyc | Bin 0 -> 8699 bytes .../__pycache__/simple.cpython-38.pyc | Bin 0 -> 4538 bytes .../importlib_resources/_adapters.py | 168 + .../importlib_resources/_common.py | 211 + .../importlib_resources/_functional.py | 81 + .../importlib_resources/_itertools.py | 38 + .../site-packages/importlib_resources/abc.py | 171 + .../importlib_resources/compat/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 219 bytes .../compat/__pycache__/py38.cpython-38.pyc | Bin 0 -> 396 bytes .../compat/__pycache__/py39.cpython-38.pyc | Bin 0 -> 357 bytes .../importlib_resources/compat/py38.py | 11 + .../importlib_resources/compat/py39.py | 10 + .../importlib_resources/future/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 219 bytes .../__pycache__/adapters.cpython-38.pyc | Bin 0 -> 3479 bytes .../importlib_resources/future/adapters.py | 95 + .../importlib_resources/py.typed | 0 .../importlib_resources/readers.py | 203 + .../importlib_resources/simple.py | 106 + .../importlib_resources/tests/__init__.py | 0 .../tests/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 218 bytes .../tests/__pycache__/_path.cpython-38.pyc | Bin 0 -> 3245 bytes .../test_compatibilty_files.cpython-38.pyc | Bin 0 -> 5407 bytes .../__pycache__/test_contents.cpython-38.pyc | Bin 0 -> 1528 bytes .../__pycache__/test_custom.cpython-38.pyc | Bin 0 -> 2357 bytes .../__pycache__/test_files.cpython-38.pyc | Bin 0 -> 7656 bytes .../test_functional.cpython-38.pyc | Bin 0 -> 6724 bytes .../__pycache__/test_open.cpython-38.pyc | Bin 0 -> 3751 bytes .../__pycache__/test_path.cpython-38.pyc | Bin 0 -> 2990 bytes .../__pycache__/test_read.cpython-38.pyc | Bin 0 -> 4159 bytes .../__pycache__/test_reader.cpython-38.pyc | Bin 0 -> 5808 bytes .../__pycache__/test_resource.cpython-38.pyc | Bin 0 -> 10318 bytes .../tests/__pycache__/util.cpython-38.pyc | Bin 0 -> 7566 bytes .../tests/__pycache__/zip.cpython-38.pyc | Bin 0 -> 870 bytes .../importlib_resources/tests/_path.py | 94 + .../tests/compat/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 225 bytes .../compat/__pycache__/py312.cpython-38.pyc | Bin 0 -> 594 bytes .../compat/__pycache__/py39.cpython-38.pyc | Bin 0 -> 607 bytes .../importlib_resources/tests/compat/py312.py | 18 + .../importlib_resources/tests/compat/py39.py | 13 + .../tests/test_compatibilty_files.py | 104 + .../tests/test_contents.py | 38 + .../importlib_resources/tests/test_custom.py | 47 + .../importlib_resources/tests/test_files.py | 194 + .../tests/test_functional.py | 255 + .../importlib_resources/tests/test_open.py | 84 + .../importlib_resources/tests/test_path.py | 62 + .../importlib_resources/tests/test_read.py | 93 + .../importlib_resources/tests/test_reader.py | 137 + .../tests/test_resource.py | 237 + .../importlib_resources/tests/util.py | 206 + .../importlib_resources/tests/zip.py | 26 + .../mako-1.3.10.dist-info/INSTALLER | 1 + .../mako-1.3.10.dist-info/METADATA | 88 + .../mako-1.3.10.dist-info/RECORD | 74 + .../site-packages/mako-1.3.10.dist-info/WHEEL | 5 + .../mako-1.3.10.dist-info/entry_points.txt | 18 + .../mako-1.3.10.dist-info/licenses/LICENSE | 19 + .../mako-1.3.10.dist-info/top_level.txt | 1 + .../python3.8/site-packages/mako/__init__.py | 8 + .../mako/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 219 bytes .../mako/__pycache__/_ast_util.cpython-38.pyc | Bin 0 -> 21569 bytes .../mako/__pycache__/ast.cpython-38.pyc | Bin 0 -> 4827 bytes .../mako/__pycache__/cache.cpython-38.pyc | Bin 0 -> 6693 bytes .../mako/__pycache__/cmd.cpython-38.pyc | Bin 0 -> 2407 bytes .../mako/__pycache__/codegen.cpython-38.pyc | Bin 0 -> 32959 bytes .../mako/__pycache__/compat.cpython-38.pyc | Bin 0 -> 1931 bytes .../__pycache__/exceptions.cpython-38.pyc | Bin 0 -> 11620 bytes .../mako/__pycache__/filters.cpython-38.pyc | Bin 0 -> 5090 bytes .../mako/__pycache__/lexer.cpython-38.pyc | Bin 0 -> 10588 bytes .../mako/__pycache__/lookup.cpython-38.pyc | Bin 0 -> 10298 bytes .../mako/__pycache__/parsetree.cpython-38.pyc | Bin 0 -> 19664 bytes .../mako/__pycache__/pygen.cpython-38.pyc | Bin 0 -> 6734 bytes .../mako/__pycache__/pyparser.cpython-38.pyc | Bin 0 -> 7261 bytes .../mako/__pycache__/runtime.cpython-38.pyc | Bin 0 -> 27742 bytes .../mako/__pycache__/template.cpython-38.pyc | Bin 0 -> 19716 bytes .../mako/__pycache__/util.cpython-38.pyc | Bin 0 -> 11349 bytes .../python3.8/site-packages/mako/_ast_util.py | 713 ++ venv/lib/python3.8/site-packages/mako/ast.py | 202 + .../lib/python3.8/site-packages/mako/cache.py | 239 + venv/lib/python3.8/site-packages/mako/cmd.py | 99 + .../python3.8/site-packages/mako/codegen.py | 1319 +++ .../python3.8/site-packages/mako/compat.py | 70 + .../site-packages/mako/exceptions.py | 417 + .../site-packages/mako/ext/__init__.py | 0 .../ext/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 201 bytes .../__pycache__/autohandler.cpython-38.pyc | Bin 0 -> 1521 bytes .../__pycache__/babelplugin.cpython-38.pyc | Bin 0 -> 2147 bytes .../__pycache__/beaker_cache.cpython-38.pyc | Bin 0 -> 2591 bytes .../ext/__pycache__/extract.cpython-38.pyc | Bin 0 -> 2944 bytes .../__pycache__/linguaplugin.cpython-38.pyc | Bin 0 -> 1686 bytes .../__pycache__/preprocessors.cpython-38.pyc | Bin 0 -> 634 bytes .../__pycache__/pygmentplugin.cpython-38.pyc | Bin 0 -> 4537 bytes .../ext/__pycache__/turbogears.cpython-38.pyc | Bin 0 -> 1667 bytes .../site-packages/mako/ext/autohandler.py | 70 + .../site-packages/mako/ext/babelplugin.py | 57 + .../site-packages/mako/ext/beaker_cache.py | 82 + .../site-packages/mako/ext/extract.py | 129 + .../site-packages/mako/ext/linguaplugin.py | 57 + .../site-packages/mako/ext/preprocessors.py | 20 + .../site-packages/mako/ext/pygmentplugin.py | 150 + .../site-packages/mako/ext/turbogears.py | 61 + .../python3.8/site-packages/mako/filters.py | 163 + .../lib/python3.8/site-packages/mako/lexer.py | 481 + .../python3.8/site-packages/mako/lookup.py | 361 + .../python3.8/site-packages/mako/parsetree.py | 656 ++ .../lib/python3.8/site-packages/mako/pygen.py | 309 + .../python3.8/site-packages/mako/pyparser.py | 235 + .../python3.8/site-packages/mako/runtime.py | 968 ++ .../python3.8/site-packages/mako/template.py | 711 ++ .../site-packages/mako/testing/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 205 bytes .../__pycache__/_config.cpython-38.pyc | Bin 0 -> 4403 bytes .../__pycache__/assertions.cpython-38.pyc | Bin 0 -> 4598 bytes .../testing/__pycache__/config.cpython-38.pyc | Bin 0 -> 712 bytes .../__pycache__/exclusions.cpython-38.pyc | Bin 0 -> 1684 bytes .../__pycache__/fixtures.cpython-38.pyc | Bin 0 -> 3565 bytes .../__pycache__/helpers.cpython-38.pyc | Bin 0 -> 2441 bytes .../site-packages/mako/testing/_config.py | 128 + .../site-packages/mako/testing/assertions.py | 166 + .../site-packages/mako/testing/config.py | 17 + .../site-packages/mako/testing/exclusions.py | 80 + .../site-packages/mako/testing/fixtures.py | 119 + .../site-packages/mako/testing/helpers.py | 71 + venv/lib/python3.8/site-packages/mako/util.py | 388 + .../site-packages/markupsafe/__init__.py | 332 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 11480 bytes .../__pycache__/_native.cpython-38.pyc | Bin 0 -> 2043 bytes .../site-packages/markupsafe/_native.py | 63 + .../site-packages/markupsafe/_speedups.c | 320 + .../_speedups.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 45024 bytes .../site-packages/markupsafe/_speedups.pyi | 9 + .../site-packages/markupsafe/py.typed | 0 .../pip-25.0.1.dist-info/AUTHORS.txt | 806 ++ .../pip-25.0.1.dist-info/INSTALLER | 1 + .../pip-25.0.1.dist-info/LICENSE.txt | 20 + .../pip-25.0.1.dist-info/METADATA | 90 + .../site-packages/pip-25.0.1.dist-info/RECORD | 854 ++ .../pip-25.0.1.dist-info/REQUESTED | 0 .../site-packages/pip-25.0.1.dist-info/WHEEL | 5 + .../pip-25.0.1.dist-info/entry_points.txt | 3 + .../pip-25.0.1.dist-info/top_level.txt | 1 + .../python3.8/site-packages/pip/__init__.py | 13 + .../python3.8/site-packages/pip/__main__.py | 24 + .../site-packages/pip/__pip-runner__.py | 50 + .../pip/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 652 bytes .../pip/__pycache__/__main__.cpython-38.pyc | Bin 0 -> 480 bytes .../__pycache__/__pip-runner__.cpython-38.pyc | Bin 0 -> 1669 bytes .../site-packages/pip/_internal/__init__.py | 18 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 714 bytes .../__pycache__/build_env.cpython-38.pyc | Bin 0 -> 9840 bytes .../__pycache__/cache.cpython-38.pyc | Bin 0 -> 9031 bytes .../__pycache__/configuration.cpython-38.pyc | Bin 0 -> 11724 bytes .../__pycache__/exceptions.cpython-38.pyc | Bin 0 -> 28360 bytes .../_internal/__pycache__/main.cpython-38.pyc | Bin 0 -> 638 bytes .../__pycache__/pyproject.cpython-38.pyc | Bin 0 -> 3795 bytes .../self_outdated_check.cpython-38.pyc | Bin 0 -> 6828 bytes .../__pycache__/wheel_builder.cpython-38.pyc | Bin 0 -> 8681 bytes .../site-packages/pip/_internal/build_env.py | 322 + .../site-packages/pip/_internal/cache.py | 290 + .../pip/_internal/cli/__init__.py | 4 + .../cli/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 293 bytes .../__pycache__/autocompletion.cpython-38.pyc | Bin 0 -> 5454 bytes .../__pycache__/base_command.cpython-38.pyc | Bin 0 -> 6645 bytes .../cli/__pycache__/cmdoptions.cpython-38.pyc | Bin 0 -> 23734 bytes .../command_context.cpython-38.pyc | Bin 0 -> 1309 bytes .../__pycache__/index_command.cpython-38.pyc | Bin 0 -> 4971 bytes .../cli/__pycache__/main.cpython-38.pyc | Bin 0 -> 1519 bytes .../__pycache__/main_parser.cpython-38.pyc | Bin 0 -> 3034 bytes .../cli/__pycache__/parser.cpython-38.pyc | Bin 0 -> 9984 bytes .../__pycache__/progress_bars.cpython-38.pyc | Bin 0 -> 2643 bytes .../__pycache__/req_command.cpython-38.pyc | Bin 0 -> 8690 bytes .../cli/__pycache__/spinners.cpython-38.pyc | Bin 0 -> 4974 bytes .../__pycache__/status_codes.cpython-38.pyc | Bin 0 -> 372 bytes .../pip/_internal/cli/autocompletion.py | 176 + .../pip/_internal/cli/base_command.py | 240 + .../pip/_internal/cli/cmdoptions.py | 1075 ++ .../pip/_internal/cli/command_context.py | 27 + .../pip/_internal/cli/index_command.py | 171 + .../site-packages/pip/_internal/cli/main.py | 80 + .../pip/_internal/cli/main_parser.py | 134 + .../site-packages/pip/_internal/cli/parser.py | 294 + .../pip/_internal/cli/progress_bars.py | 94 + .../pip/_internal/cli/req_command.py | 329 + .../pip/_internal/cli/spinners.py | 159 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 132 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 3180 bytes .../commands/__pycache__/cache.cpython-38.pyc | Bin 0 -> 6563 bytes .../commands/__pycache__/check.cpython-38.pyc | Bin 0 -> 1988 bytes .../__pycache__/completion.cpython-38.pyc | Bin 0 -> 4345 bytes .../__pycache__/configuration.cpython-38.pyc | Bin 0 -> 8905 bytes .../commands/__pycache__/debug.cpython-38.pyc | Bin 0 -> 6838 bytes .../__pycache__/download.cpython-38.pyc | Bin 0 -> 4236 bytes .../__pycache__/freeze.cpython-38.pyc | Bin 0 -> 3018 bytes .../commands/__pycache__/hash.cpython-38.pyc | Bin 0 -> 2137 bytes .../commands/__pycache__/help.cpython-38.pyc | Bin 0 -> 1326 bytes .../commands/__pycache__/index.cpython-38.pyc | Bin 0 -> 4522 bytes .../__pycache__/inspect.cpython-38.pyc | Bin 0 -> 2999 bytes .../__pycache__/install.cpython-38.pyc | Bin 0 -> 17857 bytes .../commands/__pycache__/list.cpython-38.pyc | Bin 0 -> 10587 bytes .../__pycache__/search.cpython-38.pyc | Bin 0 -> 5288 bytes .../commands/__pycache__/show.cpython-38.pyc | Bin 0 -> 7099 bytes .../__pycache__/uninstall.cpython-38.pyc | Bin 0 -> 3350 bytes .../commands/__pycache__/wheel.cpython-38.pyc | Bin 0 -> 4975 bytes .../pip/_internal/commands/cache.py | 228 + .../pip/_internal/commands/check.py | 67 + .../pip/_internal/commands/completion.py | 130 + .../pip/_internal/commands/configuration.py | 280 + .../pip/_internal/commands/debug.py | 201 + .../pip/_internal/commands/download.py | 146 + .../pip/_internal/commands/freeze.py | 109 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 139 + .../pip/_internal/commands/inspect.py | 92 + .../pip/_internal/commands/install.py | 784 ++ .../pip/_internal/commands/list.py | 375 + .../pip/_internal/commands/search.py | 172 + .../pip/_internal/commands/show.py | 224 + .../pip/_internal/commands/uninstall.py | 114 + .../pip/_internal/commands/wheel.py | 182 + .../pip/_internal/configuration.py | 383 + .../pip/_internal/distributions/__init__.py | 21 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 816 bytes .../__pycache__/base.cpython-38.pyc | Bin 0 -> 2555 bytes .../__pycache__/installed.cpython-38.pyc | Bin 0 -> 1517 bytes .../__pycache__/sdist.cpython-38.pyc | Bin 0 -> 5432 bytes .../__pycache__/wheel.cpython-38.pyc | Bin 0 -> 1912 bytes .../pip/_internal/distributions/base.py | 53 + .../pip/_internal/distributions/installed.py | 29 + .../pip/_internal/distributions/sdist.py | 158 + .../pip/_internal/distributions/wheel.py | 42 + .../site-packages/pip/_internal/exceptions.py | 809 ++ .../pip/_internal/index/__init__.py | 2 + .../index/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 247 bytes .../__pycache__/collector.cpython-38.pyc | Bin 0 -> 15129 bytes .../__pycache__/package_finder.cpython-38.pyc | Bin 0 -> 29621 bytes .../index/__pycache__/sources.cpython-38.pyc | Bin 0 -> 8988 bytes .../pip/_internal/index/collector.py | 494 + .../pip/_internal/index/package_finder.py | 1029 ++ .../pip/_internal/index/sources.py | 284 + .../pip/_internal/locations/__init__.py | 456 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 10965 bytes .../__pycache__/_distutils.cpython-38.pyc | Bin 0 -> 4604 bytes .../__pycache__/_sysconfig.cpython-38.pyc | Bin 0 -> 6060 bytes .../locations/__pycache__/base.cpython-38.pyc | Bin 0 -> 2430 bytes .../pip/_internal/locations/_distutils.py | 172 + .../pip/_internal/locations/_sysconfig.py | 214 + .../pip/_internal/locations/base.py | 81 + .../site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 128 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 4843 bytes .../metadata/__pycache__/_json.cpython-38.pyc | Bin 0 -> 2380 bytes .../metadata/__pycache__/base.cpython-38.pyc | Bin 0 -> 27287 bytes .../__pycache__/pkg_resources.cpython-38.pyc | Bin 0 -> 11049 bytes .../pip/_internal/metadata/_json.py | 86 + .../pip/_internal/metadata/base.py | 688 ++ .../_internal/metadata/importlib/__init__.py | 6 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 368 bytes .../__pycache__/_compat.cpython-38.pyc | Bin 0 -> 3525 bytes .../__pycache__/_dists.cpython-38.pyc | Bin 0 -> 8837 bytes .../__pycache__/_envs.cpython-38.pyc | Bin 0 -> 7630 bytes .../_internal/metadata/importlib/_compat.py | 85 + .../_internal/metadata/importlib/_dists.py | 228 + .../pip/_internal/metadata/importlib/_envs.py | 189 + .../pip/_internal/metadata/pkg_resources.py | 301 + .../pip/_internal/models/__init__.py | 2 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 281 bytes .../__pycache__/candidate.cpython-38.pyc | Bin 0 -> 1266 bytes .../__pycache__/direct_url.cpython-38.pyc | Bin 0 -> 7420 bytes .../__pycache__/format_control.cpython-38.pyc | Bin 0 -> 2733 bytes .../models/__pycache__/index.cpython-38.pyc | Bin 0 -> 1257 bytes .../installation_report.cpython-38.pyc | Bin 0 -> 1792 bytes .../models/__pycache__/link.cpython-38.pyc | Bin 0 -> 18829 bytes .../models/__pycache__/scheme.cpython-38.pyc | Bin 0 -> 954 bytes .../__pycache__/search_scope.cpython-38.pyc | Bin 0 -> 3500 bytes .../selection_prefs.cpython-38.pyc | Bin 0 -> 1719 bytes .../__pycache__/target_python.cpython-38.pyc | Bin 0 -> 3827 bytes .../models/__pycache__/wheel.cpython-38.pyc | Bin 0 -> 5112 bytes .../pip/_internal/models/candidate.py | 25 + .../pip/_internal/models/direct_url.py | 224 + .../pip/_internal/models/format_control.py | 78 + .../pip/_internal/models/index.py | 28 + .../_internal/models/installation_report.py | 56 + .../pip/_internal/models/link.py | 604 ++ .../pip/_internal/models/scheme.py | 25 + .../pip/_internal/models/search_scope.py | 127 + .../pip/_internal/models/selection_prefs.py | 53 + .../pip/_internal/models/target_python.py | 121 + .../pip/_internal/models/wheel.py | 118 + .../pip/_internal/network/__init__.py | 2 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 269 bytes .../network/__pycache__/auth.cpython-38.pyc | Bin 0 -> 14508 bytes .../network/__pycache__/cache.cpython-38.pyc | Bin 0 -> 4695 bytes .../__pycache__/download.cpython-38.pyc | Bin 0 -> 5506 bytes .../__pycache__/lazy_wheel.cpython-38.pyc | Bin 0 -> 8382 bytes .../__pycache__/session.cpython-38.pyc | Bin 0 -> 12640 bytes .../network/__pycache__/utils.cpython-38.pyc | Bin 0 -> 1465 bytes .../network/__pycache__/xmlrpc.cpython-38.pyc | Bin 0 -> 2129 bytes .../pip/_internal/network/auth.py | 566 ++ .../pip/_internal/network/cache.py | 118 + .../pip/_internal/network/download.py | 187 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 523 + .../pip/_internal/network/utils.py | 98 + .../pip/_internal/network/xmlrpc.py | 62 + .../pip/_internal/operations/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 217 bytes .../__pycache__/check.cpython-38.pyc | Bin 0 -> 4796 bytes .../__pycache__/freeze.cpython-38.pyc | Bin 0 -> 6321 bytes .../__pycache__/prepare.cpython-38.pyc | Bin 0 -> 15617 bytes .../_internal/operations/build/__init__.py | 0 .../build/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 223 bytes .../__pycache__/build_tracker.cpython-38.pyc | Bin 0 -> 4820 bytes .../build/__pycache__/metadata.cpython-38.pyc | Bin 0 -> 1402 bytes .../metadata_editable.cpython-38.pyc | Bin 0 -> 1466 bytes .../metadata_legacy.cpython-38.pyc | Bin 0 -> 2298 bytes .../build/__pycache__/wheel.cpython-38.pyc | Bin 0 -> 1222 bytes .../__pycache__/wheel_editable.cpython-38.pyc | Bin 0 -> 1438 bytes .../__pycache__/wheel_legacy.cpython-38.pyc | Bin 0 -> 2695 bytes .../operations/build/build_tracker.py | 138 + .../_internal/operations/build/metadata.py | 39 + .../operations/build/metadata_editable.py | 42 + .../operations/build/metadata_legacy.py | 74 + .../pip/_internal/operations/build/wheel.py | 37 + .../operations/build/wheel_editable.py | 46 + .../operations/build/wheel_legacy.py | 102 + .../pip/_internal/operations/check.py | 181 + .../pip/_internal/operations/freeze.py | 256 + .../_internal/operations/install/__init__.py | 2 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 281 bytes .../editable_legacy.cpython-38.pyc | Bin 0 -> 1408 bytes .../install/__pycache__/wheel.cpython-38.pyc | Bin 0 -> 21146 bytes .../operations/install/editable_legacy.py | 47 + .../pip/_internal/operations/install/wheel.py | 741 ++ .../pip/_internal/operations/prepare.py | 732 ++ .../site-packages/pip/_internal/pyproject.py | 185 + .../pip/_internal/req/__init__.py | 90 + .../req/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2290 bytes .../__pycache__/constructors.cpython-38.pyc | Bin 0 -> 13979 bytes .../req/__pycache__/req_file.cpython-38.pyc | Bin 0 -> 15478 bytes .../__pycache__/req_install.cpython-38.pyc | Bin 0 -> 24862 bytes .../req/__pycache__/req_set.cpython-38.pyc | Bin 0 -> 4002 bytes .../__pycache__/req_uninstall.cpython-38.pyc | Bin 0 -> 18593 bytes .../pip/_internal/req/constructors.py | 560 ++ .../pip/_internal/req/req_file.py | 623 ++ .../pip/_internal/req/req_install.py | 934 ++ .../pip/_internal/req/req_set.py | 82 + .../pip/_internal/req/req_uninstall.py | 633 ++ .../pip/_internal/resolution/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 217 bytes .../__pycache__/base.cpython-38.pyc | Bin 0 -> 1076 bytes .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 224 bytes .../__pycache__/resolver.cpython-38.pyc | Bin 0 -> 14958 bytes .../_internal/resolution/legacy/resolver.py | 597 ++ .../resolution/resolvelib/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 228 bytes .../__pycache__/base.cpython-38.pyc | Bin 0 -> 6295 bytes .../__pycache__/candidates.cpython-38.pyc | Bin 0 -> 19702 bytes .../__pycache__/factory.cpython-38.pyc | Bin 0 -> 21651 bytes .../found_candidates.cpython-38.pyc | Bin 0 -> 5224 bytes .../__pycache__/provider.cpython-38.pyc | Bin 0 -> 7995 bytes .../__pycache__/reporter.cpython-38.pyc | Bin 0 -> 3881 bytes .../__pycache__/requirements.cpython-38.pyc | Bin 0 -> 10830 bytes .../__pycache__/resolver.cpython-38.pyc | Bin 0 -> 8741 bytes .../_internal/resolution/resolvelib/base.py | 139 + .../resolution/resolvelib/candidates.py | 574 ++ .../resolution/resolvelib/factory.py | 823 ++ .../resolution/resolvelib/found_candidates.py | 174 + .../resolution/resolvelib/provider.py | 258 + .../resolution/resolvelib/reporter.py | 81 + .../resolution/resolvelib/requirements.py | 245 + .../resolution/resolvelib/resolver.py | 317 + .../pip/_internal/self_outdated_check.py | 252 + .../pip/_internal/utils/__init__.py | 0 .../utils/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 212 bytes .../__pycache__/_jaraco_text.cpython-38.pyc | Bin 0 -> 3851 bytes .../utils/__pycache__/_log.cpython-38.pyc | Bin 0 -> 1531 bytes .../utils/__pycache__/appdirs.cpython-38.pyc | Bin 0 -> 1643 bytes .../utils/__pycache__/compat.cpython-38.pyc | Bin 0 -> 1975 bytes .../compatibility_tags.cpython-38.pyc | Bin 0 -> 4602 bytes .../utils/__pycache__/datetime.cpython-38.pyc | Bin 0 -> 531 bytes .../__pycache__/deprecation.cpython-38.pyc | Bin 0 -> 3271 bytes .../direct_url_helpers.cpython-38.pyc | Bin 0 -> 2123 bytes .../utils/__pycache__/egg_link.cpython-38.pyc | Bin 0 -> 2419 bytes .../__pycache__/entrypoints.cpython-38.pyc | Bin 0 -> 2719 bytes .../__pycache__/filesystem.cpython-38.pyc | Bin 0 -> 4389 bytes .../__pycache__/filetypes.cpython-38.pyc | Bin 0 -> 962 bytes .../utils/__pycache__/glibc.cpython-38.pyc | Bin 0 -> 1780 bytes .../utils/__pycache__/hashes.cpython-38.pyc | Bin 0 -> 5629 bytes .../utils/__pycache__/logging.cpython-38.pyc | Bin 0 -> 9957 bytes .../utils/__pycache__/misc.cpython-38.pyc | Bin 0 -> 23033 bytes .../__pycache__/packaging.cpython-38.pyc | Bin 0 -> 2121 bytes .../utils/__pycache__/retry.cpython-38.pyc | Bin 0 -> 1624 bytes .../setuptools_build.cpython-38.pyc | Bin 0 -> 3843 bytes .../__pycache__/subprocess.cpython-38.pyc | Bin 0 -> 5658 bytes .../utils/__pycache__/temp_dir.cpython-38.pyc | Bin 0 -> 8278 bytes .../__pycache__/unpacking.cpython-38.pyc | Bin 0 -> 8057 bytes .../utils/__pycache__/urls.cpython-38.pyc | Bin 0 -> 1412 bytes .../__pycache__/virtualenv.cpython-38.pyc | Bin 0 -> 3296 bytes .../utils/__pycache__/wheel.cpython-38.pyc | Bin 0 -> 4482 bytes .../pip/_internal/utils/_jaraco_text.py | 109 + .../site-packages/pip/_internal/utils/_log.py | 38 + .../pip/_internal/utils/appdirs.py | 52 + .../pip/_internal/utils/compat.py | 79 + .../pip/_internal/utils/compatibility_tags.py | 188 + .../pip/_internal/utils/datetime.py | 11 + .../pip/_internal/utils/deprecation.py | 124 + .../pip/_internal/utils/direct_url_helpers.py | 87 + .../pip/_internal/utils/egg_link.py | 80 + .../pip/_internal/utils/entrypoints.py | 84 + .../pip/_internal/utils/filesystem.py | 149 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 101 + .../pip/_internal/utils/hashes.py | 147 + .../pip/_internal/utils/logging.py | 354 + .../site-packages/pip/_internal/utils/misc.py | 773 ++ .../pip/_internal/utils/packaging.py | 58 + .../pip/_internal/utils/retry.py | 42 + .../pip/_internal/utils/setuptools_build.py | 146 + .../pip/_internal/utils/subprocess.py | 245 + .../pip/_internal/utils/temp_dir.py | 296 + .../pip/_internal/utils/unpacking.py | 337 + .../site-packages/pip/_internal/utils/urls.py | 55 + .../pip/_internal/utils/virtualenv.py | 104 + .../pip/_internal/utils/wheel.py | 134 + .../pip/_internal/vcs/__init__.py | 15 + .../vcs/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 535 bytes .../vcs/__pycache__/bazaar.cpython-38.pyc | Bin 0 -> 3560 bytes .../vcs/__pycache__/git.cpython-38.pyc | Bin 0 -> 12537 bytes .../vcs/__pycache__/mercurial.cpython-38.pyc | Bin 0 -> 5094 bytes .../vcs/__pycache__/subversion.cpython-38.pyc | Bin 0 -> 8540 bytes .../__pycache__/versioncontrol.cpython-38.pyc | Bin 0 -> 21084 bytes .../site-packages/pip/_internal/vcs/bazaar.py | 112 + .../site-packages/pip/_internal/vcs/git.py | 527 + .../pip/_internal/vcs/mercurial.py | 163 + .../pip/_internal/vcs/subversion.py | 324 + .../pip/_internal/vcs/versioncontrol.py | 688 ++ .../pip/_internal/wheel_builder.py | 354 + .../site-packages/pip/_vendor/__init__.py | 116 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 3073 bytes .../typing_extensions.cpython-38.pyc | Bin 0 -> 102091 bytes .../pip/_vendor/cachecontrol/__init__.py | 29 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 801 bytes .../__pycache__/_cmd.cpython-38.pyc | Bin 0 -> 1832 bytes .../__pycache__/adapter.cpython-38.pyc | Bin 0 -> 4266 bytes .../__pycache__/cache.cpython-38.pyc | Bin 0 -> 3170 bytes .../__pycache__/controller.cpython-38.pyc | Bin 0 -> 10045 bytes .../__pycache__/filewrapper.cpython-38.pyc | Bin 0 -> 3169 bytes .../__pycache__/heuristics.cpython-38.pyc | Bin 0 -> 5328 bytes .../__pycache__/serialize.cpython-38.pyc | Bin 0 -> 3305 bytes .../__pycache__/wrapper.cpython-38.pyc | Bin 0 -> 1403 bytes .../pip/_vendor/cachecontrol/_cmd.py | 70 + .../pip/_vendor/cachecontrol/adapter.py | 161 + .../pip/_vendor/cachecontrol/cache.py | 75 + .../_vendor/cachecontrol/caches/__init__.py | 8 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 446 bytes .../__pycache__/file_cache.cpython-38.pyc | Bin 0 -> 5363 bytes .../__pycache__/redis_cache.cpython-38.pyc | Bin 0 -> 1993 bytes .../_vendor/cachecontrol/caches/file_cache.py | 182 + .../cachecontrol/caches/redis_cache.py | 48 + .../pip/_vendor/cachecontrol/controller.py | 500 + .../pip/_vendor/cachecontrol/filewrapper.py | 119 + .../pip/_vendor/cachecontrol/heuristics.py | 157 + .../pip/_vendor/cachecontrol/py.typed | 0 .../pip/_vendor/cachecontrol/serialize.py | 146 + .../pip/_vendor/cachecontrol/wrapper.py | 43 + .../pip/_vendor/certifi/__init__.py | 4 + .../pip/_vendor/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 325 bytes .../__pycache__/__main__.cpython-38.pyc | Bin 0 -> 473 bytes .../certifi/__pycache__/core.cpython-38.pyc | Bin 0 -> 2151 bytes .../pip/_vendor/certifi/cacert.pem | 4929 ++++++++++ .../site-packages/pip/_vendor/certifi/core.py | 114 + .../pip/_vendor/certifi/py.typed | 0 .../pip/_vendor/distlib/__init__.py | 33 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1085 bytes .../distlib/__pycache__/compat.cpython-38.pyc | Bin 0 -> 31819 bytes .../__pycache__/database.cpython-38.pyc | Bin 0 -> 42580 bytes .../distlib/__pycache__/index.cpython-38.pyc | Bin 0 -> 17204 bytes .../__pycache__/locators.cpython-38.pyc | Bin 0 -> 38043 bytes .../__pycache__/manifest.cpython-38.pyc | Bin 0 -> 10251 bytes .../__pycache__/markers.cpython-38.pyc | Bin 0 -> 5354 bytes .../__pycache__/metadata.cpython-38.pyc | Bin 0 -> 26980 bytes .../__pycache__/resources.cpython-38.pyc | Bin 0 -> 11017 bytes .../__pycache__/scripts.cpython-38.pyc | Bin 0 -> 11654 bytes .../distlib/__pycache__/util.cpython-38.pyc | Bin 0 -> 52011 bytes .../__pycache__/version.cpython-38.pyc | Bin 0 -> 20572 bytes .../distlib/__pycache__/wheel.cpython-38.pyc | Bin 0 -> 27712 bytes .../pip/_vendor/distlib/compat.py | 1137 +++ .../pip/_vendor/distlib/database.py | 1329 +++ .../pip/_vendor/distlib/index.py | 508 + .../pip/_vendor/distlib/locators.py | 1295 +++ .../pip/_vendor/distlib/manifest.py | 384 + .../pip/_vendor/distlib/markers.py | 162 + .../pip/_vendor/distlib/metadata.py | 1031 ++ .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 447 + .../site-packages/pip/_vendor/distlib/t32.exe | Bin 0 -> 97792 bytes .../pip/_vendor/distlib/t64-arm.exe | Bin 0 -> 182784 bytes .../site-packages/pip/_vendor/distlib/t64.exe | Bin 0 -> 108032 bytes .../site-packages/pip/_vendor/distlib/util.py | 1984 ++++ .../pip/_vendor/distlib/version.py | 750 ++ .../site-packages/pip/_vendor/distlib/w32.exe | Bin 0 -> 91648 bytes .../pip/_vendor/distlib/w64-arm.exe | Bin 0 -> 168448 bytes .../site-packages/pip/_vendor/distlib/w64.exe | Bin 0 -> 101888 bytes .../pip/_vendor/distlib/wheel.py | 1100 +++ .../pip/_vendor/distro/__init__.py | 54 + .../pip/_vendor/distro/__main__.py | 4 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1022 bytes .../__pycache__/__main__.cpython-38.pyc | Bin 0 -> 284 bytes .../distro/__pycache__/distro.cpython-38.pyc | Bin 0 -> 42458 bytes .../pip/_vendor/distro/distro.py | 1403 +++ .../site-packages/pip/_vendor/distro/py.typed | 0 .../pip/_vendor/idna/__init__.py | 45 + .../idna/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 947 bytes .../idna/__pycache__/codec.cpython-38.pyc | Bin 0 -> 3336 bytes .../idna/__pycache__/compat.cpython-38.pyc | Bin 0 -> 795 bytes .../idna/__pycache__/core.cpython-38.pyc | Bin 0 -> 9914 bytes .../idna/__pycache__/idnadata.cpython-38.pyc | Bin 0 -> 42113 bytes .../idna/__pycache__/intranges.cpython-38.pyc | Bin 0 -> 2018 bytes .../__pycache__/package_data.cpython-38.pyc | Bin 0 -> 233 bytes .../idna/__pycache__/uts46data.cpython-38.pyc | Bin 0 -> 185417 bytes .../site-packages/pip/_vendor/idna/codec.py | 122 + .../site-packages/pip/_vendor/idna/compat.py | 15 + .../site-packages/pip/_vendor/idna/core.py | 437 + .../pip/_vendor/idna/idnadata.py | 4243 ++++++++ .../pip/_vendor/idna/intranges.py | 57 + .../pip/_vendor/idna/package_data.py | 1 + .../site-packages/pip/_vendor/idna/py.typed | 0 .../pip/_vendor/idna/uts46data.py | 8681 +++++++++++++++++ .../pip/_vendor/msgpack/__init__.py | 55 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1395 bytes .../__pycache__/exceptions.cpython-38.pyc | Bin 0 -> 1873 bytes .../msgpack/__pycache__/ext.cpython-38.pyc | Bin 0 -> 6082 bytes .../__pycache__/fallback.cpython-38.pyc | Bin 0 -> 24003 bytes .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 170 + .../pip/_vendor/msgpack/fallback.py | 929 ++ .../pip/_vendor/packaging/__init__.py | 15 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 544 bytes .../__pycache__/_elffile.cpython-38.pyc | Bin 0 -> 3435 bytes .../__pycache__/_manylinux.cpython-38.pyc | Bin 0 -> 6488 bytes .../__pycache__/_musllinux.cpython-38.pyc | Bin 0 -> 3416 bytes .../__pycache__/_parser.cpython-38.pyc | Bin 0 -> 9061 bytes .../__pycache__/_structures.cpython-38.pyc | Bin 0 -> 2824 bytes .../__pycache__/_tokenizer.cpython-38.pyc | Bin 0 -> 5704 bytes .../__pycache__/markers.cpython-38.pyc | Bin 0 -> 7793 bytes .../__pycache__/metadata.cpython-38.pyc | Bin 0 -> 18416 bytes .../__pycache__/requirements.cpython-38.pyc | Bin 0 -> 2885 bytes .../__pycache__/specifiers.cpython-38.pyc | Bin 0 -> 31282 bytes .../packaging/__pycache__/tags.cpython-38.pyc | Bin 0 -> 14964 bytes .../__pycache__/utils.cpython-38.pyc | Bin 0 -> 4628 bytes .../__pycache__/version.cpython-38.pyc | Bin 0 -> 15149 bytes .../pip/_vendor/packaging/_elffile.py | 110 + .../pip/_vendor/packaging/_manylinux.py | 263 + .../pip/_vendor/packaging/_musllinux.py | 85 + .../pip/_vendor/packaging/_parser.py | 354 + .../pip/_vendor/packaging/_structures.py | 61 + .../pip/_vendor/packaging/_tokenizer.py | 194 + .../_vendor/packaging/licenses/__init__.py | 145 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2638 bytes .../licenses/__pycache__/_spdx.cpython-38.pyc | Bin 0 -> 29649 bytes .../pip/_vendor/packaging/licenses/_spdx.py | 759 ++ .../pip/_vendor/packaging/markers.py | 331 + .../pip/_vendor/packaging/metadata.py | 863 ++ .../pip/_vendor/packaging/py.typed | 0 .../pip/_vendor/packaging/requirements.py | 91 + .../pip/_vendor/packaging/specifiers.py | 1020 ++ .../pip/_vendor/packaging/tags.py | 617 ++ .../pip/_vendor/packaging/utils.py | 163 + .../pip/_vendor/packaging/version.py | 582 ++ .../pip/_vendor/pkg_resources/__init__.py | 3676 +++++++ .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 112459 bytes .../pip/_vendor/platformdirs/__init__.py | 631 ++ .../pip/_vendor/platformdirs/__main__.py | 55 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 16525 bytes .../__pycache__/__main__.cpython-38.pyc | Bin 0 -> 1410 bytes .../__pycache__/android.cpython-38.pyc | Bin 0 -> 8007 bytes .../__pycache__/api.cpython-38.pyc | Bin 0 -> 10646 bytes .../__pycache__/macos.cpython-38.pyc | Bin 0 -> 6850 bytes .../__pycache__/unix.cpython-38.pyc | Bin 0 -> 10870 bytes .../__pycache__/version.cpython-38.pyc | Bin 0 -> 541 bytes .../__pycache__/windows.cpython-38.pyc | Bin 0 -> 9272 bytes .../pip/_vendor/platformdirs/android.py | 249 + .../pip/_vendor/platformdirs/api.py | 298 + .../pip/_vendor/platformdirs/macos.py | 144 + .../pip/_vendor/platformdirs/py.typed | 0 .../pip/_vendor/platformdirs/unix.py | 269 + .../pip/_vendor/platformdirs/version.py | 16 + .../pip/_vendor/platformdirs/windows.py | 272 + .../pip/_vendor/pygments/__init__.py | 82 + .../pip/_vendor/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2959 bytes .../__pycache__/__main__.cpython-38.pyc | Bin 0 -> 609 bytes .../__pycache__/cmdline.cpython-38.pyc | Bin 0 -> 15450 bytes .../__pycache__/console.cpython-38.pyc | Bin 0 -> 1942 bytes .../__pycache__/filter.cpython-38.pyc | Bin 0 -> 2657 bytes .../__pycache__/formatter.cpython-38.pyc | Bin 0 -> 4120 bytes .../pygments/__pycache__/lexer.cpython-38.pyc | Bin 0 -> 26777 bytes .../__pycache__/modeline.cpython-38.pyc | Bin 0 -> 1208 bytes .../__pycache__/plugin.cpython-38.pyc | Bin 0 -> 2038 bytes .../__pycache__/regexopt.cpython-38.pyc | Bin 0 -> 2973 bytes .../__pycache__/scanner.cpython-38.pyc | Bin 0 -> 3575 bytes .../__pycache__/sphinxext.cpython-38.pyc | Bin 0 -> 7822 bytes .../pygments/__pycache__/style.cpython-38.pyc | Bin 0 -> 4536 bytes .../pygments/__pycache__/token.cpython-38.pyc | Bin 0 -> 4583 bytes .../__pycache__/unistring.cpython-38.pyc | Bin 0 -> 31281 bytes .../pygments/__pycache__/util.cpython-38.pyc | Bin 0 -> 10138 bytes .../pip/_vendor/pygments/cmdline.py | 668 ++ .../pip/_vendor/pygments/console.py | 70 + .../pip/_vendor/pygments/filter.py | 70 + .../pip/_vendor/pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 23496 bytes .../pip/_vendor/pygments/formatter.py | 129 + .../_vendor/pygments/formatters/__init__.py | 157 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 4983 bytes .../__pycache__/_mapping.cpython-38.pyc | Bin 0 -> 3915 bytes .../__pycache__/bbcode.cpython-38.pyc | Bin 0 -> 3092 bytes .../__pycache__/groff.cpython-38.pyc | Bin 0 -> 4414 bytes .../__pycache__/html.cpython-38.pyc | Bin 0 -> 29387 bytes .../formatters/__pycache__/img.cpython-38.pyc | Bin 0 -> 18411 bytes .../formatters/__pycache__/irc.cpython-38.pyc | Bin 0 -> 3989 bytes .../__pycache__/latex.cpython-38.pyc | Bin 0 -> 13933 bytes .../__pycache__/other.cpython-38.pyc | Bin 0 -> 4795 bytes .../__pycache__/pangomarkup.cpython-38.pyc | Bin 0 -> 2121 bytes .../formatters/__pycache__/rtf.cpython-38.pyc | Bin 0 -> 8875 bytes .../formatters/__pycache__/svg.cpython-38.pyc | Bin 0 -> 6348 bytes .../__pycache__/terminal.cpython-38.pyc | Bin 0 -> 3976 bytes .../__pycache__/terminal256.cpython-38.pyc | Bin 0 -> 9238 bytes .../_vendor/pygments/formatters/_mapping.py | 23 + .../pip/_vendor/pygments/formatters/bbcode.py | 108 + .../pip/_vendor/pygments/formatters/groff.py | 170 + .../pip/_vendor/pygments/formatters/html.py | 987 ++ .../pip/_vendor/pygments/formatters/img.py | 685 ++ .../pip/_vendor/pygments/formatters/irc.py | 154 + .../pip/_vendor/pygments/formatters/latex.py | 518 + .../pip/_vendor/pygments/formatters/other.py | 160 + .../pygments/formatters/pangomarkup.py | 83 + .../pip/_vendor/pygments/formatters/rtf.py | 349 + .../pip/_vendor/pygments/formatters/svg.py | 185 + .../_vendor/pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../pip/_vendor/pygments/lexer.py | 963 ++ .../pip/_vendor/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 9950 bytes .../__pycache__/_mapping.cpython-38.pyc | Bin 0 -> 55992 bytes .../lexers/__pycache__/python.cpython-38.pyc | Bin 0 -> 31564 bytes .../pip/_vendor/pygments/lexers/_mapping.py | 589 ++ .../pip/_vendor/pygments/lexers/python.py | 1198 +++ .../pip/_vendor/pygments/modeline.py | 43 + .../pip/_vendor/pygments/plugin.py | 72 + .../pip/_vendor/pygments/regexopt.py | 91 + .../pip/_vendor/pygments/scanner.py | 104 + .../pip/_vendor/pygments/sphinxext.py | 247 + .../pip/_vendor/pygments/style.py | 203 + .../pip/_vendor/pygments/styles/__init__.py | 61 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2095 bytes .../__pycache__/_mapping.cpython-38.pyc | Bin 0 -> 3119 bytes .../pip/_vendor/pygments/styles/_mapping.py | 54 + .../pip/_vendor/pygments/token.py | 214 + .../pip/_vendor/pygments/unistring.py | 153 + .../pip/_vendor/pygments/util.py | 324 + .../pip/_vendor/pyproject_hooks/__init__.py | 31 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 737 bytes .../__pycache__/_impl.cpython-38.pyc | Bin 0 -> 14033 bytes .../pip/_vendor/pyproject_hooks/_impl.py | 410 + .../pyproject_hooks/_in_process/__init__.py | 21 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 843 bytes .../__pycache__/_in_process.cpython-38.pyc | Bin 0 -> 10590 bytes .../_in_process/_in_process.py | 389 + .../pip/_vendor/pyproject_hooks/py.typed | 0 .../pip/_vendor/requests/__init__.py | 179 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 3894 bytes .../__pycache__/__version__.cpython-38.pyc | Bin 0 -> 571 bytes .../_internal_utils.cpython-38.pyc | Bin 0 -> 1668 bytes .../__pycache__/adapters.cpython-38.pyc | Bin 0 -> 22152 bytes .../requests/__pycache__/api.cpython-38.pyc | Bin 0 -> 6788 bytes .../requests/__pycache__/auth.cpython-38.pyc | Bin 0 -> 8369 bytes .../requests/__pycache__/certs.cpython-38.pyc | Bin 0 -> 654 bytes .../__pycache__/compat.cpython-38.pyc | Bin 0 -> 1539 bytes .../__pycache__/cookies.cpython-38.pyc | Bin 0 -> 18875 bytes .../__pycache__/exceptions.cpython-38.pyc | Bin 0 -> 6640 bytes .../requests/__pycache__/help.cpython-38.pyc | Bin 0 -> 2835 bytes .../requests/__pycache__/hooks.cpython-38.pyc | Bin 0 -> 1013 bytes .../__pycache__/models.cpython-38.pyc | Bin 0 -> 24399 bytes .../__pycache__/packages.cpython-38.pyc | Bin 0 -> 747 bytes .../__pycache__/sessions.cpython-38.pyc | Bin 0 -> 19858 bytes .../__pycache__/status_codes.cpython-38.pyc | Bin 0 -> 4335 bytes .../__pycache__/structures.cpython-38.pyc | Bin 0 -> 4472 bytes .../requests/__pycache__/utils.cpython-38.pyc | Bin 0 -> 24501 bytes .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 50 + .../pip/_vendor/requests/adapters.py | 719 ++ .../site-packages/pip/_vendor/requests/api.py | 157 + .../pip/_vendor/requests/auth.py | 314 + .../pip/_vendor/requests/certs.py | 17 + .../pip/_vendor/requests/compat.py | 78 + .../pip/_vendor/requests/cookies.py | 561 ++ .../pip/_vendor/requests/exceptions.py | 151 + .../pip/_vendor/requests/help.py | 127 + .../pip/_vendor/requests/hooks.py | 33 + .../pip/_vendor/requests/models.py | 1037 ++ .../pip/_vendor/requests/packages.py | 25 + .../pip/_vendor/requests/sessions.py | 831 ++ .../pip/_vendor/requests/status_codes.py | 128 + .../pip/_vendor/requests/structures.py | 99 + .../pip/_vendor/requests/utils.py | 1096 +++ .../pip/_vendor/resolvelib/__init__.py | 26 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 661 bytes .../__pycache__/providers.cpython-38.pyc | Bin 0 -> 6725 bytes .../__pycache__/reporters.cpython-38.pyc | Bin 0 -> 2670 bytes .../__pycache__/resolvers.cpython-38.pyc | Bin 0 -> 17718 bytes .../__pycache__/structs.cpython-38.pyc | Bin 0 -> 7418 bytes .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 222 bytes .../collections_abc.cpython-38.pyc | Bin 0 -> 398 bytes .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/py.typed | 0 .../pip/_vendor/resolvelib/reporters.py | 43 + .../pip/_vendor/resolvelib/resolvers.py | 547 ++ .../pip/_vendor/resolvelib/structs.py | 170 + .../pip/_vendor/rich/__init__.py | 177 + .../pip/_vendor/rich/__main__.py | 273 + .../rich/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 6017 bytes .../rich/__pycache__/__main__.cpython-38.pyc | Bin 0 -> 7174 bytes .../__pycache__/_cell_widths.cpython-38.pyc | Bin 0 -> 10078 bytes .../__pycache__/_emoji_codes.cpython-38.pyc | Bin 0 -> 132722 bytes .../__pycache__/_emoji_replace.cpython-38.pyc | Bin 0 -> 1211 bytes .../__pycache__/_export_format.cpython-38.pyc | Bin 0 -> 2346 bytes .../__pycache__/_extension.cpython-38.pyc | Bin 0 -> 516 bytes .../rich/__pycache__/_fileno.cpython-38.pyc | Bin 0 -> 795 bytes .../rich/__pycache__/_inspect.cpython-38.pyc | Bin 0 -> 8686 bytes .../__pycache__/_log_render.cpython-38.pyc | Bin 0 -> 2601 bytes .../rich/__pycache__/_loop.cpython-38.pyc | Bin 0 -> 1295 bytes .../__pycache__/_null_file.cpython-38.pyc | Bin 0 -> 3406 bytes .../rich/__pycache__/_palettes.cpython-38.pyc | Bin 0 -> 6244 bytes .../rich/__pycache__/_pick.cpython-38.pyc | Bin 0 -> 675 bytes .../rich/__pycache__/_ratio.cpython-38.pyc | Bin 0 -> 5191 bytes .../rich/__pycache__/_spinners.cpython-38.pyc | Bin 0 -> 12451 bytes .../rich/__pycache__/_stack.cpython-38.pyc | Bin 0 -> 864 bytes .../rich/__pycache__/_timer.cpython-38.pyc | Bin 0 -> 711 bytes .../__pycache__/_win32_console.cpython-38.pyc | Bin 0 -> 19237 bytes .../rich/__pycache__/_windows.cpython-38.pyc | Bin 0 -> 1800 bytes .../_windows_renderer.cpython-38.pyc | Bin 0 -> 2076 bytes .../rich/__pycache__/_wrap.cpython-38.pyc | Bin 0 -> 2434 bytes .../rich/__pycache__/abc.cpython-38.pyc | Bin 0 -> 1325 bytes .../rich/__pycache__/align.cpython-38.pyc | Bin 0 -> 7985 bytes .../rich/__pycache__/ansi.cpython-38.pyc | Bin 0 -> 5632 bytes .../rich/__pycache__/bar.cpython-38.pyc | Bin 0 -> 2957 bytes .../rich/__pycache__/box.cpython-38.pyc | Bin 0 -> 8520 bytes .../rich/__pycache__/cells.cpython-38.pyc | Bin 0 -> 4238 bytes .../rich/__pycache__/color.cpython-38.pyc | Bin 0 -> 15515 bytes .../__pycache__/color_triplet.cpython-38.pyc | Bin 0 -> 1446 bytes .../rich/__pycache__/columns.cpython-38.pyc | Bin 0 -> 6160 bytes .../rich/__pycache__/console.cpython-38.pyc | Bin 0 -> 82723 bytes .../rich/__pycache__/constrain.cpython-38.pyc | Bin 0 -> 1688 bytes .../__pycache__/containers.cpython-38.pyc | Bin 0 -> 6472 bytes .../rich/__pycache__/control.cpython-38.pyc | Bin 0 -> 8236 bytes .../__pycache__/default_styles.cpython-38.pyc | Bin 0 -> 5407 bytes .../rich/__pycache__/diagnose.cpython-38.pyc | Bin 0 -> 1238 bytes .../rich/__pycache__/emoji.cpython-38.pyc | Bin 0 -> 3235 bytes .../rich/__pycache__/errors.cpython-38.pyc | Bin 0 -> 1690 bytes .../__pycache__/file_proxy.cpython-38.pyc | Bin 0 -> 2388 bytes .../rich/__pycache__/filesize.cpython-38.pyc | Bin 0 -> 2635 bytes .../__pycache__/highlighter.cpython-38.pyc | Bin 0 -> 8090 bytes .../rich/__pycache__/json.cpython-38.pyc | Bin 0 -> 4706 bytes .../rich/__pycache__/jupyter.cpython-38.pyc | Bin 0 -> 4088 bytes .../rich/__pycache__/layout.cpython-38.pyc | Bin 0 -> 14648 bytes .../rich/__pycache__/live.cpython-38.pyc | Bin 0 -> 11101 bytes .../__pycache__/live_render.cpython-38.pyc | Bin 0 -> 3409 bytes .../rich/__pycache__/logging.cpython-38.pyc | Bin 0 -> 10316 bytes .../rich/__pycache__/markup.cpython-38.pyc | Bin 0 -> 6180 bytes .../rich/__pycache__/measure.cpython-38.pyc | Bin 0 -> 5030 bytes .../rich/__pycache__/padding.cpython-38.pyc | Bin 0 -> 4380 bytes .../rich/__pycache__/pager.cpython-38.pyc | Bin 0 -> 1454 bytes .../rich/__pycache__/palette.cpython-38.pyc | Bin 0 -> 3711 bytes .../rich/__pycache__/panel.cpython-38.pyc | Bin 0 -> 7968 bytes .../rich/__pycache__/pretty.cpython-38.pyc | Bin 0 -> 27794 bytes .../rich/__pycache__/progress.cpython-38.pyc | Bin 0 -> 53756 bytes .../__pycache__/progress_bar.cpython-38.pyc | Bin 0 -> 6862 bytes .../rich/__pycache__/prompt.cpython-38.pyc | Bin 0 -> 12310 bytes .../rich/__pycache__/protocol.cpython-38.pyc | Bin 0 -> 1360 bytes .../rich/__pycache__/region.cpython-38.pyc | Bin 0 -> 546 bytes .../rich/__pycache__/repr.cpython-38.pyc | Bin 0 -> 4116 bytes .../rich/__pycache__/rule.cpython-38.pyc | Bin 0 -> 3933 bytes .../rich/__pycache__/scope.cpython-38.pyc | Bin 0 -> 2995 bytes .../rich/__pycache__/screen.cpython-38.pyc | Bin 0 -> 1851 bytes .../rich/__pycache__/segment.cpython-38.pyc | Bin 0 -> 21045 bytes .../rich/__pycache__/spinner.cpython-38.pyc | Bin 0 -> 4364 bytes .../rich/__pycache__/status.cpython-38.pyc | Bin 0 -> 4584 bytes .../rich/__pycache__/style.cpython-38.pyc | Bin 0 -> 21441 bytes .../rich/__pycache__/styled.cpython-38.pyc | Bin 0 -> 1713 bytes .../rich/__pycache__/syntax.cpython-38.pyc | Bin 0 -> 26122 bytes .../rich/__pycache__/table.cpython-38.pyc | Bin 0 -> 29950 bytes .../__pycache__/terminal_theme.cpython-38.pyc | Bin 0 -> 3217 bytes .../rich/__pycache__/text.cpython-38.pyc | Bin 0 -> 42127 bytes .../rich/__pycache__/theme.cpython-38.pyc | Bin 0 -> 4831 bytes .../rich/__pycache__/themes.cpython-38.pyc | Bin 0 -> 314 bytes .../rich/__pycache__/traceback.cpython-38.pyc | Bin 0 -> 22477 bytes .../rich/__pycache__/tree.cpython-38.pyc | Bin 0 -> 7577 bytes .../pip/_vendor/rich/_cell_widths.py | 454 + .../pip/_vendor/rich/_emoji_codes.py | 3610 +++++++ .../pip/_vendor/rich/_emoji_replace.py | 32 + .../pip/_vendor/rich/_export_format.py | 76 + .../pip/_vendor/rich/_extension.py | 10 + .../site-packages/pip/_vendor/rich/_fileno.py | 24 + .../pip/_vendor/rich/_inspect.py | 268 + .../pip/_vendor/rich/_log_render.py | 94 + .../site-packages/pip/_vendor/rich/_loop.py | 43 + .../pip/_vendor/rich/_null_file.py | 69 + .../pip/_vendor/rich/_palettes.py | 309 + .../site-packages/pip/_vendor/rich/_pick.py | 17 + .../site-packages/pip/_vendor/rich/_ratio.py | 159 + .../pip/_vendor/rich/_spinners.py | 482 + .../site-packages/pip/_vendor/rich/_stack.py | 16 + .../site-packages/pip/_vendor/rich/_timer.py | 19 + .../pip/_vendor/rich/_win32_console.py | 661 ++ .../pip/_vendor/rich/_windows.py | 71 + .../pip/_vendor/rich/_windows_renderer.py | 56 + .../site-packages/pip/_vendor/rich/_wrap.py | 93 + .../site-packages/pip/_vendor/rich/abc.py | 33 + .../site-packages/pip/_vendor/rich/align.py | 312 + .../site-packages/pip/_vendor/rich/ansi.py | 241 + .../site-packages/pip/_vendor/rich/bar.py | 93 + .../site-packages/pip/_vendor/rich/box.py | 480 + .../site-packages/pip/_vendor/rich/cells.py | 174 + .../site-packages/pip/_vendor/rich/color.py | 621 ++ .../pip/_vendor/rich/color_triplet.py | 38 + .../site-packages/pip/_vendor/rich/columns.py | 187 + .../site-packages/pip/_vendor/rich/console.py | 2661 +++++ .../pip/_vendor/rich/constrain.py | 37 + .../pip/_vendor/rich/containers.py | 167 + .../site-packages/pip/_vendor/rich/control.py | 225 + .../pip/_vendor/rich/default_styles.py | 191 + .../pip/_vendor/rich/diagnose.py | 37 + .../site-packages/pip/_vendor/rich/emoji.py | 96 + .../site-packages/pip/_vendor/rich/errors.py | 34 + .../pip/_vendor/rich/file_proxy.py | 57 + .../pip/_vendor/rich/filesize.py | 88 + .../pip/_vendor/rich/highlighter.py | 232 + .../site-packages/pip/_vendor/rich/json.py | 139 + .../site-packages/pip/_vendor/rich/jupyter.py | 101 + .../site-packages/pip/_vendor/rich/layout.py | 442 + .../site-packages/pip/_vendor/rich/live.py | 375 + .../pip/_vendor/rich/live_render.py | 112 + .../site-packages/pip/_vendor/rich/logging.py | 297 + .../site-packages/pip/_vendor/rich/markup.py | 251 + .../site-packages/pip/_vendor/rich/measure.py | 151 + .../site-packages/pip/_vendor/rich/padding.py | 141 + .../site-packages/pip/_vendor/rich/pager.py | 34 + .../site-packages/pip/_vendor/rich/palette.py | 100 + .../site-packages/pip/_vendor/rich/panel.py | 318 + .../site-packages/pip/_vendor/rich/pretty.py | 1016 ++ .../pip/_vendor/rich/progress.py | 1715 ++++ .../pip/_vendor/rich/progress_bar.py | 223 + .../site-packages/pip/_vendor/rich/prompt.py | 400 + .../pip/_vendor/rich/protocol.py | 42 + .../site-packages/pip/_vendor/rich/py.typed | 0 .../site-packages/pip/_vendor/rich/region.py | 10 + .../site-packages/pip/_vendor/rich/repr.py | 149 + .../site-packages/pip/_vendor/rich/rule.py | 130 + .../site-packages/pip/_vendor/rich/scope.py | 86 + .../site-packages/pip/_vendor/rich/screen.py | 54 + .../site-packages/pip/_vendor/rich/segment.py | 752 ++ .../site-packages/pip/_vendor/rich/spinner.py | 138 + .../site-packages/pip/_vendor/rich/status.py | 131 + .../site-packages/pip/_vendor/rich/style.py | 796 ++ .../site-packages/pip/_vendor/rich/styled.py | 42 + .../site-packages/pip/_vendor/rich/syntax.py | 966 ++ .../site-packages/pip/_vendor/rich/table.py | 1007 ++ .../pip/_vendor/rich/terminal_theme.py | 153 + .../site-packages/pip/_vendor/rich/text.py | 1361 +++ .../site-packages/pip/_vendor/rich/theme.py | 115 + .../site-packages/pip/_vendor/rich/themes.py | 5 + .../pip/_vendor/rich/traceback.py | 797 ++ .../site-packages/pip/_vendor/rich/tree.py | 257 + .../pip/_vendor/tomli/__init__.py | 8 + .../tomli/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 346 bytes .../tomli/__pycache__/_parser.cpython-38.pyc | Bin 0 -> 18214 bytes .../tomli/__pycache__/_re.cpython-38.pyc | Bin 0 -> 2955 bytes .../tomli/__pycache__/_types.cpython-38.pyc | Bin 0 -> 346 bytes .../pip/_vendor/tomli/_parser.py | 770 ++ .../site-packages/pip/_vendor/tomli/_re.py | 112 + .../site-packages/pip/_vendor/tomli/_types.py | 10 + .../site-packages/pip/_vendor/tomli/py.typed | 1 + .../pip/_vendor/truststore/__init__.py | 36 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 986 bytes .../__pycache__/_api.cpython-38.pyc | Bin 0 -> 10721 bytes .../__pycache__/_macos.cpython-38.pyc | Bin 0 -> 9973 bytes .../__pycache__/_openssl.cpython-38.pyc | Bin 0 -> 1501 bytes .../__pycache__/_ssl_constants.cpython-38.pyc | Bin 0 -> 811 bytes .../__pycache__/_windows.cpython-38.pyc | Bin 0 -> 10493 bytes .../pip/_vendor/truststore/_api.py | 316 + .../pip/_vendor/truststore/_macos.py | 571 ++ .../pip/_vendor/truststore/_openssl.py | 66 + .../pip/_vendor/truststore/_ssl_constants.py | 31 + .../pip/_vendor/truststore/_windows.py | 567 ++ .../pip/_vendor/truststore/py.typed | 0 .../pip/_vendor/typing_extensions.py | 3641 +++++++ .../pip/_vendor/urllib3/__init__.py | 102 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2530 bytes .../__pycache__/_collections.cpython-38.pyc | Bin 0 -> 11236 bytes .../__pycache__/_version.cpython-38.pyc | Bin 0 -> 235 bytes .../__pycache__/connection.cpython-38.pyc | Bin 0 -> 13778 bytes .../__pycache__/connectionpool.cpython-38.pyc | Bin 0 -> 25952 bytes .../__pycache__/exceptions.cpython-38.pyc | Bin 0 -> 11667 bytes .../urllib3/__pycache__/fields.cpython-38.pyc | Bin 0 -> 8204 bytes .../__pycache__/filepost.cpython-38.pyc | Bin 0 -> 2791 bytes .../__pycache__/poolmanager.cpython-38.pyc | Bin 0 -> 15113 bytes .../__pycache__/request.cpython-38.pyc | Bin 0 -> 6408 bytes .../__pycache__/response.cpython-38.pyc | Bin 0 -> 22316 bytes .../pip/_vendor/urllib3/_collections.py | 355 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 572 ++ .../pip/_vendor/urllib3/connectionpool.py | 1140 +++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 220 bytes .../_appengine_environ.cpython-38.pyc | Bin 0 -> 1444 bytes .../__pycache__/appengine.cpython-38.pyc | Bin 0 -> 8294 bytes .../__pycache__/ntlmpool.cpython-38.pyc | Bin 0 -> 3652 bytes .../__pycache__/pyopenssl.cpython-38.pyc | Bin 0 -> 15903 bytes .../securetransport.cpython-38.pyc | Bin 0 -> 21678 bytes .../contrib/__pycache__/socks.cpython-38.pyc | Bin 0 -> 5658 bytes .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 237 bytes .../__pycache__/bindings.cpython-38.pyc | Bin 0 -> 10755 bytes .../__pycache__/low_level.cpython-38.pyc | Bin 0 -> 9129 bytes .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + .../pip/_vendor/urllib3/contrib/appengine.py | 314 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 130 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 518 + .../urllib3/contrib/securetransport.py | 920 ++ .../pip/_vendor/urllib3/contrib/socks.py | 216 + .../pip/_vendor/urllib3/exceptions.py | 323 + .../pip/_vendor/urllib3/fields.py | 274 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 221 bytes .../packages/__pycache__/six.cpython-38.pyc | Bin 0 -> 27616 bytes .../urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 231 bytes .../__pycache__/makefile.cpython-38.pyc | Bin 0 -> 1335 bytes .../weakref_finalize.cpython-38.pyc | Bin 0 -> 4883 bytes .../urllib3/packages/backports/makefile.py | 51 + .../packages/backports/weakref_finalize.py | 155 + .../pip/_vendor/urllib3/packages/six.py | 1076 ++ .../pip/_vendor/urllib3/poolmanager.py | 540 + .../pip/_vendor/urllib3/request.py | 191 + .../pip/_vendor/urllib3/response.py | 879 ++ .../pip/_vendor/urllib3/util/__init__.py | 49 + .../util/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1130 bytes .../__pycache__/connection.cpython-38.pyc | Bin 0 -> 3468 bytes .../util/__pycache__/proxy.cpython-38.pyc | Bin 0 -> 1374 bytes .../util/__pycache__/queue.cpython-38.pyc | Bin 0 -> 1085 bytes .../util/__pycache__/request.cpython-38.pyc | Bin 0 -> 3378 bytes .../util/__pycache__/response.cpython-38.pyc | Bin 0 -> 2378 bytes .../util/__pycache__/retry.cpython-38.pyc | Bin 0 -> 16335 bytes .../util/__pycache__/ssl_.cpython-38.pyc | Bin 0 -> 11657 bytes .../ssl_match_hostname.cpython-38.pyc | Bin 0 -> 3290 bytes .../__pycache__/ssltransport.cpython-38.pyc | Bin 0 -> 7464 bytes .../util/__pycache__/timeout.cpython-38.pyc | Bin 0 -> 9175 bytes .../util/__pycache__/url.cpython-38.pyc | Bin 0 -> 10830 bytes .../util/__pycache__/wait.cpython-38.pyc | Bin 0 -> 3132 bytes .../pip/_vendor/urllib3/util/connection.py | 149 + .../pip/_vendor/urllib3/util/proxy.py | 57 + .../pip/_vendor/urllib3/util/queue.py | 22 + .../pip/_vendor/urllib3/util/request.py | 137 + .../pip/_vendor/urllib3/util/response.py | 107 + .../pip/_vendor/urllib3/util/retry.py | 622 ++ .../pip/_vendor/urllib3/util/ssl_.py | 504 + .../urllib3/util/ssl_match_hostname.py | 159 + .../pip/_vendor/urllib3/util/ssltransport.py | 221 + .../pip/_vendor/urllib3/util/timeout.py | 271 + .../pip/_vendor/urllib3/util/url.py | 435 + .../pip/_vendor/urllib3/util/wait.py | 152 + .../site-packages/pip/_vendor/vendor.txt | 18 + venv/lib/python3.8/site-packages/pip/py.typed | 4 + .../site-packages/pkg_resources/__init__.py | 3288 +++++++ .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 100447 bytes .../pkg_resources/_vendor/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 214 bytes .../__pycache__/appdirs.cpython-38.pyc | Bin 0 -> 20567 bytes .../__pycache__/pyparsing.cpython-38.pyc | Bin 0 -> 201691 bytes .../pkg_resources/_vendor/appdirs.py | 608 ++ .../_vendor/packaging/__about__.py | 27 + .../_vendor/packaging/__init__.py | 26 + .../__pycache__/__about__.cpython-38.pyc | Bin 0 -> 756 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 602 bytes .../__pycache__/_compat.cpython-38.pyc | Bin 0 -> 1174 bytes .../__pycache__/_structures.cpython-38.pyc | Bin 0 -> 2928 bytes .../__pycache__/_typing.cpython-38.pyc | Bin 0 -> 1519 bytes .../__pycache__/markers.cpython-38.pyc | Bin 0 -> 9363 bytes .../__pycache__/requirements.cpython-38.pyc | Bin 0 -> 4116 bytes .../__pycache__/specifiers.cpython-38.pyc | Bin 0 -> 20637 bytes .../packaging/__pycache__/tags.cpython-38.pyc | Bin 0 -> 17289 bytes .../__pycache__/utils.cpython-38.pyc | Bin 0 -> 1691 bytes .../__pycache__/version.cpython-38.pyc | Bin 0 -> 13366 bytes .../_vendor/packaging/_compat.py | 38 + .../_vendor/packaging/_structures.py | 86 + .../_vendor/packaging/_typing.py | 48 + .../_vendor/packaging/markers.py | 328 + .../_vendor/packaging/requirements.py | 145 + .../_vendor/packaging/specifiers.py | 863 ++ .../pkg_resources/_vendor/packaging/tags.py | 751 ++ .../pkg_resources/_vendor/packaging/utils.py | 65 + .../_vendor/packaging/version.py | 535 + .../pkg_resources/_vendor/pyparsing.py | 5742 +++++++++++ .../pkg_resources/extern/__init__.py | 73 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2901 bytes .../__pycache__/setup.cpython-38.pyc | Bin 0 -> 342 bytes .../data/my-test-package-source/setup.py | 6 + .../setuptools-56.0.0.dist-info/INSTALLER | 1 + .../setuptools-56.0.0.dist-info/LICENSE | 19 + .../setuptools-56.0.0.dist-info/METADATA | 114 + .../setuptools-56.0.0.dist-info/RECORD | 295 + .../setuptools-56.0.0.dist-info/REQUESTED | 0 .../setuptools-56.0.0.dist-info/WHEEL | 5 + .../dependency_links.txt | 2 + .../entry_points.txt | 60 + .../setuptools-56.0.0.dist-info/top_level.txt | 3 + .../site-packages/setuptools/__init__.py | 241 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 8677 bytes .../_deprecation_warning.cpython-38.pyc | Bin 0 -> 573 bytes .../__pycache__/_imp.cpython-38.pyc | Bin 0 -> 2112 bytes .../__pycache__/archive_util.cpython-38.pyc | Bin 0 -> 5773 bytes .../__pycache__/build_meta.cpython-38.pyc | Bin 0 -> 9005 bytes .../__pycache__/config.cpython-38.pyc | Bin 0 -> 19819 bytes .../__pycache__/dep_util.cpython-38.pyc | Bin 0 -> 880 bytes .../__pycache__/depends.cpython-38.pyc | Bin 0 -> 5251 bytes .../__pycache__/dist.cpython-38.pyc | Bin 0 -> 33349 bytes .../__pycache__/errors.cpython-38.pyc | Bin 0 -> 873 bytes .../__pycache__/extension.cpython-38.pyc | Bin 0 -> 1963 bytes .../__pycache__/glob.cpython-38.pyc | Bin 0 -> 3727 bytes .../__pycache__/installer.cpython-38.pyc | Bin 0 -> 2808 bytes .../__pycache__/launch.cpython-38.pyc | Bin 0 -> 904 bytes .../__pycache__/lib2to3_ex.cpython-38.pyc | Bin 0 -> 2720 bytes .../__pycache__/monkey.cpython-38.pyc | Bin 0 -> 4642 bytes .../__pycache__/msvc.cpython-38.pyc | Bin 0 -> 43215 bytes .../__pycache__/namespaces.cpython-38.pyc | Bin 0 -> 3603 bytes .../__pycache__/package_index.cpython-38.pyc | Bin 0 -> 33011 bytes .../__pycache__/py34compat.cpython-38.pyc | Bin 0 -> 507 bytes .../__pycache__/sandbox.cpython-38.pyc | Bin 0 -> 15468 bytes .../__pycache__/ssl_support.cpython-38.pyc | Bin 0 -> 6895 bytes .../__pycache__/unicode_utils.cpython-38.pyc | Bin 0 -> 1143 bytes .../__pycache__/version.cpython-38.pyc | Bin 0 -> 349 bytes .../__pycache__/wheel.cpython-38.pyc | Bin 0 -> 7255 bytes .../windows_support.cpython-38.pyc | Bin 0 -> 1046 bytes .../setuptools/_deprecation_warning.py | 7 + .../setuptools/_distutils/__init__.py | 15 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 481 bytes .../__pycache__/_msvccompiler.cpython-38.pyc | Bin 0 -> 13874 bytes .../__pycache__/archive_util.cpython-38.pyc | Bin 0 -> 6622 bytes .../__pycache__/bcppcompiler.cpython-38.pyc | Bin 0 -> 6547 bytes .../__pycache__/ccompiler.cpython-38.pyc | Bin 0 -> 33371 bytes .../_distutils/__pycache__/cmd.cpython-38.pyc | Bin 0 -> 14031 bytes .../__pycache__/config.cpython-38.pyc | Bin 0 -> 3592 bytes .../__pycache__/core.cpython-38.pyc | Bin 0 -> 6697 bytes .../cygwinccompiler.cpython-38.pyc | Bin 0 -> 8572 bytes .../__pycache__/debug.cpython-38.pyc | Bin 0 -> 277 bytes .../__pycache__/dep_util.cpython-38.pyc | Bin 0 -> 2797 bytes .../__pycache__/dir_util.cpython-38.pyc | Bin 0 -> 5906 bytes .../__pycache__/dist.cpython-38.pyc | Bin 0 -> 34610 bytes .../__pycache__/errors.cpython-38.pyc | Bin 0 -> 5333 bytes .../__pycache__/extension.cpython-38.pyc | Bin 0 -> 7006 bytes .../__pycache__/fancy_getopt.cpython-38.pyc | Bin 0 -> 10739 bytes .../__pycache__/file_util.cpython-38.pyc | Bin 0 -> 6016 bytes .../__pycache__/filelist.cpython-38.pyc | Bin 0 -> 9950 bytes .../_distutils/__pycache__/log.cpython-38.pyc | Bin 0 -> 2398 bytes .../__pycache__/msvc9compiler.cpython-38.pyc | Bin 0 -> 17560 bytes .../__pycache__/msvccompiler.cpython-38.pyc | Bin 0 -> 14771 bytes .../__pycache__/py35compat.cpython-38.pyc | Bin 0 -> 653 bytes .../__pycache__/py38compat.cpython-38.pyc | Bin 0 -> 450 bytes .../__pycache__/spawn.cpython-38.pyc | Bin 0 -> 3457 bytes .../__pycache__/sysconfig.cpython-38.pyc | Bin 0 -> 12462 bytes .../__pycache__/text_file.cpython-38.pyc | Bin 0 -> 8522 bytes .../__pycache__/unixccompiler.cpython-38.pyc | Bin 0 -> 6701 bytes .../__pycache__/util.cpython-38.pyc | Bin 0 -> 15710 bytes .../__pycache__/version.cpython-38.pyc | Bin 0 -> 7444 bytes .../versionpredicate.cpython-38.pyc | Bin 0 -> 5218 bytes .../setuptools/_distutils/_msvccompiler.py | 561 ++ .../setuptools/_distutils/archive_util.py | 256 + .../setuptools/_distutils/bcppcompiler.py | 393 + .../setuptools/_distutils/ccompiler.py | 1116 +++ .../setuptools/_distutils/cmd.py | 403 + .../setuptools/_distutils/command/__init__.py | 31 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 626 bytes .../command/__pycache__/bdist.cpython-38.pyc | Bin 0 -> 3749 bytes .../__pycache__/bdist_dumb.cpython-38.pyc | Bin 0 -> 3675 bytes .../__pycache__/bdist_msi.cpython-38.pyc | Bin 0 -> 20056 bytes .../__pycache__/bdist_rpm.cpython-38.pyc | Bin 0 -> 12460 bytes .../__pycache__/bdist_wininst.cpython-38.pyc | Bin 0 -> 8550 bytes .../command/__pycache__/build.cpython-38.pyc | Bin 0 -> 3964 bytes .../__pycache__/build_clib.cpython-38.pyc | Bin 0 -> 4897 bytes .../__pycache__/build_ext.cpython-38.pyc | Bin 0 -> 16286 bytes .../__pycache__/build_py.cpython-38.pyc | Bin 0 -> 10563 bytes .../__pycache__/build_scripts.cpython-38.pyc | Bin 0 -> 4407 bytes .../command/__pycache__/check.cpython-38.pyc | Bin 0 -> 5002 bytes .../command/__pycache__/clean.cpython-38.pyc | Bin 0 -> 2183 bytes .../command/__pycache__/config.cpython-38.pyc | Bin 0 -> 10310 bytes .../__pycache__/install.cpython-38.pyc | Bin 0 -> 13971 bytes .../__pycache__/install_data.cpython-38.pyc | Bin 0 -> 2372 bytes .../install_egg_info.cpython-38.pyc | Bin 0 -> 3079 bytes .../install_headers.cpython-38.pyc | Bin 0 -> 1791 bytes .../__pycache__/install_lib.cpython-38.pyc | Bin 0 -> 5196 bytes .../install_scripts.cpython-38.pyc | Bin 0 -> 2224 bytes .../__pycache__/py37compat.cpython-38.pyc | Bin 0 -> 1053 bytes .../__pycache__/register.cpython-38.pyc | Bin 0 -> 8538 bytes .../command/__pycache__/sdist.cpython-38.pyc | Bin 0 -> 14599 bytes .../command/__pycache__/upload.cpython-38.pyc | Bin 0 -> 5252 bytes .../setuptools/_distutils/command/bdist.py | 143 + .../_distutils/command/bdist_dumb.py | 123 + .../_distutils/command/bdist_msi.py | 749 ++ .../_distutils/command/bdist_rpm.py | 579 ++ .../_distutils/command/bdist_wininst.py | 377 + .../setuptools/_distutils/command/build.py | 157 + .../_distutils/command/build_clib.py | 209 + .../_distutils/command/build_ext.py | 755 ++ .../setuptools/_distutils/command/build_py.py | 416 + .../_distutils/command/build_scripts.py | 160 + .../setuptools/_distutils/command/check.py | 148 + .../setuptools/_distutils/command/clean.py | 76 + .../setuptools/_distutils/command/config.py | 344 + .../setuptools/_distutils/command/install.py | 677 ++ .../_distutils/command/install_data.py | 79 + .../_distutils/command/install_egg_info.py | 77 + .../_distutils/command/install_headers.py | 47 + .../_distutils/command/install_lib.py | 217 + .../_distutils/command/install_scripts.py | 60 + .../_distutils/command/py37compat.py | 30 + .../setuptools/_distutils/command/register.py | 304 + .../setuptools/_distutils/command/sdist.py | 494 + .../setuptools/_distutils/command/upload.py | 214 + .../setuptools/_distutils/config.py | 130 + .../setuptools/_distutils/core.py | 234 + .../setuptools/_distutils/cygwinccompiler.py | 403 + .../setuptools/_distutils/debug.py | 5 + .../setuptools/_distutils/dep_util.py | 92 + .../setuptools/_distutils/dir_util.py | 210 + .../setuptools/_distutils/dist.py | 1257 +++ .../setuptools/_distutils/errors.py | 97 + .../setuptools/_distutils/extension.py | 240 + .../setuptools/_distutils/fancy_getopt.py | 457 + .../setuptools/_distutils/file_util.py | 238 + .../setuptools/_distutils/filelist.py | 327 + .../setuptools/_distutils/log.py | 77 + .../setuptools/_distutils/msvc9compiler.py | 788 ++ .../setuptools/_distutils/msvccompiler.py | 643 ++ .../setuptools/_distutils/py35compat.py | 19 + .../setuptools/_distutils/py38compat.py | 7 + .../setuptools/_distutils/spawn.py | 125 + .../setuptools/_distutils/sysconfig.py | 573 ++ .../setuptools/_distutils/text_file.py | 286 + .../setuptools/_distutils/unixccompiler.py | 328 + .../setuptools/_distutils/util.py | 561 ++ .../setuptools/_distutils/version.py | 347 + .../setuptools/_distutils/versionpredicate.py | 166 + .../site-packages/setuptools/_imp.py | 82 + .../setuptools/_vendor/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 211 bytes .../__pycache__/ordered_set.cpython-38.pyc | Bin 0 -> 16469 bytes .../__pycache__/pyparsing.cpython-38.pyc | Bin 0 -> 201688 bytes .../setuptools/_vendor/ordered_set.py | 488 + .../setuptools/_vendor/packaging/__about__.py | 27 + .../setuptools/_vendor/packaging/__init__.py | 26 + .../__pycache__/__about__.cpython-38.pyc | Bin 0 -> 753 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 599 bytes .../__pycache__/_compat.cpython-38.pyc | Bin 0 -> 1171 bytes .../__pycache__/_structures.cpython-38.pyc | Bin 0 -> 2925 bytes .../__pycache__/_typing.cpython-38.pyc | Bin 0 -> 1516 bytes .../__pycache__/markers.cpython-38.pyc | Bin 0 -> 9357 bytes .../__pycache__/requirements.cpython-38.pyc | Bin 0 -> 4110 bytes .../__pycache__/specifiers.cpython-38.pyc | Bin 0 -> 20634 bytes .../packaging/__pycache__/tags.cpython-38.pyc | Bin 0 -> 17286 bytes .../__pycache__/utils.cpython-38.pyc | Bin 0 -> 1688 bytes .../__pycache__/version.cpython-38.pyc | Bin 0 -> 13363 bytes .../setuptools/_vendor/packaging/_compat.py | 38 + .../_vendor/packaging/_structures.py | 86 + .../setuptools/_vendor/packaging/_typing.py | 48 + .../setuptools/_vendor/packaging/markers.py | 328 + .../_vendor/packaging/requirements.py | 145 + .../_vendor/packaging/specifiers.py | 863 ++ .../setuptools/_vendor/packaging/tags.py | 751 ++ .../setuptools/_vendor/packaging/utils.py | 65 + .../setuptools/_vendor/packaging/version.py | 535 + .../setuptools/_vendor/pyparsing.py | 5742 +++++++++++ .../site-packages/setuptools/archive_util.py | 205 + .../site-packages/setuptools/build_meta.py | 281 + .../site-packages/setuptools/cli-32.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/cli-64.exe | Bin 0 -> 74752 bytes .../site-packages/setuptools/cli.exe | Bin 0 -> 65536 bytes .../setuptools/command/__init__.py | 17 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 750 bytes .../command/__pycache__/alias.cpython-38.pyc | Bin 0 -> 2394 bytes .../__pycache__/bdist_egg.cpython-38.pyc | Bin 0 -> 13060 bytes .../__pycache__/bdist_rpm.cpython-38.pyc | Bin 0 -> 1380 bytes .../__pycache__/build_clib.cpython-38.pyc | Bin 0 -> 2491 bytes .../__pycache__/build_ext.cpython-38.pyc | Bin 0 -> 9828 bytes .../__pycache__/build_py.cpython-38.pyc | Bin 0 -> 8701 bytes .../__pycache__/develop.cpython-38.pyc | Bin 0 -> 6443 bytes .../__pycache__/dist_info.cpython-38.pyc | Bin 0 -> 1418 bytes .../__pycache__/easy_install.cpython-38.pyc | Bin 0 -> 63278 bytes .../__pycache__/egg_info.cpython-38.pyc | Bin 0 -> 21718 bytes .../__pycache__/install.cpython-38.pyc | Bin 0 -> 4073 bytes .../install_egg_info.cpython-38.pyc | Bin 0 -> 2460 bytes .../__pycache__/install_lib.cpython-38.pyc | Bin 0 -> 4187 bytes .../install_scripts.cpython-38.pyc | Bin 0 -> 2451 bytes .../__pycache__/py36compat.cpython-38.pyc | Bin 0 -> 4612 bytes .../__pycache__/register.cpython-38.pyc | Bin 0 -> 868 bytes .../command/__pycache__/rotate.cpython-38.pyc | Bin 0 -> 2537 bytes .../__pycache__/saveopts.cpython-38.pyc | Bin 0 -> 946 bytes .../command/__pycache__/sdist.cpython-38.pyc | Bin 0 -> 7606 bytes .../command/__pycache__/setopt.cpython-38.pyc | Bin 0 -> 4554 bytes .../command/__pycache__/test.cpython-38.pyc | Bin 0 -> 8359 bytes .../command/__pycache__/upload.cpython-38.pyc | Bin 0 -> 841 bytes .../__pycache__/upload_docs.cpython-38.pyc | Bin 0 -> 6148 bytes .../site-packages/setuptools/command/alias.py | 78 + .../setuptools/command/bdist_egg.py | 456 + .../setuptools/command/bdist_rpm.py | 31 + .../setuptools/command/build_clib.py | 101 + .../setuptools/command/build_ext.py | 322 + .../setuptools/command/build_py.py | 270 + .../setuptools/command/develop.py | 216 + .../setuptools/command/dist_info.py | 36 + .../setuptools/command/easy_install.py | 2290 +++++ .../setuptools/command/egg_info.py | 727 ++ .../setuptools/command/install.py | 125 + .../setuptools/command/install_egg_info.py | 62 + .../setuptools/command/install_lib.py | 122 + .../setuptools/command/install_scripts.py | 69 + .../setuptools/command/launcher manifest.xml | 15 + .../setuptools/command/py36compat.py | 134 + .../setuptools/command/register.py | 18 + .../setuptools/command/rotate.py | 64 + .../setuptools/command/saveopts.py | 22 + .../site-packages/setuptools/command/sdist.py | 235 + .../setuptools/command/setopt.py | 148 + .../site-packages/setuptools/command/test.py | 274 + .../setuptools/command/upload.py | 17 + .../setuptools/command/upload_docs.py | 202 + .../site-packages/setuptools/config.py | 710 ++ .../site-packages/setuptools/dep_util.py | 25 + .../site-packages/setuptools/depends.py | 175 + .../site-packages/setuptools/dist.py | 1057 ++ .../site-packages/setuptools/errors.py | 16 + .../site-packages/setuptools/extension.py | 55 + .../setuptools/extern/__init__.py | 73 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2924 bytes .../site-packages/setuptools/glob.py | 167 + .../site-packages/setuptools/gui-32.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/gui-64.exe | Bin 0 -> 75264 bytes .../site-packages/setuptools/gui.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/installer.py | 97 + .../site-packages/setuptools/launch.py | 36 + .../site-packages/setuptools/lib2to3_ex.py | 68 + .../site-packages/setuptools/monkey.py | 177 + .../site-packages/setuptools/msvc.py | 1826 ++++ .../site-packages/setuptools/namespaces.py | 107 + .../site-packages/setuptools/package_index.py | 1145 +++ .../site-packages/setuptools/py34compat.py | 13 + .../site-packages/setuptools/sandbox.py | 496 + .../setuptools/script (dev).tmpl | 6 + .../site-packages/setuptools/script.tmpl | 3 + .../site-packages/setuptools/ssl_support.py | 266 + .../site-packages/setuptools/unicode_utils.py | 42 + .../site-packages/setuptools/version.py | 6 + .../site-packages/setuptools/wheel.py | 213 + .../setuptools/windows_support.py | 29 + .../sqlalchemy-2.0.44.dist-info/INSTALLER | 1 + .../sqlalchemy-2.0.44.dist-info/LICENSE | 19 + .../sqlalchemy-2.0.44.dist-info/METADATA | 242 + .../sqlalchemy-2.0.44.dist-info/RECORD | 532 + .../sqlalchemy-2.0.44.dist-info/REQUESTED | 0 .../sqlalchemy-2.0.44.dist-info/WHEEL | 6 + .../sqlalchemy-2.0.44.dist-info/top_level.txt | 1 + .../site-packages/sqlalchemy/__init__.py | 283 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 8986 bytes .../__pycache__/events.cpython-38.pyc | Bin 0 -> 564 bytes .../sqlalchemy/__pycache__/exc.cpython-38.pyc | Bin 0 -> 24655 bytes .../__pycache__/inspection.cpython-38.pyc | Bin 0 -> 5804 bytes .../sqlalchemy/__pycache__/log.cpython-38.pyc | Bin 0 -> 8309 bytes .../__pycache__/schema.cpython-38.pyc | Bin 0 -> 2321 bytes .../__pycache__/types.cpython-38.pyc | Bin 0 -> 2231 bytes .../sqlalchemy/connectors/__init__.py | 18 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 588 bytes .../__pycache__/aioodbc.cpython-38.pyc | Bin 0 -> 5805 bytes .../__pycache__/asyncio.cpython-38.pyc | Bin 0 -> 15418 bytes .../__pycache__/pyodbc.cpython-38.pyc | Bin 0 -> 6835 bytes .../sqlalchemy/connectors/aioodbc.py | 184 + .../sqlalchemy/connectors/asyncio.py | 429 + .../sqlalchemy/connectors/pyodbc.py | 250 + .../sqlalchemy/cyextension/__init__.py | 6 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 215 bytes ...collections.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 2145632 bytes .../sqlalchemy/cyextension/collections.pyx | 409 + ...mutabledict.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 641704 bytes .../sqlalchemy/cyextension/immutabledict.pxd | 8 + .../sqlalchemy/cyextension/immutabledict.pyx | 133 + .../processors.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 558832 bytes .../sqlalchemy/cyextension/processors.pyx | 68 + ...resultproxy.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 573960 bytes .../sqlalchemy/cyextension/resultproxy.pyx | 102 + .../util.cpython-38-x86_64-linux-gnu.so | Bin 0 -> 842584 bytes .../sqlalchemy/cyextension/util.pyx | 90 + .../sqlalchemy/dialects/__init__.py | 62 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1431 bytes .../__pycache__/_typing.cpython-38.pyc | Bin 0 -> 888 bytes .../sqlalchemy/dialects/_typing.py | 30 + .../sqlalchemy/dialects/mssql/__init__.py | 88 + .../mssql/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1594 bytes .../mssql/__pycache__/aioodbc.cpython-38.pyc | Bin 0 -> 2286 bytes .../mssql/__pycache__/base.cpython-38.pyc | Bin 0 -> 112132 bytes .../information_schema.cpython-38.pyc | Bin 0 -> 6278 bytes .../mssql/__pycache__/json.cpython-38.pyc | Bin 0 -> 4724 bytes .../__pycache__/provision.cpython-38.pyc | Bin 0 -> 4548 bytes .../mssql/__pycache__/pymssql.cpython-38.pyc | Bin 0 -> 4543 bytes .../mssql/__pycache__/pyodbc.cpython-38.pyc | Bin 0 -> 26040 bytes .../sqlalchemy/dialects/mssql/aioodbc.py | 63 + .../sqlalchemy/dialects/mssql/base.py | 4089 ++++++++ .../dialects/mssql/information_schema.py | 285 + .../sqlalchemy/dialects/mssql/json.py | 129 + .../sqlalchemy/dialects/mssql/provision.py | 162 + .../sqlalchemy/dialects/mssql/pymssql.py | 126 + .../sqlalchemy/dialects/mssql/pyodbc.py | 760 ++ .../sqlalchemy/dialects/mysql/__init__.py | 104 + .../mysql/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1913 bytes .../mysql/__pycache__/aiomysql.cpython-38.pyc | Bin 0 -> 9243 bytes .../mysql/__pycache__/asyncmy.cpython-38.pyc | Bin 0 -> 8492 bytes .../mysql/__pycache__/base.cpython-38.pyc | Bin 0 -> 111634 bytes .../mysql/__pycache__/cymysql.cpython-38.pyc | Bin 0 -> 3502 bytes .../mysql/__pycache__/dml.cpython-38.pyc | Bin 0 -> 7514 bytes .../__pycache__/enumerated.cpython-38.pyc | Bin 0 -> 9217 bytes .../__pycache__/expression.cpython-38.pyc | Bin 0 -> 4197 bytes .../mysql/__pycache__/json.cpython-38.pyc | Bin 0 -> 3449 bytes .../mysql/__pycache__/mariadb.cpython-38.pyc | Bin 0 -> 2306 bytes .../mariadbconnector.cpython-38.pyc | Bin 0 -> 10079 bytes .../__pycache__/mysqlconnector.cpython-38.pyc | Bin 0 -> 10799 bytes .../mysql/__pycache__/mysqldb.cpython-38.pyc | Bin 0 -> 9599 bytes .../__pycache__/provision.cpython-38.pyc | Bin 0 -> 2699 bytes .../mysql/__pycache__/pymysql.cpython-38.pyc | Bin 0 -> 4992 bytes .../mysql/__pycache__/pyodbc.cpython-38.pyc | Bin 0 -> 5110 bytes .../__pycache__/reflection.cpython-38.pyc | Bin 0 -> 17204 bytes .../__pycache__/reserved_words.cpython-38.pyc | Bin 0 -> 5411 bytes .../mysql/__pycache__/types.cpython-38.pyc | Bin 0 -> 28577 bytes .../sqlalchemy/dialects/mysql/aiomysql.py | 250 + .../sqlalchemy/dialects/mysql/asyncmy.py | 231 + .../sqlalchemy/dialects/mysql/base.py | 3923 ++++++++ .../sqlalchemy/dialects/mysql/cymysql.py | 106 + .../sqlalchemy/dialects/mysql/dml.py | 225 + .../sqlalchemy/dialects/mysql/enumerated.py | 282 + .../sqlalchemy/dialects/mysql/expression.py | 146 + .../sqlalchemy/dialects/mysql/json.py | 91 + .../sqlalchemy/dialects/mysql/mariadb.py | 73 + .../dialects/mysql/mariadbconnector.py | 322 + .../dialects/mysql/mysqlconnector.py | 302 + .../sqlalchemy/dialects/mysql/mysqldb.py | 314 + .../sqlalchemy/dialects/mysql/provision.py | 113 + .../sqlalchemy/dialects/mysql/pymysql.py | 158 + .../sqlalchemy/dialects/mysql/pyodbc.py | 157 + .../sqlalchemy/dialects/mysql/reflection.py | 727 ++ .../dialects/mysql/reserved_words.py | 570 ++ .../sqlalchemy/dialects/mysql/types.py | 835 ++ .../sqlalchemy/dialects/oracle/__init__.py | 81 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1550 bytes .../oracle/__pycache__/base.cpython-38.pyc | Bin 0 -> 102336 bytes .../__pycache__/cx_oracle.cpython-38.pyc | Bin 0 -> 47303 bytes .../__pycache__/dictionary.cpython-38.pyc | Bin 0 -> 11238 bytes .../__pycache__/oracledb.cpython-38.pyc | Bin 0 -> 35650 bytes .../__pycache__/provision.cpython-38.pyc | Bin 0 -> 6599 bytes .../oracle/__pycache__/types.cpython-38.pyc | Bin 0 -> 10752 bytes .../oracle/__pycache__/vector.cpython-38.pyc | Bin 0 -> 9749 bytes .../sqlalchemy/dialects/oracle/base.py | 3802 ++++++++ .../sqlalchemy/dialects/oracle/cx_oracle.py | 1555 +++ .../sqlalchemy/dialects/oracle/dictionary.py | 507 + .../sqlalchemy/dialects/oracle/oracledb.py | 945 ++ .../sqlalchemy/dialects/oracle/provision.py | 220 + .../sqlalchemy/dialects/oracle/types.py | 316 + .../sqlalchemy/dialects/oracle/vector.py | 364 + .../dialects/postgresql/__init__.py | 167 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 3270 bytes .../_psycopg_common.cpython-38.pyc | Bin 0 -> 5906 bytes .../__pycache__/array.cpython-38.pyc | Bin 0 -> 16064 bytes .../__pycache__/asyncpg.cpython-38.pyc | Bin 0 -> 42102 bytes .../__pycache__/base.cpython-38.pyc | Bin 0 -> 141583 bytes .../postgresql/__pycache__/dml.cpython-38.pyc | Bin 0 -> 10485 bytes .../postgresql/__pycache__/ext.cpython-38.pyc | Bin 0 -> 16992 bytes .../__pycache__/hstore.cpython-38.pyc | Bin 0 -> 12258 bytes .../__pycache__/json.cpython-38.pyc | Bin 0 -> 14506 bytes .../__pycache__/named_types.cpython-38.pyc | Bin 0 -> 18060 bytes .../__pycache__/operators.cpython-38.pyc | Bin 0 -> 1277 bytes .../__pycache__/pg8000.cpython-38.pyc | Bin 0 -> 21913 bytes .../__pycache__/pg_catalog.cpython-38.pyc | Bin 0 -> 7523 bytes .../__pycache__/provision.cpython-38.pyc | Bin 0 -> 4809 bytes .../__pycache__/psycopg.cpython-38.pyc | Bin 0 -> 26964 bytes .../__pycache__/psycopg2.cpython-38.pyc | Bin 0 -> 31007 bytes .../__pycache__/psycopg2cffi.cpython-38.pyc | Bin 0 -> 1875 bytes .../__pycache__/ranges.cpython-38.pyc | Bin 0 -> 24356 bytes .../__pycache__/types.cpython-38.pyc | Bin 0 -> 9579 bytes .../dialects/postgresql/_psycopg_common.py | 189 + .../sqlalchemy/dialects/postgresql/array.py | 519 + .../sqlalchemy/dialects/postgresql/asyncpg.py | 1284 +++ .../sqlalchemy/dialects/postgresql/base.py | 5226 ++++++++++ .../sqlalchemy/dialects/postgresql/dml.py | 339 + .../sqlalchemy/dialects/postgresql/ext.py | 536 + .../sqlalchemy/dialects/postgresql/hstore.py | 406 + .../sqlalchemy/dialects/postgresql/json.py | 395 + .../dialects/postgresql/named_types.py | 524 + .../dialects/postgresql/operators.py | 129 + .../sqlalchemy/dialects/postgresql/pg8000.py | 669 ++ .../dialects/postgresql/pg_catalog.py | 326 + .../dialects/postgresql/provision.py | 175 + .../sqlalchemy/dialects/postgresql/psycopg.py | 786 ++ .../dialects/postgresql/psycopg2.py | 892 ++ .../dialects/postgresql/psycopg2cffi.py | 61 + .../sqlalchemy/dialects/postgresql/ranges.py | 1031 ++ .../sqlalchemy/dialects/postgresql/types.py | 313 + .../sqlalchemy/dialects/sqlite/__init__.py | 57 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1004 bytes .../__pycache__/aiosqlite.cpython-38.pyc | Bin 0 -> 14756 bytes .../sqlite/__pycache__/base.cpython-38.pyc | Bin 0 -> 84050 bytes .../sqlite/__pycache__/dml.cpython-38.pyc | Bin 0 -> 8843 bytes .../sqlite/__pycache__/json.cpython-38.pyc | Bin 0 -> 3275 bytes .../__pycache__/provision.cpython-38.pyc | Bin 0 -> 4764 bytes .../__pycache__/pysqlcipher.cpython-38.pyc | Bin 0 -> 5483 bytes .../__pycache__/pysqlite.cpython-38.pyc | Bin 0 -> 27835 bytes .../sqlalchemy/dialects/sqlite/aiosqlite.py | 446 + .../sqlalchemy/dialects/sqlite/base.py | 2976 ++++++ .../sqlalchemy/dialects/sqlite/dml.py | 263 + .../sqlalchemy/dialects/sqlite/json.py | 92 + .../sqlalchemy/dialects/sqlite/provision.py | 196 + .../sqlalchemy/dialects/sqlite/pysqlcipher.py | 157 + .../sqlalchemy/dialects/sqlite/pysqlite.py | 756 ++ .../dialects/type_migration_guidelines.txt | 145 + .../sqlalchemy/engine/__init__.py | 62 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 2242 bytes .../__pycache__/_py_processors.cpython-38.pyc | Bin 0 -> 3410 bytes .../engine/__pycache__/_py_row.cpython-38.pyc | Bin 0 -> 4214 bytes .../__pycache__/_py_util.cpython-38.pyc | Bin 0 -> 1569 bytes .../engine/__pycache__/base.cpython-38.pyc | Bin 0 -> 96971 bytes .../characteristics.cpython-38.pyc | Bin 0 -> 5656 bytes .../engine/__pycache__/create.cpython-38.pyc | Bin 0 -> 29919 bytes .../engine/__pycache__/cursor.cpython-38.pyc | Bin 0 -> 61481 bytes .../engine/__pycache__/default.cpython-38.pyc | Bin 0 -> 60143 bytes .../engine/__pycache__/events.cpython-38.pyc | Bin 0 -> 37211 bytes .../__pycache__/interfaces.cpython-38.pyc | Bin 0 -> 93579 bytes .../engine/__pycache__/mock.cpython-38.pyc | Bin 0 -> 4829 bytes .../__pycache__/processors.cpython-38.pyc | Bin 0 -> 1176 bytes .../__pycache__/reflection.cpython-38.pyc | Bin 0 -> 61439 bytes .../engine/__pycache__/result.cpython-38.pyc | Bin 0 -> 75206 bytes .../engine/__pycache__/row.cpython-38.pyc | Bin 0 -> 14857 bytes .../__pycache__/strategies.cpython-38.pyc | Bin 0 -> 545 bytes .../engine/__pycache__/url.cpython-38.pyc | Bin 0 -> 26923 bytes .../engine/__pycache__/util.cpython-38.pyc | Bin 0 -> 5029 bytes .../sqlalchemy/engine/_py_processors.py | 136 + .../sqlalchemy/engine/_py_row.py | 128 + .../sqlalchemy/engine/_py_util.py | 74 + .../site-packages/sqlalchemy/engine/base.py | 3374 +++++++ .../sqlalchemy/engine/characteristics.py | 155 + .../site-packages/sqlalchemy/engine/create.py | 893 ++ .../site-packages/sqlalchemy/engine/cursor.py | 2181 +++++ .../sqlalchemy/engine/default.py | 2389 +++++ .../site-packages/sqlalchemy/engine/events.py | 965 ++ .../sqlalchemy/engine/interfaces.py | 3464 +++++++ .../site-packages/sqlalchemy/engine/mock.py | 134 + .../sqlalchemy/engine/processors.py | 61 + .../sqlalchemy/engine/reflection.py | 2102 ++++ .../site-packages/sqlalchemy/engine/result.py | 2387 +++++ .../site-packages/sqlalchemy/engine/row.py | 400 + .../sqlalchemy/engine/strategies.py | 16 + .../site-packages/sqlalchemy/engine/url.py | 924 ++ .../site-packages/sqlalchemy/engine/util.py | 167 + .../sqlalchemy/event/__init__.py | 26 + .../event/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 881 bytes .../event/__pycache__/api.cpython-38.pyc | Bin 0 -> 8311 bytes .../event/__pycache__/attr.cpython-38.pyc | Bin 0 -> 23558 bytes .../event/__pycache__/base.cpython-38.pyc | Bin 0 -> 14697 bytes .../event/__pycache__/legacy.cpython-38.pyc | Bin 0 -> 7008 bytes .../event/__pycache__/registry.cpython-38.pyc | Bin 0 -> 8048 bytes .../site-packages/sqlalchemy/event/api.py | 220 + .../site-packages/sqlalchemy/event/attr.py | 676 ++ .../site-packages/sqlalchemy/event/base.py | 472 + .../site-packages/sqlalchemy/event/legacy.py | 258 + .../sqlalchemy/event/registry.py | 390 + .../site-packages/sqlalchemy/events.py | 17 + .../python3.8/site-packages/sqlalchemy/exc.py | 832 ++ .../site-packages/sqlalchemy/ext/__init__.py | 11 + .../ext/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 302 bytes .../associationproxy.cpython-38.pyc | Bin 0 -> 63130 bytes .../ext/__pycache__/automap.cpython-38.pyc | Bin 0 -> 50360 bytes .../ext/__pycache__/baked.cpython-38.pyc | Bin 0 -> 17088 bytes .../ext/__pycache__/compiler.cpython-38.pyc | Bin 0 -> 20039 bytes .../horizontal_shard.cpython-38.pyc | Bin 0 -> 13944 bytes .../ext/__pycache__/hybrid.cpython-38.pyc | Bin 0 -> 54562 bytes .../ext/__pycache__/indexable.cpython-38.pyc | Bin 0 -> 11554 bytes .../instrumentation.cpython-38.pyc | Bin 0 -> 14825 bytes .../ext/__pycache__/mutable.cpython-38.pyc | Bin 0 -> 38332 bytes .../__pycache__/orderinglist.cpython-38.pyc | Bin 0 -> 15400 bytes .../ext/__pycache__/serializer.cpython-38.pyc | Bin 0 -> 5529 bytes .../sqlalchemy/ext/associationproxy.py | 2027 ++++ .../sqlalchemy/ext/asyncio/__init__.py | 25 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 970 bytes .../asyncio/__pycache__/base.cpython-38.pyc | Bin 0 -> 8197 bytes .../asyncio/__pycache__/engine.cpython-38.pyc | Bin 0 -> 45219 bytes .../asyncio/__pycache__/exc.cpython-38.pyc | Bin 0 -> 914 bytes .../asyncio/__pycache__/result.cpython-38.pyc | Bin 0 -> 31786 bytes .../__pycache__/scoping.cpython-38.pyc | Bin 0 -> 48031 bytes .../__pycache__/session.cpython-38.pyc | Bin 0 -> 58865 bytes .../sqlalchemy/ext/asyncio/base.py | 281 + .../sqlalchemy/ext/asyncio/engine.py | 1471 +++ .../sqlalchemy/ext/asyncio/exc.py | 21 + .../sqlalchemy/ext/asyncio/result.py | 965 ++ .../sqlalchemy/ext/asyncio/scoping.py | 1599 +++ .../sqlalchemy/ext/asyncio/session.py | 1947 ++++ .../site-packages/sqlalchemy/ext/automap.py | 1701 ++++ .../site-packages/sqlalchemy/ext/baked.py | 570 ++ .../site-packages/sqlalchemy/ext/compiler.py | 600 ++ .../sqlalchemy/ext/declarative/__init__.py | 65 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1672 bytes .../__pycache__/extensions.cpython-38.pyc | Bin 0 -> 17121 bytes .../sqlalchemy/ext/declarative/extensions.py | 564 ++ .../sqlalchemy/ext/horizontal_shard.py | 478 + .../site-packages/sqlalchemy/ext/hybrid.py | 1535 +++ .../site-packages/sqlalchemy/ext/indexable.py | 364 + .../sqlalchemy/ext/instrumentation.py | 450 + .../site-packages/sqlalchemy/ext/mutable.py | 1085 ++ .../sqlalchemy/ext/mypy/__init__.py | 6 + .../mypy/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 212 bytes .../ext/mypy/__pycache__/apply.cpython-38.pyc | Bin 0 -> 6286 bytes .../__pycache__/decl_class.cpython-38.pyc | Bin 0 -> 8283 bytes .../ext/mypy/__pycache__/infer.cpython-38.pyc | Bin 0 -> 9405 bytes .../ext/mypy/__pycache__/names.cpython-38.pyc | Bin 0 -> 7051 bytes .../mypy/__pycache__/plugin.cpython-38.pyc | Bin 0 -> 7874 bytes .../ext/mypy/__pycache__/util.cpython-38.pyc | Bin 0 -> 9751 bytes .../sqlalchemy/ext/mypy/apply.py | 324 + .../sqlalchemy/ext/mypy/decl_class.py | 515 + .../sqlalchemy/ext/mypy/infer.py | 590 ++ .../sqlalchemy/ext/mypy/names.py | 335 + .../sqlalchemy/ext/mypy/plugin.py | 303 + .../site-packages/sqlalchemy/ext/mypy/util.py | 357 + .../sqlalchemy/ext/orderinglist.py | 439 + .../sqlalchemy/ext/serializer.py | 185 + .../sqlalchemy/future/__init__.py | 16 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 469 bytes .../future/__pycache__/engine.cpython-38.pyc | Bin 0 -> 403 bytes .../site-packages/sqlalchemy/future/engine.py | 15 + .../site-packages/sqlalchemy/inspection.py | 174 + .../python3.8/site-packages/sqlalchemy/log.py | 288 + .../site-packages/sqlalchemy/orm/__init__.py | 171 + .../orm/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 6088 bytes .../_orm_constructors.cpython-38.pyc | Bin 0 -> 97838 bytes .../orm/__pycache__/_typing.cpython-38.pyc | Bin 0 -> 5603 bytes .../orm/__pycache__/attributes.cpython-38.pyc | Bin 0 -> 67789 bytes .../orm/__pycache__/base.cpython-38.pyc | Bin 0 -> 24748 bytes .../bulk_persistence.cpython-38.pyc | Bin 0 -> 42069 bytes .../__pycache__/clsregistry.cpython-38.pyc | Bin 0 -> 16106 bytes .../__pycache__/collections.cpython-38.pyc | Bin 0 -> 47175 bytes .../orm/__pycache__/context.cpython-38.pyc | Bin 0 -> 63724 bytes .../orm/__pycache__/decl_api.cpython-38.pyc | Bin 0 -> 57940 bytes .../orm/__pycache__/decl_base.cpython-38.pyc | Bin 0 -> 44704 bytes .../orm/__pycache__/dependency.cpython-38.pyc | Bin 0 -> 23961 bytes .../descriptor_props.cpython-38.pyc | Bin 0 -> 33933 bytes .../orm/__pycache__/dynamic.cpython-38.pyc | Bin 0 -> 9343 bytes .../orm/__pycache__/evaluator.cpython-38.pyc | Bin 0 -> 12458 bytes .../orm/__pycache__/events.cpython-38.pyc | Bin 0 -> 125659 bytes .../orm/__pycache__/exc.cpython-38.pyc | Bin 0 -> 8094 bytes .../orm/__pycache__/identity.cpython-38.pyc | Bin 0 -> 9225 bytes .../instrumentation.cpython-38.pyc | Bin 0 -> 22292 bytes .../orm/__pycache__/interfaces.cpython-38.pyc | Bin 0 -> 45568 bytes .../orm/__pycache__/loading.cpython-38.pyc | Bin 0 -> 29762 bytes .../mapped_collection.cpython-38.pyc | Bin 0 -> 18003 bytes .../orm/__pycache__/mapper.cpython-38.pyc | Bin 0 -> 119207 bytes .../__pycache__/path_registry.cpython-38.pyc | Bin 0 -> 21702 bytes .../__pycache__/persistence.cpython-38.pyc | Bin 0 -> 28005 bytes .../orm/__pycache__/properties.cpython-38.pyc | Bin 0 -> 22813 bytes .../orm/__pycache__/query.cpython-38.pyc | Bin 0 -> 105842 bytes .../__pycache__/relationships.cpython-38.pyc | Bin 0 -> 85326 bytes .../orm/__pycache__/scoping.cpython-38.pyc | Bin 0 -> 74824 bytes .../orm/__pycache__/session.cpython-38.pyc | Bin 0 -> 154858 bytes .../orm/__pycache__/state.cpython-38.pyc | Bin 0 -> 32963 bytes .../__pycache__/state_changes.cpython-38.pyc | Bin 0 -> 4897 bytes .../orm/__pycache__/strategies.cpython-38.pyc | Bin 0 -> 61000 bytes .../strategy_options.cpython-38.pyc | Bin 0 -> 64637 bytes .../orm/__pycache__/sync.cpython-38.pyc | Bin 0 -> 3974 bytes .../orm/__pycache__/unitofwork.cpython-38.pyc | Bin 0 -> 21461 bytes .../orm/__pycache__/util.cpython-38.pyc | Bin 0 -> 60386 bytes .../orm/__pycache__/writeonly.cpython-38.pyc | Bin 0 -> 19258 bytes .../sqlalchemy/orm/_orm_constructors.py | 2661 +++++ .../site-packages/sqlalchemy/orm/_typing.py | 179 + .../sqlalchemy/orm/attributes.py | 2845 ++++++ .../site-packages/sqlalchemy/orm/base.py | 971 ++ .../sqlalchemy/orm/bulk_persistence.py | 2135 ++++ .../sqlalchemy/orm/clsregistry.py | 571 ++ .../sqlalchemy/orm/collections.py | 1627 +++ .../site-packages/sqlalchemy/orm/context.py | 3334 +++++++ .../site-packages/sqlalchemy/orm/decl_api.py | 2004 ++++ .../site-packages/sqlalchemy/orm/decl_base.py | 2192 +++++ .../sqlalchemy/orm/dependency.py | 1302 +++ .../sqlalchemy/orm/descriptor_props.py | 1092 +++ .../site-packages/sqlalchemy/orm/dynamic.py | 300 + .../site-packages/sqlalchemy/orm/evaluator.py | 379 + .../site-packages/sqlalchemy/orm/events.py | 3252 ++++++ .../site-packages/sqlalchemy/orm/exc.py | 237 + .../site-packages/sqlalchemy/orm/identity.py | 302 + .../sqlalchemy/orm/instrumentation.py | 754 ++ .../sqlalchemy/orm/interfaces.py | 1496 +++ .../site-packages/sqlalchemy/orm/loading.py | 1686 ++++ .../sqlalchemy/orm/mapped_collection.py | 557 ++ .../site-packages/sqlalchemy/orm/mapper.py | 4435 +++++++++ .../sqlalchemy/orm/path_registry.py | 809 ++ .../sqlalchemy/orm/persistence.py | 1788 ++++ .../sqlalchemy/orm/properties.py | 935 ++ .../site-packages/sqlalchemy/orm/query.py | 3459 +++++++ .../sqlalchemy/orm/relationships.py | 3508 +++++++ .../site-packages/sqlalchemy/orm/scoping.py | 2148 ++++ .../site-packages/sqlalchemy/orm/session.py | 5280 ++++++++++ .../site-packages/sqlalchemy/orm/state.py | 1143 +++ .../sqlalchemy/orm/state_changes.py | 196 + .../sqlalchemy/orm/strategies.py | 3470 +++++++ .../sqlalchemy/orm/strategy_options.py | 2568 +++++ .../site-packages/sqlalchemy/orm/sync.py | 164 + .../sqlalchemy/orm/unitofwork.py | 796 ++ .../site-packages/sqlalchemy/orm/util.py | 2403 +++++ .../site-packages/sqlalchemy/orm/writeonly.py | 674 ++ .../site-packages/sqlalchemy/pool/__init__.py | 44 + .../pool/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1497 bytes .../pool/__pycache__/base.cpython-38.pyc | Bin 0 -> 40895 bytes .../pool/__pycache__/events.cpython-38.pyc | Bin 0 -> 13131 bytes .../pool/__pycache__/impl.cpython-38.pyc | Bin 0 -> 18638 bytes .../site-packages/sqlalchemy/pool/base.py | 1516 +++ .../site-packages/sqlalchemy/pool/events.py | 372 + .../site-packages/sqlalchemy/pool/impl.py | 579 ++ .../site-packages/sqlalchemy/py.typed | 0 .../site-packages/sqlalchemy/schema.py | 69 + .../site-packages/sqlalchemy/sql/__init__.py | 145 + .../sql/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 4280 bytes .../_dml_constructors.cpython-38.pyc | Bin 0 -> 3905 bytes .../_elements_constructors.cpython-38.pyc | Bin 0 -> 63340 bytes .../sql/__pycache__/_orm_types.cpython-38.pyc | Bin 0 -> 611 bytes .../sql/__pycache__/_py_util.cpython-38.pyc | Bin 0 -> 2470 bytes .../_selectable_constructors.cpython-38.pyc | Bin 0 -> 21883 bytes .../sql/__pycache__/_typing.cpython-38.pyc | Bin 0 -> 11855 bytes .../sql/__pycache__/annotation.cpython-38.pyc | Bin 0 -> 14742 bytes .../sql/__pycache__/base.cpython-38.pyc | Bin 0 -> 77759 bytes .../sql/__pycache__/cache_key.cpython-38.pyc | Bin 0 -> 25924 bytes .../sql/__pycache__/coercions.cpython-38.pyc | Bin 0 -> 33793 bytes .../sql/__pycache__/compiler.cpython-38.pyc | Bin 0 -> 178270 bytes .../sql/__pycache__/crud.cpython-38.pyc | Bin 0 -> 27559 bytes .../sql/__pycache__/ddl.cpython-38.pyc | Bin 0 -> 45977 bytes .../default_comparator.cpython-38.pyc | Bin 0 -> 9833 bytes .../sql/__pycache__/dml.cpython-38.pyc | Bin 0 -> 60575 bytes .../sql/__pycache__/elements.cpython-38.pyc | Bin 0 -> 159135 bytes .../sql/__pycache__/events.cpython-38.pyc | Bin 0 -> 18867 bytes .../sql/__pycache__/expression.cpython-38.pyc | Bin 0 -> 4997 bytes .../sql/__pycache__/functions.cpython-38.pyc | Bin 0 -> 63386 bytes .../sql/__pycache__/lambdas.cpython-38.pyc | Bin 0 -> 38495 bytes .../sql/__pycache__/naming.cpython-38.pyc | Bin 0 -> 5315 bytes .../sql/__pycache__/operators.cpython-38.pyc | Bin 0 -> 77393 bytes .../sql/__pycache__/roles.cpython-38.pyc | Bin 0 -> 10872 bytes .../sql/__pycache__/schema.cpython-38.pyc | Bin 0 -> 193522 bytes .../sql/__pycache__/selectable.cpython-38.pyc | Bin 0 -> 210551 bytes .../sql/__pycache__/sqltypes.cpython-38.pyc | Bin 0 -> 122107 bytes .../sql/__pycache__/traversals.cpython-38.pyc | Bin 0 -> 31486 bytes .../sql/__pycache__/type_api.cpython-38.pyc | Bin 0 -> 72737 bytes .../sql/__pycache__/util.cpython-38.pyc | Bin 0 -> 36410 bytes .../sql/__pycache__/visitors.cpython-38.pyc | Bin 0 -> 28902 bytes .../sqlalchemy/sql/_dml_constructors.py | 132 + .../sqlalchemy/sql/_elements_constructors.py | 1872 ++++ .../sqlalchemy/sql/_orm_types.py | 20 + .../site-packages/sqlalchemy/sql/_py_util.py | 75 + .../sql/_selectable_constructors.py | 763 ++ .../site-packages/sqlalchemy/sql/_typing.py | 468 + .../sqlalchemy/sql/annotation.py | 585 ++ .../site-packages/sqlalchemy/sql/base.py | 2219 +++++ .../site-packages/sqlalchemy/sql/cache_key.py | 1057 ++ .../site-packages/sqlalchemy/sql/coercions.py | 1403 +++ .../site-packages/sqlalchemy/sql/compiler.py | 7999 +++++++++++++++ .../site-packages/sqlalchemy/sql/crud.py | 1744 ++++ .../site-packages/sqlalchemy/sql/ddl.py | 1444 +++ .../sqlalchemy/sql/default_comparator.py | 551 ++ .../site-packages/sqlalchemy/sql/dml.py | 1850 ++++ .../site-packages/sqlalchemy/sql/elements.py | 5588 +++++++++++ .../site-packages/sqlalchemy/sql/events.py | 458 + .../sqlalchemy/sql/expression.py | 159 + .../site-packages/sqlalchemy/sql/functions.py | 2104 ++++ .../site-packages/sqlalchemy/sql/lambdas.py | 1442 +++ .../site-packages/sqlalchemy/sql/naming.py | 209 + .../site-packages/sqlalchemy/sql/operators.py | 2623 +++++ .../site-packages/sqlalchemy/sql/roles.py | 323 + .../site-packages/sqlalchemy/sql/schema.py | 6222 ++++++++++++ .../sqlalchemy/sql/selectable.py | 7235 ++++++++++++++ .../site-packages/sqlalchemy/sql/sqltypes.py | 3930 ++++++++ .../sqlalchemy/sql/traversals.py | 1024 ++ .../site-packages/sqlalchemy/sql/type_api.py | 2368 +++++ .../site-packages/sqlalchemy/sql/util.py | 1485 +++ .../site-packages/sqlalchemy/sql/visitors.py | 1164 +++ .../sqlalchemy/testing/__init__.py | 96 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 3204 bytes .../__pycache__/assertions.cpython-38.pyc | Bin 0 -> 29003 bytes .../__pycache__/assertsql.cpython-38.pyc | Bin 0 -> 13140 bytes .../__pycache__/asyncio.cpython-38.pyc | Bin 0 -> 3223 bytes .../testing/__pycache__/config.cpython-38.pyc | Bin 0 -> 13984 bytes .../__pycache__/engines.cpython-38.pyc | Bin 0 -> 13890 bytes .../__pycache__/entities.cpython-38.pyc | Bin 0 -> 2913 bytes .../__pycache__/exclusions.cpython-38.pyc | Bin 0 -> 14184 bytes .../__pycache__/pickleable.cpython-38.pyc | Bin 0 -> 5541 bytes .../__pycache__/profiling.cpython-38.pyc | Bin 0 -> 8244 bytes .../__pycache__/provision.cpython-38.pyc | Bin 0 -> 13540 bytes .../__pycache__/requirements.cpython-38.pyc | Bin 0 -> 75234 bytes .../testing/__pycache__/schema.cpython-38.pyc | Bin 0 -> 6520 bytes .../testing/__pycache__/util.cpython-38.pyc | Bin 0 -> 14994 bytes .../__pycache__/warnings.cpython-38.pyc | Bin 0 -> 1686 bytes .../sqlalchemy/testing/assertions.py | 991 ++ .../sqlalchemy/testing/assertsql.py | 516 + .../sqlalchemy/testing/asyncio.py | 135 + .../sqlalchemy/testing/config.py | 423 + .../sqlalchemy/testing/engines.py | 474 + .../sqlalchemy/testing/entities.py | 117 + .../sqlalchemy/testing/exclusions.py | 435 + .../sqlalchemy/testing/fixtures/__init__.py | 28 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 899 bytes .../fixtures/__pycache__/base.cpython-38.pyc | Bin 0 -> 7768 bytes .../fixtures/__pycache__/mypy.cpython-38.pyc | Bin 0 -> 8074 bytes .../fixtures/__pycache__/orm.cpython-38.pyc | Bin 0 -> 7772 bytes .../fixtures/__pycache__/sql.cpython-38.pyc | Bin 0 -> 13180 bytes .../sqlalchemy/testing/fixtures/base.py | 371 + .../sqlalchemy/testing/fixtures/mypy.py | 332 + .../sqlalchemy/testing/fixtures/orm.py | 227 + .../sqlalchemy/testing/fixtures/sql.py | 503 + .../sqlalchemy/testing/pickleable.py | 155 + .../sqlalchemy/testing/plugin/__init__.py | 6 + .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 218 bytes .../__pycache__/bootstrap.cpython-38.pyc | Bin 0 -> 1580 bytes .../__pycache__/plugin_base.cpython-38.pyc | Bin 0 -> 18751 bytes .../__pycache__/pytestplugin.cpython-38.pyc | Bin 0 -> 20682 bytes .../sqlalchemy/testing/plugin/bootstrap.py | 51 + .../sqlalchemy/testing/plugin/plugin_base.py | 779 ++ .../sqlalchemy/testing/plugin/pytestplugin.py | 873 ++ .../sqlalchemy/testing/profiling.py | 327 + .../sqlalchemy/testing/provision.py | 502 + .../sqlalchemy/testing/requirements.py | 1929 ++++ .../sqlalchemy/testing/schema.py | 224 + .../sqlalchemy/testing/suite/__init__.py | 19 + .../suite/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 542 bytes .../suite/__pycache__/test_cte.cpython-38.pyc | Bin 0 -> 5299 bytes .../suite/__pycache__/test_ddl.cpython-38.pyc | Bin 0 -> 11477 bytes .../test_deprecations.cpython-38.pyc | Bin 0 -> 4330 bytes .../__pycache__/test_dialect.cpython-38.pyc | Bin 0 -> 20482 bytes .../__pycache__/test_insert.cpython-38.pyc | Bin 0 -> 13738 bytes .../test_reflection.cpython-38.pyc | Bin 0 -> 82642 bytes .../__pycache__/test_results.cpython-38.pyc | Bin 0 -> 13171 bytes .../__pycache__/test_rowcount.cpython-38.pyc | Bin 0 -> 5944 bytes .../__pycache__/test_select.cpython-38.pyc | Bin 0 -> 61318 bytes .../__pycache__/test_sequence.cpython-38.pyc | Bin 0 -> 8874 bytes .../__pycache__/test_types.cpython-38.pyc | Bin 0 -> 57652 bytes .../test_unicode_ddl.cpython-38.pyc | Bin 0 -> 3739 bytes .../test_update_delete.cpython-38.pyc | Bin 0 -> 3572 bytes .../sqlalchemy/testing/suite/test_cte.py | 237 + .../sqlalchemy/testing/suite/test_ddl.py | 389 + .../testing/suite/test_deprecations.py | 153 + .../sqlalchemy/testing/suite/test_dialect.py | 776 ++ .../sqlalchemy/testing/suite/test_insert.py | 630 ++ .../testing/suite/test_reflection.py | 3390 +++++++ .../sqlalchemy/testing/suite/test_results.py | 504 + .../sqlalchemy/testing/suite/test_rowcount.py | 258 + .../sqlalchemy/testing/suite/test_select.py | 2008 ++++ .../sqlalchemy/testing/suite/test_sequence.py | 317 + .../sqlalchemy/testing/suite/test_types.py | 2145 ++++ .../testing/suite/test_unicode_ddl.py | 189 + .../testing/suite/test_update_delete.py | 139 + .../site-packages/sqlalchemy/testing/util.py | 535 + .../sqlalchemy/testing/warnings.py | 52 + .../site-packages/sqlalchemy/types.py | 74 + .../site-packages/sqlalchemy/util/__init__.py | 162 + .../util/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 5591 bytes .../__pycache__/_collections.cpython-38.pyc | Bin 0 -> 24493 bytes .../_concurrency_py3k.cpython-38.pyc | Bin 0 -> 8157 bytes .../util/__pycache__/_has_cy.cpython-38.pyc | Bin 0 -> 896 bytes .../_py_collections.cpython-38.pyc | Bin 0 -> 21328 bytes .../util/__pycache__/compat.cpython-38.pyc | Bin 0 -> 8627 bytes .../__pycache__/concurrency.cpython-38.pyc | Bin 0 -> 3206 bytes .../__pycache__/deprecations.cpython-38.pyc | Bin 0 -> 9482 bytes .../__pycache__/langhelpers.cpython-38.pyc | Bin 0 -> 61587 bytes .../util/__pycache__/preloaded.cpython-38.pyc | Bin 0 -> 5142 bytes .../util/__pycache__/queue.cpython-38.pyc | Bin 0 -> 10509 bytes .../__pycache__/tool_support.cpython-38.pyc | Bin 0 -> 5498 bytes .../__pycache__/topological.cpython-38.pyc | Bin 0 -> 2876 bytes .../util/__pycache__/typing.cpython-38.pyc | Bin 0 -> 19031 bytes .../sqlalchemy/util/_collections.py | 717 ++ .../sqlalchemy/util/_concurrency_py3k.py | 288 + .../site-packages/sqlalchemy/util/_has_cy.py | 40 + .../sqlalchemy/util/_py_collections.py | 541 + .../site-packages/sqlalchemy/util/compat.py | 316 + .../sqlalchemy/util/concurrency.py | 110 + .../sqlalchemy/util/deprecations.py | 401 + .../sqlalchemy/util/langhelpers.py | 2306 +++++ .../sqlalchemy/util/preloaded.py | 150 + .../site-packages/sqlalchemy/util/queue.py | 322 + .../sqlalchemy/util/tool_support.py | 201 + .../sqlalchemy/util/topological.py | 120 + .../site-packages/sqlalchemy/util/typing.py | 734 ++ .../INSTALLER | 1 + .../METADATA | 68 + .../typing_extensions-4.13.2.dist-info/RECORD | 7 + .../typing_extensions-4.13.2.dist-info/WHEEL | 4 + .../licenses/LICENSE | 279 + .../site-packages/typing_extensions.py | 4584 +++++++++ .../zipp-3.20.2.dist-info/INSTALLER | 1 + .../zipp-3.20.2.dist-info/LICENSE | 17 + .../zipp-3.20.2.dist-info/METADATA | 106 + .../zipp-3.20.2.dist-info/RECORD | 16 + .../site-packages/zipp-3.20.2.dist-info/WHEEL | 5 + .../zipp-3.20.2.dist-info/top_level.txt | 1 + .../python3.8/site-packages/zipp/__init__.py | 452 + .../zipp/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 14369 bytes .../zipp/__pycache__/glob.cpython-38.pyc | Bin 0 -> 4287 bytes .../site-packages/zipp/compat/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 204 bytes .../compat/__pycache__/overlay.cpython-38.pyc | Bin 0 -> 1173 bytes .../compat/__pycache__/py310.cpython-38.pyc | Bin 0 -> 441 bytes .../site-packages/zipp/compat/overlay.py | 37 + .../site-packages/zipp/compat/py310.py | 13 + venv/lib/python3.8/site-packages/zipp/glob.py | 114 + venv/lib64 | 1 + venv/pyvenv.cfg | 3 + 2187 files changed, 497659 insertions(+) create mode 100644 alembic.ini create mode 100644 lib/__pycache__/db.cpython-38.pyc create mode 100644 lib/__pycache__/debug.cpython-38.pyc create mode 100644 lib/__pycache__/models.cpython-38.pyc create mode 100644 lib/__pycache__/seed.cpython-38.pyc create mode 100644 lib/db.py create mode 100644 lib/debug.py create mode 100644 lib/freebies.db create mode 100644 lib/migrations/README create mode 100644 lib/migrations/__pycache__/env.cpython-38.pyc create mode 100644 lib/migrations/env.py create mode 100644 lib/migrations/script.py.mako create mode 100644 lib/migrations/versions/__pycache__/a5f8be6737c8_initial_tables.cpython-38.pyc create mode 100644 lib/migrations/versions/a5f8be6737c8_initial_tables.py create mode 100644 lib/models.py create mode 100644 lib/seed.py create mode 100644 venv/bin/Activate.ps1 create mode 100644 venv/bin/activate create mode 100644 venv/bin/activate.csh create mode 100644 venv/bin/activate.fish create mode 100755 venv/bin/alembic create mode 100755 venv/bin/mako-render create mode 100755 venv/bin/pip create mode 100755 venv/bin/pip3 create mode 100755 venv/bin/pip3.8 create mode 120000 venv/bin/python create mode 120000 venv/bin/python3 create mode 100644 venv/include/site/python3.8/greenlet/greenlet.h create mode 100644 venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst create mode 100644 venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/__pycache__/typing_extensions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/_distutils_hack/__init__.py create mode 100644 venv/lib/python3.8/site-packages/_distutils_hack/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/_distutils_hack/__pycache__/override.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/_distutils_hack/override.py create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/LICENSE create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/REQUESTED create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/entry_points.txt create mode 100644 venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/alembic/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/__main__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/__main__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/command.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/config.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/context.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/environment.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/migration.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/__pycache__/op.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/api.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/compare.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/render.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/api.py create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/compare.py create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/render.py create mode 100644 venv/lib/python3.8/site-packages/alembic/autogenerate/rewriter.py create mode 100644 venv/lib/python3.8/site-packages/alembic/command.py create mode 100644 venv/lib/python3.8/site-packages/alembic/config.py create mode 100644 venv/lib/python3.8/site-packages/alembic/context.py create mode 100644 venv/lib/python3.8/site-packages/alembic/context.pyi create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/_autogen.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/impl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/mssql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/mysql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/oracle.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/postgresql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/sqlite.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/_autogen.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/base.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/impl.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/mssql.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/mysql.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/oracle.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/postgresql.py create mode 100644 venv/lib/python3.8/site-packages/alembic/ddl/sqlite.py create mode 100644 venv/lib/python3.8/site-packages/alembic/environment.py create mode 100644 venv/lib/python3.8/site-packages/alembic/migration.py create mode 100644 venv/lib/python3.8/site-packages/alembic/op.py create mode 100644 venv/lib/python3.8/site-packages/alembic/op.pyi create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/__pycache__/batch.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/__pycache__/ops.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/__pycache__/schemaobj.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/__pycache__/toimpl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/base.py create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/batch.py create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/ops.py create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/schemaobj.py create mode 100644 venv/lib/python3.8/site-packages/alembic/operations/toimpl.py create mode 100644 venv/lib/python3.8/site-packages/alembic/py.typed create mode 100644 venv/lib/python3.8/site-packages/alembic/runtime/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/environment.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/migration.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/runtime/environment.py create mode 100644 venv/lib/python3.8/site-packages/alembic/runtime/migration.py create mode 100644 venv/lib/python3.8/site-packages/alembic/script/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/script/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/script/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/script/__pycache__/revision.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/script/__pycache__/write_hooks.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/script/base.py create mode 100644 venv/lib/python3.8/site-packages/alembic/script/revision.py create mode 100644 venv/lib/python3.8/site-packages/alembic/script/write_hooks.py create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/async/README create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/async/__pycache__/env.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/async/alembic.ini.mako create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/async/env.py create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/async/script.py.mako create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/generic/README create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/generic/__pycache__/env.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/generic/alembic.ini.mako create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/generic/env.py create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/generic/script.py.mako create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/multidb/README create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/multidb/__pycache__/env.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/multidb/alembic.ini.mako create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/multidb/env.py create mode 100644 venv/lib/python3.8/site-packages/alembic/templates/multidb/script.py.mako create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/assertions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/env.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/fixtures.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/requirements.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/schemacompare.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/__pycache__/warnings.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/assertions.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/env.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/fixtures.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/plugin/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/plugin/bootstrap.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/requirements.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/schemacompare.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_computed.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_identity.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_environment.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_op.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/_autogen_fixtures.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_comments.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_computed.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_diffs.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_fks.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_identity.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/test_environment.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/suite/test_op.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/util.py create mode 100644 venv/lib/python3.8/site-packages/alembic/testing/warnings.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__init__.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/editor.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/exc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/langhelpers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/messaging.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/pyfiles.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/__pycache__/sqla_compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/alembic/util/compat.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/editor.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/exc.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/langhelpers.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/messaging.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/pyfiles.py create mode 100644 venv/lib/python3.8/site-packages/alembic/util/sqla_compat.py create mode 100644 venv/lib/python3.8/site-packages/distutils-precedence.pth create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/AUTHORS create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE.PSF create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/greenlet/CObjects.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/PyGreenlet.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/PyGreenlet.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/PyGreenletUnswitchable.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/PyModule.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TBrokenGreenlet.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TExceptionState.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TGreenlet.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TGreenlet.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TGreenletGlobals.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TMainGreenlet.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TPythonState.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TStackState.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TThreadState.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TThreadStateCreator.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TThreadStateDestroy.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/TUserGreenlet.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/__init__.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/__pycache__/__init__.cpython-38.pyc create mode 100755 venv/lib/python3.8/site-packages/greenlet/_greenlet.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet.cpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_allocator.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_compiler_compat.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_add_pending.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_compat.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_exceptions.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_internal.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_refs.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_slp_switch.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/greenlet_thread_support.hpp create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/__init__.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/setup_switch_x64_masm.cmd create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_aarch64_gcc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_alpha_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_amd64_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_gcc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_ios.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_masm.asm create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_masm.obj create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_msvc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_csky_gcc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_loongarch64_linux.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_m68k_gcc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_mips_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_aix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_linux.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_aix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_linux.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_macosx.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_riscv_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_s390_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_sh_gcc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_sparc_sun_gcc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_x32_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_masm.asm create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_masm.obj create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_msvc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_msvc.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_unix.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/slp_platformselect.h create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__init__.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_gc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_generator.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_throw.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/_test_extension.c create mode 100755 venv/lib/python3.8/site-packages/greenlet/tests/_test_extension.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/_test_extension_cpp.cpp create mode 100755 venv/lib/python3.8/site-packages/greenlet/tests/_test_extension_cpp.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/fail_clearing_run_switches.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/fail_cpp_exception.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/fail_initialstub_already_started.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/fail_slp_switch.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets2.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_two_greenlets.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/leakcheck.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_contextvars.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_cpp.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_extension_interface.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_gc.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_generator.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_generator_nested.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet_trash.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_leaks.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_stack_saved.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_throw.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_tracing.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_version.py create mode 100644 venv/lib/python3.8/site-packages/greenlet/tests/test_weakref.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/LICENSE create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__init__.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_adapters.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_collections.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_functools.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_itertools.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_meta.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_text.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/diagnose.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/_adapters.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/_collections.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/_compat.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/_functools.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/_itertools.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/_meta.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/_text.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/compat/__init__.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/compat/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/compat/__pycache__/py311.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/compat/__pycache__/py39.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/compat/py311.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/compat/py39.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/diagnose.py create mode 100644 venv/lib/python3.8/site-packages/importlib_metadata/py.typed create mode 100644 venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/LICENSE create mode 100644 venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__init__.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_adapters.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_common.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_functional.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_itertools.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/abc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/readers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/__pycache__/simple.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/_adapters.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/_common.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/_functional.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/_itertools.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/abc.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/compat/__init__.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/compat/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/compat/__pycache__/py38.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/compat/__pycache__/py39.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/compat/py38.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/compat/py39.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/future/__init__.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/future/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/future/__pycache__/adapters.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/future/adapters.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/py.typed create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/readers.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/simple.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__init__.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/_path.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_compatibilty_files.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_contents.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_custom.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_files.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_functional.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_open.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_path.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_read.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_reader.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_resource.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/zip.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/_path.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__init__.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__pycache__/py312.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__pycache__/py39.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py312.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py39.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_compatibilty_files.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_contents.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_custom.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_files.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_functional.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_open.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_path.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_read.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_reader.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/test_resource.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/util.py create mode 100644 venv/lib/python3.8/site-packages/importlib_resources/tests/zip.py create mode 100644 venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/entry_points.txt create mode 100644 venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/mako/__init__.py create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/_ast_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/ast.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/cmd.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/codegen.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/exceptions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/filters.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/lexer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/lookup.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/parsetree.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/pygen.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/pyparser.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/runtime.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/template.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/_ast_util.py create mode 100644 venv/lib/python3.8/site-packages/mako/ast.py create mode 100644 venv/lib/python3.8/site-packages/mako/cache.py create mode 100644 venv/lib/python3.8/site-packages/mako/cmd.py create mode 100644 venv/lib/python3.8/site-packages/mako/codegen.py create mode 100644 venv/lib/python3.8/site-packages/mako/compat.py create mode 100644 venv/lib/python3.8/site-packages/mako/exceptions.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__init__.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/autohandler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/babelplugin.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/beaker_cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/extract.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/linguaplugin.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/preprocessors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/pygmentplugin.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/__pycache__/turbogears.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/ext/autohandler.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/babelplugin.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/beaker_cache.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/extract.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/linguaplugin.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/preprocessors.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/pygmentplugin.py create mode 100644 venv/lib/python3.8/site-packages/mako/ext/turbogears.py create mode 100644 venv/lib/python3.8/site-packages/mako/filters.py create mode 100644 venv/lib/python3.8/site-packages/mako/lexer.py create mode 100644 venv/lib/python3.8/site-packages/mako/lookup.py create mode 100644 venv/lib/python3.8/site-packages/mako/parsetree.py create mode 100644 venv/lib/python3.8/site-packages/mako/pygen.py create mode 100644 venv/lib/python3.8/site-packages/mako/pyparser.py create mode 100644 venv/lib/python3.8/site-packages/mako/runtime.py create mode 100644 venv/lib/python3.8/site-packages/mako/template.py create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__init__.py create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__pycache__/_config.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__pycache__/assertions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__pycache__/config.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__pycache__/exclusions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__pycache__/fixtures.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/testing/__pycache__/helpers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/mako/testing/_config.py create mode 100644 venv/lib/python3.8/site-packages/mako/testing/assertions.py create mode 100644 venv/lib/python3.8/site-packages/mako/testing/config.py create mode 100644 venv/lib/python3.8/site-packages/mako/testing/exclusions.py create mode 100644 venv/lib/python3.8/site-packages/mako/testing/fixtures.py create mode 100644 venv/lib/python3.8/site-packages/mako/testing/helpers.py create mode 100644 venv/lib/python3.8/site-packages/mako/util.py create mode 100644 venv/lib/python3.8/site-packages/markupsafe/__init__.py create mode 100644 venv/lib/python3.8/site-packages/markupsafe/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/markupsafe/__pycache__/_native.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/markupsafe/_native.py create mode 100644 venv/lib/python3.8/site-packages/markupsafe/_speedups.c create mode 100755 venv/lib/python3.8/site-packages/markupsafe/_speedups.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/markupsafe/_speedups.pyi create mode 100644 venv/lib/python3.8/site-packages/markupsafe/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/AUTHORS.txt create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/REQUESTED create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/entry_points.txt create mode 100644 venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/pip/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/__main__.py create mode 100644 venv/lib/python3.8/site-packages/pip/__pip-runner__.py create mode 100644 venv/lib/python3.8/site-packages/pip/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/__pycache__/__main__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/__pycache__/__pip-runner__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/build_env.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/configuration.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/exceptions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/main.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/pyproject.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/build_env.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cache.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/main.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/parser.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/base_command.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/command_context.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/index_command.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/main.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/main_parser.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/parser.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/req_command.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/spinners.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/cli/status_codes.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/check.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/completion.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/debug.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/download.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/hash.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/help.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/index.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/install.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/list.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/search.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/show.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/cache.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/check.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/completion.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/configuration.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/debug.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/download.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/freeze.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/hash.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/help.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/index.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/inspect.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/install.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/list.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/search.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/show.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/uninstall.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/commands/wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/configuration.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/base.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/installed.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/sdist.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/distributions/wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/exceptions.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/__pycache__/collector.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/__pycache__/sources.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/collector.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/package_finder.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/index/sources.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/_distutils.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/locations/base.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/main.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/_json.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/base.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_compat.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_dists.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_envs.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/candidate.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/format_control.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/index.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/link.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/scheme.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/target_python.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/candidate.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/direct_url.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/format_control.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/index.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/installation_report.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/link.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/scheme.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/search_scope.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/target_python.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/models/wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/auth.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/download.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/session.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/utils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/auth.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/cache.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/download.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/session.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/utils.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/check.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/metadata_editable.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/build_tracker.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/check.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/freeze.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/operations/prepare.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/pyproject.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/constructors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_file.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_install.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_set.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/constructors.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/req_file.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/req_install.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/req_set.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/base.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/self_outdated_check.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/_log.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/logging.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/misc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/retry.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/urls.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/_jaraco_text.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/_log.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/appdirs.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/compat.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/datetime.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/deprecation.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/egg_link.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/filesystem.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/filetypes.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/glibc.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/hashes.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/logging.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/misc.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/packaging.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/retry.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/subprocess.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/unpacking.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/urls.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/utils/wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/git.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/git.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/subversion.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 venv/lib/python3.8/site-packages/pip/_internal/wheel_builder.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/__pycache__/typing_extensions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/core.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/certifi/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/locators.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/manifest.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/metadata.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/compat.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/database.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/index.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/locators.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/markers.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/resources.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/t32.exe create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/t64-arm.exe create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/t64.exe create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/util.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/version.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/w32.exe create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/w64-arm.exe create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/w64.exe create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distro/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distro/__main__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distro/distro.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/distro/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/core.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/codec.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/compat.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/core.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/intranges.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/package_data.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/_elffile.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/_parser.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/_tokenizer.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/_spdx.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/metadata.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/packaging/version.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__main__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/cmdline.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/console.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/filter.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/filters/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatter.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/_mapping.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/bbcode.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/groff.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/html.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/img.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/irc.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/latex.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/other.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/rtf.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/svg.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/terminal.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/terminal256.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/lexer.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/lexers/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/lexers/_mapping.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/lexers/python.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/modeline.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/plugin.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/regexopt.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/scanner.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/sphinxext.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/style.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/styles/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/styles/_mapping.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/token.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/unistring.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pygments/util.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_impl.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/api.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/help.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/models.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/__version__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/adapters.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/api.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/auth.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/certs.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/compat.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/cookies.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/help.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/hooks.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/models.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/packages.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/sessions.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/structures.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/requests/utils.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/__pycache__/resolvers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__main__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/align.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/box.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/color.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/console.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/control.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/json.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/live.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/region.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/status.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/style.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/table.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/text.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_cell_widths.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_emoji_codes.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_emoji_replace.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_export_format.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_extension.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_fileno.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_inspect.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_log_render.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_loop.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_null_file.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_palettes.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_pick.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_ratio.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_spinners.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_stack.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_timer.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_win32_console.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows_renderer.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/_wrap.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/abc.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/align.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/ansi.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/bar.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/box.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/cells.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/color.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/color_triplet.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/columns.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/console.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/constrain.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/containers.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/control.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/default_styles.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/diagnose.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/emoji.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/errors.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/file_proxy.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/filesize.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/highlighter.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/json.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/jupyter.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/layout.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/live.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/live_render.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/logging.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/markup.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/measure.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/padding.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/pager.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/palette.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/panel.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/pretty.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/progress.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/progress_bar.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/prompt.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/protocol.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/region.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/repr.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/rule.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/scope.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/screen.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/segment.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/spinner.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/status.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/style.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/styled.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/syntax.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/table.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/terminal_theme.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/text.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/theme.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/themes.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/traceback.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/rich/tree.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/_re.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/_types.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/tomli/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/_api.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/_macos.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/_openssl.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/_ssl_constants.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/_windows.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/truststore/py.typed create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/typing_extensions.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/request.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/response.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 venv/lib/python3.8/site-packages/pip/_vendor/vendor.txt create mode 100644 venv/lib/python3.8/site-packages/pip/py.typed create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/appdirs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/appdirs.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__about__.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_typing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_compat.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_typing.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/version.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/_vendor/pyparsing.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/extern/__init__.py create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/__pycache__/setup.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/LICENSE create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/dependency_links.txt create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/entry_points.txt create mode 100644 venv/lib/python3.8/site-packages/setuptools-56.0.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/setuptools/__init__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/_deprecation_warning.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/_imp.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/archive_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/build_meta.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/config.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/dep_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/depends.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/dist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/errors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/extension.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/glob.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/installer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/launch.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/lib2to3_ex.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/monkey.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/msvc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/namespaces.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/package_index.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/py34compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/sandbox.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/ssl_support.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/unicode_utils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/wheel.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/__pycache__/windows_support.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_deprecation_warning.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__init__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/_msvccompiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/archive_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/bcppcompiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/ccompiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/cmd.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/config.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/core.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/cygwinccompiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/debug.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/dep_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/dir_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/dist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/errors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/extension.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/fancy_getopt.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/file_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/filelist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/log.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/msvc9compiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/msvccompiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/py35compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/py38compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/spawn.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/sysconfig.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/text_file.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/unixccompiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/__pycache__/versionpredicate.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/_msvccompiler.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/archive_util.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/bcppcompiler.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/ccompiler.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/cmd.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__init__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/bdist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/bdist_dumb.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/bdist_msi.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/bdist_rpm.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/bdist_wininst.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/build.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/build_clib.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/build_ext.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/build_py.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/build_scripts.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/check.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/clean.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/config.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/install.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/install_data.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/install_egg_info.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/install_headers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/install_lib.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/install_scripts.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/py37compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/register.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/sdist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/__pycache__/upload.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/bdist.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/bdist_dumb.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/bdist_msi.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/bdist_rpm.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/bdist_wininst.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/build.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/build_clib.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/build_ext.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/build_py.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/build_scripts.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/check.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/clean.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/config.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/install.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/install_data.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/install_egg_info.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/install_headers.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/install_lib.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/install_scripts.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/py37compat.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/register.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/sdist.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/command/upload.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/config.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/core.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/cygwinccompiler.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/debug.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/dep_util.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/dir_util.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/dist.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/errors.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/extension.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/fancy_getopt.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/file_util.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/filelist.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/log.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/msvc9compiler.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/msvccompiler.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/py35compat.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/py38compat.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/spawn.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/sysconfig.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/text_file.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/unixccompiler.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/util.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/version.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_distutils/versionpredicate.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_imp.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/__init__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/__pycache__/ordered_set.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/__pycache__/pyparsing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/ordered_set.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__about__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/__about__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/_compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/_structures.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/_typing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/markers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/requirements.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/tags.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/utils.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/__pycache__/version.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/_compat.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/_typing.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/packaging/version.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/_vendor/pyparsing.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/archive_util.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/build_meta.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/cli-32.exe create mode 100644 venv/lib/python3.8/site-packages/setuptools/cli-64.exe create mode 100644 venv/lib/python3.8/site-packages/setuptools/cli.exe create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__init__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/alias.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/bdist_egg.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/bdist_rpm.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/build_clib.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/build_ext.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/build_py.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/develop.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/dist_info.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/easy_install.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/egg_info.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/install.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/install_egg_info.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/install_lib.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/install_scripts.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/py36compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/register.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/rotate.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/saveopts.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/sdist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/setopt.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/test.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/upload.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/__pycache__/upload_docs.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/alias.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/bdist_egg.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/bdist_rpm.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/build_clib.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/build_ext.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/build_py.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/develop.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/dist_info.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/easy_install.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/egg_info.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/install.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/install_egg_info.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/install_lib.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/install_scripts.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/launcher manifest.xml create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/py36compat.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/register.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/rotate.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/saveopts.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/sdist.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/setopt.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/test.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/upload.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/command/upload_docs.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/config.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/dep_util.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/depends.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/dist.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/errors.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/extension.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/extern/__init__.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/extern/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/setuptools/glob.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/gui-32.exe create mode 100644 venv/lib/python3.8/site-packages/setuptools/gui-64.exe create mode 100644 venv/lib/python3.8/site-packages/setuptools/gui.exe create mode 100644 venv/lib/python3.8/site-packages/setuptools/installer.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/launch.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/lib2to3_ex.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/monkey.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/msvc.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/namespaces.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/package_index.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/py34compat.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/sandbox.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/script (dev).tmpl create mode 100644 venv/lib/python3.8/site-packages/setuptools/script.tmpl create mode 100644 venv/lib/python3.8/site-packages/setuptools/ssl_support.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/unicode_utils.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/version.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/wheel.py create mode 100644 venv/lib/python3.8/site-packages/setuptools/windows_support.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy-2.0.44.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy-2.0.44.dist-info/LICENSE create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy-2.0.44.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy-2.0.44.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy-2.0.44.dist-info/REQUESTED create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy-2.0.44.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy-2.0.44.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__pycache__/events.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__pycache__/exc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__pycache__/inspection.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__pycache__/log.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__pycache__/schema.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/__pycache__/types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/__pycache__/aioodbc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/__pycache__/asyncio.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/__pycache__/pyodbc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/aioodbc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/asyncio.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/connectors/pyodbc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/__pycache__/__init__.cpython-38.pyc create mode 100755 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/collections.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/collections.pyx create mode 100755 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/immutabledict.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/immutabledict.pxd create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/immutabledict.pyx create mode 100755 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/processors.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/processors.pyx create mode 100755 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/resultproxy.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/resultproxy.pyx create mode 100755 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/util.cpython-38-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/cyextension/util.pyx create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/_typing.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/json.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/provision.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/aioodbc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/information_schema.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/json.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/provision.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/pymssql.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mssql/pyodbc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/aiomysql.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/asyncmy.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/cymysql.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/dml.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/enumerated.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/expression.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/json.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/mariadb.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/mysqldb.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/provision.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/pymysql.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/pyodbc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/reflection.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/reserved_words.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/mysql/types.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/provision.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/__pycache__/vector.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/cx_oracle.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/dictionary.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/oracledb.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/provision.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/types.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/oracle/vector.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/_psycopg_common.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/array.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/dml.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/ext.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/hstore.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/json.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/named_types.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/operators.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/pg8000.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/pg_catalog.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/provision.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/psycopg.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/psycopg2cffi.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/ranges.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/postgresql/types.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/json.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/dml.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/json.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/provision.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/pysqlcipher.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/sqlite/pysqlite.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/dialects/type_migration_guidelines.txt create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/_py_processors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/_py_row.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/_py_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/characteristics.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/create.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/cursor.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/default.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/events.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/interfaces.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/mock.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/processors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/reflection.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/result.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/row.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/strategies.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/url.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/_py_processors.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/_py_row.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/_py_util.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/characteristics.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/create.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/cursor.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/events.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/interfaces.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/mock.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/processors.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/reflection.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/result.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/row.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/url.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/engine/util.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/__pycache__/api.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/__pycache__/attr.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/__pycache__/legacy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/__pycache__/registry.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/api.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/attr.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/legacy.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/event/registry.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/events.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/exc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/associationproxy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/automap.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/baked.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/compiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/horizontal_shard.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/hybrid.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/indexable.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/instrumentation.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/mutable.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/orderinglist.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/__pycache__/serializer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/associationproxy.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__pycache__/engine.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__pycache__/exc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__pycache__/result.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/__pycache__/session.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/engine.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/exc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/result.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/scoping.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/asyncio/session.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/automap.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/baked.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/compiler.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/declarative/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/declarative/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/declarative/__pycache__/extensions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/declarative/extensions.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/horizontal_shard.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/hybrid.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/indexable.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/instrumentation.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mutable.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__pycache__/apply.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__pycache__/infer.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__pycache__/names.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__pycache__/plugin.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/apply.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/infer.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/names.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/plugin.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/util.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/orderinglist.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/ext/serializer.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/future/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/future/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/future/__pycache__/engine.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/future/engine.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/inspection.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/log.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/context.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/events.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/query.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/session.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/state.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/_orm_constructors.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/_typing.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/attributes.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/bulk_persistence.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/clsregistry.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/collections.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/context.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/decl_api.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/decl_base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/dependency.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/descriptor_props.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/dynamic.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/evaluator.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/events.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/exc.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/identity.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/instrumentation.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/interfaces.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/loading.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/mapped_collection.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/mapper.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/path_registry.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/persistence.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/properties.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/relationships.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/scoping.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/state.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/state_changes.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/strategies.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/strategy_options.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/sync.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/unitofwork.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/util.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/orm/writeonly.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/__pycache__/events.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/events.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/py.typed create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/schema.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/_py_util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/events.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/_dml_constructors.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/_elements_constructors.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/_orm_types.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/_py_util.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/_selectable_constructors.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/_typing.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/annotation.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/cache_key.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/coercions.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/compiler.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/crud.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/ddl.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/default_comparator.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/dml.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/elements.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/events.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/expression.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/functions.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/lambdas.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/naming.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/operators.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/roles.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/schema.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/selectable.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/sqltypes.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/traversals.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/type_api.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/util.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/sql/visitors.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/assertions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/assertsql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/asyncio.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/config.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/engines.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/entities.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/exclusions.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/pickleable.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/profiling.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/provision.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/requirements.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/schema.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/util.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/__pycache__/warnings.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/assertions.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/assertsql.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/asyncio.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/config.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/engines.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/entities.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/exclusions.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/__pycache__/base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/__pycache__/mypy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/__pycache__/orm.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/__pycache__/sql.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/mypy.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/orm.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/fixtures/sql.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/pickleable.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/bootstrap.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/plugin_base.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/plugin/pytestplugin.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/profiling.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/provision.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/requirements.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/schema.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_cte.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_insert.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_results.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_select.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_types.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_cte.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_ddl.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_deprecations.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_dialect.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_insert.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_reflection.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_results.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_rowcount.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_select.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_sequence.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_types.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_unicode_ddl.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/suite/test_update_delete.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/util.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/testing/warnings.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/types.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__init__.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/_collections.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/_has_cy.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/_py_collections.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/compat.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/concurrency.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/deprecations.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/langhelpers.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/preloaded.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/queue.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/tool_support.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/topological.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/__pycache__/typing.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/_collections.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/_concurrency_py3k.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/_has_cy.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/_py_collections.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/concurrency.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/deprecations.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/preloaded.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/queue.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/tool_support.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/topological.py create mode 100644 venv/lib/python3.8/site-packages/sqlalchemy/util/typing.py create mode 100644 venv/lib/python3.8/site-packages/typing_extensions-4.13.2.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/typing_extensions-4.13.2.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/typing_extensions-4.13.2.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/typing_extensions-4.13.2.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/typing_extensions-4.13.2.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.8/site-packages/typing_extensions.py create mode 100644 venv/lib/python3.8/site-packages/zipp-3.20.2.dist-info/INSTALLER create mode 100644 venv/lib/python3.8/site-packages/zipp-3.20.2.dist-info/LICENSE create mode 100644 venv/lib/python3.8/site-packages/zipp-3.20.2.dist-info/METADATA create mode 100644 venv/lib/python3.8/site-packages/zipp-3.20.2.dist-info/RECORD create mode 100644 venv/lib/python3.8/site-packages/zipp-3.20.2.dist-info/WHEEL create mode 100644 venv/lib/python3.8/site-packages/zipp-3.20.2.dist-info/top_level.txt create mode 100644 venv/lib/python3.8/site-packages/zipp/__init__.py create mode 100644 venv/lib/python3.8/site-packages/zipp/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/zipp/__pycache__/glob.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/zipp/compat/__init__.py create mode 100644 venv/lib/python3.8/site-packages/zipp/compat/__pycache__/__init__.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/zipp/compat/__pycache__/overlay.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/zipp/compat/__pycache__/py310.cpython-38.pyc create mode 100644 venv/lib/python3.8/site-packages/zipp/compat/overlay.py create mode 100644 venv/lib/python3.8/site-packages/zipp/compat/py310.py create mode 100644 venv/lib/python3.8/site-packages/zipp/glob.py create mode 120000 venv/lib64 create mode 100644 venv/pyvenv.cfg diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 000000000..0204aaf8e --- /dev/null +++ b/alembic.ini @@ -0,0 +1,119 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +# Use forward slashes (/) also on windows to provide an os agnostic path +script_location = lib/migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to lib/migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:lib/migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +# version_path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +version_path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = sqlite:///lib/freebies.db + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/lib/__pycache__/db.cpython-38.pyc b/lib/__pycache__/db.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cb82a4a2e239f46c65cb31ce5d545afeb6538ee GIT binary patch literal 360 zcmYjLF;2uV5VYgu5`~1GC!|PR&>(~mLI_bHniEYL6vqp(&W?i}ka86g^*w~=(7L7K z3sg9FC&5ZPyE~fI%zd7x$k*E^6*m~6ule|2p+7Es+Y=8QaV(L-HOBKE$Uu>rU<8;G z9`a}t)FBWE`C^0X2%>jXFL=B}Me^;xQGs3F8fZp%fc{DJ;EBwFwLOk=0}ayTycsmjqbtq;9T%2_o` WE;&=0zeqTr!`Q?_hBK0pl>7poPGkQ7 literal 0 HcmV?d00001 diff --git a/lib/__pycache__/debug.cpython-38.pyc b/lib/__pycache__/debug.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb812433095eff7a836d29df1904b4d6d4d0dd26 GIT binary patch literal 352 zcmYk1ze)r#5XQ5A&Xt4fEmH``6}u^HMFdYPxkfB*yX<9j!)=l!$*!!e_!eUC`&gya z%2%**f))O`e=sJkvEa!#xE(N^7(N z?WqMWriyOOKzCeIsXbV}^YlUbmiiW`;GQ?!0Uaf$%fExmYO^}1=bdaSWdwB2M)Nj{ z$RlQmFzYSedn2}pqloWI)*1y29$_z`W9Sw-(?=)lnm3O1lW&bK`;+qS_lMG>ojq8F XT1-UyRX>HpQVizhT9;zxLRS^P3Kno8m&}zLijyKthGqXu* zD}8Dtc8P7t)ZpXI11I5*%ln&mF%b zTB3c-{I2YYj_4jUoL>_?xV2U`HuH2AX7jQc877wc@hSJ)GZm+yng_dbUN#;oDaWxa z8`YWlDKC5DFxm}f`D`D4N?qCyvF}nEE}Cf*n~$_7OkAJ3n`xIt71W9XThQGjR3^8Y)x6-&6)JjLoWTLLo_RB=B z(wW9m!6{QWA^-JoG|f{vk_)*T%VY${Ws=WQnH8h2!m%FB=EXG6)@OIump@!D6oMty zD2d0TG#4__!`Zy-Y!eukXGonUlbF@#E$;Hg)#b|!FCL3H9a#?IZA=rq>tG7@0)acB zkg**bU+07&SbXvEx3bt*nO97uw|qnIFfOKML53>9vlj^9ekOorZ;quw%|ytr?d?l7 z_uX6xseUlbj+_!Vxt9rA`W;wm_J@xW01l@{z4U zzjOohazRkGf&lT_PbBzG5bW)T3B4(#ZV(jVIFXg8=u$3xqsSO%N(i`XvyTJG_Ipajg#TTW-VRW;&!UE^}AKlsy?n&lr4`{n^=QqgW4X6WV3NskJM3q+YkiN5-m`>(>DDPXX*aM+Yj~&R6ZJlR zbOBDyd)qRTPiE@=2Xq@b;=rZFZRFr^Wn7SFa(FMvqcG9;hiAXQr8erEiOaxJiKYMICr=pi%a~Q=24yt?K%Izv}l@PEo9@99uMy1bP^YMW-resF;wdR6NVN zOUS44O#RJ3;tenah#me5o*$fruPM{C@@vgx+?{2a4n1xDQ~&}xQt_B-GL`NXNPq^Z z=%2;Ht&kCVi#m4m)B`{a3e3xx9%gzTuK;rXA_f*+bos0Zge<~1(Hk&v1@>zXt4=<= zbYg38<$puN=%)UuCM9wWd#|)LXPcSdnVBX_BAg5!Q zr`j5<`7OPdgh@1&>HGp(4N#2@z4QHHu2Q4lzjE5dv*L|A|CxF~Q>mYOMR{whc+TZV-aZ~^R literal 0 HcmV?d00001 diff --git a/lib/__pycache__/seed.cpython-38.pyc b/lib/__pycache__/seed.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cf92807cafc6d1bfd914c88e9253e4963cc4488 GIT binary patch literal 1036 zcmYk5J#Q0B6oz-ayIy;}Hi;7;T)wuol*9!ELP7`mqh5a1++7Yudb9 z@XwWN!)>@@-A%ar#?ZLecRKff8gd=62ns9;%CnBQ@U~Qei(orXrz$&LxIH{x1kW|% zfKb-^>QHGK<|%y%IJ2_wGL@oi9_F(QC>tG|qSEum-U*FV{5;R0pl=*Fk9LZ&nrVvn za5$##c8In$suJTDYEaO>W~lirp&`bvRdz&^Sx0Cd6x-+mGe?6Wi(=j2bc9P{Hdm_{N!l!>)GyiuRG3D?BX0Jgh`ig2`2e8#jNa_^XyJf z%W=-p&SN} zc)KSC!^1#R9Mv8s2qO)zbBnuoBGGH*&Fw<@27B4Q!Iqq6J{-BRQ?@s+mcrQ5!>jVF zY;V|QyHvC*tRI%kn0QgY_{aq^)E}G3H#B2*dYT^RPdvgCVULYC^oReeL3H`iRV4Dq zN!6HLSfIz^q{`WR&knSadS~HBz(Y-EZ< z`SOYUbBaE@yG)7WHpOe*SKIP_tMOjooxHC_lRX^?sPc;z55H__*(v-%^US7T2xRQ|T!u|g_`EYR=D1ZVefC4Ch z0w{n2D1ZVefC4CRDgf%DIlTW5pZ}*|j==BmJ$wnD!iR7VUWXR!!8$x8Phdj<6hHwK zKmim$0Te(16hHwKK!I~EkWFc1QE`gC;PNN(TfE*ie#$1p*mk$Yd%nY}qR}#b&LlK) zL8;z$4mjKJyk^(<5loFNC>6fXeMkOi!T35GpODL#kV_xW6^rK5Cgf5l z} zbJ(-uku1h|7Co9JHkL(;WKqYnDA6o5nkC%-=d5D_zruI$1$+$e!`pKA&tVfT!8};j z-_|eIH`aaY1M3|*3>yld01BW03ZMWApa2S>01BW03j8;Lshmz8SEyNiveI(=K>I{V zXLXWODBbQID4!=&GZ|ezai_ErIE_Q$vunI@NZvJ5*(sgO%XrBPMBVca>Ah5P2Kr<7 z+%UbrPnKKY~6Q`1pU)A)2X4-MtmkYsdOx! zO+-t_jTv)NX+zJ%Bc=6N&KN5li>39U(yBHc3)7jARcl!-T3J(P)Jc_9Wl9;5RUarB L8WuLqp*8;ls#)6a literal 0 HcmV?d00001 diff --git a/lib/migrations/README b/lib/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/lib/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/lib/migrations/__pycache__/env.cpython-38.pyc b/lib/migrations/__pycache__/env.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8e67a39897d3c36f3917bde404b69dfe1335b8d GIT binary patch literal 1679 zcmZvcPj4eN6u|A7$z(D~({7=w6$kLGL_#SS#080EcQN&K%MspM`HQS5ABd1cGNzvT1>d#(93vet-Oao~K(|DT3$Z*8}lgg3xbn za{CEk@(^D06ATP7EKz|+7&|$TK@pBZFNYvUqsYsVY!&e+_Hs+Mi)55w#N&Bq)Zv|Z z%Hn4o{UygvegU3%GVhLhEN1PWF-utI`)IVanx(7@vp$cP+c!x6z_(r?j8x3~?w`e9 zph53<$O{c{olb=0N2QvGDa>}cnhM3Ui7tyQUu&a9SxT_BL1X#31u;6Lh983vYi=v8 za_4USU=TX+9>HrK!eG%7Gkk-ukPYVHC9)9N#}aW{3D zlQUswWL_CdD0zDFnO{#8BdP?ABA@v3-+?V#Fk_X?oO$YF2bsk zBPu22U#`<1@^hY7mcKn9K0Ggk_0gunc{Eo0@TJrFh$h!!VM$rpMP*k`pZCqTlFEF> zi;H2U<&!#HP)!SCFC?#9iWZy=qB;aqy=|$Ua+?+0QbsMUx1_LKQ<;s0Vy5mfK_$;^ zRxYeL9<=MYIrnosw=wd=J&=NaLcTw%Fx`ctNXfA*`~<@tldTX z1?kt|?~gqx#s?eg0avHP#YKJp?xt)-#apn&?81P0DUNX9fAODjJH>hrw0EElK;c4p zXuko$B8!)S#q&_0n}A^(&08$E4zJK9S^}gm-@U@NJx?yN?Xd7V0C?NWbcJzy*}aSL zEubT~f34>~{vXZiKQKR1&`hAuVi45vLi33@uRHGA=MtQOGTr#`y*nQCbO@)U`!Lj9*KX5 None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/lib/migrations/script.py.mako b/lib/migrations/script.py.mako new file mode 100644 index 000000000..fbc4b07dc --- /dev/null +++ b/lib/migrations/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/lib/migrations/versions/__pycache__/a5f8be6737c8_initial_tables.cpython-38.pyc b/lib/migrations/versions/__pycache__/a5f8be6737c8_initial_tables.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7a35d9c42a7769d8370e826232ae8cd88738e14 GIT binary patch literal 1439 zcma)6Uu)b(5ZCJe$BQl7Rjm*rg$ZB{qvm=LDkqbTWnEgy+X0!ta^q9HO zA8Uv?4dyEhbm%=si-pl>+(r|8K+{ti=KVz24)cF7qkbm^tdG)wnHSnkTu zu%`12D$n}{)@hX%eE8~YHl$A$Cv*6GdOS@|S`AdQp?4~P)-XInG8;|~Cr`#llkws4 z@bF}IJeeI$4-Ti(qo)(k`yET5jdbZ=z?&8D1f+Gz(fNo-y(rHk|8@ZBiY~ScSn-y} z@5Q;GJh_T9I)|*19)l7%tK!CyEudZro~Tf_SAfDP{*^~Gt=4V`Benj$!Sq_cF}E5s znE9j8AQmDPBKAAP>1;aW#%B7KY_VI7ANT+H>2{gjuHJv_^6o|7xf>mG_U|FVUF61L z!IH54l5Exa9Ok1m=%hCL9?qJEJ>NZ-y`;!X%2TMMp0bzN4VSMntZoL<O5U$~M3R!ptYsiW+2VRF?(s>n@oBFEY8wiJ!{DJByw8wmx=kK5pZk|-x4iqe zY5j{TT^~R95Ag|F-6f1n$Fn5o5L=2G|?Mf$(S&_uCwChcYO%XccLggnNnn6BK zlP*&ACZkz$1^GriQZ|;dF|ccL5XY4BqHe1i$3le>jv}rissv5E52olo%L}&3;5lAs Xt$KjOc6N22xTa6`$UfOO`uqO?kT!Dv literal 0 HcmV?d00001 diff --git a/lib/migrations/versions/a5f8be6737c8_initial_tables.py b/lib/migrations/versions/a5f8be6737c8_initial_tables.py new file mode 100644 index 000000000..fdcfe18fa --- /dev/null +++ b/lib/migrations/versions/a5f8be6737c8_initial_tables.py @@ -0,0 +1,52 @@ +"""initial tables + +Revision ID: a5f8be6737c8 +Revises: +Create Date: 2025-12-03 08:32:17.077192 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'a5f8be6737c8' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('companies', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('founding_year', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('devs', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('freebies', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('item_name', sa.String(), nullable=True), + sa.Column('value', sa.Integer(), nullable=True), + sa.Column('dev_id', sa.Integer(), nullable=True), + sa.Column('company_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ), + sa.ForeignKeyConstraint(['dev_id'], ['devs.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('freebies') + op.drop_table('devs') + op.drop_table('companies') + # ### end Alembic commands ### diff --git a/lib/models.py b/lib/models.py new file mode 100644 index 000000000..fb3eb0721 --- /dev/null +++ b/lib/models.py @@ -0,0 +1,66 @@ +# lib/models.py + +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declarative_base +from lib.db import session + +Base = declarative_base() + +class Company(Base): + __tablename__ = "companies" + id = Column(Integer, primary_key=True) + name = Column(String) + founding_year = Column(Integer) + + freebies = relationship("Freebie", back_populates="company") + devs = relationship("Dev", secondary="freebies", back_populates="companies", viewonly=True) + + def give_freebie(self, dev, item_name, value): + """Create a freebie for a dev""" + freebie = Freebie(item_name=item_name, value=value, dev=dev, company=self) + session.add(freebie) + session.commit() + return freebie + + @classmethod + def oldest_company(cls): + """Return the company with the earliest founding year""" + return session.query(cls).order_by(cls.founding_year).first() + + +class Dev(Base): + __tablename__ = "devs" + id = Column(Integer, primary_key=True) + name = Column(String) + + freebies = relationship("Freebie", back_populates="dev") + companies = relationship("Company", secondary="freebies", back_populates="devs", viewonly=True) + + def received_one(self, item_name): + """Check if dev has received a freebie with a specific name""" + return any(f.item_name == item_name for f in self.freebies) + + def give_away(self, dev, freebie): + """Transfer a freebie to another dev if self owns it""" + if freebie in self.freebies: + freebie.dev = dev + session.commit() + return freebie + return None + + +class Freebie(Base): + __tablename__ = "freebies" + id = Column(Integer, primary_key=True) + item_name = Column(String) + value = Column(Integer) + dev_id = Column(Integer, ForeignKey("devs.id")) + company_id = Column(Integer, ForeignKey("companies.id")) + + dev = relationship("Dev", back_populates="freebies") + company = relationship("Company", back_populates="freebies") + + def print_details(self): + """Return a string describing the freebie ownership""" + return f"{self.dev.name} owns a {self.item_name} from {self.company.name}" diff --git a/lib/seed.py b/lib/seed.py new file mode 100644 index 000000000..63d12c342 --- /dev/null +++ b/lib/seed.py @@ -0,0 +1,36 @@ +# lib/seed.py +from lib.db import session +from lib.models import Base, Company, Dev, Freebie + +# Create tables +Base.metadata.create_all(session.bind) + +# --- Companies --- +safaricom = Company(name="Safaricom", founding_year=1997) +twiga = Company(name="Twiga Foods", founding_year=2014) +mshauri = Company(name="Mshauri Tech", founding_year=2018) +icreate = Company(name="iCreate Labs", founding_year=2015) + +companies = [safaricom, twiga, mshauri, icreate] +session.add_all(companies) + +# --- Developers --- +alice = Dev(name="Alice Wanjiku") +brian = Dev(name="Brian Otieno") +charles = Dev(name="Charles Mwangi") +diana = Dev(name="Diana Njeri") + +devs = [alice, brian, charles, diana] +session.add_all(devs) + +session.commit() + +# --- Freebies --- +safaricom.give_freebie(alice, "USB Drive", 500) +safaricom.give_freebie(brian, "T-Shirt", 1000) +twiga.give_freebie(charles, "Water Bottle", 300) +mshauri.give_freebie(diana, "Notebook", 400) +icreate.give_freebie(alice, "Sticker Pack", 150) +icreate.give_freebie(charles, "Mug", 700) + +print("✅ Database seeded with companies, devs, and freebies.") diff --git a/venv/bin/Activate.ps1 b/venv/bin/Activate.ps1 new file mode 100644 index 000000000..2fb3852c3 --- /dev/null +++ b/venv/bin/Activate.ps1 @@ -0,0 +1,241 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/venv/bin/activate b/venv/bin/activate new file mode 100644 index 000000000..9189af674 --- /dev/null +++ b/venv/bin/activate @@ -0,0 +1,76 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + if [ "x(venv) " != x ] ; then + PS1="(venv) ${PS1:-}" + else + if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then + # special case for Aspen magic directories + # see https://aspen.io/ + PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1" + else + PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1" + fi + fi + export PS1 +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r +fi diff --git a/venv/bin/activate.csh b/venv/bin/activate.csh new file mode 100644 index 000000000..73c962b1e --- /dev/null +++ b/venv/bin/activate.csh @@ -0,0 +1,37 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + if ("venv" != "") then + set env_name = "venv" + else + if (`basename "VIRTUAL_ENV"` == "__") then + # special case for Aspen magic directories + # see https://aspen.io/ + set env_name = `basename \`dirname "$VIRTUAL_ENV"\`` + else + set env_name = `basename "$VIRTUAL_ENV"` + endif + endif + set prompt = "[$env_name] $prompt" + unset env_name +endif + +alias pydoc python -m pydoc + +rehash diff --git a/venv/bin/activate.fish b/venv/bin/activate.fish new file mode 100644 index 000000000..9e9310cf4 --- /dev/null +++ b/venv/bin/activate.fish @@ -0,0 +1,75 @@ +# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org) +# you cannot run it directly + +function deactivate -d "Exit virtualenv and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + functions -e fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + + set -e VIRTUAL_ENV + if test "$argv[1]" != "nondestructive" + # Self destruct! + functions -e deactivate + end +end + +# unset irrelevant variables +deactivate nondestructive + +set -gx VIRTUAL_ENV "/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# unset PYTHONHOME if set +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # save the current fish_prompt function as the function _old_fish_prompt + functions -c fish_prompt _old_fish_prompt + + # with the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command + set -l old_status $status + + # Prompt override? + if test -n "(venv) " + printf "%s%s" "(venv) " (set_color normal) + else + # ...Otherwise, prepend env + set -l _checkbase (basename "$VIRTUAL_ENV") + if test $_checkbase = "__" + # special case for Aspen magic directories + # see https://aspen.io/ + printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal) + else + printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal) + end + end + + # Restore the return status of the previous command. + echo "exit $old_status" | . + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/venv/bin/alembic b/venv/bin/alembic new file mode 100755 index 000000000..eb41d7600 --- /dev/null +++ b/venv/bin/alembic @@ -0,0 +1,8 @@ +#!/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from alembic.config import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/mako-render b/venv/bin/mako-render new file mode 100755 index 000000000..8e051cbe6 --- /dev/null +++ b/venv/bin/mako-render @@ -0,0 +1,8 @@ +#!/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from mako.cmd import cmdline +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cmdline()) diff --git a/venv/bin/pip b/venv/bin/pip new file mode 100755 index 000000000..254a573d4 --- /dev/null +++ b/venv/bin/pip @@ -0,0 +1,8 @@ +#!/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3 b/venv/bin/pip3 new file mode 100755 index 000000000..254a573d4 --- /dev/null +++ b/venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3.8 b/venv/bin/pip3.8 new file mode 100755 index 000000000..254a573d4 --- /dev/null +++ b/venv/bin/pip3.8 @@ -0,0 +1,8 @@ +#!/home/ezekiel/Development/Labs/python-p3-freebie-tracker/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/python b/venv/bin/python new file mode 120000 index 000000000..b8a0adbbb --- /dev/null +++ b/venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/bin/python3 b/venv/bin/python3 new file mode 120000 index 000000000..848a7281c --- /dev/null +++ b/venv/bin/python3 @@ -0,0 +1 @@ +/home/ezekiel/.pyenv/versions/3.8.13/bin/python3 \ No newline at end of file diff --git a/venv/include/site/python3.8/greenlet/greenlet.h b/venv/include/site/python3.8/greenlet/greenlet.h new file mode 100644 index 000000000..d02a16e43 --- /dev/null +++ b/venv/include/site/python3.8/greenlet/greenlet.h @@ -0,0 +1,164 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ + +/* Greenlet object interface */ + +#ifndef Py_GREENLETOBJECT_H +#define Py_GREENLETOBJECT_H + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is deprecated and undocumented. It does not change. */ +#define GREENLET_VERSION "1.0.0" + +#ifndef GREENLET_MODULE +#define implementation_ptr_t void* +#endif + +typedef struct _greenlet { + PyObject_HEAD + PyObject* weakreflist; + PyObject* dict; + implementation_ptr_t pimpl; +} PyGreenlet; + +#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) + + +/* C API functions */ + +/* Total number of symbols that are exported */ +#define PyGreenlet_API_pointers 12 + +#define PyGreenlet_Type_NUM 0 +#define PyExc_GreenletError_NUM 1 +#define PyExc_GreenletExit_NUM 2 + +#define PyGreenlet_New_NUM 3 +#define PyGreenlet_GetCurrent_NUM 4 +#define PyGreenlet_Throw_NUM 5 +#define PyGreenlet_Switch_NUM 6 +#define PyGreenlet_SetParent_NUM 7 + +#define PyGreenlet_MAIN_NUM 8 +#define PyGreenlet_STARTED_NUM 9 +#define PyGreenlet_ACTIVE_NUM 10 +#define PyGreenlet_GET_PARENT_NUM 11 + +#ifndef GREENLET_MODULE +/* This section is used by modules that uses the greenlet C API */ +static void** _PyGreenlet_API = NULL; + +# define PyGreenlet_Type \ + (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) + +# define PyExc_GreenletError \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) + +# define PyExc_GreenletExit \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) + +/* + * PyGreenlet_New(PyObject *args) + * + * greenlet.greenlet(run, parent=None) + */ +# define PyGreenlet_New \ + (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ + _PyGreenlet_API[PyGreenlet_New_NUM]) + +/* + * PyGreenlet_GetCurrent(void) + * + * greenlet.getcurrent() + */ +# define PyGreenlet_GetCurrent \ + (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) + +/* + * PyGreenlet_Throw( + * PyGreenlet *greenlet, + * PyObject *typ, + * PyObject *val, + * PyObject *tb) + * + * g.throw(...) + */ +# define PyGreenlet_Throw \ + (*(PyObject * (*)(PyGreenlet * self, \ + PyObject * typ, \ + PyObject * val, \ + PyObject * tb)) \ + _PyGreenlet_API[PyGreenlet_Throw_NUM]) + +/* + * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) + * + * g.switch(*args, **kwargs) + */ +# define PyGreenlet_Switch \ + (*(PyObject * \ + (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ + _PyGreenlet_API[PyGreenlet_Switch_NUM]) + +/* + * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) + * + * g.parent = new_parent + */ +# define PyGreenlet_SetParent \ + (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ + _PyGreenlet_API[PyGreenlet_SetParent_NUM]) + +/* + * PyGreenlet_GetParent(PyObject* greenlet) + * + * return greenlet.parent; + * + * This could return NULL even if there is no exception active. + * If it does not return NULL, you are responsible for decrementing the + * reference count. + */ +# define PyGreenlet_GetParent \ + (*(PyGreenlet* (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) + +/* + * deprecated, undocumented alias. + */ +# define PyGreenlet_GET_PARENT PyGreenlet_GetParent + +# define PyGreenlet_MAIN \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_MAIN_NUM]) + +# define PyGreenlet_STARTED \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_STARTED_NUM]) + +# define PyGreenlet_ACTIVE \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) + + + + +/* Macro that imports greenlet and initializes C API */ +/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we + keep the older definition to be sure older code that might have a copy of + the header still works. */ +# define PyGreenlet_Import() \ + { \ + _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ + } + +#endif /* GREENLET_MODULE */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_GREENLETOBJECT_H */ diff --git a/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst new file mode 100644 index 000000000..9d227a0cc --- /dev/null +++ b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2010 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/METADATA b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/METADATA new file mode 100644 index 000000000..dfe37d52d --- /dev/null +++ b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/METADATA @@ -0,0 +1,93 @@ +Metadata-Version: 2.1 +Name: MarkupSafe +Version: 2.1.5 +Summary: Safely add untrusted strings to HTML/XML markup. +Home-page: https://palletsprojects.com/p/markupsafe/ +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://markupsafe.palletsprojects.com/ +Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/markupsafe/ +Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/ +Project-URL: Chat, https://discord.gg/pallets +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst + +MarkupSafe +========== + +MarkupSafe implements a text object that escapes characters so it is +safe to use in HTML and XML. Characters that have special meanings are +replaced so that they display as the actual characters. This mitigates +injection attacks, meaning untrusted user input can safely be displayed +on a page. + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + pip install -U MarkupSafe + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +Examples +-------- + +.. code-block:: pycon + + >>> from markupsafe import Markup, escape + + >>> # escape replaces special characters and wraps in Markup + >>> escape("") + Markup('<script>alert(document.cookie);</script>') + + >>> # wrap in Markup to mark text "safe" and prevent escaping + >>> Markup("Hello") + Markup('hello') + + >>> escape(Markup("Hello")) + Markup('hello') + + >>> # Markup is a str subclass + >>> # methods and operators escape their arguments + >>> template = Markup("Hello {name}") + >>> template.format(name='"World"') + Markup('Hello "World"') + + +Donate +------ + +The Pallets organization develops and supports MarkupSafe and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +`please donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://markupsafe.palletsprojects.com/ +- Changes: https://markupsafe.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/MarkupSafe/ +- Source Code: https://github.com/pallets/markupsafe/ +- Issue Tracker: https://github.com/pallets/markupsafe/issues/ +- Chat: https://discord.gg/pallets diff --git a/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/RECORD b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/RECORD new file mode 100644 index 000000000..090301b9c --- /dev/null +++ b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/RECORD @@ -0,0 +1,14 @@ +MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +MarkupSafe-2.1.5.dist-info/METADATA,sha256=2dRDPam6OZLfpX0wg1JN5P3u9arqACxVSfdGmsJU7o8,3003 +MarkupSafe-2.1.5.dist-info/RECORD,, +MarkupSafe-2.1.5.dist-info/WHEEL,sha256=zTDqV7OR0em6fvysya0bwC-51Mb7EQ0x5PBJySRF2iQ,148 +MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 +markupsafe/__init__.py,sha256=r7VOTjUq7EMQ4v3p4R1LoVOGJg6ysfYRncLr34laRBs,10958 +markupsafe/__pycache__/__init__.cpython-38.pyc,, +markupsafe/__pycache__/_native.cpython-38.pyc,, +markupsafe/_native.py,sha256=GR86Qvo_GcgKmKreA1WmYN9ud17OFwkww8E-fiW-57s,1713 +markupsafe/_speedups.c,sha256=X2XvQVtIdcK4Usz70BvkzoOfjTCmQlDkkjYSn-swE0g,7083 +markupsafe/_speedups.cpython-38-x86_64-linux-gnu.so,sha256=dR4WFvrcpISjeacwjKYJrqkz-0j0ZXF4oAHtTK0cKpw,45024 +markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229 +markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL new file mode 100644 index 000000000..c825336ae --- /dev/null +++ b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: false +Tag: cp38-cp38-manylinux_2_17_x86_64 +Tag: cp38-cp38-manylinux2014_x86_64 + diff --git a/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt new file mode 100644 index 000000000..75bf72925 --- /dev/null +++ b/venv/lib/python3.8/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt @@ -0,0 +1 @@ +markupsafe diff --git a/venv/lib/python3.8/site-packages/__pycache__/typing_extensions.cpython-38.pyc b/venv/lib/python3.8/site-packages/__pycache__/typing_extensions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7b84ca8bbe4f9d5c10b8dd62933fcddb671bd0e GIT binary patch literal 122543 zcmeFad3;<~bq7AXMx({D?AVUuBooJpBqJ+{vjD~kwq-jBaTH@K+cRc7(tWZ<%Zz;I zDYk|Y1neXZ#UYf?29kiBkdRPl%hncXDO>3RZBw?wl%;Gffffq1lp08W-|x9^n~~%M zO8@x%K0n#-&3pIVcb9X|J@;()yc^rvG74Op`t)^VC{(zFMa)P#3B$)vbEedi6Zjt2U@Ub&XrIf8k)Il|>3TjM^s|hu!imIfh)FGu+`JlFzD97rs zms*{6W^9?79#2}!)opg$*coFh##7eHt0U^LI&v(cjvS3ytL#-rBi5Ocb7nkwZ&c5z zih9Mdh_$+Sj-*y4^^er7o0?XyG*>9`EbA=0ZElTv)yb%Jwp`tgt8?V))wntrdDf`c zC`TPV5wo&#cMNxT;O;yr{Z5n~)2lE-cTKcSuC>-iBgM67;c<00T6nj;^l02VANQ|S zG2F-O^VM&ih+At>(mHD$(qE@eApHdHFF?8XoJd#~;=0S~3Y9*Ely0lLm{zY>edt-Y zx>sF+-yX^H25G^4$g|#BU)-*KTfGs_o~Q0pZ^Cac>i8Y?yQt%Lkz<2fM;}t}S8qn@ zo9zqTw2SPE)LYa8C!*GRt<2l&*18cdEyb7L~M}NK1?jsK?YwlzW4{`^*TQyaVHQwLEz# zo-A*8vIFyCiOhnfm=P10A$x)QXB=!%kEfP%1 zM-$s3){W{9)E}ZZH{BamC)9h?dyhq}n*|Q<0~`|i9=vmlbxUy&&+k$1S0BLhTh$+_ zKgK(j`V;j*{O&{FKcqg4zJJ(W%KS&8fXK`2{pwHEpB;+;0xw67kEkn=gQJnNUj{r) zShs}g`6<#5SOKAau@fNq{gv*k!V`(m0o` za`83lAMB<%2Y1saqhq&$Mjlq5Ri8s&j-VZ%S6@KMUy!zHd+FSat+66}u@E_#uqt*z z{iFI4F~B|(c|-32L(<+afC{AaFIcOsM`G41iYfd?tyidjQZGO~ zWy#frTumC(bF6ArfhS*4U3gZNXD^Xw#F1HRws^7ns`?t9y%IS4di8a6IdFE?n~Un7 z)i?0$Rd{x<`lk98p4~2GTp(o}Lm98OUXAl>tk)DtL%*%QgC2edDIR@$4O0I_U4T@) zgS=cvqUy0bVrNDaSBWP5H^_a|I*Ri#>sWE3(xD>iUqMaouvf^r$4zeal-h{?997>_m*Dr9^kp0RlGdP4 zV|QD37yIg3$Q3KCCqS9MueQnTyt~+nyKU;vDSb>b|TMDRV(te+9w1**GQ|<(yD(! ztM0K#S6*+uzR0!Wztzu>_h%^mUflhUO5-kV-wPUY5AIK?X52T+{p(R%v+hftr z2I~!|ZKIUkgtD9UHOT)9)r9;_Zq09y*8I{<`)&JmM-$c?AWC{{bm~r<7kq`43qSEm;23Qhuk}Ddj)pmcI<; zESK_^`{lpYeyiGv^fRRVhot=ND8EJ4h__j9D~`;APm2bv!aFN)td#Or`sK~pb4XpK zwoA=#b89|x;hbta(pO8(Z<7+w!u=X4aSeKR(jDguq*Wf5US1a&`dJ-)!`w(74)Z5OJSNNtb1Fz!M*;NN)mYkR_eLcIhjJyQPTxLR+$ zQ+h)_piPr|{5|XUkp4Uzy_hraD!zYSZ`(8|37&7j0iKp;zlL|qlZ%jcu{^mLC3=+o z_w9Eh{SwtK?PM(*ahO^L>!b85c?!ufvn~ zTkl61Z@1r$r(2|qt?ELQVW7@YY1i*Zz7JR*D2@XEpOk-JMDA@5ChgtszqYYILY_ay zK|X!$nOyPy1bN7LuN}e4$%FU&Wg4H`XCI8(XS+|fKiGulJ5)MBG{KK{B!!>F%`tU(w4$`hgFP4D*wU2$+`fw35d5$H$ z1Tx2;3XHE)>jC-opw=H2T$=%(@8kBzQTE4+t8jlS4hyaQ1ZwRlkJ_KWlTS#G$nAIN zDq#0X>yt%|d~d|D568<;#$O=)Q`TSFY}I}oFUOG^B?tFc0-po8&kN?~g)8`L;ROF0 z`T@fU{tccF;TT5UpSBD3rwvDl6s68OMj7do=mTUVbq4ydRHsB<`i%9N;zlXoR%ZbI zXV{UkzXg_m#{SIQ-`RhEG77$lS5Nv^{~%YF*sbbPDQB16I`&!Xv&9T*8c~;`_RmUh zDOWGmW9ZfAtj`rYq>h7X6YAK6G5(y36{C{tUgY|`_4(rO$eYCXrTU#HuYh9=C4NE1 zbKJX=T*Qw~y+YtMfhUtVia1I*rf?j>K^T{DI5?(p+=k=uXe(gxMQP6w+|LM1XV3zV zLj9vWtEhgowI6N$ykOe%(c4Zo)1j=l`(&1LrfWGjOh1{1B4bww7m#!`HG-r=-KTeA?V!(xqvqP_%39lJRQ0 z=;mA|_vnFRA91eF7xM>gT}y479vQ)Jx_>lZC|;kRs%GS1$2hHq;xop`xJvCL*PI>E4xz^_0Y@2axz{I=l zFHNGqM}p@&CrWuIxV^Th@YFBr+G6P09yCw^M8#ss!N90mD~8L~POfO*hIY5OcMjpa zWO%~P>s$#BP=PAfmZ`jxALan(9Gx$gM@o9KmQnVUwmH(O7T;bPt}VB3%TG+>K8NQR zJ(bh;NUiOl?SzU-jtD}ecA^NheF>lDO`{5>V!4)<1de=0Uetwyg<`EUxXw@HhVo^A zWjWKPbjc|VmnL%1nhe>xTx(}4dyyM06rFM{1tZK->a8y>ZX#{~-$gFvF1jPllrLLLLl z(&f@r!!rS4@?1R)nAK8Dyq0-y-Mu5%f6b2m>#iNRx|SS}aY_%A_Snv}F4od}?7X_6 zIDx6R2RK&%Qq@`pN{)ZiJWz76u+}Oi24P{iTTsd)UFiaLyDRU_WprVXztv=OFLF7&q_KrHWl-dCkFGYLd&s za4mgJzC0==#!!Bx7}tA=h4izV#%PL41aB9-EB_U!zCd_gFT6-;-H#O zwc(^eLnF1sknQAa%X9v$0dktTmK(UxU&mf1KP8Ba2Elt`N z*%f=dU{74M9T1x+O-|Gp`pa}IQ4O6PJ9v8C*BBwFe8JJshs z9(W?byhRY#T&|YMTebQB?9l_~A98yO*D)&#sw1cFl zXxf_xz9bR_d@FF_M5+-dIu>){V+kC|k@(TrUchglCsoT7%7tRt$rA%prjxa{oI4#1 zDM$&nlxwYZsm+33qyQL*sMFBm+PS&%XlZ&v@ojG?|;?Vrm1Q%a5X6cmC3Pa`q*M;}9SPg383AAKiJcky(br@MLjEuMU2 zF@R;I@5JE_-n=^FcqVlci@iFJ#!8*l$XL8J;?7K2Fp;$Pb^yNyMtP5(ZO2m zIvh6*f_(tQdZM-F;nHn+UC3i$Of#tnOhs~=xpE(Q;A%lxJyA^r9L7=i#~$#n+>Mhr z5AULVhjTG)#pN0nSX?LWiXoWdy*P2*o>wbw(; z(}p7F;eprhGUt=q=T?#>CyM^YP6881pw3vZw>@#`jOGkBbG*WkFnx2$?72ms^r&sgDu1Tqwx2EUA_QV88Pk`!k%p#S%1y>)%A!IcjZB1&X zERHl;JoS)9fe9C6fx*5GSp)Dtgk|-!EH1q&ilrd35(MB05P;74 z1OOx{DNDbWSs(PppY>m3Su7OX!?_E$Az&r+E0He*k$`}39s4Q{k4fM?fRfDVQpuUu z2`;hH39bv347-t|jc`>0G|h>P#Ktokejog2EPhp?arvL2p@XBCsy?GgV(w!trlULU zF+)oAXfKn>$7IeLw+u{UGj&7j0_7S=Gs-UZf_lKw$MB`dDjcU@Ot6xar-$T`t@^SU zx2%06Pa$+u#?MP?BU!TBAeU{bOXssKBJs^jR`y6%m5O#Z+0CBphO4pzxSRhvi|FeM z2np{^b$h3Wx>;q<{FfFfJexf~E9+hsbRW_-`aNU_Vb-N0^P0I(^$YaKv}l)|ADQ2n zg#!7 zi$KeDi5wiIuwXz3d*Wm--0Gl_@oJNi5)z3C7Y&IdX4UP(6#E_yAsCSrv1lgRnMg(F z&B?eoCrMbG$Q@`%mCCO{pN%`C!H8SY|!+fK1b5 zMi;4de;nN{ILd2r^3cPMtLTFe0l~}DnHT5*B!LqykfaG}33u|5{{gj$gCna$8urIW z8p=e5f2c+i;5l>TC1S*U$VB>ucnV5HdAF7zYo{M)iV@oMU*kH|HT; zw@5Sh(4BirPnyNqtoRMohqVET#1Qo=DUpJeqCNT$6`sLNcT)LW6(KA7^qmgQS zuDO~NI{ETyT;JpG}205C{3plU^@(-d?k=R|<60$l>Khz2;GFgy>yHI2o*|xeaQP3}) zwnHe`faNk_;=%L@T{np#ws3()xfEz0*vI`LxggBtquq_n4? z6M&tB!2wEH44XTUrz@1QGRzy~UGoCOKK)m~$+}gB)Y2zQ+i(N2Boy;#d^TEHL+vaU zRd}G_9G7!qEV-G|m`9E_`{W3onobibYyMsFNMyUGtrvESG84^UK%Eh&TSybi7x_vN zzZcgQ(bTdtJ(L|OOxV5IsfqkB5a2KdM`tHX0I58LWI{5`LZoz}W*J2c7Ax&?^0$NC@de~`@JnzpOPpzTBxfY;@R1hwG0dX2u6H$*wgLN)HA6#*6w;~6r?5Ki?`z@OtCD33pF&oNG9g9slaZ)vkF2cgr}KgICFlcaAqk~7mVE=@blKQi zQd|{6Z3)<#ZalxA4PtGP7{nuLDfKd*R`PTUPIW5^G6(!OGgl*XXx7OnEmL!_#klsD zS`640t7Wj3!()T=pq7TTELTufB#45cCJFnU$-%6vgavr7->9hmCTf9%>MBOVQu#cf zQKI5sc{5I)z(IQxWzh$X5doscu?(bB?D?Z~3eTw&_x_0oY?Iib(!iJ&{VfzRI3R6= zaG&6EtKWyTS{nF6$CdJM9Jl=AFC~B`_qx0b7mk=dVWR4*QY@j&8p$P`N zZU}|pT%Jx9;tdj*CTR#RiPH$CcRB~pAQ(r>#!5ss7^)_(i!MK3OPTyulb6}ARey(V znd2$IE0*$M4xuPCq8ZHcRCHB*IR()L#xanoJc#0bAp=SL_Twn;!O0uQ(Zp=rhi(=U zPh5$_Lk+>vX!&A5my*#ICpnj*v;)0S958N&92FUZCJE9M?gg%?0V5wK#So{TLM@sK zGyPqhdYbjW^7>Am?!u`S&nso{F=aG)HOMCr$%)N?Ws`ep-Zr`h{SA2$WWs6_U7xLT z%wBH5e*Ht_`&0lk5QkVM+7a!DRkDk~%&)jk6y+SCj-8+^{|u)_QB+0m25JTdwQ6)b zs3e6$fG4K411suLUMC=Ox_3y!v!l_KnD0nWY7HtpkfJrS$L`YslI8#Nh|*o^DX zkXrL*w&lo_zUm7ca}HVp_MOV5i;T4|zxHjOC=KT)%2)OIC7fyogMbwQy*ahgsJLN5 z^gAnh_fv_%QYl+YOy=Q;7$30@pIQQA;z3w4X;!4l^i=bUT=^q=ss#-75o)2aO7^5f z`T?06e}~4Jsq#5qiFyET15-0O(s@_MCdx)DVcwLTq-ulQvwxdt*l(^tv49ddv7Gl}G)GsA}#rf`E|OXPgcHJR2R zb8ikhWvr7cDVJ> z)5}v#cjkOAK0|3hna+XLvs6S!joX^Z;5h`s15Z7JzpDsHeI-w~;sjnZ?EQ$&By)fYcv<~1K8D$LQkT#N z8E7(UWM0XBA?@p?fE~B=@49jOj_tXvgM)ka>VHQnRGyFWVN!@OJj`GvgNtY2W+{X_ zgbKoe^ThdL0=YL_u{`>Wy!k5EL`yc=`XuF3C9L7TPf(VX39Wlv~YypPEZ-=4o;MY zKrCu;BpS}$xaEn)4pb=#2UTaZv6oGPD7HSEJtMx;S84@%q|ZkCLF*hO>1mwi^^IdE zedEec-W~g|qvKTW#wy?0YNSoqwLlaz#waKX5<`Yu76+OD1$>8AfDUURzXM867=C$K z^Z=#O1W38bCMMwWur5r(Fz47%q!8ml5{P9WFs zxxr{w;3lMvF|{{)c(ed5!(<-C8c$DZ%*3}g)WECa=s93dJqX(d=WnzZM1ru>XYmx! z7eT5u;L;$5Lkt@pM_)bHMgx={jsc+>N$T_Pavj836`$RJLp+2G#K)q#(*c9Y_0ZLZ zSF)aOE__{lAE-Kii@5#>ih&9qT)5XSA?{DajZ*h9Jl5~vsi6blAxok;znG2XT1E{m zckk9W7x#hiumU%1u2_76=0Y>w6VLw~S@+}%u*eQ#Jr!)t7DloMuw)(ZS2&vfs4EDy10(GY@l%~sB zo1Sb?>pG+8;ArVE-J606QPN>J{EE~uq8$Rr%Gx>rVeqn-`0QHV zOveGp+;;$n1ZxeRg3ChIM(kV@;>m#9-WYPZ%w2MskFl+pnC>FRqRhHKPO>dH%G+=X z3zhXoPKcGXV;Qm16)CMIJFQw^?Lt@?Si724JFt5R{N=pKcs>1wWWu0q{!xtNaUG5_ z1&=D}XG~+2pb-cwZA7mqTC^38MMuehBXvtePl;J*8_hykH)3N+H%DSTRqm}q#&w!# zCK_u#2sg|ObCi{xU6b`&|A1Fq=*@!Gk=woJhQS+h zTX*fc;pSb}?j6);qDp-`Gd!OsvYR@Kv|7{FZT$qGIBa44OH9Hm%*g0TF6USb>8wzO zFSTvx7LDu;=pio;5DV6z;7D{imby-Go1Nf3JHdUXz1R?2*a5wMvsRD;@kX0R zkU5+}ko$1qM8=?C4?Dg^VcDhuKYru$4;+p~kWf->1k<$ldIsUeA-TtfGM; zJE5C-dOON)>Q{8#VbOH zDz{z)t9h*>p!K7r(s(Xc+2f5p2rr$6se3mT4!DrXz8aTc!&{hsQ*|W1k9LHH?3=Rs z{eZP@!En~1eV83GF=P#fudzp_q;=qAW&Fti2$3?gfG#(B2~V6O5IzMOIkvc#DvR@$ z77@+ICD2TAz53%gSsfG!Odl3jNo+)^#FX-SWTzKMYb=9lk$5_mfRPmwCH71rg}Zn= zCJB93z$HGDNWhoS1C~2c8ah*leVi`7Pau`cr^mi>p933JfEOVwyv9iZFRyW$3=W3# zX^UPc#7D>@J7;5+4ySo8BdQ^2AYkuW4Su@?=J$I%UK?`ByIlXnxT2@|0ntIULUuX8 zXNL~3L`8N5N+g-Ix{pI~5l(|WZNX?7M`;;9e;kCBGlR(KTx$X+LwQ^FW{Eb*xbF(j zt~t&St3_T(dpwVkPksb|HPV5HkT%OA|gbhLq710MzM0IQ?T0Aj#>W^m&PUY@H> z%&-!lVmqRy9rcW^IK({8Cb0i-tC>e#J%5@^quSvS?5aozA9PG@sNB2`M-s(~qP4I-G#*qLHUWSq6hG_#X%U7sXB=YEEb7ExQId)1-`EW?S@B}$o7!nA;mY1rN^4#sJ9j+ zfXmQp!BIfs;8ZxY>d;#A{$55Ldl2e%pKR%mqOE38YbR)AqTGSBT1!X~ zVlXDptmFb*>0RiUnFzA9FJl1>V|FRhevcqrhxZ~egZtF2(pMxAZ;iErq@~=IK81A6 zH-?!x{1L#dTWL|O(yJ+LHH$eRLlR+7oJ||tS%;M@ zmosSzj`GWKqM~ICW_dcv!v+9VsizhwZ*&sIsSc@0qqGI*kWy5i()_iJ8tz;gcPVY* z-0Wn=nlTqG2Mcbl+0%Fnv8rnX(U}b?^Ppe`b!WTrDI8CWKVhKige1#_3N!V;@KcKp zH3FPE=f5Yw*)36OZt#`^@Jzygcl>cw&3W;QGPE@Mu3Jk z9Z?elwz7O75e52jm#e4VJ(r|b-9g-_@)id(S0%@j`uCm8QH*tt{8%gK!OP(lvfOE7 zNsv2IjPN!KZB+3xtd8wi9p4g-%*LbCStS&-><>T_PhPDWe+_b79#N^;_$%YLKvQ*S z;1?@*)!QPlu>lY2stdli66Uo3d7^ry;Fp*IsI5dw2n&OY?)mJJg%jaFqqR2 z6e!qxdkh*j8O&#D{a$DBXV~KYiBg$Gc6$ik;K0C~^l%tPK6medab2k^3t)lwA3*H! z3Egm=FLrlbROVJ#I{0+K^TeyGC-8l^Ij@UQm25I9jl%^f=!{-Ncy#I%uxB3oZHGmz z!+Y&+-uCuc1)2-)TSATkzzgjUV0{pJKPWKD=lN|lyjf+-e9(9ZB=v?cpnmMB5~BDD z1FYHi%B5IS4j!oV?#!c~f;Nh~OMwVbrHo=X7_=UKF-AVfR+a%+K(9Kfp{Ij8VLf_i zOpUj)0$Bz{))m^p1yR;~y#h7YT7g>J4GiK4?a;^gP=oDoZ-oCywzlA?+X~8EeONQa0B>XEgqDP>Cd5aC@D*=heLmi)bZ;$YA?O>ai!%pG zFWH*!0g@{Obp3sXtiO;Az~TS#nnynlS(3O6iqX!zu)yXh+7zVBb#c4{Ab@YEEb~H4 zTw8Qx9YdW1^X*F{|GFO)3dd4w_gZU4UBjO)qOapAU{mBumLqofT-KS!MLI_MpNJ0Q zy6*4S1?PDvrLz9n!8yW^8NXS8a`TEQbwdcGD?N}(kPZOV&GW2a)w=+}T2iv>>lcG2 z``i702F)y?+57C!Bz46K)57xmScBdX@)KHx3G5>gRioP;GKw&^IgwSN8mp z|1-FrOSnDmnr28kk|Lp}$XeDy-mxt7m_FiM^Q*k>DfWS=U^ z^fjb#?_ywK+(Hw*&#wgTKIjIu#k zkbqPC4MHBvk+fh>2=4xJv~`F$DbJr9Mrx{f_26QFCvdI{;bVM3&{Ny61&8=-gz?c^ z(h<>?#*f5c_DFf6(LZ7rLT3?T1;X*Bc_YOtyg&908H{Y-feTSi!1Qn|P3az^5g({ymYA9B#WEb?b7$-qVuIR^ zYjA8ZKy5cx3ny_LwhM4>bortV0?L1=fZ|Z=)7XT79N0z*wbOBmm~hvEx|9N7a-f!& znwgr>_n;CC4PA{PQNghTo*s1NfJ+h_pD8=DuRR&V^0wD|p&LJ&o#<_?)#yrH4vPwO zLwU~S!}jSZMCP({hc)+WYJDpb!O7EG*Gj`)WE^#=FN4~!B~-nczcS9r0*twOn4l&Q zBXCZFL*vt_1or&ls&YP>OtY!4v=@QR*KEk!rY|x5a@4PLJjp=r=ap2le4#>ySSJT0 z5^Zgv;(0!E%`x7B<8%QA+V(==ny{TxkqUWs#Q)$3`AEtl(DfNBZOY2n>W+hvR_f*+S{3N0 zjd#RR*QI)DxdSbr*_6$Fd)Ff?LOV6N?mfmtDu&cY@I=1|MTHY+%>ofdD zvCRPOd}AgX#G@aFp^T&$2sUvRI){#goY4CG(oDSz2vrg)=I&4nR`~C@Eii#*=D}oM z6ErsyELsn}n-S=3dKjA*UKgrv)%@yArhzBsaVh~pegu6tp>sz4N(Hk5^ip{6YF13BM>nLdoy>;C>XXx0%9QAe0ny5?gVB8j*`?0!?oHh>P`^8`38hF5Q?2XoMb zUmdm1SqQa)0v8>18KZ|n?Kra$SX_uDX4-Q=uyKE^7eZuW!d$4Tvlgt%MJ`?N%WA-z zF+6w^i)p|crthq!j7wWxW05&R1RBOsz8xpy?j?$P+T7N2136W;)tIhwhXFRnL=IIB zRS!X(OG_M9?l{%?Cg01Vc*-XUJbAg3Sl~$#PpVF`@d-;5HOgnu2pBgu;zwV=(R5bmjYwbBAxkM=C+B!J!L>wU@5I}UImJ$UjLQcYWSLbuw#9SVh#T*rJESKZ( z_l$fABP&ikYiFApJ1jl~lVAoomO-dtxZuMG_#{$VU_wM#XQU>O zH>29H-JnhX^sdNV2u{I|YaL5hGjr`HBWxRTtq`WPqcPoC0*B)kIk(EWP0sDr)a(+0 z)5vU7wP~zlwxilSx3t-s%batt;#92{YD%&VJot4#2dHyAg z%Sze8u*yyO9#U3`gUnL&g6+=GG~&Ty|8Ku6HoI)W(qei&dUa-X8S@u6I;O;!-coJ# zQ`b}z_`KAfP`*s?)LAWUAZ2KuJIj<)ZS&t?}A9VUG}730~T+ zQNrw}30XO^_!Dc3;p;{4hL`|tI~43i@e7q%#|NJN+*W~UfbYXlGC#AiSC}AUjPir` z59eomaa8K{rZh@6F^AodY>3{OMc8B~e}sD`CK-&1?#4rCm8%OtoC&C>!5syf0G7Dd zixCA20Q{3Yn~4O0^9hKjNHCiw1UgXN!-X^K0L{`W*SKtWo!8wE-0rgZ5M)R!r8;BN z3hJT3)WCh9vZCK`^icZ^(R^Wu)|HhGc{5D9FoOOcmm!ieu^6w$g||Tl%LyFbjy7(P zfdH@r?}rQ=SMj!CNu=qRv0sJT6w`4}J$d!`)P>B(QWmYKpvkho&9@8F=YSDU-9-nC zp}1aEUhG4pyK4ZRf?d7YSBzW?!_>vS*^x`|dr2?1Y|r+*$m}n7LO!U3?YzaAYI_l# z*Wsc-n5@Bs=7NmqJTU6Vjz>UVVM{*|e`D+r{UW09G?_s203R_S{{%0Jv*QzS*S=n? z#hfX~Qvc1KHDR|(u(MR;#>{3WP-%Nr3?m}qQ!18v2Ahf2KP&M3SLbzw>ymVZ`XK4b zil8e95QVOsjG0cDQT#Dmewc&$Q(gu9aTRGz^(~`W-!tmv#u3aI!atHqZt&qy5-0}z zpeJU$Bsk`|LZ+MI8lIkNN2OFCgb)^9dzZVr4YQ~u+wc(ndN93;FzdSPd2yjDFZh*S z8wesw_gr9nh)RTm4Ct){hRDIfM{nhWN7&YcjHAe~ujYLde23`yr5$8}i}2vq>vrtT zU4O&&8+YxfC53q~hCX`E)KW&RVI)7A1huF_;!~w5;c`Me7BdKFK%<3zQVFfh5C1zF0_1B};EJnLC*{G#5+cYB#RRxE1<;MCM@=`v4r z#j#qw9jK%=dKr_Jz-`={gWTL?wbkK()mAtDd>R^it2I3CR!4Z8ITx*FR%9l}Bx}ns zzOi8tt@aSR2~2RM2)5OijlWE?^B}qvX~ym@Fp%7OiQPQ0L}CS4NCuqYVPbQoW9_)& zzgD;orIXm;3T+TKx$+<6KjiF$8!`W_NW#JH89Ll?zqj~ICcIy!cF?O*p0(m#Uh^M+ zsarC4-1^3Ua5(cx7P(E29L`f{l05h0aCeW_Zw0x`FJt}`@D?T^{$Vd#&?&*<7Z7R< zWP2mhc5#NYT9`_+^IGXG(5^4xe6y)c4$cdGj1}C%6N@#TazT9oS>Y7>M-Eym-lDBE z_Dt&`fIgvR@-br_QA74Xn?W6ugJ`6l>t7UF10O2rDp9+km$mffiTvb{%3n2zu98ki z@JSP#rf`x0VDHhOh%^Yl6oblXJSFuXPnQu2N?aqsSWE2Ub5v|L1%^C6OU0?|c%{#d zNPfYL`LE!&)%~TwfP07sZJVLS zgLfZ<2Bh6nrO42}8GFjG7cV&*Ew)yZdd^9zCBlp*^{1TFSd;32PtYGB6^txHMq#Du z4C+(M9BiIG8ZAU;o1JDb=b0*^ccS#~Rx{ORwH&2q;3IxUwV6JJ*xJ70L<$Ui3u3;u zTfx1UGVT&ZY)uATxb;GeGdh?cF|<7V!2tvI!h%5Eh0kCjVv@PR5rH?1@cucM6<( zm_5HK>sFS%GW&|IBVC)aV4%8Y@VgPe@*1wY4wg!)YnA~$Z@A9yB?}m;FQ9_Vmxf;) z%FBDqDu3r^>3@R}BA+YpX=spfQSu;gY zVAwqkkT6nFAh=-b6+YrYCm=Ycg(egQm`0Qd#?MEW?k+cx+_4NQP{kcqbL9;z5Mnmu z=e2EyUSy1vfj+~@i_^ttppAR2SwK8eL=4_4=<65i(Ixaz{L;Kl^CUy=m;z#MG&JBpdO$Ik0cYthNQ4se z3M%u|t(* zBn_fqkD(S!4zbB*?0}9&h=wqW3d^N%Fy~1)i29W=vNIynfuS>4gpLl`kg@%G2?7#H zuN}`35f&wdBu<3P+9PX4bdXRH`3dL=ShMS(=w!S74%S1m$8Whusz6FZ{ojX)v)`{{ z#?(P5plla00-CcII)~jmc4x1+{BjA@Ac4~0ZYZP=o0k1HkX(c+2^0_mr#6yM5@5ih zDXdFCA%i6t0O=@bp*^9>T|wx-Q{B6fk?$P9<|X@phg}Qgj2@`8f#mzsxVw@9t(fgO zP9)JJuPPK$tefK2-H{9DKm&}(RZ*ft1lt@mcR+7Uq&k8_G^5( zW#?cZ%v84cy~;W=Gi>bs0^eX*4B{Q8^Wl=l!t909=w*yGK}Tw2^f{;iHbI{`= zaNdiRcW}~}c=j78&cLVGS2hLUHB-_JkXH|1Ows_5SnWJmn~SHt_uK6&Bc&t?S_hkIIUX|{l@Vl8EczESGr#`Ic8fNMFd z6Zj-jlj|v6TjD)J8*&WB@;VeOx)&%|R0FfGSvC3mGA$U97k1E&0K;AtUrE@^Ox=MgR{($DSY+z?N~>4&V~T$?vv0y?r2xEi>PdxVL6$0)SD ztZl#dmaKOEYcfVVu@mSH4x}hs1Yzy~B(Z^$-v-;h{o4LPx!Mb%Zie{+QBp+z7a;S; zJmplRbvZ`Sm*wzC%xxT}ejK8elsyd5{NEsPt6?v@K~uR_gr`UkQs6AcWJ6~$H16tu zuz+tqN#@XEc{5eojfG${{;>IG9>JAp{|BMzcDgy=WWa+Em_GPD6bQG6`DMst#7e;ezIbM=gpxT7 zJ=1{zbGHWZ4~6+N{&t3U5gy*By`W&j2sUsKl#`@Q_Msm}7zw}Cp3aZ7Ay^;r&yrak4FYY(!dL|DF@RSUK{YC!3G&jVF>s#wKp@x!Dbw% zX@nND&5{S!#4}+HLp~^to#ruoC|f(+nh%#wq;T7+8={L_t7)AZBO)T$RjUbhg-!o$ z3ZGZkACeNVby}`lV6j}Rgm_K{SiLVZ1QF)*dhemR>Cc3iF?(lol4P1RSFYR(TgX%ypPB*oQ4;Q5lwC2A0}!?&{Hje5m;O%4Gb$5G}H z>{dgAp`D zEi4)m&#DnJY{vK4;BMq)tguXUwd~|vjw`OdD-id9SM5l5udt^WZ~2yYzL>;89iWhP zj=sP2o+D~Dpc4Ei5X|ZX6lO(yWQ;K!BrS0wKFg2U!7BiskUQ!32;~}b9|a>R=_#ZS z84s~)VlL&{xe*Kta~38asP7PXGI)YTJ(E9<2DuSRg%HYzIfdH?IiF`56~R+h6n@cy zL|!8MLMlSmMy-S4MxjmvT~r7hm*K(>VLf+WZYaWsvMOJK5Ra4qqlv0{bP zZe2dw?Hd&1+kU7Wx;znQ-{Af(vfNz*fyqVEL@4MQ?DE^w?FPx|Dint&rV+?i>;>g3 zDfV{tgjU``K+ogcg%t)Hj6GTsPpa2&M!oJ%qh40+i5XPbA*auL1#rzn9cWf41Ie&5 z(!^62evA9cLa##;A`KNm*zbmG-$g-gkcn{1v2^b+D?MD$87v9>y;<}94gvJ;vY$8n ziLfP6Wq!aROheeYEhyX@+?moeE6tANvBk2Gr!p57a6~7{ z@-w?8))!b_dl;7x(Z+lFB%Zb3=Q6lf?6QRL27wM0n#hU}EfMXQaiB1{To21oKQ?5q zm7Y_a^f3o*M>{BQa)|@zV96u&Sn4F=qH``_pDN4rYtMRxyD@Wy# z7Zbb6L1yG}5vV8Z?Lgq+T~xxRh&n}f#=1%&(>DMBt!EX6s00QedPnDjNKRR{SQJ+- z1f*9BAp|RF1P^C@9efA}4?0G(FKwgtLA(~+N{9g=__%Ej7(szOgs1YsJQ4Z~pE$BH+-}-K%vzI+m9_SIa)LGMU0mhf&C~r@M#QJRjv*j3z}03i z4%sXrw)Ay@EuFz{;OQ0D=Tu`w7~n~nAvDE-VDXmI#Y0-&S>N(dI}JPY2Lwd}JHr-T z&K7aWP2pb*-+Bp&ZI`oWz7_yhd`7G;`tAVwF1!yp?`_N$!29=+ijU)fJT%tF@?xxy zLUkldb|8j2@$Z>*q66$fdo&XVWPXuK(S}>sYZNL%pKt~j;)Z;%>xt)(<{!+%TRnxO1Hx#Lz7C)mB7x z1|`H7ZHH`Zz@qR6-pDfsywDqHh7(UPxNZl&eW<5pHdb6)gtJIZMVmR2+QQx1FU`gaL-1;|g8{^|VG*;zSHa)Yx2S z6?;?Q@fe@Yc<({p>2;P>GZ^RP<7v5DUd@c1LElCgH=D-e_eRUNIIRxCCZHc1QEmoO z+6wo&$y@^#3@Mb_967X+5JX@g#)mdLPq2T9H!h)rJHUv&KT}2^yf(h(Ev$2OmWgL#X#{!6{4$ov{34dptY?^2g#Spa`B%OMr@$4s{`0fJC#%F6I0eDD znNGnfqgwErmSBI8Q1F*_{WvPiUx(A4X?%7ZAK{=UI%6F08?8{`g9#$f;7MGK+9Bva zsM*0+f9!0CK?aY4Pi{cVi!+Rx{9cWGDu+c;W)t~=YrTjRT~5Fq3!fw$u~#Wt;ePVa zyej+|B!Y>;IG71=dU}!^z3lrir3bu+F_qJyk`e<}de-SlB=ryZM3l@E#%>3a2l<5N zo#H`%;Rk~N=7WtEHc(g@ugE}WGwcl(2aMMg+xc7OYKa+&RTcAdH9iFae|R`h$VVZt zQ!RmAHK1$MQ^1J=a-I2Pm_CNr;LCmoeq_MtF$mw&JhqSJF<(XYrGt>fmBFI?3SNI7 zCw%b1&8Fv=Caa&Q6h4jH6F!9oBGZuvpNU)5AYDUdTO=+YJJgRuq8G7K*l>qws05PUyd0c>N?u)pK=lROa#^+#~>+Wujr*3#VfLs&OXM2heC zvF%(rB5+f}m#fcgY_}ivd)^ufKM&LJUR)?RyzrxvWAM&~TMO3DIrsB`TxF=+lJw+5 z@~Oaq${K@c;SEMxy~(lfR19(%d*}WAD*7N$+yp1zqH347u**ax(EvTKvBUIRoIcAnjqcIfD!HX-05Hbe;22NHkh#^@3kz^jplWo@5~4w+qA_0h{hY zGOj;~XF+oogx#l*<%!desfiXhKbX|xES8Ft381&+%4B4rlot zthOCVSP7e2n-DWDful(vIgI4GKlXPE4k4T2HIF;FjRM+2gaLSm$(9lc&tkSeHk<8_ zf=Kz3z{ITzkvC1Ode=JxA$~pDNSdB+T-kQGpGpoC#59dVGBfL>`H({2jc}DZh>y2J z=Y*AF7;FR97%{r|YsToz6qWt>;yIS7y0={k8jF$`WVk$m+Hsk}Iyvo@O!uc?Y1tux zo5Xm){_uP6qNot#gS<@*3Yx?PSrYwDiWE-MwcoPfC9KF&jsIv6t6_uv0^Rb_)OVH< zV|@YQJsaN5Dm=Sxq39RJ6*Bm)i?NTxKEz~Lf8n;q#o6ODjmSKX*g(|vVOaPLTePmB zG~;9oS0ftRLqh&`<9!hFH-lvEKuH0x7~>QAAqJ}hes2yK?Tm!3##lga2|3E^Xa7B@$oqCvvRdQ1(P zoA(wVAAdr=ROs$JM#qO`1H-d;2l`F3_jg||K56~<4L8Yeyytl4>34!}eF%mK2iI5& zHMf(63gFrR1$VOac^SHU_WLcT+0IK>#%ZMvh~c?fSHQw0?1DwNu6iawf9ucpx-wX~ z_vbf4-axetqvOLYXN-LKRFLNEkhR!j)Fm1!aTQQ*Oat zn-H}l7WWAki{KA;00zdOB&@7>jBwO6Uups?}4e{4NIL_O@=7|-AfEEZm7a;SyShM)R zM-y4@g@**+4!(P7ZW~;ht~MVg6Beu|DyG3YAfaU06Wfp9@>MvI1rtfmd=;E`u)Y}! zI(Dy8AG00|8X_^s69yXJgdB*vN~RK@E4>2K>uT7axDsN$pC00ly(z9RZV){YzXfGO zOPtu)d?#9WGmB(`-KHN{#TXyG*+DLxy+-BNn{_`O+ne2q?~hC{`XMX=H-*&doD%*x z_GMj@pIFJL3up6WzabjONdS@D<&esa^qeXoqfO~{Yu$I*MtI7AuW|3|XmBVysHbFO zt&8RH8ZIoclIlSH5 zVd&Tg%eIG1K#Fefw%%vge??{s^DXk+peJ&T9&6hNUNoHOiaN0r%;w}rDEBl2VN}>&1Y!wkeR@+5lgXJnVHZ+ z3k~+gVLqgyYoe?0ySQS3t1@Q-7SX8rh>1jIi03avW!^qc!+cAWW^NYxxjIxt>wd+mN<_!zLVTYyN`&ALNUG z+#hQL`UTOMTy2?pydX*&l+z}Bnvq8ktqqUs{s{SHI7CHv95Z1YoRHCFM>XR_$(X?n zuE{kZnK6`E$L))|FnszAxTu`N=vtncDWu0P#CU){yvg3 zK*6Vp9ON;dR!gHS5E}6zza6PPF{_0wL&Qd!G6V`ac=t45GJ3<#8@Hd`Y#k0T`!p)) zFR^{R4gVo6vIU0>Sfkp^P9jzeR@$BfjCb43CaCKRM+(Ktx$+~HnL#uQyx}fdSX@ZL zvz{|-0M_wE`Ef$aC_u$l=OUn6jWh5yVq}`Yaxn%KQcVvc zFb^z>{9G6{;Tb8u(#|1(dz~(jH1|4BwAoKbzHu6jmVFX&-hs=Mv9CY!bUZ6njQkk({!A6@}>bz%y=T9zsBJW`tx<% z!rP@jwQ>n&O+h~U2Q{p#j0rs2MKe(kf<7Mxs72I0L`I>Sv`!t5%tcN{bT<)lj&UvA z4;aJCx*IW&U`QE>KA#^y^$h??{W*a&zO?FpUzi^z1%F{5F($XqBLk*M5^FQOXCFf~ zPara{i_r#yiM+LCIR_(!FE`mJN+L*>omUaXh*S~A@!)63@V%uX z?IkSlL?MDedNo^&Z^PDu2BsS!D5P<fW9|}!amfGh9UMGD1NU66ab!x z_@uD@7uGHl^*g+hiTVf{Gs(WKW8Ywr$A^3)u+l%Bft_Exb7B&(i)1&}nnt{XB@KPn zFGOK=YX$BDsfcnE*9vC=l}d`R&vsS1``j|cR!;OG7l;S!uPgH|?ZRu)mL1f%j5)*;I@fwQ<`h1f94D#`AWuBYhUK;T7UPq&^ujMTb6 zwrmRy0d<)AgsnQpqQh{On1pHgf?VrG@T^>F)+y0z8@|+V%&yy@s||D~AFROGOR`8v zOAb&!Tf+68+X93+F{nW)wt<>UI9Ps5I(L5v!pnlDQOPFH=S}L<)P&`O219%z(8l)3 z`t^f$er4NJrql;QwXUqIHOOq#fPvP zS?h-ot8)a#IYy@AHz!S0Iyj{A$^Af&I%3 zS+cu(vqm%G+RMCUlqxen^H$Um5_0O&-MY%gmoyKF%5^r!Ji`_?;1|?#_@EF9`Cm~?D1`P_ix@7!l;bKG08`xL+EX!H}Ga+9Q zQLy9}X(9<50WXl>D4+3f9>#H&32xKYEG%h+8eG9}Y&fIX?1@jJaSkv-kbx%NcZTq7 zg%1S*sqYkmvGo6V?N>`1FIFw1LWcB<@rTJGDY`6FEMmJbk_${UaXNVS>1uaf4p^J_ z$Llx_>GWz`c-DyqlfKW^C5#q<*sedyleFot(2LOY5MX=)Pydk3!QtBNE6W=JLW}*t z6AN?4gr*7A%A!KW2eXgOcq{AR1!i9h1GNt0y6%si+=4^)nEVP8kIYqmpF8o)F5ds` z)N@M#eq)hZaPnP;Jsb;9Kl9{&Hvvuk|8Jdw`iI1`|5yOoh$|lp;r=Qg(Oh``x^u6o z&wxz-h_x;@?LK+>s(*tMPqfe-+p7 z|92wQ)9eHnUwA;C<(COK{|Z8tfcLoyRm}W(R=S{2b;06fYXUEZ83TTV$4Wt&L%kU$ zy@ijT817s?d&2Jp;Gd1S%24$nd>i6Px2P7?3ho_WhF9(QZBt8B2Y%btQq_syC2E;k zj^7UX7TOs?NAq21u>fX1tqW8`fqh`1s9?KcRPbSo*iKy`7o%q@8XH89(1mA87hq5+ zU=JtxZQ9j`VAUUi-!{Yo!$SKUweaUYD=qu)X2g6arV8-?C_=WOvY07Pn_{frEG}%M z9(K*{;z|hFlW|zQH0)3F_SwKvI5{c7lSZf?qw!<3aFCg0r=Q<6Z!0I6E5ss(tw(kv z!|2Yvou-viOrwDX1s*uiUn~%D*83y(M!q&hY&^21gtDOrl+KVcAP=4wT{u`f5UHbRU#9SY(xdd1Dg zxrx%jgAmWUM>YjkFvz3y8rX1EL((RHP*DK?y1KfyN=G)7^CQ%+yMrsJ121KcKv$8=`qDzNxKH;HHFP_tLr1gyGOcSF~ZdY!7_5W z+3aCRrNm-e)q{!e@z3nI&5oD`1Qpl8G?YynK!x7@H^joOsckbB~e z^fgd-uw(|f3aL_n1iO8dqc1^tue|$Ej0sS(;Ax|45a0D-XR_4C;&F?p18F#)58iF#yT_|fn@I@)(xqkSISzY)!vv)3AhN2EbnGdKFEcH|Pnl5HgK=$S-iOE=y;T8df z4g-Ndf7E#FEF`TxhBuy%RoCz(u}ZsO?OyPcLI!@dT0|IIsFru2mKTL;=?H2uq-9{I z{t0+3Uqqm2K#_$yRq50eF3cXAYUH5V*+dIz456bCe+E~GSy6>;23JX3;d5-bO5r;p zJI%Mc6y8Bfb1?-5AHPn9`*o@*?vZj@5bPDA3q*#vo~ZI0)c? z#Wgf0F?tuSJ8&J->k#KRifF$HN=HbEucQ=+kD*0QMW1&ny5Usxm5^bi@BhOlpW=yN z^K{oK7-dEZM;iOW-61b!UpjC>xyAnn+i!gz7TH4-$9sZA)M6SWI_a6Zphf%vQq885 zg;4iXc=|FQbsH))8(e&>MOFcr4>Y1M$j2I?++QG!&{OP(XqL`kS0sviC>e@m`#IIURikuoo^tRJNFZ&B z(5ED7K4ZkqqG=pZKtzX`8vY~J@vm&*)Jlm15oOUdwj~=Y5x%J{+m#W0 zNVY3y>=v_O84_x%yJZ=Ys9Hi9^e!wc))GLp1G^li54iCPH z2y$0BZ?QIF(u(dNH*cNhShBtDX5le7GVn8%_;LwN$Iy<=#RZ5Az?MdOc#|rfVp0RA zqWkp6F{pJ#ld`ecOIdUfnhZr_=SD+lGU*iP>wh7ypa(2Qe?cq>I{Py`I>FA8u*ing z%TCi(L*4_ofX(D(p)R{he-g#Ids9sn0`E_;GI8Dv2zv{WQ`AaaSANT@o9oJH!TfAX z3l}FcU8mnn+0d8(5Ts)FvM~fO)I+Bmad*){=lnko3GaVZB7NU;tkTfzI!s`7thOJA zc(ocIHbnjc3`{gXpi#r;SJ^v~$uf_%IN=L2tKq5*^r61liHk!wwgEMu66TVSo;&f1 z@%BK&Nu~t1W;o6Q%K)IntT(fFoJQQ&+Yza3It_05j5^|&FMwgF>VvF%(E+5A_jkSe zNZ=CBRJxvHef~;<8iarFxn3eFfx|D|$sUmlsi0O-DyU8BSnPhXVAuG$pr98Z?rO|Q z&w29>_}p~4=)VCECfvcFkIHjITm=6+h7Td|%eGIz_pk*$>pOKl&UomaFhhlH~}FmyVrUyXXLjF9gNzI@r?*g)$8By8KqO4Uhob9c+%qp0hRZ z{sMeFGXtI_$`>)YX%hWtnWpZ4AbKF;&5^PQVUqtV5(EMMZd=~PK$$#s;(&DBkU z9mh`GCW_Zq(?n?)$r{;~EKANiqd1<9AtY@sEC~flDQ#Mub}0=De0mCOX%7pW?zXU$ zOKG8$IlzG~^uWSdc3HXX5=_6u%C0zjy;dwdEe)Kp8s?EKmX@{WmF%2 zD>OisP!t*Otq4YlNrzyhtI;LV#1lA4@P5(W&)52%$kr4h;}<2USm-~JHUUYHugm)) zkTa?Qyp?ucm5i0wIaPk#2M`2L}ZUP`O`gY3W-v z&l+7OGQmbqxqOjHm%@5PA`?n6xXvl|*ciYP~< zyN}wFjXc4_4LcTX_H%DqxWw|GBZgcL{gA1S)Gn=UsnA;TK5p-AS$LKItANKIc-T*5 z?OC?b19a=0=xOPIpExiBa>Bz~4_jT3# z4JF@H@{*GCO0?&hg`HHKR+dhpaf4QEdEdFq0)2nS*@gjWZ2CY**Rdx$kTjR1TJ|Ap zy}#`=(~ly5>oPka*DFR=s6+m1yhFxb?L^ZL2;y6nyibWq1q}&mW1#^Ke&-bQ_mqP| z;&MYf_n6%9t7^812n}l*5`8=On%VmNNrz62TK@$VAey?FxH#Iz zE~^7XCOvvy-pb`jsgJLW%nH1Tan1Q$w(!jy4v}(Q#jocIp}Zn5O7ep=sXFuzNE zGt8|*HlNL9`nt@)g^sG-gZ9T2QNEhfb zM>vRR7=(Bb;oz+yf1)?kzhQ6O&JX|i2VBlQQ4Qxr-ffCtTgkBBv%gzh+S_XQ!bSx? zy!RaUp~Il9P#@@shm1CU8H~(HY9k;*7a0NkePPR@<_Qn=t9b!p_6F4`8XbQ4ZRDoa zsDnAwcv0NDPFOFf>ru_&Xy#g1s;+@}UGNL6^KGt&NY2}>ch^&Ga0)gwBbZtTM=$oO zM7|7DA69Z3(%xlywau{MB3IQnwW{)zCTD7W#kM7+p4r9M6w{+6#6V@x&j>92?pQf- z;Znmf2ZL>-c?crNJOA@ULZ&c2X%;z6E`T@-k zB}z179hi8QJKjZPWav+5PV)iobd=4PH`GW&6$Tr!sGrRE1nWSZx+-OIMK~#mo>$*q zc0pIf*b7A4WwT%CM2T5=~Lo}*;NoGnyuH}4!$ zxdLbMD}~QVsQOC&8D~GE`N{c{Id6(snx*b-fA0I|BW6I)gf+K*rB4Ja@=R$aaVSN@ z0*z=ncJK!kjmUlN4+W(;zmQ0m0m1e!}juO)kQ&Hcd2A-sgl-trDxwCc<#1a<5j^ z+G_%qY~J?29Q$A%BYp46PUEgMx`(ehAY<=l?9H=E`cK6JezUphHviiT1gkLJ3zKh8 zPag=h@23g21d}?hFCxjYcx>i0GUj(ZuGkXlF<=Wkk6&w~{+QOg&_B3__ew~~m#R|v zkTYhK=_FRRxFnGucX6`Jz$}u}i>2P#XnBvH`N8;PK@N@4fHN|W*(!IIbaVEKDWOEt zTM(D}Ha1exoY027M*~9!f^E(!jem4|7hfY0qm4>5#uN&Utcx5LjWLjE;EA@?yUHj! zDb?s|uxVdD6vCFSZ)vRuj&yj)n|LwTx=k8EU`>q@BO%79@nBFemWTT7Z6!$nE}*sW zZ2FbBa7)hE1egcl`X|F31icM5N-?m;GJo1bsgh^UNB%uEMVy--W}UC@BdNU%;%h6O zi(ccrro{D-LLt8$CXcQtxImy|g|5T&^dxBJPTgjLoA}?EU7@#x>xZSIFu@YX&}cf~ z0M)nZATYPTYUEg*U{mOK_z~O#2AdD#44%c8dD<(HNwbbBgYJbCsA1sN5xi)IpvA*=XRQDTdFP z({&&ZK?7BWksrgd47cF!-FLytsD@1z^z<9v&W~$&^$In^+U!_jtPLpsxHu2XAQc?k zDoy}~K1fWhNR9A_0dBKmBsL_6!Z%PKJS9KZ;*bJ3QIh5@I;!)WKa*5>^T#%v4IzN= zG8i)^TFI)Cj?Id2VVEZaSwyhtPXJd-eBf;+g8u-U zfsHlt%@(DO>QG#pj4gs19ABm&L(swS$f?P&76IOZ;)HDsmOxCw=UNw!!Uq?&4)ax3 zBhex#`JWxA9=Z?!t(jRJM8`mx(IB!uWD;0RDPMa#gn*(2wdKJqMPWG;Yb)0F)wd7R z^n=}kFm@D(clwUg&9#Z?ojuecKI7%3k$HNP{=&9-BsS6^Y(Yckfj!Kr3S4BWHf1); zf4Pl|CjIm@S(ykl(X9(Nho`53YkIn^7QKycj<)-A1m;|=s=Yj;lQl^%RH5|&s-kUW z2X%%I4k6Aa8ikb@EoQX<^VT_OdCt-b(3u;g+iBj?9Cv*CP5e6>(wmEP>TC-z;3ffc zH!qB`KBa0#ghbOfn*nhtZiaG>8BVe3uV{i`U9I~NJi&DpzOAH*3DzSbtgmAW3J3GS zkFA0SHK(+~KzF7KM{yF-%WXqgQq;KVY4h~t>`@;AKHl~3F;Kc;fYy#JuN*yQlhqtZ zTAUA>9hS(}vvN2X3e9P?XATcROr2THYE?sNKqpTtGCZ$2oT#Rp#Nv7WFu_L%kr)fO zddkBk4C)8gaBH^JU>J#VXw=*h=j56SwS~skOJW_-9fURp;3-2isZWudM$E;?~yb8C_Eqi%#OmvH=w$%2a^sN5IKhQ9#U3@P@t__$+2ZNUO zCW*By?}jwb2tNXvXCZ^&A)3+Vj^x{>@lJ7&{->bv z0<;lsu1rWbfA?2eGyfB5JgfhI5qQEupqL14{{ND|+h6@>rS5F*{lA^G6B+AO6@<#g zr|3-Xxn%~gLXTk!nT_Aphw2oB0d=J@o9_Pzaaio)BjUF<3)o%YXtY;~Ut>4Pi3Mf#xE_S^l{e+=Tl zyH>F7SPfutXujUI;ctuqaFM(Hb<}V!w%hBs_j;0Vi9+f(0M!1l4=Bn$`U~UHF@;2i zg?@}2G4la#wURXw%wCPEkK?hW&uvH)PYa#K+XRYdErK2s=|)6gMBQ8u9NU8;@p+=( z-!@_Mdla%ebm(z}=0lbHTv8*zK}#$y4#Q@1;vYU-z&)WXKMWB#E+m0D^!N;fh`*u3 zHB`GR$py)D2jCd-CYAQ&1$+XbPz$;;hWiC^*8ZTI(WdG0Zlstt1g#_JF22gz^yN7Spq;pcwXIGGJHfs8W%9r(B>h zMeEF9`>4A%w#3x}JKBp(&ZiiBc(8;L#Ccx?Ba!7Ygyo9$q}#?_#`h+F>+aUsoa zL@XKK`>+Gg`*n4UqCxPd&iBN1|DZyeJ9532(U!H`$w?L+^T)%;87Z>H&j7 zVsOE*U^=YWIsI#Hct542t|Zw9<|OHKtk%}CSw)#&Ra$i#RyQV29JXpjGC69*D%#l6 zyj3l#)W4>zziR*1*ZD$i#|u`V7MP3b?|>=wVajOcj-RTud7IXH|Lz!At?l>K`PIF_ zN7n+%+AobLsf0+hdjwVUz_2Xi*hb4d_I`~zTKc`2mn*Y!`SDoQ>soq5&sU#qVZ~G( ztJu?8=vZSnR%&=MgKIWpLbRsa{ zPai!U1M0FCpaKrRiB~SvC9fPj_`PPdHR`|%mgkRa)LItG4hHdGUI5CVsMVm) z_#Jw%ONj=;Q#;+<&FV^($GRed>L%VeCaAP+paGLX^D-|VYVm(~99F(pG@~Sn&d%IH zzy90K8!pnkEt$d6R#7LSbR5M-+iy`l*8mm8~K zfDTba^cId6Wn@`8XvzmwJ!~BW4IC;h)wxmCPDE5r@#}|8&@?YhHP$&ioXsW|E#HB6@B4gmox~Mc1)*y7B#2;=@!fH)8#2r&Ew| z(&vSo!|;9}Ep{iQz;i377GkeQr&R5Eid}Ew=Hx3&me;Y-+Tze0@f@hEq*%DlU?3;- zYSf47at)Q0=y(Vx=0@Qg?j-0NIc=wJ7oDdlMh8d_#Oenz{V&VgqK?pcd;Tq6|RU)i4~FM*(;RVp{pc+ zW4l9k4?`v%54s|>eMR!`Wzz{_&f~Aem%I+{q1ix9F+X>Qg}&*`}tZ)cK6 zpxY<#p5&@)d&7#ob*u2iJu_@zk8a&AHiM>bV)mBcM5`Axd?)4!omgorN|oPw?aepe z9C@5lk$&-7#Fk4h(8@K5$_8qsH`z+}woqT7;5_Tvr)0ypCL+ zy(H|{RvyTzoY*%ph5l62%XlDbhm$d4zO@N9{fZ}82seDEu%*Nzl@5^UYn_-!%?pa< zmCmo#rxde2tVsi=kDEvn%e8R6fm>qgm&)@$c~%JoeH?3R)nUDxVf|`2tOE=yHxeJV z$bYChxT==D76zc0R8rME4iKI z+ieN~@qX+gCpjuP1V40mG(OE)von>eB;1dYAarvm$-4;bx%%urNa6QV_A5e|A}UU- zz}A@~hKtbjiJcCCUVsib@!*J7%?P1HOJKnrLZhaA6hm2V#_+L}{pL|M#Ac7S%^M}k;bvZ#UWr$o-B(<5^vQ*!+o@yN) zac(|tk{uL+kIkbQtW0N{#Sc9s1hE$q5E+@T&8*xZWI`2_RWMK-G(2Wj%@^e5C1VE@e%qICP2WD_nJ!`g zi4@1?CdR!zm3Q>8`xmf)@P>k5?z`G(Fp5MGX2mk;1H zCM8b(S7X$sU9$}&>q!xH>c&z9qI$Ll8AwxuQ0*$lcOmrPL(}6r`M92>RinWJzauBB zs9Egv59mC%NQ#2wJ_#Fn)4%5iSPynfRnkO}#o|6qkprjE{#+`(e*9V@fg7UL5!qBA zP%$^H`4y37JfkJsm?Zv`^jS$7sBNtSO3j_1-UhrrxHg@GqD*BrsM46EGmFbqX{=Iz zM%r=06j%dWi{f4$u6Ea2Yu!9NsG-_`!{9)HO?-(y$SUMv+uqV>B$kQEc=N)W`o>=-= zJN=lhTiA3mVgBA=pF@PH-)+QjmKXIgr(1#S(X85KX*$HzAQs3q;ELfWlg;tu8cqYL7+A#5E@fQGObI>`=_$~c3hePks)ssq0 z_xWjEJ)`6kO5R8EqA%Ap&vwy>n3VOhFp}6x;C93isk!eUw3Eh3F2PdxwirS@Ec;qS zu<3KJi+3h-0Wev}k@J#xDT!`|{EGy1*YM-zx|!~!qbmw!fL{efGQUC*gqFE*kMvV= zCz)#V@(|Y0fk+g=&_P}PkeV&#yKDBjYdSY}^#U%uTGe8&DD5tMVad$Td4+g8^&u0z zmx&m89S8lPtOuvA%qoj6HEN)sIS6GlLdecwhk-p zYW^y?qne_C_!&W?m7L0O;L>~xK8d+UE~m$LiEhj5}pX!KOY>gFW|5+8!Ulb-* zM_W6297kJIU2}QoQUe6l&9(d$N;#xp$jU-3PW2eFDuS%KUDw57SB>hvJ$$PNNM3H; zXw}5g6MjALr3PXufSC5x2wEu_afFz>H&O*&NiMvuB1{wF7w=8H*xFN?KA%)i#j!`9 zk3GD5c)I$?e9KtDn-C?Jm2Is}?O=DaEkhY|+i~JMmGgcJaLs7Y@3>(Eo+` z3nfaqs0Hgys$V*(5vy~4M_?DY2+eRd#;U(a-oky<@HrKv=0Qd1_v$l>jIC?kd$V9% zZIOQ|D7s~l3#|x&-O0Bq4k$j`AO!H(cAHGL@p6m52fX0n&D*@--O|Vs?g#xcd`4s; zC2nb?akh&8yL9Sdd_#cy&GD+PS@w7-G_>2x!XHON%-m=F8*1@>fdn1i5A$cLduh&M zi8)ubuTnEw(A0YF{h?YQbeLjWkSnT9xT5MyJGV7yPy1urRMEuW#kbY_648c(xGB%$ z{{qyd5V%5GDT3cxP)aMXKCHthXFC`C)}39)z2BiZ;X)HmQ!lD6eSOES^a{J+Gf=O1 zw8P&9bZj;jmdR*j^{V34T39ALa?;B1vxY0psdap-@1zo;$3z?^LgiM{fg?W#Y|EJ( zq%!W-L5w9F9ziw-js+hsiKcr3Cx!e4O`gd&vgYP5y9xEJO}Oc+;L?t75hr%!qqO_p znmN;ZrjHW)YTdX4S;D1ZA6=!%(BJ6ymMEzo>M@6Nk)=j|1^0rLJdTx@aKb0IFuwo< z-G-2RahBI#Dd04(_;B#_uZix|jZ44GnQpFdZrJE?kZ`B4w4G^2L@)^mbdb|dAqPFWlj@sAtF`r1)K_cx72zGpxZ}V2k?VVaAB8A?hwxm z$cag#+njY4k1~B5-J4Rwsm1^irSuy|ew%pC*Y6*m;XqM$eYc5HU+weh-qCwxYL8HA z3A?ko1V1J6cA?X>TMIeH(m6b--;jJWpjad2ldE9e9IatRYCMy`%*EnYdFG4nJhP)d zyj@oP9Gy8;qKb?WEZ#sNkK~WW#kVpdz$i%3&lc{g*)s>rMx3xY=R#U*vzf(vfQ1;g zWo1tP#@4jNx0)If3D25v#H9;8yh*Eevyw|mnmO5-HoH9H%G9RTkhdJmJk@<<9%mok zQSYOI(8zeL`erZse~UA-bBfo63%_jbky^A5H7^aE7P+EO%|`LU4_y5BLJmS-hJx`J zChv2F^ZA@S(aG;zj(>yPmBT6vPIW$)lV3XdsT;=E++`V|lfkTVWWJfnZAuS(y?(P+ zf55f_kAtV;PG~OxPvG;|`uvM(<(HKF1qoR1$2AB)tK{>#`;fZLFy32rWt;AYc>1?$ zl(xeZKw2EFt8j*FD5({w#7-eBL_sOjW7PZ76V+1DaSa95bAel)K#QzRur0CkT-7Pj z-4a4)AR2VL0DEgM(5aeK`rKtL9n}&yYvMxDh^>n*HgJ|Q|3B&$p+@O}Z*`I+pjwj=&_VP%Zs$P%Li-t^eYw_}Dq5Htk%`T>-wZiQRrp<{Q}B&^iT4r`MdvIfwkN@cnt{ItOZh5r%n{=^i@CTM zsn}?}VkIIS5?IvJVvUIj?O5L|PK)ebavaBP65O{H=sybqY$VOVXQ*xcGZy?=GuorOwjg?zGR%h!*j=G9)S%mcK~EIY~dCw6T)pG zfQS?zn$gDl#?`~(3T8<@TrjJJIyLoKpH+E`!E|-z@bV+L?cnz*={)i`V3~)l4F^ri ztCfgg_4)cLuq95KUt@A~+!~42u3IHebuO@+@D&}a>UWHmY~}m!1=-_&rOP;8Zp)3} z$?kQo3nq_y#VRvVdQ?$oeKOzJHdbA-XseA{NAH>wQ8IenP1g{T<1O=*+Og3auDfO| zsSQU^GV7TKU2Z?41op1OthD4Sr+GMYxH|f%Y4z=23cL_$>4Q=`6&DVlI;_t73RLze z*T}2f2VSZ!F6RA5$D>{|5cFd7zQbF|=0rU-%Z46)bgl0!U!y;C^pNY($TBDGtrc0& zfir>aPgxq%WSAk^3Ui@-)voOa96`5>jk)E-rR9SL+dCPFnGEICG5P^c_Hp>!Ng z6BO!7Xm^E}f~AV%eF97QB#w&%<%jG30~+Jh(qE;k$0*iup4A4(F*A~h$g$PpHa(>F zIC3nA)P(6yTgzC#9V}+lpVlbxYt)}UhtA8a(vPM%f9wKXxQeYzHmC?H$uT#Gup3mC zUahjvkooupD*H!{r?eESYF*3JP`Kxg)A5yc-!Xdp;aD{rTdG;r0*Eehl=Cnb*fwR3 zcSqhcZF(W__!*vg->Ke|;g7c(ja(A|#<}I7;g!ih!EvgEYxWV9^`sI_F2}K+R?+w9 z=Vz6iQKB)51ym9Z9^;cl#JOnX*Qw$mE})UO=QD-ufD567y#(?cN$zC6Sz+K_6AzLlGt?PMU&N_Xl^CA2k*%?wj5{Y(Lj$V7DSJcza4!%- zTWKb?j*bOE#~8`rYuTfDhn~zV%#5Q7Y8ZlDAEf|RHfaDgD(^mYXz#(v^2FZz_8u%B+OkwbV1oMRutnK7(3$4hkTG6aLNSp(!|z6{%l3A%4|22= zz4eN%2mZfm1Rz!e?nulQK(-XICAUBHh!6|_*+HhF5YhFjV z0#+R}(^SAjCFzq~Oblx67~VKVPP2sNm<$>k{UZ)G&R``r&=d76bXI-a-hc^b^Md(< zvQKPkxOlBcmuovq5FP)HCF0`FSA^?7$E*c2+x!+*j$wA=xLnEeAJu{}Im58{--BtE zUXksRQ^O3d?V9}-Wjg%1=J=_ybXdY01L&{S=a$0XgQ#~cR$TBrE-~yuPIpw$98)r{ zWI@SsB|iZKo-I8t>?65BH{U#8Ky$`{2zEp+o1LU>c9Zbv~2$!BS?!d;G$7%J0fy2?2t(85#%FgC?IGs-nYOn+&Jf@+( zu;ox#YB}^;oP50;4>3Jbp2YYi1WCnTj>lB+H`8TC%nTRXJ8WMf>#h!!jV6y3(qHmD zg`dDH>f-3^BC()_l^!hn5vhY8?FS>KWuiWT=O2_PCJFYgcrA&y+z{+psbU;9=iMVH z+N+wcICBzJ5&uN`Ux_l~opf0COIArD%SCjKgUlehokE<}UZRi=u*0vX1JByko=k-P1%Qk;BJ0=L-AFv6ljJ2woC6lrbN(ukc!P6Pkvn1wnw;FXDoA)m_lw$&;!9kY*(Sy;m7 z#J#RGk)1i3T!qz?-M7zjE`L~pP~7qe8Z2J{lGsu%0E^{o0Vgb|1kWl4*tY>r%a(Ql zfc%{Q^dKcS)eAu6GX66F)UvBnBgWFR^QO!lo?aI5E~}+0=TVrZGim(g8XGlcOt3Bc zL|5QJsbr)BOv>2NWqLYXf2>8zFv@W)Mkuw>SkWRuXPYSmI~o1oXfYugU^#{mX+4H@ zMB8(@OcUiVW&ZFATD*4hLe1K3z1oQnFCZL^!4Vp=-*oLNhU~MDVBtsCqH|0Xzln>d zQ%`q2oqampzvGGt=dA;6(yVKWX@b4!?gYS(~a?s>?I*gk>`BF7?1BQ_g4 zIp=a)tICpin3QCL4N<^;G16+H0xbFu|p(bdR^%>Vsr zs4FZ_!(wjA1GbLXF3ZC1I-bTF5MsESZ(0SzO^#)!2+kM*<$aXJ=t9@LmPOin0KJ8$ zL&7iOE2lQ2otGZU<Tz zD9jQ4^ODqDQQGg%=PY)3eyVq_Z?3OWcs^so`P@{0MRoD`OT!yKc58W&9<(7gLDz*Yw>bikSb&T#Y#Q0;!&zs zS~+R;Tl1nOAL<~O2o7nQtMZC8bL5Cqw=-v{@s18_*GMILE~IK>XnK<72kp@C62Ha} zgw~YXg{}D^6N&1pD5LmC>8Rt4Fb;8A%LYTau~K+Bpoe3#CBE;2E7vSiM4~L)s_=Qu+QCi z&5l*}IkQcAG1DvrE82TEyBq$4o85kIdk>3>)m_Kl<>>96Erp-fEjXe0|r70`#h ztd>EZfCK^QST1nEXwqslY$b^{$#l0bkFM_snZs}c+yK}_mXHVhWWaCr0m8A0CfQiQ z#bW;g)w+H8mvm(U7Ed6Da0fE%+q2DT*#qJY@GubppsfC_ratfmUbnXR$;u`ZLkk-lh4RJ_h5~RD=)X-BG?{}LX7dTn zV>&;D1g?0Gaib=gMu}6FMo8@WS8Jq>ku+!*xWh^=q&}$HP}%3|2gXOPBLlg z>qp0YQ+l?X5CL+)e2-S!k1F}k%KRZEnxAf-+Z6v-{6vk`BqBD`|DGGozGZZZ=ZQBa zWn8yt!;`QT^JF-~iIgcl;6aS0hBEal5;I(zZbFsU>GsZ2MflOI5Bt!`!|6Nb;nPm< z-u0dIQqtROG00^wN(=IzgMjiB1+Mk`uNMV zB~cvO6nZcCyuwy_E4<)6qU4m4N0mf9F4#U4hwUQj65Ax+gUmA5gDrJ|)slnX(lO2M zQ=Z5u?_+TQUS^>ESG+d@c>RGGO&Z&@?6kVHymXc5C8!QZ35F)E@x9WKYC2wMMo9qd zi41TT}W@}#Uv9weO(Xd`q1zn&gC%#3UZ_aKG!S3O&^N@`S&hf&@O;TtOYmBJnxg$9llHwU^9Q0_@=o4i3 zep89qlrC<3r!)LlDgQG91~IG|6=YJy^PcXuL7{>S3L(CRgM!T?f}+;7kUPS*j%wKx z^Z*0ADk#>6tHJYG!6Qv2898JaDfch;^~_7NS>5(O0$D>~K-B@+4ENYo3AGIlmTnRh z!Cle>T)-}fZqu)B{W4SQf}S>%o1pc&^F+FOmx8i3GS#VC(d>(IZbxfmYQ5}u-&^aW z;i6n%aW3Smk0o;jHDM;_Y^$J2P!`Lwvp>?vK(+X9^LyOb7C5p}kV9t$89_TXVdJyG7-pwBAH% zdCJJ>b_uuR$!b_Y0cAg|(G_qdRV^3=pt;4VG89d$;msWi?q@%iSUN^>vMI(qZe7 z^^Gjo8yY!EDVhlh^^I*j_d|P*&EZN1L6GP``@ z8C6hY$npFW3hy;VZ&8)oIfD+|nKsf}UfbZ(13TTMgmp^x*upd_upCoDK<}k-IBRAA;OxLFj^LwfvJGyf15oh66Z4T%syeFyTaC<5bmz{8u zt#fD~IWB*qL3<;$q`Ehx`_5-F121K=GDItWBh#JByVE!g>sOD)Xd-*wjfcu7o)LG-iE7baS8gtUG7B2HX zYM2cCigA6$d%K;@(=ST?_M{bcuWKpo$Sq)o5*dPpaDA zWmZglJFCTeTE#r0WVJPNb=w+wO4)6pB-e!Z5~CPf4&82I+qBojc5^>kmCsTRTXi_6 zz2{W0*=>HmuHLD{5S=APm1!Dn6hv22SE|cRMOjsHw#pU!$(Kn?KJRAgo>lhXLmAQ3 zTi{21KpS`k8|gC={cT3{2X%8KbG+z{2BN8D$cL3bIZ>LI=)4dZF(-^;4@C`@DXs|gqHx?dy?0X>?xEGufqK93Z@t zJLG*|rqccXte#+X-qT<*V&spFIyL?hcW~8oB<=V>jjn}0VN7$AaD~<04;)uCx`6Xa zD6xS?_fdw8UmM^U6%3}Pl?s0d5}^6;Z#;ZOtzUd7hOt=x@LY47kEK=~1c@G5;ou4tbRKVfUMfkC^3IKnSC|mA@v4ql8s{(!dsEv+J zZZBDQlx1yY6zYzgV=;1r(vLaaI>!omBC-h7v{}THMgpI8A3mKj0 zWszUNX1|y%_#{wb-~!BG=uyP4+?U9CoVd*30=UP93%&#{z=H!=0v+pzH`ui}$lvL& zz&vX*AFjY|yhWrL_SFRzLdI8z~w3Xf)iIvAWSgieMYAiN{3`53xD&+DSlM5IbA=Y^|S zWjj6nQn(gEfU1T!_V^sM2M;81$AJiU?3)@4y`AB_po*oP;S%pX%&XT#tHhRq zmMKaC+Ed*c(ynH1d%`&cGg7A%2`l{9Ot zV3ov2hZ^A;TLa>pA)UwsH20aDPqQ1RfmOhc3BNTbio4SFY>s)=l}k19gkQ@qWXqWv zj&bngg1aC5dH&U3Q$B$M-ty83vs&$hDic^h00|QqKv!}x50UiHWdT6ODdG?do*Aj9_OW>)nlP}SCkd{ zyxv=<((+3jN>47fzzk0}-m||a=*0+0fn4*P9mQ15r1W<{EY`I56Dt2P)nq~$!8NP= z@ef&2yZBznA=jGngj#3zzsRg+5;Iu7j>Oy3I93&W9wIk$JYVgr343P1f0u#Bb2a%D z8sx8&Rt@482%72f^uvQSu(BLyDzEX=G8LqbMDUw@I#tQx@uzEPb48S1-do^~`6?3E zZ7Da;GxiKt0kzzXB(K|YJ=e&e&HI@D5$M22NB_=|bCbw5?<;k;U8R^cn; zERwz#WKU*Zi5`ALe8xuCS-cS$9oSzPcsn@+9}uj8k2lEbE5NBMlzY`8wHQ9!wi-T{ zDr=v}aJ`|{&y#^C*g?u`mWGvUVa>wenVwodzLV^(^LGU7f5vPtp6P8AYiq5XgSDZ` zP@}k!`W{a&eTw_*j(1b$jz+OjSQxhJM{Dcxy@celQRI-2P3us$q0x&(d*hit4rLoS zlnos(@)q;T*T#JG{#1Ri(bwqJn|gr0ZFK znfK?<4AeF+Tw2*+yvVOL`p<4@3}`odJhfD;y{e(s_CKC^OX~U*{;2xDtN(sq2lSok zhj)bil<~GV3TH2S0jMM+9osljf4%hV&1diY651UootT}^Oevapj#AcMc&B5}C9sI5 zvCTlu!plg&A#lAoR7!F>R7)>`+}ap&xS)+Hu+4=dlHoi0q77w;MEUCg)#YFr*0;r8 z*TQIbGjvPHs57jvqGH9oslN*O)Kvkw93c;@*E`{k;l>xZ>Fo>H&oKU zq}kr5w5<7tAN55@5JxEf40E@Uz9bdrk{VKc4KLtK`1@3%-3UqH^FeFHAg#ad8QI_+UpmC-uwr%G%FlbTZ*Bmr=|ttH;V^ zsZ-U9zM9qST&bSZamnO^W3TpJRPTI_&WN!9jP(A5?iu}U=jG3+;f5=p($(!sjFLE` z0)9%#f6%?(n%zOp{~{plsE#IQ z)TCy=)Qa~8R*MdEhx^D>VWvX0_`7cQ`8K`QJ0`#51Y*3Q_X*hTN^D4NDr>80_6LUq zfmLl5-Ik;u-iLWU)t?xkW)?r*-hWl23juuIeibVLxSJ{eTAgFlMf=@XG+m^ykb=W0 z_q>RM*p1|>S01ni;DmBC;Gg&iJ90c_=N(MvznSmCDFgooGWg2M$kms4rQ(vpVo?t0 zEI6i_&F3>ceLTr!N{9xD54&-dDcK>!xB!f8sL@3K2yOUtLC#+}Z`AL^dZNipf1gH^ zv`cK9V24BFom0JI@MpbCdFrN|O}_6}Z&^)Ybkuy^jkBRjRK( zA{4v$zKL&D)7D3AJ2}K`sOY!^>*V3bV3weIV+>e?X&~6=%(=Y8~hTd}v5T_Nk%V!a+Ro1Xg zvdN%elYc=+I|O>XRd^y*S^I4EOup8Gx=Ox``V5=E(2QiU;t=2g2Xj_w)FCg57Enyy z)X^`O&E3*o+!!sNo|_w;(8@E4rGufMN{#&52dZ@X8<-G=s(_}^F8#wu1--W1%1jAr zE!_$S$CzO{n~g`nk8TaJpda?&Rz2Wi>wIPFLovO6X%xC(^Fnm4L5kkT-bgq~ZwA~K zQM!cojWqMNOZ=vnWOw8lA`i?j zg+)mT&_`SGDqTPa`i5henIkD*)aKIe1N$vngX`bjD8Me72(gBwFy5bmQUkI@nefd# zDoss;!G)Bct6ZSJC&F<~_VH-Y$x7dA5wE{~k16BQaZx#e1~2;F z>e04f2K9&ErbeDg>OR{k-So!~qZT?}V?0_0j&ln2x|PRpmk=u2^-k|3c`)&e^5Y{TOFNz3S9} z2&j3io+A#(^$>-2Ui)p(u1O=jAJ%w0LIR7z1d&EpyhnGX7}w0B366x@GhGy;DvZ7` z7e=;6)buU_AH8AczMoLp@7GTS+VVa?(#&8WlH5IPgSl2a#a-l2tru$ng_QF-Bs)Wy zOfK{FOjqXXdAM{)Xtnva+!trgq!oI~wkxq@Aw8?I;vXtc?c!^4j=0H(#5Y)ttmoOx zPh?Xm(JDTois`;Pv?Gsl<_*-w_V5a7*PhI7Or8?M=97$k%TtEj*vJ0LaVoclw-DVf zQ{_yF=QvnJtvy`K1UN=f;WkjK9b{KjIG~%^Zq&4o| zHH~qnRTlJ{@Fsyz12jjA^h!%r?ug~r;ToaUM$Yg>+yKV5vzf!Q`O%02AnI=d4M{rPJFCAK#XkK-Ko>4&hfx(*>&X$|$}j)F>@UrU*bu|KK(KK77Vfp08_*~ejQ zlXx#|Jg_rYc!Nx?v#)Z)qe&OMCVzT3wUW*}YKB*Vbawc|6_#U&SbO$y3lkb_ry-3FjP6m3%_@RRPl6^emEJLOLG?ts$0wOtR zQJQU_t)W}1rIA3xcEn6SWl@NO5Mk;Q%M}cw9S7XZ49evgA+b!q>D!b2z51(ay?ZxC4_xfaYP&Bs4JN@wSFDbu_cr*ba*(G=2%N;kjrPGfSayHc$5@*6|Zg<_=- zJ;@0j7P-@_!yt{~5QP5!lt5Xg@S~05rzTxqgNNQGw*;cZyljW$c@O@D=!YC!7v8O`W+FT90 zxG)Pdq*%y;^WGL@!KOkbeP(g0u{*OU_>lu>qna$?pS= z{&=g+Sf!lM_fd<3miwG|?97N!Vv-a|HwlzRqzKw7&e<5DGk7EMQ4#u>bSpqF(d$z< z{}bU*zcT9n#Qq=29PiL-GZMy?==fkaK3(1 z08k5g)Y3hH&0x^1a=8P!s2W^_zsSOgs%~~!7yo{+DBz#|TV;@ETn>tF102VSw!1dX ztN;W10Sf)R-joa$lab6&PkrMmKqm%~_o9_0j;sbz@?ri;il^y8@NKOXy7DnH*fQzl z^4Q$7emMX?xu#d~(yQ9{@HMbibHM*iuU^YBUi$M`EkjW=1826TMx9tnLQz@^Rv9nD zcs2!q^bt@-fUIs)cmEp2w=UxE;K5(VsvL-_M6ofsXgk+LcV1LYyLkO?Vl}OaYC>|O z`P3Hf&5j>Jr_K&XGr>8^RYyf~b5D)%RDP^=(OE@Q!pBvy<^Vj8e$=Ong+bD6F;~cX z-$P+-6GxnM>(LP|P>z<3PDw0%+2}0DoD!GR3;9~!S;)MYsqfq)Q;F)yW%FOj8C9B+ zaCl`hhLI%4N&D#txA9rs*yE0R<08r4K|Rh|>}g#+qvU&)JgG#WbaJ!mEV#mY{z1*; z3kD;|iP%13@qFF;W%{PAiCRzAL?;QgCWZz;K@*)RlJ`p#5EuX%jo1R@YVcXjIi&y2 zt#$}i<{RD%YLbc3{z6wDSF%=PXqr71R7wM`P0FAAbB}UqL;u$r|NpFU;I~b za}n$cPF-foVnPwqD&<{i`-Gxz7N+tR!cGyP@<<^k*$OS%rhmM%>$df#t;@Qu#D&Q? z^4_rT!aV%~>@OF3Q>v)JvhQk^%DX2g5AMJH&69h}ckI1;@146R_uo5F-h2O^yWf0> zt3%FG_6_O-Q%_RAfSC+E{wde7*=?9nIbWl`HOy!dvpU`?JGB7x%?V2KQo8Ut%1Sgz zofdq}C@l)kitQ8B1`_)Ww#s2+a#6HLH)mfEs6HmY8nBvqC!+Aq@*vs>ywCG4BMsa39#d)Gqr`e?12e#kIILXngL)7VDQET5I_m!bqEb*RqQ$Oa+S9#R zawI+)SAD9VB8g@Xv;@2T^XWN`EYGLS^gK^^#2i5pF@AWSIJl?*_f(2+?<3?)j-WHW z__67$^uIkf)f4_6sI1|4kunA;V{K)KUwFM)=&!6}Mm}1(va-Ii;qB?Efy&0pCJs4k zDwk9?^LMavX=Mw4*H&Itxs1Odt9l$lYCS-BVqDQ9D0h3qSNVwycJPjYtZfmOIP zcZzW6yo>ue=9XjQg4SXj$(E=sR?*#X8t(ND&D9>9dgc`yXp^MRx@DU7%*2+2Uk*93kQ@s_vW}-&K`or~~=|5Wwq<kpawiwijK6cOjDnk=`czw$&$lH8fZ*ftL?`36$qbDiLoFbOH z4}GutFj?Z%t3!WKxxr(eT6D`nXUxW0V7^DgVOWMG1!h`0`1(F1V?cLspkk3s1h9*# z?OYBfCYH>vae>ZsOGc~FpzPM%(*;m))b-U_fECuNx3V}_jm@L0EQXD*wdx(k8zzQ{ z=5$8%?pS2REl!l^=|ymeH?tU-5xNqR^@b#Rpc*)T0?{x&dFJ4n!U!TZx)usP4nmLj zVP}#n-Cxxz>b#4hYfVBIU|1&eYR9tkpffYwRT)9PSh_?P6JJ$wi#5vG}dpir>4lc zuYPI7b8{2kmM6k?jGErRN{N7w+n$y7mv(cNC0ZJ>r)%ZrXyqF23SrnLplTlpZ>EE{6zzu-0 z07N=^|DLy}E(w%_T%B=S0aKg)J>@j*=+!s|cWEEK37BLJZYn?G891dMQ;W`jBa9; z8qx}i=p&Iv=r&>|vgi|rg38KIw?h=>N>Gl<9TLO41O+S-?DDZWb@*Xv8+WpBvbJ*v zBOA`&UCyFvX^=ZuJaETf;nv>;*WdLabS^5knS@<^n`&~$gU?X0_h}`Maj)JR=3za( zeyi~DR@M;Me>~B%JTdQnhFT@pl}e!wX!UC@m>+L$CYp;?b1uCV=cmRw|24sp>{sje z74@eMohezvF?q2AK-jLdenkSV@l|Hk&6>r0!;h}@wNV$@ALA@sIMC1&vB7OyL5_#v zy@vul6(8;kT-z*DRhn(pNfI05g4ZYGM6RVfyGdimin9H}yAAzk%oDxa))) z8IGSL3l>}0e#m)2*#${-8=hCBFua8I;Lc#hG{@Y*%EnDg!3=`GU%jMhCRO?R^(}+c zUTVZ8i>&|8^&i4;W!9S~q6Fl}xwQ~}=!J10D$Upi5qkCbO;!7(lK-Z}@QjZ>EZXXC zg%j&Hb!AZg23JoB%1ykYi6v#p9O{!C_)(+exS#4fUw_TZLf&y1+YsMCx#A(QkW)xf zGj_U>^1VNVGEnsIVtNw4JJvN}b0{%of!9#P>-lRyJO2l$wKIY*w5nTspCvyC%k-;t zl8mwAto>i3m>&s7Nm?y+voZ`xpmBffBN{n2MuDDn8-SW*2H+K30N@tcm}yt2$bT6g zHk?(7uN$uUn)QBL(0z_XJ5O>Hz28{{^7jeyB6Z|2{dZ%<%h5SjTe|l-mVm=2b^TPc zn`_&e^}{O4cs<~jJ_K-uTpxwjU)^!&qwYuyyr#OohEFP72%l63#OOA94@S4qdzUrR zN95H+wnG_?oY&RzXH8Mlm>oin~?t0O!h9>ojc3NxxhzKG~#RvN0uW;WBlLHZi&@J!KsIkfyXIrOR zsX;LH3L7XF5ba-4N$aL3{{PPlY&BGt@2t+&=~!-cU8dd%KDt&5thYqHwzgoiIwE<1 zl}ycMiN$HN6=P!=4X8NCzZmPyw(ZY}rX`^B8`JH1~a ztHTJ(Tm>w=DNV)}(8Rg^m9WR4cyoUE=NLYdhk*+);yc3C!j%64)dXNX&;g8H-22rK z3~eZB7xpj?hH>fHWHm@*8(SH2A5ojXphR*($IAR0UEQVRKd4gCqK^b!{Ue(CUQH*^hV2M1){Rvakw71qM(?}pc( zE_Bler&YL7(`tvl`sik z!X&(-FafE3fgd!JJs5LbZaUl=`yd%&Q^RJ(jXeDw%?jHf*Dw>D?RmEF4MWcwZ+-Mv zXwwjmBYec|uF8H~h7p0!9L)h)g>Q3=eItSJf}_0VuOT}lD~_0c8V5L?LQpq7ES?{I z90bVurpU6^dHWNiLY=F(29WA+0<;jQZT`VVsa;X@WQ|iWzZzT0iy38&=Y3 zN?k{muV_k%d`k^T*@0=5p4G4TC(Dmre5-ep#AOI@{v}0#`GwpmC;34o|9slqC*`K{ zb2zHym!t?esXpN!KawK!no9M6_d1H4IReVO!{(FWnac94@jyd&;qH)uE-)>3H=Qaz zk-punxr$tb!^@0E5E3I!h2EDIvniU+Oh(RqMw}X z6p{VtAr-{yLU7mHk<5sYJjB`IgatIF8#(cP!6_y`Y{DHWorZHb065QYp+S)lSPjI`WodXnHT>X8bg{ zxEeuSvvk#MCHX>4V*UyIAoVBUPx{YhHX6Ccgmf5GOT*V+3V2Z1jQ%dq1L{N9t;kvg z!x~fy18TRAMuluW=V*#W8GdxFMYe~p;dj&M5Vp>kP6wxp93_mNOOJWKr|LSUJ7t0MB3nBc7T7)-aTkQA>R>(Z|3%33+DX-4WA`@ z;-ewo#W$hrRY^-OiyCPb71A7}SeJJ-d7S$zxdXFkC5qRSY)-T5zTJ1fd2h40@7{xN z*?sVigM0Ti^M{bkPu$rNcon)c=q{1QxirVgqg2q$JMUr50`7Mf%PcVmzQEN-6r~6d z{nc1jchWefPma?vG86AIc|2Hw(1!6uR=z{MY3I9&C)16t zv)u@x;hQ02$s!FYEc9@{g8U`FM0eo5Jd4LEz0VczBLVT=T`Mm1a%L~sdp(UF?sPSJ z{2IFPNq|Q_^Q^$x{Y>xDW1@5@d4Q5XXJt_%Wf2N%0neh;fM-(j-CrBjd)~Qt9=j$F z`7NiFI8F=T{+i!|&6KiMaOP5Hy7}VQU}>N>WHeX7_tDDJ;{`ku+%PZRqT1P~cdG|P zk+a%+f_&VYQl18$S*uT4pRThvzsqS7)rQ1Ke;d7&F8``u0xEnv7*ro_ z^e(JF(~BR1cT!((ZNtJwyZ;&PZ*uo}_8EJww~#`q1fE`U2Cou6ykBW_Ka;{szJvZ; z1Mdnazc!zGCT+dae*`}aw73sn3YS#|r0Lu+&tXreZzqq>FW0%6vQ8h?Q!tE3B-!wllI!+$ef78O1x?XH`XSi&jATN zVPCSN!3S_`b5XQ2OT=fQYB+;B+L$!Nx$rU)EmozYhIRB2WSlib5=K-BfgXI)L0P0r zU=6@N3i~X@p$qCAGUGEl5MW#=yH=X@LMZr1v3y&}kTdabrKk{_-JWy#% zK;?z#kv~>Ze_@SSYMU99Xgerjak0z_K>5RfFU%iBA)8-bsRkqBo0i9p65C4AX0bD% zcoX0Wq7p~{z*5k~f`*=0#Jo_eCI;0CLG0XlQ1WKqG-2PeN1U9>L5MeBNM+p@nHf4- zo+F-&8`aIR68#PbGHl%}=+GkZ-B&OQ@FS-ArzP}NbRGf=1cRzIMSsM~VqLSquELm{ z_(jfrSU?&(5Lzq|)SI^YoXVFJ7iksCkHxHt6GcP_3G_qa{Nb3j`n@-Fu5g$yYo9*P1-((lDlLnrM;mt|fi|MFF z*(sL9m!M`lN+@;|;vmC`<`m@oDbp3QQ;<90gdjxIRu+tI7-H}&8y<42RR}LM!knwP z39Qm(S{)X6ik8^%!B_DK%qx9l7Aze*U-}cp{I;|pD%YuT9&`&18f6i1_B1?mD{F(% z7>^ng)5@yf6cAsxI`*5UKZ6`07+dqnl5AYe3~IvJLG_7Vd<+`R`!`(GUmI}_up?w+ zjBk~}_BmdE9mkZ|@Z)TOK3wt*NZq~%|G~4PRYEL~r6y6|hxk^vk(fWTk;tDJ9_ryf zV*im@DwxDH^FrGBG&34i>Il3Qi(v3+z<$y9!}jx#nV#y4(X+-DYjE!a0V$i$m_M`2 zTRJ`_F4MMXKG40NS7DOBBwE35qiwAf!$B+hWT&#gIWPvVQ}y@7dM4Y9c)1~Dp9mr2 zRGCXwr49=v!P|qXu4wJBsyeI)fk<$or*UH*0|7Z(*kw5ShG6&pm>q6MD@%$} z;*@{--2UtIja*;-iR!QcyoSp6sKXxPBK2Av`QSxBu{kVChctYUP0OXv0AIQB!I=+F zKIcl*JF+yApl@E&UE1o zZ8%4~tH^p8JLRGKIjf;pmcplEs*G2-P&4AN$qy)nvk-bkrY-S+K$#r1}Z+Pn{`y=$u zFn>SRz_Yx0w|gfVeVInjEu8$$uIDUEYtuy1c=(;<+;FDYD4yN;LW*AJ&$BOIRnOeCYl9AcXKlOo18*Z&*mhWdPC(h`;o}KvcLSwZs}|D zaodoP?bxfE>!X}PSt}@{Rw!U?blaGbDEpaI$Z*fog5p0ai|~`NPcV(}ci%GnlJZ@<50&@az5CE1@04DBhHO$l3>pQG z9w7VXWiSijgKHJ9+3mYkY4*%l{Y$4(S2(;fzlf?#wb?TPIc&$sCSG8J!sK;=_WfVL zGuGGa4Q``N?fs=-j4+;-xZC%>qAOuDPn%7%X#8r2;}oT+nNy)HyCZcPB_VRbjnt=w z&%T!ix=2NGG$wooE()+umMv6)9B@-Mz@~+d6=b-5CKw2C-2iWxqcS9rIm1=LYs59` zkxH2U;kfKPT&441x|j6+AmwkcG9+N2H1MdyjCp1)?P1TdwsmnGs%mjl{hnCkZZZWspl3>NrJn5|MkHU=y{q zwV~R&+Hh@sZNm}eIcMlsA#a9w3S*0y16uI>GKjir8!36?5d?ZLjCPR6;ITY+)CMcP zehna^3N^@0Gye5A86rC-z-H)rPa0FxDCYEmoH@7I386zLZ%*@)$8k`2|a`!C}3R?=RFa2!SnQjPf*uCn>wTFLkD8N$lDJ8)y#>&qZEzvrT4k zOIu{qX4bPZe~~a&7K0`EUc*%z!qOaN&jmw zu)oM^V3jOoLV}Y)QYJ|&l<-dSBSWqe} z^rqU-g8zhC>7QdTMJvT;;qF_mUVDE!mJ36ISU#=qsD#)vIdiY{sSRWOGk*c~x7XiH zOU7@|P7LqFn1i!$Ys8V{ggS?SY^hCB$Ptf&EdoDJ=U^QM#g7!1VqeI@84osVn4C^z z8ku$+CS3i>k~^%M=RFJXiQgd~q;TT{AYRw$J9c>9RyybuSn+CgI(!G$eXj1P6*`C# zI=o^mFP{+^Ym+f~NmSkfOiehfb6!oT1M=T~C&cT(Ht!pXsZSEDv4f7u1!p4YGN0QN zv`GDAkro~Ija=1n{nmjfV!yy8ay$jcR@oMv_Te+aH`(U67uy0BVvJ=Xexy-TblJAZ zLL+H;9~+(0HlxJ0!Eqw`xD<7w;faLO#(1*MV02M6IJUg#^FL!}e)mXO+eRQn=a3rI zQ8A?+9OSIb;)q03EWujv``w&`1tRyDA14_fg$2$p$xWInH3s5ISd7LOl4u(+LRSLB zzqSnHrEUIs&+kfiyxSHQ2*1oGnmB`aPlIDNUz2{O(_vbML%RG3w47DxO0prbVq!4F z22*pvH#OSkSm|ai<8OR13mAW{h`-C^BZ?)<4j*AlA#DCE3Km&gH~W0xP$eN8e;??I ztizU9jvfn3gex4#LIM|r1{_w}M}nylFo0Ya_zomsT27*xgBVDz}z3-XJwb>2u>DT8Qx#8 zN7c9SFp1-;u9xB~R3la$M<8vxvuI1BEhZ&FZK1JkGd!;x+5!U_;bYUBsG zNR?!;1dWk38pKf|*y0zvEy7!-06RqVhum=JAGFFh@`UV`D7K=T@$oYg%0VdEL63(`553NbL2>|F_Ejle#kc@@@KQM@yqQ<@gfM)@3%# za(Sv7kwy9X8?G^mbE?-jx4#FwC$s3D8ZIlt4L9FV-hU6VsqWc3F}e2+FCtVmDZEED z#6O}6pVgHSprW|EA5fxU@P0_iyOg|J32zZVLDm7oLxp{|69lJYoUaw1nE z6%TKV5*c=Q8;lLex?}*$H9P$SB*i_%l4A3b4*XZ6AO13H)RbtN0U$3hNRQ@g{-=XAkC9hFZ zQu1mgBT6n)a)pvKC-wMAJzi4sA^m(#S1%~ps-M>@`EmXHIVJy_ zlGp0qM|5R}n``y+v;1uK?mr>3cVkRA#~i<*T(>L37j^X|C4Zo#sidF>BKp0*($8*P znW>MR>I#|_BT8PYB&#e}>PkFGZ%kKvb#;xd_UY<6UEQIp>$z&?%li}_-JQAYq-AHW zHz~v2O7b*VDTw=L#|Y|6f4eDA0A>}QmGM9GUvKCWav z`I}jsXEqDYsV3~BqVwGP$o$-?X3yc%HRMj-%xUkSYJa`T->GDel3!LCpHgD>A2;gW zbxPi%uxUJd$4>Xe`08=*}Z?MwpX}OxDjm`F|d-dRel7FG(kdjFyZ&os;kSHTmp@E(I`TjS8d zWl0YeTE5-56)W=XEeviV$`!F23VM5J07^%F;a~Bq3x;RE>w}?8Vg2CS1~bKM<|Uj{ zYmEM3;3(8ihImD5c`I8Ov~*M7_4HzI-%cRGfzyVq1Gf%aFJ_>S2BDAqznQ|O!Y0)= zOj&&cR^m(fHH<{}Kw*$b5(C+xt!hmnJ5U_lIJil7`m)2<4jt*El^gox#dNch^kH3^35AThI_eR}4{T|q`xyR4BVaw}uANbb! zf7Md`-fWP;jnXsTSsRwL)_+&MVb2HFh0lia+jx(-PQ&TVU0W{Y-P}NK%O#uh2-upT9>+E0xQRR6dS{Sv9?opo2LzfCw?hMG zhS%GQp|=t1z1ni!Lb>$$KreF;ctjz0`1g1DHCy_qd&GK)ayGBGw@VTm(rV@by}(+e zT;A_v{uSZr^?ftbJ)CF6p}BMC2fvGV*pTht`Jo4Bqt?dxp&O#M=e{<$5})rEd+jiR L=zpC6^zVNFbN$G? literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/_distutils_hack/__init__.py b/venv/lib/python3.8/site-packages/_distutils_hack/__init__.py new file mode 100644 index 000000000..47ce24944 --- /dev/null +++ b/venv/lib/python3.8/site-packages/_distutils_hack/__init__.py @@ -0,0 +1,128 @@ +import sys +import os +import re +import importlib +import warnings + + +is_pypy = '__pypy__' in sys.builtin_module_names + + +warnings.filterwarnings('ignore', + '.+ distutils .+ deprecated', + DeprecationWarning) + + +def warn_distutils_present(): + if 'distutils' not in sys.modules: + return + if is_pypy and sys.version_info < (3, 7): + # PyPy for 3.6 unconditionally imports distutils, so bypass the warning + # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 + return + warnings.warn( + "Distutils was imported before Setuptools, but importing Setuptools " + "also replaces the `distutils` module in `sys.modules`. This may lead " + "to undesirable behaviors or errors. To avoid these issues, avoid " + "using distutils directly, ensure that setuptools is installed in the " + "traditional way (e.g. not an editable install), and/or make sure " + "that setuptools is always imported before distutils.") + + +def clear_distutils(): + if 'distutils' not in sys.modules: + return + warnings.warn("Setuptools is replacing distutils.") + mods = [name for name in sys.modules if re.match(r'distutils\b', name)] + for name in mods: + del sys.modules[name] + + +def enabled(): + """ + Allow selection of distutils by environment variable. + """ + which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib') + return which == 'local' + + +def ensure_local_distutils(): + clear_distutils() + distutils = importlib.import_module('setuptools._distutils') + distutils.__name__ = 'distutils' + sys.modules['distutils'] = distutils + + # sanity check that submodules load as expected + core = importlib.import_module('distutils.core') + assert '_distutils' in core.__file__, core.__file__ + + +def do_override(): + """ + Ensure that the local copy of distutils is preferred over stdlib. + + See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 + for more motivation. + """ + if enabled(): + warn_distutils_present() + ensure_local_distutils() + + +class DistutilsMetaFinder: + def find_spec(self, fullname, path, target=None): + if path is not None: + return + + method_name = 'spec_for_{fullname}'.format(**locals()) + method = getattr(self, method_name, lambda: None) + return method() + + def spec_for_distutils(self): + import importlib.abc + import importlib.util + + class DistutilsLoader(importlib.abc.Loader): + + def create_module(self, spec): + return importlib.import_module('setuptools._distutils') + + def exec_module(self, module): + pass + + return importlib.util.spec_from_loader('distutils', DistutilsLoader()) + + def spec_for_pip(self): + """ + Ensure stdlib distutils when running under pip. + See pypa/pip#8761 for rationale. + """ + if self.pip_imported_during_build(): + return + clear_distutils() + self.spec_for_distutils = lambda: None + + @staticmethod + def pip_imported_during_build(): + """ + Detect if pip is being imported in a build script. Ref #2355. + """ + import traceback + return any( + frame.f_globals['__file__'].endswith('setup.py') + for frame, line in traceback.walk_stack(None) + ) + + +DISTUTILS_FINDER = DistutilsMetaFinder() + + +def add_shim(): + sys.meta_path.insert(0, DISTUTILS_FINDER) + + +def remove_shim(): + try: + sys.meta_path.remove(DISTUTILS_FINDER) + except ValueError: + pass diff --git a/venv/lib/python3.8/site-packages/_distutils_hack/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/_distutils_hack/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6591d43e4aba6fdae505cff32b4110e6573dd8a GIT binary patch literal 5147 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 F`T_8dO&9=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: SQLAlchemy>=1.3.0 +Requires-Dist: Mako +Requires-Dist: importlib-metadata; python_version < "3.9" +Requires-Dist: importlib-resources; python_version < "3.9" +Requires-Dist: typing-extensions>=4 +Provides-Extra: tz +Requires-Dist: backports.zoneinfo; python_version < "3.9" and extra == "tz" +Requires-Dist: tzdata; extra == "tz" + +Alembic is a database migrations tool written by the author +of `SQLAlchemy `_. A migrations tool +offers the following functionality: + +* Can emit ALTER statements to a database in order to change + the structure of tables and other constructs +* Provides a system whereby "migration scripts" may be constructed; + each script indicates a particular series of steps that can "upgrade" a + target database to a new version, and optionally a series of steps that can + "downgrade" similarly, doing the same steps in reverse. +* Allows the scripts to execute in some sequential manner. + +The goals of Alembic are: + +* Very open ended and transparent configuration and operation. A new + Alembic environment is generated from a set of templates which is selected + among a set of options when setup first occurs. The templates then deposit a + series of scripts that define fully how database connectivity is established + and how migration scripts are invoked; the migration scripts themselves are + generated from a template within that series of scripts. The scripts can + then be further customized to define exactly how databases will be + interacted with and what structure new migration files should take. +* Full support for transactional DDL. The default scripts ensure that all + migrations occur within a transaction - for those databases which support + this (Postgresql, Microsoft SQL Server), migrations can be tested with no + need to manually undo changes upon failure. +* Minimalist script construction. Basic operations like renaming + tables/columns, adding/removing columns, changing column attributes can be + performed through one line commands like alter_column(), rename_table(), + add_constraint(). There is no need to recreate full SQLAlchemy Table + structures for simple operations like these - the functions themselves + generate minimalist schema structures behind the scenes to achieve the given + DDL sequence. +* "auto generation" of migrations. While real world migrations are far more + complex than what can be automatically determined, Alembic can still + eliminate the initial grunt work in generating new migration directives + from an altered schema. The ``--autogenerate`` feature will inspect the + current status of a database using SQLAlchemy's schema inspection + capabilities, compare it to the current state of the database model as + specified in Python, and generate a series of "candidate" migrations, + rendering them into a new migration script as Python directives. The + developer then edits the new file, adding additional directives and data + migrations as needed, to produce a finished migration. Table and column + level changes can be detected, with constraints and indexes to follow as + well. +* Full support for migrations generated as SQL scripts. Those of us who + work in corporate environments know that direct access to DDL commands on a + production database is a rare privilege, and DBAs want textual SQL scripts. + Alembic's usage model and commands are oriented towards being able to run a + series of migrations into a textual output file as easily as it runs them + directly to a database. Care must be taken in this mode to not invoke other + operations that rely upon in-memory SELECTs of rows - Alembic tries to + provide helper constructs like bulk_insert() to help with data-oriented + operations that are compatible with script-based DDL. +* Non-linear, dependency-graph versioning. Scripts are given UUID + identifiers similarly to a DVCS, and the linkage of one script to the next + is achieved via human-editable markers within the scripts themselves. + The structure of a set of migration files is considered as a + directed-acyclic graph, meaning any migration file can be dependent + on any other arbitrary set of migration files, or none at + all. Through this open-ended system, migration files can be organized + into branches, multiple roots, and mergepoints, without restriction. + Commands are provided to produce new branches, roots, and merges of + branches automatically. +* Provide a library of ALTER constructs that can be used by any SQLAlchemy + application. The DDL constructs build upon SQLAlchemy's own DDLElement base + and can be used standalone by any application or script. +* At long last, bring SQLite and its inability to ALTER things into the fold, + but in such a way that SQLite's very special workflow needs are accommodated + in an explicit way that makes the most of a bad situation, through the + concept of a "batch" migration, where multiple changes to a table can + be batched together to form a series of instructions for a single, subsequent + "move-and-copy" workflow. You can even use "move-and-copy" workflow for + other databases, if you want to recreate a table in the background + on a busy system. + +Documentation and status of Alembic is at https://alembic.sqlalchemy.org/ + +The SQLAlchemy Project +====================== + +Alembic is part of the `SQLAlchemy Project `_ and +adheres to the same standards and conventions as the core project. + +Development / Bug reporting / Pull requests +___________________________________________ + +Please refer to the +`SQLAlchemy Community Guide `_ for +guidelines on coding and participating in this project. + +Code of Conduct +_______________ + +Above all, SQLAlchemy places great emphasis on polite, thoughtful, and +constructive communication between users and developers. +Please see our current Code of Conduct at +`Code of Conduct `_. + +License +======= + +Alembic is distributed under the `MIT license +`_. diff --git a/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/RECORD b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/RECORD new file mode 100644 index 000000000..0044da8fe --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/RECORD @@ -0,0 +1,151 @@ +../../../bin/alembic,sha256=sfC72GGr_ARPz7j_p4wYnxXfcbN7qLIqdGPcXgMZzwU,270 +alembic-1.14.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +alembic-1.14.1.dist-info/LICENSE,sha256=NeqcNBmyYfrxvkSMT0fZJVKBv2s2tf_qVQUiJ9S6VN4,1059 +alembic-1.14.1.dist-info/METADATA,sha256=D5AHibKWuwgPf9ORQhNm0k_iTmpKrkDNF-MboIrflY8,7420 +alembic-1.14.1.dist-info/RECORD,, +alembic-1.14.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic-1.14.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91 +alembic-1.14.1.dist-info/entry_points.txt,sha256=aykM30soxwGN0pB7etLc1q0cHJbL9dy46RnK9VX4LLw,48 +alembic-1.14.1.dist-info/top_level.txt,sha256=FwKWd5VsPFC8iQjpu1u9Cn-JnK3-V1RhUCmWqz1cl-s,8 +alembic/__init__.py,sha256=cdCZTWAIlk-sWQ6BPu9FEdV7AUyFJpTFU1bwmzWvqTo,63 +alembic/__main__.py,sha256=373m7-TBh72JqrSMYviGrxCHZo-cnweM8AGF8A22PmY,78 +alembic/__pycache__/__init__.cpython-38.pyc,, +alembic/__pycache__/__main__.cpython-38.pyc,, +alembic/__pycache__/command.cpython-38.pyc,, +alembic/__pycache__/config.cpython-38.pyc,, +alembic/__pycache__/context.cpython-38.pyc,, +alembic/__pycache__/environment.cpython-38.pyc,, +alembic/__pycache__/migration.cpython-38.pyc,, +alembic/__pycache__/op.cpython-38.pyc,, +alembic/autogenerate/__init__.py,sha256=ntmUTXhjLm4_zmqIwyVaECdpPDn6_u1yM9vYk6-553E,543 +alembic/autogenerate/__pycache__/__init__.cpython-38.pyc,, +alembic/autogenerate/__pycache__/api.cpython-38.pyc,, +alembic/autogenerate/__pycache__/compare.cpython-38.pyc,, +alembic/autogenerate/__pycache__/render.cpython-38.pyc,, +alembic/autogenerate/__pycache__/rewriter.cpython-38.pyc,, +alembic/autogenerate/api.py,sha256=L4qkapSJO1Ypymx8HsjLl0vFFt202agwMYsQbIe6ZtI,22219 +alembic/autogenerate/compare.py,sha256=cdUBH6qsedaJsnToSOu4MfcJaI4bjUJ4VWqtBlqsSr8,44944 +alembic/autogenerate/render.py,sha256=JUjXpAmxhO-WPJGgvs0V_xtEC0Tpv0AngDepVfPIWUc,35482 +alembic/autogenerate/rewriter.py,sha256=uZWRkTYJoncoEJ5WY1QBRiozjyChqZDJPy4LtcRibjM,7846 +alembic/command.py,sha256=2tkKrIoEgPfXrGgvMRGrUXH4l-7z466DOxd7Q2XOfL8,22169 +alembic/config.py,sha256=BZ7mwFRk2gq8GFNxxy9qvMUFx43YbDbQTC99OnjqiKY,22216 +alembic/context.py,sha256=hK1AJOQXJ29Bhn276GYcosxeG7pC5aZRT5E8c4bMJ4Q,195 +alembic/context.pyi,sha256=hUHbSnbSeEEMVkk0gDSXOq4_9edSjYzsjmmf-mL9Iao,31737 +alembic/ddl/__init__.py,sha256=Df8fy4Vn_abP8B7q3x8gyFwEwnLw6hs2Ljt_bV3EZWE,152 +alembic/ddl/__pycache__/__init__.cpython-38.pyc,, +alembic/ddl/__pycache__/_autogen.cpython-38.pyc,, +alembic/ddl/__pycache__/base.cpython-38.pyc,, +alembic/ddl/__pycache__/impl.cpython-38.pyc,, +alembic/ddl/__pycache__/mssql.cpython-38.pyc,, +alembic/ddl/__pycache__/mysql.cpython-38.pyc,, +alembic/ddl/__pycache__/oracle.cpython-38.pyc,, +alembic/ddl/__pycache__/postgresql.cpython-38.pyc,, +alembic/ddl/__pycache__/sqlite.cpython-38.pyc,, +alembic/ddl/_autogen.py,sha256=Blv2RrHNyF4cE6znCQXNXG5T9aO-YmiwD4Fz-qfoaWA,9275 +alembic/ddl/base.py,sha256=gazpvtk_6XURcsa0libwcaIquL5HwJDP1ZWKJ6P7x0I,9788 +alembic/ddl/impl.py,sha256=7-oxMb7KeycaK96x-kXw4mR6NSE1tmN0UEZIZrPcuhY,30195 +alembic/ddl/mssql.py,sha256=ydvgBSaftKYjaBaMyqius66Ta4CICQSj79Og3Ed2atY,14219 +alembic/ddl/mysql.py,sha256=kXOGYmpnL_9WL3ijXNsG4aAwy3m1HWJOoLZSePzmJF0,17316 +alembic/ddl/oracle.py,sha256=669YlkcZihlXFbnXhH2krdrvDry8q5pcUGfoqkg_R6Y,6243 +alembic/ddl/postgresql.py,sha256=OR9ufFDr-uvzIBk38OBsLlfr_PXG9qBSDYIj0-oNnwc,29921 +alembic/ddl/sqlite.py,sha256=yR1ov010h3HIpJvZbCLTkR7HIkKQMfEAGJE7ZnLwmSU,7980 +alembic/environment.py,sha256=MM5lPayGT04H3aeng1H7GQ8HEAs3VGX5yy6mDLCPLT4,43 +alembic/migration.py,sha256=MV6Fju6rZtn2fTREKzXrCZM6aIBGII4OMZFix0X-GLs,41 +alembic/op.py,sha256=flHtcsVqOD-ZgZKK2pv-CJ5Cwh-KJ7puMUNXzishxLw,167 +alembic/op.pyi,sha256=QZ1ERetxIrpZNTyg48Btn5OJhhpMId-_MLMP36RauOw,50168 +alembic/operations/__init__.py,sha256=e0KQSZAgLpTWvyvreB7DWg7RJV_MWSOPVDgCqsd2FzY,318 +alembic/operations/__pycache__/__init__.cpython-38.pyc,, +alembic/operations/__pycache__/base.cpython-38.pyc,, +alembic/operations/__pycache__/batch.cpython-38.pyc,, +alembic/operations/__pycache__/ops.cpython-38.pyc,, +alembic/operations/__pycache__/schemaobj.cpython-38.pyc,, +alembic/operations/__pycache__/toimpl.cpython-38.pyc,, +alembic/operations/base.py,sha256=JRaOtPqyqfaPjzGHxuP9VMcO1KsJNmbbLOvwG82qxGA,74474 +alembic/operations/batch.py,sha256=YqtD4hJ3_RkFxvI7zbmBwxcLEyLHYyWQpsz4l5L85yI,26943 +alembic/operations/ops.py,sha256=guIpLQzlqgkdP2LGDW8vWg_DXeAouEldiVZDgRas7YI,94953 +alembic/operations/schemaobj.py,sha256=Wp-bBe4a8lXPTvIHJttBY0ejtpVR5Jvtb2kI-U2PztQ,9468 +alembic/operations/toimpl.py,sha256=Fx-UKcq6S8pVtsEwPFjTKtEcAVKjfptn-BfpE1k3_ck,7517 +alembic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic/runtime/__pycache__/__init__.cpython-38.pyc,, +alembic/runtime/__pycache__/environment.cpython-38.pyc,, +alembic/runtime/__pycache__/migration.cpython-38.pyc,, +alembic/runtime/environment.py,sha256=SkYB_am1h3FSG8IsExAQxGP_7WwzOVigqjlO747Aokc,41497 +alembic/runtime/migration.py,sha256=9GZ_bYZ6yMF7DUD1hgZdmB0YqvcdcNBBfxFaXKHeQoM,49857 +alembic/script/__init__.py,sha256=lSj06O391Iy5avWAiq8SPs6N8RBgxkSPjP8wpXcNDGg,100 +alembic/script/__pycache__/__init__.cpython-38.pyc,, +alembic/script/__pycache__/base.cpython-38.pyc,, +alembic/script/__pycache__/revision.cpython-38.pyc,, +alembic/script/__pycache__/write_hooks.cpython-38.pyc,, +alembic/script/base.py,sha256=XLNpdsLnBBSz4ZKMFUArFUdtL1HcjtuUDHNbA-5VlZA,37809 +alembic/script/revision.py,sha256=NTu-eu5Y78u4NoVXpT0alpD2oL40SGATA2sEMEf1el4,62306 +alembic/script/write_hooks.py,sha256=NGB6NGgfdf7HK6XNNpSKqUCfzxazj-NRUePgFx7MJSM,5036 +alembic/templates/async/README,sha256=ISVtAOvqvKk_5ThM5ioJE-lMkvf9IbknFUFVU_vPma4,58 +alembic/templates/async/__pycache__/env.cpython-38.pyc,, +alembic/templates/async/alembic.ini.mako,sha256=lYN5fP_fIX3FWtTcOy86Xznr6P5r1f0rlkdDhxLhVN4,3658 +alembic/templates/async/env.py,sha256=zbOCf3Y7w2lg92hxSwmG1MM_7y56i_oRH4AKp0pQBYo,2389 +alembic/templates/async/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635 +alembic/templates/generic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38 +alembic/templates/generic/__pycache__/env.cpython-38.pyc,, +alembic/templates/generic/alembic.ini.mako,sha256=TPnqGqwg9QG8uCvBY5jaz32U2pqVqLK1dG1e8i2yDYM,3766 +alembic/templates/generic/env.py,sha256=TLRWOVW3Xpt_Tpf8JFzlnoPn_qoUu8UV77Y4o9XD6yI,2103 +alembic/templates/generic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635 +alembic/templates/multidb/README,sha256=dWLDhnBgphA4Nzb7sNlMfCS3_06YqVbHhz-9O5JNqyI,606 +alembic/templates/multidb/__pycache__/env.cpython-38.pyc,, +alembic/templates/multidb/alembic.ini.mako,sha256=cVrikS0KRB46GVcjSO8xKYm7rqUngqTnd06pCozm1Pw,3860 +alembic/templates/multidb/env.py,sha256=6zNjnW8mXGUk7erTsAvrfhvqoczJ-gagjVq1Ypg2YIQ,4230 +alembic/templates/multidb/script.py.mako,sha256=N06nMtNSwHkgl0EBXDyMt8njp9tlOesR583gfq21nbY,1090 +alembic/testing/__init__.py,sha256=kOxOh5nwmui9d-_CCq9WA4Udwy7ITjm453w74CTLZDo,1159 +alembic/testing/__pycache__/__init__.cpython-38.pyc,, +alembic/testing/__pycache__/assertions.cpython-38.pyc,, +alembic/testing/__pycache__/env.cpython-38.pyc,, +alembic/testing/__pycache__/fixtures.cpython-38.pyc,, +alembic/testing/__pycache__/requirements.cpython-38.pyc,, +alembic/testing/__pycache__/schemacompare.cpython-38.pyc,, +alembic/testing/__pycache__/util.cpython-38.pyc,, +alembic/testing/__pycache__/warnings.cpython-38.pyc,, +alembic/testing/assertions.py,sha256=ScUb1sVopIl70BirfHUJDvwswC70Q93CiIWwkiZbhHg,5207 +alembic/testing/env.py,sha256=giHWVLhHkfNWrPEfrAqhpMOLL6FgWoBCVAzBVrVbSSA,10766 +alembic/testing/fixtures.py,sha256=nBntOynOmVCFc7IYiN3DIQ3TBNTfiGCvL_1-FyCry8o,9462 +alembic/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic/testing/plugin/__pycache__/__init__.cpython-38.pyc,, +alembic/testing/plugin/__pycache__/bootstrap.cpython-38.pyc,, +alembic/testing/plugin/bootstrap.py,sha256=9C6wtjGrIVztZ928w27hsQE0KcjDLIUtUN3dvZKsMVk,50 +alembic/testing/requirements.py,sha256=dKeAO1l5TwBqXarJN-IPORlCqCJv-41Dj6oXoEikxHQ,5133 +alembic/testing/schemacompare.py,sha256=N5UqSNCOJetIKC4vKhpYzQEpj08XkdgIoqBmEPQ3tlc,4838 +alembic/testing/suite/__init__.py,sha256=MvE7-hwbaVN1q3NM-ztGxORU9dnIelUCINKqNxewn7Y,288 +alembic/testing/suite/__pycache__/__init__.cpython-38.pyc,, +alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-38.pyc,, +alembic/testing/suite/__pycache__/test_autogen_comments.cpython-38.pyc,, +alembic/testing/suite/__pycache__/test_autogen_computed.cpython-38.pyc,, +alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-38.pyc,, +alembic/testing/suite/__pycache__/test_autogen_fks.cpython-38.pyc,, +alembic/testing/suite/__pycache__/test_autogen_identity.cpython-38.pyc,, +alembic/testing/suite/__pycache__/test_environment.cpython-38.pyc,, +alembic/testing/suite/__pycache__/test_op.cpython-38.pyc,, +alembic/testing/suite/_autogen_fixtures.py,sha256=cDq1pmzHe15S6dZPGNC6sqFaCQ3hLT_oPV2IDigUGQ0,9880 +alembic/testing/suite/test_autogen_comments.py,sha256=aEGqKUDw4kHjnDk298aoGcQvXJWmZXcIX_2FxH4cJK8,6283 +alembic/testing/suite/test_autogen_computed.py,sha256=CXAeF-5Wr2cmW8PB7ztHG_4ZQsn1gSWrHWfxi72grNU,6147 +alembic/testing/suite/test_autogen_diffs.py,sha256=T4SR1n_kmcOKYhR4W1-dA0e5sddJ69DSVL2HW96kAkE,8394 +alembic/testing/suite/test_autogen_fks.py,sha256=AqFmb26Buex167HYa9dZWOk8x-JlB1OK3bwcvvjDFaU,32927 +alembic/testing/suite/test_autogen_identity.py,sha256=kcuqngG7qXAKPJDX4U8sRzPKHEJECHuZ0DtuaS6tVkk,5824 +alembic/testing/suite/test_environment.py,sha256=OwD-kpESdLoc4byBrGrXbZHvqtPbzhFCG4W9hJOJXPQ,11877 +alembic/testing/suite/test_op.py,sha256=2XQCdm_NmnPxHGuGj7hmxMzIhKxXNotUsKdACXzE1mM,1343 +alembic/testing/util.py,sha256=CQrcQDA8fs_7ME85z5ydb-Bt70soIIID-qNY1vbR2dg,3350 +alembic/testing/warnings.py,sha256=RxA7x_8GseANgw07Us8JN_1iGbANxaw6_VitX2ZGQH4,1078 +alembic/util/__init__.py,sha256=KSZ7UT2YzH6CietgUtljVoE3QnGjoFKOi7RL5sgUxrk,1688 +alembic/util/__pycache__/__init__.cpython-38.pyc,, +alembic/util/__pycache__/compat.cpython-38.pyc,, +alembic/util/__pycache__/editor.cpython-38.pyc,, +alembic/util/__pycache__/exc.cpython-38.pyc,, +alembic/util/__pycache__/langhelpers.cpython-38.pyc,, +alembic/util/__pycache__/messaging.cpython-38.pyc,, +alembic/util/__pycache__/pyfiles.cpython-38.pyc,, +alembic/util/__pycache__/sqla_compat.cpython-38.pyc,, +alembic/util/compat.py,sha256=eoR9ReCTV_l0xGgGlr_OJmVvJecttBYXRKfDhoK8zKU,2630 +alembic/util/editor.py,sha256=JIz6_BdgV8_oKtnheR6DZoB7qnrHrlRgWjx09AsTsUw,2546 +alembic/util/exc.py,sha256=KQTru4zcgAmN4IxLMwLFS56XToUewaXB7oOLcPNjPwg,98 +alembic/util/langhelpers.py,sha256=LpOcovnhMnP45kTt8zNJ4BHpyQrlF40OL6yDXjqKtsE,10026 +alembic/util/messaging.py,sha256=BxAHiJsYHBPb2m8zv4yaueSRAlVuYXWkRCeN02JXhqw,3250 +alembic/util/pyfiles.py,sha256=zltVdcwEJJCPS2gHsQvkHkQakuF6wXiZ6zfwHbGNT0g,3489 +alembic/util/sqla_compat.py,sha256=XMfZaLdbVbJoniNUyI3RUUXu4gCWljjVBbJ7db6vCgc,19526 diff --git a/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/REQUESTED b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/WHEEL b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/WHEEL new file mode 100644 index 000000000..505164bc0 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.8.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/entry_points.txt b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/entry_points.txt new file mode 100644 index 000000000..594526817 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +alembic = alembic.config:main diff --git a/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/top_level.txt b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/top_level.txt new file mode 100644 index 000000000..b5bd98d32 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic-1.14.1.dist-info/top_level.txt @@ -0,0 +1 @@ +alembic diff --git a/venv/lib/python3.8/site-packages/alembic/__init__.py b/venv/lib/python3.8/site-packages/alembic/__init__.py new file mode 100644 index 000000000..243d3a8d3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/__init__.py @@ -0,0 +1,4 @@ +from . import context +from . import op + +__version__ = "1.14.1" diff --git a/venv/lib/python3.8/site-packages/alembic/__main__.py b/venv/lib/python3.8/site-packages/alembic/__main__.py new file mode 100644 index 000000000..af1b8e870 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/__main__.py @@ -0,0 +1,4 @@ +from .config import main + +if __name__ == "__main__": + main(prog="alembic") diff --git a/venv/lib/python3.8/site-packages/alembic/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30192034281403498b57c610cc19707d8ebc2271 GIT binary patch literal 284 zcmYjLyH3O~5VW0eAh?tG1lknIDmn@Z1Qe8r2GLw&<*{(q`6b&#-2D;%!B?cE;uok` zOB9T>voo5}YM)oDQ${%d1a(XME1UliWbSC<1<}Yxu9@K*&a)Pz#YT`WHpN&zvFhlX zQ?26j@!)L2_e4@Se^|6ld)c&)RXMYePlkeMK9h375S6o1ez35}L8c!ATW1jPfIEd6 z?r}oxyg{4bq3a{~ecC!(`*r;i5POAn3f-{702A5?wCevV>*flgN?3b}yH||RX*9hW bKuTp*lCtsp`Q+NT@lE4R{yvsOOTHApO`u7$ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/__pycache__/__main__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6219bc56e7532f732f554414bb17cdc1ca24338 GIT binary patch literal 294 zcmYjMJ5Iwu5Z$#+up%OH0tz}8&J<}NYp*MS;Q*&V@E36%j;Xw{UcDH+oxd0gqKmBLKL>tLqyPW_ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/__pycache__/command.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/__pycache__/command.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7381b402dce9e17d0819f97346b05ba7f66b59fe GIT binary patch literal 18135 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 Q-!^{x_#I)t6JvM&FFN`O6951J literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/__pycache__/config.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/__pycache__/config.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31e237113bf2838b94c1f01c7de76e7c008db530 GIT binary patch literal 17677 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/__pycache__/context.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/__pycache__/context.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fc737990c121a642b35489b50f5bfe2866d87fd GIT binary patch literal 323 zcmYk1zfQw25XS8^1%#-CU}9sJ zOuPaU4n=~K?mnIFJKgUdCX;;<tD;XIAdN0{FfkA@FccHL#BA3sb#PwHjtkx&-GBHMrbfn>8C~yT zU_n(=-9>kQCCP_i46dX;x!zmzjt-kSv+lE0qLNx{MAcb~wEK|UO65(CR;k<$!`Vh< zki#}AIBQ_zj|g4P{L9nR(HRiFaZ=E3}W*5oho=xpZ4***ZJLKZ`@J0VcR rD@m*J6&dFfs#Qehd0ZS&Q=wr>wWGJwxxd_cQ{$?$#;kqxmnr@L0NXVVlZ2omJKl zLI!4**&Uc&k48f!7@vs?nV)y_A3ZS(Vf!G`s7710Qcdqw75%H0RiQ8X@ukpjlXUQz z9V*A$WHJZ;p;BKGKXUW53l4-6_6$o)bg&^h_)O2HZYdt~F8DG|%l8D-j4~&)*&|U0 q-;w3|H!`iBsO5|@%GqqulCf|#Z|JYMipR@f=0bC@nBSlL5~^>>a6u3N literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/__pycache__/op.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/__pycache__/op.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e13052eba3cd7562215dc8cb0388d48d7c0e8e1c GIT binary patch literal 306 zcmYjMF;2uV5VUhSKqpSn($S_!TwFte5CtWI0@0+2Y-h+>=fsw6B0_r~feP^tUXhlH zFHqqCLX5OC8ttq!d%IjNNW|eAQ!i29C-Wa^H0N>raip5mRFjsLloHUV1hXwcx+TvE zC3SK`ve^fXv5bz3YYWGNGM&H5=H-F;vxVRuE{U=7NILL9m3jlxn1RV4iE}?a9nQS sSdXq}T8ZDtsyJa@1>`oac>~Y5Mk|zL#uj!M_fCJRolRf)sq=pN1-`vk&;S4c literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/__init__.py b/venv/lib/python3.8/site-packages/alembic/autogenerate/__init__.py new file mode 100644 index 000000000..445ddb251 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/autogenerate/__init__.py @@ -0,0 +1,10 @@ +from .api import _render_migration_diffs as _render_migration_diffs +from .api import compare_metadata as compare_metadata +from .api import produce_migrations as produce_migrations +from .api import render_python_code as render_python_code +from .api import RevisionContext as RevisionContext +from .compare import _produce_net_changes as _produce_net_changes +from .compare import comparators as comparators +from .render import render_op_text as render_op_text +from .render import renderers as renderers +from .rewriter import Rewriter as Rewriter diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49391afc17128d5a26f05c9bc105687d9328b317 GIT binary patch literal 627 zcmZ9JyKdVs6ow_swq(mr0;533&K?{NSvmwkfp!csB*?;rprvD*2yZxIoD2FweUCnc z*GzeZPCcX*pePCb`1l|47ccko`81<;_M53MgDiWCgMS-&**E&SpCoP8<~lPu&-2Iw z;sGBdE{F?WBpwnE`7rT_c*IAE$9il^UM4PyD_$k8^u$d0H1UME=5^w!o|!qH6W4dy za`v9n7MA(pvp}#dpfGBSGAL(7t2UdCN@tBTULptsLAEkT@)jOl``D28F+I*)^cnso z>?ojdEs%S93wza3;&*2QJO{EqiI4Rbf@pTqZXpKL$6v|dFb+@pJm*EfaB_4Ym8)BL zLKOgSmM4codWG>~v4}E^GKw-LIh6ewu#8p}t?@AmC(()T#VhRdu%_X!3{PVz%AZ%Z za|Rf^!h-_Me!w2I^9F2S*K*yl_6`!hGkj(P``(F)LtdIZz literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/api.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/api.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df913374f33394d9480872540f64d524a4b52195 GIT binary patch literal 16841 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/compare.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/compare.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5bccdb48eb5b67ca822c268ff645652efa2b3373 GIT binary patch literal 27097 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 TpD4PElGvj=V literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/render.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/autogenerate/__pycache__/render.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a27a8c13252f9e756cda0fee9cde8c47b8a72b45 GIT binary patch literal 27365 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 U-N--cYsJx#A*`VQdTJE@4b`Gt$N&HU literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/api.py b/venv/lib/python3.8/site-packages/alembic/autogenerate/api.py new file mode 100644 index 000000000..811462e82 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/autogenerate/api.py @@ -0,0 +1,650 @@ +from __future__ import annotations + +import contextlib +from typing import Any +from typing import Dict +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import inspect + +from . import compare +from . import render +from .. import util +from ..operations import ops +from ..util import sqla_compat + +"""Provide the 'autogenerate' feature which can produce migration operations +automatically.""" + +if TYPE_CHECKING: + from sqlalchemy.engine import Connection + from sqlalchemy.engine import Dialect + from sqlalchemy.engine import Inspector + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.schema import Table + + from ..config import Config + from ..operations.ops import DowngradeOps + from ..operations.ops import MigrationScript + from ..operations.ops import UpgradeOps + from ..runtime.environment import NameFilterParentNames + from ..runtime.environment import NameFilterType + from ..runtime.environment import ProcessRevisionDirectiveFn + from ..runtime.environment import RenderItemFn + from ..runtime.migration import MigrationContext + from ..script.base import Script + from ..script.base import ScriptDirectory + from ..script.revision import _GetRevArg + + +def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any: + """Compare a database schema to that given in a + :class:`~sqlalchemy.schema.MetaData` instance. + + The database connection is presented in the context + of a :class:`.MigrationContext` object, which + provides database connectivity as well as optional + comparison functions to use for datatypes and + server defaults - see the "autogenerate" arguments + at :meth:`.EnvironmentContext.configure` + for details on these. + + The return format is a list of "diff" directives, + each representing individual differences:: + + from alembic.migration import MigrationContext + from alembic.autogenerate import compare_metadata + from sqlalchemy import ( + create_engine, + MetaData, + Column, + Integer, + String, + Table, + text, + ) + import pprint + + engine = create_engine("sqlite://") + + with engine.begin() as conn: + conn.execute( + text( + ''' + create table foo ( + id integer not null primary key, + old_data varchar, + x integer + ) + ''' + ) + ) + conn.execute(text("create table bar (data varchar)")) + + metadata = MetaData() + Table( + "foo", + metadata, + Column("id", Integer, primary_key=True), + Column("data", Integer), + Column("x", Integer, nullable=False), + ) + Table("bat", metadata, Column("info", String)) + + mc = MigrationContext.configure(engine.connect()) + + diff = compare_metadata(mc, metadata) + pprint.pprint(diff, indent=2, width=20) + + Output:: + + [ + ( + "add_table", + Table( + "bat", + MetaData(), + Column("info", String(), table=), + schema=None, + ), + ), + ( + "remove_table", + Table( + "bar", + MetaData(), + Column("data", VARCHAR(), table=), + schema=None, + ), + ), + ( + "add_column", + None, + "foo", + Column("data", Integer(), table=), + ), + [ + ( + "modify_nullable", + None, + "foo", + "x", + { + "existing_comment": None, + "existing_server_default": False, + "existing_type": INTEGER(), + }, + True, + False, + ) + ], + ( + "remove_column", + None, + "foo", + Column("old_data", VARCHAR(), table=), + ), + ] + + :param context: a :class:`.MigrationContext` + instance. + :param metadata: a :class:`~sqlalchemy.schema.MetaData` + instance. + + .. seealso:: + + :func:`.produce_migrations` - produces a :class:`.MigrationScript` + structure based on metadata comparison. + + """ + + migration_script = produce_migrations(context, metadata) + assert migration_script.upgrade_ops is not None + return migration_script.upgrade_ops.as_diffs() + + +def produce_migrations( + context: MigrationContext, metadata: MetaData +) -> MigrationScript: + """Produce a :class:`.MigrationScript` structure based on schema + comparison. + + This function does essentially what :func:`.compare_metadata` does, + but then runs the resulting list of diffs to produce the full + :class:`.MigrationScript` object. For an example of what this looks like, + see the example in :ref:`customizing_revision`. + + .. seealso:: + + :func:`.compare_metadata` - returns more fundamental "diff" + data from comparing a schema. + + """ + + autogen_context = AutogenContext(context, metadata=metadata) + + migration_script = ops.MigrationScript( + rev_id=None, + upgrade_ops=ops.UpgradeOps([]), + downgrade_ops=ops.DowngradeOps([]), + ) + + compare._populate_migration_script(autogen_context, migration_script) + + return migration_script + + +def render_python_code( + up_or_down_op: Union[UpgradeOps, DowngradeOps], + sqlalchemy_module_prefix: str = "sa.", + alembic_module_prefix: str = "op.", + render_as_batch: bool = False, + imports: Sequence[str] = (), + render_item: Optional[RenderItemFn] = None, + migration_context: Optional[MigrationContext] = None, + user_module_prefix: Optional[str] = None, +) -> str: + """Render Python code given an :class:`.UpgradeOps` or + :class:`.DowngradeOps` object. + + This is a convenience function that can be used to test the + autogenerate output of a user-defined :class:`.MigrationScript` structure. + + :param up_or_down_op: :class:`.UpgradeOps` or :class:`.DowngradeOps` object + :param sqlalchemy_module_prefix: module prefix for SQLAlchemy objects + :param alembic_module_prefix: module prefix for Alembic constructs + :param render_as_batch: use "batch operations" style for rendering + :param imports: sequence of import symbols to add + :param render_item: callable to render items + :param migration_context: optional :class:`.MigrationContext` + :param user_module_prefix: optional string prefix for user-defined types + + .. versionadded:: 1.11.0 + + """ + opts = { + "sqlalchemy_module_prefix": sqlalchemy_module_prefix, + "alembic_module_prefix": alembic_module_prefix, + "render_item": render_item, + "render_as_batch": render_as_batch, + "user_module_prefix": user_module_prefix, + } + + if migration_context is None: + from ..runtime.migration import MigrationContext + from sqlalchemy.engine.default import DefaultDialect + + migration_context = MigrationContext.configure( + dialect=DefaultDialect() + ) + + autogen_context = AutogenContext(migration_context, opts=opts) + autogen_context.imports = set(imports) + return render._indent( + render._render_cmd_body(up_or_down_op, autogen_context) + ) + + +def _render_migration_diffs( + context: MigrationContext, template_args: Dict[Any, Any] +) -> None: + """legacy, used by test_autogen_composition at the moment""" + + autogen_context = AutogenContext(context) + + upgrade_ops = ops.UpgradeOps([]) + compare._produce_net_changes(autogen_context, upgrade_ops) + + migration_script = ops.MigrationScript( + rev_id=None, + upgrade_ops=upgrade_ops, + downgrade_ops=upgrade_ops.reverse(), + ) + + render._render_python_into_templatevars( + autogen_context, migration_script, template_args + ) + + +class AutogenContext: + """Maintains configuration and state that's specific to an + autogenerate operation.""" + + metadata: Union[MetaData, Sequence[MetaData], None] = None + """The :class:`~sqlalchemy.schema.MetaData` object + representing the destination. + + This object is the one that is passed within ``env.py`` + to the :paramref:`.EnvironmentContext.configure.target_metadata` + parameter. It represents the structure of :class:`.Table` and other + objects as stated in the current database model, and represents the + destination structure for the database being examined. + + While the :class:`~sqlalchemy.schema.MetaData` object is primarily + known as a collection of :class:`~sqlalchemy.schema.Table` objects, + it also has an :attr:`~sqlalchemy.schema.MetaData.info` dictionary + that may be used by end-user schemes to store additional schema-level + objects that are to be compared in custom autogeneration schemes. + + """ + + connection: Optional[Connection] = None + """The :class:`~sqlalchemy.engine.base.Connection` object currently + connected to the database backend being compared. + + This is obtained from the :attr:`.MigrationContext.bind` and is + ultimately set up in the ``env.py`` script. + + """ + + dialect: Optional[Dialect] = None + """The :class:`~sqlalchemy.engine.Dialect` object currently in use. + + This is normally obtained from the + :attr:`~sqlalchemy.engine.base.Connection.dialect` attribute. + + """ + + imports: Set[str] = None # type: ignore[assignment] + """A ``set()`` which contains string Python import directives. + + The directives are to be rendered into the ``${imports}`` section + of a script template. The set is normally empty and can be modified + within hooks such as the + :paramref:`.EnvironmentContext.configure.render_item` hook. + + .. seealso:: + + :ref:`autogen_render_types` + + """ + + migration_context: MigrationContext = None # type: ignore[assignment] + """The :class:`.MigrationContext` established by the ``env.py`` script.""" + + def __init__( + self, + migration_context: MigrationContext, + metadata: Union[MetaData, Sequence[MetaData], None] = None, + opts: Optional[Dict[str, Any]] = None, + autogenerate: bool = True, + ) -> None: + if ( + autogenerate + and migration_context is not None + and migration_context.as_sql + ): + raise util.CommandError( + "autogenerate can't use as_sql=True as it prevents querying " + "the database for schema information" + ) + + if opts is None: + opts = migration_context.opts + + self.metadata = metadata = ( + opts.get("target_metadata", None) if metadata is None else metadata + ) + + if ( + autogenerate + and metadata is None + and migration_context is not None + and migration_context.script is not None + ): + raise util.CommandError( + "Can't proceed with --autogenerate option; environment " + "script %s does not provide " + "a MetaData object or sequence of objects to the context." + % (migration_context.script.env_py_location) + ) + + include_object = opts.get("include_object", None) + include_name = opts.get("include_name", None) + + object_filters = [] + name_filters = [] + if include_object: + object_filters.append(include_object) + if include_name: + name_filters.append(include_name) + + self._object_filters = object_filters + self._name_filters = name_filters + + self.migration_context = migration_context + if self.migration_context is not None: + self.connection = self.migration_context.bind + self.dialect = self.migration_context.dialect + + self.imports = set() + self.opts: Dict[str, Any] = opts + self._has_batch: bool = False + + @util.memoized_property + def inspector(self) -> Inspector: + if self.connection is None: + raise TypeError( + "can't return inspector as this " + "AutogenContext has no database connection" + ) + return inspect(self.connection) + + @contextlib.contextmanager + def _within_batch(self) -> Iterator[None]: + self._has_batch = True + yield + self._has_batch = False + + def run_name_filters( + self, + name: Optional[str], + type_: NameFilterType, + parent_names: NameFilterParentNames, + ) -> bool: + """Run the context's name filters and return True if the targets + should be part of the autogenerate operation. + + This method should be run for every kind of name encountered within the + reflection side of an autogenerate operation, giving the environment + the chance to filter what names should be reflected as database + objects. The filters here are produced directly via the + :paramref:`.EnvironmentContext.configure.include_name` parameter. + + """ + if "schema_name" in parent_names: + if type_ == "table": + table_name = name + else: + table_name = parent_names.get("table_name", None) + if table_name: + schema_name = parent_names["schema_name"] + if schema_name: + parent_names["schema_qualified_table_name"] = "%s.%s" % ( + schema_name, + table_name, + ) + else: + parent_names["schema_qualified_table_name"] = table_name + + for fn in self._name_filters: + if not fn(name, type_, parent_names): + return False + else: + return True + + def run_object_filters( + self, + object_: SchemaItem, + name: sqla_compat._ConstraintName, + type_: NameFilterType, + reflected: bool, + compare_to: Optional[SchemaItem], + ) -> bool: + """Run the context's object filters and return True if the targets + should be part of the autogenerate operation. + + This method should be run for every kind of object encountered within + an autogenerate operation, giving the environment the chance + to filter what objects should be included in the comparison. + The filters here are produced directly via the + :paramref:`.EnvironmentContext.configure.include_object` parameter. + + """ + for fn in self._object_filters: + if not fn(object_, name, type_, reflected, compare_to): + return False + else: + return True + + run_filters = run_object_filters + + @util.memoized_property + def sorted_tables(self) -> List[Table]: + """Return an aggregate of the :attr:`.MetaData.sorted_tables` + collection(s). + + For a sequence of :class:`.MetaData` objects, this + concatenates the :attr:`.MetaData.sorted_tables` collection + for each individual :class:`.MetaData` in the order of the + sequence. It does **not** collate the sorted tables collections. + + """ + result = [] + for m in util.to_list(self.metadata): + result.extend(m.sorted_tables) + return result + + @util.memoized_property + def table_key_to_table(self) -> Dict[str, Table]: + """Return an aggregate of the :attr:`.MetaData.tables` dictionaries. + + The :attr:`.MetaData.tables` collection is a dictionary of table key + to :class:`.Table`; this method aggregates the dictionary across + multiple :class:`.MetaData` objects into one dictionary. + + Duplicate table keys are **not** supported; if two :class:`.MetaData` + objects contain the same table key, an exception is raised. + + """ + result: Dict[str, Table] = {} + for m in util.to_list(self.metadata): + intersect = set(result).intersection(set(m.tables)) + if intersect: + raise ValueError( + "Duplicate table keys across multiple " + "MetaData objects: %s" + % (", ".join('"%s"' % key for key in sorted(intersect))) + ) + + result.update(m.tables) + return result + + +class RevisionContext: + """Maintains configuration and state that's specific to a revision + file generation operation.""" + + generated_revisions: List[MigrationScript] + process_revision_directives: Optional[ProcessRevisionDirectiveFn] + + def __init__( + self, + config: Config, + script_directory: ScriptDirectory, + command_args: Dict[str, Any], + process_revision_directives: Optional[ + ProcessRevisionDirectiveFn + ] = None, + ) -> None: + self.config = config + self.script_directory = script_directory + self.command_args = command_args + self.process_revision_directives = process_revision_directives + self.template_args = { + "config": config # Let templates use config for + # e.g. multiple databases + } + self.generated_revisions = [self._default_revision()] + + def _to_script( + self, migration_script: MigrationScript + ) -> Optional[Script]: + template_args: Dict[str, Any] = self.template_args.copy() + + if getattr(migration_script, "_needs_render", False): + autogen_context = self._last_autogen_context + + # clear out existing imports if we are doing multiple + # renders + autogen_context.imports = set() + if migration_script.imports: + autogen_context.imports.update(migration_script.imports) + render._render_python_into_templatevars( + autogen_context, migration_script, template_args + ) + + assert migration_script.rev_id is not None + return self.script_directory.generate_revision( + migration_script.rev_id, + migration_script.message, + refresh=True, + head=migration_script.head, + splice=migration_script.splice, + branch_labels=migration_script.branch_label, + version_path=migration_script.version_path, + depends_on=migration_script.depends_on, + **template_args, + ) + + def run_autogenerate( + self, rev: _GetRevArg, migration_context: MigrationContext + ) -> None: + self._run_environment(rev, migration_context, True) + + def run_no_autogenerate( + self, rev: _GetRevArg, migration_context: MigrationContext + ) -> None: + self._run_environment(rev, migration_context, False) + + def _run_environment( + self, + rev: _GetRevArg, + migration_context: MigrationContext, + autogenerate: bool, + ) -> None: + if autogenerate: + if self.command_args["sql"]: + raise util.CommandError( + "Using --sql with --autogenerate does not make any sense" + ) + if set(self.script_directory.get_revisions(rev)) != set( + self.script_directory.get_revisions("heads") + ): + raise util.CommandError("Target database is not up to date.") + + upgrade_token = migration_context.opts["upgrade_token"] + downgrade_token = migration_context.opts["downgrade_token"] + + migration_script = self.generated_revisions[-1] + if not getattr(migration_script, "_needs_render", False): + migration_script.upgrade_ops_list[-1].upgrade_token = upgrade_token + migration_script.downgrade_ops_list[-1].downgrade_token = ( + downgrade_token + ) + migration_script._needs_render = True + else: + migration_script._upgrade_ops.append( + ops.UpgradeOps([], upgrade_token=upgrade_token) + ) + migration_script._downgrade_ops.append( + ops.DowngradeOps([], downgrade_token=downgrade_token) + ) + + autogen_context = AutogenContext( + migration_context, autogenerate=autogenerate + ) + self._last_autogen_context: AutogenContext = autogen_context + + if autogenerate: + compare._populate_migration_script( + autogen_context, migration_script + ) + + if self.process_revision_directives: + self.process_revision_directives( + migration_context, rev, self.generated_revisions + ) + + hook = migration_context.opts["process_revision_directives"] + if hook: + hook(migration_context, rev, self.generated_revisions) + + for migration_script in self.generated_revisions: + migration_script._needs_render = True + + def _default_revision(self) -> MigrationScript: + command_args: Dict[str, Any] = self.command_args + op = ops.MigrationScript( + rev_id=command_args["rev_id"] or util.rev_id(), + message=command_args["message"], + upgrade_ops=ops.UpgradeOps([]), + downgrade_ops=ops.DowngradeOps([]), + head=command_args["head"], + splice=command_args["splice"], + branch_label=command_args["branch_label"], + version_path=command_args["version_path"], + depends_on=command_args["depends_on"], + ) + return op + + def generate_scripts(self) -> Iterator[Optional[Script]]: + for generated_revision in self.generated_revisions: + yield self._to_script(generated_revision) diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/compare.py b/venv/lib/python3.8/site-packages/alembic/autogenerate/compare.py new file mode 100644 index 000000000..0d9851964 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/autogenerate/compare.py @@ -0,0 +1,1329 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import contextlib +import logging +import re +from typing import Any +from typing import cast +from typing import Dict +from typing import Iterator +from typing import Mapping +from typing import Optional +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy import event +from sqlalchemy import inspect +from sqlalchemy import schema as sa_schema +from sqlalchemy import text +from sqlalchemy import types as sqltypes +from sqlalchemy.sql import expression +from sqlalchemy.sql.schema import ForeignKeyConstraint +from sqlalchemy.sql.schema import Index +from sqlalchemy.sql.schema import UniqueConstraint +from sqlalchemy.util import OrderedSet + +from .. import util +from ..ddl._autogen import is_index_sig +from ..ddl._autogen import is_uq_sig +from ..operations import ops +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.engine.reflection import Inspector + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Table + + from alembic.autogenerate.api import AutogenContext + from alembic.ddl.impl import DefaultImpl + from alembic.operations.ops import AlterColumnOp + from alembic.operations.ops import MigrationScript + from alembic.operations.ops import ModifyTableOps + from alembic.operations.ops import UpgradeOps + from ..ddl._autogen import _constraint_sig + + +log = logging.getLogger(__name__) + + +def _populate_migration_script( + autogen_context: AutogenContext, migration_script: MigrationScript +) -> None: + upgrade_ops = migration_script.upgrade_ops_list[-1] + downgrade_ops = migration_script.downgrade_ops_list[-1] + + _produce_net_changes(autogen_context, upgrade_ops) + upgrade_ops.reverse_into(downgrade_ops) + + +comparators = util.Dispatcher(uselist=True) + + +def _produce_net_changes( + autogen_context: AutogenContext, upgrade_ops: UpgradeOps +) -> None: + connection = autogen_context.connection + assert connection is not None + include_schemas = autogen_context.opts.get("include_schemas", False) + + inspector: Inspector = inspect(connection) + + default_schema = connection.dialect.default_schema_name + schemas: Set[Optional[str]] + if include_schemas: + schemas = set(inspector.get_schema_names()) + # replace default schema name with None + schemas.discard("information_schema") + # replace the "default" schema with None + schemas.discard(default_schema) + schemas.add(None) + else: + schemas = {None} + + schemas = { + s for s in schemas if autogen_context.run_name_filters(s, "schema", {}) + } + + assert autogen_context.dialect is not None + comparators.dispatch("schema", autogen_context.dialect.name)( + autogen_context, upgrade_ops, schemas + ) + + +@comparators.dispatch_for("schema") +def _autogen_for_tables( + autogen_context: AutogenContext, + upgrade_ops: UpgradeOps, + schemas: Union[Set[None], Set[Optional[str]]], +) -> None: + inspector = autogen_context.inspector + + conn_table_names: Set[Tuple[Optional[str], str]] = set() + + version_table_schema = ( + autogen_context.migration_context.version_table_schema + ) + version_table = autogen_context.migration_context.version_table + + for schema_name in schemas: + tables = set(inspector.get_table_names(schema=schema_name)) + if schema_name == version_table_schema: + tables = tables.difference( + [autogen_context.migration_context.version_table] + ) + + conn_table_names.update( + (schema_name, tname) + for tname in tables + if autogen_context.run_name_filters( + tname, "table", {"schema_name": schema_name} + ) + ) + + metadata_table_names = OrderedSet( + [(table.schema, table.name) for table in autogen_context.sorted_tables] + ).difference([(version_table_schema, version_table)]) + + _compare_tables( + conn_table_names, + metadata_table_names, + inspector, + upgrade_ops, + autogen_context, + ) + + +def _compare_tables( + conn_table_names: set, + metadata_table_names: set, + inspector: Inspector, + upgrade_ops: UpgradeOps, + autogen_context: AutogenContext, +) -> None: + default_schema = inspector.bind.dialect.default_schema_name + + # tables coming from the connection will not have "schema" + # set if it matches default_schema_name; so we need a list + # of table names from local metadata that also have "None" if schema + # == default_schema_name. Most setups will be like this anyway but + # some are not (see #170) + metadata_table_names_no_dflt_schema = OrderedSet( + [ + (schema if schema != default_schema else None, tname) + for schema, tname in metadata_table_names + ] + ) + + # to adjust for the MetaData collection storing the tables either + # as "schemaname.tablename" or just "tablename", create a new lookup + # which will match the "non-default-schema" keys to the Table object. + tname_to_table = { + no_dflt_schema: autogen_context.table_key_to_table[ + sa_schema._get_table_key(tname, schema) + ] + for no_dflt_schema, (schema, tname) in zip( + metadata_table_names_no_dflt_schema, metadata_table_names + ) + } + metadata_table_names = metadata_table_names_no_dflt_schema + + for s, tname in metadata_table_names.difference(conn_table_names): + name = "%s.%s" % (s, tname) if s else tname + metadata_table = tname_to_table[(s, tname)] + if autogen_context.run_object_filters( + metadata_table, tname, "table", False, None + ): + upgrade_ops.ops.append( + ops.CreateTableOp.from_table(metadata_table) + ) + log.info("Detected added table %r", name) + modify_table_ops = ops.ModifyTableOps(tname, [], schema=s) + + comparators.dispatch("table")( + autogen_context, + modify_table_ops, + s, + tname, + None, + metadata_table, + ) + if not modify_table_ops.is_empty(): + upgrade_ops.ops.append(modify_table_ops) + + removal_metadata = sa_schema.MetaData() + for s, tname in conn_table_names.difference(metadata_table_names): + name = sa_schema._get_table_key(tname, s) + exists = name in removal_metadata.tables + t = sa_schema.Table(tname, removal_metadata, schema=s) + + if not exists: + event.listen( + t, + "column_reflect", + # fmt: off + autogen_context.migration_context.impl. + _compat_autogen_column_reflect + (inspector), + # fmt: on + ) + sqla_compat._reflect_table(inspector, t) + if autogen_context.run_object_filters(t, tname, "table", True, None): + modify_table_ops = ops.ModifyTableOps(tname, [], schema=s) + + comparators.dispatch("table")( + autogen_context, modify_table_ops, s, tname, t, None + ) + if not modify_table_ops.is_empty(): + upgrade_ops.ops.append(modify_table_ops) + + upgrade_ops.ops.append(ops.DropTableOp.from_table(t)) + log.info("Detected removed table %r", name) + + existing_tables = conn_table_names.intersection(metadata_table_names) + + existing_metadata = sa_schema.MetaData() + conn_column_info = {} + for s, tname in existing_tables: + name = sa_schema._get_table_key(tname, s) + exists = name in existing_metadata.tables + t = sa_schema.Table(tname, existing_metadata, schema=s) + if not exists: + event.listen( + t, + "column_reflect", + # fmt: off + autogen_context.migration_context.impl. + _compat_autogen_column_reflect(inspector), + # fmt: on + ) + sqla_compat._reflect_table(inspector, t) + conn_column_info[(s, tname)] = t + + for s, tname in sorted(existing_tables, key=lambda x: (x[0] or "", x[1])): + s = s or None + name = "%s.%s" % (s, tname) if s else tname + metadata_table = tname_to_table[(s, tname)] + conn_table = existing_metadata.tables[name] + + if autogen_context.run_object_filters( + metadata_table, tname, "table", False, conn_table + ): + modify_table_ops = ops.ModifyTableOps(tname, [], schema=s) + with _compare_columns( + s, + tname, + conn_table, + metadata_table, + modify_table_ops, + autogen_context, + inspector, + ): + comparators.dispatch("table")( + autogen_context, + modify_table_ops, + s, + tname, + conn_table, + metadata_table, + ) + + if not modify_table_ops.is_empty(): + upgrade_ops.ops.append(modify_table_ops) + + +_IndexColumnSortingOps: Mapping[str, Any] = util.immutabledict( + { + "asc": expression.asc, + "desc": expression.desc, + "nulls_first": expression.nullsfirst, + "nulls_last": expression.nullslast, + "nullsfirst": expression.nullsfirst, # 1_3 name + "nullslast": expression.nullslast, # 1_3 name + } +) + + +def _make_index( + impl: DefaultImpl, params: Dict[str, Any], conn_table: Table +) -> Optional[Index]: + exprs: list[Union[Column[Any], TextClause]] = [] + sorting = params.get("column_sorting") + + for num, col_name in enumerate(params["column_names"]): + item: Union[Column[Any], TextClause] + if col_name is None: + assert "expressions" in params + name = params["expressions"][num] + item = text(name) + else: + name = col_name + item = conn_table.c[col_name] + if sorting and name in sorting: + for operator in sorting[name]: + if operator in _IndexColumnSortingOps: + item = _IndexColumnSortingOps[operator](item) + exprs.append(item) + ix = sa_schema.Index( + params["name"], + *exprs, + unique=params["unique"], + _table=conn_table, + **impl.adjust_reflected_dialect_options(params, "index"), + ) + if "duplicates_constraint" in params: + ix.info["duplicates_constraint"] = params["duplicates_constraint"] + return ix + + +def _make_unique_constraint( + impl: DefaultImpl, params: Dict[str, Any], conn_table: Table +) -> UniqueConstraint: + uq = sa_schema.UniqueConstraint( + *[conn_table.c[cname] for cname in params["column_names"]], + name=params["name"], + **impl.adjust_reflected_dialect_options(params, "unique_constraint"), + ) + if "duplicates_index" in params: + uq.info["duplicates_index"] = params["duplicates_index"] + + return uq + + +def _make_foreign_key( + params: Dict[str, Any], conn_table: Table +) -> ForeignKeyConstraint: + tname = params["referred_table"] + if params["referred_schema"]: + tname = "%s.%s" % (params["referred_schema"], tname) + + options = params.get("options", {}) + + const = sa_schema.ForeignKeyConstraint( + [conn_table.c[cname] for cname in params["constrained_columns"]], + ["%s.%s" % (tname, n) for n in params["referred_columns"]], + onupdate=options.get("onupdate"), + ondelete=options.get("ondelete"), + deferrable=options.get("deferrable"), + initially=options.get("initially"), + name=params["name"], + ) + # needed by 0.7 + conn_table.append_constraint(const) + return const + + +@contextlib.contextmanager +def _compare_columns( + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Table, + metadata_table: Table, + modify_table_ops: ModifyTableOps, + autogen_context: AutogenContext, + inspector: Inspector, +) -> Iterator[None]: + name = "%s.%s" % (schema, tname) if schema else tname + metadata_col_names = OrderedSet( + c.name for c in metadata_table.c if not c.system + ) + metadata_cols_by_name = { + c.name: c for c in metadata_table.c if not c.system + } + + conn_col_names = { + c.name: c + for c in conn_table.c + if autogen_context.run_name_filters( + c.name, "column", {"table_name": tname, "schema_name": schema} + ) + } + + for cname in metadata_col_names.difference(conn_col_names): + if autogen_context.run_object_filters( + metadata_cols_by_name[cname], cname, "column", False, None + ): + modify_table_ops.ops.append( + ops.AddColumnOp.from_column_and_tablename( + schema, tname, metadata_cols_by_name[cname] + ) + ) + log.info("Detected added column '%s.%s'", name, cname) + + for colname in metadata_col_names.intersection(conn_col_names): + metadata_col = metadata_cols_by_name[colname] + conn_col = conn_table.c[colname] + if not autogen_context.run_object_filters( + metadata_col, colname, "column", False, conn_col + ): + continue + alter_column_op = ops.AlterColumnOp(tname, colname, schema=schema) + + comparators.dispatch("column")( + autogen_context, + alter_column_op, + schema, + tname, + colname, + conn_col, + metadata_col, + ) + + if alter_column_op.has_changes(): + modify_table_ops.ops.append(alter_column_op) + + yield + + for cname in set(conn_col_names).difference(metadata_col_names): + if autogen_context.run_object_filters( + conn_table.c[cname], cname, "column", True, None + ): + modify_table_ops.ops.append( + ops.DropColumnOp.from_column_and_tablename( + schema, tname, conn_table.c[cname] + ) + ) + log.info("Detected removed column '%s.%s'", name, cname) + + +_C = TypeVar("_C", bound=Union[UniqueConstraint, ForeignKeyConstraint, Index]) + + +@comparators.dispatch_for("table") +def _compare_indexes_and_uniques( + autogen_context: AutogenContext, + modify_ops: ModifyTableOps, + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Optional[Table], + metadata_table: Optional[Table], +) -> None: + inspector = autogen_context.inspector + is_create_table = conn_table is None + is_drop_table = metadata_table is None + impl = autogen_context.migration_context.impl + + # 1a. get raw indexes and unique constraints from metadata ... + if metadata_table is not None: + metadata_unique_constraints = { + uq + for uq in metadata_table.constraints + if isinstance(uq, sa_schema.UniqueConstraint) + } + metadata_indexes = set(metadata_table.indexes) + else: + metadata_unique_constraints = set() + metadata_indexes = set() + + conn_uniques = conn_indexes = frozenset() # type:ignore[var-annotated] + + supports_unique_constraints = False + + unique_constraints_duplicate_unique_indexes = False + + if conn_table is not None: + # 1b. ... and from connection, if the table exists + try: + conn_uniques = inspector.get_unique_constraints( # type:ignore[assignment] # noqa + tname, schema=schema + ) + supports_unique_constraints = True + except NotImplementedError: + pass + except TypeError: + # number of arguments is off for the base + # method in SQLAlchemy due to the cache decorator + # not being present + pass + else: + conn_uniques = [ # type:ignore[assignment] + uq + for uq in conn_uniques + if autogen_context.run_name_filters( + uq["name"], + "unique_constraint", + {"table_name": tname, "schema_name": schema}, + ) + ] + for uq in conn_uniques: + if uq.get("duplicates_index"): + unique_constraints_duplicate_unique_indexes = True + try: + conn_indexes = inspector.get_indexes( # type:ignore[assignment] + tname, schema=schema + ) + except NotImplementedError: + pass + else: + conn_indexes = [ # type:ignore[assignment] + ix + for ix in conn_indexes + if autogen_context.run_name_filters( + ix["name"], + "index", + {"table_name": tname, "schema_name": schema}, + ) + ] + + # 2. convert conn-level objects from raw inspector records + # into schema objects + if is_drop_table: + # for DROP TABLE uniques are inline, don't need them + conn_uniques = set() # type:ignore[assignment] + else: + conn_uniques = { # type:ignore[assignment] + _make_unique_constraint(impl, uq_def, conn_table) + for uq_def in conn_uniques + } + + conn_indexes = { # type:ignore[assignment] + index + for index in ( + _make_index(impl, ix, conn_table) for ix in conn_indexes + ) + if index is not None + } + + # 2a. if the dialect dupes unique indexes as unique constraints + # (mysql and oracle), correct for that + + if unique_constraints_duplicate_unique_indexes: + _correct_for_uq_duplicates_uix( + conn_uniques, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + autogen_context.dialect, + impl, + ) + + # 3. give the dialect a chance to omit indexes and constraints that + # we know are either added implicitly by the DB or that the DB + # can't accurately report on + impl.correct_for_autogen_constraints( + conn_uniques, # type: ignore[arg-type] + conn_indexes, # type: ignore[arg-type] + metadata_unique_constraints, + metadata_indexes, + ) + + # 4. organize the constraints into "signature" collections, the + # _constraint_sig() objects provide a consistent facade over both + # Index and UniqueConstraint so we can easily work with them + # interchangeably + metadata_unique_constraints_sig = { + impl._create_metadata_constraint_sig(uq) + for uq in metadata_unique_constraints + } + + metadata_indexes_sig = { + impl._create_metadata_constraint_sig(ix) for ix in metadata_indexes + } + + conn_unique_constraints = { + impl._create_reflected_constraint_sig(uq) for uq in conn_uniques + } + + conn_indexes_sig = { + impl._create_reflected_constraint_sig(ix) for ix in conn_indexes + } + + # 5. index things by name, for those objects that have names + metadata_names = { + cast(str, c.md_name_to_sql_name(autogen_context)): c + for c in metadata_unique_constraints_sig.union(metadata_indexes_sig) + if c.is_named + } + + conn_uniques_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig] + conn_indexes_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig] + + conn_uniques_by_name = {c.name: c for c in conn_unique_constraints} + conn_indexes_by_name = {c.name: c for c in conn_indexes_sig} + conn_names = { + c.name: c + for c in conn_unique_constraints.union(conn_indexes_sig) + if sqla_compat.constraint_name_string(c.name) + } + + doubled_constraints = { + name: (conn_uniques_by_name[name], conn_indexes_by_name[name]) + for name in set(conn_uniques_by_name).intersection( + conn_indexes_by_name + ) + } + + # 6. index things by "column signature", to help with unnamed unique + # constraints. + conn_uniques_by_sig = {uq.unnamed: uq for uq in conn_unique_constraints} + metadata_uniques_by_sig = { + uq.unnamed: uq for uq in metadata_unique_constraints_sig + } + unnamed_metadata_uniques = { + uq.unnamed: uq + for uq in metadata_unique_constraints_sig + if not sqla_compat._constraint_is_named( + uq.const, autogen_context.dialect + ) + } + + # assumptions: + # 1. a unique constraint or an index from the connection *always* + # has a name. + # 2. an index on the metadata side *always* has a name. + # 3. a unique constraint on the metadata side *might* have a name. + # 4. The backend may double up indexes as unique constraints and + # vice versa (e.g. MySQL, Postgresql) + + def obj_added(obj: _constraint_sig): + if is_index_sig(obj): + if autogen_context.run_object_filters( + obj.const, obj.name, "index", False, None + ): + modify_ops.ops.append(ops.CreateIndexOp.from_index(obj.const)) + log.info( + "Detected added index '%r' on '%s'", + obj.name, + obj.column_names, + ) + elif is_uq_sig(obj): + if not supports_unique_constraints: + # can't report unique indexes as added if we don't + # detect them + return + if is_create_table or is_drop_table: + # unique constraints are created inline with table defs + return + if autogen_context.run_object_filters( + obj.const, obj.name, "unique_constraint", False, None + ): + modify_ops.ops.append( + ops.AddConstraintOp.from_constraint(obj.const) + ) + log.info( + "Detected added unique constraint %r on '%s'", + obj.name, + obj.column_names, + ) + else: + assert False + + def obj_removed(obj: _constraint_sig): + if is_index_sig(obj): + if obj.is_unique and not supports_unique_constraints: + # many databases double up unique constraints + # as unique indexes. without that list we can't + # be sure what we're doing here + return + + if autogen_context.run_object_filters( + obj.const, obj.name, "index", True, None + ): + modify_ops.ops.append(ops.DropIndexOp.from_index(obj.const)) + log.info("Detected removed index %r on %r", obj.name, tname) + elif is_uq_sig(obj): + if is_create_table or is_drop_table: + # if the whole table is being dropped, we don't need to + # consider unique constraint separately + return + if autogen_context.run_object_filters( + obj.const, obj.name, "unique_constraint", True, None + ): + modify_ops.ops.append( + ops.DropConstraintOp.from_constraint(obj.const) + ) + log.info( + "Detected removed unique constraint %r on %r", + obj.name, + tname, + ) + else: + assert False + + def obj_changed( + old: _constraint_sig, + new: _constraint_sig, + msg: str, + ): + if is_index_sig(old): + assert is_index_sig(new) + + if autogen_context.run_object_filters( + new.const, new.name, "index", False, old.const + ): + log.info( + "Detected changed index %r on %r: %s", old.name, tname, msg + ) + modify_ops.ops.append(ops.DropIndexOp.from_index(old.const)) + modify_ops.ops.append(ops.CreateIndexOp.from_index(new.const)) + elif is_uq_sig(old): + assert is_uq_sig(new) + + if autogen_context.run_object_filters( + new.const, new.name, "unique_constraint", False, old.const + ): + log.info( + "Detected changed unique constraint %r on %r: %s", + old.name, + tname, + msg, + ) + modify_ops.ops.append( + ops.DropConstraintOp.from_constraint(old.const) + ) + modify_ops.ops.append( + ops.AddConstraintOp.from_constraint(new.const) + ) + else: + assert False + + for removed_name in sorted(set(conn_names).difference(metadata_names)): + conn_obj = conn_names[removed_name] + if ( + is_uq_sig(conn_obj) + and conn_obj.unnamed in unnamed_metadata_uniques + ): + continue + elif removed_name in doubled_constraints: + conn_uq, conn_idx = doubled_constraints[removed_name] + if ( + all( + conn_idx.unnamed != meta_idx.unnamed + for meta_idx in metadata_indexes_sig + ) + and conn_uq.unnamed not in metadata_uniques_by_sig + ): + obj_removed(conn_uq) + obj_removed(conn_idx) + else: + obj_removed(conn_obj) + + for existing_name in sorted(set(metadata_names).intersection(conn_names)): + metadata_obj = metadata_names[existing_name] + + if existing_name in doubled_constraints: + conn_uq, conn_idx = doubled_constraints[existing_name] + if is_index_sig(metadata_obj): + conn_obj = conn_idx + else: + conn_obj = conn_uq + else: + conn_obj = conn_names[existing_name] + + if type(conn_obj) != type(metadata_obj): + obj_removed(conn_obj) + obj_added(metadata_obj) + else: + comparison = metadata_obj.compare_to_reflected(conn_obj) + + if comparison.is_different: + # constraint are different + obj_changed(conn_obj, metadata_obj, comparison.message) + elif comparison.is_skip: + # constraint cannot be compared, skip them + thing = ( + "index" if is_index_sig(conn_obj) else "unique constraint" + ) + log.info( + "Cannot compare %s %r, assuming equal and skipping. %s", + thing, + conn_obj.name, + comparison.message, + ) + else: + # constraint are equal + assert comparison.is_equal + + for added_name in sorted(set(metadata_names).difference(conn_names)): + obj = metadata_names[added_name] + obj_added(obj) + + for uq_sig in unnamed_metadata_uniques: + if uq_sig not in conn_uniques_by_sig: + obj_added(unnamed_metadata_uniques[uq_sig]) + + +def _correct_for_uq_duplicates_uix( + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + dialect, + impl, +): + # dedupe unique indexes vs. constraints, since MySQL / Oracle + # doesn't really have unique constraints as a separate construct. + # but look in the metadata and try to maintain constructs + # that already seem to be defined one way or the other + # on that side. This logic was formerly local to MySQL dialect, + # generalized to Oracle and others. See #276 + + # resolve final rendered name for unique constraints defined in the + # metadata. this includes truncation of long names. naming convention + # names currently should already be set as cons.name, however leave this + # to the sqla_compat to decide. + metadata_cons_names = [ + (sqla_compat._get_constraint_final_name(cons, dialect), cons) + for cons in metadata_unique_constraints + ] + + metadata_uq_names = { + name for name, cons in metadata_cons_names if name is not None + } + + unnamed_metadata_uqs = { + impl._create_metadata_constraint_sig(cons).unnamed + for name, cons in metadata_cons_names + if name is None + } + + metadata_ix_names = { + sqla_compat._get_constraint_final_name(cons, dialect) + for cons in metadata_indexes + if cons.unique + } + + # for reflection side, names are in their final database form + # already since they're from the database + conn_ix_names = {cons.name: cons for cons in conn_indexes if cons.unique} + + uqs_dupe_indexes = { + cons.name: cons + for cons in conn_unique_constraints + if cons.info["duplicates_index"] + } + + for overlap in uqs_dupe_indexes: + if overlap not in metadata_uq_names: + if ( + impl._create_reflected_constraint_sig( + uqs_dupe_indexes[overlap] + ).unnamed + not in unnamed_metadata_uqs + ): + conn_unique_constraints.discard(uqs_dupe_indexes[overlap]) + elif overlap not in metadata_ix_names: + conn_indexes.discard(conn_ix_names[overlap]) + + +@comparators.dispatch_for("column") +def _compare_nullable( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> None: + metadata_col_nullable = metadata_col.nullable + conn_col_nullable = conn_col.nullable + alter_column_op.existing_nullable = conn_col_nullable + + if conn_col_nullable is not metadata_col_nullable: + if ( + sqla_compat._server_default_is_computed( + metadata_col.server_default, conn_col.server_default + ) + and sqla_compat._nullability_might_be_unset(metadata_col) + or ( + sqla_compat._server_default_is_identity( + metadata_col.server_default, conn_col.server_default + ) + ) + ): + log.info( + "Ignoring nullable change on identity column '%s.%s'", + tname, + cname, + ) + else: + alter_column_op.modify_nullable = metadata_col_nullable + log.info( + "Detected %s on column '%s.%s'", + "NULL" if metadata_col_nullable else "NOT NULL", + tname, + cname, + ) + + +@comparators.dispatch_for("column") +def _setup_autoincrement( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: quoted_name, + conn_col: Column[Any], + metadata_col: Column[Any], +) -> None: + if metadata_col.table._autoincrement_column is metadata_col: + alter_column_op.kw["autoincrement"] = True + elif metadata_col.autoincrement is True: + alter_column_op.kw["autoincrement"] = True + elif metadata_col.autoincrement is False: + alter_column_op.kw["autoincrement"] = False + + +@comparators.dispatch_for("column") +def _compare_type( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> None: + conn_type = conn_col.type + alter_column_op.existing_type = conn_type + metadata_type = metadata_col.type + if conn_type._type_affinity is sqltypes.NullType: + log.info( + "Couldn't determine database type " "for column '%s.%s'", + tname, + cname, + ) + return + if metadata_type._type_affinity is sqltypes.NullType: + log.info( + "Column '%s.%s' has no type within " "the model; can't compare", + tname, + cname, + ) + return + + isdiff = autogen_context.migration_context._compare_type( + conn_col, metadata_col + ) + + if isdiff: + alter_column_op.modify_type = metadata_type + log.info( + "Detected type change from %r to %r on '%s.%s'", + conn_type, + metadata_type, + tname, + cname, + ) + + +def _render_server_default_for_compare( + metadata_default: Optional[Any], autogen_context: AutogenContext +) -> Optional[str]: + if isinstance(metadata_default, sa_schema.DefaultClause): + if isinstance(metadata_default.arg, str): + metadata_default = metadata_default.arg + else: + metadata_default = str( + metadata_default.arg.compile( + dialect=autogen_context.dialect, + compile_kwargs={"literal_binds": True}, + ) + ) + if isinstance(metadata_default, str): + return metadata_default + else: + return None + + +def _normalize_computed_default(sqltext: str) -> str: + """we want to warn if a computed sql expression has changed. however + we don't want false positives and the warning is not that critical. + so filter out most forms of variability from the SQL text. + + """ + + return re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower() + + +def _compare_computed_default( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: str, + cname: str, + conn_col: Column[Any], + metadata_col: Column[Any], +) -> None: + rendered_metadata_default = str( + cast(sa_schema.Computed, metadata_col.server_default).sqltext.compile( + dialect=autogen_context.dialect, + compile_kwargs={"literal_binds": True}, + ) + ) + + # since we cannot change computed columns, we do only a crude comparison + # here where we try to eliminate syntactical differences in order to + # get a minimal comparison just to emit a warning. + + rendered_metadata_default = _normalize_computed_default( + rendered_metadata_default + ) + + if isinstance(conn_col.server_default, sa_schema.Computed): + rendered_conn_default = str( + conn_col.server_default.sqltext.compile( + dialect=autogen_context.dialect, + compile_kwargs={"literal_binds": True}, + ) + ) + if rendered_conn_default is None: + rendered_conn_default = "" + else: + rendered_conn_default = _normalize_computed_default( + rendered_conn_default + ) + else: + rendered_conn_default = "" + + if rendered_metadata_default != rendered_conn_default: + _warn_computed_not_supported(tname, cname) + + +def _warn_computed_not_supported(tname: str, cname: str) -> None: + util.warn("Computed default on %s.%s cannot be modified" % (tname, cname)) + + +def _compare_identity_default( + autogen_context, + alter_column_op, + schema, + tname, + cname, + conn_col, + metadata_col, +): + impl = autogen_context.migration_context.impl + diff, ignored_attr, is_alter = impl._compare_identity_default( + metadata_col.server_default, conn_col.server_default + ) + + return diff, is_alter + + +@comparators.dispatch_for("column") +def _compare_server_default( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> Optional[bool]: + metadata_default = metadata_col.server_default + conn_col_default = conn_col.server_default + if conn_col_default is None and metadata_default is None: + return False + + if sqla_compat._server_default_is_computed(metadata_default): + # return False in case of a computed column as the server + # default. Note that DDL for adding or removing "GENERATED AS" from + # an existing column is not currently known for any backend. + # Once SQLAlchemy can reflect "GENERATED" as the "computed" element, + # we would also want to ignore and/or warn for changes vs. the + # metadata (or support backend specific DDL if applicable). + if not sqla_compat.has_computed_reflection: + return False + + else: + return ( + _compare_computed_default( # type:ignore[func-returns-value] + autogen_context, + alter_column_op, + schema, + tname, + cname, + conn_col, + metadata_col, + ) + ) + if sqla_compat._server_default_is_computed(conn_col_default): + _warn_computed_not_supported(tname, cname) + return False + + if sqla_compat._server_default_is_identity( + metadata_default, conn_col_default + ): + alter_column_op.existing_server_default = conn_col_default + diff, is_alter = _compare_identity_default( + autogen_context, + alter_column_op, + schema, + tname, + cname, + conn_col, + metadata_col, + ) + if is_alter: + alter_column_op.modify_server_default = metadata_default + if diff: + log.info( + "Detected server default on column '%s.%s': " + "identity options attributes %s", + tname, + cname, + sorted(diff), + ) + else: + rendered_metadata_default = _render_server_default_for_compare( + metadata_default, autogen_context + ) + + rendered_conn_default = ( + cast(Any, conn_col_default).arg.text if conn_col_default else None + ) + + alter_column_op.existing_server_default = conn_col_default + + is_diff = autogen_context.migration_context._compare_server_default( + conn_col, + metadata_col, + rendered_metadata_default, + rendered_conn_default, + ) + if is_diff: + alter_column_op.modify_server_default = metadata_default + log.info("Detected server default on column '%s.%s'", tname, cname) + + return None + + +@comparators.dispatch_for("column") +def _compare_column_comment( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: quoted_name, + conn_col: Column[Any], + metadata_col: Column[Any], +) -> Optional[Literal[False]]: + assert autogen_context.dialect is not None + if not autogen_context.dialect.supports_comments: + return None + + metadata_comment = metadata_col.comment + conn_col_comment = conn_col.comment + if conn_col_comment is None and metadata_comment is None: + return False + + alter_column_op.existing_comment = conn_col_comment + + if conn_col_comment != metadata_comment: + alter_column_op.modify_comment = metadata_comment + log.info("Detected column comment '%s.%s'", tname, cname) + + return None + + +@comparators.dispatch_for("table") +def _compare_foreign_keys( + autogen_context: AutogenContext, + modify_table_ops: ModifyTableOps, + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Table, + metadata_table: Table, +) -> None: + # if we're doing CREATE TABLE, all FKs are created + # inline within the table def + if conn_table is None or metadata_table is None: + return + + inspector = autogen_context.inspector + metadata_fks = { + fk + for fk in metadata_table.constraints + if isinstance(fk, sa_schema.ForeignKeyConstraint) + } + + conn_fks_list = [ + fk + for fk in inspector.get_foreign_keys(tname, schema=schema) + if autogen_context.run_name_filters( + fk["name"], + "foreign_key_constraint", + {"table_name": tname, "schema_name": schema}, + ) + ] + + conn_fks = { + _make_foreign_key(const, conn_table) # type: ignore[arg-type] + for const in conn_fks_list + } + + impl = autogen_context.migration_context.impl + + # give the dialect a chance to correct the FKs to match more + # closely + autogen_context.migration_context.impl.correct_for_autogen_foreignkeys( + conn_fks, metadata_fks + ) + + metadata_fks_sig = { + impl._create_metadata_constraint_sig(fk) for fk in metadata_fks + } + + conn_fks_sig = { + impl._create_reflected_constraint_sig(fk) for fk in conn_fks + } + + # check if reflected FKs include options, indicating the backend + # can reflect FK options + if conn_fks_list and "options" in conn_fks_list[0]: + conn_fks_by_sig = {c.unnamed: c for c in conn_fks_sig} + metadata_fks_by_sig = {c.unnamed: c for c in metadata_fks_sig} + else: + # otherwise compare by sig without options added + conn_fks_by_sig = {c.unnamed_no_options: c for c in conn_fks_sig} + metadata_fks_by_sig = { + c.unnamed_no_options: c for c in metadata_fks_sig + } + + metadata_fks_by_name = { + c.name: c for c in metadata_fks_sig if c.name is not None + } + conn_fks_by_name = {c.name: c for c in conn_fks_sig if c.name is not None} + + def _add_fk(obj, compare_to): + if autogen_context.run_object_filters( + obj.const, obj.name, "foreign_key_constraint", False, compare_to + ): + modify_table_ops.ops.append( + ops.CreateForeignKeyOp.from_constraint(const.const) # type: ignore[has-type] # noqa: E501 + ) + + log.info( + "Detected added foreign key (%s)(%s) on table %s%s", + ", ".join(obj.source_columns), + ", ".join(obj.target_columns), + "%s." % obj.source_schema if obj.source_schema else "", + obj.source_table, + ) + + def _remove_fk(obj, compare_to): + if autogen_context.run_object_filters( + obj.const, obj.name, "foreign_key_constraint", True, compare_to + ): + modify_table_ops.ops.append( + ops.DropConstraintOp.from_constraint(obj.const) + ) + log.info( + "Detected removed foreign key (%s)(%s) on table %s%s", + ", ".join(obj.source_columns), + ", ".join(obj.target_columns), + "%s." % obj.source_schema if obj.source_schema else "", + obj.source_table, + ) + + # so far it appears we don't need to do this by name at all. + # SQLite doesn't preserve constraint names anyway + + for removed_sig in set(conn_fks_by_sig).difference(metadata_fks_by_sig): + const = conn_fks_by_sig[removed_sig] + if removed_sig not in metadata_fks_by_sig: + compare_to = ( + metadata_fks_by_name[const.name].const + if const.name in metadata_fks_by_name + else None + ) + _remove_fk(const, compare_to) + + for added_sig in set(metadata_fks_by_sig).difference(conn_fks_by_sig): + const = metadata_fks_by_sig[added_sig] + if added_sig not in conn_fks_by_sig: + compare_to = ( + conn_fks_by_name[const.name].const + if const.name in conn_fks_by_name + else None + ) + _add_fk(const, compare_to) + + +@comparators.dispatch_for("table") +def _compare_table_comment( + autogen_context: AutogenContext, + modify_table_ops: ModifyTableOps, + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Optional[Table], + metadata_table: Optional[Table], +) -> None: + assert autogen_context.dialect is not None + if not autogen_context.dialect.supports_comments: + return + + # if we're doing CREATE TABLE, comments will be created inline + # with the create_table op. + if conn_table is None or metadata_table is None: + return + + if conn_table.comment is None and metadata_table.comment is None: + return + + if metadata_table.comment is None and conn_table.comment is not None: + modify_table_ops.ops.append( + ops.DropTableCommentOp( + tname, existing_comment=conn_table.comment, schema=schema + ) + ) + elif metadata_table.comment != conn_table.comment: + modify_table_ops.ops.append( + ops.CreateTableCommentOp( + tname, + metadata_table.comment, + existing_comment=conn_table.comment, + schema=schema, + ) + ) diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/render.py b/venv/lib/python3.8/site-packages/alembic/autogenerate/render.py new file mode 100644 index 000000000..6ebfbf9f3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/autogenerate/render.py @@ -0,0 +1,1119 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from io import StringIO +import re +from typing import Any +from typing import cast +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from mako.pygen import PythonPrinter +from sqlalchemy import schema as sa_schema +from sqlalchemy import sql +from sqlalchemy import types as sqltypes +from sqlalchemy.sql.elements import conv +from sqlalchemy.sql.elements import quoted_name + +from .. import util +from ..operations import ops +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.sql.base import DialectKWArgs + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.schema import CheckConstraint + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import FetchedValue + from sqlalchemy.sql.schema import ForeignKey + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import PrimaryKeyConstraint + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.sqltypes import ARRAY + from sqlalchemy.sql.type_api import TypeEngine + + from alembic.autogenerate.api import AutogenContext + from alembic.config import Config + from alembic.operations.ops import MigrationScript + from alembic.operations.ops import ModifyTableOps + from alembic.util.sqla_compat import Computed + from alembic.util.sqla_compat import Identity + + +MAX_PYTHON_ARGS = 255 + + +def _render_gen_name( + autogen_context: AutogenContext, + name: sqla_compat._ConstraintName, +) -> Optional[Union[quoted_name, str, _f_name]]: + if isinstance(name, conv): + return _f_name(_alembic_autogenerate_prefix(autogen_context), name) + else: + return sqla_compat.constraint_name_or_none(name) + + +def _indent(text: str) -> str: + text = re.compile(r"^", re.M).sub(" ", text).strip() + text = re.compile(r" +$", re.M).sub("", text) + return text + + +def _render_python_into_templatevars( + autogen_context: AutogenContext, + migration_script: MigrationScript, + template_args: Dict[str, Union[str, Config]], +) -> None: + imports = autogen_context.imports + + for upgrade_ops, downgrade_ops in zip( + migration_script.upgrade_ops_list, migration_script.downgrade_ops_list + ): + template_args[upgrade_ops.upgrade_token] = _indent( + _render_cmd_body(upgrade_ops, autogen_context) + ) + template_args[downgrade_ops.downgrade_token] = _indent( + _render_cmd_body(downgrade_ops, autogen_context) + ) + template_args["imports"] = "\n".join(sorted(imports)) + + +default_renderers = renderers = util.Dispatcher() + + +def _render_cmd_body( + op_container: ops.OpContainer, + autogen_context: AutogenContext, +) -> str: + buf = StringIO() + printer = PythonPrinter(buf) + + printer.writeline( + "# ### commands auto generated by Alembic - please adjust! ###" + ) + + has_lines = False + for op in op_container.ops: + lines = render_op(autogen_context, op) + has_lines = has_lines or bool(lines) + + for line in lines: + printer.writeline(line) + + if not has_lines: + printer.writeline("pass") + + printer.writeline("# ### end Alembic commands ###") + + return buf.getvalue() + + +def render_op( + autogen_context: AutogenContext, op: ops.MigrateOperation +) -> List[str]: + renderer = renderers.dispatch(op) + lines = util.to_list(renderer(autogen_context, op)) + return lines + + +def render_op_text( + autogen_context: AutogenContext, op: ops.MigrateOperation +) -> str: + return "\n".join(render_op(autogen_context, op)) + + +@renderers.dispatch_for(ops.ModifyTableOps) +def _render_modify_table( + autogen_context: AutogenContext, op: ModifyTableOps +) -> List[str]: + opts = autogen_context.opts + render_as_batch = opts.get("render_as_batch", False) + + if op.ops: + lines = [] + if render_as_batch: + with autogen_context._within_batch(): + lines.append( + "with op.batch_alter_table(%r, schema=%r) as batch_op:" + % (op.table_name, op.schema) + ) + for t_op in op.ops: + t_lines = render_op(autogen_context, t_op) + lines.extend(t_lines) + lines.append("") + else: + for t_op in op.ops: + t_lines = render_op(autogen_context, t_op) + lines.extend(t_lines) + + return lines + else: + return [] + + +@renderers.dispatch_for(ops.CreateTableCommentOp) +def _render_create_table_comment( + autogen_context: AutogenContext, op: ops.CreateTableCommentOp +) -> str: + if autogen_context._has_batch: + templ = ( + "{prefix}create_table_comment(\n" + "{indent}{comment},\n" + "{indent}existing_comment={existing}\n" + ")" + ) + else: + templ = ( + "{prefix}create_table_comment(\n" + "{indent}'{tname}',\n" + "{indent}{comment},\n" + "{indent}existing_comment={existing},\n" + "{indent}schema={schema}\n" + ")" + ) + return templ.format( + prefix=_alembic_autogenerate_prefix(autogen_context), + tname=op.table_name, + comment="%r" % op.comment if op.comment is not None else None, + existing=( + "%r" % op.existing_comment + if op.existing_comment is not None + else None + ), + schema="'%s'" % op.schema if op.schema is not None else None, + indent=" ", + ) + + +@renderers.dispatch_for(ops.DropTableCommentOp) +def _render_drop_table_comment( + autogen_context: AutogenContext, op: ops.DropTableCommentOp +) -> str: + if autogen_context._has_batch: + templ = ( + "{prefix}drop_table_comment(\n" + "{indent}existing_comment={existing}\n" + ")" + ) + else: + templ = ( + "{prefix}drop_table_comment(\n" + "{indent}'{tname}',\n" + "{indent}existing_comment={existing},\n" + "{indent}schema={schema}\n" + ")" + ) + return templ.format( + prefix=_alembic_autogenerate_prefix(autogen_context), + tname=op.table_name, + existing=( + "%r" % op.existing_comment + if op.existing_comment is not None + else None + ), + schema="'%s'" % op.schema if op.schema is not None else None, + indent=" ", + ) + + +@renderers.dispatch_for(ops.CreateTableOp) +def _add_table(autogen_context: AutogenContext, op: ops.CreateTableOp) -> str: + table = op.to_table() + + args = [ + col + for col in [ + _render_column(col, autogen_context) for col in table.columns + ] + if col + ] + sorted( + [ + rcons + for rcons in [ + _render_constraint( + cons, autogen_context, op._namespace_metadata + ) + for cons in table.constraints + ] + if rcons is not None + ] + ) + + if len(args) > MAX_PYTHON_ARGS: + args_str = "*[" + ",\n".join(args) + "]" + else: + args_str = ",\n".join(args) + + text = "%(prefix)screate_table(%(tablename)r,\n%(args)s" % { + "tablename": _ident(op.table_name), + "prefix": _alembic_autogenerate_prefix(autogen_context), + "args": args_str, + } + if op.schema: + text += ",\nschema=%r" % _ident(op.schema) + + comment = table.comment + if comment: + text += ",\ncomment=%r" % _ident(comment) + + info = table.info + if info: + text += f",\ninfo={info!r}" + + for k in sorted(op.kw): + text += ",\n%s=%r" % (k.replace(" ", "_"), op.kw[k]) + + if table._prefixes: + prefixes = ", ".join("'%s'" % p for p in table._prefixes) + text += ",\nprefixes=[%s]" % prefixes + + if op.if_not_exists is not None: + text += ",\nif_not_exists=%r" % bool(op.if_not_exists) + + text += "\n)" + return text + + +@renderers.dispatch_for(ops.DropTableOp) +def _drop_table(autogen_context: AutogenContext, op: ops.DropTableOp) -> str: + text = "%(prefix)sdrop_table(%(tname)r" % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": _ident(op.table_name), + } + if op.schema: + text += ", schema=%r" % _ident(op.schema) + + if op.if_exists is not None: + text += ", if_exists=%r" % bool(op.if_exists) + + text += ")" + return text + + +def _render_dialect_kwargs_items( + autogen_context: AutogenContext, item: DialectKWArgs +) -> list[str]: + return [ + f"{key}={_render_potential_expr(val, autogen_context)}" + for key, val in item.dialect_kwargs.items() + ] + + +@renderers.dispatch_for(ops.CreateIndexOp) +def _add_index(autogen_context: AutogenContext, op: ops.CreateIndexOp) -> str: + index = op.to_index() + + has_batch = autogen_context._has_batch + + if has_batch: + tmpl = ( + "%(prefix)screate_index(%(name)r, [%(columns)s], " + "unique=%(unique)r%(kwargs)s)" + ) + else: + tmpl = ( + "%(prefix)screate_index(%(name)r, %(table)r, [%(columns)s], " + "unique=%(unique)r%(schema)s%(kwargs)s)" + ) + + assert index.table is not None + + opts = _render_dialect_kwargs_items(autogen_context, index) + if op.if_not_exists is not None: + opts.append("if_not_exists=%r" % bool(op.if_not_exists)) + text = tmpl % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "name": _render_gen_name(autogen_context, index.name), + "table": _ident(index.table.name), + "columns": ", ".join( + _get_index_rendered_expressions(index, autogen_context) + ), + "unique": index.unique or False, + "schema": ( + (", schema=%r" % _ident(index.table.schema)) + if index.table.schema + else "" + ), + "kwargs": ", " + ", ".join(opts) if opts else "", + } + return text + + +@renderers.dispatch_for(ops.DropIndexOp) +def _drop_index(autogen_context: AutogenContext, op: ops.DropIndexOp) -> str: + index = op.to_index() + + has_batch = autogen_context._has_batch + + if has_batch: + tmpl = "%(prefix)sdrop_index(%(name)r%(kwargs)s)" + else: + tmpl = ( + "%(prefix)sdrop_index(%(name)r, " + "table_name=%(table_name)r%(schema)s%(kwargs)s)" + ) + opts = _render_dialect_kwargs_items(autogen_context, index) + if op.if_exists is not None: + opts.append("if_exists=%r" % bool(op.if_exists)) + text = tmpl % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "name": _render_gen_name(autogen_context, op.index_name), + "table_name": _ident(op.table_name), + "schema": ((", schema=%r" % _ident(op.schema)) if op.schema else ""), + "kwargs": ", " + ", ".join(opts) if opts else "", + } + return text + + +@renderers.dispatch_for(ops.CreateUniqueConstraintOp) +def _add_unique_constraint( + autogen_context: AutogenContext, op: ops.CreateUniqueConstraintOp +) -> List[str]: + return [_uq_constraint(op.to_constraint(), autogen_context, True)] + + +@renderers.dispatch_for(ops.CreateForeignKeyOp) +def _add_fk_constraint( + autogen_context: AutogenContext, op: ops.CreateForeignKeyOp +) -> str: + args = [repr(_render_gen_name(autogen_context, op.constraint_name))] + if not autogen_context._has_batch: + args.append(repr(_ident(op.source_table))) + + args.extend( + [ + repr(_ident(op.referent_table)), + repr([_ident(col) for col in op.local_cols]), + repr([_ident(col) for col in op.remote_cols]), + ] + ) + kwargs = [ + "referent_schema", + "onupdate", + "ondelete", + "initially", + "deferrable", + "use_alter", + "match", + ] + if not autogen_context._has_batch: + kwargs.insert(0, "source_schema") + + for k in kwargs: + if k in op.kw: + value = op.kw[k] + if value is not None: + args.append("%s=%r" % (k, value)) + + return "%(prefix)screate_foreign_key(%(args)s)" % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + + +@renderers.dispatch_for(ops.CreatePrimaryKeyOp) +def _add_pk_constraint(constraint, autogen_context): + raise NotImplementedError() + + +@renderers.dispatch_for(ops.CreateCheckConstraintOp) +def _add_check_constraint(constraint, autogen_context): + raise NotImplementedError() + + +@renderers.dispatch_for(ops.DropConstraintOp) +def _drop_constraint( + autogen_context: AutogenContext, op: ops.DropConstraintOp +) -> str: + prefix = _alembic_autogenerate_prefix(autogen_context) + name = _render_gen_name(autogen_context, op.constraint_name) + schema = _ident(op.schema) if op.schema else None + type_ = _ident(op.constraint_type) if op.constraint_type else None + + params_strs = [] + params_strs.append(repr(name)) + if not autogen_context._has_batch: + params_strs.append(repr(_ident(op.table_name))) + if schema is not None: + params_strs.append(f"schema={schema!r}") + if type_ is not None: + params_strs.append(f"type_={type_!r}") + + return f"{prefix}drop_constraint({', '.join(params_strs)})" + + +@renderers.dispatch_for(ops.AddColumnOp) +def _add_column(autogen_context: AutogenContext, op: ops.AddColumnOp) -> str: + schema, tname, column = op.schema, op.table_name, op.column + if autogen_context._has_batch: + template = "%(prefix)sadd_column(%(column)s)" + else: + template = "%(prefix)sadd_column(%(tname)r, %(column)s" + if schema: + template += ", schema=%(schema)r" + template += ")" + text = template % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": tname, + "column": _render_column(column, autogen_context), + "schema": schema, + } + return text + + +@renderers.dispatch_for(ops.DropColumnOp) +def _drop_column(autogen_context: AutogenContext, op: ops.DropColumnOp) -> str: + schema, tname, column_name = op.schema, op.table_name, op.column_name + + if autogen_context._has_batch: + template = "%(prefix)sdrop_column(%(cname)r)" + else: + template = "%(prefix)sdrop_column(%(tname)r, %(cname)r" + if schema: + template += ", schema=%(schema)r" + template += ")" + + text = template % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": _ident(tname), + "cname": _ident(column_name), + "schema": _ident(schema), + } + return text + + +@renderers.dispatch_for(ops.AlterColumnOp) +def _alter_column( + autogen_context: AutogenContext, op: ops.AlterColumnOp +) -> str: + tname = op.table_name + cname = op.column_name + server_default = op.modify_server_default + type_ = op.modify_type + nullable = op.modify_nullable + comment = op.modify_comment + autoincrement = op.kw.get("autoincrement", None) + existing_type = op.existing_type + existing_nullable = op.existing_nullable + existing_comment = op.existing_comment + existing_server_default = op.existing_server_default + schema = op.schema + + indent = " " * 11 + + if autogen_context._has_batch: + template = "%(prefix)salter_column(%(cname)r" + else: + template = "%(prefix)salter_column(%(tname)r, %(cname)r" + + text = template % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": tname, + "cname": cname, + } + if existing_type is not None: + text += ",\n%sexisting_type=%s" % ( + indent, + _repr_type(existing_type, autogen_context), + ) + if server_default is not False: + rendered = _render_server_default(server_default, autogen_context) + text += ",\n%sserver_default=%s" % (indent, rendered) + + if type_ is not None: + text += ",\n%stype_=%s" % (indent, _repr_type(type_, autogen_context)) + if nullable is not None: + text += ",\n%snullable=%r" % (indent, nullable) + if comment is not False: + text += ",\n%scomment=%r" % (indent, comment) + if existing_comment is not None: + text += ",\n%sexisting_comment=%r" % (indent, existing_comment) + if nullable is None and existing_nullable is not None: + text += ",\n%sexisting_nullable=%r" % (indent, existing_nullable) + if autoincrement is not None: + text += ",\n%sautoincrement=%r" % (indent, autoincrement) + if server_default is False and existing_server_default: + rendered = _render_server_default( + existing_server_default, autogen_context + ) + text += ",\n%sexisting_server_default=%s" % (indent, rendered) + if schema and not autogen_context._has_batch: + text += ",\n%sschema=%r" % (indent, schema) + text += ")" + return text + + +class _f_name: + def __init__(self, prefix: str, name: conv) -> None: + self.prefix = prefix + self.name = name + + def __repr__(self) -> str: + return "%sf(%r)" % (self.prefix, _ident(self.name)) + + +def _ident(name: Optional[Union[quoted_name, str]]) -> Optional[str]: + """produce a __repr__() object for a string identifier that may + use quoted_name() in SQLAlchemy 0.9 and greater. + + The issue worked around here is that quoted_name() doesn't have + very good repr() behavior by itself when unicode is involved. + + """ + if name is None: + return name + elif isinstance(name, quoted_name): + return str(name) + elif isinstance(name, str): + return name + + +def _render_potential_expr( + value: Any, + autogen_context: AutogenContext, + *, + wrap_in_text: bool = True, + is_server_default: bool = False, + is_index: bool = False, +) -> str: + if isinstance(value, sql.ClauseElement): + if wrap_in_text: + template = "%(prefix)stext(%(sql)r)" + else: + template = "%(sql)r" + + return template % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "sql": autogen_context.migration_context.impl.render_ddl_sql_expr( + value, is_server_default=is_server_default, is_index=is_index + ), + } + + else: + return repr(value) + + +def _get_index_rendered_expressions( + idx: Index, autogen_context: AutogenContext +) -> List[str]: + return [ + ( + repr(_ident(getattr(exp, "name", None))) + if isinstance(exp, sa_schema.Column) + else _render_potential_expr(exp, autogen_context, is_index=True) + ) + for exp in idx.expressions + ] + + +def _uq_constraint( + constraint: UniqueConstraint, + autogen_context: AutogenContext, + alter: bool, +) -> str: + opts: List[Tuple[str, Any]] = [] + + has_batch = autogen_context._has_batch + + if constraint.deferrable: + opts.append(("deferrable", str(constraint.deferrable))) + if constraint.initially: + opts.append(("initially", str(constraint.initially))) + if not has_batch and alter and constraint.table.schema: + opts.append(("schema", _ident(constraint.table.schema))) + if not alter and constraint.name: + opts.append( + ("name", _render_gen_name(autogen_context, constraint.name)) + ) + dialect_options = _render_dialect_kwargs_items(autogen_context, constraint) + + if alter: + args = [repr(_render_gen_name(autogen_context, constraint.name))] + if not has_batch: + args += [repr(_ident(constraint.table.name))] + args.append(repr([_ident(col.name) for col in constraint.columns])) + args.extend(["%s=%r" % (k, v) for k, v in opts]) + args.extend(dialect_options) + return "%(prefix)screate_unique_constraint(%(args)s)" % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + else: + args = [repr(_ident(col.name)) for col in constraint.columns] + args.extend(["%s=%r" % (k, v) for k, v in opts]) + args.extend(dialect_options) + return "%(prefix)sUniqueConstraint(%(args)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + + +def _user_autogenerate_prefix(autogen_context, target): + prefix = autogen_context.opts["user_module_prefix"] + if prefix is None: + return "%s." % target.__module__ + else: + return prefix + + +def _sqlalchemy_autogenerate_prefix(autogen_context: AutogenContext) -> str: + return autogen_context.opts["sqlalchemy_module_prefix"] or "" + + +def _alembic_autogenerate_prefix(autogen_context: AutogenContext) -> str: + if autogen_context._has_batch: + return "batch_op." + else: + return autogen_context.opts["alembic_module_prefix"] or "" + + +def _user_defined_render( + type_: str, object_: Any, autogen_context: AutogenContext +) -> Union[str, Literal[False]]: + if "render_item" in autogen_context.opts: + render = autogen_context.opts["render_item"] + if render: + rendered = render(type_, object_, autogen_context) + if rendered is not False: + return rendered + return False + + +def _render_column( + column: Column[Any], autogen_context: AutogenContext +) -> str: + rendered = _user_defined_render("column", column, autogen_context) + if rendered is not False: + return rendered + + args: List[str] = [] + opts: List[Tuple[str, Any]] = [] + + if column.server_default: + rendered = _render_server_default( # type:ignore[assignment] + column.server_default, autogen_context + ) + if rendered: + if _should_render_server_default_positionally( + column.server_default + ): + args.append(rendered) + else: + opts.append(("server_default", rendered)) + + if ( + column.autoincrement is not None + and column.autoincrement != sqla_compat.AUTOINCREMENT_DEFAULT + ): + opts.append(("autoincrement", column.autoincrement)) + + if column.nullable is not None: + opts.append(("nullable", column.nullable)) + + if column.system: + opts.append(("system", column.system)) + + comment = column.comment + if comment: + opts.append(("comment", "%r" % comment)) + + # TODO: for non-ascii colname, assign a "key" + return "%(prefix)sColumn(%(name)r, %(type)s, %(args)s%(kwargs)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "name": _ident(column.name), + "type": _repr_type(column.type, autogen_context), + "args": ", ".join([str(arg) for arg in args]) + ", " if args else "", + "kwargs": ( + ", ".join( + ["%s=%s" % (kwname, val) for kwname, val in opts] + + [ + "%s=%s" + % (key, _render_potential_expr(val, autogen_context)) + for key, val in sqla_compat._column_kwargs(column).items() + ] + ) + ), + } + + +def _should_render_server_default_positionally(server_default: Any) -> bool: + return sqla_compat._server_default_is_computed( + server_default + ) or sqla_compat._server_default_is_identity(server_default) + + +def _render_server_default( + default: Optional[ + Union[FetchedValue, str, TextClause, ColumnElement[Any]] + ], + autogen_context: AutogenContext, + repr_: bool = True, +) -> Optional[str]: + rendered = _user_defined_render("server_default", default, autogen_context) + if rendered is not False: + return rendered + + if sqla_compat._server_default_is_computed(default): + return _render_computed(cast("Computed", default), autogen_context) + elif sqla_compat._server_default_is_identity(default): + return _render_identity(cast("Identity", default), autogen_context) + elif isinstance(default, sa_schema.DefaultClause): + if isinstance(default.arg, str): + default = default.arg + else: + return _render_potential_expr( + default.arg, autogen_context, is_server_default=True + ) + + if isinstance(default, str) and repr_: + default = repr(re.sub(r"^'|'$", "", default)) + + return cast(str, default) + + +def _render_computed( + computed: Computed, autogen_context: AutogenContext +) -> str: + text = _render_potential_expr( + computed.sqltext, autogen_context, wrap_in_text=False + ) + + kwargs = {} + if computed.persisted is not None: + kwargs["persisted"] = computed.persisted + return "%(prefix)sComputed(%(text)s, %(kwargs)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "text": text, + "kwargs": (", ".join("%s=%s" % pair for pair in kwargs.items())), + } + + +def _render_identity( + identity: Identity, autogen_context: AutogenContext +) -> str: + kwargs = sqla_compat._get_identity_options_dict( + identity, dialect_kwargs=True + ) + + return "%(prefix)sIdentity(%(kwargs)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "kwargs": (", ".join("%s=%s" % pair for pair in kwargs.items())), + } + + +def _repr_type( + type_: TypeEngine, + autogen_context: AutogenContext, + _skip_variants: bool = False, +) -> str: + rendered = _user_defined_render("type", type_, autogen_context) + if rendered is not False: + return rendered + + if hasattr(autogen_context.migration_context, "impl"): + impl_rt = autogen_context.migration_context.impl.render_type( + type_, autogen_context + ) + else: + impl_rt = None + + mod = type(type_).__module__ + imports = autogen_context.imports + + if not _skip_variants and sqla_compat._type_has_variants(type_): + return _render_Variant_type(type_, autogen_context) + elif mod.startswith("sqlalchemy.dialects"): + match = re.match(r"sqlalchemy\.dialects\.(\w+)", mod) + assert match is not None + dname = match.group(1) + if imports is not None: + imports.add("from sqlalchemy.dialects import %s" % dname) + if impl_rt: + return impl_rt + else: + return "%s.%r" % (dname, type_) + elif impl_rt: + return impl_rt + elif mod.startswith("sqlalchemy."): + if "_render_%s_type" % type_.__visit_name__ in globals(): + fn = globals()["_render_%s_type" % type_.__visit_name__] + return fn(type_, autogen_context) + else: + prefix = _sqlalchemy_autogenerate_prefix(autogen_context) + return "%s%r" % (prefix, type_) + else: + prefix = _user_autogenerate_prefix(autogen_context, type_) + return "%s%r" % (prefix, type_) + + +def _render_ARRAY_type(type_: ARRAY, autogen_context: AutogenContext) -> str: + return cast( + str, + _render_type_w_subtype( + type_, autogen_context, "item_type", r"(.+?\()" + ), + ) + + +def _render_Variant_type( + type_: TypeEngine, autogen_context: AutogenContext +) -> str: + base_type, variant_mapping = sqla_compat._get_variant_mapping(type_) + base = _repr_type(base_type, autogen_context, _skip_variants=True) + assert base is not None and base is not False # type: ignore[comparison-overlap] # noqa:E501 + for dialect in sorted(variant_mapping): + typ = variant_mapping[dialect] + base += ".with_variant(%s, %r)" % ( + _repr_type(typ, autogen_context, _skip_variants=True), + dialect, + ) + return base + + +def _render_type_w_subtype( + type_: TypeEngine, + autogen_context: AutogenContext, + attrname: str, + regexp: str, + prefix: Optional[str] = None, +) -> Union[Optional[str], Literal[False]]: + outer_repr = repr(type_) + inner_type = getattr(type_, attrname, None) + if inner_type is None: + return False + + inner_repr = repr(inner_type) + + inner_repr = re.sub(r"([\(\)])", r"\\\1", inner_repr) + sub_type = _repr_type(getattr(type_, attrname), autogen_context) + outer_type = re.sub(regexp + inner_repr, r"\1%s" % sub_type, outer_repr) + + if prefix: + return "%s%s" % (prefix, outer_type) + + mod = type(type_).__module__ + if mod.startswith("sqlalchemy.dialects"): + match = re.match(r"sqlalchemy\.dialects\.(\w+)", mod) + assert match is not None + dname = match.group(1) + return "%s.%s" % (dname, outer_type) + elif mod.startswith("sqlalchemy"): + prefix = _sqlalchemy_autogenerate_prefix(autogen_context) + return "%s%s" % (prefix, outer_type) + else: + return None + + +_constraint_renderers = util.Dispatcher() + + +def _render_constraint( + constraint: Constraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> Optional[str]: + try: + renderer = _constraint_renderers.dispatch(constraint) + except ValueError: + util.warn("No renderer is established for object %r" % constraint) + return "[Unknown Python object %r]" % constraint + else: + return renderer(constraint, autogen_context, namespace_metadata) + + +@_constraint_renderers.dispatch_for(sa_schema.PrimaryKeyConstraint) +def _render_primary_key( + constraint: PrimaryKeyConstraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> Optional[str]: + rendered = _user_defined_render("primary_key", constraint, autogen_context) + if rendered is not False: + return rendered + + if not constraint.columns: + return None + + opts = [] + if constraint.name: + opts.append( + ("name", repr(_render_gen_name(autogen_context, constraint.name))) + ) + return "%(prefix)sPrimaryKeyConstraint(%(args)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "args": ", ".join( + [repr(c.name) for c in constraint.columns] + + ["%s=%s" % (kwname, val) for kwname, val in opts] + ), + } + + +def _fk_colspec( + fk: ForeignKey, + metadata_schema: Optional[str], + namespace_metadata: MetaData, +) -> str: + """Implement a 'safe' version of ForeignKey._get_colspec() that + won't fail if the remote table can't be resolved. + + """ + colspec = fk._get_colspec() + tokens = colspec.split(".") + tname, colname = tokens[-2:] + + if metadata_schema is not None and len(tokens) == 2: + table_fullname = "%s.%s" % (metadata_schema, tname) + else: + table_fullname = ".".join(tokens[0:-1]) + + if ( + not fk.link_to_name + and fk.parent is not None + and fk.parent.table is not None + ): + # try to resolve the remote table in order to adjust for column.key. + # the FK constraint needs to be rendered in terms of the column + # name. + + if table_fullname in namespace_metadata.tables: + col = namespace_metadata.tables[table_fullname].c.get(colname) + if col is not None: + colname = _ident(col.name) # type: ignore[assignment] + + colspec = "%s.%s" % (table_fullname, colname) + + return colspec + + +def _populate_render_fk_opts( + constraint: ForeignKeyConstraint, opts: List[Tuple[str, str]] +) -> None: + if constraint.onupdate: + opts.append(("onupdate", repr(constraint.onupdate))) + if constraint.ondelete: + opts.append(("ondelete", repr(constraint.ondelete))) + if constraint.initially: + opts.append(("initially", repr(constraint.initially))) + if constraint.deferrable: + opts.append(("deferrable", repr(constraint.deferrable))) + if constraint.use_alter: + opts.append(("use_alter", repr(constraint.use_alter))) + if constraint.match: + opts.append(("match", repr(constraint.match))) + + +@_constraint_renderers.dispatch_for(sa_schema.ForeignKeyConstraint) +def _render_foreign_key( + constraint: ForeignKeyConstraint, + autogen_context: AutogenContext, + namespace_metadata: MetaData, +) -> Optional[str]: + rendered = _user_defined_render("foreign_key", constraint, autogen_context) + if rendered is not False: + return rendered + + opts = [] + if constraint.name: + opts.append( + ("name", repr(_render_gen_name(autogen_context, constraint.name))) + ) + + _populate_render_fk_opts(constraint, opts) + + apply_metadata_schema = namespace_metadata.schema + return ( + "%(prefix)sForeignKeyConstraint([%(cols)s], " + "[%(refcols)s], %(args)s)" + % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "cols": ", ".join( + repr(_ident(f.parent.name)) for f in constraint.elements + ), + "refcols": ", ".join( + repr(_fk_colspec(f, apply_metadata_schema, namespace_metadata)) + for f in constraint.elements + ), + "args": ", ".join( + ["%s=%s" % (kwname, val) for kwname, val in opts] + ), + } + ) + + +@_constraint_renderers.dispatch_for(sa_schema.UniqueConstraint) +def _render_unique_constraint( + constraint: UniqueConstraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> str: + rendered = _user_defined_render("unique", constraint, autogen_context) + if rendered is not False: + return rendered + + return _uq_constraint(constraint, autogen_context, False) + + +@_constraint_renderers.dispatch_for(sa_schema.CheckConstraint) +def _render_check_constraint( + constraint: CheckConstraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> Optional[str]: + rendered = _user_defined_render("check", constraint, autogen_context) + if rendered is not False: + return rendered + + # detect the constraint being part of + # a parent type which is probably in the Table already. + # ideally SQLAlchemy would give us more of a first class + # way to detect this. + if ( + constraint._create_rule + and hasattr(constraint._create_rule, "target") + and isinstance( + constraint._create_rule.target, + sqltypes.TypeEngine, + ) + ): + return None + opts = [] + if constraint.name: + opts.append( + ("name", repr(_render_gen_name(autogen_context, constraint.name))) + ) + return "%(prefix)sCheckConstraint(%(sqltext)s%(opts)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "opts": ( + ", " + (", ".join("%s=%s" % (k, v) for k, v in opts)) + if opts + else "" + ), + "sqltext": _render_potential_expr( + constraint.sqltext, autogen_context, wrap_in_text=False + ), + } + + +@renderers.dispatch_for(ops.ExecuteSQLOp) +def _execute_sql(autogen_context: AutogenContext, op: ops.ExecuteSQLOp) -> str: + if not isinstance(op.sqltext, str): + raise NotImplementedError( + "Autogenerate rendering of SQL Expression language constructs " + "not supported here; please use a plain SQL string" + ) + return "op.execute(%r)" % op.sqltext + + +renderers = default_renderers.branch() diff --git a/venv/lib/python3.8/site-packages/alembic/autogenerate/rewriter.py b/venv/lib/python3.8/site-packages/alembic/autogenerate/rewriter.py new file mode 100644 index 000000000..8994dcf82 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/autogenerate/rewriter.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +from typing import Any +from typing import Callable +from typing import Iterator +from typing import List +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union + +from .. import util +from ..operations import ops + +if TYPE_CHECKING: + from ..operations.ops import AddColumnOp + from ..operations.ops import AlterColumnOp + from ..operations.ops import CreateTableOp + from ..operations.ops import DowngradeOps + from ..operations.ops import MigrateOperation + from ..operations.ops import MigrationScript + from ..operations.ops import ModifyTableOps + from ..operations.ops import OpContainer + from ..operations.ops import UpgradeOps + from ..runtime.migration import MigrationContext + from ..script.revision import _GetRevArg + +ProcessRevisionDirectiveFn = Callable[ + ["MigrationContext", "_GetRevArg", List["MigrationScript"]], None +] + + +class Rewriter: + """A helper object that allows easy 'rewriting' of ops streams. + + The :class:`.Rewriter` object is intended to be passed along + to the + :paramref:`.EnvironmentContext.configure.process_revision_directives` + parameter in an ``env.py`` script. Once constructed, any number + of "rewrites" functions can be associated with it, which will be given + the opportunity to modify the structure without having to have explicit + knowledge of the overall structure. + + The function is passed the :class:`.MigrationContext` object and + ``revision`` tuple that are passed to the :paramref:`.Environment + Context.configure.process_revision_directives` function normally, + and the third argument is an individual directive of the type + noted in the decorator. The function has the choice of returning + a single op directive, which normally can be the directive that + was actually passed, or a new directive to replace it, or a list + of zero or more directives to replace it. + + .. seealso:: + + :ref:`autogen_rewriter` - usage example + + """ + + _traverse = util.Dispatcher() + + _chained: Tuple[Union[ProcessRevisionDirectiveFn, Rewriter], ...] = () + + def __init__(self) -> None: + self.dispatch = util.Dispatcher() + + def chain( + self, + other: Union[ + ProcessRevisionDirectiveFn, + Rewriter, + ], + ) -> Rewriter: + """Produce a "chain" of this :class:`.Rewriter` to another. + + This allows two or more rewriters to operate serially on a stream, + e.g.:: + + writer1 = autogenerate.Rewriter() + writer2 = autogenerate.Rewriter() + + + @writer1.rewrites(ops.AddColumnOp) + def add_column_nullable(context, revision, op): + op.column.nullable = True + return op + + + @writer2.rewrites(ops.AddColumnOp) + def add_column_idx(context, revision, op): + idx_op = ops.CreateIndexOp( + "ixc", op.table_name, [op.column.name] + ) + return [op, idx_op] + + writer = writer1.chain(writer2) + + :param other: a :class:`.Rewriter` instance + :return: a new :class:`.Rewriter` that will run the operations + of this writer, then the "other" writer, in succession. + + """ + wr = self.__class__.__new__(self.__class__) + wr.__dict__.update(self.__dict__) + wr._chained += (other,) + return wr + + def rewrites( + self, + operator: Union[ + Type[AddColumnOp], + Type[MigrateOperation], + Type[AlterColumnOp], + Type[CreateTableOp], + Type[ModifyTableOps], + ], + ) -> Callable[..., Any]: + """Register a function as rewriter for a given type. + + The function should receive three arguments, which are + the :class:`.MigrationContext`, a ``revision`` tuple, and + an op directive of the type indicated. E.g.:: + + @writer1.rewrites(ops.AddColumnOp) + def add_column_nullable(context, revision, op): + op.column.nullable = True + return op + + """ + return self.dispatch.dispatch_for(operator) + + def _rewrite( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrateOperation, + ) -> Iterator[MigrateOperation]: + try: + _rewriter = self.dispatch.dispatch(directive) + except ValueError: + _rewriter = None + yield directive + else: + if self in directive._mutations: + yield directive + else: + for r_directive in util.to_list( + _rewriter(context, revision, directive), [] + ): + r_directive._mutations = r_directive._mutations.union( + [self] + ) + yield r_directive + + def __call__( + self, + context: MigrationContext, + revision: _GetRevArg, + directives: List[MigrationScript], + ) -> None: + self.process_revision_directives(context, revision, directives) + for process_revision_directives in self._chained: + process_revision_directives(context, revision, directives) + + @_traverse.dispatch_for(ops.MigrationScript) + def _traverse_script( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrationScript, + ) -> None: + upgrade_ops_list: List[UpgradeOps] = [] + for upgrade_ops in directive.upgrade_ops_list: + ret = self._traverse_for(context, revision, upgrade_ops) + if len(ret) != 1: + raise ValueError( + "Can only return single object for UpgradeOps traverse" + ) + upgrade_ops_list.append(ret[0]) + + directive.upgrade_ops = upgrade_ops_list # type: ignore + + downgrade_ops_list: List[DowngradeOps] = [] + for downgrade_ops in directive.downgrade_ops_list: + ret = self._traverse_for(context, revision, downgrade_ops) + if len(ret) != 1: + raise ValueError( + "Can only return single object for DowngradeOps traverse" + ) + downgrade_ops_list.append(ret[0]) + directive.downgrade_ops = downgrade_ops_list # type: ignore + + @_traverse.dispatch_for(ops.OpContainer) + def _traverse_op_container( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: OpContainer, + ) -> None: + self._traverse_list(context, revision, directive.ops) + + @_traverse.dispatch_for(ops.MigrateOperation) + def _traverse_any_directive( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrateOperation, + ) -> None: + pass + + def _traverse_for( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrateOperation, + ) -> Any: + directives = list(self._rewrite(context, revision, directive)) + for directive in directives: + traverser = self._traverse.dispatch(directive) + traverser(self, context, revision, directive) + return directives + + def _traverse_list( + self, + context: MigrationContext, + revision: _GetRevArg, + directives: Any, + ) -> None: + dest = [] + for directive in directives: + dest.extend(self._traverse_for(context, revision, directive)) + + directives[:] = dest + + def process_revision_directives( + self, + context: MigrationContext, + revision: _GetRevArg, + directives: List[MigrationScript], + ) -> None: + self._traverse_list(context, revision, directives) diff --git a/venv/lib/python3.8/site-packages/alembic/command.py b/venv/lib/python3.8/site-packages/alembic/command.py new file mode 100644 index 000000000..89c12354a --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/command.py @@ -0,0 +1,758 @@ +# mypy: allow-untyped-defs, allow-untyped-calls + +from __future__ import annotations + +import os +from typing import List +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from . import autogenerate as autogen +from . import util +from .runtime.environment import EnvironmentContext +from .script import ScriptDirectory + +if TYPE_CHECKING: + from alembic.config import Config + from alembic.script.base import Script + from alembic.script.revision import _RevIdType + from .runtime.environment import ProcessRevisionDirectiveFn + + +def list_templates(config: Config) -> None: + """List available templates. + + :param config: a :class:`.Config` object. + + """ + + config.print_stdout("Available templates:\n") + for tempname in os.listdir(config.get_template_directory()): + with open( + os.path.join(config.get_template_directory(), tempname, "README") + ) as readme: + synopsis = next(readme).rstrip() + config.print_stdout("%s - %s", tempname, synopsis) + + config.print_stdout("\nTemplates are used via the 'init' command, e.g.:") + config.print_stdout("\n alembic init --template generic ./scripts") + + +def init( + config: Config, + directory: str, + template: str = "generic", + package: bool = False, +) -> None: + """Initialize a new scripts directory. + + :param config: a :class:`.Config` object. + + :param directory: string path of the target directory. + + :param template: string name of the migration environment template to + use. + + :param package: when True, write ``__init__.py`` files into the + environment location as well as the versions/ location. + + """ + + if os.access(directory, os.F_OK) and os.listdir(directory): + raise util.CommandError( + "Directory %s already exists and is not empty" % directory + ) + + template_dir = os.path.join(config.get_template_directory(), template) + if not os.access(template_dir, os.F_OK): + raise util.CommandError("No such template %r" % template) + + if not os.access(directory, os.F_OK): + with util.status( + f"Creating directory {os.path.abspath(directory)!r}", + **config.messaging_opts, + ): + os.makedirs(directory) + + versions = os.path.join(directory, "versions") + with util.status( + f"Creating directory {os.path.abspath(versions)!r}", + **config.messaging_opts, + ): + os.makedirs(versions) + + script = ScriptDirectory(directory) + + config_file: str | None = None + for file_ in os.listdir(template_dir): + file_path = os.path.join(template_dir, file_) + if file_ == "alembic.ini.mako": + assert config.config_file_name is not None + config_file = os.path.abspath(config.config_file_name) + if os.access(config_file, os.F_OK): + util.msg( + f"File {config_file!r} already exists, skipping", + **config.messaging_opts, + ) + else: + script._generate_template( + file_path, config_file, script_location=directory + ) + elif os.path.isfile(file_path): + output_file = os.path.join(directory, file_) + script._copy_file(file_path, output_file) + + if package: + for path in [ + os.path.join(os.path.abspath(directory), "__init__.py"), + os.path.join(os.path.abspath(versions), "__init__.py"), + ]: + with util.status(f"Adding {path!r}", **config.messaging_opts): + with open(path, "w"): + pass + + assert config_file is not None + util.msg( + "Please edit configuration/connection/logging " + f"settings in {config_file!r} before proceeding.", + **config.messaging_opts, + ) + + +def revision( + config: Config, + message: Optional[str] = None, + autogenerate: bool = False, + sql: bool = False, + head: str = "head", + splice: bool = False, + branch_label: Optional[_RevIdType] = None, + version_path: Optional[str] = None, + rev_id: Optional[str] = None, + depends_on: Optional[str] = None, + process_revision_directives: Optional[ProcessRevisionDirectiveFn] = None, +) -> Union[Optional[Script], List[Optional[Script]]]: + """Create a new revision file. + + :param config: a :class:`.Config` object. + + :param message: string message to apply to the revision; this is the + ``-m`` option to ``alembic revision``. + + :param autogenerate: whether or not to autogenerate the script from + the database; this is the ``--autogenerate`` option to + ``alembic revision``. + + :param sql: whether to dump the script out as a SQL string; when specified, + the script is dumped to stdout. This is the ``--sql`` option to + ``alembic revision``. + + :param head: head revision to build the new revision upon as a parent; + this is the ``--head`` option to ``alembic revision``. + + :param splice: whether or not the new revision should be made into a + new head of its own; is required when the given ``head`` is not itself + a head. This is the ``--splice`` option to ``alembic revision``. + + :param branch_label: string label to apply to the branch; this is the + ``--branch-label`` option to ``alembic revision``. + + :param version_path: string symbol identifying a specific version path + from the configuration; this is the ``--version-path`` option to + ``alembic revision``. + + :param rev_id: optional revision identifier to use instead of having + one generated; this is the ``--rev-id`` option to ``alembic revision``. + + :param depends_on: optional list of "depends on" identifiers; this is the + ``--depends-on`` option to ``alembic revision``. + + :param process_revision_directives: this is a callable that takes the + same form as the callable described at + :paramref:`.EnvironmentContext.configure.process_revision_directives`; + will be applied to the structure generated by the revision process + where it can be altered programmatically. Note that unlike all + the other parameters, this option is only available via programmatic + use of :func:`.command.revision`. + + """ + + script_directory = ScriptDirectory.from_config(config) + + command_args = dict( + message=message, + autogenerate=autogenerate, + sql=sql, + head=head, + splice=splice, + branch_label=branch_label, + version_path=version_path, + rev_id=rev_id, + depends_on=depends_on, + ) + revision_context = autogen.RevisionContext( + config, + script_directory, + command_args, + process_revision_directives=process_revision_directives, + ) + + environment = util.asbool(config.get_main_option("revision_environment")) + + if autogenerate: + environment = True + + if sql: + raise util.CommandError( + "Using --sql with --autogenerate does not make any sense" + ) + + def retrieve_migrations(rev, context): + revision_context.run_autogenerate(rev, context) + return [] + + elif environment: + + def retrieve_migrations(rev, context): + revision_context.run_no_autogenerate(rev, context) + return [] + + elif sql: + raise util.CommandError( + "Using --sql with the revision command when " + "revision_environment is not configured does not make any sense" + ) + + if environment: + with EnvironmentContext( + config, + script_directory, + fn=retrieve_migrations, + as_sql=sql, + template_args=revision_context.template_args, + revision_context=revision_context, + ): + script_directory.run_env() + + # the revision_context now has MigrationScript structure(s) present. + # these could theoretically be further processed / rewritten *here*, + # in addition to the hooks present within each run_migrations() call, + # or at the end of env.py run_migrations_online(). + + scripts = [script for script in revision_context.generate_scripts()] + if len(scripts) == 1: + return scripts[0] + else: + return scripts + + +def check(config: "Config") -> None: + """Check if revision command with autogenerate has pending upgrade ops. + + :param config: a :class:`.Config` object. + + .. versionadded:: 1.9.0 + + """ + + script_directory = ScriptDirectory.from_config(config) + + command_args = dict( + message=None, + autogenerate=True, + sql=False, + head="head", + splice=False, + branch_label=None, + version_path=None, + rev_id=None, + depends_on=None, + ) + revision_context = autogen.RevisionContext( + config, + script_directory, + command_args, + ) + + def retrieve_migrations(rev, context): + revision_context.run_autogenerate(rev, context) + return [] + + with EnvironmentContext( + config, + script_directory, + fn=retrieve_migrations, + as_sql=False, + template_args=revision_context.template_args, + revision_context=revision_context, + ): + script_directory.run_env() + + # the revision_context now has MigrationScript structure(s) present. + + migration_script = revision_context.generated_revisions[-1] + diffs = [] + for upgrade_ops in migration_script.upgrade_ops_list: + diffs.extend(upgrade_ops.as_diffs()) + + if diffs: + raise util.AutogenerateDiffsDetected( + f"New upgrade operations detected: {diffs}" + ) + else: + config.print_stdout("No new upgrade operations detected.") + + +def merge( + config: Config, + revisions: _RevIdType, + message: Optional[str] = None, + branch_label: Optional[_RevIdType] = None, + rev_id: Optional[str] = None, +) -> Optional[Script]: + """Merge two revisions together. Creates a new migration file. + + :param config: a :class:`.Config` instance + + :param revisions: The revisions to merge. + + :param message: string message to apply to the revision. + + :param branch_label: string label name to apply to the new revision. + + :param rev_id: hardcoded revision identifier instead of generating a new + one. + + .. seealso:: + + :ref:`branches` + + """ + + script = ScriptDirectory.from_config(config) + template_args = { + "config": config # Let templates use config for + # e.g. multiple databases + } + + environment = util.asbool(config.get_main_option("revision_environment")) + + if environment: + + def nothing(rev, context): + return [] + + with EnvironmentContext( + config, + script, + fn=nothing, + as_sql=False, + template_args=template_args, + ): + script.run_env() + + return script.generate_revision( + rev_id or util.rev_id(), + message, + refresh=True, + head=revisions, + branch_labels=branch_label, + **template_args, # type:ignore[arg-type] + ) + + +def upgrade( + config: Config, + revision: str, + sql: bool = False, + tag: Optional[str] = None, +) -> None: + """Upgrade to a later version. + + :param config: a :class:`.Config` instance. + + :param revision: string revision target or range for --sql mode. May be + ``"heads"`` to target the most recent revision(s). + + :param sql: if True, use ``--sql`` mode. + + :param tag: an arbitrary "tag" that can be intercepted by custom + ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument` + method. + + """ + + script = ScriptDirectory.from_config(config) + + starting_rev = None + if ":" in revision: + if not sql: + raise util.CommandError("Range revision not allowed") + starting_rev, revision = revision.split(":", 2) + + def upgrade(rev, context): + return script._upgrade_revs(revision, rev) + + with EnvironmentContext( + config, + script, + fn=upgrade, + as_sql=sql, + starting_rev=starting_rev, + destination_rev=revision, + tag=tag, + ): + script.run_env() + + +def downgrade( + config: Config, + revision: str, + sql: bool = False, + tag: Optional[str] = None, +) -> None: + """Revert to a previous version. + + :param config: a :class:`.Config` instance. + + :param revision: string revision target or range for --sql mode. May + be ``"base"`` to target the first revision. + + :param sql: if True, use ``--sql`` mode. + + :param tag: an arbitrary "tag" that can be intercepted by custom + ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument` + method. + + """ + + script = ScriptDirectory.from_config(config) + starting_rev = None + if ":" in revision: + if not sql: + raise util.CommandError("Range revision not allowed") + starting_rev, revision = revision.split(":", 2) + elif sql: + raise util.CommandError( + "downgrade with --sql requires :" + ) + + def downgrade(rev, context): + return script._downgrade_revs(revision, rev) + + with EnvironmentContext( + config, + script, + fn=downgrade, + as_sql=sql, + starting_rev=starting_rev, + destination_rev=revision, + tag=tag, + ): + script.run_env() + + +def show(config: Config, rev: str) -> None: + """Show the revision(s) denoted by the given symbol. + + :param config: a :class:`.Config` instance. + + :param rev: string revision target. May be ``"current"`` to show the + revision(s) currently applied in the database. + + """ + + script = ScriptDirectory.from_config(config) + + if rev == "current": + + def show_current(rev, context): + for sc in script.get_revisions(rev): + config.print_stdout(sc.log_entry) + return [] + + with EnvironmentContext(config, script, fn=show_current): + script.run_env() + else: + for sc in script.get_revisions(rev): + config.print_stdout(sc.log_entry) + + +def history( + config: Config, + rev_range: Optional[str] = None, + verbose: bool = False, + indicate_current: bool = False, +) -> None: + """List changeset scripts in chronological order. + + :param config: a :class:`.Config` instance. + + :param rev_range: string revision range. + + :param verbose: output in verbose mode. + + :param indicate_current: indicate current revision. + + """ + base: Optional[str] + head: Optional[str] + script = ScriptDirectory.from_config(config) + if rev_range is not None: + if ":" not in rev_range: + raise util.CommandError( + "History range requires [start]:[end], " "[start]:, or :[end]" + ) + base, head = rev_range.strip().split(":") + else: + base = head = None + + environment = ( + util.asbool(config.get_main_option("revision_environment")) + or indicate_current + ) + + def _display_history(config, script, base, head, currents=()): + for sc in script.walk_revisions( + base=base or "base", head=head or "heads" + ): + if indicate_current: + sc._db_current_indicator = sc.revision in currents + + config.print_stdout( + sc.cmd_format( + verbose=verbose, + include_branches=True, + include_doc=True, + include_parents=True, + ) + ) + + def _display_history_w_current(config, script, base, head): + def _display_current_history(rev, context): + if head == "current": + _display_history(config, script, base, rev, rev) + elif base == "current": + _display_history(config, script, rev, head, rev) + else: + _display_history(config, script, base, head, rev) + return [] + + with EnvironmentContext(config, script, fn=_display_current_history): + script.run_env() + + if base == "current" or head == "current" or environment: + _display_history_w_current(config, script, base, head) + else: + _display_history(config, script, base, head) + + +def heads( + config: Config, verbose: bool = False, resolve_dependencies: bool = False +) -> None: + """Show current available heads in the script directory. + + :param config: a :class:`.Config` instance. + + :param verbose: output in verbose mode. + + :param resolve_dependencies: treat dependency version as down revisions. + + """ + + script = ScriptDirectory.from_config(config) + if resolve_dependencies: + heads = script.get_revisions("heads") + else: + heads = script.get_revisions(script.get_heads()) + + for rev in heads: + config.print_stdout( + rev.cmd_format( + verbose, include_branches=True, tree_indicators=False + ) + ) + + +def branches(config: Config, verbose: bool = False) -> None: + """Show current branch points. + + :param config: a :class:`.Config` instance. + + :param verbose: output in verbose mode. + + """ + script = ScriptDirectory.from_config(config) + for sc in script.walk_revisions(): + if sc.is_branch_point: + config.print_stdout( + "%s\n%s\n", + sc.cmd_format(verbose, include_branches=True), + "\n".join( + "%s -> %s" + % ( + " " * len(str(sc.revision)), + rev_obj.cmd_format( + False, include_branches=True, include_doc=verbose + ), + ) + for rev_obj in ( + script.get_revision(rev) for rev in sc.nextrev + ) + ), + ) + + +def current(config: Config, verbose: bool = False) -> None: + """Display the current revision for a database. + + :param config: a :class:`.Config` instance. + + :param verbose: output in verbose mode. + + """ + + script = ScriptDirectory.from_config(config) + + def display_version(rev, context): + if verbose: + config.print_stdout( + "Current revision(s) for %s:", + util.obfuscate_url_pw(context.connection.engine.url), + ) + for rev in script.get_all_current(rev): + config.print_stdout(rev.cmd_format(verbose)) + + return [] + + with EnvironmentContext( + config, script, fn=display_version, dont_mutate=True + ): + script.run_env() + + +def stamp( + config: Config, + revision: _RevIdType, + sql: bool = False, + tag: Optional[str] = None, + purge: bool = False, +) -> None: + """'stamp' the revision table with the given revision; don't + run any migrations. + + :param config: a :class:`.Config` instance. + + :param revision: target revision or list of revisions. May be a list + to indicate stamping of multiple branch heads; may be ``"base"`` + to remove all revisions from the table or ``"heads"`` to stamp the + most recent revision(s). + + .. note:: this parameter is called "revisions" in the command line + interface. + + :param sql: use ``--sql`` mode + + :param tag: an arbitrary "tag" that can be intercepted by custom + ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument` + method. + + :param purge: delete all entries in the version table before stamping. + + """ + + script = ScriptDirectory.from_config(config) + + if sql: + destination_revs = [] + starting_rev = None + for _revision in util.to_list(revision): + if ":" in _revision: + srev, _revision = _revision.split(":", 2) + + if starting_rev != srev: + if starting_rev is None: + starting_rev = srev + else: + raise util.CommandError( + "Stamp operation with --sql only supports a " + "single starting revision at a time" + ) + destination_revs.append(_revision) + else: + destination_revs = util.to_list(revision) + + def do_stamp(rev, context): + return script._stamp_revs(util.to_tuple(destination_revs), rev) + + with EnvironmentContext( + config, + script, + fn=do_stamp, + as_sql=sql, + starting_rev=starting_rev if sql else None, + destination_rev=util.to_tuple(destination_revs), + tag=tag, + purge=purge, + ): + script.run_env() + + +def edit(config: Config, rev: str) -> None: + """Edit revision script(s) using $EDITOR. + + :param config: a :class:`.Config` instance. + + :param rev: target revision. + + """ + + script = ScriptDirectory.from_config(config) + + if rev == "current": + + def edit_current(rev, context): + if not rev: + raise util.CommandError("No current revisions") + for sc in script.get_revisions(rev): + util.open_in_editor(sc.path) + return [] + + with EnvironmentContext(config, script, fn=edit_current): + script.run_env() + else: + revs = script.get_revisions(rev) + if not revs: + raise util.CommandError( + "No revision files indicated by symbol '%s'" % rev + ) + for sc in revs: + assert sc + util.open_in_editor(sc.path) + + +def ensure_version(config: Config, sql: bool = False) -> None: + """Create the alembic version table if it doesn't exist already . + + :param config: a :class:`.Config` instance. + + :param sql: use ``--sql`` mode. + + .. versionadded:: 1.7.6 + + """ + + script = ScriptDirectory.from_config(config) + + def do_ensure_version(rev, context): + context._ensure_version_table() + return [] + + with EnvironmentContext( + config, + script, + fn=do_ensure_version, + as_sql=sql, + ): + script.run_env() diff --git a/venv/lib/python3.8/site-packages/alembic/config.py b/venv/lib/python3.8/site-packages/alembic/config.py new file mode 100644 index 000000000..2c52e7cd1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/config.py @@ -0,0 +1,640 @@ +from __future__ import annotations + +from argparse import ArgumentParser +from argparse import Namespace +from configparser import ConfigParser +import inspect +import os +import sys +from typing import Any +from typing import cast +from typing import Dict +from typing import Mapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import TextIO +from typing import Union + +from typing_extensions import TypedDict + +from . import __version__ +from . import command +from . import util +from .util import compat + + +class Config: + r"""Represent an Alembic configuration. + + Within an ``env.py`` script, this is available + via the :attr:`.EnvironmentContext.config` attribute, + which in turn is available at ``alembic.context``:: + + from alembic import context + + some_param = context.config.get_main_option("my option") + + When invoking Alembic programmatically, a new + :class:`.Config` can be created by passing + the name of an .ini file to the constructor:: + + from alembic.config import Config + alembic_cfg = Config("/path/to/yourapp/alembic.ini") + + With a :class:`.Config` object, you can then + run Alembic commands programmatically using the directives + in :mod:`alembic.command`. + + The :class:`.Config` object can also be constructed without + a filename. Values can be set programmatically, and + new sections will be created as needed:: + + from alembic.config import Config + alembic_cfg = Config() + alembic_cfg.set_main_option("script_location", "myapp:migrations") + alembic_cfg.set_main_option("sqlalchemy.url", "postgresql://foo/bar") + alembic_cfg.set_section_option("mysection", "foo", "bar") + + .. warning:: + + When using programmatic configuration, make sure the + ``env.py`` file in use is compatible with the target configuration; + including that the call to Python ``logging.fileConfig()`` is + omitted if the programmatic configuration doesn't actually include + logging directives. + + For passing non-string values to environments, such as connections and + engines, use the :attr:`.Config.attributes` dictionary:: + + with engine.begin() as connection: + alembic_cfg.attributes['connection'] = connection + command.upgrade(alembic_cfg, "head") + + :param file\_: name of the .ini file to open. + :param ini_section: name of the main Alembic section within the + .ini file + :param output_buffer: optional file-like input buffer which + will be passed to the :class:`.MigrationContext` - used to redirect + the output of "offline generation" when using Alembic programmatically. + :param stdout: buffer where the "print" output of commands will be sent. + Defaults to ``sys.stdout``. + + :param config_args: A dictionary of keys and values that will be used + for substitution in the alembic config file. The dictionary as given + is **copied** to a new one, stored locally as the attribute + ``.config_args``. When the :attr:`.Config.file_config` attribute is + first invoked, the replacement variable ``here`` will be added to this + dictionary before the dictionary is passed to ``ConfigParser()`` + to parse the .ini file. + + :param attributes: optional dictionary of arbitrary Python keys/values, + which will be populated into the :attr:`.Config.attributes` dictionary. + + .. seealso:: + + :ref:`connection_sharing` + + """ + + def __init__( + self, + file_: Union[str, os.PathLike[str], None] = None, + ini_section: str = "alembic", + output_buffer: Optional[TextIO] = None, + stdout: TextIO = sys.stdout, + cmd_opts: Optional[Namespace] = None, + config_args: Mapping[str, Any] = util.immutabledict(), + attributes: Optional[Dict[str, Any]] = None, + ) -> None: + """Construct a new :class:`.Config`""" + self.config_file_name = file_ + self.config_ini_section = ini_section + self.output_buffer = output_buffer + self.stdout = stdout + self.cmd_opts = cmd_opts + self.config_args = dict(config_args) + if attributes: + self.attributes.update(attributes) + + cmd_opts: Optional[Namespace] = None + """The command-line options passed to the ``alembic`` script. + + Within an ``env.py`` script this can be accessed via the + :attr:`.EnvironmentContext.config` attribute. + + .. seealso:: + + :meth:`.EnvironmentContext.get_x_argument` + + """ + + config_file_name: Union[str, os.PathLike[str], None] = None + """Filesystem path to the .ini file in use.""" + + config_ini_section: str = None # type:ignore[assignment] + """Name of the config file section to read basic configuration + from. Defaults to ``alembic``, that is the ``[alembic]`` section + of the .ini file. This value is modified using the ``-n/--name`` + option to the Alembic runner. + + """ + + @util.memoized_property + def attributes(self) -> Dict[str, Any]: + """A Python dictionary for storage of additional state. + + + This is a utility dictionary which can include not just strings but + engines, connections, schema objects, or anything else. + Use this to pass objects into an env.py script, such as passing + a :class:`sqlalchemy.engine.base.Connection` when calling + commands from :mod:`alembic.command` programmatically. + + .. seealso:: + + :ref:`connection_sharing` + + :paramref:`.Config.attributes` + + """ + return {} + + def print_stdout(self, text: str, *arg: Any) -> None: + """Render a message to standard out. + + When :meth:`.Config.print_stdout` is called with additional args + those arguments will formatted against the provided text, + otherwise we simply output the provided text verbatim. + + This is a no-op when the``quiet`` messaging option is enabled. + + e.g.:: + + >>> config.print_stdout('Some text %s', 'arg') + Some Text arg + + """ + + if arg: + output = str(text) % arg + else: + output = str(text) + + util.write_outstream(self.stdout, output, "\n", **self.messaging_opts) + + @util.memoized_property + def file_config(self) -> ConfigParser: + """Return the underlying ``ConfigParser`` object. + + Direct access to the .ini file is available here, + though the :meth:`.Config.get_section` and + :meth:`.Config.get_main_option` + methods provide a possibly simpler interface. + + """ + + if self.config_file_name: + here = os.path.abspath(os.path.dirname(self.config_file_name)) + else: + here = "" + self.config_args["here"] = here + file_config = ConfigParser(self.config_args) + if self.config_file_name: + compat.read_config_parser(file_config, [self.config_file_name]) + else: + file_config.add_section(self.config_ini_section) + return file_config + + def get_template_directory(self) -> str: + """Return the directory where Alembic setup templates are found. + + This method is used by the alembic ``init`` and ``list_templates`` + commands. + + """ + import alembic + + package_dir = os.path.abspath(os.path.dirname(alembic.__file__)) + return os.path.join(package_dir, "templates") + + @overload + def get_section( + self, name: str, default: None = ... + ) -> Optional[Dict[str, str]]: ... + + # "default" here could also be a TypeVar + # _MT = TypeVar("_MT", bound=Mapping[str, str]), + # however mypy wasn't handling that correctly (pyright was) + @overload + def get_section( + self, name: str, default: Dict[str, str] + ) -> Dict[str, str]: ... + + @overload + def get_section( + self, name: str, default: Mapping[str, str] + ) -> Union[Dict[str, str], Mapping[str, str]]: ... + + def get_section( + self, name: str, default: Optional[Mapping[str, str]] = None + ) -> Optional[Mapping[str, str]]: + """Return all the configuration options from a given .ini file section + as a dictionary. + + If the given section does not exist, the value of ``default`` + is returned, which is expected to be a dictionary or other mapping. + + """ + if not self.file_config.has_section(name): + return default + + return dict(self.file_config.items(name)) + + def set_main_option(self, name: str, value: str) -> None: + """Set an option programmatically within the 'main' section. + + This overrides whatever was in the .ini file. + + :param name: name of the value + + :param value: the value. Note that this value is passed to + ``ConfigParser.set``, which supports variable interpolation using + pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of + an interpolation symbol must therefore be escaped, e.g. ``%%``. + The given value may refer to another value already in the file + using the interpolation format. + + """ + self.set_section_option(self.config_ini_section, name, value) + + def remove_main_option(self, name: str) -> None: + self.file_config.remove_option(self.config_ini_section, name) + + def set_section_option(self, section: str, name: str, value: str) -> None: + """Set an option programmatically within the given section. + + The section is created if it doesn't exist already. + The value here will override whatever was in the .ini + file. + + :param section: name of the section + + :param name: name of the value + + :param value: the value. Note that this value is passed to + ``ConfigParser.set``, which supports variable interpolation using + pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of + an interpolation symbol must therefore be escaped, e.g. ``%%``. + The given value may refer to another value already in the file + using the interpolation format. + + """ + + if not self.file_config.has_section(section): + self.file_config.add_section(section) + self.file_config.set(section, name, value) + + def get_section_option( + self, section: str, name: str, default: Optional[str] = None + ) -> Optional[str]: + """Return an option from the given section of the .ini file.""" + if not self.file_config.has_section(section): + raise util.CommandError( + "No config file %r found, or file has no " + "'[%s]' section" % (self.config_file_name, section) + ) + if self.file_config.has_option(section, name): + return self.file_config.get(section, name) + else: + return default + + @overload + def get_main_option(self, name: str, default: str) -> str: ... + + @overload + def get_main_option( + self, name: str, default: Optional[str] = None + ) -> Optional[str]: ... + + def get_main_option( + self, name: str, default: Optional[str] = None + ) -> Optional[str]: + """Return an option from the 'main' section of the .ini file. + + This defaults to being a key from the ``[alembic]`` + section, unless the ``-n/--name`` flag were used to + indicate a different section. + + """ + return self.get_section_option(self.config_ini_section, name, default) + + @util.memoized_property + def messaging_opts(self) -> MessagingOptions: + """The messaging options.""" + return cast( + MessagingOptions, + util.immutabledict( + {"quiet": getattr(self.cmd_opts, "quiet", False)} + ), + ) + + +class MessagingOptions(TypedDict, total=False): + quiet: bool + + +class CommandLine: + def __init__(self, prog: Optional[str] = None) -> None: + self._generate_args(prog) + + def _generate_args(self, prog: Optional[str]) -> None: + def add_options( + fn: Any, parser: Any, positional: Any, kwargs: Any + ) -> None: + kwargs_opts = { + "template": ( + "-t", + "--template", + dict( + default="generic", + type=str, + help="Setup template for use with 'init'", + ), + ), + "message": ( + "-m", + "--message", + dict( + type=str, help="Message string to use with 'revision'" + ), + ), + "sql": ( + "--sql", + dict( + action="store_true", + help="Don't emit SQL to database - dump to " + "standard output/file instead. See docs on " + "offline mode.", + ), + ), + "tag": ( + "--tag", + dict( + type=str, + help="Arbitrary 'tag' name - can be used by " + "custom env.py scripts.", + ), + ), + "head": ( + "--head", + dict( + type=str, + help="Specify head revision or @head " + "to base new revision on.", + ), + ), + "splice": ( + "--splice", + dict( + action="store_true", + help="Allow a non-head revision as the " + "'head' to splice onto", + ), + ), + "depends_on": ( + "--depends-on", + dict( + action="append", + help="Specify one or more revision identifiers " + "which this revision should depend on.", + ), + ), + "rev_id": ( + "--rev-id", + dict( + type=str, + help="Specify a hardcoded revision id instead of " + "generating one", + ), + ), + "version_path": ( + "--version-path", + dict( + type=str, + help="Specify specific path from config for " + "version file", + ), + ), + "branch_label": ( + "--branch-label", + dict( + type=str, + help="Specify a branch label to apply to the " + "new revision", + ), + ), + "verbose": ( + "-v", + "--verbose", + dict(action="store_true", help="Use more verbose output"), + ), + "resolve_dependencies": ( + "--resolve-dependencies", + dict( + action="store_true", + help="Treat dependency versions as down revisions", + ), + ), + "autogenerate": ( + "--autogenerate", + dict( + action="store_true", + help="Populate revision script with candidate " + "migration operations, based on comparison " + "of database to model.", + ), + ), + "rev_range": ( + "-r", + "--rev-range", + dict( + action="store", + help="Specify a revision range; " + "format is [start]:[end]", + ), + ), + "indicate_current": ( + "-i", + "--indicate-current", + dict( + action="store_true", + help="Indicate the current revision", + ), + ), + "purge": ( + "--purge", + dict( + action="store_true", + help="Unconditionally erase the version table " + "before stamping", + ), + ), + "package": ( + "--package", + dict( + action="store_true", + help="Write empty __init__.py files to the " + "environment and version locations", + ), + ), + } + positional_help = { + "directory": "location of scripts directory", + "revision": "revision identifier", + "revisions": "one or more revisions, or 'heads' for all heads", + } + for arg in kwargs: + if arg in kwargs_opts: + args = kwargs_opts[arg] + args, kw = args[0:-1], args[-1] + parser.add_argument(*args, **kw) + + for arg in positional: + if ( + arg == "revisions" + or fn in positional_translations + and positional_translations[fn][arg] == "revisions" + ): + subparser.add_argument( + "revisions", + nargs="+", + help=positional_help.get("revisions"), + ) + else: + subparser.add_argument(arg, help=positional_help.get(arg)) + + parser = ArgumentParser(prog=prog) + + parser.add_argument( + "--version", action="version", version="%%(prog)s %s" % __version__ + ) + parser.add_argument( + "-c", + "--config", + type=str, + default=os.environ.get("ALEMBIC_CONFIG", "alembic.ini"), + help="Alternate config file; defaults to value of " + 'ALEMBIC_CONFIG environment variable, or "alembic.ini"', + ) + parser.add_argument( + "-n", + "--name", + type=str, + default="alembic", + help="Name of section in .ini file to " "use for Alembic config", + ) + parser.add_argument( + "-x", + action="append", + help="Additional arguments consumed by " + "custom env.py scripts, e.g. -x " + "setting1=somesetting -x setting2=somesetting", + ) + parser.add_argument( + "--raiseerr", + action="store_true", + help="Raise a full stack trace on error", + ) + parser.add_argument( + "-q", + "--quiet", + action="store_true", + help="Do not log to std output.", + ) + subparsers = parser.add_subparsers() + + positional_translations: Dict[Any, Any] = { + command.stamp: {"revision": "revisions"} + } + + for fn in [getattr(command, n) for n in dir(command)]: + if ( + inspect.isfunction(fn) + and fn.__name__[0] != "_" + and fn.__module__ == "alembic.command" + ): + spec = compat.inspect_getfullargspec(fn) + if spec[3] is not None: + positional = spec[0][1 : -len(spec[3])] + kwarg = spec[0][-len(spec[3]) :] + else: + positional = spec[0][1:] + kwarg = [] + + if fn in positional_translations: + positional = [ + positional_translations[fn].get(name, name) + for name in positional + ] + + # parse first line(s) of helptext without a line break + help_ = fn.__doc__ + if help_: + help_text = [] + for line in help_.split("\n"): + if not line.strip(): + break + else: + help_text.append(line.strip()) + else: + help_text = [] + subparser = subparsers.add_parser( + fn.__name__, help=" ".join(help_text) + ) + add_options(fn, subparser, positional, kwarg) + subparser.set_defaults(cmd=(fn, positional, kwarg)) + self.parser = parser + + def run_cmd(self, config: Config, options: Namespace) -> None: + fn, positional, kwarg = options.cmd + + try: + fn( + config, + *[getattr(options, k, None) for k in positional], + **{k: getattr(options, k, None) for k in kwarg}, + ) + except util.CommandError as e: + if options.raiseerr: + raise + else: + util.err(str(e), **config.messaging_opts) + + def main(self, argv: Optional[Sequence[str]] = None) -> None: + options = self.parser.parse_args(argv) + if not hasattr(options, "cmd"): + # see http://bugs.python.org/issue9253, argparse + # behavior changed incompatibly in py3.3 + self.parser.error("too few arguments") + else: + cfg = Config( + file_=options.config, + ini_section=options.name, + cmd_opts=options, + ) + self.run_cmd(cfg, options) + + +def main( + argv: Optional[Sequence[str]] = None, + prog: Optional[str] = None, + **kwargs: Any, +) -> None: + """The console runner function for Alembic.""" + + CommandLine(prog=prog).main(argv=argv) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.8/site-packages/alembic/context.py b/venv/lib/python3.8/site-packages/alembic/context.py new file mode 100644 index 000000000..758fca875 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/context.py @@ -0,0 +1,5 @@ +from .runtime.environment import EnvironmentContext + +# create proxy functions for +# each method on the EnvironmentContext class. +EnvironmentContext.create_module_class_proxy(globals(), locals()) diff --git a/venv/lib/python3.8/site-packages/alembic/context.pyi b/venv/lib/python3.8/site-packages/alembic/context.pyi new file mode 100644 index 000000000..80619fb24 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/context.pyi @@ -0,0 +1,853 @@ +# ### this file stubs are generated by tools/write_pyi.py - do not edit ### +# ### imports are manually managed +from __future__ import annotations + +from typing import Any +from typing import Callable +from typing import Collection +from typing import ContextManager +from typing import Dict +from typing import Iterable +from typing import List +from typing import Literal +from typing import Mapping +from typing import MutableMapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import TextIO +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +if TYPE_CHECKING: + from sqlalchemy.engine.base import Connection + from sqlalchemy.engine.url import URL + from sqlalchemy.sql import Executable + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import FetchedValue + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.type_api import TypeEngine + + from .autogenerate.api import AutogenContext + from .config import Config + from .operations.ops import MigrationScript + from .runtime.migration import _ProxyTransaction + from .runtime.migration import MigrationContext + from .runtime.migration import MigrationInfo + from .script import ScriptDirectory + +### end imports ### + +def begin_transaction() -> Union[_ProxyTransaction, ContextManager[None]]: + """Return a context manager that will + enclose an operation within a "transaction", + as defined by the environment's offline + and transactional DDL settings. + + e.g.:: + + with context.begin_transaction(): + context.run_migrations() + + :meth:`.begin_transaction` is intended to + "do the right thing" regardless of + calling context: + + * If :meth:`.is_transactional_ddl` is ``False``, + returns a "do nothing" context manager + which otherwise produces no transactional + state or directives. + * If :meth:`.is_offline_mode` is ``True``, + returns a context manager that will + invoke the :meth:`.DefaultImpl.emit_begin` + and :meth:`.DefaultImpl.emit_commit` + methods, which will produce the string + directives ``BEGIN`` and ``COMMIT`` on + the output stream, as rendered by the + target backend (e.g. SQL Server would + emit ``BEGIN TRANSACTION``). + * Otherwise, calls :meth:`sqlalchemy.engine.Connection.begin` + on the current online connection, which + returns a :class:`sqlalchemy.engine.Transaction` + object. This object demarcates a real + transaction and is itself a context manager, + which will roll back if an exception + is raised. + + Note that a custom ``env.py`` script which + has more specific transactional needs can of course + manipulate the :class:`~sqlalchemy.engine.Connection` + directly to produce transactional state in "online" + mode. + + """ + +config: Config + +def configure( + connection: Optional[Connection] = None, + url: Union[str, URL, None] = None, + dialect_name: Optional[str] = None, + dialect_opts: Optional[Dict[str, Any]] = None, + transactional_ddl: Optional[bool] = None, + transaction_per_migration: bool = False, + output_buffer: Optional[TextIO] = None, + starting_rev: Optional[str] = None, + tag: Optional[str] = None, + template_args: Optional[Dict[str, Any]] = None, + render_as_batch: bool = False, + target_metadata: Union[MetaData, Sequence[MetaData], None] = None, + include_name: Optional[ + Callable[ + [ + Optional[str], + Literal[ + "schema", + "table", + "column", + "index", + "unique_constraint", + "foreign_key_constraint", + ], + MutableMapping[ + Literal[ + "schema_name", + "table_name", + "schema_qualified_table_name", + ], + Optional[str], + ], + ], + bool, + ] + ] = None, + include_object: Optional[ + Callable[ + [ + SchemaItem, + Optional[str], + Literal[ + "schema", + "table", + "column", + "index", + "unique_constraint", + "foreign_key_constraint", + ], + bool, + Optional[SchemaItem], + ], + bool, + ] + ] = None, + include_schemas: bool = False, + process_revision_directives: Optional[ + Callable[ + [ + MigrationContext, + Union[str, Iterable[Optional[str]], Iterable[str]], + List[MigrationScript], + ], + None, + ] + ] = None, + compare_type: Union[ + bool, + Callable[ + [ + MigrationContext, + Column[Any], + Column[Any], + TypeEngine[Any], + TypeEngine[Any], + ], + Optional[bool], + ], + ] = True, + compare_server_default: Union[ + bool, + Callable[ + [ + MigrationContext, + Column[Any], + Column[Any], + Optional[str], + Optional[FetchedValue], + Optional[str], + ], + Optional[bool], + ], + ] = False, + render_item: Optional[ + Callable[[str, Any, AutogenContext], Union[str, Literal[False]]] + ] = None, + literal_binds: bool = False, + upgrade_token: str = "upgrades", + downgrade_token: str = "downgrades", + alembic_module_prefix: str = "op.", + sqlalchemy_module_prefix: str = "sa.", + user_module_prefix: Optional[str] = None, + on_version_apply: Optional[ + Callable[ + [ + MigrationContext, + MigrationInfo, + Collection[Any], + Mapping[str, Any], + ], + None, + ] + ] = None, + **kw: Any, +) -> None: + """Configure a :class:`.MigrationContext` within this + :class:`.EnvironmentContext` which will provide database + connectivity and other configuration to a series of + migration scripts. + + Many methods on :class:`.EnvironmentContext` require that + this method has been called in order to function, as they + ultimately need to have database access or at least access + to the dialect in use. Those which do are documented as such. + + The important thing needed by :meth:`.configure` is a + means to determine what kind of database dialect is in use. + An actual connection to that database is needed only if + the :class:`.MigrationContext` is to be used in + "online" mode. + + If the :meth:`.is_offline_mode` function returns ``True``, + then no connection is needed here. Otherwise, the + ``connection`` parameter should be present as an + instance of :class:`sqlalchemy.engine.Connection`. + + This function is typically called from the ``env.py`` + script within a migration environment. It can be called + multiple times for an invocation. The most recent + :class:`~sqlalchemy.engine.Connection` + for which it was called is the one that will be operated upon + by the next call to :meth:`.run_migrations`. + + General parameters: + + :param connection: a :class:`~sqlalchemy.engine.Connection` + to use + for SQL execution in "online" mode. When present, is also + used to determine the type of dialect in use. + :param url: a string database url, or a + :class:`sqlalchemy.engine.url.URL` object. + The type of dialect to be used will be derived from this if + ``connection`` is not passed. + :param dialect_name: string name of a dialect, such as + "postgresql", "mssql", etc. + The type of dialect to be used will be derived from this if + ``connection`` and ``url`` are not passed. + :param dialect_opts: dictionary of options to be passed to dialect + constructor. + :param transactional_ddl: Force the usage of "transactional" + DDL on or off; + this otherwise defaults to whether or not the dialect in + use supports it. + :param transaction_per_migration: if True, nest each migration script + in a transaction rather than the full series of migrations to + run. + :param output_buffer: a file-like object that will be used + for textual output + when the ``--sql`` option is used to generate SQL scripts. + Defaults to + ``sys.stdout`` if not passed here and also not present on + the :class:`.Config` + object. The value here overrides that of the :class:`.Config` + object. + :param output_encoding: when using ``--sql`` to generate SQL + scripts, apply this encoding to the string output. + :param literal_binds: when using ``--sql`` to generate SQL + scripts, pass through the ``literal_binds`` flag to the compiler + so that any literal values that would ordinarily be bound + parameters are converted to plain strings. + + .. warning:: Dialects can typically only handle simple datatypes + like strings and numbers for auto-literal generation. Datatypes + like dates, intervals, and others may still require manual + formatting, typically using :meth:`.Operations.inline_literal`. + + .. note:: the ``literal_binds`` flag is ignored on SQLAlchemy + versions prior to 0.8 where this feature is not supported. + + .. seealso:: + + :meth:`.Operations.inline_literal` + + :param starting_rev: Override the "starting revision" argument + when using ``--sql`` mode. + :param tag: a string tag for usage by custom ``env.py`` scripts. + Set via the ``--tag`` option, can be overridden here. + :param template_args: dictionary of template arguments which + will be added to the template argument environment when + running the "revision" command. Note that the script environment + is only run within the "revision" command if the --autogenerate + option is used, or if the option "revision_environment=true" + is present in the alembic.ini file. + + :param version_table: The name of the Alembic version table. + The default is ``'alembic_version'``. + :param version_table_schema: Optional schema to place version + table within. + :param version_table_pk: boolean, whether the Alembic version table + should use a primary key constraint for the "value" column; this + only takes effect when the table is first created. + Defaults to True; setting to False should not be necessary and is + here for backwards compatibility reasons. + :param on_version_apply: a callable or collection of callables to be + run for each migration step. + The callables will be run in the order they are given, once for + each migration step, after the respective operation has been + applied but before its transaction is finalized. + Each callable accepts no positional arguments and the following + keyword arguments: + + * ``ctx``: the :class:`.MigrationContext` running the migration, + * ``step``: a :class:`.MigrationInfo` representing the + step currently being applied, + * ``heads``: a collection of version strings representing the + current heads, + * ``run_args``: the ``**kwargs`` passed to :meth:`.run_migrations`. + + Parameters specific to the autogenerate feature, when + ``alembic revision`` is run with the ``--autogenerate`` feature: + + :param target_metadata: a :class:`sqlalchemy.schema.MetaData` + object, or a sequence of :class:`~sqlalchemy.schema.MetaData` + objects, that will be consulted during autogeneration. + The tables present in each :class:`~sqlalchemy.schema.MetaData` + will be compared against + what is locally available on the target + :class:`~sqlalchemy.engine.Connection` + to produce candidate upgrade/downgrade operations. + :param compare_type: Indicates type comparison behavior during + an autogenerate + operation. Defaults to ``True`` turning on type comparison, which + has good accuracy on most backends. See :ref:`compare_types` + for an example as well as information on other type + comparison options. Set to ``False`` which disables type + comparison. A callable can also be passed to provide custom type + comparison, see :ref:`compare_types` for additional details. + + .. versionchanged:: 1.12.0 The default value of + :paramref:`.EnvironmentContext.configure.compare_type` has been + changed to ``True``. + + .. seealso:: + + :ref:`compare_types` + + :paramref:`.EnvironmentContext.configure.compare_server_default` + + :param compare_server_default: Indicates server default comparison + behavior during + an autogenerate operation. Defaults to ``False`` which disables + server default + comparison. Set to ``True`` to turn on server default comparison, + which has + varied accuracy depending on backend. + + To customize server default comparison behavior, a callable may + be specified + which can filter server default comparisons during an + autogenerate operation. + defaults during an autogenerate operation. The format of this + callable is:: + + def my_compare_server_default(context, inspected_column, + metadata_column, inspected_default, metadata_default, + rendered_metadata_default): + # return True if the defaults are different, + # False if not, or None to allow the default implementation + # to compare these defaults + return None + + context.configure( + # ... + compare_server_default = my_compare_server_default + ) + + ``inspected_column`` is a dictionary structure as returned by + :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas + ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from + the local model environment. + + A return value of ``None`` indicates to allow default server default + comparison + to proceed. Note that some backends such as Postgresql actually + execute + the two defaults on the database side to compare for equivalence. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.compare_type` + + :param include_name: A callable function which is given + the chance to return ``True`` or ``False`` for any database reflected + object based on its name, including database schema names when + the :paramref:`.EnvironmentContext.configure.include_schemas` flag + is set to ``True``. + + The function accepts the following positional arguments: + + * ``name``: the name of the object, such as schema name or table name. + Will be ``None`` when indicating the default schema name of the + database connection. + * ``type``: a string describing the type of object; currently + ``"schema"``, ``"table"``, ``"column"``, ``"index"``, + ``"unique_constraint"``, or ``"foreign_key_constraint"`` + * ``parent_names``: a dictionary of "parent" object names, that are + relative to the name being given. Keys in this dictionary may + include: ``"schema_name"``, ``"table_name"`` or + ``"schema_qualified_table_name"``. + + E.g.:: + + def include_name(name, type_, parent_names): + if type_ == "schema": + return name in ["schema_one", "schema_two"] + else: + return True + + context.configure( + # ... + include_schemas = True, + include_name = include_name + ) + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_object` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + + :param include_object: A callable function which is given + the chance to return ``True`` or ``False`` for any object, + indicating if the given object should be considered in the + autogenerate sweep. + + The function accepts the following positional arguments: + + * ``object``: a :class:`~sqlalchemy.schema.SchemaItem` object such + as a :class:`~sqlalchemy.schema.Table`, + :class:`~sqlalchemy.schema.Column`, + :class:`~sqlalchemy.schema.Index` + :class:`~sqlalchemy.schema.UniqueConstraint`, + or :class:`~sqlalchemy.schema.ForeignKeyConstraint` object + * ``name``: the name of the object. This is typically available + via ``object.name``. + * ``type``: a string describing the type of object; currently + ``"table"``, ``"column"``, ``"index"``, ``"unique_constraint"``, + or ``"foreign_key_constraint"`` + * ``reflected``: ``True`` if the given object was produced based on + table reflection, ``False`` if it's from a local :class:`.MetaData` + object. + * ``compare_to``: the object being compared against, if available, + else ``None``. + + E.g.:: + + def include_object(object, name, type_, reflected, compare_to): + if (type_ == "column" and + not reflected and + object.info.get("skip_autogenerate", False)): + return False + else: + return True + + context.configure( + # ... + include_object = include_object + ) + + For the use case of omitting specific schemas from a target database + when :paramref:`.EnvironmentContext.configure.include_schemas` is + set to ``True``, the :attr:`~sqlalchemy.schema.Table.schema` + attribute can be checked for each :class:`~sqlalchemy.schema.Table` + object passed to the hook, however it is much more efficient + to filter on schemas before reflection of objects takes place + using the :paramref:`.EnvironmentContext.configure.include_name` + hook. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + :param render_as_batch: if True, commands which alter elements + within a table will be placed under a ``with batch_alter_table():`` + directive, so that batch migrations will take place. + + .. seealso:: + + :ref:`batch_migrations` + + :param include_schemas: If True, autogenerate will scan across + all schemas located by the SQLAlchemy + :meth:`~sqlalchemy.engine.reflection.Inspector.get_schema_names` + method, and include all differences in tables found across all + those schemas. When using this option, you may want to also + use the :paramref:`.EnvironmentContext.configure.include_name` + parameter to specify a callable which + can filter the tables/schemas that get included. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_object` + + :param render_item: Callable that can be used to override how + any schema item, i.e. column, constraint, type, + etc., is rendered for autogenerate. The callable receives a + string describing the type of object, the object, and + the autogen context. If it returns False, the + default rendering method will be used. If it returns None, + the item will not be rendered in the context of a Table + construct, that is, can be used to skip columns or constraints + within op.create_table():: + + def my_render_column(type_, col, autogen_context): + if type_ == "column" and isinstance(col, MySpecialCol): + return repr(col) + else: + return False + + context.configure( + # ... + render_item = my_render_column + ) + + Available values for the type string include: ``"column"``, + ``"primary_key"``, ``"foreign_key"``, ``"unique"``, ``"check"``, + ``"type"``, ``"server_default"``. + + .. seealso:: + + :ref:`autogen_render_types` + + :param upgrade_token: When autogenerate completes, the text of the + candidate upgrade operations will be present in this template + variable when ``script.py.mako`` is rendered. Defaults to + ``upgrades``. + :param downgrade_token: When autogenerate completes, the text of the + candidate downgrade operations will be present in this + template variable when ``script.py.mako`` is rendered. Defaults to + ``downgrades``. + + :param alembic_module_prefix: When autogenerate refers to Alembic + :mod:`alembic.operations` constructs, this prefix will be used + (i.e. ``op.create_table``) Defaults to "``op.``". + Can be ``None`` to indicate no prefix. + + :param sqlalchemy_module_prefix: When autogenerate refers to + SQLAlchemy + :class:`~sqlalchemy.schema.Column` or type classes, this prefix + will be used + (i.e. ``sa.Column("somename", sa.Integer)``) Defaults to "``sa.``". + Can be ``None`` to indicate no prefix. + Note that when dialect-specific types are rendered, autogenerate + will render them using the dialect module name, i.e. ``mssql.BIT()``, + ``postgresql.UUID()``. + + :param user_module_prefix: When autogenerate refers to a SQLAlchemy + type (e.g. :class:`.TypeEngine`) where the module name is not + under the ``sqlalchemy`` namespace, this prefix will be used + within autogenerate. If left at its default of + ``None``, the ``__module__`` attribute of the type is used to + render the import module. It's a good practice to set this + and to have all custom types be available from a fixed module space, + in order to future-proof migration files against reorganizations + in modules. + + .. seealso:: + + :ref:`autogen_module_prefix` + + :param process_revision_directives: a callable function that will + be passed a structure representing the end result of an autogenerate + or plain "revision" operation, which can be manipulated to affect + how the ``alembic revision`` command ultimately outputs new + revision scripts. The structure of the callable is:: + + def process_revision_directives(context, revision, directives): + pass + + The ``directives`` parameter is a Python list containing + a single :class:`.MigrationScript` directive, which represents + the revision file to be generated. This list as well as its + contents may be freely modified to produce any set of commands. + The section :ref:`customizing_revision` shows an example of + doing this. The ``context`` parameter is the + :class:`.MigrationContext` in use, + and ``revision`` is a tuple of revision identifiers representing the + current revision of the database. + + The callable is invoked at all times when the ``--autogenerate`` + option is passed to ``alembic revision``. If ``--autogenerate`` + is not passed, the callable is invoked only if the + ``revision_environment`` variable is set to True in the Alembic + configuration, in which case the given ``directives`` collection + will contain empty :class:`.UpgradeOps` and :class:`.DowngradeOps` + collections for ``.upgrade_ops`` and ``.downgrade_ops``. The + ``--autogenerate`` option itself can be inferred by inspecting + ``context.config.cmd_opts.autogenerate``. + + The callable function may optionally be an instance of + a :class:`.Rewriter` object. This is a helper object that + assists in the production of autogenerate-stream rewriter functions. + + .. seealso:: + + :ref:`customizing_revision` + + :ref:`autogen_rewriter` + + :paramref:`.command.revision.process_revision_directives` + + Parameters specific to individual backends: + + :param mssql_batch_separator: The "batch separator" which will + be placed between each statement when generating offline SQL Server + migrations. Defaults to ``GO``. Note this is in addition to the + customary semicolon ``;`` at the end of each statement; SQL Server + considers the "batch separator" to denote the end of an + individual statement execution, and cannot group certain + dependent operations in one step. + :param oracle_batch_separator: The "batch separator" which will + be placed between each statement when generating offline + Oracle migrations. Defaults to ``/``. Oracle doesn't add a + semicolon between statements like most other backends. + + """ + +def execute( + sql: Union[Executable, str], + execution_options: Optional[Dict[str, Any]] = None, +) -> None: + """Execute the given SQL using the current change context. + + The behavior of :meth:`.execute` is the same + as that of :meth:`.Operations.execute`. Please see that + function's documentation for full detail including + caveats and limitations. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + +def get_bind() -> Connection: + """Return the current 'bind'. + + In "online" mode, this is the + :class:`sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + +def get_context() -> MigrationContext: + """Return the current :class:`.MigrationContext` object. + + If :meth:`.EnvironmentContext.configure` has not been + called yet, raises an exception. + + """ + +def get_head_revision() -> Union[str, Tuple[str, ...], None]: + """Return the hex identifier of the 'head' script revision. + + If the script directory has multiple heads, this + method raises a :class:`.CommandError`; + :meth:`.EnvironmentContext.get_head_revisions` should be preferred. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: :meth:`.EnvironmentContext.get_head_revisions` + + """ + +def get_head_revisions() -> Union[str, Tuple[str, ...], None]: + """Return the hex identifier of the 'heads' script revision(s). + + This returns a tuple containing the version number of all + heads in the script directory. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def get_revision_argument() -> Union[str, Tuple[str, ...], None]: + """Get the 'destination' revision argument. + + This is typically the argument passed to the + ``upgrade`` or ``downgrade`` command. + + If it was specified as ``head``, the actual + version number is returned; if specified + as ``base``, ``None`` is returned. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def get_starting_revision_argument() -> Union[str, Tuple[str, ...], None]: + """Return the 'starting revision' argument, + if the revision was passed using ``start:end``. + + This is only meaningful in "offline" mode. + Returns ``None`` if no value is available + or was configured. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def get_tag_argument() -> Optional[str]: + """Return the value passed for the ``--tag`` argument, if any. + + The ``--tag`` argument is not used directly by Alembic, + but is available for custom ``env.py`` configurations that + wish to use it; particularly for offline generation scripts + that wish to generate tagged filenames. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_x_argument` - a newer and more + open ended system of extending ``env.py`` scripts via the command + line. + + """ + +@overload +def get_x_argument(as_dictionary: Literal[False]) -> List[str]: ... +@overload +def get_x_argument(as_dictionary: Literal[True]) -> Dict[str, str]: ... +@overload +def get_x_argument( + as_dictionary: bool = ..., +) -> Union[List[str], Dict[str, str]]: + """Return the value(s) passed for the ``-x`` argument, if any. + + The ``-x`` argument is an open ended flag that allows any user-defined + value or values to be passed on the command line, then available + here for consumption by a custom ``env.py`` script. + + The return value is a list, returned directly from the ``argparse`` + structure. If ``as_dictionary=True`` is passed, the ``x`` arguments + are parsed using ``key=value`` format into a dictionary that is + then returned. If there is no ``=`` in the argument, value is an empty + string. + + .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when + arguments are passed without the ``=`` symbol. + + For example, to support passing a database URL on the command line, + the standard ``env.py`` script can be modified like this:: + + cmd_line_url = context.get_x_argument( + as_dictionary=True).get('dbname') + if cmd_line_url: + engine = create_engine(cmd_line_url) + else: + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + This then takes effect by running the ``alembic`` script as:: + + alembic -x dbname=postgresql://user:pass@host/dbname upgrade head + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_tag_argument` + + :attr:`.Config.cmd_opts` + + """ + +def is_offline_mode() -> bool: + """Return True if the current migrations environment + is running in "offline mode". + + This is ``True`` or ``False`` depending + on the ``--sql`` flag passed. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def is_transactional_ddl() -> bool: + """Return True if the context is configured to expect a + transactional DDL capable backend. + + This defaults to the type of database in use, and + can be overridden by the ``transactional_ddl`` argument + to :meth:`.configure` + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + +def run_migrations(**kw: Any) -> None: + """Run migrations as determined by the current command line + configuration + as well as versioning information present (or not) in the current + database connection (if one is present). + + The function accepts optional ``**kw`` arguments. If these are + passed, they are sent directly to the ``upgrade()`` and + ``downgrade()`` + functions within each target revision file. By modifying the + ``script.py.mako`` file so that the ``upgrade()`` and ``downgrade()`` + functions accept arguments, parameters can be passed here so that + contextual information, usually information to identify a particular + database in use, can be passed from a custom ``env.py`` script + to the migration functions. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + +script: ScriptDirectory + +def static_output(text: str) -> None: + """Emit text directly to the "offline" SQL stream. + + Typically this is for emitting comments that + start with --. The statement is not treated + as a SQL execution, no ; or batch separator + is added, etc. + + """ diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__init__.py b/venv/lib/python3.8/site-packages/alembic/ddl/__init__.py new file mode 100644 index 000000000..f2f72b3dd --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/__init__.py @@ -0,0 +1,6 @@ +from . import mssql +from . import mysql +from . import oracle +from . import postgresql +from . import sqlite +from .impl import DefaultImpl as DefaultImpl diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04b29d296c05fd7032a37951fa08878ae7f2c447 GIT binary patch literal 393 zcmYk1zfQw25XR&DSD_NG(INHFg#jUkE=Uy%V(}6s&9${^$FA*03Qxi-@E$xxRwiD7 ziL)sZXZgF|oln+xFZ1~RmcvS0;r@f>&iAj@ zuxrl3JxVQ}7UdKfZ`34SQZJd89A!#5&X@x6Ju-gTAm1l?P!cG-!jXfZYZyQ%EunXG z)3lcA(H&G@=~cBe04;~gp{XMnIzT^A!Q0EoYI#d7x~eojH+!(O5g=Q>rCldzUGtv1 Xx?bvWD(|G~&I0cJ;?`TaCV%G#8BAjm literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/_autogen.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/_autogen.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7d79e2e2d58f3dfa0c805d2eab02a71ae7d7472 GIT binary patch literal 11117 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 DA{g=K literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/base.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcdee833e2cf8f45d539eefb71f1072d889b925c GIT binary patch literal 10711 zcmb_i+jA3Fn(uD4WLcJl5WYaTq`3wYBNJcJP-I?90N$urf_kjo4t=cMf-}ZSQ=W*Y+s-L!MpYk6}QOmHu@9T>s%TAy+?ds$2 za{8R_cD{4djp5;xf+zcjsoL~0Mfo2>x}P|ZONyd8{}NLap$OGh>Z+-#1Z%ctYWN?s zV|CrsnWh7dn{kHYfD>kd;e#Q@Z;7b_$Pr+h!en1FrEkgwwMGy$@moT zlOhj1&-f|eQ{oixQ;fd@{2g%`_-V#Z13x3q0zb?BodNy}aSr%7#?JzOSDXiap6ma@ z8n)ivP{k4Pp12_1e;gCj;)6%}hH9Pz=L|S!BhE$fA#>gZ=PWqqBF-f-!<_TroCN21 z#Q8|fGUq*T^5C3^ICG-FoD1MgfioF#E{iM7c^{lp;N&CD$Kon;ronj!oKxU@@>mm} zig~7g0QzarPY3kRM3Lzi#WnFc-Y=lvb+PbBH$N1Mz;EFF^06+y62AoP622nJP1G{Q zEl_4a`5Jav0_9ht1jp#LfHGQg%geUa-%f0%@sPfr^(CabLy#*#NsH8?^}wCA%WIBB6)&$fDr617 z>C2W|xnqeh%k~=aQj6%a&{(N8tRK|Clyg?Kyk@&s>n*Y#ce4gH3BD?zdCh$&$52=9 zM$sM)TfYUdmMS3iJW*CX0zb6<)vn9*OH5* zef#gPTPo7hrp4CQJ`tvvFvJx@=TF$f$_XPb*EXkrmVJgw`SQ>bJl&!Znh|J zr{>Gc&Q$B6d#Bkr**bHwDlKcdW}QTwR_b}*uKV{dJgDPk8=ck;SYn^O?U0$)A zsWJw@a;-8Yggv!fcC38sAws!SYt-CQ=`=cbbQDTFp|*E*Jedz`OhAP=eL&VImAq7` zRBwtkM0&|fmrD26%67mRD3wIBQYy&_G{^@0@qQce!Ko7z6A`n#P4FImPp{%M1gP2f%uc>}0U@3x7(2(fD zD2O|2Elc`_dIz6P=n`ujMV_L92wd@*>J64k6}#*>uqwrgWHXX|r}6rZOJvpdNSD^# zo_iLTWI_2h6_ZrZx888jYQ?hcuw`!kELGt?@_^pQ%Y7u{%Ku8D^f`*xXrw#^Qsk&1 zI4W`#T!wt{ILQth6sbqu4oiDO9CLok4jeWdjZ_+z?U4xWWWS|$CZaAkoA%Q4-4R|| zu;hJ926=JG_i)nF*{?%>zUOFWBYVTvgPP+a1#v$2c1CCe`Gk_1Hy)83Zn-4*tsmc8 z)RY_&y|H%%A2>M_N-C=+Vu_b&O?#mufgpv9bh)z=iSZ&m@*MTVZEcFS^I%-Y>!eX2 z+SK6hkCHfjQAe{B9@U{xjiOE!svmWa{p5hT>QTBYIShE2Aj|^Ca(eJ|ik~12$jBgG zGIW3csHg5R;*}v1VIP3Vr&?88*T2UmWIYC7@wk^qQDGw1<*Nb_;+q{nR8PD6%~l5g z6?pz&kLA?%c&`?bmA5zVEWTf&HfXX+s*>=Z&8R6~92%Q!P`L-K_uG?~YNekrCe4)n z#&oqOvGv$fnp-hU5dLh^c$Z0MsLPM2_!vdMafvD^z_zk(74(jEcXiZx1*i! z)hQIDff-5mS1{vMd{w9K12Yo-8fN6VX4_Fe?0Ynr4}42rKnEP7&-89{28sRWFz2)G zA^#b=!(7aBe3W7&QMWbceK37<4#1paJvzLCML)&oYt%$wQNn*Vi;mLfg*F*IQn6** zxo92^EBQ09oF@r5J^;`Y#~Z`@>S}(vwl+o#|;^M@EfxGOFH{Q8_lRT z4utCT===)4_yP_66*U(4g78=L#j9)<24_SN!okXO$QO|J#+~g!kR!ZAh0hB7wbBt1 zr8}GnrWdCFsl5QN-+B_nm-rCfbP@y+{EoJW^nV1?k91o6=OQg4i(=EZ{c2zVU;jhc z0x#`}@-vz>H7A=b-;%yvWhn7l##{eNsHzoKS-NyQfC`Df6BL;&;b>PYiy z_PL4A|0!%DsVOfdQTpr?47t}{ga*?Q(i86(Xp7o04K{+&{NN2#x_1@b_Imx)*|P$@ zg+BXdccN}Pr2h@fPI3*VqdI)ng|9m;c}D;(jVs{1D1bI7oFruKe18$ z@WIGZD63#DQ=KCKgzEB<3wGSfsxJUXX9Z*N znsL;@fyPjo4z2thzC47s{LSD_90S(NZuicgw&NJ9UsD}gMEfS{>|$sS;^pqwaOzT$ zeE8sh{qLYVNfe5%sTno%Cs_tu*{l$`@+TF-wnF<}^%cTFLOTwHNDWClgD&w?Wf7iO zO3om+iN8tKtS@V)>VKe;FR2ztDnQv@Qp!@>nSM3iEvsBcRm>op#2gYx5W&f+FDD26 zZB*VY>`q_Ut`GizT3ym+|0JuRY%eVxdu%7|22Sc$SLo=iBLp0-ylvS1j#ERWMIXlRgD1Zd0+z4Wqthe%(^I+x$<|bW$k% zs7a~m7qSMdPrm^WId14&W_$gF=EiwYBsMS{0u5tDxq`3S19a3*fi$qmL-G#Td@$e* zg(HIYcf60fO}tIY1a1nHp+kuOLM>kr4yu%zc_HtD)~84`qECRky&}Qbj&Epz5*P-# z&bS8T5AD8S%LNLAhNkTjd*&0;<2DsPstoO~{u{M=lu5miv|s5{Ch`jPu)Q)Ab$sA+ z7XC@Ff1ucy(i4Tgk1`Z-bA`(@H|7^9Ak!R>0pUj+=PwaQ1Lb=UuC~;Aq$eJOozM@Y zcm_ofjdBv(Tx3Q{ouE z-n>t6**Js=kM|Xa4F4)-t>R7^R(>UKTqzU_*Jl zZ3qwJ@Gaf{7`P&!ivb+9wwe-~mHabJQm}bx%v*F>RFJYMx3onxHhpn|RP{%E>@RC| zUDaMn3H)^x|2tpt$&T9400-)|>O;0c!#Gq6Zf$f+7UHG@%W;4THm z5j9*5;Fd(TpvRAyArvSu1CF(O%;3=HQz+Uyt8{gR<1{DVDoYpbW7$Ee-HY>W%l1<` z!bNpxDTfJNan%h?=6(lgLtVfqdZO`O3K{xQa$R|XqxGli9C%OFZ!s@oir3qRxqZGP z3KkNMfjgtJA(}2+&zDG^ny*0g=%JaI7FD~b?-2ki=y56it%{!24fSued$>~Qca>wt<_$aXRXR zwZ^g3@*#N4Uv^14f51{}Fp3IqAP5UDiCaRzxC3ERl8pV5b*#(@nv|6#6!@V;sAERe z)iI5!FjYr0R;qicMR24PnHebc7NCIv?#<-;O zq)-6fI&Uce%?@l;at9(Ak&Zb+TgNfr?_pa+GG^v)s43OV^rq~In7L=ga)Til-awT6 z%Tq}U21%_WC$r!z!zRHS;GZDDDG4e_VdHh<%@B4%r( z3JRCdxIE}CZ_%{{+J{lsswBLp>o8Zb-sFJ(u6OuaM}5n`ppL$yVw0kGK%4FAjd0$g zhjQ}Pp)Ux??(_~(UbqB6AXBcw_ElK_dv#3tHYT(uD(7naRsgwLUDbj-jY};#jZ0Bn zkBlqtqUL%G28$Ql!!vVpMkMod)z~scpP?>yqe~>{EG6hC==Q1lC9a9?b=|V4@)7DF z%I@Qzm+JhS1AgFP!G<_a%)#jut6@E8$%~(mCCIIq)0OuA&bFv4Ux#%u3U_ynLi#aC zty-(_4_V}&QMa<8K1G~~9I z$ry&I#=MD->F&L{hF>Az?p;zu$X}!I5*Tb)JIEMOND)b1GE=w>EZg)Oiif=H;rt+z z#GfJlc!!^dRQYhU%0c4p?Be>rfbf$HoZtH?>gB^JS5jg%vuke6&eQjt0m`lh10p3BB^PLcbuAeP(de0k`Ft| z1PLmtRM4J|_d~po;Po`GC#6FSp0jDH^JkX^L7WgNM{#!WcW4wdU8B1b-pOhIM;aF? z9-O~Kk=BxGn*KB{eYhLb65Ut`YuQ+KXLcw%o=x&!8f6AJu!K4?h<6}(Q}|CbdNT<% X2VO3f8yZRH61l;gK9bDoIsN|t(g@df literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/impl.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/impl.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13eb8aca9c80b6badd22f970bdef9a94b831dced GIT binary patch literal 23982 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/script/__pycache__/revision.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/script/__pycache__/revision.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15b3d9c1f154abbe65dcc1a6dfb353317830f2af GIT binary patch literal 42426 zcmd_Tdz4(qc^`QD_U*^?^gJ*a44wp=AVFYAV8E9s5~L`Q03lMMMlt|ORFhDnF?DCq zgP!g|-8*=+dt`;6El_JJv7DC`9%tZWb(V^)-B^xeJANPK5~Ah&vpv(d>(;GXkFTn}cU8SOGE#8x8UOmB+SxyHod1hA z$xjwHM;s>={Cn4Nl%rC0XDL-qr5JYWZrPRJbUBURblqFZlr!?oLpWQ`N;rdXuAGx_ z7U6t3FX0@*L**d}=MgTH3lbhec(^<);R3?Na#6y=2#=IUBwR#zv^*-|5roIeV-g-k zc)UC=;W30al{ZOv9N~%bgoHOCyt%wt!V?HjmM0~=8R0GEEfStYc&a=l;VlSnEpL_Z zRDIjh_VNydx2mc7PK38hcw2q@(ysEZl;dytODX**ps^iq-BG>+Fy# zyVZ_)ce^v6-tLs|lqW@hjL-12$X|I-i2DcCL%4s)e-Q6Kg!_lpBe;J=?jOee`_!Yjf7E}- zfB33begrY^SC1j)v8!(ReYksE?Zn+q+&$`#tHbzv;!RgQsXp+UhgQBHaZjlaO5Bk+ zPCc!jk(i^5DZgJGQ#XgQqZwAiTC)+%;y(R!;|hNA zN2~RE^=#co*qf^cA$|)-oAtUs$E3K+9P=-%$m3(RIX)gbuA42tank2a{`t_?(fcMG zHZ|hC7iyAM{$tC`r&?#)=0#uEn^nc|$8|Go&NU_eBui+_v5>UMHgjraS?V-$>Qf(o zwsQ2jXODj5`PmO4LGIL*W&aaZmXUd}VXEk@gthv&Fs0ehJepZ)G#kt<_d<=;tIu@u zC;W@GfXQ9G0|{GW(W965ovjA`zSD0x9k+6-ldhaP!K}|VR~o9dd;bRy@1MEv3s;EmM%a|Ckolf%ssBAR;;@SSo>IY2Hc{JHX% zR^FRgl~GxAR9fZGQTfjHN(A?~szD5+&elpC_q_5OVQs$VYr>)9oozPjH=Yn^y$sO3 z9H%>dpkyLr_`htk_?UhMEk=TwnVHk4-^z9}E6dBi?qurCOZXY8HRkFoLG7a78L2HT z*K2dNutFGhrm8{U>#(BzI>ymOzf$)b3*os=R{P$Y<{u1ob?&^c4_)*d7Z25IXQLz!&pdW0z;HOYjK|dl zKR8sa`%7nQbBBUCU0V(h>1c$^EMMv5E0sA+j7sGZR3zZYa&oOb8+Oo4oP2iLuiH|6h;e&xc^F?QGoO9l?jiS$FfFylml0%R$bt$YgKxK!S+E0w*7Pb#<<*KVmYrqgex zKj+M+l&3PU70U>FPT50!vYjm_s*MGIwi!N+2_67BNWJvk$fM)Ua+;>i2#~RG0MT=4 zT;dPI8GP}Z3i!00YC9FD?c$mW-K)-8TBX{l8~8W1DK+OyamOPCCKN{cwAZatCljo2cA!^l4a|fJ>N|sabW^+ql!*SX zV|G+32D}2)$C2tNzQHIiPCA?7pPL%J<=$%T9taf-BU8p(nf_j5W}T6!m^q(AGtrZW z@zji;_aXEQzHv|Teh%NB&qh7zD&i&6lf+HQp3HSdKDts5Ye37-`BfF%7>Tj=%R#75 z-x#rohPV1r6gZvkWX>^dCtuYIE1a`>Uh<4c2s<6-`-%~6X@p~?t#*DMn8@6G{L1G<3)h%EjDY7xRbjH z1jOAL!=xX>*L1FQEjzc!&YgHQ2&QOdx0yD5#i}=H^9oR&fW?+XY zz!0oJ+gU{Ug9mkls#H{Rsn)PKDk$yW8%*n?$PeT9!}#gKz2lv)HIzPv$hTaBq$I%r zz*mt*sE3opN>e=zw%~s&+mDSt!g4tU^apquG^U@!gHPit-J4H&u4c^ltZ%j+^OyYw zn4!5VJ(X^acOR7I*niy))6cU8{e5Id_&A;>N4Oo^0A_b=zkpO`>h>aoI^ty%I;mEv zTZf*?S%~V7GM7PMBO0hNE8=JY8)Lp_U@N}RY%a$mx;1nXOxVFXqV#c;5|8S9JgNr| zY74Llyb}X#L6Cw4nI>+G2jL0kJzx+9h>tF((T##;k+b z5F(F|+4>2TI0#urQj6zj7PBg+@^7ZpkSd6nnoGnDtD?l^6LBMIRN{sbabs#+;tCSC zN%EUenuQSDHhz^m+hGp%;1I}kIHh}o^~M9gR+rljsd z%vd4@ssY4|tItv~fSmRrr~PXBwM==F`U?>LA^t=BrqG`$Pe9nusRwXRA>S)+rf{#` zhx

`*A;s)Q`#i+0YA@$swo_bz=7^$|a*VISU3y8f}eH5{?>SKuAf$~2t<$a$zfp>SRA5^FC z$5i}#QGMdgocg4C32Aq!A5x!E<><-F>J`B7wE8rj+@U^apS-9lZ)VgPRYm-6bxoa( zp1i2$5U-Sv_&Wi;d9|R)h;mEnb#-3VrPfQTfs{?PjFfjpwYi`)>Kv#LY4=2N zk4W5#x`?>D6ScmiE~92wR15LmsGWq+%!kZsHGZQB~Q)-@F1W^xW3WX9r>k=TRGZYq90$-KR zUg;({DWc-Bn#S8redSCkY@!rXqGVNVs1gKFC^`ba6rS@-P;o@Ml%A@XjBG^?lrEjC z&7C9rD%s%8CE%BfK4g|g5Q?(sMD)gDoQK*kXy)p+Os?_i=bs-a1h@gw=B%occ>grO-mU$TO zwS3(?2$sA=5u3Ur0IC|6F4gMwIH6A&O>{SSED&L4gCUTB5PIXRFTFN{)Thot4iJeT zys}(lVOP3Y**=eZB3L;q;{+hTR141u&$X~pujtx&NIgI;L{0Mnn~ zrAyhZB=|YR8zi1@O(uY4G53uzqp$&ZjcVqtl<6Qq-F_J6S30PO0N6-G;L0vawSMJk zz;F*mp*;zfu^N<+Ypt;m4|u}z)Hw`r5s38?L4%G7sdOWdjbf(+>HL6&*h-@gX`!^z zSn~A(r-jC-1cZ|!R{}hTjZ%pHZL>9%uoWvcR+i3Uq9op;0t5ObqpGW5{)&hONC<5E z!1^@W|0DPcMRN+-RBKZbup$u7eK*kiMS&hQ0Jj0Xcx=VB4a_J$hq;e7pE;wNmm0Py zXE4*}OH_As0Sf8VUs?{Y82BWB@LoZu%K!#cm#BZBUq-lp9KM3MAKw^Y^5S;|Fkb+s z{c3%W$ltbWF)9X)xj$RWh5^oMy(0ZOuzjb|w!dTBm&1VAnj8>`*WYt138Vimt=za5 zDOLTwcWb4*-Wj(_U!Y|wjC3Nra7CQ1nD{<^c8!mo>8H~NK!ZPmj!6Q-{^ zt690`+RmDH-8tr*zPOri=hiaqyl4gTi`jPSd{(~_=GOAi$>HAU`gnEp$v*Bns)=ph@ zue(HsYvb))PfTiUlhpOotA%!9aRRkIu{zuyMon|DZxy7jY1|iL^%CkjyxmzXwu@_< z+XZPEo-R(F&m#RpZFg-;yC|{gICSAZT=*tRp6V;vS=-tkg094Cr`wt9uqKAvQ1k8A zovW$U5#@PK8&|+&M5Uf|!X3g~L$`7a&#zL`xf5|YsN$S<+Q#idTwdZl8+Qldh9oXy z<94Gyceb8? z?^0DaIwOYnsDOF&>p>@13ygtkq=Jyp2%jZ`g|t7_HR0!t*> zD@?6qA)~mVRC2nWRRJ1d)FNP&N7yV>UyPxFfVPvvy)auo3(2uDY~V;wtIEarElJupSjl?)3cdkH zt&`8Y`4qHQQ2W8?;TBT`w}89+EiYYw!jX6Ki*V6nI@iIo>;zNd5BI4oH0d6m6`&a| zq((CnDLx%fO`1Q1TiZ`W%~&@*VK9BZ0n2Y~DUpQquLY|`!bsYhG(2~l$}ry3S^XI_ z!a!7jFWtZ%FdTW7ZHzv7{8DEkM3fRzI~C-q2Wz`}yqykqg-!^pM0EbD*nfe_okdYR zBjkqZ5O$5LuFAY22!0J}XqAi7}C{g;5a-*CJlC#&JGfE$}r57a)h<4OH9$F55Ca2WO7~&sPX5Vyb?L0CJ_YX?=Z}!8pImHIi|94vy^5E5 z>T>%u?VYK{?ie`U?E@#KfISs@XW(>l5h~QLp(T9)(m#Z_yO4pVh?^NsQlm^G(Y*^z%8`hY$#;fnbY`k;FJRH}V9dp}t#`r4=HF`I2KN?ql zx**n;j#q~lPG?6&G%2AEybsR}kgFJz2MJ18gcgfhEAji$v_Zh0c|rg9xQy(E2@k5Fojhm-C`J*~!UI)sQ~9^O zo7r6Q&il4EHjY$9(3W>k2jOo=jMmp?4(hUTz0h%#z$STD$BTw@eGDS zTBpF$G<9vEmmlbiJ}pi?v3*!Sf|SA)fC`Gzr3(>q(Vb$Fohavo^;pMIYno<0$j&Vx z;H2~D(ov9cG6h9+uA6qzk>149CV}?$O+mwpgJP0y1KL88`^4XPTg^34taKR73`vr5bT~-_PQ^O6SE#GQ* zwPm;)mwI=#tDRxiYtpyGrt%J^evenqTx$D&Sa={x}l#$5Ucj2kc+Vhp9F61^a~p>MD2- z*j>nv#6oPk;sK(V4f0D9e6~tTCSCzhs!=lCXE=2O-F@QW;a8zIx1$|u+iF5GO$eV+1SH?vO4zU>(3-6=dt+9ABw6^A1t-V?5-Va3LVk~`Nw*Rqr?4YSHB+j8WdBVhS!L~r5waN1 zdDDd1=}ZU2C-M*gXpQuk51SPuTzx;%ccun+1JLCl#OEQuh=>WOM6-VZ`J873$j&-N z;yDN~x3a)*#anr=weuZ-)iaGu10u9GkGy0Wy%J5MGMIQw9Znkx4@7Ao9B_&fl(vWH ztDQcd*M~3Mci}!DDY#O$y)|f@3>v&}A6R(!5o2m)FvUQy;%niG+V_*%VyHGmn8zKHsYU6>RP?-fm5AaV1f0zVQIO{aGxT;LLmfz# zEYYp3hZv%cyLwQEEhnKyIxe2Xzd%jpvHJ6dY;qIgr0KEQ!=WcDrw$5>0EOb1UEr{4+4JOXyLPo za4rrbeIBX_kg3HXzDs?+>r@yn=r2qBa3Y@JBG|SEA{>{~|3vBt+;$BsK};>f5o*`B z13#{fUK7Ue!cRi?J(ehQ914obaFcaX&ZG6P15jCFO4|x}R3;GKjFeABDdD338dFZL zOF3zr_FF##0tmbrV79|_YS|DBd?>m7%OIh|1Hc!Fp)1(Tpd8L}A`tWbLHj;T&9IKc zjtAv@xKF&6?F^|dFKc*g$t>c#Jy?pRv1BX~03x>e^;STi!8mA5*{2X6=oSq$iFCS( zhwF$oS-giywC_UzTAMv2*&+IU5fEJCm;g<>4tt7r+o?VUJlv@{B}MSYVgoMPkkJ~` zb&nQ3BRC7^DwLHp@HaSNXr%&!iirZ1A(etRTlb$x$P45^;+7=%kAeCO@<-o`mQX9C zzrqV=ouA-QQvw5qL$=`vKlcFPvpaSixcv z#9v{XnACxNbrdKo4{m2PHR=|i>MimQzY{=a6HxMsYBy5lPP!tvL(G6!|LtrZl3*03 zdBVk~Z8?vj5f`CPd{%%H0^$RkhK z)Q5>Q;K$VGg)~Ek>WyLG$oo0imTuC6XGw z>18mT+}5t-bm-4QJayqFQ|K~E>n8>$iHEOD3?fiW2Tl&)?CV$+Xgu~^{nL0NXazG& zM4q2zL|-EeBSnLnMYBN`1z3;NWNn4%>P_34v@1g@AM!(_ep)Y_ZtXRem+rGTo1W!q zXlS|y{C)rx>+!2)>+<*l1*C%CKp=#~6kz2QknCPeiP|oS%=vJypGC2NPRUl zD+cmv2;6~~Z6KMY5t~AZt|}zsKM9obNvARCIbq>^LH~3L)`pbxDWIO09Off$49gp? z;WB`JxFE1t{cC`70{vKzwHQ)xQMA5+bXgPZV-~z<|6-}8>_jCx>GIWy3H8zH6?)7? zt7Kv}rkfV{2s6&C&ZI=xeFDw|xhAne0QVAj5tNK_B#7P6kZKP{#2&chQC5KD0bEG* zmhcRN5j9y^@e}ATiX`SV6fT|6lD&JF^1V~R}H6;}0K?yjPUKPDCGZg{YBA9m@ zY1@yomZ~9K>rkf!da}aT5?+1Xl#!kQFsTDLPS%$6A?qURMx5EdrkRxBCNWM%iW2?YcUD+_D) z&0hDCjT-Xqd@Sh+syvA*LwVLl#g+4h8&y|4cLDSa#`v-<{LWM^RqN+XRoxC)b0~}j z`S(_MCWiSsQxzU`nP}?pP&hPr*HY|kh+lEhu@x1iZ?^p{G*yOj%>85a@Wo0^#Z2CS zs=O~*mFZ!SK8Q=kDO$gQ@QvVQKhp;vsmR|UftSoi5p%PfajYtgt* zPNcTEqhk0dg1(b2F1qf`e0CJqQEwCsGef-^_nz_4ny_^8M1+=3_mD#hH5|i2@Q)xM z1V18jAWh(KBM$f;a>MjE5U%$DDiOB}A57IERE8qHgZy|^HsJQiPahH@!T`ETpbK!u zpd(48c_~<-yZd@7K_n&Qh^SV&u1BktK!czFkvhGHF$cXNDd=LYQYmK4qY7B;CLFG? z$H01UOjAy4QjD??4mnBM-mA#reZZ)z-ena)dE=JnVtWLe?>5i082lMn#K?lI29rZ z3Dc)Mf*mZnw;fme{lFWusLiDN7~vAX<+LGI9y;227@_ zY3U!33iOYM9@vH+aM1&YP5&rp5)FUchSxEy8sE6)1TTc?S5umV>#gO5*rc>WSFHn0Tw5}lhVnBWoQ3_8wsmt&}l6l&t{p7d8cq+oR24>=-Q@GiB zI3_&Q(R8UPMPwo8gLtpjUGa3lkT%RTv9qzQaw-1a;9I0xtyi8XVdV+2HfZ+>I8~?% zc+WNbE4tKWW|NCeL~$B3*|vZ#n;db`LB|o~)tZiRxg`x;qF{Mw*5@$1C9o@sEU#YI z4FqPuVNDhnJP#OL1tc*Q`x^_Z)$*8++2-Ue17`h$NZ&Vmg(v(1!OZrHv++Z>58#+5 zG#u^!h8$Y=#cxL**Rst8>{?u4L-8m9bT|k7bJmDBN&gdGev}tc1`sD1q<#z`c*h2i zBm5xNf{4a4Rv*g1`Cs|MKjnqmM6i?o#mGqdn~W50h@_#ywo-5DWa+`JUmn#v6MGToDR4VM4tDyj5`jTXsbBBkpeaC%Au_&5eRG&?l5046v6VCwL1H!@ccg zT6e~9*kG8p?zT(gEK6k;5@Ra`+zQQ28(dW|m@A zg}8yjmhs3cSg^=VC=_`;0`l+9CJDgtd@d1JHpW0{1s$3oio~W z;e4g%{vu-akKxiObnRQ3gfP}s{VS&Md69{3XFGhDBUKl9sR*eX_VzkEY!a`)VFb>$ z)Z~ud^3YLKSGBBzzkqq8JX#|b*w|L-bSqlnhH&I)>uJ^uS~Q_4k+f^fdZO{+ttaff`d^f3r}_YihLB$ zS{Vxe8l{*Mqy?5JJ+tYZP4^NiLGH; z+w8_MiHXN2*gGj}rd4NC2&GIV8W5y5!8?o!&Ndkp8Uf6#JlCrlec)9$*b}15Fv~Eb z=s(c_eO&droJ8m?V!m~rL-S?6D?^i0?CWgnBM6Y5aA4Al zt$?s-)Nf>mNfr3Sz3JWZa#85pUeWzy_>1?t<@CZO)=V4ywD7{*DD6PW4rs)k?iIg@ zm-rQ&|6dV^ds^Sa-Kn5Jx(B%2CFZ&AQt!M94m5IknwPZDOzY=hn0k(P3i~N?oaj%O ze8Tj+i%~NV>pMp8JXj7|--VaPK3I!b!h?4*&|rTF+%|0Mpc>91^xD@!XXYWD;0}6i zh8m}kGK-X7M=Efi0U%-XdLm}};38QS(Oqfm+61GxFJ3gz zwSps@!+Le-UPOPV#bhI+;{7c0cW{YE>2ZXi?ZUowq0tL*kQiQvD9yZ2o2cd*%I+vm zYJEg(Efpw0;AC#mcSCl8a*H=HAUOtMvIcL1?Y}@mK}azk zGbE=5m15k|;&V?DnOeCn24BUftWL?MX{`yNsaU?B@LSF$cG9DeV7Ak{AEC}WvJ>abDJ6 zVwGnbat%0}$<+c3tdz#EX7Z|0nLZ->P^POZ5C%=veW;vub7kRNDKeGD4Ta)G7;dyXS}=-@H|A0eP^7UmY5=O;wCRoORu#3pumTe>cRe#Ic2oJMJ^(K>?6cTi2aY9d ze(6ecWuI(+5YiO`yALcti#2z?ul_YC^tp3G=?eBjgfE;`Q^n)QZYYs>5lrATI<&jH zBE2JbRU)6Q?x3(jU4Rt>@za%M88C@wn<`;*xmj;6a7Tp-*9I6bgnx`OriYCZbRWy3 z?$Z=1#d9?3{~MQ%JKr#V_D?hFO=W&5Lfmz~5Kw~>pe+#ik*@e<{pueo!YsVSr7~f@4<{Ld6 z7Hi1wNdk>ove8j}^#6c+{fE5tjw-3dM_36q&Pm;Vj<3H{jvw#l2#9ht=>LcdVDJU} zKw2Z~-yJIQQ2##T$OuWLOhx}QZ-v1j8}&G|ok9RhW85it1x{qf(~)!lgawm}S@b4St#vuEXLuwieW>B7F(DQtlA=X)DWSJ{+}29w_{UjG*+BaV-452mptW`ID(nI}$d4$j|{3TQ5L00h0e4t2sEA=O8%c6I(iX`iP_* z0vqu&h|eN@0pqIN9$L#q8!8mq88F7`!jX2y#--YYt5EDhMKTOFBie#NRH|PH^H8J~ zp)wslpVn((L4mD4xcP2)SBnv=DdVGr;ePk zP6RP0LoJ-_c3oXI+X&~VY`p#jR^S_anufo;UgWJP zr%2MwqO!lp&~Ni{43}u}`k-OS&7u89#|xXPSuPm)kalDJO}tkwU^m`KXf*WeR~eh< z+DLLK(JteFdp|sgj_ZU6m2yswG>A+&|HzDIq2ZvGs{lqGzNVt{qMDH`J#}944D7r| zEQ}RwUTWed*RQqiy*;RE6z#NzUVuxm?3fF-j1@5giA+Q$h(ARHh?EhR3f4gDu3`Ph z>mor4W8i)cGNBCN;_Hy;WYIPz4*Zt9FvYaukpzFJ&w22H%HugCKt6{JB2CE+DWMO; zvw|9aEq_fc9jo2~rnf4tf_;EB<2)McnyI_)Yvvh6|cIh*$WM%Eg;P^A$Mgd!j3a`o|c$r+OXej2e;DZ zJ~Mc8J9meX((z8>8T2osxF1s^wDL$jvEvKh1%&V(-(DLP_>8MfsGCh|&;?t2%|nTA zvK4K*i%zuu@KfZm^@-0*=0 za@HszScog)jXZVbBr?!?{whYSkuAxc#UF;raBSvS-hlv_i`PVdi=KiVjR=}>9juGx zv=By*1o9j);YM2q48nr1;-#NL7RG;w>)}5XcK`*2b;3*+`6Wqh(2cfR5J1^W+Kr!I zVbu}X0kv^wYS~?CsN?hOvX(DTu`mNjbVGT(E(Qo6&R8bgW*6!zH zsL=_mz>^-*7_b``N~$S}SI|uiGjIzDMXSmCSC9mJ`rEyQ(E|EQJ)r2(#J|Ic=`NWx z{=^$su7onAa&R<*(-bv?(Cmqnh2M1*<-o@D`WgK6gNC*J*Q~85ejyg;o%~Hv!mTSE zsf2PD0FAKZ5MB?G5&E zzT6q|q2{7`!#8{c{KA&VLJ1e~$4r=Bs$SOr6KnJ}URHRy$jiUO1^Pb`v@A_6k3waG z<0`PoRxn(7+)lvC*()$wU+!d^E8+4=Sl$wA`sScUs&S>ZJRY*Q<#A)7qT?5~=$Go+ z+{z<19p`?zDDGjz1X#)-zytR!T_*$WPg!HBstj@&-@k{rA17SNvpK~q1isO9L3AA0 z@qij^?2Y4rK>@?!j(b=J<7S~>gjNwt>#^Vz`{Wd{yn_C`2BSV9Y3owu;*l;6NT$s3N$N(@Dcf# z{8l)khD2b2bqZWi<^t3?pm&ApTOf~?{(lm#nL?DQJv4%XQU20~e^`7tu>iKT--Mnj zJupo#Y*|Vf#XX5Qn;pqJhNMx#W7DeS{-uYFOMuxIie7h6iOK>FqcDy<2EZdU8}Ju~ z-*ThGj-zIBCApd{a<1dE(n1sIm+7~`lI0AYs>bqb)}Aq}g76}T6GH)I+X|o3E7*^Y z4o9q!@wN7`Fjtn}U&3k_$wtZop1`tF%vCrwS9SeLf-!m8@>t?-C0WT*njTl^MiZNv zVh^9%!b)?cyT%lZj;)R9y4(qThshzMXNy5W1+(URkZbVd?Lu!LqyV#Ye z{2pY}WmS63?5s_K53yFe=un-M7Gnq<^;)A(+jj9S2wT6#0*~|Zk8$Z_;Dp0fEjU8Q zVA!AMqgQzOJTE`Z%hSAkmKQ;|k01o+5y4$J|IF!5ZH*()xX8@xBiKn*$~hK`HMX7n z+&L`U#=0r)yti&^ieE&QSvL7e1e{51_664vm_+bPd2HgAD~=&>k>wsDK!Mp?dDnxZ zhX+Z`NM&M7aeKFZ6&L5$p}r+1WKt7mynd8L`}3xl62!A65TR3x@>)<9yE*=hBVbY1 z?*asZi1NBflLxxq6=U!2kgLp~TL{xIjL_r;aSEctFGIx0Tz6hcuVz-W5V9a9i53OB zex1+gFGAp<^V_))dR=TP2_$n>PCpSOg2l7g*fGy#|1eN{L^+cB^GJgD}Yw;uM}JYP)1ZLVLClleF+@5MG7CUaSdyS~5aJdU)M9rR8i%wmZjj zn^$a7O&~|((%oHsvp-1(uo^^aWktW~Xz5Hck?;s z76AzHa>3I6W>;P{D1RQtuQ;VRflnV0a?w{QBJ`@o@sS*DYh{)!CaYpyyl48GCwq*> zWhY*-nb}uM`)HcpXEt7ldUVOGo|1)6IFd~^ngBwjs?eu*;u60$B6}owyBYVZxFRJ&YV@7v~=)6Gx%cn{b*Gx$4E>BpTH2r{R$5| z!eRv&TF+v_)&eNbVT(2WhIRB<*+qUyJW*$EWUxSVZvY3bF0CWL#LHoNDnU?x6m7#E zPWD(l7-F#MUkt>Zj4b8qWEWuYhDwrLv(bhQGb@t6t_y-8y3~~DO}r4`*uq)#5ZJUc zn(~GgQxPkx#tk_yXmNz4O?rCVQ0Q?M@J%KY;)R1iBQ`v17GiYZmT!hk8T)_MA=ZF9 zL-O*bINlJXGKo03iR7_4iM+kZ<_RsB_^-YCM3_UWT6U z+;KFHVSZWcBg)-Jxx<|67P$Rbc7i41H=l-tmCoLxs~bw8_r{-OhoiKNxs}bJB(Nu3 zyW6_^HpcH!}uI8uNO=GT!Mx5|e2a~(Dl(0gzM5L!%Z zA$}4)8xDsf>&P%Ez1W(awuHCg-n@(43lMN%ufd{NPgB4FhXNkJSsaCm7iv{(e4PbF z9s+*??jovL0qF|uJhtnGI7GTF?-b{u{F66j^KR6`zRM?#6Sid^miLGH-e;M@=>Q66 z(Hd+39&rfcY8bT;*MG@1t%`5J*$D=0svAeDA4W%56m2MZf0~|KLd)h^07}G2Zr1dRH&@Lk&9mFu-n2@}y z9(N9}_X|f<0sd!8K&YVXRht^b61%GyN|_)Z@FkPI%zA1?(#9Do`G?wIFs$pE%l1w4*0@!P(Ew^ri$snq;gXe39y??lYd6I04!*G)UyTFZ03o^YS%b zDBl^9B*ckw;`GRd#H6b=i#d*fG{UTh#4*wiH;gu;rvi3GbD!L@S%gBu5wk|+nMnYjWkfySXe^7ow2}UA-@GUMN@bYlb zgDgqMP@w%hpUDCPY&UJT9tGkDSrN;hpncEF;t8#Se1athLq;_hk)l45;r07w46480 z%ckCoOV6pfJtX1`00xu@4FX(w=y8{Z^$mz0sL+K6X9)aImef zk?*EAATF;COWY7h2~argl^`O+*we|JyFhaH2=2#mLc=DMJjC-S_y^IEt>TK$lo?_GAx(6+0wazTa}EN!y&&iEUixNfZ{_h1RL(^bkhFhIKr_ zI+9=jd)(UC#v)4Jp0zW?U}iL=Q{t}DTcjPDr;zRe$$UU-Vu zp;?~XT*C1TwE;4*(9n3QiUH;n3})i+_WS6t))?LvuUZJ-3fremZ`M26)xU;^X1T?W zGF0Y8^CFZ}RG~r}g;f4?J`gt+A(bM7{TSjpn;|^ey|d$;1^+rz|28l0!zHGqUDw!Y zKDv(=5zt7@%425Q+p!HjZL^70W;{bf)Uh~yFh(pkQJ0ijO58_9_3t9{`w3IF%^8ox zr)cBBDUigho4=XQcy2zOg;>D|j<4=~VO ztP{&*VSb|cmVMjH72MYT+a1QO`;5|-P6hEhP}-WvI3hZ_zK3*-B?&Q1V9C9%Y^U`SXny?!1C`oLU7&EM;AcL7<2q{PLvH0{86G^8)% z03vKS2;y}adfNOVbgW!P5iHUx4d)&}qG6mk)j0Ezh*Ta#5hdFnCntzM-}k{AM^N4kuiK42OEYdo`thGaQfiYk{=!o(!(fibHp^D&0rdGM*kaZ6Cb)lrtEkTu&g+EB zhiMgZogf^bTQ1^wq!CG9gf2KsiBO30@1X-NSsFl;Bj@_Avkjd2Mc+s*+&fU^7(+y~ z-B8djtKFB0Swc`c4vQ~orL9nRl_m5@;Uze$847y|yE{|A+2tW@JtJN}xmWZ%QtPOq zholOL3MQzwt1mha5F7CLj+~yO*2YbGuLVO5~8<+e*>lX1O*d3%4eb$i=Qdps( ze+E+hKUxApm!HEi<`7+D%u2+UEUbKvazJsXuPE=`jFvVwWHxublT_*x0H^Twf zz8HDABz#<;bFCv4D3iiZ*=zW|f+I`TfYBiBuEPtPK$OO9w+sE=o43=5m=Pc;{Ex^; z=;1nWlUhH_2JA)vZXr{amjm9UOUgj^kU1nq)uAkuWiB`t2|LhNP-3DL^GNgcxD{O8 zn!nlSgL*ryh&@MIcSi%WOZd9m9fC6u1*OqH3^dTPodMfPdDytW82+!neH-$CY|rn- z1B;9H*TM38F?9bhE`g3t$EUaHT^xbGhO)(e+ZlpD8REb;?4n8$-RvUzHHJ90#G2c| z$hsVMJT8MVD>J{$N7S&I?T*s0Cka~uFI%i_F-iboh=gGg)`gsnz7u7~uDTntF5*=# z`rd!acSMHp5rW%{*)r1(j&g=WJ}7SzycRE6z!E9rt&4Gb7aO~W7nunE53>0p0rDCG z&StU~B8KyHkdX<5I{_ zS-=yj`0Xyfw(nxq-VUtRixdQJXOT;vyYGerxMznN4n5A~qa1z@Ab^3#4N5^OA=g65 z1pgN7pbI}+$hl-(xDNm%S%-T8T&JQkJ$uS5#?6}k{CQ-erJJN@-{5Tr7wD$MKL2wF zm$BbH4hFM3GITu+czV*G5UK?vb3AV2Iu1>F@6MC`hKK3Y@#z$nxZ@vzpySJ-M;F7o3AK(ZKphw#hqN47mOv1MW_ z{`OBk?%bNVXFNUr;P{sD&Cc7C&x~(!{&Z>wzWc`yz-fqo-`=%93f~ktH8H?KEn0qJWC4j3q7&m`!CJD_s-1YiP+u;v^OE>gcn0^>DVb(Z3oj@?T6D_Lsypm;|MlJOdc&@+0ixAP582a11h`;Hh z41FIi?~2ud&)2Z(0wNNyx<0_Y#FV?{g}~z!jIeq?GI_}|qx5X+@uTrho%@4nbFvdT z<%DJ0c0^(oJvSMWRYO>9CTa+3C175S(^kN7`wLi~%dL|FquD>kM*Ija)9Frnu5QkL zERXc26x?PJTxSFThZ6_$g-h%VXjni&+kvLt5aMy@&|6c0ruZ1n^dxWXweFz_A5tUA zqmv>j{xrN}**l4TkS;iYxH-@$`(fC_SBU865deU9aT9v(^o_0Ovl=IF(VqxgBkYFV zk>|-Aj-IFG`C%+hm**g?&`t`8g$!QdmLE|rSpMNu;EN^qcuVf{AvgVkWtM~)&nfRi z87|L;*^xK{%Fu`3LABtCJcS=)arrnyr+B%aM1&3PY!*&cV2a~8aZo4@nNPI-i%cz@ zN;m*ILeK|+sJF9(yuP1Zv<>e$Ias>63mklYx#uK&g5|I4C}&Te*PlZ2b*wDGEubb5 zCcna+{^b`Vc>z0EkYj<#Ikg7BFwA`^4A+9gvW`hSxtdHIk8@|7#q45EdDsmNz~@W= zXOo$2jm4^Oo|*N5y}C7?bT^Wi?A~BMPBJrNeu(=_E$jYa^9@wgOy4IM5-x;;9$Y0s z*zurJ?_U+x$%8LJUX{uVgj^Bn9Goy3ZDF4{Q6A7T>3UFri{xp11yEE9qKU%TEy@;7 z7%Pmys9fan8V?}sk?pfj0OCJDqN|jk=>`*WVk*=olxOlQybU zrob;NKB6Z%^n~tED`8JEf+G)97s)gv#MbpcVS7o;e*-c7ASZkmP(nxs0qTzds52H& zSqmuZU^27u%IJ0QvIueZ=AIx>J6dlkLJ!Sq-bAud!lUady^cUx^(0{W-; z8vgAz*3bfiYXY|guk~{{H319cAo515aBcn(3u5~{VqVB}^`$u$pXP7|#p65$uq!;u zE#bP}_re)$cV4nP4vIwrC%QDK0*iRDMD>B82s|^}2aS>c8Bd$GJBC{0F=S@22Q@Rl zeYnS)Hb{Yjat0OnRTS8=1-c0m-*d#s#>H~h6B*L&2c7NGoF}Hv+v{oe-o9>6&uX3o zIB=vcp`g#$ntHvUVKy7raWYZI<^*aB**mJO{%&f!2U^tn9>b>ty9g_k*xzMl0e+`E z0RvmZ@GKDcxb|QaA1t%4XW;jl-~tA9N(1$M%{DbZs8im#!K26^o`YyAyjrH+DjAb^ zY;DY_V}4a*?_4>NGBJXmjvh$7nThi_IYV)a=W73CIuog(k)cc`oE&1OAf^@c_af#1 zW)0b>9(zU4L2X2j*Zx@+ZC{U^_)0gI_|^XO(gD@}&3=+Q2}uiLxr)SqM+YQYDq3K4 zm1x|AJP+uq&KTxp#KcI3U$EU)=pJ{&$(u~H@kkxiajn+v_u4*=(0x6f*Bzi?H9GVT zu&>U|HCM1>QO^KHA71k7^^JhAArko#hTE5H!?WEz17|D}`7<>C(c5pnoT6GXW45RN zgvT1tfBJ8r>OWzt?zOfi5pLp8R{CmB@9NWd>HS1jzm6;fqhlLY?AZaSPF#WmFp>hX z0USNHR-h`toDF6q2GN;4!2HPZb@EnUM*d2ZcQ6YfNg*_`wmB?{s6s!)*VqZtoCJfm zlW{`lcOc}lozEeFZ4e5h=~1_sF6N7cB32dB=XRnfTBj|e`EmHvj%V>L;ER1%?Jwhp z@Wrls<1hv5N16X0;IeK+qeg}uAfsaU5HeN!L%t(=ieUU(f6ckKV_?yZcSNfx4Xe5D|1J;GRX(gzS`@mVHqY8 z=r(*}2qhwIYKCEe-qm*KTuXOZvkMcHuQH~^^Z^T#Af}CA`88HpIPx@I;;?wim5nLZ zRU;jD)C2-7Ue+>=$|@feY`BQzc9lQM-WycxFW|v1vsiYy12I5Wz)e5iDO4)P=N>bH zt=9jImw$!}_5r=J3>+*~(|peyRE}I6P5q|M^D@K>8>5GLDe^MH3(=(>#kDaMaFRs%fv8C3%fJOPNPo#$t5fSsryf%7$^7wE3n~w>izq;oq9}?2O$^wN0rDGKpy;pQYoGEDTDOV%o#E=57I`Tpan8(~xt;mW zcMf%PXsFEajQ(xT|NJ;(f2Yd9r-aH+8RPL;&e%;RndAX$^E&4wi$K(cDXqF?O1o~O zw1PsrST7nK8*-^$GI9a3Q+JG9ggj6mFmefUxn4H119`AMXygINm3jqoSq=t6?cw^c zkt@M)d!#-Bd8j_>v-&YPtj6TX0+XZim>m05$m4SSpIx);H8rRQXTYURQ z{gi%aLOT+D2V>9c``Rx6laZ)O(W+NNnlW`|x|Wks!$t`3{H zwYcO`Jao2r%5g@k_m^{Ss~a|Hi>_47NP9`7-DK5E+zmen+6XbTp2aHZ58G=a|1tISJGWpLA2qvFuNDJ>b}?R1gbx-R&!&eydaL| z=ku9pwt1~u^LIK?oHWcz4O%H~G`n#UweOU3#X|?C!_$G`20X4nNLj+R*#b%&mgDmD zFj`@A(F#A~nr!kJd%7{V8nxA&>Zx^K1#=hFJ#4i@2bjC;Eyr`6&15wSXFIRWwzN{q zzM4(6*IZY6?w$(o%?18)U*)yh>vJ(|GTTA7x1!=XFHr4ezd08-wcknR4xFRb+02~2 z#nowSB&Pdh+~K`vc8y+J2%;v;@%=UhOOI-}_Me8ay*aCpsUXlQjyC(&<2vr2U6(2X^l0)>7 zHQ({H*ETKl?hR;ApBuGYkH+*T@A!xx}-|+NG>~=68cArGFz8Olmk2|XAxBUEA z`4(&WyxQ|)70>VOq>ru|5lQWQ)NXsByr^}g?->2w^ip)-m>YhwnxFqnuX5&q`Id0& zoBS~o@B;#6{xTRWHGFenvuKXT@M434SNJHm zd3i?&@zuDkpGRx&*dagymA5l{DGF89%4{&=YBoqqG+?+EOl|;3oVNQ=WTTA&lsx_` z1mZS|ZCiq~R6G{j43M*={X{(BTUKgaV7LGDA>Zcl%-vHTun%}k$bu|Bbhgz7IlwR2HFgX7au9$Y>7CBeZg@4$`O;lc zp#tOpiCsX}z96y#lT9?@PT(hvD1_iO6|4muWCcT(89T&}WV|B&3_pGsf)9VaJww6Z zG27;nO|d6j+bK_AB!ovk1z-KOH~|h#`3d%Wao47xk@1>7h0#lhK4suuCoy3wvjP>O zNz`7=taY{dnb0plD~FANh(SkrfxLlY-W!+E9Xogxh2XHKzF;o?sEbKj{{-KUFuC=>xRY!k9OUFElZ)=K(tcT zfy`d^Vx?ci-m-!*aAvKjL=&qT=g`kmR}rUAy0JlXwWM9@HD}HT(l+DF1`)(rfi~FC zCrQN^;}w!}i|ilzO*F*P-7VpxlG_ z{5Edrc(X0(B#k}^H7H?lq4sqzg7+{AJ;saXArOhLm-Ki7f0}|k#kN4@YXZU4TC=72 zNNfp{Nx-unI;k+eBT~wl3cY{7?dcc{n+FZIrK7g%xmcZO4{;_!Ex<RT7#c)E&NvSPkbq%tn52Pn9_ zKt?uB)<%y>1mI*ly|WEO#274b7#|0mZEqkL|HrgQAzlsCef=F;fs(4a;>6PKYI}%eCx3-3r{!#&E!@gEvyw%YhCDiTA)${vWV0iSJ^Q=$KyIBNqoOUts1d=?*5 z3kW1@C>lcxEWw0}~v%xmsp5H>9~S{St-} zi%MLXDV5eeG285w{B|eOiQ5xDay6yLJ@In2I&y@7b39Jwy2!&DZIyV^OS}e=HKq6Z zHF_BYmBBL|9W_-PXQc}l-&wkO`Eq6<-r#d;GlnJZt7bQ$FS@MQj${|5jZ9Nl2lpYS zFJZd+$o}C>C^D>`6-;YpV>f~R87*s^gD4$Y(njtIdvVZMEkR#Q3e4Um_sJb$i7&_P zVP1wG6a`3r$F?f)iZW!Ij|m&4;Lm+23gTa4$1aI~3P=1SvuXc_67&Jgy9f8rQTczv z9e}lvK@oThMh2827ZW=z8t|3?UPK7}8)QO7fY*@@zzc{2yvU7GtHsmO9|XV&X8|~! z1K=z#>8sfDVN~v2(Kn;g+yEu224s4Xq@Uweb&i5)4 None: + self.dir = dir + self.file_template = file_template + self.version_locations = version_locations + self.truncate_slug_length = truncate_slug_length or 40 + self.sourceless = sourceless + self.output_encoding = output_encoding + self.revision_map = revision.RevisionMap(self._load_revisions) + self.timezone = timezone + self.hook_config = hook_config + self.recursive_version_locations = recursive_version_locations + self.messaging_opts = messaging_opts + + if not os.access(dir, os.F_OK): + raise util.CommandError( + "Path doesn't exist: %r. Please use " + "the 'init' command to create a new " + "scripts folder." % os.path.abspath(dir) + ) + + @property + def versions(self) -> str: + loc = self._version_locations + if len(loc) > 1: + raise util.CommandError("Multiple version_locations present") + else: + return loc[0] + + @util.memoized_property + def _version_locations(self) -> Sequence[str]: + if self.version_locations: + return [ + os.path.abspath(util.coerce_resource_to_filename(location)) + for location in self.version_locations + ] + else: + return (os.path.abspath(os.path.join(self.dir, "versions")),) + + def _load_revisions(self) -> Iterator[Script]: + if self.version_locations: + paths = [ + vers + for vers in self._version_locations + if os.path.exists(vers) + ] + else: + paths = [self.versions] + + dupes = set() + for vers in paths: + for file_path in Script._list_py_dir(self, vers): + real_path = os.path.realpath(file_path) + if real_path in dupes: + util.warn( + "File %s loaded twice! ignoring. Please ensure " + "version_locations is unique." % real_path + ) + continue + dupes.add(real_path) + + filename = os.path.basename(real_path) + dir_name = os.path.dirname(real_path) + script = Script._from_filename(self, dir_name, filename) + if script is None: + continue + yield script + + @classmethod + def from_config(cls, config: Config) -> ScriptDirectory: + """Produce a new :class:`.ScriptDirectory` given a :class:`.Config` + instance. + + The :class:`.Config` need only have the ``script_location`` key + present. + + """ + script_location = config.get_main_option("script_location") + if script_location is None: + raise util.CommandError( + "No 'script_location' key " "found in configuration." + ) + truncate_slug_length: Optional[int] + tsl = config.get_main_option("truncate_slug_length") + if tsl is not None: + truncate_slug_length = int(tsl) + else: + truncate_slug_length = None + + version_locations_str = config.get_main_option("version_locations") + version_locations: Optional[List[str]] + if version_locations_str: + version_path_separator = config.get_main_option( + "version_path_separator" + ) + + split_on_path = { + None: None, + "space": " ", + "newline": "\n", + "os": os.pathsep, + ":": ":", + ";": ";", + } + + try: + split_char: Optional[str] = split_on_path[ + version_path_separator + ] + except KeyError as ke: + raise ValueError( + "'%s' is not a valid value for " + "version_path_separator; " + "expected 'space', 'newline', 'os', ':', ';'" + % version_path_separator + ) from ke + else: + if split_char is None: + # legacy behaviour for backwards compatibility + version_locations = _split_on_space_comma.split( + version_locations_str + ) + else: + version_locations = [ + x.strip() + for x in version_locations_str.split(split_char) + if x + ] + else: + version_locations = None + + prepend_sys_path = config.get_main_option("prepend_sys_path") + if prepend_sys_path: + sys.path[:0] = list( + _split_on_space_comma_colon.split(prepend_sys_path) + ) + + rvl = config.get_main_option("recursive_version_locations") == "true" + return ScriptDirectory( + util.coerce_resource_to_filename(script_location), + file_template=config.get_main_option( + "file_template", _default_file_template + ), + truncate_slug_length=truncate_slug_length, + sourceless=config.get_main_option("sourceless") == "true", + output_encoding=config.get_main_option("output_encoding", "utf-8"), + version_locations=version_locations, + timezone=config.get_main_option("timezone"), + hook_config=config.get_section("post_write_hooks", {}), + recursive_version_locations=rvl, + messaging_opts=config.messaging_opts, + ) + + @contextmanager + def _catch_revision_errors( + self, + ancestor: Optional[str] = None, + multiple_heads: Optional[str] = None, + start: Optional[str] = None, + end: Optional[str] = None, + resolution: Optional[str] = None, + ) -> Iterator[None]: + try: + yield + except revision.RangeNotAncestorError as rna: + if start is None: + start = cast(Any, rna.lower) + if end is None: + end = cast(Any, rna.upper) + if not ancestor: + ancestor = ( + "Requested range %(start)s:%(end)s does not refer to " + "ancestor/descendant revisions along the same branch" + ) + ancestor = ancestor % {"start": start, "end": end} + raise util.CommandError(ancestor) from rna + except revision.MultipleHeads as mh: + if not multiple_heads: + multiple_heads = ( + "Multiple head revisions are present for given " + "argument '%(head_arg)s'; please " + "specify a specific target revision, " + "'@%(head_arg)s' to " + "narrow to a specific head, or 'heads' for all heads" + ) + multiple_heads = multiple_heads % { + "head_arg": end or mh.argument, + "heads": util.format_as_comma(mh.heads), + } + raise util.CommandError(multiple_heads) from mh + except revision.ResolutionError as re: + if resolution is None: + resolution = "Can't locate revision identified by '%s'" % ( + re.argument + ) + raise util.CommandError(resolution) from re + except revision.RevisionError as err: + raise util.CommandError(err.args[0]) from err + + def walk_revisions( + self, base: str = "base", head: str = "heads" + ) -> Iterator[Script]: + """Iterate through all revisions. + + :param base: the base revision, or "base" to start from the + empty revision. + + :param head: the head revision; defaults to "heads" to indicate + all head revisions. May also be "head" to indicate a single + head revision. + + """ + with self._catch_revision_errors(start=base, end=head): + for rev in self.revision_map.iterate_revisions( + head, base, inclusive=True, assert_relative_length=False + ): + yield cast(Script, rev) + + def get_revisions(self, id_: _GetRevArg) -> Tuple[Script, ...]: + """Return the :class:`.Script` instance with the given rev identifier, + symbolic name, or sequence of identifiers. + + """ + with self._catch_revision_errors(): + return cast( + Tuple[Script, ...], + self.revision_map.get_revisions(id_), + ) + + def get_all_current(self, id_: Tuple[str, ...]) -> Set[Script]: + with self._catch_revision_errors(): + return cast(Set[Script], self.revision_map._get_all_current(id_)) + + def get_revision(self, id_: str) -> Script: + """Return the :class:`.Script` instance with the given rev id. + + .. seealso:: + + :meth:`.ScriptDirectory.get_revisions` + + """ + + with self._catch_revision_errors(): + return cast(Script, self.revision_map.get_revision(id_)) + + def as_revision_number( + self, id_: Optional[str] + ) -> Optional[Union[str, Tuple[str, ...]]]: + """Convert a symbolic revision, i.e. 'head' or 'base', into + an actual revision number.""" + + with self._catch_revision_errors(): + rev, branch_name = self.revision_map._resolve_revision_number(id_) + + if not rev: + # convert () to None + return None + elif id_ == "heads": + return rev + else: + return rev[0] + + def iterate_revisions( + self, + upper: Union[str, Tuple[str, ...], None], + lower: Union[str, Tuple[str, ...], None], + **kw: Any, + ) -> Iterator[Script]: + """Iterate through script revisions, starting at the given + upper revision identifier and ending at the lower. + + The traversal uses strictly the `down_revision` + marker inside each migration script, so + it is a requirement that upper >= lower, + else you'll get nothing back. + + The iterator yields :class:`.Script` objects. + + .. seealso:: + + :meth:`.RevisionMap.iterate_revisions` + + """ + return cast( + Iterator[Script], + self.revision_map.iterate_revisions(upper, lower, **kw), + ) + + def get_current_head(self) -> Optional[str]: + """Return the current head revision. + + If the script directory has multiple heads + due to branching, an error is raised; + :meth:`.ScriptDirectory.get_heads` should be + preferred. + + :return: a string revision number. + + .. seealso:: + + :meth:`.ScriptDirectory.get_heads` + + """ + with self._catch_revision_errors( + multiple_heads=( + "The script directory has multiple heads (due to branching)." + "Please use get_heads(), or merge the branches using " + "alembic merge." + ) + ): + return self.revision_map.get_current_head() + + def get_heads(self) -> List[str]: + """Return all "versioned head" revisions as strings. + + This is normally a list of length one, + unless branches are present. The + :meth:`.ScriptDirectory.get_current_head()` method + can be used normally when a script directory + has only one head. + + :return: a tuple of string revision numbers. + """ + return list(self.revision_map.heads) + + def get_base(self) -> Optional[str]: + """Return the "base" revision as a string. + + This is the revision number of the script that + has a ``down_revision`` of None. + + If the script directory has multiple bases, an error is raised; + :meth:`.ScriptDirectory.get_bases` should be + preferred. + + """ + bases = self.get_bases() + if len(bases) > 1: + raise util.CommandError( + "The script directory has multiple bases. " + "Please use get_bases()." + ) + elif bases: + return bases[0] + else: + return None + + def get_bases(self) -> List[str]: + """return all "base" revisions as strings. + + This is the revision number of all scripts that + have a ``down_revision`` of None. + + """ + return list(self.revision_map.bases) + + def _upgrade_revs( + self, destination: str, current_rev: str + ) -> List[RevisionStep]: + with self._catch_revision_errors( + ancestor="Destination %(end)s is not a valid upgrade " + "target from current head(s)", + end=destination, + ): + revs = self.iterate_revisions( + destination, current_rev, implicit_base=True + ) + return [ + migration.MigrationStep.upgrade_from_script( + self.revision_map, script + ) + for script in reversed(list(revs)) + ] + + def _downgrade_revs( + self, destination: str, current_rev: Optional[str] + ) -> List[RevisionStep]: + with self._catch_revision_errors( + ancestor="Destination %(end)s is not a valid downgrade " + "target from current head(s)", + end=destination, + ): + revs = self.iterate_revisions( + current_rev, destination, select_for_downgrade=True + ) + return [ + migration.MigrationStep.downgrade_from_script( + self.revision_map, script + ) + for script in revs + ] + + def _stamp_revs( + self, revision: _RevIdType, heads: _RevIdType + ) -> List[StampStep]: + with self._catch_revision_errors( + multiple_heads="Multiple heads are present; please specify a " + "single target revision" + ): + heads_revs = self.get_revisions(heads) + + steps = [] + + if not revision: + revision = "base" + + filtered_heads: List[Script] = [] + for rev in util.to_tuple(revision): + if rev: + filtered_heads.extend( + self.revision_map.filter_for_lineage( + cast(Sequence[Script], heads_revs), + rev, + include_dependencies=True, + ) + ) + filtered_heads = util.unique_list(filtered_heads) + + dests = self.get_revisions(revision) or [None] + + for dest in dests: + if dest is None: + # dest is 'base'. Return a "delete branch" migration + # for all applicable heads. + steps.extend( + [ + migration.StampStep( + head.revision, + None, + False, + True, + self.revision_map, + ) + for head in filtered_heads + ] + ) + continue + elif dest in filtered_heads: + # the dest is already in the version table, do nothing. + continue + + # figure out if the dest is a descendant or an + # ancestor of the selected nodes + descendants = set( + self.revision_map._get_descendant_nodes([dest]) + ) + ancestors = set(self.revision_map._get_ancestor_nodes([dest])) + + if descendants.intersection(filtered_heads): + # heads are above the target, so this is a downgrade. + # we can treat them as a "merge", single step. + assert not ancestors.intersection(filtered_heads) + todo_heads = [head.revision for head in filtered_heads] + step = migration.StampStep( + todo_heads, + dest.revision, + False, + False, + self.revision_map, + ) + steps.append(step) + continue + elif ancestors.intersection(filtered_heads): + # heads are below the target, so this is an upgrade. + # we can treat them as a "merge", single step. + todo_heads = [head.revision for head in filtered_heads] + step = migration.StampStep( + todo_heads, + dest.revision, + True, + False, + self.revision_map, + ) + steps.append(step) + continue + else: + # destination is in a branch not represented, + # treat it as new branch + step = migration.StampStep( + (), dest.revision, True, True, self.revision_map + ) + steps.append(step) + continue + + return steps + + def run_env(self) -> None: + """Run the script environment. + + This basically runs the ``env.py`` script present + in the migration environment. It is called exclusively + by the command functions in :mod:`alembic.command`. + + + """ + util.load_python_file(self.dir, "env.py") + + @property + def env_py_location(self) -> str: + return os.path.abspath(os.path.join(self.dir, "env.py")) + + def _generate_template(self, src: str, dest: str, **kw: Any) -> None: + with util.status( + f"Generating {os.path.abspath(dest)}", **self.messaging_opts + ): + util.template_to_file(src, dest, self.output_encoding, **kw) + + def _copy_file(self, src: str, dest: str) -> None: + with util.status( + f"Generating {os.path.abspath(dest)}", **self.messaging_opts + ): + shutil.copy(src, dest) + + def _ensure_directory(self, path: str) -> None: + path = os.path.abspath(path) + if not os.path.exists(path): + with util.status( + f"Creating directory {path}", **self.messaging_opts + ): + os.makedirs(path) + + def _generate_create_date(self) -> datetime.datetime: + if self.timezone is not None: + if ZoneInfo is None: + raise util.CommandError( + "Python >= 3.9 is required for timezone support or " + "the 'backports.zoneinfo' package must be installed." + ) + # First, assume correct capitalization + try: + tzinfo = ZoneInfo(self.timezone) + except ZoneInfoNotFoundError: + tzinfo = None + if tzinfo is None: + try: + tzinfo = ZoneInfo(self.timezone.upper()) + except ZoneInfoNotFoundError: + raise util.CommandError( + "Can't locate timezone: %s" % self.timezone + ) from None + create_date = ( + datetime.datetime.utcnow() + .replace(tzinfo=datetime.timezone.utc) + .astimezone(tzinfo) + ) + else: + create_date = datetime.datetime.now() + return create_date + + def generate_revision( + self, + revid: str, + message: Optional[str], + head: Optional[_RevIdType] = None, + splice: Optional[bool] = False, + branch_labels: Optional[_RevIdType] = None, + version_path: Optional[str] = None, + depends_on: Optional[_RevIdType] = None, + **kw: Any, + ) -> Optional[Script]: + """Generate a new revision file. + + This runs the ``script.py.mako`` template, given + template arguments, and creates a new file. + + :param revid: String revision id. Typically this + comes from ``alembic.util.rev_id()``. + :param message: the revision message, the one passed + by the -m argument to the ``revision`` command. + :param head: the head revision to generate against. Defaults + to the current "head" if no branches are present, else raises + an exception. + :param splice: if True, allow the "head" version to not be an + actual head; otherwise, the selected head must be a head + (e.g. endpoint) revision. + + """ + if head is None: + head = "head" + + try: + Script.verify_rev_id(revid) + except revision.RevisionError as err: + raise util.CommandError(err.args[0]) from err + + with self._catch_revision_errors( + multiple_heads=( + "Multiple heads are present; please specify the head " + "revision on which the new revision should be based, " + "or perform a merge." + ) + ): + heads = cast( + Tuple[Optional["Revision"], ...], + self.revision_map.get_revisions(head), + ) + for h in heads: + assert h != "base" # type: ignore[comparison-overlap] + + if len(set(heads)) != len(heads): + raise util.CommandError("Duplicate head revisions specified") + + create_date = self._generate_create_date() + + if version_path is None: + if len(self._version_locations) > 1: + for head_ in heads: + if head_ is not None: + assert isinstance(head_, Script) + version_path = os.path.dirname(head_.path) + break + else: + raise util.CommandError( + "Multiple version locations present, " + "please specify --version-path" + ) + else: + version_path = self.versions + + norm_path = os.path.normpath(os.path.abspath(version_path)) + for vers_path in self._version_locations: + if os.path.normpath(vers_path) == norm_path: + break + else: + raise util.CommandError( + "Path %s is not represented in current " + "version locations" % version_path + ) + + if self.version_locations: + self._ensure_directory(version_path) + + path = self._rev_path(version_path, revid, message, create_date) + + if not splice: + for head_ in heads: + if head_ is not None and not head_.is_head: + raise util.CommandError( + "Revision %s is not a head revision; please specify " + "--splice to create a new branch from this revision" + % head_.revision + ) + + resolved_depends_on: Optional[List[str]] + if depends_on: + with self._catch_revision_errors(): + resolved_depends_on = [ + ( + dep + if dep in rev.branch_labels # maintain branch labels + else rev.revision + ) # resolve partial revision identifiers + for rev, dep in [ + (not_none(self.revision_map.get_revision(dep)), dep) + for dep in util.to_list(depends_on) + ] + ] + else: + resolved_depends_on = None + + self._generate_template( + os.path.join(self.dir, "script.py.mako"), + path, + up_revision=str(revid), + down_revision=revision.tuple_rev_as_scalar( + tuple(h.revision if h is not None else None for h in heads) + ), + branch_labels=util.to_tuple(branch_labels), + depends_on=revision.tuple_rev_as_scalar(resolved_depends_on), + create_date=create_date, + comma=util.format_as_comma, + message=message if message is not None else ("empty message"), + **kw, + ) + + post_write_hooks = self.hook_config + if post_write_hooks: + write_hooks._run_hooks(path, post_write_hooks) + + try: + script = Script._from_path(self, path) + except revision.RevisionError as err: + raise util.CommandError(err.args[0]) from err + if script is None: + return None + if branch_labels and not script.branch_labels: + raise util.CommandError( + "Version %s specified branch_labels %s, however the " + "migration file %s does not have them; have you upgraded " + "your script.py.mako to include the " + "'branch_labels' section?" + % (script.revision, branch_labels, script.path) + ) + self.revision_map.add_revision(script) + return script + + def _rev_path( + self, + path: str, + rev_id: str, + message: Optional[str], + create_date: datetime.datetime, + ) -> str: + epoch = int(create_date.timestamp()) + slug = "_".join(_slug_re.findall(message or "")).lower() + if len(slug) > self.truncate_slug_length: + slug = slug[: self.truncate_slug_length].rsplit("_", 1)[0] + "_" + filename = "%s.py" % ( + self.file_template + % { + "rev": rev_id, + "slug": slug, + "epoch": epoch, + "year": create_date.year, + "month": create_date.month, + "day": create_date.day, + "hour": create_date.hour, + "minute": create_date.minute, + "second": create_date.second, + } + ) + return os.path.join(path, filename) + + +class Script(revision.Revision): + """Represent a single revision file in a ``versions/`` directory. + + The :class:`.Script` instance is returned by methods + such as :meth:`.ScriptDirectory.iterate_revisions`. + + """ + + def __init__(self, module: ModuleType, rev_id: str, path: str): + self.module = module + self.path = path + super().__init__( + rev_id, + module.down_revision, + branch_labels=util.to_tuple( + getattr(module, "branch_labels", None), default=() + ), + dependencies=util.to_tuple( + getattr(module, "depends_on", None), default=() + ), + ) + + module: ModuleType + """The Python module representing the actual script itself.""" + + path: str + """Filesystem path of the script.""" + + _db_current_indicator: Optional[bool] = None + """Utility variable which when set will cause string output to indicate + this is a "current" version in some database""" + + @property + def doc(self) -> str: + """Return the docstring given in the script.""" + + return re.split("\n\n", self.longdoc)[0] + + @property + def longdoc(self) -> str: + """Return the docstring given in the script.""" + + doc = self.module.__doc__ + if doc: + if hasattr(self.module, "_alembic_source_encoding"): + doc = doc.decode( # type: ignore[attr-defined] + self.module._alembic_source_encoding + ) + return doc.strip() # type: ignore[union-attr] + else: + return "" + + @property + def log_entry(self) -> str: + entry = "Rev: %s%s%s%s%s\n" % ( + self.revision, + " (head)" if self.is_head else "", + " (branchpoint)" if self.is_branch_point else "", + " (mergepoint)" if self.is_merge_point else "", + " (current)" if self._db_current_indicator else "", + ) + if self.is_merge_point: + entry += "Merges: %s\n" % (self._format_down_revision(),) + else: + entry += "Parent: %s\n" % (self._format_down_revision(),) + + if self.dependencies: + entry += "Also depends on: %s\n" % ( + util.format_as_comma(self.dependencies) + ) + + if self.is_branch_point: + entry += "Branches into: %s\n" % ( + util.format_as_comma(self.nextrev) + ) + + if self.branch_labels: + entry += "Branch names: %s\n" % ( + util.format_as_comma(self.branch_labels), + ) + + entry += "Path: %s\n" % (self.path,) + + entry += "\n%s\n" % ( + "\n".join(" %s" % para for para in self.longdoc.splitlines()) + ) + return entry + + def __str__(self) -> str: + return "%s -> %s%s%s%s, %s" % ( + self._format_down_revision(), + self.revision, + " (head)" if self.is_head else "", + " (branchpoint)" if self.is_branch_point else "", + " (mergepoint)" if self.is_merge_point else "", + self.doc, + ) + + def _head_only( + self, + include_branches: bool = False, + include_doc: bool = False, + include_parents: bool = False, + tree_indicators: bool = True, + head_indicators: bool = True, + ) -> str: + text = self.revision + if include_parents: + if self.dependencies: + text = "%s (%s) -> %s" % ( + self._format_down_revision(), + util.format_as_comma(self.dependencies), + text, + ) + else: + text = "%s -> %s" % (self._format_down_revision(), text) + assert text is not None + if include_branches and self.branch_labels: + text += " (%s)" % util.format_as_comma(self.branch_labels) + if head_indicators or tree_indicators: + text += "%s%s%s" % ( + " (head)" if self._is_real_head else "", + ( + " (effective head)" + if self.is_head and not self._is_real_head + else "" + ), + " (current)" if self._db_current_indicator else "", + ) + if tree_indicators: + text += "%s%s" % ( + " (branchpoint)" if self.is_branch_point else "", + " (mergepoint)" if self.is_merge_point else "", + ) + if include_doc: + text += ", %s" % self.doc + return text + + def cmd_format( + self, + verbose: bool, + include_branches: bool = False, + include_doc: bool = False, + include_parents: bool = False, + tree_indicators: bool = True, + ) -> str: + if verbose: + return self.log_entry + else: + return self._head_only( + include_branches, include_doc, include_parents, tree_indicators + ) + + def _format_down_revision(self) -> str: + if not self.down_revision: + return "" + else: + return util.format_as_comma(self._versioned_down_revisions) + + @classmethod + def _from_path( + cls, scriptdir: ScriptDirectory, path: str + ) -> Optional[Script]: + dir_, filename = os.path.split(path) + return cls._from_filename(scriptdir, dir_, filename) + + @classmethod + def _list_py_dir(cls, scriptdir: ScriptDirectory, path: str) -> List[str]: + paths = [] + for root, dirs, files in os.walk(path, topdown=True): + if root.endswith("__pycache__"): + # a special case - we may include these files + # if a `sourceless` option is specified + continue + + for filename in sorted(files): + paths.append(os.path.join(root, filename)) + + if scriptdir.sourceless: + # look for __pycache__ + py_cache_path = os.path.join(root, "__pycache__") + if os.path.exists(py_cache_path): + # add all files from __pycache__ whose filename is not + # already in the names we got from the version directory. + # add as relative paths including __pycache__ token + names = {filename.split(".")[0] for filename in files} + paths.extend( + os.path.join(py_cache_path, pyc) + for pyc in os.listdir(py_cache_path) + if pyc.split(".")[0] not in names + ) + + if not scriptdir.recursive_version_locations: + break + + # the real script order is defined by revision, + # but it may be undefined if there are many files with a same + # `down_revision`, for a better user experience (ex. debugging), + # we use a deterministic order + dirs.sort() + + return paths + + @classmethod + def _from_filename( + cls, scriptdir: ScriptDirectory, dir_: str, filename: str + ) -> Optional[Script]: + if scriptdir.sourceless: + py_match = _sourceless_rev_file.match(filename) + else: + py_match = _only_source_rev_file.match(filename) + + if not py_match: + return None + + py_filename = py_match.group(1) + + if scriptdir.sourceless: + is_c = py_match.group(2) == "c" + is_o = py_match.group(2) == "o" + else: + is_c = is_o = False + + if is_o or is_c: + py_exists = os.path.exists(os.path.join(dir_, py_filename)) + pyc_exists = os.path.exists(os.path.join(dir_, py_filename + "c")) + + # prefer .py over .pyc because we'd like to get the + # source encoding; prefer .pyc over .pyo because we'd like to + # have the docstrings which a -OO file would not have + if py_exists or is_o and pyc_exists: + return None + + module = util.load_python_file(dir_, filename) + + if not hasattr(module, "revision"): + # attempt to get the revision id from the script name, + # this for legacy only + m = _legacy_rev.match(filename) + if not m: + raise util.CommandError( + "Could not determine revision id from filename %s. " + "Be sure the 'revision' variable is " + "declared inside the script (please see 'Upgrading " + "from Alembic 0.1 to 0.2' in the documentation)." + % filename + ) + else: + revision = m.group(1) + else: + revision = module.revision + return Script(module, revision, os.path.join(dir_, filename)) diff --git a/venv/lib/python3.8/site-packages/alembic/script/revision.py b/venv/lib/python3.8/site-packages/alembic/script/revision.py new file mode 100644 index 000000000..c3108e985 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/script/revision.py @@ -0,0 +1,1728 @@ +from __future__ import annotations + +import collections +import re +from typing import Any +from typing import Callable +from typing import cast +from typing import Collection +from typing import Deque +from typing import Dict +from typing import FrozenSet +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import overload +from typing import Protocol +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy import util as sqlautil + +from .. import util +from ..util import not_none + +if TYPE_CHECKING: + from typing import Literal + +_RevIdType = Union[str, List[str], Tuple[str, ...]] +_GetRevArg = Union[ + str, + Iterable[Optional[str]], + Iterable[str], +] +_RevisionIdentifierType = Union[str, Tuple[str, ...], None] +_RevisionOrStr = Union["Revision", str] +_RevisionOrBase = Union["Revision", "Literal['base']"] +_InterimRevisionMapType = Dict[str, "Revision"] +_RevisionMapType = Dict[Union[None, str, Tuple[()]], Optional["Revision"]] +_T = TypeVar("_T") +_TR = TypeVar("_TR", bound=Optional[_RevisionOrStr]) + +_relative_destination = re.compile(r"(?:(.+?)@)?(\w+)?((?:\+|-)\d+)") +_revision_illegal_chars = ["@", "-", "+"] + + +class _CollectRevisionsProtocol(Protocol): + def __call__( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + inclusive: bool, + implicit_base: bool, + assert_relative_length: bool, + ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]: ... + + +class RevisionError(Exception): + pass + + +class RangeNotAncestorError(RevisionError): + def __init__( + self, lower: _RevisionIdentifierType, upper: _RevisionIdentifierType + ) -> None: + self.lower = lower + self.upper = upper + super().__init__( + "Revision %s is not an ancestor of revision %s" + % (lower or "base", upper or "base") + ) + + +class MultipleHeads(RevisionError): + def __init__(self, heads: Sequence[str], argument: Optional[str]) -> None: + self.heads = heads + self.argument = argument + super().__init__( + "Multiple heads are present for given argument '%s'; " + "%s" % (argument, ", ".join(heads)) + ) + + +class ResolutionError(RevisionError): + def __init__(self, message: str, argument: str) -> None: + super().__init__(message) + self.argument = argument + + +class CycleDetected(RevisionError): + kind = "Cycle" + + def __init__(self, revisions: Sequence[str]) -> None: + self.revisions = revisions + super().__init__( + "%s is detected in revisions (%s)" + % (self.kind, ", ".join(revisions)) + ) + + +class DependencyCycleDetected(CycleDetected): + kind = "Dependency cycle" + + def __init__(self, revisions: Sequence[str]) -> None: + super().__init__(revisions) + + +class LoopDetected(CycleDetected): + kind = "Self-loop" + + def __init__(self, revision: str) -> None: + super().__init__([revision]) + + +class DependencyLoopDetected(DependencyCycleDetected, LoopDetected): + kind = "Dependency self-loop" + + def __init__(self, revision: Sequence[str]) -> None: + super().__init__(revision) + + +class RevisionMap: + """Maintains a map of :class:`.Revision` objects. + + :class:`.RevisionMap` is used by :class:`.ScriptDirectory` to maintain + and traverse the collection of :class:`.Script` objects, which are + themselves instances of :class:`.Revision`. + + """ + + def __init__(self, generator: Callable[[], Iterable[Revision]]) -> None: + """Construct a new :class:`.RevisionMap`. + + :param generator: a zero-arg callable that will generate an iterable + of :class:`.Revision` instances to be used. These are typically + :class:`.Script` subclasses within regular Alembic use. + + """ + self._generator = generator + + @util.memoized_property + def heads(self) -> Tuple[str, ...]: + """All "head" revisions as strings. + + This is normally a tuple of length one, + unless unmerged branches are present. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self.heads + + @util.memoized_property + def bases(self) -> Tuple[str, ...]: + """All "base" revisions as strings. + + These are revisions that have a ``down_revision`` of None, + or empty tuple. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self.bases + + @util.memoized_property + def _real_heads(self) -> Tuple[str, ...]: + """All "real" head revisions as strings. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self._real_heads + + @util.memoized_property + def _real_bases(self) -> Tuple[str, ...]: + """All "real" base revisions as strings. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self._real_bases + + @util.memoized_property + def _revision_map(self) -> _RevisionMapType: + """memoized attribute, initializes the revision map from the + initial collection. + + """ + # Ordering required for some tests to pass (but not required in + # general) + map_: _InterimRevisionMapType = sqlautil.OrderedDict() + + heads: Set[Revision] = sqlautil.OrderedSet() + _real_heads: Set[Revision] = sqlautil.OrderedSet() + bases: Tuple[Revision, ...] = () + _real_bases: Tuple[Revision, ...] = () + + has_branch_labels = set() + all_revisions = set() + + for revision in self._generator(): + all_revisions.add(revision) + + if revision.revision in map_: + util.warn( + "Revision %s is present more than once" % revision.revision + ) + map_[revision.revision] = revision + if revision.branch_labels: + has_branch_labels.add(revision) + + heads.add(revision) + _real_heads.add(revision) + if revision.is_base: + bases += (revision,) + if revision._is_real_base: + _real_bases += (revision,) + + # add the branch_labels to the map_. We'll need these + # to resolve the dependencies. + rev_map = map_.copy() + self._map_branch_labels( + has_branch_labels, cast(_RevisionMapType, map_) + ) + + # resolve dependency names from branch labels and symbolic + # names + self._add_depends_on(all_revisions, cast(_RevisionMapType, map_)) + + for rev in map_.values(): + for downrev in rev._all_down_revisions: + if downrev not in map_: + util.warn( + "Revision %s referenced from %s is not present" + % (downrev, rev) + ) + down_revision = map_[downrev] + down_revision.add_nextrev(rev) + if downrev in rev._versioned_down_revisions: + heads.discard(down_revision) + _real_heads.discard(down_revision) + + # once the map has downrevisions populated, the dependencies + # can be further refined to include only those which are not + # already ancestors + self._normalize_depends_on(all_revisions, cast(_RevisionMapType, map_)) + self._detect_cycles(rev_map, heads, bases, _real_heads, _real_bases) + + revision_map: _RevisionMapType = dict(map_.items()) + revision_map[None] = revision_map[()] = None + self.heads = tuple(rev.revision for rev in heads) + self._real_heads = tuple(rev.revision for rev in _real_heads) + self.bases = tuple(rev.revision for rev in bases) + self._real_bases = tuple(rev.revision for rev in _real_bases) + + self._add_branches(has_branch_labels, revision_map) + return revision_map + + def _detect_cycles( + self, + rev_map: _InterimRevisionMapType, + heads: Set[Revision], + bases: Tuple[Revision, ...], + _real_heads: Set[Revision], + _real_bases: Tuple[Revision, ...], + ) -> None: + if not rev_map: + return + if not heads or not bases: + raise CycleDetected(list(rev_map)) + total_space = { + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r._versioned_down_revisions, + heads, + map_=cast(_RevisionMapType, rev_map), + ) + }.intersection( + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r.nextrev, + bases, + map_=cast(_RevisionMapType, rev_map), + ) + ) + deleted_revs = set(rev_map.keys()) - total_space + if deleted_revs: + raise CycleDetected(sorted(deleted_revs)) + + if not _real_heads or not _real_bases: + raise DependencyCycleDetected(list(rev_map)) + total_space = { + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r._all_down_revisions, + _real_heads, + map_=cast(_RevisionMapType, rev_map), + ) + }.intersection( + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r._all_nextrev, + _real_bases, + map_=cast(_RevisionMapType, rev_map), + ) + ) + deleted_revs = set(rev_map.keys()) - total_space + if deleted_revs: + raise DependencyCycleDetected(sorted(deleted_revs)) + + def _map_branch_labels( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + for revision in revisions: + if revision.branch_labels: + assert revision._orig_branch_labels is not None + for branch_label in revision._orig_branch_labels: + if branch_label in map_: + map_rev = map_[branch_label] + assert map_rev is not None + raise RevisionError( + "Branch name '%s' in revision %s already " + "used by revision %s" + % ( + branch_label, + revision.revision, + map_rev.revision, + ) + ) + map_[branch_label] = revision + + def _add_branches( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + for revision in revisions: + if revision.branch_labels: + revision.branch_labels.update(revision.branch_labels) + for node in self._get_descendant_nodes( + [revision], map_, include_dependencies=False + ): + node.branch_labels.update(revision.branch_labels) + + parent = node + while ( + parent + and not parent._is_real_branch_point + and not parent.is_merge_point + ): + parent.branch_labels.update(revision.branch_labels) + if parent.down_revision: + parent = map_[parent.down_revision] + else: + break + + def _add_depends_on( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + """Resolve the 'dependencies' for each revision in a collection + in terms of actual revision ids, as opposed to branch labels or other + symbolic names. + + The collection is then assigned to the _resolved_dependencies + attribute on each revision object. + + """ + + for revision in revisions: + if revision.dependencies: + deps = [ + map_[dep] for dep in util.to_tuple(revision.dependencies) + ] + revision._resolved_dependencies = tuple( + [d.revision for d in deps if d is not None] + ) + else: + revision._resolved_dependencies = () + + def _normalize_depends_on( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + """Create a collection of "dependencies" that omits dependencies + that are already ancestor nodes for each revision in a given + collection. + + This builds upon the _resolved_dependencies collection created in the + _add_depends_on() method, looking in the fully populated revision map + for ancestors, and omitting them as the _resolved_dependencies + collection as it is copied to a new collection. The new collection is + then assigned to the _normalized_resolved_dependencies attribute on + each revision object. + + The collection is then used to determine the immediate "down revision" + identifiers for this revision. + + """ + + for revision in revisions: + if revision._resolved_dependencies: + normalized_resolved = set(revision._resolved_dependencies) + for rev in self._get_ancestor_nodes( + [revision], + include_dependencies=False, + map_=map_, + ): + if rev is revision: + continue + elif rev._resolved_dependencies: + normalized_resolved.difference_update( + rev._resolved_dependencies + ) + + revision._normalized_resolved_dependencies = tuple( + normalized_resolved + ) + else: + revision._normalized_resolved_dependencies = () + + def add_revision(self, revision: Revision, _replace: bool = False) -> None: + """add a single revision to an existing map. + + This method is for single-revision use cases, it's not + appropriate for fully populating an entire revision map. + + """ + map_ = self._revision_map + if not _replace and revision.revision in map_: + util.warn( + "Revision %s is present more than once" % revision.revision + ) + elif _replace and revision.revision not in map_: + raise Exception("revision %s not in map" % revision.revision) + + map_[revision.revision] = revision + + revisions = [revision] + self._add_branches(revisions, map_) + self._map_branch_labels(revisions, map_) + self._add_depends_on(revisions, map_) + + if revision.is_base: + self.bases += (revision.revision,) + if revision._is_real_base: + self._real_bases += (revision.revision,) + + for downrev in revision._all_down_revisions: + if downrev not in map_: + util.warn( + "Revision %s referenced from %s is not present" + % (downrev, revision) + ) + not_none(map_[downrev]).add_nextrev(revision) + + self._normalize_depends_on(revisions, map_) + + if revision._is_real_head: + self._real_heads = tuple( + head + for head in self._real_heads + if head + not in set(revision._all_down_revisions).union( + [revision.revision] + ) + ) + (revision.revision,) + if revision.is_head: + self.heads = tuple( + head + for head in self.heads + if head + not in set(revision._versioned_down_revisions).union( + [revision.revision] + ) + ) + (revision.revision,) + + def get_current_head( + self, branch_label: Optional[str] = None + ) -> Optional[str]: + """Return the current head revision. + + If the script directory has multiple heads + due to branching, an error is raised; + :meth:`.ScriptDirectory.get_heads` should be + preferred. + + :param branch_label: optional branch name which will limit the + heads considered to those which include that branch_label. + + :return: a string revision number. + + .. seealso:: + + :meth:`.ScriptDirectory.get_heads` + + """ + current_heads: Sequence[str] = self.heads + if branch_label: + current_heads = self.filter_for_lineage( + current_heads, branch_label + ) + if len(current_heads) > 1: + raise MultipleHeads( + current_heads, + "%s@head" % branch_label if branch_label else "head", + ) + + if current_heads: + return current_heads[0] + else: + return None + + def _get_base_revisions(self, identifier: str) -> Tuple[str, ...]: + return self.filter_for_lineage(self.bases, identifier) + + def get_revisions( + self, id_: Optional[_GetRevArg] + ) -> Tuple[Optional[_RevisionOrBase], ...]: + """Return the :class:`.Revision` instances with the given rev id + or identifiers. + + May be given a single identifier, a sequence of identifiers, or the + special symbols "head" or "base". The result is a tuple of one + or more identifiers, or an empty tuple in the case of "base". + + In the cases where 'head', 'heads' is requested and the + revision map is empty, returns an empty tuple. + + Supports partial identifiers, where the given identifier + is matched against all identifiers that start with the given + characters; if there is exactly one match, that determines the + full revision. + + """ + + if isinstance(id_, (list, tuple, set, frozenset)): + return sum([self.get_revisions(id_elem) for id_elem in id_], ()) + else: + resolved_id, branch_label = self._resolve_revision_number(id_) + if len(resolved_id) == 1: + try: + rint = int(resolved_id[0]) + if rint < 0: + # branch@-n -> walk down from heads + select_heads = self.get_revisions("heads") + if branch_label is not None: + select_heads = tuple( + head + for head in select_heads + if branch_label + in is_revision(head).branch_labels + ) + return tuple( + self._walk(head, steps=rint) + for head in select_heads + ) + except ValueError: + # couldn't resolve as integer + pass + return tuple( + self._revision_for_ident(rev_id, branch_label) + for rev_id in resolved_id + ) + + def get_revision(self, id_: Optional[str]) -> Optional[Revision]: + """Return the :class:`.Revision` instance with the given rev id. + + If a symbolic name such as "head" or "base" is given, resolves + the identifier into the current head or base revision. If the symbolic + name refers to multiples, :class:`.MultipleHeads` is raised. + + Supports partial identifiers, where the given identifier + is matched against all identifiers that start with the given + characters; if there is exactly one match, that determines the + full revision. + + """ + + resolved_id, branch_label = self._resolve_revision_number(id_) + if len(resolved_id) > 1: + raise MultipleHeads(resolved_id, id_) + + resolved: Union[str, Tuple[()]] = resolved_id[0] if resolved_id else () + return self._revision_for_ident(resolved, branch_label) + + def _resolve_branch(self, branch_label: str) -> Optional[Revision]: + try: + branch_rev = self._revision_map[branch_label] + except KeyError: + try: + nonbranch_rev = self._revision_for_ident(branch_label) + except ResolutionError as re: + raise ResolutionError( + "No such branch: '%s'" % branch_label, branch_label + ) from re + + else: + return nonbranch_rev + else: + return branch_rev + + def _revision_for_ident( + self, + resolved_id: Union[str, Tuple[()], None], + check_branch: Optional[str] = None, + ) -> Optional[Revision]: + branch_rev: Optional[Revision] + if check_branch: + branch_rev = self._resolve_branch(check_branch) + else: + branch_rev = None + + revision: Union[Optional[Revision], Literal[False]] + try: + revision = self._revision_map[resolved_id] + except KeyError: + # break out to avoid misleading py3k stack traces + revision = False + revs: Sequence[str] + if revision is False: + assert resolved_id + # do a partial lookup + revs = [ + x + for x in self._revision_map + if x and len(x) > 3 and x.startswith(resolved_id) + ] + + if branch_rev: + revs = self.filter_for_lineage(revs, check_branch) + if not revs: + raise ResolutionError( + "No such revision or branch '%s'%s" + % ( + resolved_id, + ( + "; please ensure at least four characters are " + "present for partial revision identifier matches" + if len(resolved_id) < 4 + else "" + ), + ), + resolved_id, + ) + elif len(revs) > 1: + raise ResolutionError( + "Multiple revisions start " + "with '%s': %s..." + % (resolved_id, ", ".join("'%s'" % r for r in revs[0:3])), + resolved_id, + ) + else: + revision = self._revision_map[revs[0]] + + if check_branch and revision is not None: + assert branch_rev is not None + assert resolved_id + if not self._shares_lineage( + revision.revision, branch_rev.revision + ): + raise ResolutionError( + "Revision %s is not a member of branch '%s'" + % (revision.revision, check_branch), + resolved_id, + ) + return revision + + def _filter_into_branch_heads( + self, targets: Iterable[Optional[_RevisionOrBase]] + ) -> Set[Optional[_RevisionOrBase]]: + targets = set(targets) + + for rev in list(targets): + assert rev + if targets.intersection( + self._get_descendant_nodes([rev], include_dependencies=False) + ).difference([rev]): + targets.discard(rev) + return targets + + def filter_for_lineage( + self, + targets: Iterable[_TR], + check_against: Optional[str], + include_dependencies: bool = False, + ) -> Tuple[_TR, ...]: + id_, branch_label = self._resolve_revision_number(check_against) + + shares = [] + if branch_label: + shares.append(branch_label) + if id_: + shares.extend(id_) + + return tuple( + tg + for tg in targets + if self._shares_lineage( + tg, shares, include_dependencies=include_dependencies + ) + ) + + def _shares_lineage( + self, + target: Optional[_RevisionOrStr], + test_against_revs: Sequence[_RevisionOrStr], + include_dependencies: bool = False, + ) -> bool: + if not test_against_revs: + return True + if not isinstance(target, Revision): + resolved_target = not_none(self._revision_for_ident(target)) + else: + resolved_target = target + + resolved_test_against_revs = [ + ( + self._revision_for_ident(test_against_rev) + if not isinstance(test_against_rev, Revision) + else test_against_rev + ) + for test_against_rev in util.to_tuple( + test_against_revs, default=() + ) + ] + + return bool( + set( + self._get_descendant_nodes( + [resolved_target], + include_dependencies=include_dependencies, + ) + ) + .union( + self._get_ancestor_nodes( + [resolved_target], + include_dependencies=include_dependencies, + ) + ) + .intersection(resolved_test_against_revs) + ) + + def _resolve_revision_number( + self, id_: Optional[_GetRevArg] + ) -> Tuple[Tuple[str, ...], Optional[str]]: + branch_label: Optional[str] + if isinstance(id_, str) and "@" in id_: + branch_label, id_ = id_.split("@", 1) + + elif id_ is not None and ( + (isinstance(id_, tuple) and id_ and not isinstance(id_[0], str)) + or not isinstance(id_, (str, tuple)) + ): + raise RevisionError( + "revision identifier %r is not a string; ensure database " + "driver settings are correct" % (id_,) + ) + + else: + branch_label = None + + # ensure map is loaded + self._revision_map + if id_ == "heads": + if branch_label: + return ( + self.filter_for_lineage(self.heads, branch_label), + branch_label, + ) + else: + return self._real_heads, branch_label + elif id_ == "head": + current_head = self.get_current_head(branch_label) + if current_head: + return (current_head,), branch_label + else: + return (), branch_label + elif id_ == "base" or id_ is None: + return (), branch_label + else: + return util.to_tuple(id_, default=None), branch_label + + def iterate_revisions( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + implicit_base: bool = False, + inclusive: bool = False, + assert_relative_length: bool = True, + select_for_downgrade: bool = False, + ) -> Iterator[Revision]: + """Iterate through script revisions, starting at the given + upper revision identifier and ending at the lower. + + The traversal uses strictly the `down_revision` + marker inside each migration script, so + it is a requirement that upper >= lower, + else you'll get nothing back. + + The iterator yields :class:`.Revision` objects. + + """ + fn: _CollectRevisionsProtocol + if select_for_downgrade: + fn = self._collect_downgrade_revisions + else: + fn = self._collect_upgrade_revisions + + revisions, heads = fn( + upper, + lower, + inclusive=inclusive, + implicit_base=implicit_base, + assert_relative_length=assert_relative_length, + ) + + for node in self._topological_sort(revisions, heads): + yield not_none(self.get_revision(node)) + + def _get_descendant_nodes( + self, + targets: Collection[Optional[_RevisionOrBase]], + map_: Optional[_RevisionMapType] = None, + check: bool = False, + omit_immediate_dependencies: bool = False, + include_dependencies: bool = True, + ) -> Iterator[Any]: + if omit_immediate_dependencies: + + def fn(rev: Revision) -> Iterable[str]: + if rev not in targets: + return rev._all_nextrev + else: + return rev.nextrev + + elif include_dependencies: + + def fn(rev: Revision) -> Iterable[str]: + return rev._all_nextrev + + else: + + def fn(rev: Revision) -> Iterable[str]: + return rev.nextrev + + return self._iterate_related_revisions( + fn, targets, map_=map_, check=check + ) + + def _get_ancestor_nodes( + self, + targets: Collection[Optional[_RevisionOrBase]], + map_: Optional[_RevisionMapType] = None, + check: bool = False, + include_dependencies: bool = True, + ) -> Iterator[Revision]: + if include_dependencies: + + def fn(rev: Revision) -> Iterable[str]: + return rev._normalized_down_revisions + + else: + + def fn(rev: Revision) -> Iterable[str]: + return rev._versioned_down_revisions + + return self._iterate_related_revisions( + fn, targets, map_=map_, check=check + ) + + def _iterate_related_revisions( + self, + fn: Callable[[Revision], Iterable[str]], + targets: Collection[Optional[_RevisionOrBase]], + map_: Optional[_RevisionMapType], + check: bool = False, + ) -> Iterator[Revision]: + if map_ is None: + map_ = self._revision_map + + seen = set() + todo: Deque[Revision] = collections.deque() + for target_for in targets: + target = is_revision(target_for) + todo.append(target) + if check: + per_target = set() + + while todo: + rev = todo.pop() + if check: + per_target.add(rev) + + if rev in seen: + continue + seen.add(rev) + # Check for map errors before collecting. + for rev_id in fn(rev): + next_rev = map_[rev_id] + assert next_rev is not None + if next_rev.revision != rev_id: + raise RevisionError( + "Dependency resolution failed; broken map" + ) + todo.append(next_rev) + yield rev + if check: + overlaps = per_target.intersection(targets).difference( + [target] + ) + if overlaps: + raise RevisionError( + "Requested revision %s overlaps with " + "other requested revisions %s" + % ( + target.revision, + ", ".join(r.revision for r in overlaps), + ) + ) + + def _topological_sort( + self, + revisions: Collection[Revision], + heads: Any, + ) -> List[str]: + """Yield revision ids of a collection of Revision objects in + topological sorted order (i.e. revisions always come after their + down_revisions and dependencies). Uses the order of keys in + _revision_map to sort. + + """ + + id_to_rev = self._revision_map + + def get_ancestors(rev_id: str) -> Set[str]: + return { + r.revision + for r in self._get_ancestor_nodes([id_to_rev[rev_id]]) + } + + todo = {d.revision for d in revisions} + + # Use revision map (ordered dict) key order to pre-sort. + inserted_order = list(self._revision_map) + + current_heads = list( + sorted( + {d.revision for d in heads if d.revision in todo}, + key=inserted_order.index, + ) + ) + ancestors_by_idx = [get_ancestors(rev_id) for rev_id in current_heads] + + output = [] + + current_candidate_idx = 0 + while current_heads: + candidate = current_heads[current_candidate_idx] + + for check_head_index, ancestors in enumerate(ancestors_by_idx): + # scan all the heads. see if we can continue walking + # down the current branch indicated by current_candidate_idx. + if ( + check_head_index != current_candidate_idx + and candidate in ancestors + ): + current_candidate_idx = check_head_index + # nope, another head is dependent on us, they have + # to be traversed first + break + else: + # yup, we can emit + if candidate in todo: + output.append(candidate) + todo.remove(candidate) + + # now update the heads with our ancestors. + + candidate_rev = id_to_rev[candidate] + assert candidate_rev is not None + + heads_to_add = [ + r + for r in candidate_rev._normalized_down_revisions + if r in todo and r not in current_heads + ] + + if not heads_to_add: + # no ancestors, so remove this head from the list + del current_heads[current_candidate_idx] + del ancestors_by_idx[current_candidate_idx] + current_candidate_idx = max(current_candidate_idx - 1, 0) + else: + if ( + not candidate_rev._normalized_resolved_dependencies + and len(candidate_rev._versioned_down_revisions) == 1 + ): + current_heads[current_candidate_idx] = heads_to_add[0] + + # for plain movement down a revision line without + # any mergepoints, branchpoints, or deps, we + # can update the ancestors collection directly + # by popping out the candidate we just emitted + ancestors_by_idx[current_candidate_idx].discard( + candidate + ) + + else: + # otherwise recalculate it again, things get + # complicated otherwise. This can possibly be + # improved to not run the whole ancestor thing + # each time but it was getting complicated + current_heads[current_candidate_idx] = heads_to_add[0] + current_heads.extend(heads_to_add[1:]) + ancestors_by_idx[current_candidate_idx] = ( + get_ancestors(heads_to_add[0]) + ) + ancestors_by_idx.extend( + get_ancestors(head) for head in heads_to_add[1:] + ) + + assert not todo + return output + + def _walk( + self, + start: Optional[Union[str, Revision]], + steps: int, + branch_label: Optional[str] = None, + no_overwalk: bool = True, + ) -> Optional[_RevisionOrBase]: + """ + Walk the requested number of :steps up (steps > 0) or down (steps < 0) + the revision tree. + + :branch_label is used to select branches only when walking up. + + If the walk goes past the boundaries of the tree and :no_overwalk is + True, None is returned, otherwise the walk terminates early. + + A RevisionError is raised if there is no unambiguous revision to + walk to. + """ + initial: Optional[_RevisionOrBase] + if isinstance(start, str): + initial = self.get_revision(start) + else: + initial = start + + children: Sequence[Optional[_RevisionOrBase]] + for _ in range(abs(steps)): + if steps > 0: + assert initial != "base" # type: ignore[comparison-overlap] + # Walk up + walk_up = [ + is_revision(rev) + for rev in self.get_revisions( + self.bases if initial is None else initial.nextrev + ) + ] + if branch_label: + children = self.filter_for_lineage(walk_up, branch_label) + else: + children = walk_up + else: + # Walk down + if initial == "base": # type: ignore[comparison-overlap] + children = () + else: + children = self.get_revisions( + self.heads + if initial is None + else initial.down_revision + ) + if not children: + children = ("base",) + if not children: + # This will return an invalid result if no_overwalk, otherwise + # further steps will stay where we are. + ret = None if no_overwalk else initial + return ret + elif len(children) > 1: + raise RevisionError("Ambiguous walk") + initial = children[0] + + return initial + + def _parse_downgrade_target( + self, + current_revisions: _RevisionIdentifierType, + target: _RevisionIdentifierType, + assert_relative_length: bool, + ) -> Tuple[Optional[str], Optional[_RevisionOrBase]]: + """ + Parse downgrade command syntax :target to retrieve the target revision + and branch label (if any) given the :current_revisions stamp of the + database. + + Returns a tuple (branch_label, target_revision) where branch_label + is a string from the command specifying the branch to consider (or + None if no branch given), and target_revision is a Revision object + which the command refers to. target_revisions is None if the command + refers to 'base'. The target may be specified in absolute form, or + relative to :current_revisions. + """ + if target is None: + return None, None + assert isinstance( + target, str + ), "Expected downgrade target in string form" + match = _relative_destination.match(target) + if match: + branch_label, symbol, relative = match.groups() + rel_int = int(relative) + if rel_int >= 0: + if symbol is None: + # Downgrading to current + n is not valid. + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative, abs(rel_int)) + ) + # Find target revision relative to given symbol. + rev = self._walk( + symbol, + rel_int, + branch_label, + no_overwalk=assert_relative_length, + ) + if rev is None: + raise RevisionError("Walked too far") + return branch_label, rev + else: + relative_revision = symbol is None + if relative_revision: + # Find target revision relative to current state. + if branch_label: + cr_tuple = util.to_tuple(current_revisions) + symbol_list: Sequence[str] + symbol_list = self.filter_for_lineage( + cr_tuple, branch_label + ) + if not symbol_list: + # check the case where there are multiple branches + # but there is currently a single heads, since all + # other branch heads are dependent of the current + # single heads. + all_current = cast( + Set[Revision], self._get_all_current(cr_tuple) + ) + sl_all_current = self.filter_for_lineage( + all_current, branch_label + ) + symbol_list = [ + r.revision if r else r # type: ignore[misc] + for r in sl_all_current + ] + + assert len(symbol_list) == 1 + symbol = symbol_list[0] + else: + current_revisions = util.to_tuple(current_revisions) + if not current_revisions: + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" + % (relative, abs(rel_int)) + ) + # Have to check uniques here for duplicate rows test. + if len(set(current_revisions)) > 1: + util.warn( + "downgrade -1 from multiple heads is " + "ambiguous; " + "this usage will be disallowed in a future " + "release." + ) + symbol = current_revisions[0] + # Restrict iteration to just the selected branch when + # ambiguous branches are involved. + branch_label = symbol + # Walk down the tree to find downgrade target. + rev = self._walk( + start=( + self.get_revision(symbol) + if branch_label is None + else self.get_revision( + "%s@%s" % (branch_label, symbol) + ) + ), + steps=rel_int, + no_overwalk=assert_relative_length, + ) + if rev is None: + if relative_revision: + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative, abs(rel_int)) + ) + else: + raise RevisionError("Walked too far") + return branch_label, rev + + # No relative destination given, revision specified is absolute. + branch_label, _, symbol = target.rpartition("@") + if not branch_label: + branch_label = None + return branch_label, self.get_revision(symbol) + + def _parse_upgrade_target( + self, + current_revisions: _RevisionIdentifierType, + target: _RevisionIdentifierType, + assert_relative_length: bool, + ) -> Tuple[Optional[_RevisionOrBase], ...]: + """ + Parse upgrade command syntax :target to retrieve the target revision + and given the :current_revisions stamp of the database. + + Returns a tuple of Revision objects which should be iterated/upgraded + to. The target may be specified in absolute form, or relative to + :current_revisions. + """ + if isinstance(target, str): + match = _relative_destination.match(target) + else: + match = None + + if not match: + # No relative destination, target is absolute. + return self.get_revisions(target) + + current_revisions_tup: Union[str, Tuple[Optional[str], ...], None] + current_revisions_tup = util.to_tuple(current_revisions) + + branch_label, symbol, relative_str = match.groups() + relative = int(relative_str) + if relative > 0: + if symbol is None: + if not current_revisions_tup: + current_revisions_tup = (None,) + # Try to filter to a single target (avoid ambiguous branches). + start_revs = current_revisions_tup + if branch_label: + start_revs = self.filter_for_lineage( + self.get_revisions(current_revisions_tup), # type: ignore[arg-type] # noqa: E501 + branch_label, + ) + if not start_revs: + # The requested branch is not a head, so we need to + # backtrack to find a branchpoint. + active_on_branch = self.filter_for_lineage( + self._get_ancestor_nodes( + self.get_revisions(current_revisions_tup) + ), + branch_label, + ) + # Find the tips of this set of revisions (revisions + # without children within the set). + start_revs = tuple( + {rev.revision for rev in active_on_branch} + - { + down + for rev in active_on_branch + for down in rev._normalized_down_revisions + } + ) + if not start_revs: + # We must need to go right back to base to find + # a starting point for this branch. + start_revs = (None,) + if len(start_revs) > 1: + raise RevisionError( + "Ambiguous upgrade from multiple current revisions" + ) + # Walk up from unique target revision. + rev = self._walk( + start=start_revs[0], + steps=relative, + branch_label=branch_label, + no_overwalk=assert_relative_length, + ) + if rev is None: + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative_str, abs(relative)) + ) + return (rev,) + else: + # Walk is relative to a given revision, not the current state. + return ( + self._walk( + start=self.get_revision(symbol), + steps=relative, + branch_label=branch_label, + no_overwalk=assert_relative_length, + ), + ) + else: + if symbol is None: + # Upgrading to current - n is not valid. + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative, abs(relative)) + ) + return ( + self._walk( + start=( + self.get_revision(symbol) + if branch_label is None + else self.get_revision( + "%s@%s" % (branch_label, symbol) + ) + ), + steps=relative, + no_overwalk=assert_relative_length, + ), + ) + + def _collect_downgrade_revisions( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + inclusive: bool, + implicit_base: bool, + assert_relative_length: bool, + ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]: + """ + Compute the set of current revisions specified by :upper, and the + downgrade target specified by :target. Return all dependents of target + which are currently active. + + :inclusive=True includes the target revision in the set + """ + + branch_label, target_revision = self._parse_downgrade_target( + current_revisions=upper, + target=lower, + assert_relative_length=assert_relative_length, + ) + if target_revision == "base": + target_revision = None + assert target_revision is None or isinstance(target_revision, Revision) + + roots: List[Revision] + # Find candidates to drop. + if target_revision is None: + # Downgrading back to base: find all tree roots. + roots = [ + rev + for rev in self._revision_map.values() + if rev is not None and rev.down_revision is None + ] + elif inclusive: + # inclusive implies target revision should also be dropped + roots = [target_revision] + else: + # Downgrading to fixed target: find all direct children. + roots = [ + is_revision(rev) + for rev in self.get_revisions(target_revision.nextrev) + ] + + if branch_label and len(roots) > 1: + # Need to filter roots. + ancestors = { + rev.revision + for rev in self._get_ancestor_nodes( + [self._resolve_branch(branch_label)], + include_dependencies=False, + ) + } + # Intersection gives the root revisions we are trying to + # rollback with the downgrade. + roots = [ + is_revision(rev) + for rev in self.get_revisions( + {rev.revision for rev in roots}.intersection(ancestors) + ) + ] + + # Ensure we didn't throw everything away when filtering branches. + if len(roots) == 0: + raise RevisionError( + "Not a valid downgrade target from current heads" + ) + + heads = self.get_revisions(upper) + + # Aim is to drop :branch_revision; to do so we also need to drop its + # descendents and anything dependent on it. + downgrade_revisions = set( + self._get_descendant_nodes( + roots, + include_dependencies=True, + omit_immediate_dependencies=False, + ) + ) + active_revisions = set( + self._get_ancestor_nodes(heads, include_dependencies=True) + ) + + # Emit revisions to drop in reverse topological sorted order. + downgrade_revisions.intersection_update(active_revisions) + + if implicit_base: + # Wind other branches back to base. + downgrade_revisions.update( + active_revisions.difference(self._get_ancestor_nodes(roots)) + ) + + if ( + target_revision is not None + and not downgrade_revisions + and target_revision not in heads + ): + # Empty intersection: target revs are not present. + + raise RangeNotAncestorError("Nothing to drop", upper) + + return downgrade_revisions, heads + + def _collect_upgrade_revisions( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + inclusive: bool, + implicit_base: bool, + assert_relative_length: bool, + ) -> Tuple[Set[Revision], Tuple[Revision, ...]]: + """ + Compute the set of required revisions specified by :upper, and the + current set of active revisions specified by :lower. Find the + difference between the two to compute the required upgrades. + + :inclusive=True includes the current/lower revisions in the set + + :implicit_base=False only returns revisions which are downstream + of the current/lower revisions. Dependencies from branches with + different bases will not be included. + """ + targets: Collection[Revision] = [ + is_revision(rev) + for rev in self._parse_upgrade_target( + current_revisions=lower, + target=upper, + assert_relative_length=assert_relative_length, + ) + ] + + # assert type(targets) is tuple, "targets should be a tuple" + + # Handled named bases (e.g. branch@... -> heads should only produce + # targets on the given branch) + if isinstance(lower, str) and "@" in lower: + branch, _, _ = lower.partition("@") + branch_rev = self.get_revision(branch) + if branch_rev is not None and branch_rev.revision == branch: + # A revision was used as a label; get its branch instead + assert len(branch_rev.branch_labels) == 1 + branch = next(iter(branch_rev.branch_labels)) + targets = { + need for need in targets if branch in need.branch_labels + } + + required_node_set = set( + self._get_ancestor_nodes( + targets, check=True, include_dependencies=True + ) + ).union(targets) + + current_revisions = self.get_revisions(lower) + if not implicit_base and any( + rev not in required_node_set + for rev in current_revisions + if rev is not None + ): + raise RangeNotAncestorError(lower, upper) + assert ( + type(current_revisions) is tuple + ), "current_revisions should be a tuple" + + # Special case where lower = a relative value (get_revisions can't + # find it) + if current_revisions and current_revisions[0] is None: + _, rev = self._parse_downgrade_target( + current_revisions=upper, + target=lower, + assert_relative_length=assert_relative_length, + ) + assert rev + if rev == "base": + current_revisions = tuple() + lower = None + else: + current_revisions = (rev,) + lower = rev.revision + + current_node_set = set( + self._get_ancestor_nodes( + current_revisions, check=True, include_dependencies=True + ) + ).union(current_revisions) + + needs = required_node_set.difference(current_node_set) + + # Include the lower revision (=current_revisions?) in the iteration + if inclusive: + needs.update(is_revision(rev) for rev in self.get_revisions(lower)) + # By default, base is implicit as we want all dependencies returned. + # Base is also implicit if lower = base + # implicit_base=False -> only return direct downstreams of + # current_revisions + if current_revisions and not implicit_base: + lower_descendents = self._get_descendant_nodes( + [is_revision(rev) for rev in current_revisions], + check=True, + include_dependencies=False, + ) + needs.intersection_update(lower_descendents) + + return needs, tuple(targets) + + def _get_all_current( + self, id_: Tuple[str, ...] + ) -> Set[Optional[_RevisionOrBase]]: + top_revs: Set[Optional[_RevisionOrBase]] + top_revs = set(self.get_revisions(id_)) + top_revs.update( + self._get_ancestor_nodes(list(top_revs), include_dependencies=True) + ) + return self._filter_into_branch_heads(top_revs) + + +class Revision: + """Base class for revisioned objects. + + The :class:`.Revision` class is the base of the more public-facing + :class:`.Script` object, which represents a migration script. + The mechanics of revision management and traversal are encapsulated + within :class:`.Revision`, while :class:`.Script` applies this logic + to Python files in a version directory. + + """ + + nextrev: FrozenSet[str] = frozenset() + """following revisions, based on down_revision only.""" + + _all_nextrev: FrozenSet[str] = frozenset() + + revision: str = None # type: ignore[assignment] + """The string revision number.""" + + down_revision: Optional[_RevIdType] = None + """The ``down_revision`` identifier(s) within the migration script. + + Note that the total set of "down" revisions is + down_revision + dependencies. + + """ + + dependencies: Optional[_RevIdType] = None + """Additional revisions which this revision is dependent on. + + From a migration standpoint, these dependencies are added to the + down_revision to form the full iteration. However, the separation + of down_revision from "dependencies" is to assist in navigating + a history that contains many branches, typically a multi-root scenario. + + """ + + branch_labels: Set[str] = None # type: ignore[assignment] + """Optional string/tuple of symbolic names to apply to this + revision's branch""" + + _resolved_dependencies: Tuple[str, ...] + _normalized_resolved_dependencies: Tuple[str, ...] + + @classmethod + def verify_rev_id(cls, revision: str) -> None: + illegal_chars = set(revision).intersection(_revision_illegal_chars) + if illegal_chars: + raise RevisionError( + "Character(s) '%s' not allowed in revision identifier '%s'" + % (", ".join(sorted(illegal_chars)), revision) + ) + + def __init__( + self, + revision: str, + down_revision: Optional[Union[str, Tuple[str, ...]]], + dependencies: Optional[Union[str, Tuple[str, ...]]] = None, + branch_labels: Optional[Union[str, Tuple[str, ...]]] = None, + ) -> None: + if down_revision and revision in util.to_tuple(down_revision): + raise LoopDetected(revision) + elif dependencies is not None and revision in util.to_tuple( + dependencies + ): + raise DependencyLoopDetected(revision) + + self.verify_rev_id(revision) + self.revision = revision + self.down_revision = tuple_rev_as_scalar(util.to_tuple(down_revision)) + self.dependencies = tuple_rev_as_scalar(util.to_tuple(dependencies)) + self._orig_branch_labels = util.to_tuple(branch_labels, default=()) + self.branch_labels = set(self._orig_branch_labels) + + def __repr__(self) -> str: + args = [repr(self.revision), repr(self.down_revision)] + if self.dependencies: + args.append("dependencies=%r" % (self.dependencies,)) + if self.branch_labels: + args.append("branch_labels=%r" % (self.branch_labels,)) + return "%s(%s)" % (self.__class__.__name__, ", ".join(args)) + + def add_nextrev(self, revision: Revision) -> None: + self._all_nextrev = self._all_nextrev.union([revision.revision]) + if self.revision in revision._versioned_down_revisions: + self.nextrev = self.nextrev.union([revision.revision]) + + @property + def _all_down_revisions(self) -> Tuple[str, ...]: + return util.dedupe_tuple( + util.to_tuple(self.down_revision, default=()) + + self._resolved_dependencies + ) + + @property + def _normalized_down_revisions(self) -> Tuple[str, ...]: + """return immediate down revisions for a rev, omitting dependencies + that are still dependencies of ancestors. + + """ + return util.dedupe_tuple( + util.to_tuple(self.down_revision, default=()) + + self._normalized_resolved_dependencies + ) + + @property + def _versioned_down_revisions(self) -> Tuple[str, ...]: + return util.to_tuple(self.down_revision, default=()) + + @property + def is_head(self) -> bool: + """Return True if this :class:`.Revision` is a 'head' revision. + + This is determined based on whether any other :class:`.Script` + within the :class:`.ScriptDirectory` refers to this + :class:`.Script`. Multiple heads can be present. + + """ + return not bool(self.nextrev) + + @property + def _is_real_head(self) -> bool: + return not bool(self._all_nextrev) + + @property + def is_base(self) -> bool: + """Return True if this :class:`.Revision` is a 'base' revision.""" + + return self.down_revision is None + + @property + def _is_real_base(self) -> bool: + """Return True if this :class:`.Revision` is a "real" base revision, + e.g. that it has no dependencies either.""" + + # we use self.dependencies here because this is called up + # in initialization where _real_dependencies isn't set up + # yet + return self.down_revision is None and self.dependencies is None + + @property + def is_branch_point(self) -> bool: + """Return True if this :class:`.Script` is a branch point. + + A branchpoint is defined as a :class:`.Script` which is referred + to by more than one succeeding :class:`.Script`, that is more + than one :class:`.Script` has a `down_revision` identifier pointing + here. + + """ + return len(self.nextrev) > 1 + + @property + def _is_real_branch_point(self) -> bool: + """Return True if this :class:`.Script` is a 'real' branch point, + taking into account dependencies as well. + + """ + return len(self._all_nextrev) > 1 + + @property + def is_merge_point(self) -> bool: + """Return True if this :class:`.Script` is a merge point.""" + + return len(self._versioned_down_revisions) > 1 + + +@overload +def tuple_rev_as_scalar(rev: None) -> None: ... + + +@overload +def tuple_rev_as_scalar( + rev: Union[Tuple[_T, ...], List[_T]] +) -> Union[_T, Tuple[_T, ...], List[_T]]: ... + + +def tuple_rev_as_scalar( + rev: Optional[Sequence[_T]], +) -> Union[_T, Sequence[_T], None]: + if not rev: + return None + elif len(rev) == 1: + return rev[0] + else: + return rev + + +def is_revision(rev: Any) -> Revision: + assert isinstance(rev, Revision) + return rev diff --git a/venv/lib/python3.8/site-packages/alembic/script/write_hooks.py b/venv/lib/python3.8/site-packages/alembic/script/write_hooks.py new file mode 100644 index 000000000..997714792 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/script/write_hooks.py @@ -0,0 +1,179 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import shlex +import subprocess +import sys +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional +from typing import Union + +from .. import util +from ..util import compat + + +REVISION_SCRIPT_TOKEN = "REVISION_SCRIPT_FILENAME" + +_registry: dict = {} + + +def register(name: str) -> Callable: + """A function decorator that will register that function as a write hook. + + See the documentation linked below for an example. + + .. seealso:: + + :ref:`post_write_hooks_custom` + + + """ + + def decorate(fn): + _registry[name] = fn + return fn + + return decorate + + +def _invoke( + name: str, revision: str, options: Mapping[str, Union[str, int]] +) -> Any: + """Invokes the formatter registered for the given name. + + :param name: The name of a formatter in the registry + :param revision: A :class:`.MigrationRevision` instance + :param options: A dict containing kwargs passed to the + specified formatter. + :raises: :class:`alembic.util.CommandError` + """ + try: + hook = _registry[name] + except KeyError as ke: + raise util.CommandError( + f"No formatter with name '{name}' registered" + ) from ke + else: + return hook(revision, options) + + +def _run_hooks(path: str, hook_config: Mapping[str, str]) -> None: + """Invoke hooks for a generated revision.""" + + from .base import _split_on_space_comma + + names = _split_on_space_comma.split(hook_config.get("hooks", "")) + + for name in names: + if not name: + continue + opts = { + key[len(name) + 1 :]: hook_config[key] + for key in hook_config + if key.startswith(name + ".") + } + opts["_hook_name"] = name + try: + type_ = opts["type"] + except KeyError as ke: + raise util.CommandError( + f"Key {name}.type is required for post write hook {name!r}" + ) from ke + else: + with util.status( + f"Running post write hook {name!r}", newline=True + ): + _invoke(type_, path, opts) + + +def _parse_cmdline_options(cmdline_options_str: str, path: str) -> List[str]: + """Parse options from a string into a list. + + Also substitutes the revision script token with the actual filename of + the revision script. + + If the revision script token doesn't occur in the options string, it is + automatically prepended. + """ + if REVISION_SCRIPT_TOKEN not in cmdline_options_str: + cmdline_options_str = REVISION_SCRIPT_TOKEN + " " + cmdline_options_str + cmdline_options_list = shlex.split( + cmdline_options_str, posix=compat.is_posix + ) + cmdline_options_list = [ + option.replace(REVISION_SCRIPT_TOKEN, path) + for option in cmdline_options_list + ] + return cmdline_options_list + + +@register("console_scripts") +def console_scripts( + path: str, options: dict, ignore_output: bool = False +) -> None: + try: + entrypoint_name = options["entrypoint"] + except KeyError as ke: + raise util.CommandError( + f"Key {options['_hook_name']}.entrypoint is required for post " + f"write hook {options['_hook_name']!r}" + ) from ke + for entry in compat.importlib_metadata_get("console_scripts"): + if entry.name == entrypoint_name: + impl: Any = entry + break + else: + raise util.CommandError( + f"Could not find entrypoint console_scripts.{entrypoint_name}" + ) + cwd: Optional[str] = options.get("cwd", None) + cmdline_options_str = options.get("options", "") + cmdline_options_list = _parse_cmdline_options(cmdline_options_str, path) + + kw: Dict[str, Any] = {} + if ignore_output: + kw["stdout"] = kw["stderr"] = subprocess.DEVNULL + + subprocess.run( + [ + sys.executable, + "-c", + f"import {impl.module}; {impl.module}.{impl.attr}()", + ] + + cmdline_options_list, + cwd=cwd, + **kw, + ) + + +@register("exec") +def exec_(path: str, options: dict, ignore_output: bool = False) -> None: + try: + executable = options["executable"] + except KeyError as ke: + raise util.CommandError( + f"Key {options['_hook_name']}.executable is required for post " + f"write hook {options['_hook_name']!r}" + ) from ke + cwd: Optional[str] = options.get("cwd", None) + cmdline_options_str = options.get("options", "") + cmdline_options_list = _parse_cmdline_options(cmdline_options_str, path) + + kw: Dict[str, Any] = {} + if ignore_output: + kw["stdout"] = kw["stderr"] = subprocess.DEVNULL + + subprocess.run( + [ + executable, + *cmdline_options_list, + ], + cwd=cwd, + **kw, + ) diff --git a/venv/lib/python3.8/site-packages/alembic/templates/async/README b/venv/lib/python3.8/site-packages/alembic/templates/async/README new file mode 100644 index 000000000..e0d0858f2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/async/README @@ -0,0 +1 @@ +Generic single-database configuration with an async dbapi. \ No newline at end of file diff --git a/venv/lib/python3.8/site-packages/alembic/templates/async/__pycache__/env.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/templates/async/__pycache__/env.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..971cc3c174b41e834aeb3e6928ea7a77356367d8 GIT binary patch literal 2164 zcmZuyPmdcl6t^dnNixZ1H(OZB3ExVjZgMCH4yY>Kf~Zuj%F+rnhmM@FlQ`?~47Stm zuG&+%R@_k!noh8UJ8aqmFlJV*lU<_+1%!zA?cP&V@@iTu1NTX{Qa`*|cgd7Q); z@p#cqy1cvSF?`(7Pk7+wFEQ-G$ri(`^%}D_>--cZeI9`vzYbWJ^+4WUuWqqENIPq3 zo9%!!;PLtH3hCc?|0TjmMSS4i41b4)gTDbgIvnCEo(jni3N;lokixPkq|<^%@yv=s zfpndkQ;7uux#ud<6ls!Fh9qe(w8Ov;x52qfgPd=m`CQSOm~p^lLDBv{WP)Gff>t zLxdtXis@7Wz8fSj7$3zkf#=a&7*bd$j(@ zGfr5cZdk&fazzy93{c#=`{l#?#1>=%=AKd^-3@C`4ycrXzu3SZ@e`gcE&q5({PjE+ z*55W0&Z4o}Utc(#ztLn`l$I1rTQ2R|=vm+VD5=cmJU<;RwS1JsCDk-H_Ehq!sc6pG zFsvG|RBc5Q8( z_>oUUHnu!3CACiD2r!0oqw=)cyNWAa7v4vJ%Iv~`dNB@h;D6D-Q7guJ0NPT%pPcv=%Ke5F}vu6?7FKPGe0BbRND%3<0e|xZ28%2{4=i3Z z1X=-b8!nnGc-44;o}+VM^C##&G{&y8=lvaV>_Jatk(OXZ8gBf z@GQHpprV0-CgX}~Q2^;``Z#Jhh5>apA2>YNicRejCB*-)m<2pt}Ad;cgtkAuIp4NvC-*<+tof?O8F2hRIX(@2ua(8 z+f|m2Y|>)Fl!f8LAnC5RxCZdfWWDE()@}KMA-CZk<8q0j0QaE&!vCUZ7n4Bmf$mD? z+$IO>0RH}n>&rHHiB|rzQ2Hjvbs)MQBmL$H&N*&R`g<@2Hn{?hJcWHf{(11B%W#=Kufz literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/templates/async/alembic.ini.mako b/venv/lib/python3.8/site-packages/alembic/templates/async/alembic.ini.mako new file mode 100644 index 000000000..f97e2d665 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/async/alembic.ini.mako @@ -0,0 +1,117 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# Use forward slashes (/) also on windows to provide an os agnostic path +script_location = ${script_location} + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to ${script_location}/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +# version_path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +version_path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/venv/lib/python3.8/site-packages/alembic/templates/async/env.py b/venv/lib/python3.8/site-packages/alembic/templates/async/env.py new file mode 100644 index 000000000..9f2d51940 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/async/env.py @@ -0,0 +1,89 @@ +import asyncio +from logging.config import fileConfig + +from sqlalchemy import pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_async_migrations() -> None: + """In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + connectable = async_engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + + asyncio.run(run_async_migrations()) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.8/site-packages/alembic/templates/async/script.py.mako b/venv/lib/python3.8/site-packages/alembic/templates/async/script.py.mako new file mode 100644 index 000000000..fbc4b07dc --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/async/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/venv/lib/python3.8/site-packages/alembic/templates/generic/README b/venv/lib/python3.8/site-packages/alembic/templates/generic/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/generic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/venv/lib/python3.8/site-packages/alembic/templates/generic/__pycache__/env.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/templates/generic/__pycache__/env.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4e7c372968ec2a463c9eda77a3a05a83ad20256 GIT binary patch literal 1729 zcmZux!EPHj5G7Zum9&y&H)w-g3h?F%sHFfw&>o5+jne`t0wZ-3v{)1nsO3na&E;-M zDv1sIlw5i+@&i4{$NopZVy`{*7m60`kdk7%?Fvf`hs&8aocH*AYbz!A?EE^CpR@`2 z%}=gB4V*l{*S^6)5J43wXhx~GBNY{mtP$)6+Hn>KJ66pi$r4H+IcjAsXdSgh^SEQa z2I}qCes?qLh^9z>rlKX zr&7Uwsi$&=vs<8NQp0#^%3_>vym3{Q3Rhd`aq!9=pq&_S3!`%%X7f-qI{5D6Yaig? z$dU?rNzaLkjv8mg#Uc_75lbqX7g(l?md#7@wjZqI62W~(?vroGA^Dz&1Yx%LR~Yng zp;;kkhC5km%cN$z<#ehL=q@Wt0mC$9_&l3S%T}Nl1}t-P@KcYNy_9awjuzH2&YmAW z3Ey)qm@d(y*_Wa0k8w-6n9Z*|3?uS9hsyDZg0O221{k;OB`7sy?BNNs$Fi!Vo`r;~ zNGvxH(1tB9fr(P@ItDL5GYudR#iNH`Jblbu$tD=?1((WaSXZ*ol|uaG2L2FULB4SC z(SU{YP)HZD&7_ZLoeAkn?+Y1iZe-@$i z>Zaxeh(TO8aH-yQ+|0m@3vgU;$FnUZ9T=|06RCyGdO~sqxf_?2vj>A#ovhA1+YY<9 z)P4oKWtvEXy7R6p#`B|oZvsfOu`^s-p8N0WzFFw;Rg(wQppeKf#Psp+kJHh-EMNqu za4bQM9^pZ%vMNBk(Gxzgqv`}Zr}wIRds722k+6rc^J6e0Jf9aMB`51o?hQX3SuAC* z!eu@KJHk4OiOfe1ib`>Zo*8H`=)s-C>ZHDX6WMr8y$=zWy@dnmr8K5d_$7ZPt(2M_ z^xjZLgbsg21ZDhyh9izHBS()Kl3YfDy7;ImqKn2kIU`F{!rQy&)S=AJsOyNvMT81z zEz`ADTFdTDtz1)0jH+p#{`7y6(f?7*V~vbKirXACH?l-hujuKi=NXLD+=%0yWVp3u zF2kDVr$!oJ16K;WCb>V6PE22GYdHh$Bg!Yr@?p8egErib literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/templates/generic/alembic.ini.mako b/venv/lib/python3.8/site-packages/alembic/templates/generic/alembic.ini.mako new file mode 100644 index 000000000..4fc5d01a9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/generic/alembic.ini.mako @@ -0,0 +1,119 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +# Use forward slashes (/) also on windows to provide an os agnostic path +script_location = ${script_location} + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to ${script_location}/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +# version_path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +version_path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/venv/lib/python3.8/site-packages/alembic/templates/generic/env.py b/venv/lib/python3.8/site-packages/alembic/templates/generic/env.py new file mode 100644 index 000000000..36112a3c6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/generic/env.py @@ -0,0 +1,78 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.8/site-packages/alembic/templates/generic/script.py.mako b/venv/lib/python3.8/site-packages/alembic/templates/generic/script.py.mako new file mode 100644 index 000000000..fbc4b07dc --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/generic/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/venv/lib/python3.8/site-packages/alembic/templates/multidb/README b/venv/lib/python3.8/site-packages/alembic/templates/multidb/README new file mode 100644 index 000000000..f046ec914 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/multidb/README @@ -0,0 +1,12 @@ +Rudimentary multi-database configuration. + +Multi-DB isn't vastly different from generic. The primary difference is that it +will run the migrations N times (depending on how many databases you have +configured), providing one engine name and associated context for each run. + +That engine name will then allow the migration to restrict what runs within it to +just the appropriate migrations for that engine. You can see this behavior within +the mako template. + +In the provided configuration, you'll need to have `databases` provided in +alembic's config, and an `sqlalchemy.url` provided for each engine name. diff --git a/venv/lib/python3.8/site-packages/alembic/templates/multidb/__pycache__/env.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/templates/multidb/__pycache__/env.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3267dab6d8d516bbee239fa65b8dbfbadc0fffef GIT binary patch literal 2714 zcmZve&5s*J7Qn08?RLBUIg?3Xc9CeZgkVJM143|!5ReZADI2o+P@n}^ZCBaTp6ZXO zD$isr&xIhQJp?%Hk%KMmh0Fd4oDhFRU-tt4LSUtRuev8QB-rwE)vH&pUcIXK>%QM= zc?3S)p9ay_T|)kgozov1I`6|z{Radij0zIqza?mF1r{xIm7oH;im>BqP&GOm+D_n@ zc2(43H*lft1aoMD?8XIdo9+6-X?cfXVygin{PZKlq3${#D7Nbl3wRmh?(5#(cGIvf?r9KNi>Dt zCQqhO!iN)?#>4Tk*3QyYz-SFLnm^Rn7mZNxcodEMJlPNGEY#sBR9xi*tQ^VpTqa{& z;rLU9Mgx9V;HQ2D5s@RJ$%-WP?u+eQlGa ztsAT6lCCM{l6eti@G+c@uB=*X^6e(JW^Gu#`4~JuAa9d@kUQj)Q?0$rtOa>&hu0|~ zcjk#7M^hQ9G7*r-i+-Fk-uFBoKKFJa|KXvp zcB3qdlBrp+@Z^srH_@6Y2i#|Aa#8zypC^98IfE#!e)z%dk9?i_BXGALMgmtTcXA~Z z0^(1P!|(Hld_33u^`38*=W(RXYSRckDlN_ODeBCMYAmBn`{`U~b6q-pV!!#BdSlU6 zdmhx#09873+;p7OSwC&QE5*Q@5WA~coT7)4%@ ze4JOY%!j#C?DhvDUk0B~7K5EM<^#UqyAcd(&VlFy8{J#xs<_hIOS?k0%8FB%S5O_6VHW?*k}MV``KZB z?vHT~%ZlkkTip)^JStxSyXQcI$7?_{gW7x! zH2r5mqwQIBKj#8glZ5dE`k8Qw` zJv$3nY9N;O{sn730iSIXU-t+>>1>nLxiw&Z@0X_|n@>kxz>)9hp7nqvZ%}Ac0B$+| z#OlS;>h&+lrzEio>^)-#QLZ|r-R(~ZYXS^gi+}zZ36tkQ_)!8N0CK4@PeK``z^Z~e zNE8GU5a*bA1{X-eP^okrnK8spQWR1F&Ql~UVIp0)1dGNgmh`OL$t0gd4}&^B&&C2g z=1x&7xp(3+Xq+l9iSGc5#sxK;X8?G{)uII*mL4QUw`WVdtwF0CAL?|MCqd__MWHY? zEZbmttG6vX@Gs$=g2)XN6%@z{xd|d@oC1Z!w~WBOH5cMG1TD`%*F1-D|9SKN-D|^p z4?ezqE%q%Y{;3B@6hxZrqkj3$*x zBoi6DpQ&T04e>4DRFV+B(g%^dmtklw_=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to ${script_location}/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +# version_path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +version_path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +databases = engine1, engine2 + +[engine1] +sqlalchemy.url = driver://user:pass@localhost/dbname + +[engine2] +sqlalchemy.url = driver://user:pass@localhost/dbname2 + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/venv/lib/python3.8/site-packages/alembic/templates/multidb/env.py b/venv/lib/python3.8/site-packages/alembic/templates/multidb/env.py new file mode 100644 index 000000000..e937b64ee --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/multidb/env.py @@ -0,0 +1,140 @@ +import logging +from logging.config import fileConfig +import re + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +USE_TWOPHASE = False + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) +logger = logging.getLogger("alembic.env") + +# gather section names referring to different +# databases. These are named "engine1", "engine2" +# in the sample .ini file. +db_names = config.get_main_option("databases", "") + +# add your model's MetaData objects here +# for 'autogenerate' support. These must be set +# up to hold just those tables targeting a +# particular database. table.tometadata() may be +# helpful here in case a "copy" of +# a MetaData is needed. +# from myapp import mymodel +# target_metadata = { +# 'engine1':mymodel.metadata1, +# 'engine2':mymodel.metadata2 +# } +target_metadata = {} + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + # for the --sql use case, run migrations for each URL into + # individual files. + + engines = {} + for name in re.split(r",\s*", db_names): + engines[name] = rec = {} + rec["url"] = context.config.get_section_option(name, "sqlalchemy.url") + + for name, rec in engines.items(): + logger.info("Migrating database %s" % name) + file_ = "%s.sql" % name + logger.info("Writing output to %s" % file_) + with open(file_, "w") as buffer: + context.configure( + url=rec["url"], + output_buffer=buffer, + target_metadata=target_metadata.get(name), + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + with context.begin_transaction(): + context.run_migrations(engine_name=name) + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # for the direct-to-DB use case, start a transaction on all + # engines, then run all migrations, then commit all transactions. + + engines = {} + for name in re.split(r",\s*", db_names): + engines[name] = rec = {} + rec["engine"] = engine_from_config( + context.config.get_section(name, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + for name, rec in engines.items(): + engine = rec["engine"] + rec["connection"] = conn = engine.connect() + + if USE_TWOPHASE: + rec["transaction"] = conn.begin_twophase() + else: + rec["transaction"] = conn.begin() + + try: + for name, rec in engines.items(): + logger.info("Migrating database %s" % name) + context.configure( + connection=rec["connection"], + upgrade_token="%s_upgrades" % name, + downgrade_token="%s_downgrades" % name, + target_metadata=target_metadata.get(name), + ) + context.run_migrations(engine_name=name) + + if USE_TWOPHASE: + for rec in engines.values(): + rec["transaction"].prepare() + + for rec in engines.values(): + rec["transaction"].commit() + except: + for rec in engines.values(): + rec["transaction"].rollback() + raise + finally: + for rec in engines.values(): + rec["connection"].close() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.8/site-packages/alembic/templates/multidb/script.py.mako b/venv/lib/python3.8/site-packages/alembic/templates/multidb/script.py.mako new file mode 100644 index 000000000..6108b8a0d --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/templates/multidb/script.py.mako @@ -0,0 +1,47 @@ +<%! +import re + +%>"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade(engine_name: str) -> None: + globals()["upgrade_%s" % engine_name]() + + +def downgrade(engine_name: str) -> None: + globals()["downgrade_%s" % engine_name]() + +<% + db_names = config.get_main_option("databases") +%> + +## generate an "upgrade_() / downgrade_()" function +## for each database name in the ini file. + +% for db_name in re.split(r',\s*', db_names): + +def upgrade_${db_name}() -> None: + ${context.get("%s_upgrades" % db_name, "pass")} + + +def downgrade_${db_name}() -> None: + ${context.get("%s_downgrades" % db_name, "pass")} + +% endfor diff --git a/venv/lib/python3.8/site-packages/alembic/testing/__init__.py b/venv/lib/python3.8/site-packages/alembic/testing/__init__.py new file mode 100644 index 000000000..0407adfe9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/__init__.py @@ -0,0 +1,29 @@ +from sqlalchemy.testing import config +from sqlalchemy.testing import emits_warning +from sqlalchemy.testing import engines +from sqlalchemy.testing import exclusions +from sqlalchemy.testing import mock +from sqlalchemy.testing import provide_metadata +from sqlalchemy.testing import skip_if +from sqlalchemy.testing import uses_deprecated +from sqlalchemy.testing.config import combinations +from sqlalchemy.testing.config import fixture +from sqlalchemy.testing.config import requirements as requires + +from .assertions import assert_raises +from .assertions import assert_raises_message +from .assertions import emits_python_deprecation_warning +from .assertions import eq_ +from .assertions import eq_ignore_whitespace +from .assertions import expect_raises +from .assertions import expect_raises_message +from .assertions import expect_sqlalchemy_deprecated +from .assertions import expect_sqlalchemy_deprecated_20 +from .assertions import expect_warnings +from .assertions import is_ +from .assertions import is_false +from .assertions import is_not_ +from .assertions import is_true +from .assertions import ne_ +from .fixtures import TestBase +from .util import resolve_lambda diff --git a/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b86e1ae738a3b9294fa712d619efe66e603f953 GIT binary patch literal 1298 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/env.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/env.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc4e79970152674aca806fad7e72d184650c744e GIT binary patch literal 9657 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/requirements.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/requirements.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b8cfd147bbf23e22f54f04a2011f05783145649 GIT binary patch literal 8774 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 T{opnpM~Jh^ypo4gmVDwrH7g6= literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/schemacompare.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/__pycache__/schemacompare.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88c0ec9b34a53d31957bc1577a42ce3684f74491 GIT binary patch literal 5543 zcmds5TW{P%6!wh0UhihJ*|dR{5}*>-1(GdPLOhfzQcAc-h?W8dYHjSHan|eIjGdCK zHoPQH2qBPo1Elti{11MGUy+db3*5gm_FlW81nm=E&3Mju#^?5(GiUwo>}*lPRr#^z z|9nc*ej;M{(m|}CMW3VdG>-*Zli7^Xmc1yBW07O0{(18N3T0aVGG1vLw*2&(MOftmv~1FGW9gPI3b@=jgVs;7T} zJX)0{GabL}1g-Fdt6#w08L7AXd<#$IuDQW}aqqk9qu1 zvvp7R4BVlGXX2jo^5ADBrPWrmU67XwOb;*RSW!$Y{|vt7i*Cpi={Rm*I|uXJrKt>4j33^trIvA1 zEtcDRiRCzc=*Ny@k>E-r*12z3&E?*T5q%9TCtN6fZ(-2YwzMtQWm~MFM~jN$pm;-< z=RI~+i*+?;V9q$;SG12;KH~d)U+;2HYgqEtu8w;ldSBfwc|*+?VbzeNW|C{gn?feseGct; z<%F~&$0erYh;7H2!8kgLPGgJA;AdHdnSXI=y2(n+;+@lDW};mqMj|QtMk;~mYPSK5 zJFEm)ND~uBOrEA2SAxk3#uLN|h2uPkM?L?HGp6V3MOuNMl3I$VynmD#K~a(cWDXtSGa?BsX6aT%&AA{)F4AuW?F1nt=dk`RBhiZ#l13v( znTP_~P2HDSiA*tOC8gGR%=b)m|A^x!W0<)1IWK}3!Z2^08^SP1Gnc>Z((laB1o^GMa$^=nRCYDo_@%XJ5}yS?o?SHxl`qP+w_Xap{0HfeJ%9F z?wU(4>s>VNJA`5(O`=_nREqT6a{^CtK5b443e&AH*sJPk06m0F@?q+fUIXO__BFJK z5|_rzL6TE)91jo*hE>{l1sy>`E}%zZQ^^g$i1paGouj!s?0{vI8aH_BvRZ3mt=YiZ zfe!gpN+mC$Dbb@?s@!GItV&Ujg_6rIdp>fw8#|3p>+)ta(eVVeizH$h9qCrq0k^*l zUizb-qk39S9dR!a;|y`VywqbxfO8oMm0jdu%D~E?>lnmjQW*0~ZJ%|@U3Oc)qiq@c z{5>sPG?4WhMfn2L=oiC?2W+2zpoRQ>oC#`Q{##TaU5;M=J_mFe>z(qYEXb4)=YM5S zb$Io1)sp0RN%pp9#BN)PDDqojl;m9;hJGMWHN+A}WKwu}*A?v;4<$u|B=2PKN(|a* zThu$`uThXu{*?lcoE7YeDB){3DL5;uU;kL`nI~O~;$W?smvqv|2dFzo-B_A_oaP4H z2DqlQMPV3`SuIvHtfHk$nsw1eE^^We+6l6C(odtC!fmX=UZ_0$Fg{TNKSCYm_P6$q zx~Cw?kVfj@A*Ej@2{SxB(#w~l+>Z}d!k5Cm1LRQeHi6pHM8v>n7^ zH&&I(u~GaMDUT8yX|RL{=6vB#u2=rxM&-B8L&q9p>M?zt#K?SbJllI5Cr{uduhEP8 zRYXD3+*EKmpBZDVC53-8d_(M=2$J$i2qLT04T7o>8?S{!HC1d6-tw z@-VBKt*(x$*+A8NJ*%3pQ`IafHB}3es)h8-Sr`~{5amM{rgAVXs*x|Ll#WzsaFiR= ztqGUU$$45w03Nq1ol!T?B7#5<-4#Oj@7y$)!S1TBS?BB|+fNoD~Qx_XBSbo)dTvTxR;)kX9g3?7%mHZVZ)cYDE)syWk8xWs03H_jyeof r2uWfxlu1-hp{gUXE~Q^?to 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 Bb->c + # -> d -> e + # -> f + d = util.rev_id() + e = util.rev_id() + f = util.rev_id() + + script = ScriptDirectory.from_config(cfg) + script.generate_revision( + d, "revision d from b", head=b, splice=True, refresh=True + ) + write_script( + script, + d, + """\ +"Rev D" +revision = '%s' +down_revision = '%s' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 4") + + +def downgrade(): + op.execute("DROP STEP 4") + +""" + % (d, b), + ) + + script.generate_revision( + e, "revision e from d", head=d, splice=True, refresh=True + ) + write_script( + script, + e, + """\ +"Rev E" +revision = '%s' +down_revision = '%s' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 5") + + +def downgrade(): + op.execute("DROP STEP 5") + +""" + % (e, d), + ) + + script.generate_revision( + f, "revision f from b", head=b, splice=True, refresh=True + ) + write_script( + script, + f, + """\ +"Rev F" +revision = '%s' +down_revision = '%s' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 6") + + +def downgrade(): + op.execute("DROP STEP 6") + +""" + % (f, b), + ) + + return d, e, f + + +def _multidb_testing_config(engines): + """alembic.ini fixture to work exactly with the 'multidb' template""" + + dir_ = os.path.join(_get_staging_directory(), "scripts") + + sqlalchemy_future = "future" in config.db.__class__.__module__ + + databases = ", ".join(engines.keys()) + engines = "\n\n".join( + "[%s]\n" "sqlalchemy.url = %s" % (key, value.url) + for key, value in engines.items() + ) + + return _write_config_file( + """ +[alembic] +script_location = %s +sourceless = false +sqlalchemy.future = %s +databases = %s + +%s +[loggers] +keys = root + +[handlers] +keys = console + +[logger_root] +level = WARNING +handlers = console +qualname = + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatters] +keys = generic + +[formatter_generic] +format = %%(levelname)-5.5s [%%(name)s] %%(message)s +datefmt = %%H:%%M:%%S + """ + % (dir_, "true" if sqlalchemy_future else "false", databases, engines) + ) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/fixtures.py b/venv/lib/python3.8/site-packages/alembic/testing/fixtures.py new file mode 100644 index 000000000..3b5ce596e --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/fixtures.py @@ -0,0 +1,318 @@ +from __future__ import annotations + +import configparser +from contextlib import contextmanager +import io +import re +from typing import Any +from typing import Dict + +from sqlalchemy import Column +from sqlalchemy import inspect +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import testing +from sqlalchemy import text +from sqlalchemy.testing import config +from sqlalchemy.testing import mock +from sqlalchemy.testing.assertions import eq_ +from sqlalchemy.testing.fixtures import TablesTest as SQLAlchemyTablesTest +from sqlalchemy.testing.fixtures import TestBase as SQLAlchemyTestBase + +import alembic +from .assertions import _get_dialect +from ..environment import EnvironmentContext +from ..migration import MigrationContext +from ..operations import Operations +from ..util import sqla_compat +from ..util.sqla_compat import create_mock_engine +from ..util.sqla_compat import sqla_14 +from ..util.sqla_compat import sqla_2 + + +testing_config = configparser.ConfigParser() +testing_config.read(["test.cfg"]) + + +class TestBase(SQLAlchemyTestBase): + is_sqlalchemy_future = sqla_2 + + @testing.fixture() + def ops_context(self, migration_context): + with migration_context.begin_transaction(_per_migration=True): + yield Operations(migration_context) + + @testing.fixture + def migration_context(self, connection): + return MigrationContext.configure( + connection, opts=dict(transaction_per_migration=True) + ) + + @testing.fixture + def as_sql_migration_context(self, connection): + return MigrationContext.configure( + connection, opts=dict(transaction_per_migration=True, as_sql=True) + ) + + @testing.fixture + def connection(self): + with config.db.connect() as conn: + yield conn + + +class TablesTest(TestBase, SQLAlchemyTablesTest): + pass + + +if sqla_14: + from sqlalchemy.testing.fixtures import FutureEngineMixin +else: + + class FutureEngineMixin: # type:ignore[no-redef] + __requires__ = ("sqlalchemy_14",) + + +FutureEngineMixin.is_sqlalchemy_future = True + + +def capture_db(dialect="postgresql://"): + buf = [] + + def dump(sql, *multiparams, **params): + buf.append(str(sql.compile(dialect=engine.dialect))) + + engine = create_mock_engine(dialect, dump) + return engine, buf + + +_engs: Dict[Any, Any] = {} + + +@contextmanager +def capture_context_buffer(**kw): + if kw.pop("bytes_io", False): + buf = io.BytesIO() + else: + buf = io.StringIO() + + kw.update({"dialect_name": "sqlite", "output_buffer": buf}) + conf = EnvironmentContext.configure + + def configure(*arg, **opt): + opt.update(**kw) + return conf(*arg, **opt) + + with mock.patch.object(EnvironmentContext, "configure", configure): + yield buf + + +@contextmanager +def capture_engine_context_buffer(**kw): + from .env import _sqlite_file_db + from sqlalchemy import event + + buf = io.StringIO() + + eng = _sqlite_file_db() + + conn = eng.connect() + + @event.listens_for(conn, "before_cursor_execute") + def bce(conn, cursor, statement, parameters, context, executemany): + buf.write(statement + "\n") + + kw.update({"connection": conn}) + conf = EnvironmentContext.configure + + def configure(*arg, **opt): + opt.update(**kw) + return conf(*arg, **opt) + + with mock.patch.object(EnvironmentContext, "configure", configure): + yield buf + + +def op_fixture( + dialect="default", + as_sql=False, + naming_convention=None, + literal_binds=False, + native_boolean=None, +): + opts = {} + if naming_convention: + opts["target_metadata"] = MetaData(naming_convention=naming_convention) + + class buffer_: + def __init__(self): + self.lines = [] + + def write(self, msg): + msg = msg.strip() + msg = re.sub(r"[\n\t]", "", msg) + if as_sql: + # the impl produces soft tabs, + # so search for blocks of 4 spaces + msg = re.sub(r" ", "", msg) + msg = re.sub(r"\;\n*$", "", msg) + + self.lines.append(msg) + + def flush(self): + pass + + buf = buffer_() + + class ctx(MigrationContext): + def get_buf(self): + return buf + + def clear_assertions(self): + buf.lines[:] = [] + + def assert_(self, *sql): + # TODO: make this more flexible about + # whitespace and such + eq_(buf.lines, [re.sub(r"[\n\t]", "", s) for s in sql]) + + def assert_contains(self, sql): + for stmt in buf.lines: + if re.sub(r"[\n\t]", "", sql) in stmt: + return + else: + assert False, "Could not locate fragment %r in %r" % ( + sql, + buf.lines, + ) + + if as_sql: + opts["as_sql"] = as_sql + if literal_binds: + opts["literal_binds"] = literal_binds + if not sqla_14 and dialect == "mariadb": + ctx_dialect = _get_dialect("mysql") + ctx_dialect.server_version_info = (10, 4, 0, "MariaDB") + + else: + ctx_dialect = _get_dialect(dialect) + if native_boolean is not None: + ctx_dialect.supports_native_boolean = native_boolean + # this is new as of SQLAlchemy 1.2.7 and is used by SQL Server, + # which breaks assumptions in the alembic test suite + ctx_dialect.non_native_boolean_check_constraint = True + if not as_sql: + + def execute(stmt, *multiparam, **param): + if isinstance(stmt, str): + stmt = text(stmt) + assert stmt.supports_execution + sql = str(stmt.compile(dialect=ctx_dialect)) + + buf.write(sql) + + connection = mock.Mock(dialect=ctx_dialect, execute=execute) + else: + opts["output_buffer"] = buf + connection = None + context = ctx(ctx_dialect, connection, opts) + + alembic.op._proxy = Operations(context) + return context + + +class AlterColRoundTripFixture: + # since these tests are about syntax, use more recent SQLAlchemy as some of + # the type / server default compare logic might not work on older + # SQLAlchemy versions as seems to be the case for SQLAlchemy 1.1 on Oracle + + __requires__ = ("alter_column",) + + def setUp(self): + self.conn = config.db.connect() + self.ctx = MigrationContext.configure(self.conn) + self.op = Operations(self.ctx) + self.metadata = MetaData() + + def _compare_type(self, t1, t2): + c1 = Column("q", t1) + c2 = Column("q", t2) + assert not self.ctx.impl.compare_type( + c1, c2 + ), "Type objects %r and %r didn't compare as equivalent" % (t1, t2) + + def _compare_server_default(self, t1, s1, t2, s2): + c1 = Column("q", t1, server_default=s1) + c2 = Column("q", t2, server_default=s2) + assert not self.ctx.impl.compare_server_default( + c1, c2, s2, s1 + ), "server defaults %r and %r didn't compare as equivalent" % (s1, s2) + + def tearDown(self): + sqla_compat._safe_rollback_connection_transaction(self.conn) + with self.conn.begin(): + self.metadata.drop_all(self.conn) + self.conn.close() + + def _run_alter_col(self, from_, to_, compare=None): + column = Column( + from_.get("name", "colname"), + from_.get("type", String(10)), + nullable=from_.get("nullable", True), + server_default=from_.get("server_default", None), + # comment=from_.get("comment", None) + ) + t = Table("x", self.metadata, column) + + with sqla_compat._ensure_scope_for_ddl(self.conn): + t.create(self.conn) + insp = inspect(self.conn) + old_col = insp.get_columns("x")[0] + + # TODO: conditional comment support + self.op.alter_column( + "x", + column.name, + existing_type=column.type, + existing_server_default=( + column.server_default + if column.server_default is not None + else False + ), + existing_nullable=True if column.nullable else False, + # existing_comment=column.comment, + nullable=to_.get("nullable", None), + # modify_comment=False, + server_default=to_.get("server_default", False), + new_column_name=to_.get("name", None), + type_=to_.get("type", None), + ) + + insp = inspect(self.conn) + new_col = insp.get_columns("x")[0] + + if compare is None: + compare = to_ + + eq_( + new_col["name"], + compare["name"] if "name" in compare else column.name, + ) + self._compare_type( + new_col["type"], compare.get("type", old_col["type"]) + ) + eq_(new_col["nullable"], compare.get("nullable", column.nullable)) + self._compare_server_default( + new_col["type"], + new_col.get("default", None), + compare.get("type", old_col["type"]), + ( + compare["server_default"].text + if "server_default" in compare + else ( + column.server_default.arg.text + if column.server_default is not None + else None + ) + ), + ) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/plugin/__init__.py b/venv/lib/python3.8/site-packages/alembic/testing/plugin/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad4af693ab4596d4816fc8f02bcaa58cc874a8aa GIT binary patch literal 215 zcmYk0Jqp4=5QR5jAws}wSR_-Vu@JGa6dSQ#^7D*CHk&282}Hb*_wW?99>LDa4G8++ zeZ#zY#T>(MCcNf%;&DDl{44Ow#@kLj3ULsNV1Ep$oO+w!cwYGXPlFRb*xJ=sw-KO Xl&slF8JpoTUDZv|DXhn@Uf$vZi1s^m literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00b9bb42484a4278d6260b419ddce02c09ac1d0f GIT binary patch literal 271 zcmYjMyK2Kg5Inhw5g76bYK@T=;wD81A*3{JLYg$qBI S?s%TxXxr`;Zl@" + + +class CompareCheckConstraint: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + return ( + isinstance(other, schema.CheckConstraint) + and self.constraint.name == other.name + and (str(self.constraint.sqltext) == str(other.sqltext)) + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + + def __ne__(self, other): + return not self.__eq__(other) + + +class CompareForeignKey: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + r1 = ( + isinstance(other, schema.ForeignKeyConstraint) + and self.constraint.name == other.name + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + if not r1: + return False + for c1, c2 in zip_longest(self.constraint.columns, other.columns): + if (c1 is None and c2 is not None) or ( + c2 is None and c1 is not None + ): + return False + if CompareColumn(c1) != c2: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + +class ComparePrimaryKey: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + r1 = ( + isinstance(other, schema.PrimaryKeyConstraint) + and self.constraint.name == other.name + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + if not r1: + return False + + for c1, c2 in zip_longest(self.constraint.columns, other.columns): + if (c1 is None and c2 is not None) or ( + c2 is None and c1 is not None + ): + return False + if CompareColumn(c1) != c2: + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) + + +class CompareUniqueConstraint: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + r1 = ( + isinstance(other, schema.UniqueConstraint) + and self.constraint.name == other.name + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + if not r1: + return False + + for c1, c2 in zip_longest(self.constraint.columns, other.columns): + if (c1 is None and c2 is not None) or ( + c2 is None and c1 is not None + ): + return False + if CompareColumn(c1) != c2: + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/__init__.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/__init__.py new file mode 100644 index 000000000..3da498d28 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/__init__.py @@ -0,0 +1,7 @@ +from .test_autogen_comments import * # noqa +from .test_autogen_computed import * # noqa +from .test_autogen_diffs import * # noqa +from .test_autogen_fks import * # noqa +from .test_autogen_identity import * # noqa +from .test_environment import * # noqa +from .test_op import * # noqa diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88fd0a6f62fa94400a5b39f6fe1f6a37d80d8e21 GIT binary patch literal 423 zcmZXQu};G<5Qd!uXoaX$A+fUtDZQZ!LxmU^5esz55+(H|J;kxD*ohQ)BHn|i=*q+^ zFyRzSP&mu?>F)l!v%H?oPMNd$8_;FI*!vj#w$KH)?wXZzobf=gr*8(Hp=acI8eq@ P6L>Rzk~wp38vL9uU4MO3 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec38755ea1db960eb5b30d646e97e8ebf140101e GIT binary patch literal 8089 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 O5k2U&)!HvKzW6^47KIN0 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37f191589d8da9b232b7805490612d957be22b73 GIT binary patch literal 4132 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 VLoB6Y?2VxTN&hsvkH*B-{|mp{`I`U$ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..123a0baa653e0e01b939bc4fdaf43aefe9b3f052 GIT binary patch literal 7156 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47149f90ee55ee0c173f487deb38e715fdae05aa GIT binary patch literal 19496 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

d0gosb= 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 O4>ElIL*0*P)cOxwOh8Tm literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/_autogen_fixtures.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/_autogen_fixtures.py new file mode 100644 index 000000000..d838ebef1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/_autogen_fixtures.py @@ -0,0 +1,335 @@ +from __future__ import annotations + +from typing import Any +from typing import Dict +from typing import Set + +from sqlalchemy import CHAR +from sqlalchemy import CheckConstraint +from sqlalchemy import Column +from sqlalchemy import event +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import inspect +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Numeric +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import Text +from sqlalchemy import text +from sqlalchemy import UniqueConstraint + +from ... import autogenerate +from ... import util +from ...autogenerate import api +from ...ddl.base import _fk_spec +from ...migration import MigrationContext +from ...operations import ops +from ...testing import config +from ...testing import eq_ +from ...testing.env import clear_staging_env +from ...testing.env import staging_env + +names_in_this_test: Set[Any] = set() + + +@event.listens_for(Table, "after_parent_attach") +def new_table(table, parent): + names_in_this_test.add(table.name) + + +def _default_include_object(obj, name, type_, reflected, compare_to): + if type_ == "table": + return name in names_in_this_test + else: + return True + + +_default_object_filters: Any = _default_include_object + +_default_name_filters: Any = None + + +class ModelOne: + __requires__ = ("unique_constraint_reflection",) + + schema: Any = None + + @classmethod + def _get_db_schema(cls): + schema = cls.schema + + m = MetaData(schema=schema) + + Table( + "user", + m, + Column("id", Integer, primary_key=True), + Column("name", String(50)), + Column("a1", Text), + Column("pw", String(50)), + Index("pw_idx", "pw"), + ) + + Table( + "address", + m, + Column("id", Integer, primary_key=True), + Column("email_address", String(100), nullable=False), + ) + + Table( + "order", + m, + Column("order_id", Integer, primary_key=True), + Column( + "amount", + Numeric(8, 2), + nullable=False, + server_default=text("0"), + ), + CheckConstraint("amount >= 0", name="ck_order_amount"), + ) + + Table( + "extra", + m, + Column("x", CHAR), + Column("uid", Integer, ForeignKey("user.id")), + ) + + return m + + @classmethod + def _get_model_schema(cls): + schema = cls.schema + + m = MetaData(schema=schema) + + Table( + "user", + m, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", Text, server_default="x"), + ) + + Table( + "address", + m, + Column("id", Integer, primary_key=True), + Column("email_address", String(100), nullable=False), + Column("street", String(50)), + UniqueConstraint("email_address", name="uq_email"), + ) + + Table( + "order", + m, + Column("order_id", Integer, primary_key=True), + Column( + "amount", + Numeric(10, 2), + nullable=True, + server_default=text("0"), + ), + Column("user_id", Integer, ForeignKey("user.id")), + CheckConstraint("amount > -1", name="ck_order_amount"), + ) + + Table( + "item", + m, + Column("id", Integer, primary_key=True), + Column("description", String(100)), + Column("order_id", Integer, ForeignKey("order.order_id")), + CheckConstraint("len(description) > 5"), + ) + return m + + +class _ComparesFKs: + def _assert_fk_diff( + self, + diff, + type_, + source_table, + source_columns, + target_table, + target_columns, + name=None, + conditional_name=None, + source_schema=None, + onupdate=None, + ondelete=None, + initially=None, + deferrable=None, + ): + # the public API for ForeignKeyConstraint was not very rich + # in 0.7, 0.8, so here we use the well-known but slightly + # private API to get at its elements + ( + fk_source_schema, + fk_source_table, + fk_source_columns, + fk_target_schema, + fk_target_table, + fk_target_columns, + fk_onupdate, + fk_ondelete, + fk_deferrable, + fk_initially, + ) = _fk_spec(diff[1]) + + eq_(diff[0], type_) + eq_(fk_source_table, source_table) + eq_(fk_source_columns, source_columns) + eq_(fk_target_table, target_table) + eq_(fk_source_schema, source_schema) + eq_(fk_onupdate, onupdate) + eq_(fk_ondelete, ondelete) + eq_(fk_initially, initially) + eq_(fk_deferrable, deferrable) + + eq_([elem.column.name for elem in diff[1].elements], target_columns) + if conditional_name is not None: + if conditional_name == "servergenerated": + fks = inspect(self.bind).get_foreign_keys(source_table) + server_fk_name = fks[0]["name"] + eq_(diff[1].name, server_fk_name) + else: + eq_(diff[1].name, conditional_name) + else: + eq_(diff[1].name, name) + + +class AutogenTest(_ComparesFKs): + def _flatten_diffs(self, diffs): + for d in diffs: + if isinstance(d, list): + yield from self._flatten_diffs(d) + else: + yield d + + @classmethod + def _get_bind(cls): + return config.db + + configure_opts: Dict[Any, Any] = {} + + @classmethod + def setup_class(cls): + staging_env() + cls.bind = cls._get_bind() + cls.m1 = cls._get_db_schema() + cls.m1.create_all(cls.bind) + cls.m2 = cls._get_model_schema() + + @classmethod + def teardown_class(cls): + cls.m1.drop_all(cls.bind) + clear_staging_env() + + def setUp(self): + self.conn = conn = self.bind.connect() + ctx_opts = { + "compare_type": True, + "compare_server_default": True, + "target_metadata": self.m2, + "upgrade_token": "upgrades", + "downgrade_token": "downgrades", + "alembic_module_prefix": "op.", + "sqlalchemy_module_prefix": "sa.", + "include_object": _default_object_filters, + "include_name": _default_name_filters, + } + if self.configure_opts: + ctx_opts.update(self.configure_opts) + self.context = context = MigrationContext.configure( + connection=conn, opts=ctx_opts + ) + + self.autogen_context = api.AutogenContext(context, self.m2) + + def tearDown(self): + self.conn.close() + + def _update_context( + self, object_filters=None, name_filters=None, include_schemas=None + ): + if include_schemas is not None: + self.autogen_context.opts["include_schemas"] = include_schemas + if object_filters is not None: + self.autogen_context._object_filters = [object_filters] + if name_filters is not None: + self.autogen_context._name_filters = [name_filters] + return self.autogen_context + + +class AutogenFixtureTest(_ComparesFKs): + def _fixture( + self, + m1, + m2, + include_schemas=False, + opts=None, + object_filters=_default_object_filters, + name_filters=_default_name_filters, + return_ops=False, + max_identifier_length=None, + ): + if max_identifier_length: + dialect = self.bind.dialect + existing_length = dialect.max_identifier_length + dialect.max_identifier_length = ( + dialect._user_defined_max_identifier_length + ) = max_identifier_length + try: + self._alembic_metadata, model_metadata = m1, m2 + for m in util.to_list(self._alembic_metadata): + m.create_all(self.bind) + + with self.bind.connect() as conn: + ctx_opts = { + "compare_type": True, + "compare_server_default": True, + "target_metadata": model_metadata, + "upgrade_token": "upgrades", + "downgrade_token": "downgrades", + "alembic_module_prefix": "op.", + "sqlalchemy_module_prefix": "sa.", + "include_object": object_filters, + "include_name": name_filters, + "include_schemas": include_schemas, + } + if opts: + ctx_opts.update(opts) + self.context = context = MigrationContext.configure( + connection=conn, opts=ctx_opts + ) + + autogen_context = api.AutogenContext(context, model_metadata) + uo = ops.UpgradeOps(ops=[]) + autogenerate._produce_net_changes(autogen_context, uo) + + if return_ops: + return uo + else: + return uo.as_diffs() + finally: + if max_identifier_length: + dialect = self.bind.dialect + dialect.max_identifier_length = ( + dialect._user_defined_max_identifier_length + ) = existing_length + + def setUp(self): + staging_env() + self.bind = config.db + + def tearDown(self): + if hasattr(self, "_alembic_metadata"): + for m in util.to_list(self._alembic_metadata): + m.drop_all(self.bind) + clear_staging_env() diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_comments.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_comments.py new file mode 100644 index 000000000..7ef074f57 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_comments.py @@ -0,0 +1,242 @@ +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table + +from ._autogen_fixtures import AutogenFixtureTest +from ...testing import eq_ +from ...testing import mock +from ...testing import TestBase + + +class AutogenerateCommentsTest(AutogenFixtureTest, TestBase): + __backend__ = True + + __requires__ = ("comments",) + + def test_existing_table_comment_no_change(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_add_table_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table("some_table", m1, Column("test", String(10), primary_key=True)) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_table_comment") + eq_(diffs[0][1].comment, "this is some table") + eq_(diffs[0][2], None) + + def test_remove_table_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + Table("some_table", m2, Column("test", String(10), primary_key=True)) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "remove_table_comment") + eq_(diffs[0][1].comment, None) + + def test_alter_table_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + comment="this is also some table", + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_table_comment") + eq_(diffs[0][1].comment, "this is also some table") + eq_(diffs[0][2], "this is some table") + + def test_existing_column_comment_no_change(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_add_column_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + diffs = self._fixture(m1, m2) + eq_( + diffs, + [ + [ + ( + "modify_comment", + None, + "some_table", + "amount", + { + "existing_nullable": True, + "existing_type": mock.ANY, + "existing_server_default": False, + }, + None, + "the amount", + ) + ] + ], + ) + + def test_remove_column_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float), + ) + + diffs = self._fixture(m1, m2) + eq_( + diffs, + [ + [ + ( + "modify_comment", + None, + "some_table", + "amount", + { + "existing_nullable": True, + "existing_type": mock.ANY, + "existing_server_default": False, + }, + "the amount", + None, + ) + ] + ], + ) + + def test_alter_column_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the adjusted amount"), + ) + + diffs = self._fixture(m1, m2) + + eq_( + diffs, + [ + [ + ( + "modify_comment", + None, + "some_table", + "amount", + { + "existing_nullable": True, + "existing_type": mock.ANY, + "existing_server_default": False, + }, + "the amount", + "the adjusted amount", + ) + ] + ], + ) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_computed.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_computed.py new file mode 100644 index 000000000..04a3caf07 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_computed.py @@ -0,0 +1,204 @@ +import sqlalchemy as sa +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Table + +from ._autogen_fixtures import AutogenFixtureTest +from ... import testing +from ...testing import config +from ...testing import eq_ +from ...testing import exclusions +from ...testing import is_ +from ...testing import is_true +from ...testing import mock +from ...testing import TestBase + + +class AutogenerateComputedTest(AutogenFixtureTest, TestBase): + __requires__ = ("computed_columns",) + __backend__ = True + + def test_add_computed_column(self): + m1 = MetaData() + m2 = MetaData() + + Table("user", m1, Column("id", Integer, primary_key=True)) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("foo", Integer, sa.Computed("5")), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_column") + eq_(diffs[0][2], "user") + eq_(diffs[0][3].name, "foo") + c = diffs[0][3].computed + + is_true(isinstance(c, sa.Computed)) + is_(c.persisted, None) + eq_(str(c.sqltext), "5") + + def test_remove_computed_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("foo", Integer, sa.Computed("5")), + ) + + Table("user", m2, Column("id", Integer, primary_key=True)) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "remove_column") + eq_(diffs[0][2], "user") + c = diffs[0][3] + eq_(c.name, "foo") + + if config.requirements.computed_reflects_normally.enabled: + is_true(isinstance(c.computed, sa.Computed)) + else: + is_(c.computed, None) + + if config.requirements.computed_reflects_as_server_default.enabled: + is_true(isinstance(c.server_default, sa.DefaultClause)) + eq_(str(c.server_default.arg.text), "5") + elif config.requirements.computed_reflects_normally.enabled: + is_true(isinstance(c.computed, sa.Computed)) + else: + is_(c.computed, None) + + @testing.combinations( + lambda: (None, sa.Computed("bar*5")), + (lambda: (sa.Computed("bar*5"), None)), + lambda: ( + sa.Computed("bar*5"), + sa.Computed("bar * 42", persisted=True), + ), + lambda: (sa.Computed("bar*5"), sa.Computed("bar * 42")), + ) + @config.requirements.computed_reflects_normally + def test_cant_change_computed_warning(self, test_case): + arg_before, arg_after = testing.resolve_lambda(test_case, **locals()) + m1 = MetaData() + m2 = MetaData() + + arg_before = [] if arg_before is None else [arg_before] + arg_after = [] if arg_after is None else [arg_after] + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_before), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_after), + ) + + with mock.patch("alembic.util.warn") as mock_warn: + diffs = self._fixture(m1, m2) + + eq_( + mock_warn.mock_calls, + [mock.call("Computed default on user.foo cannot be modified")], + ) + + eq_(list(diffs), []) + + @testing.combinations( + lambda: (None, None), + lambda: (sa.Computed("5"), sa.Computed("5")), + lambda: (sa.Computed("bar*5"), sa.Computed("bar*5")), + lambda: (sa.Computed("bar*5"), sa.Computed("bar * \r\n\t5")), + ( + lambda: (sa.Computed("bar*5"), None), + config.requirements.computed_doesnt_reflect_as_server_default, + ), + ) + def test_computed_unchanged(self, test_case): + arg_before, arg_after = testing.resolve_lambda(test_case, **locals()) + m1 = MetaData() + m2 = MetaData() + + arg_before = [] if arg_before is None else [arg_before] + arg_after = [] if arg_after is None else [arg_after] + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_before), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_after), + ) + + with mock.patch("alembic.util.warn") as mock_warn: + diffs = self._fixture(m1, m2) + eq_(mock_warn.mock_calls, []) + + eq_(list(diffs), []) + + @config.requirements.computed_reflects_as_server_default + def test_remove_computed_default_on_computed(self): + """Asserts the current behavior which is that on PG and Oracle, + the GENERATED ALWAYS AS is reflected as a server default which we can't + tell is actually "computed", so these come out as a modification to + the server default. + + """ + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, sa.Computed("bar + 42")), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0][0], "modify_default") + eq_(diffs[0][0][2], "user") + eq_(diffs[0][0][3], "foo") + old = diffs[0][0][-2] + new = diffs[0][0][-1] + + is_(new, None) + is_true(isinstance(old, sa.DefaultClause)) + + if exclusions.against(config, "postgresql"): + eq_(str(old.arg.text), "(bar + 42)") + elif exclusions.against(config, "oracle"): + eq_(str(old.arg.text), '"BAR"+42') diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_diffs.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_diffs.py new file mode 100644 index 000000000..75bcd37ae --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_diffs.py @@ -0,0 +1,273 @@ +from sqlalchemy import BigInteger +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Table +from sqlalchemy.testing import in_ + +from ._autogen_fixtures import AutogenFixtureTest +from ... import testing +from ...testing import config +from ...testing import eq_ +from ...testing import is_ +from ...testing import TestBase + + +class AlterColumnTest(AutogenFixtureTest, TestBase): + __backend__ = True + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, primary_key=pk)) + Table("a", m2, Column("x", Integer, comment="x", primary_key=pk)) + + alter_col = self._assert_alter_col(m1, m2, pk) + eq_(alter_col.modify_comment, "x") + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled_in_notnull(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, nullable=False, primary_key=pk)) + Table( + "a", + m2, + Column("x", Integer, nullable=False, comment="x", primary_key=pk), + ) + + self._assert_alter_col(m1, m2, pk, nullable=False) + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled_in_comment(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, comment="old", primary_key=pk)) + Table("a", m2, Column("x", Integer, comment="new", primary_key=pk)) + + alter_col = self._assert_alter_col(m1, m2, pk) + eq_(alter_col.existing_comment, "old") + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled_in_server_default(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", m1, Column("x", Integer, server_default="5", primary_key=pk) + ) + Table( + "a", + m2, + Column( + "x", Integer, server_default="5", comment="new", primary_key=pk + ), + ) + + alter_col = self._assert_alter_col(m1, m2, pk) + in_("5", alter_col.existing_server_default.arg.text) + + def _assert_alter_col(self, m1, m2, pk, nullable=None): + ops = self._fixture(m1, m2, return_ops=True) + modify_table = ops.ops[-1] + alter_col = modify_table.ops[0] + + if nullable is None: + eq_(alter_col.existing_nullable, not pk) + else: + eq_(alter_col.existing_nullable, nullable) + assert alter_col.existing_type._compare_type_affinity(Integer()) + return alter_col + + +class AutoincrementTest(AutogenFixtureTest, TestBase): + __backend__ = True + __requires__ = ("integer_subtype_comparisons",) + + def test_alter_column_autoincrement_none(self): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, nullable=False)) + Table("a", m2, Column("x", Integer, nullable=True)) + + ops = self._fixture(m1, m2, return_ops=True) + assert "autoincrement" not in ops.ops[0].ops[0].kw + + def test_alter_column_autoincrement_pk_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("x", Integer, primary_key=True, autoincrement=False), + ) + Table( + "a", + m2, + Column("x", BigInteger, primary_key=True, autoincrement=False), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], False) + + def test_alter_column_autoincrement_pk_implicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, primary_key=True)) + Table("a", m2, Column("x", BigInteger, primary_key=True)) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) + + def test_alter_column_autoincrement_pk_explicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", m1, Column("x", Integer, primary_key=True, autoincrement=True) + ) + Table( + "a", + m2, + Column("x", BigInteger, primary_key=True, autoincrement=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) + + def test_alter_column_autoincrement_nonpk_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer, autoincrement=False), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger, autoincrement=False), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], False) + + def test_alter_column_autoincrement_nonpk_implicit_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger), + ) + + ops = self._fixture(m1, m2, return_ops=True) + assert "autoincrement" not in ops.ops[0].ops[0].kw + + def test_alter_column_autoincrement_nonpk_explicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", Integer, autoincrement=True), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", BigInteger, autoincrement=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) + + def test_alter_column_autoincrement_compositepk_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer, primary_key=True, autoincrement=False), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger, primary_key=True, autoincrement=False), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], False) + + def test_alter_column_autoincrement_compositepk_implicit_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer, primary_key=True), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger, primary_key=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + assert "autoincrement" not in ops.ops[0].ops[0].kw + + @config.requirements.autoincrement_on_composite_pk + def test_alter_column_autoincrement_compositepk_explicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", Integer, primary_key=True, autoincrement=True), + # on SQLA 1.0 and earlier, this being present + # trips the "add KEY for the primary key" so that the + # AUTO_INCREMENT keyword is accepted by MySQL. SQLA 1.1 and + # greater the columns are just reorganized. + mysql_engine="InnoDB", + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", BigInteger, primary_key=True, autoincrement=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_fks.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_fks.py new file mode 100644 index 000000000..0240b98d3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_fks.py @@ -0,0 +1,1190 @@ +from sqlalchemy import Column +from sqlalchemy import ForeignKeyConstraint +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table + +from ._autogen_fixtures import AutogenFixtureTest +from ...testing import combinations +from ...testing import config +from ...testing import eq_ +from ...testing import mock +from ...testing import TestBase + + +class AutogenerateForeignKeysTest(AutogenFixtureTest, TestBase): + __backend__ = True + __requires__ = ("foreign_key_constraint_reflection",) + + def test_remove_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["test2"], ["some_table.test"]), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["test2"], + "some_table", + ["test"], + conditional_name="servergenerated", + ) + + def test_add_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ) + + Table( + "some_table", + m2, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["test2"], ["some_table.test"]), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], "add_fk", "user", ["test2"], "some_table", ["test"] + ) + + def test_no_change(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", Integer), + ForeignKeyConstraint(["test2"], ["some_table.id"]), + ) + + Table( + "some_table", + m2, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", Integer), + ForeignKeyConstraint(["test2"], ["some_table.id"]), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_no_change_composite_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + ), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + ), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_casing_convention_changed_so_put_drops_first(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["test2"], ["some_table.test"], name="MyFK"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + ) + + # foreign key autogen currently does not take "name" into account, + # so change the def just for the purposes of testing the + # add/drop order for now. + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["a1"], ["some_table.test"], name="myfk"), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["test2"], + "some_table", + ["test"], + name="MyFK" if config.requirements.fk_names.enabled else None, + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["a1"], + "some_table", + ["test"], + name="myfk", + ) + + def test_add_composite_fk_with_name(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + name="fk_test_name", + ), + ) + + diffs = self._fixture(m1, m2) + self._assert_fk_diff( + diffs[0], + "add_fk", + "user", + ["other_id_1", "other_id_2"], + "some_table", + ["id_1", "id_2"], + name="fk_test_name", + ) + + @config.requirements.no_name_normalize + def test_remove_composite_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + name="fk_test_name", + ), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["other_id_1", "other_id_2"], + "some_table", + ["id_1", "id_2"], + conditional_name="fk_test_name", + ) + + def test_add_fk_colkeys(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), key="tid1", primary_key=True), + Column("id_2", String(10), key="tid2", primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10), key="oid1"), + Column("other_id_2", String(10), key="oid2"), + ForeignKeyConstraint( + ["oid1", "oid2"], + ["some_table.tid1", "some_table.tid2"], + name="fk_test_name", + ), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "add_fk", + "user", + ["other_id_1", "other_id_2"], + "some_table", + ["id_1", "id_2"], + name="fk_test_name", + ) + + def test_no_change_colkeys(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + ), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), key="tid1", primary_key=True), + Column("id_2", String(10), key="tid2", primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10), key="oid1"), + Column("other_id_2", String(10), key="oid2"), + ForeignKeyConstraint( + ["oid1", "oid2"], ["some_table.tid1", "some_table.tid2"] + ), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + +class IncludeHooksTest(AutogenFixtureTest, TestBase): + __backend__ = True + __requires__ = ("fk_names",) + + @combinations(("object",), ("name",)) + @config.requirements.no_name_normalize + def test_remove_connection_fk(self, hook_type): + m1 = MetaData() + m2 = MetaData() + + ref = Table( + "ref", + m1, + Column("id", Integer, primary_key=True), + ) + t1 = Table( + "t", + m1, + Column("x", Integer), + Column("y", Integer), + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.x], [ref.c.id], name="fk1") + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.y], [ref.c.id], name="fk2") + ) + + ref = Table( + "ref", + m2, + Column("id", Integer, primary_key=True), + ) + Table( + "t", + m2, + Column("x", Integer), + Column("y", Integer), + ) + + if hook_type == "object": + + def include_object(object_, name, type_, reflected, compare_to): + return not ( + isinstance(object_, ForeignKeyConstraint) + and type_ == "foreign_key_constraint" + and reflected + and name == "fk1" + ) + + diffs = self._fixture(m1, m2, object_filters=include_object) + elif hook_type == "name": + + def include_name(name, type_, parent_names): + if name == "fk1": + if type_ == "index": # MariaDB thing + return True + eq_(type_, "foreign_key_constraint") + eq_( + parent_names, + { + "schema_name": None, + "table_name": "t", + "schema_qualified_table_name": "t", + }, + ) + return False + else: + return True + + diffs = self._fixture(m1, m2, name_filters=include_name) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "t", + ["y"], + "ref", + ["id"], + conditional_name="fk2", + ) + eq_(len(diffs), 1) + + def test_add_metadata_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "ref", + m1, + Column("id", Integer, primary_key=True), + ) + Table( + "t", + m1, + Column("x", Integer), + Column("y", Integer), + ) + + ref = Table( + "ref", + m2, + Column("id", Integer, primary_key=True), + ) + t2 = Table( + "t", + m2, + Column("x", Integer), + Column("y", Integer), + ) + t2.append_constraint( + ForeignKeyConstraint([t2.c.x], [ref.c.id], name="fk1") + ) + t2.append_constraint( + ForeignKeyConstraint([t2.c.y], [ref.c.id], name="fk2") + ) + + def include_object(object_, name, type_, reflected, compare_to): + return not ( + isinstance(object_, ForeignKeyConstraint) + and type_ == "foreign_key_constraint" + and not reflected + and name == "fk1" + ) + + diffs = self._fixture(m1, m2, object_filters=include_object) + + self._assert_fk_diff( + diffs[0], "add_fk", "t", ["y"], "ref", ["id"], name="fk2" + ) + eq_(len(diffs), 1) + + @combinations(("object",), ("name",)) + @config.requirements.no_name_normalize + def test_change_fk(self, hook_type): + m1 = MetaData() + m2 = MetaData() + + r1a = Table( + "ref_a", + m1, + Column("a", Integer, primary_key=True), + ) + Table( + "ref_b", + m1, + Column("a", Integer, primary_key=True), + Column("b", Integer, primary_key=True), + ) + t1 = Table( + "t", + m1, + Column("x", Integer), + Column("y", Integer), + Column("z", Integer), + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.x], [r1a.c.a], name="fk1") + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.y], [r1a.c.a], name="fk2") + ) + + Table( + "ref_a", + m2, + Column("a", Integer, primary_key=True), + ) + r2b = Table( + "ref_b", + m2, + Column("a", Integer, primary_key=True), + Column("b", Integer, primary_key=True), + ) + t2 = Table( + "t", + m2, + Column("x", Integer), + Column("y", Integer), + Column("z", Integer), + ) + t2.append_constraint( + ForeignKeyConstraint( + [t2.c.x, t2.c.z], [r2b.c.a, r2b.c.b], name="fk1" + ) + ) + t2.append_constraint( + ForeignKeyConstraint( + [t2.c.y, t2.c.z], [r2b.c.a, r2b.c.b], name="fk2" + ) + ) + + if hook_type == "object": + + def include_object(object_, name, type_, reflected, compare_to): + return not ( + isinstance(object_, ForeignKeyConstraint) + and type_ == "foreign_key_constraint" + and name == "fk1" + ) + + diffs = self._fixture(m1, m2, object_filters=include_object) + elif hook_type == "name": + + def include_name(name, type_, parent_names): + if type_ == "index": + return True # MariaDB thing + + if name == "fk1": + eq_(type_, "foreign_key_constraint") + eq_( + parent_names, + { + "schema_name": None, + "table_name": "t", + "schema_qualified_table_name": "t", + }, + ) + return False + else: + return True + + diffs = self._fixture(m1, m2, name_filters=include_name) + + if hook_type == "object": + self._assert_fk_diff( + diffs[0], "remove_fk", "t", ["y"], "ref_a", ["a"], name="fk2" + ) + self._assert_fk_diff( + diffs[1], + "add_fk", + "t", + ["y", "z"], + "ref_b", + ["a", "b"], + name="fk2", + ) + eq_(len(diffs), 2) + elif hook_type == "name": + eq_( + {(d[0], d[1].name) for d in diffs}, + {("add_fk", "fk2"), ("add_fk", "fk1"), ("remove_fk", "fk2")}, + ) + + +class AutogenerateFKOptionsTest(AutogenFixtureTest, TestBase): + __backend__ = True + + def _fk_opts_fixture(self, old_opts, new_opts): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("tid", Integer), + ForeignKeyConstraint(["tid"], ["some_table.id"], **old_opts), + ) + + Table( + "some_table", + m2, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("tid", Integer), + ForeignKeyConstraint(["tid"], ["some_table.id"], **new_opts), + ) + + return self._fixture(m1, m2) + + @config.requirements.fk_ondelete_is_reflected + def test_add_ondelete(self): + diffs = self._fk_opts_fixture({}, {"ondelete": "cascade"}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete="cascade", + ) + + @config.requirements.fk_ondelete_is_reflected + def test_remove_ondelete(self): + diffs = self._fk_opts_fixture({"ondelete": "CASCADE"}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete="CASCADE", + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete=None, + ) + + def test_nochange_ondelete(self): + """test case sensitivity""" + diffs = self._fk_opts_fixture( + {"ondelete": "caSCAde"}, {"ondelete": "CasCade"} + ) + eq_(diffs, []) + + @config.requirements.fk_onupdate_is_reflected + def test_add_onupdate(self): + diffs = self._fk_opts_fixture({}, {"onupdate": "cascade"}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="cascade", + ) + + @config.requirements.fk_onupdate_is_reflected + def test_remove_onupdate(self): + diffs = self._fk_opts_fixture({"onupdate": "CASCADE"}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="CASCADE", + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + ) + + @config.requirements.fk_onupdate + def test_nochange_onupdate(self): + """test case sensitivity""" + diffs = self._fk_opts_fixture( + {"onupdate": "caSCAde"}, {"onupdate": "CasCade"} + ) + eq_(diffs, []) + + @config.requirements.fk_ondelete_restrict + def test_nochange_ondelete_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + diffs = self._fk_opts_fixture( + {"ondelete": "restrict"}, {"ondelete": "restrict"} + ) + eq_(diffs, []) + + @config.requirements.fk_onupdate_restrict + def test_nochange_onupdate_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + diffs = self._fk_opts_fixture( + {"onupdate": "restrict"}, {"onupdate": "restrict"} + ) + eq_(diffs, []) + + @config.requirements.fk_ondelete_noaction + def test_nochange_ondelete_noaction(self): + """test the NO ACTION option which generally comes back as None""" + + diffs = self._fk_opts_fixture( + {"ondelete": "no action"}, {"ondelete": "no action"} + ) + eq_(diffs, []) + + @config.requirements.fk_onupdate + def test_nochange_onupdate_noaction(self): + """test the NO ACTION option which generally comes back as None""" + + diffs = self._fk_opts_fixture( + {"onupdate": "no action"}, {"onupdate": "no action"} + ) + eq_(diffs, []) + + @config.requirements.fk_ondelete_restrict + def test_change_ondelete_from_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + # note that this is impossible to detect if we change + # from RESTRICT to NO ACTION on MySQL. + diffs = self._fk_opts_fixture( + {"ondelete": "restrict"}, {"ondelete": "cascade"} + ) + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + ondelete=mock.ANY, # MySQL reports None, PG reports RESTRICT + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + ondelete="cascade", + ) + + @config.requirements.fk_ondelete_restrict + def test_change_onupdate_from_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + # note that this is impossible to detect if we change + # from RESTRICT to NO ACTION on MySQL. + diffs = self._fk_opts_fixture( + {"onupdate": "restrict"}, {"onupdate": "cascade"} + ) + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=mock.ANY, # MySQL reports None, PG reports RESTRICT + ondelete=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="cascade", + ondelete=None, + ) + + @config.requirements.fk_ondelete_is_reflected + @config.requirements.fk_onupdate_is_reflected + def test_ondelete_onupdate_combo(self): + diffs = self._fk_opts_fixture( + {"onupdate": "CASCADE", "ondelete": "SET NULL"}, + {"onupdate": "RESTRICT", "ondelete": "RESTRICT"}, + ) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="CASCADE", + ondelete="SET NULL", + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="RESTRICT", + ondelete="RESTRICT", + ) + + @config.requirements.fk_initially + def test_add_initially_deferred(self): + diffs = self._fk_opts_fixture({}, {"initially": "deferred"}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially="deferred", + ) + + @config.requirements.fk_initially + def test_remove_initially_deferred(self): + diffs = self._fk_opts_fixture({"initially": "deferred"}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially="DEFERRED", + deferrable=True, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + ) + + @config.requirements.fk_deferrable + @config.requirements.fk_initially + def test_add_initially_immediate_plus_deferrable(self): + diffs = self._fk_opts_fixture( + {}, {"initially": "immediate", "deferrable": True} + ) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially="immediate", + deferrable=True, + ) + + @config.requirements.fk_deferrable + @config.requirements.fk_initially + def test_remove_initially_immediate_plus_deferrable(self): + diffs = self._fk_opts_fixture( + {"initially": "immediate", "deferrable": True}, {} + ) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, # immediate is the default + deferrable=True, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + deferrable=None, + ) + + @config.requirements.fk_initially + @config.requirements.fk_deferrable + def test_add_initially_deferrable_nochange_one(self): + diffs = self._fk_opts_fixture( + {"deferrable": True, "initially": "immediate"}, + {"deferrable": True, "initially": "immediate"}, + ) + + eq_(diffs, []) + + @config.requirements.fk_initially + @config.requirements.fk_deferrable + def test_add_initially_deferrable_nochange_two(self): + diffs = self._fk_opts_fixture( + {"deferrable": True, "initially": "deferred"}, + {"deferrable": True, "initially": "deferred"}, + ) + + eq_(diffs, []) + + @config.requirements.fk_initially + @config.requirements.fk_deferrable + def test_add_initially_deferrable_nochange_three(self): + diffs = self._fk_opts_fixture( + {"deferrable": None, "initially": "deferred"}, + {"deferrable": None, "initially": "deferred"}, + ) + + eq_(diffs, []) + + @config.requirements.fk_deferrable + def test_add_deferrable(self): + diffs = self._fk_opts_fixture({}, {"deferrable": True}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=True, + ) + + @config.requirements.fk_deferrable_is_reflected + def test_remove_deferrable(self): + diffs = self._fk_opts_fixture({"deferrable": True}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=True, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=None, + ) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_identity.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_identity.py new file mode 100644 index 000000000..3dee9fc99 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_autogen_identity.py @@ -0,0 +1,226 @@ +import sqlalchemy as sa +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Table + +from alembic.util import sqla_compat +from ._autogen_fixtures import AutogenFixtureTest +from ... import testing +from ...testing import config +from ...testing import eq_ +from ...testing import is_true +from ...testing import TestBase + + +class AutogenerateIdentityTest(AutogenFixtureTest, TestBase): + __requires__ = ("identity_columns",) + __backend__ = True + + def test_add_identity_column(self): + m1 = MetaData() + m2 = MetaData() + + Table("user", m1, Column("other", sa.Text)) + + Table( + "user", + m2, + Column("other", sa.Text), + Column( + "id", + Integer, + sa.Identity(start=5, increment=7), + primary_key=True, + ), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_column") + eq_(diffs[0][2], "user") + eq_(diffs[0][3].name, "id") + i = diffs[0][3].identity + + is_true(isinstance(i, sa.Identity)) + eq_(i.start, 5) + eq_(i.increment, 7) + + def test_remove_identity_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column( + "id", + Integer, + sa.Identity(start=2, increment=3), + primary_key=True, + ), + ) + + Table("user", m2) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "remove_column") + eq_(diffs[0][2], "user") + c = diffs[0][3] + eq_(c.name, "id") + + is_true(isinstance(c.identity, sa.Identity)) + eq_(c.identity.start, 2) + eq_(c.identity.increment, 3) + + def test_no_change_identity_column(self): + m1 = MetaData() + m2 = MetaData() + + for m in (m1, m2): + id_ = sa.Identity(start=2) + Table("user", m, Column("id", Integer, id_)) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_dialect_kwargs_changes(self): + m1 = MetaData() + m2 = MetaData() + + if sqla_compat.identity_has_dialect_kwargs: + args = {"oracle_on_null": True, "oracle_order": True} + else: + args = {"on_null": True, "order": True} + + Table("user", m1, Column("id", Integer, sa.Identity(start=2))) + id_ = sa.Identity(start=2, **args) + Table("user", m2, Column("id", Integer, id_)) + + diffs = self._fixture(m1, m2) + if config.db.name == "oracle": + is_true(len(diffs), 1) + eq_(diffs[0][0][0], "modify_default") + else: + eq_(diffs, []) + + @testing.combinations( + (None, dict(start=2)), + (dict(start=2), None), + (dict(start=2), dict(start=2, increment=7)), + (dict(always=False), dict(always=True)), + ( + dict(start=1, minvalue=0, maxvalue=100, cycle=True), + dict(start=1, minvalue=0, maxvalue=100, cycle=False), + ), + ( + dict(start=10, increment=3, maxvalue=9999), + dict(start=10, increment=1, maxvalue=3333), + ), + ) + @config.requirements.identity_columns_alter + def test_change_identity(self, before, after): + arg_before = (sa.Identity(**before),) if before else () + arg_after = (sa.Identity(**after),) if after else () + + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer, *arg_before), + Column("other", sa.Text), + ) + + Table( + "user", + m2, + Column("id", Integer, *arg_after), + Column("other", sa.Text), + ) + + diffs = self._fixture(m1, m2) + + eq_(len(diffs[0]), 1) + diffs = diffs[0][0] + eq_(diffs[0], "modify_default") + eq_(diffs[2], "user") + eq_(diffs[3], "id") + old = diffs[5] + new = diffs[6] + + def check(kw, idt): + if kw: + is_true(isinstance(idt, sa.Identity)) + for k, v in kw.items(): + eq_(getattr(idt, k), v) + else: + is_true(idt in (None, False)) + + check(before, old) + check(after, new) + + def test_add_identity_to_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer), + Column("other", sa.Text), + ) + + Table( + "user", + m2, + Column("id", Integer, sa.Identity(start=2, maxvalue=1000)), + Column("other", sa.Text), + ) + + diffs = self._fixture(m1, m2) + + eq_(len(diffs[0]), 1) + diffs = diffs[0][0] + eq_(diffs[0], "modify_default") + eq_(diffs[2], "user") + eq_(diffs[3], "id") + eq_(diffs[5], None) + added = diffs[6] + + is_true(isinstance(added, sa.Identity)) + eq_(added.start, 2) + eq_(added.maxvalue, 1000) + + def test_remove_identity_from_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer, sa.Identity(start=2, maxvalue=1000)), + Column("other", sa.Text), + ) + + Table( + "user", + m2, + Column("id", Integer), + Column("other", sa.Text), + ) + + diffs = self._fixture(m1, m2) + + eq_(len(diffs[0]), 1) + diffs = diffs[0][0] + eq_(diffs[0], "modify_default") + eq_(diffs[2], "user") + eq_(diffs[3], "id") + eq_(diffs[6], None) + removed = diffs[5] + + is_true(isinstance(removed, sa.Identity)) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/test_environment.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_environment.py new file mode 100644 index 000000000..df2d9afbd --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_environment.py @@ -0,0 +1,364 @@ +import io + +from ...migration import MigrationContext +from ...testing import assert_raises +from ...testing import config +from ...testing import eq_ +from ...testing import is_ +from ...testing import is_false +from ...testing import is_not_ +from ...testing import is_true +from ...testing import ne_ +from ...testing.fixtures import TestBase + + +class MigrationTransactionTest(TestBase): + __backend__ = True + + conn = None + + def _fixture(self, opts): + self.conn = conn = config.db.connect() + + if opts.get("as_sql", False): + self.context = MigrationContext.configure( + dialect=conn.dialect, opts=opts + ) + self.context.output_buffer = self.context.impl.output_buffer = ( + io.StringIO() + ) + else: + self.context = MigrationContext.configure( + connection=conn, opts=opts + ) + return self.context + + def teardown_method(self): + if self.conn: + self.conn.close() + + def test_proxy_transaction_rollback(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + + is_false(self.conn.in_transaction()) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + proxy.rollback() + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_commit(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + proxy.commit() + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_commit(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + with proxy: + pass + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_rollback(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + + def go(): + with proxy: + raise Exception("hi") + + assert_raises(Exception, go) + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_explicit_rollback(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + + with proxy: + is_true(self.conn.in_transaction()) + proxy.rollback() + is_false(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_explicit_commit(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + + with proxy: + is_true(self.conn.in_transaction()) + proxy.commit() + is_false(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + + def test_transaction_per_migration_transactional_ddl(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_migration_non_transactional_ddl(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": False} + ) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_all_transactional_ddl(self): + context = self._fixture({"transactional_ddl": True}) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_true(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_true(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_all_non_transactional_ddl(self): + context = self._fixture({"transactional_ddl": False}) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_all_sqlmode(self): + context = self._fixture({"as_sql": True}) + + context.execute("step 1") + with context.begin_transaction(): + context.execute("step 2") + with context.begin_transaction(_per_migration=True): + context.execute("step 3") + + context.execute("step 4") + context.execute("step 5") + + if context.impl.transactional_ddl: + self._assert_impl_steps( + "step 1", + "BEGIN", + "step 2", + "step 3", + "step 4", + "COMMIT", + "step 5", + ) + else: + self._assert_impl_steps( + "step 1", "step 2", "step 3", "step 4", "step 5" + ) + + def test_transaction_per_migration_sqlmode(self): + context = self._fixture( + {"as_sql": True, "transaction_per_migration": True} + ) + + context.execute("step 1") + with context.begin_transaction(): + context.execute("step 2") + with context.begin_transaction(_per_migration=True): + context.execute("step 3") + + context.execute("step 4") + context.execute("step 5") + + if context.impl.transactional_ddl: + self._assert_impl_steps( + "step 1", + "step 2", + "BEGIN", + "step 3", + "COMMIT", + "step 4", + "step 5", + ) + else: + self._assert_impl_steps( + "step 1", "step 2", "step 3", "step 4", "step 5" + ) + + @config.requirements.autocommit_isolation + def test_autocommit_block(self): + context = self._fixture({"transaction_per_migration": True}) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + with context.autocommit_block(): + # in 1.x, self.conn is separate due to the + # execution_options call. however for future they are the + # same connection and there is a "transaction" block + # despite autocommit + if self.is_sqlalchemy_future: + is_(context.connection, self.conn) + else: + is_not_(context.connection, self.conn) + is_false(self.conn.in_transaction()) + + eq_( + context.connection._execution_options[ + "isolation_level" + ], + "AUTOCOMMIT", + ) + + ne_( + context.connection._execution_options.get( + "isolation_level", None + ), + "AUTOCOMMIT", + ) + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + @config.requirements.autocommit_isolation + def test_autocommit_block_no_transaction(self): + context = self._fixture({"transaction_per_migration": True}) + + is_false(self.conn.in_transaction()) + + with context.autocommit_block(): + is_true(context.connection.in_transaction()) + + # in 1.x, self.conn is separate due to the execution_options + # call. however for future they are the same connection and there + # is a "transaction" block despite autocommit + if self.is_sqlalchemy_future: + is_(context.connection, self.conn) + else: + is_not_(context.connection, self.conn) + is_false(self.conn.in_transaction()) + + eq_( + context.connection._execution_options["isolation_level"], + "AUTOCOMMIT", + ) + + ne_( + context.connection._execution_options.get("isolation_level", None), + "AUTOCOMMIT", + ) + + is_false(self.conn.in_transaction()) + + def test_autocommit_block_transactional_ddl_sqlmode(self): + context = self._fixture( + { + "transaction_per_migration": True, + "transactional_ddl": True, + "as_sql": True, + } + ) + + with context.begin_transaction(): + context.execute("step 1") + with context.begin_transaction(_per_migration=True): + context.execute("step 2") + + with context.autocommit_block(): + context.execute("step 3") + + context.execute("step 4") + + context.execute("step 5") + + self._assert_impl_steps( + "step 1", + "BEGIN", + "step 2", + "COMMIT", + "step 3", + "BEGIN", + "step 4", + "COMMIT", + "step 5", + ) + + def test_autocommit_block_nontransactional_ddl_sqlmode(self): + context = self._fixture( + { + "transaction_per_migration": True, + "transactional_ddl": False, + "as_sql": True, + } + ) + + with context.begin_transaction(): + context.execute("step 1") + with context.begin_transaction(_per_migration=True): + context.execute("step 2") + + with context.autocommit_block(): + context.execute("step 3") + + context.execute("step 4") + + context.execute("step 5") + + self._assert_impl_steps( + "step 1", "step 2", "step 3", "step 4", "step 5" + ) + + def _assert_impl_steps(self, *steps): + to_check = self.context.output_buffer.getvalue() + + self.context.impl.output_buffer = buf = io.StringIO() + for step in steps: + if step == "BEGIN": + self.context.impl.emit_begin() + elif step == "COMMIT": + self.context.impl.emit_commit() + else: + self.context.impl._exec(step) + + eq_(to_check, buf.getvalue()) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/suite/test_op.py b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_op.py new file mode 100644 index 000000000..a63b3f2f9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/suite/test_op.py @@ -0,0 +1,42 @@ +"""Test against the builders in the op.* module.""" + +from sqlalchemy import Column +from sqlalchemy import event +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy.sql import text + +from ...testing.fixtures import AlterColRoundTripFixture +from ...testing.fixtures import TestBase + + +@event.listens_for(Table, "after_parent_attach") +def _add_cols(table, metadata): + if table.name == "tbl_with_auto_appended_column": + table.append_column(Column("bat", Integer)) + + +class BackendAlterColumnTest(AlterColRoundTripFixture, TestBase): + __backend__ = True + + def test_rename_column(self): + self._run_alter_col({}, {"name": "newname"}) + + def test_modify_type_int_str(self): + self._run_alter_col({"type": Integer()}, {"type": String(50)}) + + def test_add_server_default_int(self): + self._run_alter_col({"type": Integer}, {"server_default": text("5")}) + + def test_modify_server_default_int(self): + self._run_alter_col( + {"type": Integer, "server_default": text("2")}, + {"server_default": text("5")}, + ) + + def test_modify_nullable_to_non(self): + self._run_alter_col({}, {"nullable": False}) + + def test_modify_non_nullable_to_nullable(self): + self._run_alter_col({"nullable": False}, {"nullable": True}) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/util.py b/venv/lib/python3.8/site-packages/alembic/testing/util.py new file mode 100644 index 000000000..4517a69f6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/util.py @@ -0,0 +1,126 @@ +# testing/util.py +# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php +from __future__ import annotations + +import types +from typing import Union + +from sqlalchemy.util import inspect_getfullargspec + +from ..util import sqla_2 + + +def flag_combinations(*combinations): + """A facade around @testing.combinations() oriented towards boolean + keyword-based arguments. + + Basically generates a nice looking identifier based on the keywords + and also sets up the argument names. + + E.g.:: + + @testing.flag_combinations( + dict(lazy=False, passive=False), + dict(lazy=True, passive=False), + dict(lazy=False, passive=True), + dict(lazy=False, passive=True, raiseload=True), + ) + + + would result in:: + + @testing.combinations( + ('', False, False, False), + ('lazy', True, False, False), + ('lazy_passive', True, True, False), + ('lazy_passive', True, True, True), + id_='iaaa', + argnames='lazy,passive,raiseload' + ) + + """ + from sqlalchemy.testing import config + + keys = set() + + for d in combinations: + keys.update(d) + + keys = sorted(keys) + + return config.combinations( + *[ + ("_".join(k for k in keys if d.get(k, False)),) + + tuple(d.get(k, False) for k in keys) + for d in combinations + ], + id_="i" + ("a" * len(keys)), + argnames=",".join(keys), + ) + + +def resolve_lambda(__fn, **kw): + """Given a no-arg lambda and a namespace, return a new lambda that + has all the values filled in. + + This is used so that we can have module-level fixtures that + refer to instance-level variables using lambdas. + + """ + + pos_args = inspect_getfullargspec(__fn)[0] + pass_pos_args = {arg: kw.pop(arg) for arg in pos_args} + glb = dict(__fn.__globals__) + glb.update(kw) + new_fn = types.FunctionType(__fn.__code__, glb) + return new_fn(**pass_pos_args) + + +def metadata_fixture(ddl="function"): + """Provide MetaData for a pytest fixture.""" + + from sqlalchemy.testing import config + from . import fixture_functions + + def decorate(fn): + def run_ddl(self): + from sqlalchemy import schema + + metadata = self.metadata = schema.MetaData() + try: + result = fn(self, metadata) + metadata.create_all(config.db) + # TODO: + # somehow get a per-function dml erase fixture here + yield result + finally: + metadata.drop_all(config.db) + + return fixture_functions.fixture(scope=ddl)(run_ddl) + + return decorate + + +def _safe_int(value: str) -> Union[int, str]: + try: + return int(value) + except: + return value + + +def testing_engine(url=None, options=None, future=False): + from sqlalchemy.testing import config + from sqlalchemy.testing.engines import testing_engine + + if not future: + future = getattr(config._current.options, "future_engine", False) + + if not sqla_2: + kw = {"future": future} if future else {} + else: + kw = {} + return testing_engine(url, options, **kw) diff --git a/venv/lib/python3.8/site-packages/alembic/testing/warnings.py b/venv/lib/python3.8/site-packages/alembic/testing/warnings.py new file mode 100644 index 000000000..e87136b85 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/testing/warnings.py @@ -0,0 +1,40 @@ +# testing/warnings.py +# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +import warnings + +from sqlalchemy import exc as sa_exc + +from ..util import sqla_14 + + +def setup_filters(): + """Set global warning behavior for the test suite.""" + + warnings.resetwarnings() + + warnings.filterwarnings("error", category=sa_exc.SADeprecationWarning) + warnings.filterwarnings("error", category=sa_exc.SAWarning) + + # some selected deprecations... + warnings.filterwarnings("error", category=DeprecationWarning) + if not sqla_14: + # 1.3 uses pkg_resources in PluginLoader + warnings.filterwarnings( + "ignore", + "pkg_resources is deprecated as an API", + DeprecationWarning, + ) + try: + import pytest + except ImportError: + pass + else: + warnings.filterwarnings( + "once", category=pytest.PytestDeprecationWarning + ) diff --git a/venv/lib/python3.8/site-packages/alembic/util/__init__.py b/venv/lib/python3.8/site-packages/alembic/util/__init__.py new file mode 100644 index 000000000..4724e1f08 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/util/__init__.py @@ -0,0 +1,35 @@ +from .editor import open_in_editor as open_in_editor +from .exc import AutogenerateDiffsDetected as AutogenerateDiffsDetected +from .exc import CommandError as CommandError +from .langhelpers import _with_legacy_names as _with_legacy_names +from .langhelpers import asbool as asbool +from .langhelpers import dedupe_tuple as dedupe_tuple +from .langhelpers import Dispatcher as Dispatcher +from .langhelpers import EMPTY_DICT as EMPTY_DICT +from .langhelpers import immutabledict as immutabledict +from .langhelpers import memoized_property as memoized_property +from .langhelpers import ModuleClsProxy as ModuleClsProxy +from .langhelpers import not_none as not_none +from .langhelpers import rev_id as rev_id +from .langhelpers import to_list as to_list +from .langhelpers import to_tuple as to_tuple +from .langhelpers import unique_list as unique_list +from .messaging import err as err +from .messaging import format_as_comma as format_as_comma +from .messaging import msg as msg +from .messaging import obfuscate_url_pw as obfuscate_url_pw +from .messaging import status as status +from .messaging import warn as warn +from .messaging import write_outstream as write_outstream +from .pyfiles import coerce_resource_to_filename as coerce_resource_to_filename +from .pyfiles import load_python_file as load_python_file +from .pyfiles import pyc_file_from_path as pyc_file_from_path +from .pyfiles import template_to_file as template_to_file +from .sqla_compat import has_computed as has_computed +from .sqla_compat import sqla_13 as sqla_13 +from .sqla_compat import sqla_14 as sqla_14 +from .sqla_compat import sqla_2 as sqla_2 + + +if not sqla_13: + raise CommandError("SQLAlchemy 1.3.0 or greater is required.") diff --git a/venv/lib/python3.8/site-packages/alembic/util/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/util/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5453c2d20bddedd3b36768d91d9f36c4f4072a6f GIT binary patch literal 1399 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 arqU>eW))$(`qQ5S-6KD4?`&*ut^WuAcZUN2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/util/__pycache__/exc.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/util/__pycache__/exc.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd57e2ff662817d309e6edfc12b468a6556f2920 GIT binary patch literal 508 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 (3, 13) +py311 = sys.version_info >= (3, 11) +py310 = sys.version_info >= (3, 10) +py39 = sys.version_info >= (3, 9) + + +# produce a wrapper that allows encoded text to stream +# into a given buffer, but doesn't close it. +# not sure of a more idiomatic approach to this. +class EncodedIO(io.TextIOWrapper): + def close(self) -> None: + pass + + +if py39: + from importlib import resources as _resources + + importlib_resources = _resources + from importlib import metadata as _metadata + + importlib_metadata = _metadata + from importlib.metadata import EntryPoint as EntryPoint +else: + import importlib_resources # type:ignore # noqa + import importlib_metadata # type:ignore # noqa + from importlib_metadata import EntryPoint # type:ignore # noqa + + +def importlib_metadata_get(group: str) -> Sequence[EntryPoint]: + ep = importlib_metadata.entry_points() + if hasattr(ep, "select"): + return ep.select(group=group) + else: + return ep.get(group, ()) # type: ignore + + +def formatannotation_fwdref( + annotation: Any, base_module: Optional[Any] = None +) -> str: + """vendored from python 3.7""" + # copied over _formatannotation from sqlalchemy 2.0 + + if isinstance(annotation, str): + return annotation + + if getattr(annotation, "__module__", None) == "typing": + return repr(annotation).replace("typing.", "").replace("~", "") + if isinstance(annotation, type): + if annotation.__module__ in ("builtins", base_module): + return repr(annotation.__qualname__) + return annotation.__module__ + "." + annotation.__qualname__ + elif isinstance(annotation, typing.TypeVar): + return repr(annotation).replace("~", "") + return repr(annotation).replace("~", "") + + +def read_config_parser( + file_config: ConfigParser, + file_argument: Sequence[Union[str, os.PathLike[str]]], +) -> List[str]: + if py310: + return file_config.read(file_argument, encoding="locale") + else: + return file_config.read(file_argument) diff --git a/venv/lib/python3.8/site-packages/alembic/util/editor.py b/venv/lib/python3.8/site-packages/alembic/util/editor.py new file mode 100644 index 000000000..f1d1557f7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/util/editor.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +import os +from os.path import exists +from os.path import join +from os.path import splitext +from subprocess import check_call +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional + +from .compat import is_posix +from .exc import CommandError + + +def open_in_editor( + filename: str, environ: Optional[Dict[str, str]] = None +) -> None: + """ + Opens the given file in a text editor. If the environment variable + ``EDITOR`` is set, this is taken as preference. + + Otherwise, a list of commonly installed editors is tried. + + If no editor matches, an :py:exc:`OSError` is raised. + + :param filename: The filename to open. Will be passed verbatim to the + editor command. + :param environ: An optional drop-in replacement for ``os.environ``. Used + mainly for testing. + """ + env = os.environ if environ is None else environ + try: + editor = _find_editor(env) + check_call([editor, filename]) + except Exception as exc: + raise CommandError("Error executing editor (%s)" % (exc,)) from exc + + +def _find_editor(environ: Mapping[str, str]) -> str: + candidates = _default_editors() + for i, var in enumerate(("EDITOR", "VISUAL")): + if var in environ: + user_choice = environ[var] + if exists(user_choice): + return user_choice + if os.sep not in user_choice: + candidates.insert(i, user_choice) + + for candidate in candidates: + path = _find_executable(candidate, environ) + if path is not None: + return path + raise OSError( + "No suitable editor found. Please set the " + '"EDITOR" or "VISUAL" environment variables' + ) + + +def _find_executable( + candidate: str, environ: Mapping[str, str] +) -> Optional[str]: + # Assuming this is on the PATH, we need to determine it's absolute + # location. Otherwise, ``check_call`` will fail + if not is_posix and splitext(candidate)[1] != ".exe": + candidate += ".exe" + for path in environ.get("PATH", "").split(os.pathsep): + value = join(path, candidate) + if exists(value): + return value + return None + + +def _default_editors() -> List[str]: + # Look for an editor. Prefer the user's choice by env-var, fall back to + # most commonly installed editor (nano/vim) + if is_posix: + return ["sensible-editor", "editor", "nano", "vim", "code"] + else: + return ["code.exe", "notepad++.exe", "notepad.exe"] diff --git a/venv/lib/python3.8/site-packages/alembic/util/exc.py b/venv/lib/python3.8/site-packages/alembic/util/exc.py new file mode 100644 index 000000000..0d0496b1e --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/util/exc.py @@ -0,0 +1,6 @@ +class CommandError(Exception): + pass + + +class AutogenerateDiffsDetected(CommandError): + pass diff --git a/venv/lib/python3.8/site-packages/alembic/util/langhelpers.py b/venv/lib/python3.8/site-packages/alembic/util/langhelpers.py new file mode 100644 index 000000000..80d88cbce --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/util/langhelpers.py @@ -0,0 +1,332 @@ +from __future__ import annotations + +import collections +from collections.abc import Iterable +import textwrap +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import List +from typing import Mapping +from typing import MutableMapping +from typing import NoReturn +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union +import uuid +import warnings + +from sqlalchemy.util import asbool as asbool # noqa: F401 +from sqlalchemy.util import immutabledict as immutabledict # noqa: F401 +from sqlalchemy.util import to_list as to_list # noqa: F401 +from sqlalchemy.util import unique_list as unique_list + +from .compat import inspect_getfullargspec + +if True: + # zimports workaround :( + from sqlalchemy.util import ( # noqa: F401 + memoized_property as memoized_property, + ) + + +EMPTY_DICT: Mapping[Any, Any] = immutabledict() +_T = TypeVar("_T", bound=Any) + +_C = TypeVar("_C", bound=Callable[..., Any]) + + +class _ModuleClsMeta(type): + def __setattr__(cls, key: str, value: Callable[..., Any]) -> None: + super().__setattr__(key, value) + cls._update_module_proxies(key) # type: ignore + + +class ModuleClsProxy(metaclass=_ModuleClsMeta): + """Create module level proxy functions for the + methods on a given class. + + The functions will have a compatible signature + as the methods. + + """ + + _setups: Dict[ + Type[Any], + Tuple[ + Set[str], + List[Tuple[MutableMapping[str, Any], MutableMapping[str, Any]]], + ], + ] = collections.defaultdict(lambda: (set(), [])) + + @classmethod + def _update_module_proxies(cls, name: str) -> None: + attr_names, modules = cls._setups[cls] + for globals_, locals_ in modules: + cls._add_proxied_attribute(name, globals_, locals_, attr_names) + + def _install_proxy(self) -> None: + attr_names, modules = self._setups[self.__class__] + for globals_, locals_ in modules: + globals_["_proxy"] = self + for attr_name in attr_names: + globals_[attr_name] = getattr(self, attr_name) + + def _remove_proxy(self) -> None: + attr_names, modules = self._setups[self.__class__] + for globals_, locals_ in modules: + globals_["_proxy"] = None + for attr_name in attr_names: + del globals_[attr_name] + + @classmethod + def create_module_class_proxy( + cls, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + ) -> None: + attr_names, modules = cls._setups[cls] + modules.append((globals_, locals_)) + cls._setup_proxy(globals_, locals_, attr_names) + + @classmethod + def _setup_proxy( + cls, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + attr_names: Set[str], + ) -> None: + for methname in dir(cls): + cls._add_proxied_attribute(methname, globals_, locals_, attr_names) + + @classmethod + def _add_proxied_attribute( + cls, + methname: str, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + attr_names: Set[str], + ) -> None: + if not methname.startswith("_"): + meth = getattr(cls, methname) + if callable(meth): + locals_[methname] = cls._create_method_proxy( + methname, globals_, locals_ + ) + else: + attr_names.add(methname) + + @classmethod + def _create_method_proxy( + cls, + name: str, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + ) -> Callable[..., Any]: + fn = getattr(cls, name) + + def _name_error(name: str, from_: Exception) -> NoReturn: + raise NameError( + "Can't invoke function '%s', as the proxy object has " + "not yet been " + "established for the Alembic '%s' class. " + "Try placing this code inside a callable." + % (name, cls.__name__) + ) from from_ + + globals_["_name_error"] = _name_error + + translations = getattr(fn, "_legacy_translations", []) + if translations: + spec = inspect_getfullargspec(fn) + if spec[0] and spec[0][0] == "self": + spec[0].pop(0) + + outer_args = inner_args = "*args, **kw" + translate_str = "args, kw = _translate(%r, %r, %r, args, kw)" % ( + fn.__name__, + tuple(spec), + translations, + ) + + def translate( + fn_name: str, spec: Any, translations: Any, args: Any, kw: Any + ) -> Any: + return_kw = {} + return_args = [] + + for oldname, newname in translations: + if oldname in kw: + warnings.warn( + "Argument %r is now named %r " + "for method %s()." % (oldname, newname, fn_name) + ) + return_kw[newname] = kw.pop(oldname) + return_kw.update(kw) + + args = list(args) + if spec[3]: + pos_only = spec[0][: -len(spec[3])] + else: + pos_only = spec[0] + for arg in pos_only: + if arg not in return_kw: + try: + return_args.append(args.pop(0)) + except IndexError: + raise TypeError( + "missing required positional argument: %s" + % arg + ) + return_args.extend(args) + + return return_args, return_kw + + globals_["_translate"] = translate + else: + outer_args = "*args, **kw" + inner_args = "*args, **kw" + translate_str = "" + + func_text = textwrap.dedent( + """\ + def %(name)s(%(args)s): + %(doc)r + %(translate)s + try: + p = _proxy + except NameError as ne: + _name_error('%(name)s', ne) + return _proxy.%(name)s(%(apply_kw)s) + e + """ + % { + "name": name, + "translate": translate_str, + "args": outer_args, + "apply_kw": inner_args, + "doc": fn.__doc__, + } + ) + lcl: MutableMapping[str, Any] = {} + + exec(func_text, cast("Dict[str, Any]", globals_), lcl) + return cast("Callable[..., Any]", lcl[name]) + + +def _with_legacy_names(translations: Any) -> Any: + def decorate(fn: _C) -> _C: + fn._legacy_translations = translations # type: ignore[attr-defined] + return fn + + return decorate + + +def rev_id() -> str: + return uuid.uuid4().hex[-12:] + + +@overload +def to_tuple(x: Any, default: Tuple[Any, ...]) -> Tuple[Any, ...]: ... + + +@overload +def to_tuple(x: None, default: Optional[_T] = ...) -> _T: ... + + +@overload +def to_tuple( + x: Any, default: Optional[Tuple[Any, ...]] = None +) -> Tuple[Any, ...]: ... + + +def to_tuple( + x: Any, default: Optional[Tuple[Any, ...]] = None +) -> Optional[Tuple[Any, ...]]: + if x is None: + return default + elif isinstance(x, str): + return (x,) + elif isinstance(x, Iterable): + return tuple(x) + else: + return (x,) + + +def dedupe_tuple(tup: Tuple[str, ...]) -> Tuple[str, ...]: + return tuple(unique_list(tup)) + + +class Dispatcher: + def __init__(self, uselist: bool = False) -> None: + self._registry: Dict[Tuple[Any, ...], Any] = {} + self.uselist = uselist + + def dispatch_for( + self, target: Any, qualifier: str = "default" + ) -> Callable[[_C], _C]: + def decorate(fn: _C) -> _C: + if self.uselist: + self._registry.setdefault((target, qualifier), []).append(fn) + else: + assert (target, qualifier) not in self._registry + self._registry[(target, qualifier)] = fn + return fn + + return decorate + + def dispatch(self, obj: Any, qualifier: str = "default") -> Any: + if isinstance(obj, str): + targets: Sequence[Any] = [obj] + elif isinstance(obj, type): + targets = obj.__mro__ + else: + targets = type(obj).__mro__ + + for spcls in targets: + if qualifier != "default" and (spcls, qualifier) in self._registry: + return self._fn_or_list(self._registry[(spcls, qualifier)]) + elif (spcls, "default") in self._registry: + return self._fn_or_list(self._registry[(spcls, "default")]) + else: + raise ValueError("no dispatch function for object: %s" % obj) + + def _fn_or_list( + self, fn_or_list: Union[List[Callable[..., Any]], Callable[..., Any]] + ) -> Callable[..., Any]: + if self.uselist: + + def go(*arg: Any, **kw: Any) -> None: + if TYPE_CHECKING: + assert isinstance(fn_or_list, Sequence) + for fn in fn_or_list: + fn(*arg, **kw) + + return go + else: + return fn_or_list # type: ignore + + def branch(self) -> Dispatcher: + """Return a copy of this dispatcher that is independently + writable.""" + + d = Dispatcher() + if self.uselist: + d._registry.update( + (k, [fn for fn in self._registry[k]]) for k in self._registry + ) + else: + d._registry.update(self._registry) + return d + + +def not_none(value: Optional[_T]) -> _T: + assert value is not None + return value diff --git a/venv/lib/python3.8/site-packages/alembic/util/messaging.py b/venv/lib/python3.8/site-packages/alembic/util/messaging.py new file mode 100644 index 000000000..6618fa7fa --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/util/messaging.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +from collections.abc import Iterable +from contextlib import contextmanager +import logging +import sys +import textwrap +from typing import Iterator +from typing import Optional +from typing import TextIO +from typing import Union +import warnings + +from sqlalchemy.engine import url + +from . import sqla_compat + +log = logging.getLogger(__name__) + +# disable "no handler found" errors +logging.getLogger("alembic").addHandler(logging.NullHandler()) + + +try: + import fcntl + import termios + import struct + + ioctl = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)) + _h, TERMWIDTH, _hp, _wp = struct.unpack("HHHH", ioctl) + if TERMWIDTH <= 0: # can occur if running in emacs pseudo-tty + TERMWIDTH = None +except (ImportError, OSError): + TERMWIDTH = None + + +def write_outstream( + stream: TextIO, *text: Union[str, bytes], quiet: bool = False +) -> None: + if quiet: + return + encoding = getattr(stream, "encoding", "ascii") or "ascii" + for t in text: + if not isinstance(t, bytes): + t = t.encode(encoding, "replace") + t = t.decode(encoding) + try: + stream.write(t) + except OSError: + # suppress "broken pipe" errors. + # no known way to handle this on Python 3 however + # as the exception is "ignored" (noisily) in TextIOWrapper. + break + + +@contextmanager +def status( + status_msg: str, newline: bool = False, quiet: bool = False +) -> Iterator[None]: + msg(status_msg + " ...", newline, flush=True, quiet=quiet) + try: + yield + except: + if not quiet: + write_outstream(sys.stdout, " FAILED\n") + raise + else: + if not quiet: + write_outstream(sys.stdout, " done\n") + + +def err(message: str, quiet: bool = False) -> None: + log.error(message) + msg(f"FAILED: {message}", quiet=quiet) + sys.exit(-1) + + +def obfuscate_url_pw(input_url: str) -> str: + u = url.make_url(input_url) + return sqla_compat.url_render_as_string(u, hide_password=True) # type: ignore # noqa: E501 + + +def warn(msg: str, stacklevel: int = 2) -> None: + warnings.warn(msg, UserWarning, stacklevel=stacklevel) + + +def msg( + msg: str, newline: bool = True, flush: bool = False, quiet: bool = False +) -> None: + if quiet: + return + if TERMWIDTH is None: + write_outstream(sys.stdout, msg) + if newline: + write_outstream(sys.stdout, "\n") + else: + # left indent output lines + indent = " " + lines = textwrap.wrap( + msg, + TERMWIDTH, + initial_indent=indent, + subsequent_indent=indent, + ) + if len(lines) > 1: + for line in lines[0:-1]: + write_outstream(sys.stdout, line, "\n") + write_outstream(sys.stdout, lines[-1], ("\n" if newline else "")) + if flush: + sys.stdout.flush() + + +def format_as_comma(value: Optional[Union[str, Iterable[str]]]) -> str: + if value is None: + return "" + elif isinstance(value, str): + return value + elif isinstance(value, Iterable): + return ", ".join(value) + else: + raise ValueError("Don't know how to comma-format %r" % value) diff --git a/venv/lib/python3.8/site-packages/alembic/util/pyfiles.py b/venv/lib/python3.8/site-packages/alembic/util/pyfiles.py new file mode 100644 index 000000000..973bd458e --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/util/pyfiles.py @@ -0,0 +1,114 @@ +from __future__ import annotations + +import atexit +from contextlib import ExitStack +import importlib +import importlib.machinery +import importlib.util +import os +import re +import tempfile +from types import ModuleType +from typing import Any +from typing import Optional + +from mako import exceptions +from mako.template import Template + +from . import compat +from .exc import CommandError + + +def template_to_file( + template_file: str, dest: str, output_encoding: str, **kw: Any +) -> None: + template = Template(filename=template_file) + try: + output = template.render_unicode(**kw).encode(output_encoding) + except: + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as ntf: + ntf.write( + exceptions.text_error_template() + .render_unicode() + .encode(output_encoding) + ) + fname = ntf.name + raise CommandError( + "Template rendering failed; see %s for a " + "template-oriented traceback." % fname + ) + else: + with open(dest, "wb") as f: + f.write(output) + + +def coerce_resource_to_filename(fname: str) -> str: + """Interpret a filename as either a filesystem location or as a package + resource. + + Names that are non absolute paths and contain a colon + are interpreted as resources and coerced to a file location. + + """ + if not os.path.isabs(fname) and ":" in fname: + tokens = fname.split(":") + + # from https://importlib-resources.readthedocs.io/en/latest/migration.html#pkg-resources-resource-filename # noqa E501 + + file_manager = ExitStack() + atexit.register(file_manager.close) + + ref = compat.importlib_resources.files(tokens[0]) + for tok in tokens[1:]: + ref = ref / tok + fname = file_manager.enter_context( # type: ignore[assignment] + compat.importlib_resources.as_file(ref) + ) + return fname + + +def pyc_file_from_path(path: str) -> Optional[str]: + """Given a python source path, locate the .pyc.""" + + candidate = importlib.util.cache_from_source(path) + if os.path.exists(candidate): + return candidate + + # even for pep3147, fall back to the old way of finding .pyc files, + # to support sourceless operation + filepath, ext = os.path.splitext(path) + for ext in importlib.machinery.BYTECODE_SUFFIXES: + if os.path.exists(filepath + ext): + return filepath + ext + else: + return None + + +def load_python_file(dir_: str, filename: str) -> ModuleType: + """Load a file from the given path as a Python module.""" + + module_id = re.sub(r"\W", "_", filename) + path = os.path.join(dir_, filename) + _, ext = os.path.splitext(filename) + if ext == ".py": + if os.path.exists(path): + module = load_module_py(module_id, path) + else: + pyc_path = pyc_file_from_path(path) + if pyc_path is None: + raise ImportError("Can't find Python file %s" % path) + else: + module = load_module_py(module_id, pyc_path) + elif ext in (".pyc", ".pyo"): + module = load_module_py(module_id, path) + else: + assert False + return module + + +def load_module_py(module_id: str, path: str) -> ModuleType: + spec = importlib.util.spec_from_file_location(module_id, path) + assert spec + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) # type: ignore + return module diff --git a/venv/lib/python3.8/site-packages/alembic/util/sqla_compat.py b/venv/lib/python3.8/site-packages/alembic/util/sqla_compat.py new file mode 100644 index 000000000..d4ed0fdd5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/util/sqla_compat.py @@ -0,0 +1,663 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import contextlib +import re +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import Mapping +from typing import Optional +from typing import Protocol +from typing import Set +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy import __version__ +from sqlalchemy import inspect +from sqlalchemy import schema +from sqlalchemy import sql +from sqlalchemy import types as sqltypes +from sqlalchemy.engine import url +from sqlalchemy.schema import CheckConstraint +from sqlalchemy.schema import Column +from sqlalchemy.schema import ForeignKeyConstraint +from sqlalchemy.sql import visitors +from sqlalchemy.sql.base import DialectKWArgs +from sqlalchemy.sql.elements import BindParameter +from sqlalchemy.sql.elements import ColumnClause +from sqlalchemy.sql.elements import quoted_name +from sqlalchemy.sql.elements import TextClause +from sqlalchemy.sql.elements import UnaryExpression +from sqlalchemy.sql.visitors import traverse +from typing_extensions import TypeGuard + +if TYPE_CHECKING: + from sqlalchemy import ClauseElement + from sqlalchemy import Index + from sqlalchemy import Table + from sqlalchemy.engine import Connection + from sqlalchemy.engine import Dialect + from sqlalchemy.engine import Transaction + from sqlalchemy.engine.reflection import Inspector + from sqlalchemy.sql.base import ColumnCollection + from sqlalchemy.sql.compiler import SQLCompiler + from sqlalchemy.sql.dml import Insert + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.selectable import Select + from sqlalchemy.sql.selectable import TableClause + +_CE = TypeVar("_CE", bound=Union["ColumnElement[Any]", "SchemaItem"]) + + +class _CompilerProtocol(Protocol): + def __call__(self, element: Any, compiler: Any, **kw: Any) -> str: ... + + +def _safe_int(value: str) -> Union[int, str]: + try: + return int(value) + except: + return value + + +_vers = tuple( + [_safe_int(x) for x in re.findall(r"(\d+|[abc]\d)", __version__)] +) +sqla_13 = _vers >= (1, 3) +sqla_14 = _vers >= (1, 4) +# https://docs.sqlalchemy.org/en/latest/changelog/changelog_14.html#change-0c6e0cc67dfe6fac5164720e57ef307d +sqla_14_18 = _vers >= (1, 4, 18) +sqla_14_26 = _vers >= (1, 4, 26) +sqla_2 = _vers >= (2,) +sqlalchemy_version = __version__ + +try: + from sqlalchemy.sql.naming import _NONE_NAME as _NONE_NAME # type: ignore[attr-defined] # noqa: E501 +except ImportError: + from sqlalchemy.sql.elements import _NONE_NAME as _NONE_NAME # type: ignore # noqa: E501 + + +class _Unsupported: + "Placeholder for unsupported SQLAlchemy classes" + + +if TYPE_CHECKING: + + def compiles( + element: Type[ClauseElement], *dialects: str + ) -> Callable[[_CompilerProtocol], _CompilerProtocol]: ... + +else: + from sqlalchemy.ext.compiler import compiles + +try: + from sqlalchemy import Computed as Computed +except ImportError: + if not TYPE_CHECKING: + + class Computed(_Unsupported): + pass + + has_computed = False + has_computed_reflection = False +else: + has_computed = True + has_computed_reflection = _vers >= (1, 3, 16) + +try: + from sqlalchemy import Identity as Identity +except ImportError: + if not TYPE_CHECKING: + + class Identity(_Unsupported): + pass + + has_identity = False +else: + identity_has_dialect_kwargs = issubclass(Identity, DialectKWArgs) + + def _get_identity_options_dict( + identity: Union[Identity, schema.Sequence, None], + dialect_kwargs: bool = False, + ) -> Dict[str, Any]: + if identity is None: + return {} + elif identity_has_dialect_kwargs: + as_dict = identity._as_dict() # type: ignore + if dialect_kwargs: + assert isinstance(identity, DialectKWArgs) + as_dict.update(identity.dialect_kwargs) + else: + as_dict = {} + if isinstance(identity, Identity): + # always=None means something different than always=False + as_dict["always"] = identity.always + if identity.on_null is not None: + as_dict["on_null"] = identity.on_null + # attributes common to Identity and Sequence + attrs = ( + "start", + "increment", + "minvalue", + "maxvalue", + "nominvalue", + "nomaxvalue", + "cycle", + "cache", + "order", + ) + as_dict.update( + { + key: getattr(identity, key, None) + for key in attrs + if getattr(identity, key, None) is not None + } + ) + return as_dict + + has_identity = True + +if sqla_2: + from sqlalchemy.sql.base import _NoneName +else: + from sqlalchemy.util import symbol as _NoneName # type: ignore[assignment] + + +_ConstraintName = Union[None, str, _NoneName] + +_ConstraintNameDefined = Union[str, _NoneName] + + +def constraint_name_defined( + name: _ConstraintName, +) -> TypeGuard[_ConstraintNameDefined]: + return name is _NONE_NAME or isinstance(name, (str, _NoneName)) + + +def constraint_name_string( + name: _ConstraintName, +) -> TypeGuard[str]: + return isinstance(name, str) + + +def constraint_name_or_none( + name: _ConstraintName, +) -> Optional[str]: + return name if constraint_name_string(name) else None + + +AUTOINCREMENT_DEFAULT = "auto" + + +@contextlib.contextmanager +def _ensure_scope_for_ddl( + connection: Optional[Connection], +) -> Iterator[None]: + try: + in_transaction = connection.in_transaction # type: ignore[union-attr] + except AttributeError: + # catch for MockConnection, None + in_transaction = None + pass + + # yield outside the catch + if in_transaction is None: + yield + else: + if not in_transaction(): + assert connection is not None + with connection.begin(): + yield + else: + yield + + +def url_render_as_string(url, hide_password=True): + if sqla_14: + return url.render_as_string(hide_password=hide_password) + else: + return url.__to_string__(hide_password=hide_password) + + +def _safe_begin_connection_transaction( + connection: Connection, +) -> Transaction: + transaction = _get_connection_transaction(connection) + if transaction: + return transaction + else: + return connection.begin() + + +def _safe_commit_connection_transaction( + connection: Connection, +) -> None: + transaction = _get_connection_transaction(connection) + if transaction: + transaction.commit() + + +def _safe_rollback_connection_transaction( + connection: Connection, +) -> None: + transaction = _get_connection_transaction(connection) + if transaction: + transaction.rollback() + + +def _get_connection_in_transaction(connection: Optional[Connection]) -> bool: + try: + in_transaction = connection.in_transaction # type: ignore + except AttributeError: + # catch for MockConnection + return False + else: + return in_transaction() + + +def _idx_table_bound_expressions(idx: Index) -> Iterable[ColumnElement[Any]]: + return idx.expressions # type: ignore + + +def _copy(schema_item: _CE, **kw) -> _CE: + if hasattr(schema_item, "_copy"): + return schema_item._copy(**kw) + else: + return schema_item.copy(**kw) # type: ignore[union-attr] + + +def _get_connection_transaction( + connection: Connection, +) -> Optional[Transaction]: + if sqla_14: + return connection.get_transaction() + else: + r = connection._root # type: ignore[attr-defined] + return r._Connection__transaction + + +def _create_url(*arg, **kw) -> url.URL: + if hasattr(url.URL, "create"): + return url.URL.create(*arg, **kw) + else: + return url.URL(*arg, **kw) + + +def _connectable_has_table( + connectable: Connection, tablename: str, schemaname: Union[str, None] +) -> bool: + if sqla_14: + return inspect(connectable).has_table(tablename, schemaname) + else: + return connectable.dialect.has_table( + connectable, tablename, schemaname + ) + + +def _exec_on_inspector(inspector, statement, **params): + if sqla_14: + with inspector._operation_context() as conn: + return conn.execute(statement, params) + else: + return inspector.bind.execute(statement, params) + + +def _nullability_might_be_unset(metadata_column): + if not sqla_14: + return metadata_column.nullable + else: + from sqlalchemy.sql import schema + + return ( + metadata_column._user_defined_nullable is schema.NULL_UNSPECIFIED + ) + + +def _server_default_is_computed(*server_default) -> bool: + if not has_computed: + return False + else: + return any(isinstance(sd, Computed) for sd in server_default) + + +def _server_default_is_identity(*server_default) -> bool: + if not sqla_14: + return False + else: + return any(isinstance(sd, Identity) for sd in server_default) + + +def _table_for_constraint(constraint: Constraint) -> Table: + if isinstance(constraint, ForeignKeyConstraint): + table = constraint.parent + assert table is not None + return table # type: ignore[return-value] + else: + return constraint.table + + +def _columns_for_constraint(constraint): + if isinstance(constraint, ForeignKeyConstraint): + return [fk.parent for fk in constraint.elements] + elif isinstance(constraint, CheckConstraint): + return _find_columns(constraint.sqltext) + else: + return list(constraint.columns) + + +def _reflect_table(inspector: Inspector, table: Table) -> None: + if sqla_14: + return inspector.reflect_table(table, None) + else: + return inspector.reflecttable( # type: ignore[attr-defined] + table, None + ) + + +def _resolve_for_variant(type_, dialect): + if _type_has_variants(type_): + base_type, mapping = _get_variant_mapping(type_) + return mapping.get(dialect.name, base_type) + else: + return type_ + + +if hasattr(sqltypes.TypeEngine, "_variant_mapping"): + + def _type_has_variants(type_): + return bool(type_._variant_mapping) + + def _get_variant_mapping(type_): + return type_, type_._variant_mapping + +else: + + def _type_has_variants(type_): + return type(type_) is sqltypes.Variant + + def _get_variant_mapping(type_): + return type_.impl, type_.mapping + + +def _fk_spec(constraint: ForeignKeyConstraint) -> Any: + if TYPE_CHECKING: + assert constraint.columns is not None + assert constraint.elements is not None + assert isinstance(constraint.parent, Table) + + source_columns = [ + constraint.columns[key].name for key in constraint.column_keys + ] + + source_table = constraint.parent.name + source_schema = constraint.parent.schema + target_schema = constraint.elements[0].column.table.schema + target_table = constraint.elements[0].column.table.name + target_columns = [element.column.name for element in constraint.elements] + ondelete = constraint.ondelete + onupdate = constraint.onupdate + deferrable = constraint.deferrable + initially = constraint.initially + return ( + source_schema, + source_table, + source_columns, + target_schema, + target_table, + target_columns, + onupdate, + ondelete, + deferrable, + initially, + ) + + +def _fk_is_self_referential(constraint: ForeignKeyConstraint) -> bool: + spec = constraint.elements[0]._get_colspec() + tokens = spec.split(".") + tokens.pop(-1) # colname + tablekey = ".".join(tokens) + assert constraint.parent is not None + return tablekey == constraint.parent.key + + +def _is_type_bound(constraint: Constraint) -> bool: + # this deals with SQLAlchemy #3260, don't copy CHECK constraints + # that will be generated by the type. + # new feature added for #3260 + return constraint._type_bound + + +def _find_columns(clause): + """locate Column objects within the given expression.""" + + cols: Set[ColumnElement[Any]] = set() + traverse(clause, {}, {"column": cols.add}) + return cols + + +def _remove_column_from_collection( + collection: ColumnCollection, column: Union[Column[Any], ColumnClause[Any]] +) -> None: + """remove a column from a ColumnCollection.""" + + # workaround for older SQLAlchemy, remove the + # same object that's present + assert column.key is not None + to_remove = collection[column.key] + + # SQLAlchemy 2.0 will use more ReadOnlyColumnCollection + # (renamed from ImmutableColumnCollection) + if hasattr(collection, "_immutable") or hasattr(collection, "_readonly"): + collection._parent.remove(to_remove) + else: + collection.remove(to_remove) + + +def _textual_index_column( + table: Table, text_: Union[str, TextClause, ColumnElement[Any]] +) -> Union[ColumnElement[Any], Column[Any]]: + """a workaround for the Index construct's severe lack of flexibility""" + if isinstance(text_, str): + c = Column(text_, sqltypes.NULLTYPE) + table.append_column(c) + return c + elif isinstance(text_, TextClause): + return _textual_index_element(table, text_) + elif isinstance(text_, _textual_index_element): + return _textual_index_column(table, text_.text) + elif isinstance(text_, sql.ColumnElement): + return _copy_expression(text_, table) + else: + raise ValueError("String or text() construct expected") + + +def _copy_expression(expression: _CE, target_table: Table) -> _CE: + def replace(col): + if ( + isinstance(col, Column) + and col.table is not None + and col.table is not target_table + ): + if col.name in target_table.c: + return target_table.c[col.name] + else: + c = _copy(col) + target_table.append_column(c) + return c + else: + return None + + return visitors.replacement_traverse( # type: ignore[call-overload] + expression, {}, replace + ) + + +class _textual_index_element(sql.ColumnElement): + """Wrap around a sqlalchemy text() construct in such a way that + we appear like a column-oriented SQL expression to an Index + construct. + + The issue here is that currently the Postgresql dialect, the biggest + recipient of functional indexes, keys all the index expressions to + the corresponding column expressions when rendering CREATE INDEX, + so the Index we create here needs to have a .columns collection that + is the same length as the .expressions collection. Ultimately + SQLAlchemy should support text() expressions in indexes. + + See SQLAlchemy issue 3174. + + """ + + __visit_name__ = "_textual_idx_element" + + def __init__(self, table: Table, text: TextClause) -> None: + self.table = table + self.text = text + self.key = text.text + self.fake_column = schema.Column(self.text.text, sqltypes.NULLTYPE) + table.append_column(self.fake_column) + + def get_children(self, **kw): + return [self.fake_column] + + +@compiles(_textual_index_element) +def _render_textual_index_column( + element: _textual_index_element, compiler: SQLCompiler, **kw +) -> str: + return compiler.process(element.text, **kw) + + +class _literal_bindparam(BindParameter): + pass + + +@compiles(_literal_bindparam) +def _render_literal_bindparam( + element: _literal_bindparam, compiler: SQLCompiler, **kw +) -> str: + return compiler.render_literal_bindparam(element, **kw) + + +def _column_kwargs(col: Column) -> Mapping: + if sqla_13: + return col.kwargs + else: + return {} + + +def _get_constraint_final_name( + constraint: Union[Index, Constraint], dialect: Optional[Dialect] +) -> Optional[str]: + if constraint.name is None: + return None + assert dialect is not None + if sqla_14: + # for SQLAlchemy 1.4 we would like to have the option to expand + # the use of "deferred" names for constraints as well as to have + # some flexibility with "None" name and similar; make use of new + # SQLAlchemy API to return what would be the final compiled form of + # the name for this dialect. + return dialect.identifier_preparer.format_constraint( + constraint, _alembic_quote=False + ) + else: + # prior to SQLAlchemy 1.4, work around quoting logic to get at the + # final compiled name without quotes. + if hasattr(constraint.name, "quote"): + # might be quoted_name, might be truncated_name, keep it the + # same + quoted_name_cls: type = type(constraint.name) + else: + quoted_name_cls = quoted_name + + new_name = quoted_name_cls(str(constraint.name), quote=False) + constraint = constraint.__class__(name=new_name) + + if isinstance(constraint, schema.Index): + # name should not be quoted. + d = dialect.ddl_compiler(dialect, None) # type: ignore[arg-type] + return d._prepared_index_name(constraint) + else: + # name should not be quoted. + return dialect.identifier_preparer.format_constraint(constraint) + + +def _constraint_is_named( + constraint: Union[Constraint, Index], dialect: Optional[Dialect] +) -> bool: + if sqla_14: + if constraint.name is None: + return False + assert dialect is not None + name = dialect.identifier_preparer.format_constraint( + constraint, _alembic_quote=False + ) + return name is not None + else: + return constraint.name is not None + + +def _is_mariadb(mysql_dialect: Dialect) -> bool: + if sqla_14: + return mysql_dialect.is_mariadb # type: ignore[attr-defined] + else: + return bool( + mysql_dialect.server_version_info + and mysql_dialect._is_mariadb # type: ignore[attr-defined] + ) + + +def _mariadb_normalized_version_info(mysql_dialect): + return mysql_dialect._mariadb_normalized_version_info + + +def _insert_inline(table: Union[TableClause, Table]) -> Insert: + if sqla_14: + return table.insert().inline() + else: + return table.insert(inline=True) # type: ignore[call-arg] + + +if sqla_14: + from sqlalchemy import create_mock_engine + + # weird mypy workaround + from sqlalchemy import select as _sa_select + + _select = _sa_select +else: + from sqlalchemy import create_engine + + def create_mock_engine(url, executor, **kw): # type: ignore[misc] + return create_engine( + "postgresql://", strategy="mock", executor=executor + ) + + def _select(*columns, **kw) -> Select: + return sql.select(list(columns), **kw) # type: ignore[call-overload] + + +def is_expression_index(index: Index) -> bool: + for expr in index.expressions: + if is_expression(expr): + return True + return False + + +def is_expression(expr: Any) -> bool: + while isinstance(expr, UnaryExpression): + expr = expr.element + if not isinstance(expr, ColumnClause) or expr.is_literal: + return True + return False diff --git a/venv/lib/python3.8/site-packages/distutils-precedence.pth b/venv/lib/python3.8/site-packages/distutils-precedence.pth new file mode 100644 index 000000000..6de4198fc --- /dev/null +++ b/venv/lib/python3.8/site-packages/distutils-precedence.pth @@ -0,0 +1 @@ +import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim(); diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/AUTHORS b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/AUTHORS new file mode 100644 index 000000000..42a5c227b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/AUTHORS @@ -0,0 +1,51 @@ +Original Authors +---------------- +* Armin Rigo +* Christian Tismer + +Contributors +------------ +* Al Stone +* Alexander Schmidt +* Alexey Borzenkov +* Andreas Schwab +* Armin Ronacher +* Bin Wang +* Bob Ippolito +* ChangBo Guo +* Christoph Gohlke +* Denis Bilenko +* Dirk Mueller +* Donovan Preston +* Fantix King +* Floris Bruynooghe +* Fredrik Fornwall +* Gerd Woetzel +* Giel van Schijndel +* Gökhan Karabulut +* Gustavo Niemeyer +* Guy Rozendorn +* Hye-Shik Chang +* Jared Kuolt +* Jason Madden +* Josh Snyder +* Kyle Ambroff +* Laszlo Boszormenyi +* Mao Han +* Marc Abramowitz +* Marc Schlaich +* Marcin Bachry +* Matt Madison +* Matt Turner +* Michael Ellerman +* Michael Matz +* Ralf Schmitt +* Robie Basak +* Ronny Pfannschmidt +* Samual M. Rushing +* Tony Bowles +* Tony Breeds +* Trevor Bowen +* Tulio Magno Quites Machado Filho +* Ulrich Weigand +* Victor Stinner diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/INSTALLER b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE new file mode 100644 index 000000000..b73a4a10c --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE @@ -0,0 +1,30 @@ +The following files are derived from Stackless Python and are subject to the +same license as Stackless Python: + + src/greenlet/slp_platformselect.h + files in src/greenlet/platform/ directory + +See LICENSE.PSF and http://www.stackless.com/ for details. + +Unless otherwise noted, the files in greenlet have been released under the +following MIT license: + +Copyright (c) Armin Rigo, Christian Tismer and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE.PSF b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE.PSF new file mode 100644 index 000000000..d3b509a2b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/LICENSE.PSF @@ -0,0 +1,47 @@ +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011 Python Software Foundation; All Rights Reserved" are retained in Python +alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/METADATA b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/METADATA new file mode 100644 index 000000000..15294101c --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/METADATA @@ -0,0 +1,103 @@ +Metadata-Version: 2.1 +Name: greenlet +Version: 3.1.1 +Summary: Lightweight in-process concurrent programming +Home-page: https://greenlet.readthedocs.io/ +Author: Alexey Borzenkov +Author-email: snaury@gmail.com +Maintainer: Jason Madden +Maintainer-email: jason@seecoresoftware.com +License: MIT License +Project-URL: Bug Tracker, https://github.com/python-greenlet/greenlet/issues +Project-URL: Source Code, https://github.com/python-greenlet/greenlet/ +Project-URL: Documentation, https://greenlet.readthedocs.io/ +Keywords: greenlet coroutine concurrency threads cooperative +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Natural Language :: English +Classifier: Programming Language :: C +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Operating System :: OS Independent +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: LICENSE.PSF +License-File: AUTHORS +Provides-Extra: docs +Requires-Dist: Sphinx ; extra == 'docs' +Requires-Dist: furo ; extra == 'docs' +Provides-Extra: test +Requires-Dist: objgraph ; extra == 'test' +Requires-Dist: psutil ; extra == 'test' + +.. This file is included into docs/history.rst + + +Greenlets are lightweight coroutines for in-process concurrent +programming. + +The "greenlet" package is a spin-off of `Stackless`_, a version of +CPython that supports micro-threads called "tasklets". Tasklets run +pseudo-concurrently (typically in a single or a few OS-level threads) +and are synchronized with data exchanges on "channels". + +A "greenlet", on the other hand, is a still more primitive notion of +micro-thread with no implicit scheduling; coroutines, in other words. +This is useful when you want to control exactly when your code runs. +You can build custom scheduled micro-threads on top of greenlet; +however, it seems that greenlets are useful on their own as a way to +make advanced control flow structures. For example, we can recreate +generators; the difference with Python's own generators is that our +generators can call nested functions and the nested functions can +yield values too. (Additionally, you don't need a "yield" keyword. See +the example in `test_generator.py +`_). + +Greenlets are provided as a C extension module for the regular unmodified +interpreter. + +.. _`Stackless`: http://www.stackless.com + + +Who is using Greenlet? +====================== + +There are several libraries that use Greenlet as a more flexible +alternative to Python's built in coroutine support: + + - `Concurrence`_ + - `Eventlet`_ + - `Gevent`_ + +.. _Concurrence: http://opensource.hyves.org/concurrence/ +.. _Eventlet: http://eventlet.net/ +.. _Gevent: http://www.gevent.org/ + +Getting Greenlet +================ + +The easiest way to get Greenlet is to install it with pip:: + + pip install greenlet + + +Source code archives and binary distributions are available on the +python package index at https://pypi.org/project/greenlet + +The source code repository is hosted on github: +https://github.com/python-greenlet/greenlet + +Documentation is available on readthedocs.org: +https://greenlet.readthedocs.io diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/RECORD b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/RECORD new file mode 100644 index 000000000..00114415d --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/RECORD @@ -0,0 +1,122 @@ +../../../include/site/python3.8/greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755 +greenlet-3.1.1.dist-info/AUTHORS,sha256=swW28t2knVRxRkaEQNZtO7MP9Sgnompb7B6cNgJM8Gk,849 +greenlet-3.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +greenlet-3.1.1.dist-info/LICENSE,sha256=dpgx1uXfrywggC-sz_H6-0wgJd2PYlPfpH_K1Z1NCXk,1434 +greenlet-3.1.1.dist-info/LICENSE.PSF,sha256=5f88I8EQ5JTNfXNsEP2W1GJFe6_soxCEDbZScpjH1Gs,2424 +greenlet-3.1.1.dist-info/METADATA,sha256=MoOb0T5ZdGbCymjJygO-CkVIAaeI5KcuwE_JPpH1hp4,3830 +greenlet-3.1.1.dist-info/RECORD,, +greenlet-3.1.1.dist-info/WHEEL,sha256=wBRf3gksDiW0S2nRMX9WcKSzf2_xDCDXEFDHQj50S6A,148 +greenlet-3.1.1.dist-info/top_level.txt,sha256=YSnRsCRoO61JGlP57o8iKL6rdLWDWuiyKD8ekpWUsDc,9 +greenlet/CObjects.cpp,sha256=OPej1bWBgc4sRrTRQ2aFFML9pzDYKlKhlJSjsI0X_eU,3508 +greenlet/PyGreenlet.cpp,sha256=ogWsQ5VhSdItWRLLpWOgSuqYuM3QwQ4cVCxOQIgHx6E,23441 +greenlet/PyGreenlet.hpp,sha256=2ZQlOxYNoy7QwD7mppFoOXe_At56NIsJ0eNsE_hoSsw,1463 +greenlet/PyGreenletUnswitchable.cpp,sha256=PQE0fSZa_IOyUM44IESHkJoD2KtGW3dkhkmZSYY3WHs,4375 +greenlet/PyModule.cpp,sha256=J2TH06dGcNEarioS6NbWXkdME8hJY05XVbdqLrfO5w4,8587 +greenlet/TBrokenGreenlet.cpp,sha256=smN26uC7ahAbNYiS10rtWPjCeTG4jevM8siA2sjJiXg,1021 +greenlet/TExceptionState.cpp,sha256=U7Ctw9fBdNraS0d174MoQW7bN-ae209Ta0JuiKpcpVI,1359 +greenlet/TGreenlet.cpp,sha256=HGYGKpmKYqQ842tASW-QaaV8wua4a5XV_quYKPDsV_Y,25731 +greenlet/TGreenlet.hpp,sha256=mMHcb_rSuozdDiGJjX3GgyYkWgVM4kuO1UgbUP84BlU,27869 +greenlet/TGreenletGlobals.cpp,sha256=YyEmDjKf1g32bsL-unIUScFLnnA1fzLWf2gOMd-D0Zw,3264 +greenlet/TMainGreenlet.cpp,sha256=fvgb8HHB-FVTPEKjR1s_ifCZSpp5D5YQByik0CnIABg,3276 +greenlet/TPythonState.cpp,sha256=FxRdi76lTGXaQKWwkq82VaCfIRdF2Z-fh-TlRTMjYqg,15359 +greenlet/TStackState.cpp,sha256=V444I8Jj9DhQz-9leVW_9dtiSRjaE1NMlgDG02Xxq-Y,7381 +greenlet/TThreadState.hpp,sha256=2Jgg7DtGggMYR_x3CLAvAFf1mIdIDtQvSSItcdmX4ZQ,19131 +greenlet/TThreadStateCreator.hpp,sha256=uYTexDWooXSSgUc5uh-Mhm5BQi3-kR6CqpizvNynBFQ,2610 +greenlet/TThreadStateDestroy.cpp,sha256=wt7lQwLI0mi_JtnZB_jB4bUmfCa5b6nQhA7XOmnI1yk,9568 +greenlet/TUserGreenlet.cpp,sha256=uemg0lwKXtYB0yzmvyYdIIAsKnNkifXM1OJ2OlrFP1A,23553 +greenlet/__init__.py,sha256=OOmvT6_vn_SekdPzkj4qm6hjfikXMmdNZYDmGTOaRNo,1723 +greenlet/__pycache__/__init__.cpython-38.pyc,, +greenlet/_greenlet.cpython-38-x86_64-linux-gnu.so,sha256=HEzEWPAz1_n0M128sIQivuYiDRyoUSjCjYE4Lp4Frsg,1420664 +greenlet/greenlet.cpp,sha256=WdItb1yWL9WNsTqJNf0Iw8ZwDHD49pkDP0rIRGBg2pw,10996 +greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755 +greenlet/greenlet_allocator.hpp,sha256=kxyWW4Qdwlrc7ufgdb5vd6Y7jhauQ699Kod0mqiO1iM,1582 +greenlet/greenlet_compiler_compat.hpp,sha256=nRxpLN9iNbnLVyFDeVmOwyeeNm6scQrOed1l7JQYMCM,4346 +greenlet/greenlet_cpython_add_pending.hpp,sha256=apAwIhGlgYrnYn03zWL6Sxy68kltDeb1e0QupZfb3DQ,6043 +greenlet/greenlet_cpython_compat.hpp,sha256=L_jig3dm2bsJWRazrhlokma2NfnwixoQ0cydshh6ce4,3964 +greenlet/greenlet_exceptions.hpp,sha256=06Bx81DtVaJTa6RtiMcV141b-XHv4ppEgVItkblcLWY,4503 +greenlet/greenlet_internal.hpp,sha256=Ajc-_09W4xWzm9XfyXHAeQAFUgKGKsnJwYsTCoNy3ns,2709 +greenlet/greenlet_refs.hpp,sha256=OnbA91yZf3QHH6-eJccvoNDAaN-pQBMMrclFU1Ot3J4,34436 +greenlet/greenlet_slp_switch.hpp,sha256=kM1QHA2iV-gH4cFyN6lfIagHQxvJZjWOVJdIxRE3TlQ,3198 +greenlet/greenlet_thread_support.hpp,sha256=XUJ6ljWjf9OYyuOILiz8e_yHvT3fbaUiHdhiPNQUV4s,867 +greenlet/platform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +greenlet/platform/__pycache__/__init__.cpython-38.pyc,, +greenlet/platform/setup_switch_x64_masm.cmd,sha256=ZpClUJeU0ujEPSTWNSepP0W2f9XiYQKA8QKSoVou8EU,143 +greenlet/platform/switch_aarch64_gcc.h,sha256=GKC0yWNXnbK2X--X6aguRCMj2Tg7hDU1Zkl3RljDvC8,4307 +greenlet/platform/switch_alpha_unix.h,sha256=Z-SvF8JQV3oxWT8JRbL9RFu4gRFxPdJ7cviM8YayMmw,671 +greenlet/platform/switch_amd64_unix.h,sha256=EcSFCBlodEBhqhKjcJqY_5Dn_jn7pKpkJlOvp7gFXLI,2748 +greenlet/platform/switch_arm32_gcc.h,sha256=Z3KkHszdgq6uU4YN3BxvKMG2AdDnovwCCNrqGWZ1Lyo,2479 +greenlet/platform/switch_arm32_ios.h,sha256=mm5_R9aXB92hyxzFRwB71M60H6AlvHjrpTrc72Pz3l8,1892 +greenlet/platform/switch_arm64_masm.asm,sha256=4kpTtfy7rfcr8j1CpJLAK21EtZpGDAJXWRU68HEy5A8,1245 +greenlet/platform/switch_arm64_masm.obj,sha256=DmLnIB_icoEHAz1naue_pJPTZgR9ElM7-Nmztr-o9_U,746 +greenlet/platform/switch_arm64_msvc.h,sha256=RqK5MHLmXI3Q-FQ7tm32KWnbDNZKnkJdq8CR89cz640,398 +greenlet/platform/switch_csky_gcc.h,sha256=kDikyiPpewP71KoBZQO_MukDTXTXBiC7x-hF0_2DL0w,1331 +greenlet/platform/switch_loongarch64_linux.h,sha256=7M-Dhc4Q8tRbJCJhalDLwU6S9Mx8MjmN1RbTDgIvQTM,779 +greenlet/platform/switch_m68k_gcc.h,sha256=VSa6NpZhvyyvF-Q58CTIWSpEDo4FKygOyTz00whctlw,928 +greenlet/platform/switch_mips_unix.h,sha256=E0tYsqc5anDY1BhenU1l8DW-nVHC_BElzLgJw3TGtPk,1426 +greenlet/platform/switch_ppc64_aix.h,sha256=_BL0iyRr3ZA5iPlr3uk9SJ5sNRWGYLrXcZ5z-CE9anE,3860 +greenlet/platform/switch_ppc64_linux.h,sha256=0rriT5XyxPb0GqsSSn_bP9iQsnjsPbBmu0yqo5goSyQ,3815 +greenlet/platform/switch_ppc_aix.h,sha256=pHA4slEjUFP3J3SYm1TAlNPhgb2G_PAtax5cO8BEe1A,2941 +greenlet/platform/switch_ppc_linux.h,sha256=YwrlKUzxlXuiKMQqr6MFAV1bPzWnmvk6X1AqJZEpOWU,2759 +greenlet/platform/switch_ppc_macosx.h,sha256=Z6KN_ud0n6nC3ltJrNz2qtvER6vnRAVRNH9mdIDpMxY,2624 +greenlet/platform/switch_ppc_unix.h,sha256=-ZG7MSSPEA5N4qO9PQChtyEJ-Fm6qInhyZm_ZBHTtMg,2652 +greenlet/platform/switch_riscv_unix.h,sha256=Xg0wBen8Je21LWzFtLNLvUUYq6p9n_WY7AUQbiBVyyk,865 +greenlet/platform/switch_s390_unix.h,sha256=RRlGu957ybmq95qNNY4Qw1mcaoT3eBnW5KbVwu48KX8,2763 +greenlet/platform/switch_sh_gcc.h,sha256=mcRJBTu-2UBf4kZtX601qofwuDuy-Y-hnxJtrcaB7do,901 +greenlet/platform/switch_sparc_sun_gcc.h,sha256=xZish9GsMHBienUbUMsX1-ZZ-as7hs36sVhYIE3ew8Y,2797 +greenlet/platform/switch_x32_unix.h,sha256=nM98PKtzTWc1lcM7TRMUZJzskVdR1C69U1UqZRWX0GE,1509 +greenlet/platform/switch_x64_masm.asm,sha256=nu6n2sWyXuXfpPx40d9YmLfHXUc1sHgeTvX1kUzuvEM,1841 +greenlet/platform/switch_x64_masm.obj,sha256=GNtTNxYdo7idFUYsQv-mrXWgyT5EJ93-9q90lN6svtQ,1078 +greenlet/platform/switch_x64_msvc.h,sha256=LIeasyKo_vHzspdMzMHbosRhrBfKI4BkQOh4qcTHyJw,1805 +greenlet/platform/switch_x86_msvc.h,sha256=TtGOwinbFfnn6clxMNkCz8i6OmgB6kVRrShoF5iT9to,12838 +greenlet/platform/switch_x86_unix.h,sha256=VplW9H0FF0cZHw1DhJdIUs5q6YLS4cwb2nYwjF83R1s,3059 +greenlet/slp_platformselect.h,sha256=s-U-BrZ3qwwfI-6W9zWw2rb404OksZYbxYC2w5kSMXM,3841 +greenlet/tests/__init__.py,sha256=cj2-qpMXnlVRLbMLX-rPNNMVJ42ZssdxHd84NSQ3YXw,9246 +greenlet/tests/__pycache__/__init__.cpython-38.pyc,, +greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-38.pyc,, +greenlet/tests/__pycache__/fail_cpp_exception.cpython-38.pyc,, +greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-38.pyc,, +greenlet/tests/__pycache__/fail_slp_switch.cpython-38.pyc,, +greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-38.pyc,, +greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-38.pyc,, +greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-38.pyc,, +greenlet/tests/__pycache__/leakcheck.cpython-38.pyc,, +greenlet/tests/__pycache__/test_contextvars.cpython-38.pyc,, +greenlet/tests/__pycache__/test_cpp.cpython-38.pyc,, +greenlet/tests/__pycache__/test_extension_interface.cpython-38.pyc,, +greenlet/tests/__pycache__/test_gc.cpython-38.pyc,, +greenlet/tests/__pycache__/test_generator.cpython-38.pyc,, +greenlet/tests/__pycache__/test_generator_nested.cpython-38.pyc,, +greenlet/tests/__pycache__/test_greenlet.cpython-38.pyc,, +greenlet/tests/__pycache__/test_greenlet_trash.cpython-38.pyc,, +greenlet/tests/__pycache__/test_leaks.cpython-38.pyc,, +greenlet/tests/__pycache__/test_stack_saved.cpython-38.pyc,, +greenlet/tests/__pycache__/test_throw.cpython-38.pyc,, +greenlet/tests/__pycache__/test_tracing.cpython-38.pyc,, +greenlet/tests/__pycache__/test_version.cpython-38.pyc,, +greenlet/tests/__pycache__/test_weakref.cpython-38.pyc,, +greenlet/tests/_test_extension.c,sha256=vkeGA-6oeJcGILsD7oIrT1qZop2GaTOHXiNT7mcSl-0,5773 +greenlet/tests/_test_extension.cpython-38-x86_64-linux-gnu.so,sha256=eOTnO6pZzdU_ZTS9bNEiAh0P9jNTejmldJd6gbvv2KA,35904 +greenlet/tests/_test_extension_cpp.cpp,sha256=e0kVnaB8CCaEhE9yHtNyfqTjevsPDKKx-zgxk7PPK48,6565 +greenlet/tests/_test_extension_cpp.cpython-38-x86_64-linux-gnu.so,sha256=hSMWkSOEZFSFcczVU9lv04nDyaxP4FxT-mqi8BFuU1E,57640 +greenlet/tests/fail_clearing_run_switches.py,sha256=o433oA_nUCtOPaMEGc8VEhZIKa71imVHXFw7TsXaP8M,1263 +greenlet/tests/fail_cpp_exception.py,sha256=o_ZbipWikok8Bjc-vjiQvcb5FHh2nVW-McGKMLcMzh0,985 +greenlet/tests/fail_initialstub_already_started.py,sha256=txENn5IyzGx2p-XR1XB7qXmC8JX_4mKDEA8kYBXUQKc,1961 +greenlet/tests/fail_slp_switch.py,sha256=rJBZcZfTWR3e2ERQtPAud6YKShiDsP84PmwOJbp4ey0,524 +greenlet/tests/fail_switch_three_greenlets.py,sha256=zSitV7rkNnaoHYVzAGGLnxz-yPtohXJJzaE8ehFDQ0M,956 +greenlet/tests/fail_switch_three_greenlets2.py,sha256=FPJensn2EJxoropl03JSTVP3kgP33k04h6aDWWozrOk,1285 +greenlet/tests/fail_switch_two_greenlets.py,sha256=1CaI8s3504VbbF1vj1uBYuy-zxBHVzHPIAd1LIc8ONg,817 +greenlet/tests/leakcheck.py,sha256=inbfM7_oVzd8jIKGxCgo4JqpFZaDAnWPkSULJ8vIE1s,11964 +greenlet/tests/test_contextvars.py,sha256=0n5pR_lbpAppc5wFfK0e1SwYLM-fsSFp72B5_ArLPGE,10348 +greenlet/tests/test_cpp.py,sha256=hpxhFAdKJTpAVZP8CBGs1ZcrKdscI9BaDZk4btkI5d4,2736 +greenlet/tests/test_extension_interface.py,sha256=eJ3cwLacdK2WbsrC-4DgeyHdwLRcG4zx7rrkRtqSzC4,3829 +greenlet/tests/test_gc.py,sha256=PCOaRpIyjNnNlDogGL3FZU_lrdXuM-pv1rxeE5TP5mc,2923 +greenlet/tests/test_generator.py,sha256=tONXiTf98VGm347o1b-810daPiwdla5cbpFg6QI1R1g,1240 +greenlet/tests/test_generator_nested.py,sha256=7v4HOYrf1XZP39dk5IUMubdZ8yc3ynwZcqj9GUJyMSA,3718 +greenlet/tests/test_greenlet.py,sha256=zoAy56MtEyz5P93Iknpt2pPjNO3ePYrgM7SDE8Cw_uI,45990 +greenlet/tests/test_greenlet_trash.py,sha256=n2dBlQfOoEO1ODatFi8QdhboH3fB86YtqzcYMYOXxbw,7947 +greenlet/tests/test_leaks.py,sha256=wskLqCAvqZ3qTZkam_wXzd-E5zelUjlXS5Ss8KshtZY,17465 +greenlet/tests/test_stack_saved.py,sha256=eyzqNY2VCGuGlxhT_In6TvZ6Okb0AXFZVyBEnK1jDwA,446 +greenlet/tests/test_throw.py,sha256=u2TQ_WvvCd6N6JdXWIxVEcXkKu5fepDlz9dktYdmtng,3712 +greenlet/tests/test_tracing.py,sha256=VlwzMU0C1noospZhuUMyB7MHw200emIvGCN_6G2p2ZU,8250 +greenlet/tests/test_version.py,sha256=O9DpAITsOFgiRcjd4odQ7ejmwx_N9Q1zQENVcbtFHIc,1339 +greenlet/tests/test_weakref.py,sha256=F8M23btEF87bIbpptLNBORosbQqNZGiYeKMqYjWrsak,883 diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/WHEEL b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/WHEEL new file mode 100644 index 000000000..c80f2c6c2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.1.0) +Root-Is-Purelib: false +Tag: cp38-cp38-manylinux_2_24_x86_64 +Tag: cp38-cp38-manylinux_2_28_x86_64 + diff --git a/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/top_level.txt b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/top_level.txt new file mode 100644 index 000000000..46725be4f --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet-3.1.1.dist-info/top_level.txt @@ -0,0 +1 @@ +greenlet diff --git a/venv/lib/python3.8/site-packages/greenlet/CObjects.cpp b/venv/lib/python3.8/site-packages/greenlet/CObjects.cpp new file mode 100644 index 000000000..c135995b6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/CObjects.cpp @@ -0,0 +1,157 @@ +#ifndef COBJECTS_CPP +#define COBJECTS_CPP +/***************************************************************************** + * C interface + * + * These are exported using the CObject API + */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +#include "greenlet_exceptions.hpp" + +#include "greenlet_internal.hpp" +#include "greenlet_refs.hpp" + + +#include "TThreadStateDestroy.cpp" + +#include "PyGreenlet.hpp" + +using greenlet::PyErrOccurred; +using greenlet::Require; + + + +extern "C" { +static PyGreenlet* +PyGreenlet_GetCurrent(void) +{ + return GET_THREAD_STATE().state().get_current().relinquish_ownership(); +} + +static int +PyGreenlet_SetParent(PyGreenlet* g, PyGreenlet* nparent) +{ + return green_setparent((PyGreenlet*)g, (PyObject*)nparent, NULL); +} + +static PyGreenlet* +PyGreenlet_New(PyObject* run, PyGreenlet* parent) +{ + using greenlet::refs::NewDictReference; + // In the past, we didn't use green_new and green_init, but that + // was a maintenance issue because we duplicated code. This way is + // much safer, but slightly slower. If that's a problem, we could + // refactor green_init to separate argument parsing from initialization. + OwnedGreenlet g = OwnedGreenlet::consuming(green_new(&PyGreenlet_Type, nullptr, nullptr)); + if (!g) { + return NULL; + } + + try { + NewDictReference kwargs; + if (run) { + kwargs.SetItem(mod_globs->str_run, run); + } + if (parent) { + kwargs.SetItem("parent", (PyObject*)parent); + } + + Require(green_init(g.borrow(), mod_globs->empty_tuple, kwargs.borrow())); + } + catch (const PyErrOccurred&) { + return nullptr; + } + + return g.relinquish_ownership(); +} + +static PyObject* +PyGreenlet_Switch(PyGreenlet* self, PyObject* args, PyObject* kwargs) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return NULL; + } + + if (args == NULL) { + args = mod_globs->empty_tuple; + } + + if (kwargs == NULL || !PyDict_Check(kwargs)) { + kwargs = NULL; + } + + return green_switch(self, args, kwargs); +} + +static PyObject* +PyGreenlet_Throw(PyGreenlet* self, PyObject* typ, PyObject* val, PyObject* tb) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return nullptr; + } + try { + PyErrPieces err_pieces(typ, val, tb); + return internal_green_throw(self, err_pieces).relinquish_ownership(); + } + catch (const PyErrOccurred&) { + return nullptr; + } +} + + + +static int +Extern_PyGreenlet_MAIN(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return -1; + } + return self->pimpl->main(); +} + +static int +Extern_PyGreenlet_ACTIVE(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return -1; + } + return self->pimpl->active(); +} + +static int +Extern_PyGreenlet_STARTED(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return -1; + } + return self->pimpl->started(); +} + +static PyGreenlet* +Extern_PyGreenlet_GET_PARENT(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return NULL; + } + // This can return NULL even if there is no exception + return self->pimpl->parent().acquire(); +} +} // extern C. + +/** End C API ****************************************************************/ +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/PyGreenlet.cpp b/venv/lib/python3.8/site-packages/greenlet/PyGreenlet.cpp new file mode 100644 index 000000000..29c0bba0b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/PyGreenlet.cpp @@ -0,0 +1,738 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef PYGREENLET_CPP +#define PYGREENLET_CPP +/***************** +The Python slot functions for TGreenlet. + */ + + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" // PyMemberDef + +#include "greenlet_internal.hpp" +#include "TThreadStateDestroy.cpp" +#include "TGreenlet.hpp" +// #include "TUserGreenlet.cpp" +// #include "TMainGreenlet.cpp" +// #include "TBrokenGreenlet.cpp" + + +#include "greenlet_refs.hpp" +#include "greenlet_slp_switch.hpp" + +#include "greenlet_thread_support.hpp" +#include "TGreenlet.hpp" + +#include "TGreenletGlobals.cpp" +#include "TThreadStateDestroy.cpp" +#include "PyGreenlet.hpp" +// #include "TGreenlet.cpp" + +// #include "TExceptionState.cpp" +// #include "TPythonState.cpp" +// #include "TStackState.cpp" + +using greenlet::LockGuard; +using greenlet::LockInitError; +using greenlet::PyErrOccurred; +using greenlet::Require; + +using greenlet::g_handle_exit; +using greenlet::single_result; + +using greenlet::Greenlet; +using greenlet::UserGreenlet; +using greenlet::MainGreenlet; +using greenlet::BrokenGreenlet; +using greenlet::ThreadState; +using greenlet::PythonState; + + + +static PyGreenlet* +green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)) +{ + PyGreenlet* o = + (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict); + if (o) { + new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current()); + assert(Py_REFCNT(o) == 1); + } + return o; +} + + +// green_init is used in the tp_init slot. So it's important that +// it can be called directly from CPython. Thus, we don't use +// BorrowedGreenlet and BorrowedObject --- although in theory +// these should be binary layout compatible, that may not be +// guaranteed to be the case (32-bit linux ppc possibly). +static int +green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs) +{ + PyArgParseParam run; + PyArgParseParam nparent; + static const char* kwlist[] = { + "run", + "parent", + NULL + }; + + // recall: The O specifier does NOT increase the reference count. + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "|OO:green", (char**)kwlist, &run, &nparent)) { + return -1; + } + + if (run) { + if (green_setrun(self, run, NULL)) { + return -1; + } + } + if (nparent && !nparent.is_None()) { + return green_setparent(self, nparent, NULL); + } + return 0; +} + + + +static int +green_traverse(PyGreenlet* self, visitproc visit, void* arg) +{ + // We must only visit referenced objects, i.e. only objects + // Py_INCREF'ed by this greenlet (directly or indirectly): + // + // - stack_prev is not visited: holds previous stack pointer, but it's not + // referenced + // - frames are not visited as we don't strongly reference them; + // alive greenlets are not garbage collected + // anyway. This can be a problem, however, if this greenlet is + // never allowed to finish, and is referenced from the frame: we + // have an uncollectible cycle in that case. Note that the + // frame object itself is also frequently not even tracked by the GC + // starting with Python 3.7 (frames are allocated by the + // interpreter untracked, and only become tracked when their + // evaluation is finished if they have a refcount > 1). All of + // this is to say that we should probably strongly reference + // the frame object. Doing so, while always allowing GC on a + // greenlet, solves several leaks for us. + + Py_VISIT(self->dict); + if (!self->pimpl) { + // Hmm. I have seen this at interpreter shutdown time, + // I think. That's very odd because this doesn't go away until + // we're ``green_dealloc()``, at which point we shouldn't be + // traversed anymore. + return 0; + } + + return self->pimpl->tp_traverse(visit, arg); +} + +static int +green_is_gc(PyObject* _self) +{ + BorrowedGreenlet self(_self); + int result = 0; + /* Main greenlet can be garbage collected since it can only + become unreachable if the underlying thread exited. + Active greenlets --- including those that are suspended --- + cannot be garbage collected, however. + */ + if (self->main() || !self->active()) { + result = 1; + } + // The main greenlet pointer will eventually go away after the thread dies. + if (self->was_running_in_dead_thread()) { + // Our thread is dead! We can never run again. Might as well + // GC us. Note that if a tuple containing only us and other + // immutable objects had been scanned before this, when we + // would have returned 0, the tuple will take itself out of GC + // tracking and never be investigated again. So that could + // result in both us and the tuple leaking due to an + // unreachable/uncollectible reference. The same goes for + // dictionaries. + // + // It's not a great idea to be changing our GC state on the + // fly. + result = 1; + } + return result; +} + + +static int +green_clear(PyGreenlet* self) +{ + /* Greenlet is only cleared if it is about to be collected. + Since active greenlets are not garbage collectable, we can + be sure that, even if they are deallocated during clear, + nothing they reference is in unreachable or finalizers, + so even if it switches we are relatively safe. */ + // XXX: Are we responsible for clearing weakrefs here? + Py_CLEAR(self->dict); + return self->pimpl->tp_clear(); +} + +/** + * Returns 0 on failure (the object was resurrected) or 1 on success. + **/ +static int +_green_dealloc_kill_started_non_main_greenlet(BorrowedGreenlet self) +{ + /* Hacks hacks hacks copied from instance_dealloc() */ + /* Temporarily resurrect the greenlet. */ + assert(self.REFCNT() == 0); + Py_SET_REFCNT(self.borrow(), 1); + /* Save the current exception, if any. */ + PyErrPieces saved_err; + try { + // BY THE TIME WE GET HERE, the state may actually be going + // away + // if we're shutting down the interpreter and freeing thread + // entries, + // this could result in freeing greenlets that were leaked. So + // we can't try to read the state. + self->deallocing_greenlet_in_thread( + self->thread_state() + ? static_cast(GET_THREAD_STATE()) + : nullptr); + } + catch (const PyErrOccurred&) { + PyErr_WriteUnraisable(self.borrow_o()); + /* XXX what else should we do? */ + } + /* Check for no resurrection must be done while we keep + * our internal reference, otherwise PyFile_WriteObject + * causes recursion if using Py_INCREF/Py_DECREF + */ + if (self.REFCNT() == 1 && self->active()) { + /* Not resurrected, but still not dead! + XXX what else should we do? we complain. */ + PyObject* f = PySys_GetObject("stderr"); + Py_INCREF(self.borrow_o()); /* leak! */ + if (f != NULL) { + PyFile_WriteString("GreenletExit did not kill ", f); + PyFile_WriteObject(self.borrow_o(), f, 0); + PyFile_WriteString("\n", f); + } + } + /* Restore the saved exception. */ + saved_err.PyErrRestore(); + /* Undo the temporary resurrection; can't use DECREF here, + * it would cause a recursive call. + */ + assert(self.REFCNT() > 0); + + Py_ssize_t refcnt = self.REFCNT() - 1; + Py_SET_REFCNT(self.borrow_o(), refcnt); + if (refcnt != 0) { + /* Resurrected! */ + _Py_NewReference(self.borrow_o()); + Py_SET_REFCNT(self.borrow_o(), refcnt); + /* Better to use tp_finalizer slot (PEP 442) + * and call ``PyObject_CallFinalizerFromDealloc``, + * but that's only supported in Python 3.4+; see + * Modules/_io/iobase.c for an example. + * + * The following approach is copied from iobase.c in CPython 2.7. + * (along with much of this function in general). Here's their + * comment: + * + * When called from a heap type's dealloc, the type will be + * decref'ed on return (see e.g. subtype_dealloc in typeobject.c). */ + if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) { + Py_INCREF(self.TYPE()); + } + + PyObject_GC_Track((PyObject*)self); + + _Py_DEC_REFTOTAL; +#ifdef COUNT_ALLOCS + --Py_TYPE(self)->tp_frees; + --Py_TYPE(self)->tp_allocs; +#endif /* COUNT_ALLOCS */ + return 0; + } + return 1; +} + + +static void +green_dealloc(PyGreenlet* self) +{ + PyObject_GC_UnTrack(self); + BorrowedGreenlet me(self); + if (me->active() + && me->started() + && !me->main()) { + if (!_green_dealloc_kill_started_non_main_greenlet(me)) { + return; + } + } + + if (self->weakreflist != NULL) { + PyObject_ClearWeakRefs((PyObject*)self); + } + Py_CLEAR(self->dict); + + if (self->pimpl) { + // In case deleting this, which frees some memory, + // somehow winds up calling back into us. That's usually a + //bug in our code. + Greenlet* p = self->pimpl; + self->pimpl = nullptr; + delete p; + } + // and finally we're done. self is now invalid. + Py_TYPE(self)->tp_free((PyObject*)self); +} + + + +static OwnedObject +internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces) +{ + PyObject* result = nullptr; + err_pieces.PyErrRestore(); + assert(PyErr_Occurred()); + if (self->started() && !self->active()) { + /* dead greenlet: turn GreenletExit into a regular return */ + result = g_handle_exit(OwnedObject()).relinquish_ownership(); + } + self->args() <<= result; + + return single_result(self->g_switch()); +} + + + +PyDoc_STRVAR( + green_switch_doc, + "switch(*args, **kwargs)\n" + "\n" + "Switch execution to this greenlet.\n" + "\n" + "If this greenlet has never been run, then this greenlet\n" + "will be switched to using the body of ``self.run(*args, **kwargs)``.\n" + "\n" + "If the greenlet is active (has been run, but was switch()'ed\n" + "out before leaving its run function), then this greenlet will\n" + "be resumed and the return value to its switch call will be\n" + "None if no arguments are given, the given argument if one\n" + "argument is given, or the args tuple and keyword args dict if\n" + "multiple arguments are given.\n" + "\n" + "If the greenlet is dead, or is the current greenlet then this\n" + "function will simply return the arguments using the same rules as\n" + "above.\n"); + +static PyObject* +green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs) +{ + using greenlet::SwitchingArgs; + SwitchingArgs switch_args(OwnedObject::owning(args), OwnedObject::owning(kwargs)); + self->pimpl->may_switch_away(); + self->pimpl->args() <<= switch_args; + + // If we're switching out of a greenlet, and that switch is the + // last thing the greenlet does, the greenlet ought to be able to + // go ahead and die at that point. Currently, someone else must + // manually switch back to the greenlet so that we "fall off the + // end" and can perform cleanup. You'd think we'd be able to + // figure out that this is happening using the frame's ``f_lasti`` + // member, which is supposed to be an index into + // ``frame->f_code->co_code``, the bytecode string. However, in + // recent interpreters, ``f_lasti`` tends not to be updated thanks + // to things like the PREDICT() macros in ceval.c. So it doesn't + // really work to do that in many cases. For example, the Python + // code: + // def run(): + // greenlet.getcurrent().parent.switch() + // produces bytecode of len 16, with the actual call to switch() + // being at index 10 (in Python 3.10). However, the reported + // ``f_lasti`` we actually see is...5! (Which happens to be the + // second byte of the CALL_METHOD op for ``getcurrent()``). + + try { + //OwnedObject result = single_result(self->pimpl->g_switch()); + OwnedObject result(single_result(self->pimpl->g_switch())); +#ifndef NDEBUG + // Note that the current greenlet isn't necessarily self. If self + // finished, we went to one of its parents. + assert(!self->pimpl->args()); + + const BorrowedGreenlet& current = GET_THREAD_STATE().state().borrow_current(); + // It's possible it's never been switched to. + assert(!current->args()); +#endif + PyObject* p = result.relinquish_ownership(); + + if (!p && !PyErr_Occurred()) { + // This shouldn't be happening anymore, so the asserts + // are there for debug builds. Non-debug builds + // crash "gracefully" in this case, although there is an + // argument to be made for killing the process in all + // cases --- for this to be the case, our switches + // probably nested in an incorrect way, so the state is + // suspicious. Nothing should be corrupt though, just + // confused at the Python level. Letting this propagate is + // probably good enough. + assert(p || PyErr_Occurred()); + throw PyErrOccurred( + mod_globs->PyExc_GreenletError, + "Greenlet.switch() returned NULL without an exception set." + ); + } + return p; + } + catch(const PyErrOccurred&) { + return nullptr; + } +} + +PyDoc_STRVAR( + green_throw_doc, + "Switches execution to this greenlet, but immediately raises the\n" + "given exception in this greenlet. If no argument is provided, the " + "exception\n" + "defaults to `greenlet.GreenletExit`. The normal exception\n" + "propagation rules apply, as described for `switch`. Note that calling " + "this\n" + "method is almost equivalent to the following::\n" + "\n" + " def raiser():\n" + " raise typ, val, tb\n" + " g_raiser = greenlet(raiser, parent=g)\n" + " g_raiser.switch()\n" + "\n" + "except that this trick does not work for the\n" + "`greenlet.GreenletExit` exception, which would not propagate\n" + "from ``g_raiser`` to ``g``.\n"); + +static PyObject* +green_throw(PyGreenlet* self, PyObject* args) +{ + PyArgParseParam typ(mod_globs->PyExc_GreenletExit); + PyArgParseParam val; + PyArgParseParam tb; + + if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) { + return nullptr; + } + + assert(typ.borrow() || val.borrow()); + + self->pimpl->may_switch_away(); + try { + // Both normalizing the error and the actual throw_greenlet + // could throw PyErrOccurred. + PyErrPieces err_pieces(typ.borrow(), val.borrow(), tb.borrow()); + + return internal_green_throw(self, err_pieces).relinquish_ownership(); + } + catch (const PyErrOccurred&) { + return nullptr; + } +} + +static int +green_bool(PyGreenlet* self) +{ + return self->pimpl->active(); +} + +/** + * CAUTION: Allocates memory, may run GC and arbitrary Python code. + */ +static PyObject* +green_getdict(PyGreenlet* self, void* UNUSED(context)) +{ + if (self->dict == NULL) { + self->dict = PyDict_New(); + if (self->dict == NULL) { + return NULL; + } + } + Py_INCREF(self->dict); + return self->dict; +} + +static int +green_setdict(PyGreenlet* self, PyObject* val, void* UNUSED(context)) +{ + PyObject* tmp; + + if (val == NULL) { + PyErr_SetString(PyExc_TypeError, "__dict__ may not be deleted"); + return -1; + } + if (!PyDict_Check(val)) { + PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary"); + return -1; + } + tmp = self->dict; + Py_INCREF(val); + self->dict = val; + Py_XDECREF(tmp); + return 0; +} + +static bool +_green_not_dead(BorrowedGreenlet self) +{ + // XXX: Where else should we do this? + // Probably on entry to most Python-facing functions? + if (self->was_running_in_dead_thread()) { + self->deactivate_and_free(); + return false; + } + return self->active() || !self->started(); +} + + +static PyObject* +green_getdead(PyGreenlet* self, void* UNUSED(context)) +{ + if (_green_not_dead(self)) { + Py_RETURN_FALSE; + } + else { + Py_RETURN_TRUE; + } +} + +static PyObject* +green_get_stack_saved(PyGreenlet* self, void* UNUSED(context)) +{ + return PyLong_FromSsize_t(self->pimpl->stack_saved()); +} + + +static PyObject* +green_getrun(PyGreenlet* self, void* UNUSED(context)) +{ + try { + OwnedObject result(BorrowedGreenlet(self)->run()); + return result.relinquish_ownership(); + } + catch(const PyErrOccurred&) { + return nullptr; + } +} + + +static int +green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context)) +{ + try { + BorrowedGreenlet(self)->run(nrun); + return 0; + } + catch(const PyErrOccurred&) { + return -1; + } +} + +static PyObject* +green_getparent(PyGreenlet* self, void* UNUSED(context)) +{ + return BorrowedGreenlet(self)->parent().acquire_or_None(); +} + + +static int +green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context)) +{ + try { + BorrowedGreenlet(self)->parent(nparent); + } + catch(const PyErrOccurred&) { + return -1; + } + return 0; +} + + +static PyObject* +green_getcontext(const PyGreenlet* self, void* UNUSED(context)) +{ + const Greenlet *const g = self->pimpl; + try { + OwnedObject result(g->context()); + return result.relinquish_ownership(); + } + catch(const PyErrOccurred&) { + return nullptr; + } +} + +static int +green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context)) +{ + try { + BorrowedGreenlet(self)->context(nctx); + return 0; + } + catch(const PyErrOccurred&) { + return -1; + } +} + + +static PyObject* +green_getframe(PyGreenlet* self, void* UNUSED(context)) +{ + const PythonState::OwnedFrame& top_frame = BorrowedGreenlet(self)->top_frame(); + return top_frame.acquire_or_None(); +} + + +static PyObject* +green_getstate(PyGreenlet* self) +{ + PyErr_Format(PyExc_TypeError, + "cannot serialize '%s' object", + Py_TYPE(self)->tp_name); + return nullptr; +} + +static PyObject* +green_repr(PyGreenlet* _self) +{ + BorrowedGreenlet self(_self); + /* + Return a string like + + + The handling of greenlets across threads is not super good. + We mostly use the internal definitions of these terms, but they + generally should make sense to users as well. + */ + PyObject* result; + int never_started = !self->started() && !self->active(); + + const char* const tp_name = Py_TYPE(self)->tp_name; + + if (_green_not_dead(self)) { + /* XXX: The otid= is almost useless because you can't correlate it to + any thread identifier exposed to Python. We could use + PyThreadState_GET()->thread_id, but we'd need to save that in the + greenlet, or save the whole PyThreadState object itself. + + As it stands, its only useful for identifying greenlets from the same thread. + */ + const char* state_in_thread; + if (self->was_running_in_dead_thread()) { + // The thread it was running in is dead! + // This can happen, especially at interpreter shut down. + // It complicates debugging output because it may be + // impossible to access the current thread state at that + // time. Thus, don't access the current thread state. + state_in_thread = " (thread exited)"; + } + else { + state_in_thread = GET_THREAD_STATE().state().is_current(self) + ? " current" + : (self->started() ? " suspended" : ""); + } + result = PyUnicode_FromFormat( + "<%s object at %p (otid=%p)%s%s%s%s>", + tp_name, + self.borrow_o(), + self->thread_state(), + state_in_thread, + self->active() ? " active" : "", + never_started ? " pending" : " started", + self->main() ? " main" : "" + ); + } + else { + result = PyUnicode_FromFormat( + "<%s object at %p (otid=%p) %sdead>", + tp_name, + self.borrow_o(), + self->thread_state(), + self->was_running_in_dead_thread() + ? "(thread exited) " + : "" + ); + } + + return result; +} + + +static PyMethodDef green_methods[] = { + { + .ml_name="switch", + .ml_meth=reinterpret_cast(green_switch), + .ml_flags=METH_VARARGS | METH_KEYWORDS, + .ml_doc=green_switch_doc + }, + {.ml_name="throw", .ml_meth=(PyCFunction)green_throw, .ml_flags=METH_VARARGS, .ml_doc=green_throw_doc}, + {.ml_name="__getstate__", .ml_meth=(PyCFunction)green_getstate, .ml_flags=METH_NOARGS, .ml_doc=NULL}, + {.ml_name=NULL, .ml_meth=NULL} /* sentinel */ +}; + +static PyGetSetDef green_getsets[] = { + /* name, getter, setter, doc, context pointer */ + {.name="__dict__", .get=(getter)green_getdict, .set=(setter)green_setdict}, + {.name="run", .get=(getter)green_getrun, .set=(setter)green_setrun}, + {.name="parent", .get=(getter)green_getparent, .set=(setter)green_setparent}, + {.name="gr_frame", .get=(getter)green_getframe }, + { + .name="gr_context", + .get=(getter)green_getcontext, + .set=(setter)green_setcontext + }, + {.name="dead", .get=(getter)green_getdead}, + {.name="_stack_saved", .get=(getter)green_get_stack_saved}, + {.name=NULL} +}; + +static PyMemberDef green_members[] = { + {.name=NULL} +}; + +static PyNumberMethods green_as_number = { + .nb_bool=(inquiry)green_bool, +}; + + +PyTypeObject PyGreenlet_Type = { + .ob_base=PyVarObject_HEAD_INIT(NULL, 0) + .tp_name="greenlet.greenlet", /* tp_name */ + .tp_basicsize=sizeof(PyGreenlet), /* tp_basicsize */ + /* methods */ + .tp_dealloc=(destructor)green_dealloc, /* tp_dealloc */ + .tp_repr=(reprfunc)green_repr, /* tp_repr */ + .tp_as_number=&green_as_number, /* tp_as _number*/ + .tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + .tp_doc="greenlet(run=None, parent=None) -> greenlet\n\n" + "Creates a new greenlet object (without running it).\n\n" + " - *run* -- The callable to invoke.\n" + " - *parent* -- The parent greenlet. The default is the current " + "greenlet.", /* tp_doc */ + .tp_traverse=(traverseproc)green_traverse, /* tp_traverse */ + .tp_clear=(inquiry)green_clear, /* tp_clear */ + .tp_weaklistoffset=offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */ + + .tp_methods=green_methods, /* tp_methods */ + .tp_members=green_members, /* tp_members */ + .tp_getset=green_getsets, /* tp_getset */ + .tp_dictoffset=offsetof(PyGreenlet, dict), /* tp_dictoffset */ + .tp_init=(initproc)green_init, /* tp_init */ + .tp_alloc=PyType_GenericAlloc, /* tp_alloc */ + .tp_new=(newfunc)green_new, /* tp_new */ + .tp_free=PyObject_GC_Del, /* tp_free */ + .tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */ +}; + +#endif + +// Local Variables: +// flycheck-clang-include-path: ("/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8") +// End: diff --git a/venv/lib/python3.8/site-packages/greenlet/PyGreenlet.hpp b/venv/lib/python3.8/site-packages/greenlet/PyGreenlet.hpp new file mode 100644 index 000000000..df6cd805b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/PyGreenlet.hpp @@ -0,0 +1,35 @@ +#ifndef PYGREENLET_HPP +#define PYGREENLET_HPP + + +#include "greenlet.h" +#include "greenlet_compiler_compat.hpp" +#include "greenlet_refs.hpp" + + +using greenlet::refs::OwnedGreenlet; +using greenlet::refs::BorrowedGreenlet; +using greenlet::refs::BorrowedObject;; +using greenlet::refs::OwnedObject; +using greenlet::refs::PyErrPieces; + + +// XXX: These doesn't really belong here, it's not a Python slot. +static OwnedObject internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces); + +static PyGreenlet* green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)); +static int green_clear(PyGreenlet* self); +static int green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs); +static int green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context)); +static int green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context)); +static int green_traverse(PyGreenlet* self, visitproc visit, void* arg); +static void green_dealloc(PyGreenlet* self); +static PyObject* green_getparent(PyGreenlet* self, void* UNUSED(context)); + +static int green_is_gc(PyObject* self); +static PyObject* green_getdead(PyGreenlet* self, void* UNUSED(context)); +static PyObject* green_getrun(PyGreenlet* self, void* UNUSED(context)); +static int green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context)); +static PyObject* green_getframe(PyGreenlet* self, void* UNUSED(context)); +static PyObject* green_repr(PyGreenlet* self); +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/PyGreenletUnswitchable.cpp b/venv/lib/python3.8/site-packages/greenlet/PyGreenletUnswitchable.cpp new file mode 100644 index 000000000..1b768ee34 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/PyGreenletUnswitchable.cpp @@ -0,0 +1,147 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + Implementation of the Python slots for PyGreenletUnswitchable_Type +*/ +#ifndef PY_GREENLET_UNSWITCHABLE_CPP +#define PY_GREENLET_UNSWITCHABLE_CPP + + + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" // PyMemberDef + +#include "greenlet_internal.hpp" +// Code after this point can assume access to things declared in stdint.h, +// including the fixed-width types. This goes for the platform-specific switch functions +// as well. +#include "greenlet_refs.hpp" +#include "greenlet_slp_switch.hpp" + +#include "greenlet_thread_support.hpp" +#include "TGreenlet.hpp" + +#include "TGreenlet.cpp" +#include "TGreenletGlobals.cpp" +#include "TThreadStateDestroy.cpp" + + +using greenlet::LockGuard; +using greenlet::LockInitError; +using greenlet::PyErrOccurred; +using greenlet::Require; + +using greenlet::g_handle_exit; +using greenlet::single_result; + +using greenlet::Greenlet; +using greenlet::UserGreenlet; +using greenlet::MainGreenlet; +using greenlet::BrokenGreenlet; +using greenlet::ThreadState; +using greenlet::PythonState; + + +#include "PyGreenlet.hpp" + +static PyGreenlet* +green_unswitchable_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)) +{ + PyGreenlet* o = + (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict); + if (o) { + new BrokenGreenlet(o, GET_THREAD_STATE().state().borrow_current()); + assert(Py_REFCNT(o) == 1); + } + return o; +} + +static PyObject* +green_unswitchable_getforce(PyGreenlet* self, void* UNUSED(context)) +{ + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + return PyBool_FromLong(broken->_force_switch_error); +} + +static int +green_unswitchable_setforce(PyGreenlet* self, PyObject* nforce, void* UNUSED(context)) +{ + if (!nforce) { + PyErr_SetString( + PyExc_AttributeError, + "Cannot delete force_switch_error" + ); + return -1; + } + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + int is_true = PyObject_IsTrue(nforce); + if (is_true == -1) { + return -1; + } + broken->_force_switch_error = is_true; + return 0; +} + +static PyObject* +green_unswitchable_getforceslp(PyGreenlet* self, void* UNUSED(context)) +{ + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + return PyBool_FromLong(broken->_force_slp_switch_error); +} + +static int +green_unswitchable_setforceslp(PyGreenlet* self, PyObject* nforce, void* UNUSED(context)) +{ + if (!nforce) { + PyErr_SetString( + PyExc_AttributeError, + "Cannot delete force_slp_switch_error" + ); + return -1; + } + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + int is_true = PyObject_IsTrue(nforce); + if (is_true == -1) { + return -1; + } + broken->_force_slp_switch_error = is_true; + return 0; +} + +static PyGetSetDef green_unswitchable_getsets[] = { + /* name, getter, setter, doc, closure (context pointer) */ + { + .name="force_switch_error", + .get=(getter)green_unswitchable_getforce, + .set=(setter)green_unswitchable_setforce, + .doc=NULL + }, + { + .name="force_slp_switch_error", + .get=(getter)green_unswitchable_getforceslp, + .set=(setter)green_unswitchable_setforceslp, + .doc=nullptr + }, + {.name=nullptr} +}; + +PyTypeObject PyGreenletUnswitchable_Type = { + .ob_base=PyVarObject_HEAD_INIT(NULL, 0) + .tp_name="greenlet._greenlet.UnswitchableGreenlet", + .tp_dealloc= (destructor)green_dealloc, /* tp_dealloc */ + .tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + .tp_doc="Undocumented internal class", /* tp_doc */ + .tp_traverse=(traverseproc)green_traverse, /* tp_traverse */ + .tp_clear=(inquiry)green_clear, /* tp_clear */ + + .tp_getset=green_unswitchable_getsets, /* tp_getset */ + .tp_base=&PyGreenlet_Type, /* tp_base */ + .tp_init=(initproc)green_init, /* tp_init */ + .tp_alloc=PyType_GenericAlloc, /* tp_alloc */ + .tp_new=(newfunc)green_unswitchable_new, /* tp_new */ + .tp_free=PyObject_GC_Del, /* tp_free */ + .tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */ +}; + + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/PyModule.cpp b/venv/lib/python3.8/site-packages/greenlet/PyModule.cpp new file mode 100644 index 000000000..6adcb5c3d --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/PyModule.cpp @@ -0,0 +1,292 @@ +#ifndef PY_MODULE_CPP +#define PY_MODULE_CPP + +#include "greenlet_internal.hpp" + + +#include "TGreenletGlobals.cpp" +#include "TMainGreenlet.cpp" +#include "TThreadStateDestroy.cpp" + +using greenlet::LockGuard; +using greenlet::ThreadState; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-variable" +#endif + +PyDoc_STRVAR(mod_getcurrent_doc, + "getcurrent() -> greenlet\n" + "\n" + "Returns the current greenlet (i.e. the one which called this " + "function).\n"); + +static PyObject* +mod_getcurrent(PyObject* UNUSED(module)) +{ + return GET_THREAD_STATE().state().get_current().relinquish_ownership_o(); +} + +PyDoc_STRVAR(mod_settrace_doc, + "settrace(callback) -> object\n" + "\n" + "Sets a new tracing function and returns the previous one.\n"); +static PyObject* +mod_settrace(PyObject* UNUSED(module), PyObject* args) +{ + PyArgParseParam tracefunc; + if (!PyArg_ParseTuple(args, "O", &tracefunc)) { + return NULL; + } + ThreadState& state = GET_THREAD_STATE(); + OwnedObject previous = state.get_tracefunc(); + if (!previous) { + previous = Py_None; + } + + state.set_tracefunc(tracefunc); + + return previous.relinquish_ownership(); +} + +PyDoc_STRVAR(mod_gettrace_doc, + "gettrace() -> object\n" + "\n" + "Returns the currently set tracing function, or None.\n"); + +static PyObject* +mod_gettrace(PyObject* UNUSED(module)) +{ + OwnedObject tracefunc = GET_THREAD_STATE().state().get_tracefunc(); + if (!tracefunc) { + tracefunc = Py_None; + } + return tracefunc.relinquish_ownership(); +} + + + +PyDoc_STRVAR(mod_set_thread_local_doc, + "set_thread_local(key, value) -> None\n" + "\n" + "Set a value in the current thread-local dictionary. Debugging only.\n"); + +static PyObject* +mod_set_thread_local(PyObject* UNUSED(module), PyObject* args) +{ + PyArgParseParam key; + PyArgParseParam value; + PyObject* result = NULL; + + if (PyArg_UnpackTuple(args, "set_thread_local", 2, 2, &key, &value)) { + if(PyDict_SetItem( + PyThreadState_GetDict(), // borrow + key, + value) == 0 ) { + // success + Py_INCREF(Py_None); + result = Py_None; + } + } + return result; +} + +PyDoc_STRVAR(mod_get_pending_cleanup_count_doc, + "get_pending_cleanup_count() -> Integer\n" + "\n" + "Get the number of greenlet cleanup operations pending. Testing only.\n"); + + +static PyObject* +mod_get_pending_cleanup_count(PyObject* UNUSED(module)) +{ + LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); + return PyLong_FromSize_t(mod_globs->thread_states_to_destroy.size()); +} + +PyDoc_STRVAR(mod_get_total_main_greenlets_doc, + "get_total_main_greenlets() -> Integer\n" + "\n" + "Quickly return the number of main greenlets that exist. Testing only.\n"); + +static PyObject* +mod_get_total_main_greenlets(PyObject* UNUSED(module)) +{ + return PyLong_FromSize_t(G_TOTAL_MAIN_GREENLETS); +} + + + +PyDoc_STRVAR(mod_get_clocks_used_doing_optional_cleanup_doc, + "get_clocks_used_doing_optional_cleanup() -> Integer\n" + "\n" + "Get the number of clock ticks the program has used doing optional " + "greenlet cleanup.\n" + "Beginning in greenlet 2.0, greenlet tries to find and dispose of greenlets\n" + "that leaked after a thread exited. This requires invoking Python's garbage collector,\n" + "which may have a performance cost proportional to the number of live objects.\n" + "This function returns the amount of processor time\n" + "greenlet has used to do this. In programs that run with very large amounts of live\n" + "objects, this metric can be used to decide whether the cost of doing this cleanup\n" + "is worth the memory leak being corrected. If not, you can disable the cleanup\n" + "using ``enable_optional_cleanup(False)``.\n" + "The units are arbitrary and can only be compared to themselves (similarly to ``time.clock()``);\n" + "for example, to see how it scales with your heap. You can attempt to convert them into seconds\n" + "by dividing by the value of CLOCKS_PER_SEC." + "If cleanup has been disabled, returns None." + "\n" + "This is an implementation specific, provisional API. It may be changed or removed\n" + "in the future.\n" + ".. versionadded:: 2.0" + ); +static PyObject* +mod_get_clocks_used_doing_optional_cleanup(PyObject* UNUSED(module)) +{ + std::clock_t& clocks = ThreadState::clocks_used_doing_gc(); + + if (clocks == std::clock_t(-1)) { + Py_RETURN_NONE; + } + // This might not actually work on some implementations; clock_t + // is an opaque type. + return PyLong_FromSsize_t(clocks); +} + +PyDoc_STRVAR(mod_enable_optional_cleanup_doc, + "mod_enable_optional_cleanup(bool) -> None\n" + "\n" + "Enable or disable optional cleanup operations.\n" + "See ``get_clocks_used_doing_optional_cleanup()`` for details.\n" + ); +static PyObject* +mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag) +{ + int is_true = PyObject_IsTrue(flag); + if (is_true == -1) { + return nullptr; + } + + std::clock_t& clocks = ThreadState::clocks_used_doing_gc(); + if (is_true) { + // If we already have a value, we don't want to lose it. + if (clocks == std::clock_t(-1)) { + clocks = 0; + } + } + else { + clocks = std::clock_t(-1); + } + Py_RETURN_NONE; +} + + + + +#if !GREENLET_PY313 +PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc, + "get_tstate_trash_delete_nesting() -> Integer\n" + "\n" + "Return the 'trash can' nesting level. Testing only.\n"); +static PyObject* +mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module)) +{ + PyThreadState* tstate = PyThreadState_GET(); + +#if GREENLET_PY312 + return PyLong_FromLong(tstate->trash.delete_nesting); +#else + return PyLong_FromLong(tstate->trash_delete_nesting); +#endif +} +#endif + + + + +static PyMethodDef GreenMethods[] = { + { + .ml_name="getcurrent", + .ml_meth=(PyCFunction)mod_getcurrent, + .ml_flags=METH_NOARGS, + .ml_doc=mod_getcurrent_doc + }, + { + .ml_name="settrace", + .ml_meth=(PyCFunction)mod_settrace, + .ml_flags=METH_VARARGS, + .ml_doc=mod_settrace_doc + }, + { + .ml_name="gettrace", + .ml_meth=(PyCFunction)mod_gettrace, + .ml_flags=METH_NOARGS, + .ml_doc=mod_gettrace_doc + }, + { + .ml_name="set_thread_local", + .ml_meth=(PyCFunction)mod_set_thread_local, + .ml_flags=METH_VARARGS, + .ml_doc=mod_set_thread_local_doc + }, + { + .ml_name="get_pending_cleanup_count", + .ml_meth=(PyCFunction)mod_get_pending_cleanup_count, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_pending_cleanup_count_doc + }, + { + .ml_name="get_total_main_greenlets", + .ml_meth=(PyCFunction)mod_get_total_main_greenlets, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_total_main_greenlets_doc + }, + { + .ml_name="get_clocks_used_doing_optional_cleanup", + .ml_meth=(PyCFunction)mod_get_clocks_used_doing_optional_cleanup, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_clocks_used_doing_optional_cleanup_doc + }, + { + .ml_name="enable_optional_cleanup", + .ml_meth=(PyCFunction)mod_enable_optional_cleanup, + .ml_flags=METH_O, + .ml_doc=mod_enable_optional_cleanup_doc + }, +#if !GREENLET_PY313 + { + .ml_name="get_tstate_trash_delete_nesting", + .ml_meth=(PyCFunction)mod_get_tstate_trash_delete_nesting, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_tstate_trash_delete_nesting_doc + }, +#endif + {.ml_name=NULL, .ml_meth=NULL} /* Sentinel */ +}; + +static const char* const copy_on_greentype[] = { + "getcurrent", + "error", + "GreenletExit", + "settrace", + "gettrace", + NULL +}; + +static struct PyModuleDef greenlet_module_def = { + .m_base=PyModuleDef_HEAD_INIT, + .m_name="greenlet._greenlet", + .m_doc=NULL, + .m_size=-1, + .m_methods=GreenMethods, +}; + + +#endif + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/TBrokenGreenlet.cpp b/venv/lib/python3.8/site-packages/greenlet/TBrokenGreenlet.cpp new file mode 100644 index 000000000..7e9ab5be9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TBrokenGreenlet.cpp @@ -0,0 +1,45 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::UserGreenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ + +#include "TGreenlet.hpp" + +namespace greenlet { + +void* BrokenGreenlet::operator new(size_t UNUSED(count)) +{ + return allocator.allocate(1); +} + + +void BrokenGreenlet::operator delete(void* ptr) +{ + return allocator.deallocate(static_cast(ptr), + 1); +} + +greenlet::PythonAllocator greenlet::BrokenGreenlet::allocator; + +bool +BrokenGreenlet::force_slp_switch_error() const noexcept +{ + return this->_force_slp_switch_error; +} + +UserGreenlet::switchstack_result_t BrokenGreenlet::g_switchstack(void) +{ + if (this->_force_switch_error) { + return switchstack_result_t(-1); + } + return UserGreenlet::g_switchstack(); +} + +}; //namespace greenlet diff --git a/venv/lib/python3.8/site-packages/greenlet/TExceptionState.cpp b/venv/lib/python3.8/site-packages/greenlet/TExceptionState.cpp new file mode 100644 index 000000000..08a94ae83 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TExceptionState.cpp @@ -0,0 +1,62 @@ +#ifndef GREENLET_EXCEPTION_STATE_CPP +#define GREENLET_EXCEPTION_STATE_CPP + +#include +#include "TGreenlet.hpp" + +namespace greenlet { + + +ExceptionState::ExceptionState() +{ + this->clear(); +} + +void ExceptionState::operator<<(const PyThreadState *const tstate) noexcept +{ + this->exc_info = tstate->exc_info; + this->exc_state = tstate->exc_state; +} + +void ExceptionState::operator>>(PyThreadState *const tstate) noexcept +{ + tstate->exc_state = this->exc_state; + tstate->exc_info = + this->exc_info ? this->exc_info : &tstate->exc_state; + this->clear(); +} + +void ExceptionState::clear() noexcept +{ + this->exc_info = nullptr; + this->exc_state.exc_value = nullptr; +#if !GREENLET_PY311 + this->exc_state.exc_type = nullptr; + this->exc_state.exc_traceback = nullptr; +#endif + this->exc_state.previous_item = nullptr; +} + +int ExceptionState::tp_traverse(visitproc visit, void* arg) noexcept +{ + Py_VISIT(this->exc_state.exc_value); +#if !GREENLET_PY311 + Py_VISIT(this->exc_state.exc_type); + Py_VISIT(this->exc_state.exc_traceback); +#endif + return 0; +} + +void ExceptionState::tp_clear() noexcept +{ + Py_CLEAR(this->exc_state.exc_value); +#if !GREENLET_PY311 + Py_CLEAR(this->exc_state.exc_type); + Py_CLEAR(this->exc_state.exc_traceback); +#endif +} + + +}; // namespace greenlet + +#endif // GREENLET_EXCEPTION_STATE_CPP diff --git a/venv/lib/python3.8/site-packages/greenlet/TGreenlet.cpp b/venv/lib/python3.8/site-packages/greenlet/TGreenlet.cpp new file mode 100644 index 000000000..4698a1784 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TGreenlet.cpp @@ -0,0 +1,718 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::Greenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef TGREENLET_CPP +#define TGREENLET_CPP +#include "greenlet_internal.hpp" +#include "TGreenlet.hpp" + + +#include "TGreenletGlobals.cpp" +#include "TThreadStateDestroy.cpp" + +namespace greenlet { + +Greenlet::Greenlet(PyGreenlet* p) + : Greenlet(p, StackState()) +{ +} + +Greenlet::Greenlet(PyGreenlet* p, const StackState& initial_stack) + : _self(p), stack_state(initial_stack) +{ + assert(p->pimpl == nullptr); + p->pimpl = this; +} + +Greenlet::~Greenlet() +{ + // XXX: Can't do this. tp_clear is a virtual function, and by the + // time we're here, we've sliced off our child classes. + //this->tp_clear(); + this->_self->pimpl = nullptr; +} + +bool +Greenlet::force_slp_switch_error() const noexcept +{ + return false; +} + +void +Greenlet::release_args() +{ + this->switch_args.CLEAR(); +} + +/** + * CAUTION: This will allocate memory and may trigger garbage + * collection and arbitrary Python code. + */ +OwnedObject +Greenlet::throw_GreenletExit_during_dealloc(const ThreadState& UNUSED(current_thread_state)) +{ + // If we're killed because we lost all references in the + // middle of a switch, that's ok. Don't reset the args/kwargs, + // we still want to pass them to the parent. + PyErr_SetString(mod_globs->PyExc_GreenletExit, + "Killing the greenlet because all references have vanished."); + // To get here it had to have run before + return this->g_switch(); +} + +inline void +Greenlet::slp_restore_state() noexcept +{ +#ifdef SLP_BEFORE_RESTORE_STATE + SLP_BEFORE_RESTORE_STATE(); +#endif + this->stack_state.copy_heap_to_stack( + this->thread_state()->borrow_current()->stack_state); +} + + +inline int +Greenlet::slp_save_state(char *const stackref) noexcept +{ + // XXX: This used to happen in the middle, before saving, but + // after finding the next owner. Does that matter? This is + // only defined for Sparc/GCC where it flushes register + // windows to the stack (I think) +#ifdef SLP_BEFORE_SAVE_STATE + SLP_BEFORE_SAVE_STATE(); +#endif + return this->stack_state.copy_stack_to_heap(stackref, + this->thread_state()->borrow_current()->stack_state); +} + +/** + * CAUTION: This will allocate memory and may trigger garbage + * collection and arbitrary Python code. + */ +OwnedObject +Greenlet::on_switchstack_or_initialstub_failure( + Greenlet* target, + const Greenlet::switchstack_result_t& err, + const bool target_was_me, + const bool was_initial_stub) +{ + // If we get here, either g_initialstub() + // failed, or g_switchstack() failed. Either one of those + // cases SHOULD leave us in the original greenlet with a valid stack. + if (!PyErr_Occurred()) { + PyErr_SetString( + PyExc_SystemError, + was_initial_stub + ? "Failed to switch stacks into a greenlet for the first time." + : "Failed to switch stacks into a running greenlet."); + } + this->release_args(); + + if (target && !target_was_me) { + target->murder_in_place(); + } + + assert(!err.the_new_current_greenlet); + assert(!err.origin_greenlet); + return OwnedObject(); + +} + +OwnedGreenlet +Greenlet::g_switchstack_success() noexcept +{ + PyThreadState* tstate = PyThreadState_GET(); + // restore the saved state + this->python_state >> tstate; + this->exception_state >> tstate; + + // The thread state hasn't been changed yet. + ThreadState* thread_state = this->thread_state(); + OwnedGreenlet result(thread_state->get_current()); + thread_state->set_current(this->self()); + //assert(thread_state->borrow_current().borrow() == this->_self); + return result; +} + +Greenlet::switchstack_result_t +Greenlet::g_switchstack(void) +{ + // if any of these assertions fail, it's likely because we + // switched away and tried to switch back to us. Early stages of + // switching are not reentrant because we re-use ``this->args()``. + // Switching away would happen if we trigger a garbage collection + // (by just using some Python APIs that happen to allocate Python + // objects) and some garbage had weakref callbacks or __del__ that + // switches (people don't write code like that by hand, but with + // gevent it's possible without realizing it) + assert(this->args() || PyErr_Occurred()); + { /* save state */ + if (this->thread_state()->is_current(this->self())) { + // Hmm, nothing to do. + // TODO: Does this bypass trace events that are + // important? + return switchstack_result_t(0, + this, this->thread_state()->borrow_current()); + } + BorrowedGreenlet current = this->thread_state()->borrow_current(); + PyThreadState* tstate = PyThreadState_GET(); + + current->python_state << tstate; + current->exception_state << tstate; + this->python_state.will_switch_from(tstate); + switching_thread_state = this; + current->expose_frames(); + } + assert(this->args() || PyErr_Occurred()); + // If this is the first switch into a greenlet, this will + // return twice, once with 1 in the new greenlet, once with 0 + // in the origin. + int err; + if (this->force_slp_switch_error()) { + err = -1; + } + else { + err = slp_switch(); + } + + if (err < 0) { /* error */ + // Tested by + // test_greenlet.TestBrokenGreenlets.test_failed_to_slp_switch_into_running + // + // It's not clear if it's worth trying to clean up and + // continue here. Failing to switch stacks is a big deal which + // may not be recoverable (who knows what state the stack is in). + // Also, we've stolen references in preparation for calling + // ``g_switchstack_success()`` and we don't have a clean + // mechanism for backing that all out. + Py_FatalError("greenlet: Failed low-level slp_switch(). The stack is probably corrupt."); + } + + // No stack-based variables are valid anymore. + + // But the global is volatile so we can reload it without the + // compiler caching it from earlier. + Greenlet* greenlet_that_switched_in = switching_thread_state; // aka this + switching_thread_state = nullptr; + // except that no stack variables are valid, we would: + // assert(this == greenlet_that_switched_in); + + // switchstack success is where we restore the exception state, + // etc. It returns the origin greenlet because its convenient. + + OwnedGreenlet origin = greenlet_that_switched_in->g_switchstack_success(); + assert(greenlet_that_switched_in->args() || PyErr_Occurred()); + return switchstack_result_t(err, greenlet_that_switched_in, origin); +} + + +inline void +Greenlet::check_switch_allowed() const +{ + // TODO: Make this take a parameter of the current greenlet, + // or current main greenlet, to make the check for + // cross-thread switching cheaper. Surely somewhere up the + // call stack we've already accessed the thread local variable. + + // We expect to always have a main greenlet now; accessing the thread state + // created it. However, if we get here and cleanup has already + // begun because we're a greenlet that was running in a + // (now dead) thread, these invariants will not hold true. In + // fact, accessing `this->thread_state` may not even be possible. + + // If the thread this greenlet was running in is dead, + // we'll still have a reference to a main greenlet, but the + // thread state pointer we have is bogus. + // TODO: Give the objects an API to determine if they belong + // to a dead thread. + + const BorrowedMainGreenlet main_greenlet = this->find_main_greenlet_in_lineage(); + + if (!main_greenlet) { + throw PyErrOccurred(mod_globs->PyExc_GreenletError, + "cannot switch to a garbage collected greenlet"); + } + + if (!main_greenlet->thread_state()) { + throw PyErrOccurred(mod_globs->PyExc_GreenletError, + "cannot switch to a different thread (which happens to have exited)"); + } + + // The main greenlet we found was from the .parent lineage. + // That may or may not have any relationship to the main + // greenlet of the running thread. We can't actually access + // our this->thread_state members to try to check that, + // because it could be in the process of getting destroyed, + // but setting the main_greenlet->thread_state member to NULL + // may not be visible yet. So we need to check against the + // current thread state (once the cheaper checks are out of + // the way) + const BorrowedMainGreenlet current_main_greenlet = GET_THREAD_STATE().state().borrow_main_greenlet(); + if ( + // lineage main greenlet is not this thread's greenlet + current_main_greenlet != main_greenlet + || ( + // atteched to some thread + this->main_greenlet() + // XXX: Same condition as above. Was this supposed to be + // this->main_greenlet()? + && current_main_greenlet != main_greenlet) + // switching into a known dead thread (XXX: which, if we get here, + // is bad, because we just accessed the thread state, which is + // gone!) + || (!current_main_greenlet->thread_state())) { + // CAUTION: This may trigger memory allocations, gc, and + // arbitrary Python code. + throw PyErrOccurred( + mod_globs->PyExc_GreenletError, + "Cannot switch to a different thread\n\tCurrent: %R\n\tExpected: %R", + current_main_greenlet, main_greenlet); + } +} + +const OwnedObject +Greenlet::context() const +{ + using greenlet::PythonStateContext; + OwnedObject result; + + if (this->is_currently_running_in_some_thread()) { + /* Currently running greenlet: context is stored in the thread state, + not the greenlet object. */ + if (GET_THREAD_STATE().state().is_current(this->self())) { + result = PythonStateContext::context(PyThreadState_GET()); + } + else { + throw ValueError( + "cannot get context of a " + "greenlet that is running in a different thread"); + } + } + else { + /* Greenlet is not running: just return context. */ + result = this->python_state.context(); + } + if (!result) { + result = OwnedObject::None(); + } + return result; +} + + +void +Greenlet::context(BorrowedObject given) +{ + using greenlet::PythonStateContext; + if (!given) { + throw AttributeError("can't delete context attribute"); + } + if (given.is_None()) { + /* "Empty context" is stored as NULL, not None. */ + given = nullptr; + } + + //checks type, incrs refcnt + greenlet::refs::OwnedContext context(given); + PyThreadState* tstate = PyThreadState_GET(); + + if (this->is_currently_running_in_some_thread()) { + if (!GET_THREAD_STATE().state().is_current(this->self())) { + throw ValueError("cannot set context of a greenlet" + " that is running in a different thread"); + } + + /* Currently running greenlet: context is stored in the thread state, + not the greenlet object. */ + OwnedObject octx = OwnedObject::consuming(PythonStateContext::context(tstate)); + PythonStateContext::context(tstate, context.relinquish_ownership()); + } + else { + /* Greenlet is not running: just set context. Note that the + greenlet may be dead.*/ + this->python_state.context() = context; + } +} + +/** + * CAUTION: May invoke arbitrary Python code. + * + * Figure out what the result of ``greenlet.switch(arg, kwargs)`` + * should be and transfers ownership of it to the left-hand-side. + * + * If switch() was just passed an arg tuple, then we'll just return that. + * If only keyword arguments were passed, then we'll pass the keyword + * argument dict. Otherwise, we'll create a tuple of (args, kwargs) and + * return both. + * + * CAUTION: This may allocate a new tuple object, which may + * cause the Python garbage collector to run, which in turn may + * run arbitrary Python code that switches. + */ +OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept +{ + // Because this may invoke arbitrary Python code, which could + // result in switching back to us, we need to get the + // arguments locally on the stack. + assert(rhs); + OwnedObject args = rhs.args(); + OwnedObject kwargs = rhs.kwargs(); + rhs.CLEAR(); + // We shouldn't be called twice for the same switch. + assert(args || kwargs); + assert(!rhs); + + if (!kwargs) { + lhs = args; + } + else if (!PyDict_Size(kwargs.borrow())) { + lhs = args; + } + else if (!PySequence_Length(args.borrow())) { + lhs = kwargs; + } + else { + // PyTuple_Pack allocates memory, may GC, may run arbitrary + // Python code. + lhs = OwnedObject::consuming(PyTuple_Pack(2, args.borrow(), kwargs.borrow())); + } + return lhs; +} + +static OwnedObject +g_handle_exit(const OwnedObject& greenlet_result) +{ + if (!greenlet_result && mod_globs->PyExc_GreenletExit.PyExceptionMatches()) { + /* catch and ignore GreenletExit */ + PyErrFetchParam val; + PyErr_Fetch(PyErrFetchParam(), val, PyErrFetchParam()); + if (!val) { + return OwnedObject::None(); + } + return OwnedObject(val); + } + + if (greenlet_result) { + // package the result into a 1-tuple + // PyTuple_Pack increments the reference of its arguments, + // so we always need to decref the greenlet result; + // the owner will do that. + return OwnedObject::consuming(PyTuple_Pack(1, greenlet_result.borrow())); + } + + return OwnedObject(); +} + + + +/** + * May run arbitrary Python code. + */ +OwnedObject +Greenlet::g_switch_finish(const switchstack_result_t& err) +{ + assert(err.the_new_current_greenlet == this); + + ThreadState& state = *this->thread_state(); + // Because calling the trace function could do arbitrary things, + // including switching away from this greenlet and then maybe + // switching back, we need to capture the arguments now so that + // they don't change. + OwnedObject result; + if (this->args()) { + result <<= this->args(); + } + else { + assert(PyErr_Occurred()); + } + assert(!this->args()); + try { + // Our only caller handles the bad error case + assert(err.status >= 0); + assert(state.borrow_current() == this->self()); + if (OwnedObject tracefunc = state.get_tracefunc()) { + assert(result || PyErr_Occurred()); + g_calltrace(tracefunc, + result ? mod_globs->event_switch : mod_globs->event_throw, + err.origin_greenlet, + this->self()); + } + // The above could have invoked arbitrary Python code, but + // it couldn't switch back to this object and *also* + // throw an exception, so the args won't have changed. + + if (PyErr_Occurred()) { + // We get here if we fell of the end of the run() function + // raising an exception. The switch itself was + // successful, but the function raised. + // valgrind reports that memory allocated here can still + // be reached after a test run. + throw PyErrOccurred::from_current(); + } + return result; + } + catch (const PyErrOccurred&) { + /* Turn switch errors into switch throws */ + /* Turn trace errors into switch throws */ + this->release_args(); + throw; + } +} + +void +Greenlet::g_calltrace(const OwnedObject& tracefunc, + const greenlet::refs::ImmortalEventName& event, + const BorrowedGreenlet& origin, + const BorrowedGreenlet& target) +{ + PyErrPieces saved_exc; + try { + TracingGuard tracing_guard; + // TODO: We have saved the active exception (if any) that's + // about to be raised. In the 'throw' case, we could provide + // the exception to the tracefunction, which seems very helpful. + tracing_guard.CallTraceFunction(tracefunc, event, origin, target); + } + catch (const PyErrOccurred&) { + // In case of exceptions trace function is removed, + // and any existing exception is replaced with the tracing + // exception. + GET_THREAD_STATE().state().set_tracefunc(Py_None); + throw; + } + + saved_exc.PyErrRestore(); + assert( + (event == mod_globs->event_throw && PyErr_Occurred()) + || (event == mod_globs->event_switch && !PyErr_Occurred()) + ); +} + +void +Greenlet::murder_in_place() +{ + if (this->active()) { + assert(!this->is_currently_running_in_some_thread()); + this->deactivate_and_free(); + } +} + +inline void +Greenlet::deactivate_and_free() +{ + if (!this->active()) { + return; + } + // Throw away any saved stack. + this->stack_state = StackState(); + assert(!this->stack_state.active()); + // Throw away any Python references. + // We're holding a borrowed reference to the last + // frame we executed. Since we borrowed it, the + // normal traversal, clear, and dealloc functions + // ignore it, meaning it leaks. (The thread state + // object can't find it to clear it when that's + // deallocated either, because by definition if we + // got an object on this list, it wasn't + // running and the thread state doesn't have + // this frame.) + // So here, we *do* clear it. + this->python_state.tp_clear(true); +} + +bool +Greenlet::belongs_to_thread(const ThreadState* thread_state) const +{ + if (!this->thread_state() // not running anywhere, or thread + // exited + || !thread_state) { // same, or there is no thread state. + return false; + } + return true; +} + + +void +Greenlet::deallocing_greenlet_in_thread(const ThreadState* current_thread_state) +{ + /* Cannot raise an exception to kill the greenlet if + it is not running in the same thread! */ + if (this->belongs_to_thread(current_thread_state)) { + assert(current_thread_state); + // To get here it had to have run before + /* Send the greenlet a GreenletExit exception. */ + + // We don't care about the return value, only whether an + // exception happened. + this->throw_GreenletExit_during_dealloc(*current_thread_state); + return; + } + + // Not the same thread! Temporarily save the greenlet + // into its thread's deleteme list, *if* it exists. + // If that thread has already exited, and processed its pending + // cleanup, we'll never be able to clean everything up: we won't + // be able to raise an exception. + // That's mostly OK! Since we can't add it to a list, our refcount + // won't increase, and we'll go ahead with the DECREFs later. + ThreadState *const thread_state = this->thread_state(); + if (thread_state) { + thread_state->delete_when_thread_running(this->self()); + } + else { + // The thread is dead, we can't raise an exception. + // We need to make it look non-active, though, so that dealloc + // finishes killing it. + this->deactivate_and_free(); + } + return; +} + + +int +Greenlet::tp_traverse(visitproc visit, void* arg) +{ + + int result; + if ((result = this->exception_state.tp_traverse(visit, arg)) != 0) { + return result; + } + //XXX: This is ugly. But so is handling everything having to do + //with the top frame. + bool visit_top_frame = this->was_running_in_dead_thread(); + // When true, the thread is dead. Our implicit weak reference to the + // frame is now all that's left; we consider ourselves to + // strongly own it now. + if ((result = this->python_state.tp_traverse(visit, arg, visit_top_frame)) != 0) { + return result; + } + return 0; +} + +int +Greenlet::tp_clear() +{ + bool own_top_frame = this->was_running_in_dead_thread(); + this->exception_state.tp_clear(); + this->python_state.tp_clear(own_top_frame); + return 0; +} + +bool Greenlet::is_currently_running_in_some_thread() const +{ + return this->stack_state.active() && !this->python_state.top_frame(); +} + +#if GREENLET_PY312 +void GREENLET_NOINLINE(Greenlet::expose_frames)() +{ + if (!this->python_state.top_frame()) { + return; + } + + _PyInterpreterFrame* last_complete_iframe = nullptr; + _PyInterpreterFrame* iframe = this->python_state.top_frame()->f_frame; + while (iframe) { + // We must make a copy before looking at the iframe contents, + // since iframe might point to a portion of the greenlet's C stack + // that was spilled when switching greenlets. + _PyInterpreterFrame iframe_copy; + this->stack_state.copy_from_stack(&iframe_copy, iframe, sizeof(*iframe)); + if (!_PyFrame_IsIncomplete(&iframe_copy)) { + // If the iframe were OWNED_BY_CSTACK then it would always be + // incomplete. Since it's not incomplete, it's not on the C stack + // and we can access it through the original `iframe` pointer + // directly. This is important since GetFrameObject might + // lazily _create_ the frame object and we don't want the + // interpreter to lose track of it. + assert(iframe_copy.owner != FRAME_OWNED_BY_CSTACK); + + // We really want to just write: + // PyFrameObject* frame = _PyFrame_GetFrameObject(iframe); + // but _PyFrame_GetFrameObject calls _PyFrame_MakeAndSetFrameObject + // which is not a visible symbol in libpython. The easiest + // way to get a public function to call it is using + // PyFrame_GetBack, which is defined as follows: + // assert(frame != NULL); + // assert(!_PyFrame_IsIncomplete(frame->f_frame)); + // PyFrameObject *back = frame->f_back; + // if (back == NULL) { + // _PyInterpreterFrame *prev = frame->f_frame->previous; + // prev = _PyFrame_GetFirstComplete(prev); + // if (prev) { + // back = _PyFrame_GetFrameObject(prev); + // } + // } + // return (PyFrameObject*)Py_XNewRef(back); + if (!iframe->frame_obj) { + PyFrameObject dummy_frame; + _PyInterpreterFrame dummy_iframe; + dummy_frame.f_back = nullptr; + dummy_frame.f_frame = &dummy_iframe; + // force the iframe to be considered complete without + // needing to check its code object: + dummy_iframe.owner = FRAME_OWNED_BY_GENERATOR; + dummy_iframe.previous = iframe; + assert(!_PyFrame_IsIncomplete(&dummy_iframe)); + // Drop the returned reference immediately; the iframe + // continues to hold a strong reference + Py_XDECREF(PyFrame_GetBack(&dummy_frame)); + assert(iframe->frame_obj); + } + + // This is a complete frame, so make the last one of those we saw + // point at it, bypassing any incomplete frames (which may have + // been on the C stack) in between the two. We're overwriting + // last_complete_iframe->previous and need that to be reversible, + // so we store the original previous ptr in the frame object + // (which we must have created on a previous iteration through + // this loop). The frame object has a bunch of storage that is + // only used when its iframe is OWNED_BY_FRAME_OBJECT, which only + // occurs when the frame object outlives the frame's execution, + // which can't have happened yet because the frame is currently + // executing as far as the interpreter is concerned. So, we can + // reuse it for our own purposes. + assert(iframe->owner == FRAME_OWNED_BY_THREAD + || iframe->owner == FRAME_OWNED_BY_GENERATOR); + if (last_complete_iframe) { + assert(last_complete_iframe->frame_obj); + memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0], + &last_complete_iframe->previous, sizeof(void *)); + last_complete_iframe->previous = iframe; + } + last_complete_iframe = iframe; + } + // Frames that are OWNED_BY_FRAME_OBJECT are linked via the + // frame's f_back while all others are linked via the iframe's + // previous ptr. Since all the frames we traverse are running + // as far as the interpreter is concerned, we don't have to + // worry about the OWNED_BY_FRAME_OBJECT case. + iframe = iframe_copy.previous; + } + + // Give the outermost complete iframe a null previous pointer to + // account for any potential incomplete/C-stack iframes between it + // and the actual top-of-stack + if (last_complete_iframe) { + assert(last_complete_iframe->frame_obj); + memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0], + &last_complete_iframe->previous, sizeof(void *)); + last_complete_iframe->previous = nullptr; + } +} +#else +void Greenlet::expose_frames() +{ + +} +#endif + +}; // namespace greenlet +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/TGreenlet.hpp b/venv/lib/python3.8/site-packages/greenlet/TGreenlet.hpp new file mode 100644 index 000000000..512f7fb3c --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TGreenlet.hpp @@ -0,0 +1,813 @@ +#ifndef GREENLET_GREENLET_HPP +#define GREENLET_GREENLET_HPP +/* + * Declarations of the core data structures. +*/ + +#define PY_SSIZE_T_CLEAN +#include + +#include "greenlet_compiler_compat.hpp" +#include "greenlet_refs.hpp" +#include "greenlet_cpython_compat.hpp" +#include "greenlet_allocator.hpp" + +using greenlet::refs::OwnedObject; +using greenlet::refs::OwnedGreenlet; +using greenlet::refs::OwnedMainGreenlet; +using greenlet::refs::BorrowedGreenlet; + +#if PY_VERSION_HEX < 0x30B00A6 +# define _PyCFrame CFrame +# define _PyInterpreterFrame _interpreter_frame +#endif + +#if GREENLET_PY312 +# define Py_BUILD_CORE +# include "internal/pycore_frame.h" +#endif + +// XXX: TODO: Work to remove all virtual functions +// for speed of calling and size of objects (no vtable). +// One pattern is the Curiously Recurring Template +namespace greenlet +{ + class ExceptionState + { + private: + G_NO_COPIES_OF_CLS(ExceptionState); + + // Even though these are borrowed objects, we actually own + // them, when they're not null. + // XXX: Express that in the API. + private: + _PyErr_StackItem* exc_info; + _PyErr_StackItem exc_state; + public: + ExceptionState(); + void operator<<(const PyThreadState *const tstate) noexcept; + void operator>>(PyThreadState* tstate) noexcept; + void clear() noexcept; + + int tp_traverse(visitproc visit, void* arg) noexcept; + void tp_clear() noexcept; + }; + + template + void operator<<(const PyThreadState *const tstate, T& exc); + + class PythonStateContext + { + protected: + greenlet::refs::OwnedContext _context; + public: + inline const greenlet::refs::OwnedContext& context() const + { + return this->_context; + } + inline greenlet::refs::OwnedContext& context() + { + return this->_context; + } + + inline void tp_clear() + { + this->_context.CLEAR(); + } + + template + inline static PyObject* context(T* tstate) + { + return tstate->context; + } + + template + inline static void context(T* tstate, PyObject* new_context) + { + tstate->context = new_context; + tstate->context_ver++; + } + }; + class SwitchingArgs; + class PythonState : public PythonStateContext + { + public: + typedef greenlet::refs::OwnedReference OwnedFrame; + private: + G_NO_COPIES_OF_CLS(PythonState); + // We own this if we're suspended (although currently we don't + // tp_traverse into it; that's a TODO). If we're running, it's + // empty. If we get deallocated and *still* have a frame, it + // won't be reachable from the place that normally decref's + // it, so we need to do it (hence owning it). + OwnedFrame _top_frame; +#if GREENLET_USE_CFRAME + _PyCFrame* cframe; + int use_tracing; +#endif +#if GREENLET_PY312 + int py_recursion_depth; + int c_recursion_depth; +#else + int recursion_depth; +#endif +#if GREENLET_PY313 + PyObject *delete_later; +#else + int trash_delete_nesting; +#endif +#if GREENLET_PY311 + _PyInterpreterFrame* current_frame; + _PyStackChunk* datastack_chunk; + PyObject** datastack_top; + PyObject** datastack_limit; +#endif + // The PyInterpreterFrame list on 3.12+ contains some entries that are + // on the C stack, which can't be directly accessed while a greenlet is + // suspended. In order to keep greenlet gr_frame introspection working, + // we adjust stack switching to rewrite the interpreter frame list + // to skip these C-stack frames; we call this "exposing" the greenlet's + // frames because it makes them valid to work with in Python. Then when + // the greenlet is resumed we need to remember to reverse the operation + // we did. The C-stack frames are "entry frames" which are a low-level + // interpreter detail; they're not needed for introspection, but do + // need to be present for the eval loop to work. + void unexpose_frames(); + + public: + + PythonState(); + // You can use this for testing whether we have a frame + // or not. It returns const so they can't modify it. + const OwnedFrame& top_frame() const noexcept; + + inline void operator<<(const PyThreadState *const tstate) noexcept; + inline void operator>>(PyThreadState* tstate) noexcept; + void clear() noexcept; + + int tp_traverse(visitproc visit, void* arg, bool visit_top_frame) noexcept; + void tp_clear(bool own_top_frame) noexcept; + void set_initial_state(const PyThreadState* const tstate) noexcept; +#if GREENLET_USE_CFRAME + void set_new_cframe(_PyCFrame& frame) noexcept; +#endif + + void may_switch_away() noexcept; + inline void will_switch_from(PyThreadState *const origin_tstate) noexcept; + void did_finish(PyThreadState* tstate) noexcept; + }; + + class StackState + { + // By having only plain C (POD) members, no virtual functions + // or bases, we get a trivial assignment operator generated + // for us. However, that's not safe since we do manage memory. + // So we declare an assignment operator that only works if we + // don't have any memory allocated. (We don't use + // std::shared_ptr for reference counting just to keep this + // object small) + private: + char* _stack_start; + char* stack_stop; + char* stack_copy; + intptr_t _stack_saved; + StackState* stack_prev; + inline int copy_stack_to_heap_up_to(const char* const stop) noexcept; + inline void free_stack_copy() noexcept; + + public: + /** + * Creates a started, but inactive, state, using *current* + * as the previous. + */ + StackState(void* mark, StackState& current); + /** + * Creates an inactive, unstarted, state. + */ + StackState(); + ~StackState(); + StackState(const StackState& other); + StackState& operator=(const StackState& other); + inline void copy_heap_to_stack(const StackState& current) noexcept; + inline int copy_stack_to_heap(char* const stackref, const StackState& current) noexcept; + inline bool started() const noexcept; + inline bool main() const noexcept; + inline bool active() const noexcept; + inline void set_active() noexcept; + inline void set_inactive() noexcept; + inline intptr_t stack_saved() const noexcept; + inline char* stack_start() const noexcept; + static inline StackState make_main() noexcept; +#ifdef GREENLET_USE_STDIO + friend std::ostream& operator<<(std::ostream& os, const StackState& s); +#endif + + // Fill in [dest, dest + n) with the values that would be at + // [src, src + n) while this greenlet is running. This is like memcpy + // except that if the greenlet is suspended it accounts for the portion + // of the greenlet's stack that was spilled to the heap. `src` may + // be on this greenlet's stack, or on the heap, but not on a different + // greenlet's stack. + void copy_from_stack(void* dest, const void* src, size_t n) const; + }; +#ifdef GREENLET_USE_STDIO + std::ostream& operator<<(std::ostream& os, const StackState& s); +#endif + + class SwitchingArgs + { + private: + G_NO_ASSIGNMENT_OF_CLS(SwitchingArgs); + // If args and kwargs are both false (NULL), this is a *throw*, not a + // switch. PyErr_... must have been called already. + OwnedObject _args; + OwnedObject _kwargs; + public: + + SwitchingArgs() + {} + + SwitchingArgs(const OwnedObject& args, const OwnedObject& kwargs) + : _args(args), + _kwargs(kwargs) + {} + + SwitchingArgs(const SwitchingArgs& other) + : _args(other._args), + _kwargs(other._kwargs) + {} + + const OwnedObject& args() + { + return this->_args; + } + + const OwnedObject& kwargs() + { + return this->_kwargs; + } + + /** + * Moves ownership from the argument to this object. + */ + SwitchingArgs& operator<<=(SwitchingArgs& other) + { + if (this != &other) { + this->_args = other._args; + this->_kwargs = other._kwargs; + other.CLEAR(); + } + return *this; + } + + /** + * Acquires ownership of the argument (consumes the reference). + */ + SwitchingArgs& operator<<=(PyObject* args) + { + this->_args = OwnedObject::consuming(args); + this->_kwargs.CLEAR(); + return *this; + } + + /** + * Acquires ownership of the argument. + * + * Sets the args to be the given value; clears the kwargs. + */ + SwitchingArgs& operator<<=(OwnedObject& args) + { + assert(&args != &this->_args); + this->_args = args; + this->_kwargs.CLEAR(); + args.CLEAR(); + + return *this; + } + + explicit operator bool() const noexcept + { + return this->_args || this->_kwargs; + } + + inline void CLEAR() + { + this->_args.CLEAR(); + this->_kwargs.CLEAR(); + } + + const std::string as_str() const noexcept + { + return PyUnicode_AsUTF8( + OwnedObject::consuming( + PyUnicode_FromFormat( + "SwitchingArgs(args=%R, kwargs=%R)", + this->_args.borrow(), + this->_kwargs.borrow() + ) + ).borrow() + ); + } + }; + + class ThreadState; + + class UserGreenlet; + class MainGreenlet; + + class Greenlet + { + private: + G_NO_COPIES_OF_CLS(Greenlet); + PyGreenlet* const _self; + private: + // XXX: Work to remove these. + friend class ThreadState; + friend class UserGreenlet; + friend class MainGreenlet; + protected: + ExceptionState exception_state; + SwitchingArgs switch_args; + StackState stack_state; + PythonState python_state; + Greenlet(PyGreenlet* p, const StackState& initial_state); + public: + // This constructor takes ownership of the PyGreenlet, by + // setting ``p->pimpl = this;``. + Greenlet(PyGreenlet* p); + virtual ~Greenlet(); + + const OwnedObject context() const; + + // You MUST call this _very_ early in the switching process to + // prepare anything that may need prepared. This might perform + // garbage collections or otherwise run arbitrary Python code. + // + // One specific use of it is for Python 3.11+, preventing + // running arbitrary code at unsafe times. See + // PythonState::may_switch_away(). + inline void may_switch_away() + { + this->python_state.may_switch_away(); + } + + inline void context(refs::BorrowedObject new_context); + + inline SwitchingArgs& args() + { + return this->switch_args; + } + + virtual const refs::BorrowedMainGreenlet main_greenlet() const = 0; + + inline intptr_t stack_saved() const noexcept + { + return this->stack_state.stack_saved(); + } + + // This is used by the macro SLP_SAVE_STATE to compute the + // difference in stack sizes. It might be nice to handle the + // computation ourself, but the type of the result + // varies by platform, so doing it in the macro is the + // simplest way. + inline const char* stack_start() const noexcept + { + return this->stack_state.stack_start(); + } + + virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state); + virtual OwnedObject g_switch() = 0; + /** + * Force the greenlet to appear dead. Used when it's not + * possible to throw an exception into a greenlet anymore. + * + * This losses access to the thread state and the main greenlet. + */ + virtual void murder_in_place(); + + /** + * Called when somebody notices we were running in a dead + * thread to allow cleaning up resources (because we can't + * raise GreenletExit into it anymore). + * This is very similar to ``murder_in_place()``, except that + * it DOES NOT lose the main greenlet or thread state. + */ + inline void deactivate_and_free(); + + + // Called when some thread wants to deallocate a greenlet + // object. + // The thread may or may not be the same thread the greenlet + // was running in. + // The thread state will be null if the thread the greenlet + // was running in was known to have exited. + void deallocing_greenlet_in_thread(const ThreadState* current_state); + + // Must be called on 3.12+ before exposing a suspended greenlet's + // frames to user code. This rewrites the linked list of interpreter + // frames to skip the ones that are being stored on the C stack (which + // can't be safely accessed while the greenlet is suspended because + // that stack space might be hosting a different greenlet), and + // sets PythonState::frames_were_exposed so we remember to restore + // the original list before resuming the greenlet. The C-stack frames + // are a low-level interpreter implementation detail; while they're + // important to the bytecode eval loop, they're superfluous for + // introspection purposes. + void expose_frames(); + + + // TODO: Figure out how to make these non-public. + inline void slp_restore_state() noexcept; + inline int slp_save_state(char *const stackref) noexcept; + + inline bool is_currently_running_in_some_thread() const; + virtual bool belongs_to_thread(const ThreadState* state) const; + + inline bool started() const + { + return this->stack_state.started(); + } + inline bool active() const + { + return this->stack_state.active(); + } + inline bool main() const + { + return this->stack_state.main(); + } + virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const = 0; + + virtual const OwnedGreenlet parent() const = 0; + virtual void parent(const refs::BorrowedObject new_parent) = 0; + + inline const PythonState::OwnedFrame& top_frame() + { + return this->python_state.top_frame(); + } + + virtual const OwnedObject& run() const = 0; + virtual void run(const refs::BorrowedObject nrun) = 0; + + + virtual int tp_traverse(visitproc visit, void* arg); + virtual int tp_clear(); + + + // Return the thread state that the greenlet is running in, or + // null if the greenlet is not running or the thread is known + // to have exited. + virtual ThreadState* thread_state() const noexcept = 0; + + // Return true if the greenlet is known to have been running + // (active) in a thread that has now exited. + virtual bool was_running_in_dead_thread() const noexcept = 0; + + // Return a borrowed greenlet that is the Python object + // this object represents. + inline BorrowedGreenlet self() const noexcept + { + return BorrowedGreenlet(this->_self); + } + + // For testing. If this returns true, we should pretend that + // slp_switch() failed. + virtual bool force_slp_switch_error() const noexcept; + + protected: + inline void release_args(); + + // The functions that must not be inlined are declared virtual. + // We also mark them as protected, not private, so that the + // compiler is forced to call them through a function pointer. + // (A sufficiently smart compiler could directly call a private + // virtual function since it can never be overridden in a + // subclass). + + // Also TODO: Switch away from integer error codes and to enums, + // or throw exceptions when possible. + struct switchstack_result_t + { + int status; + Greenlet* the_new_current_greenlet; + OwnedGreenlet origin_greenlet; + + switchstack_result_t() + : status(0), + the_new_current_greenlet(nullptr) + {} + + switchstack_result_t(int err) + : status(err), + the_new_current_greenlet(nullptr) + {} + + switchstack_result_t(int err, Greenlet* state, OwnedGreenlet& origin) + : status(err), + the_new_current_greenlet(state), + origin_greenlet(origin) + { + } + + switchstack_result_t(int err, Greenlet* state, const BorrowedGreenlet& origin) + : status(err), + the_new_current_greenlet(state), + origin_greenlet(origin) + { + } + + switchstack_result_t(const switchstack_result_t& other) + : status(other.status), + the_new_current_greenlet(other.the_new_current_greenlet), + origin_greenlet(other.origin_greenlet) + {} + + switchstack_result_t& operator=(const switchstack_result_t& other) + { + this->status = other.status; + this->the_new_current_greenlet = other.the_new_current_greenlet; + this->origin_greenlet = other.origin_greenlet; + return *this; + } + }; + + OwnedObject on_switchstack_or_initialstub_failure( + Greenlet* target, + const switchstack_result_t& err, + const bool target_was_me=false, + const bool was_initial_stub=false); + + // Returns the previous greenlet we just switched away from. + virtual OwnedGreenlet g_switchstack_success() noexcept; + + + // Check the preconditions for switching to this greenlet; if they + // aren't met, throws PyErrOccurred. Most callers will want to + // catch this and clear the arguments + inline void check_switch_allowed() const; + class GreenletStartedWhileInPython : public std::runtime_error + { + public: + GreenletStartedWhileInPython() : std::runtime_error("") + {} + }; + + protected: + + + /** + Perform a stack switch into this greenlet. + + This temporarily sets the global variable + ``switching_thread_state`` to this greenlet; as soon as the + call to ``slp_switch`` completes, this is reset to NULL. + Consequently, this depends on the GIL. + + TODO: Adopt the stackman model and pass ``slp_switch`` a + callback function and context pointer; this eliminates the + need for global variables altogether. + + Because the stack switch happens in this function, this + function can't use its own stack (local) variables, set + before the switch, and then accessed after the switch. + + Further, you con't even access ``g_thread_state_global`` + before and after the switch from the global variable. + Because it is thread local some compilers cache it in a + register/on the stack, notably new versions of MSVC; this + breaks with strange crashes sometime later, because writing + to anything in ``g_thread_state_global`` after the switch + is actually writing to random memory. For this reason, we + call a non-inlined function to finish the operation. (XXX: + The ``/GT`` MSVC compiler argument probably fixes that.) + + It is very important that stack switch is 'atomic', i.e. no + calls into other Python code allowed (except very few that + are safe), because global variables are very fragile. (This + should no longer be the case with thread-local variables.) + + */ + // Made virtual to facilitate subclassing UserGreenlet for testing. + virtual switchstack_result_t g_switchstack(void); + +class TracingGuard +{ +private: + PyThreadState* tstate; +public: + TracingGuard() + : tstate(PyThreadState_GET()) + { + PyThreadState_EnterTracing(this->tstate); + } + + ~TracingGuard() + { + PyThreadState_LeaveTracing(this->tstate); + this->tstate = nullptr; + } + + inline void CallTraceFunction(const OwnedObject& tracefunc, + const greenlet::refs::ImmortalEventName& event, + const BorrowedGreenlet& origin, + const BorrowedGreenlet& target) + { + // TODO: This calls tracefunc(event, (origin, target)). Add a shortcut + // function for that that's specialized to avoid the Py_BuildValue + // string parsing, or start with just using "ON" format with PyTuple_Pack(2, + // origin, target). That seems like what the N format is meant + // for. + // XXX: Why does event not automatically cast back to a PyObject? + // It tries to call the "deleted constructor ImmortalEventName + // const" instead. + assert(tracefunc); + assert(event); + assert(origin); + assert(target); + greenlet::refs::NewReference retval( + PyObject_CallFunction( + tracefunc.borrow(), + "O(OO)", + event.borrow(), + origin.borrow(), + target.borrow() + )); + if (!retval) { + throw PyErrOccurred::from_current(); + } + } +}; + + static void + g_calltrace(const OwnedObject& tracefunc, + const greenlet::refs::ImmortalEventName& event, + const greenlet::refs::BorrowedGreenlet& origin, + const BorrowedGreenlet& target); + private: + OwnedObject g_switch_finish(const switchstack_result_t& err); + + }; + + class UserGreenlet : public Greenlet + { + private: + static greenlet::PythonAllocator allocator; + OwnedMainGreenlet _main_greenlet; + OwnedObject _run_callable; + OwnedGreenlet _parent; + public: + static void* operator new(size_t UNUSED(count)); + static void operator delete(void* ptr); + + UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent); + virtual ~UserGreenlet(); + + virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const; + virtual bool was_running_in_dead_thread() const noexcept; + virtual ThreadState* thread_state() const noexcept; + virtual OwnedObject g_switch(); + virtual const OwnedObject& run() const + { + if (this->started() || !this->_run_callable) { + throw AttributeError("run"); + } + return this->_run_callable; + } + virtual void run(const refs::BorrowedObject nrun); + + virtual const OwnedGreenlet parent() const; + virtual void parent(const refs::BorrowedObject new_parent); + + virtual const refs::BorrowedMainGreenlet main_greenlet() const; + + virtual void murder_in_place(); + virtual bool belongs_to_thread(const ThreadState* state) const; + virtual int tp_traverse(visitproc visit, void* arg); + virtual int tp_clear(); + class ParentIsCurrentGuard + { + private: + OwnedGreenlet oldparent; + UserGreenlet* greenlet; + G_NO_COPIES_OF_CLS(ParentIsCurrentGuard); + public: + ParentIsCurrentGuard(UserGreenlet* p, const ThreadState& thread_state); + ~ParentIsCurrentGuard(); + }; + virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state); + protected: + virtual switchstack_result_t g_initialstub(void* mark); + private: + // This function isn't meant to return. + // This accepts raw pointers and the ownership of them at the + // same time. The caller should use ``inner_bootstrap(origin.relinquish_ownership())``. + void inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run); + }; + + class BrokenGreenlet : public UserGreenlet + { + private: + static greenlet::PythonAllocator allocator; + public: + bool _force_switch_error = false; + bool _force_slp_switch_error = false; + + static void* operator new(size_t UNUSED(count)); + static void operator delete(void* ptr); + BrokenGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent) + : UserGreenlet(p, the_parent) + {} + virtual ~BrokenGreenlet() + {} + + virtual switchstack_result_t g_switchstack(void); + virtual bool force_slp_switch_error() const noexcept; + + }; + + class MainGreenlet : public Greenlet + { + private: + static greenlet::PythonAllocator allocator; + refs::BorrowedMainGreenlet _self; + ThreadState* _thread_state; + G_NO_COPIES_OF_CLS(MainGreenlet); + public: + static void* operator new(size_t UNUSED(count)); + static void operator delete(void* ptr); + + MainGreenlet(refs::BorrowedMainGreenlet::PyType*, ThreadState*); + virtual ~MainGreenlet(); + + + virtual const OwnedObject& run() const; + virtual void run(const refs::BorrowedObject nrun); + + virtual const OwnedGreenlet parent() const; + virtual void parent(const refs::BorrowedObject new_parent); + + virtual const refs::BorrowedMainGreenlet main_greenlet() const; + + virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const; + virtual bool was_running_in_dead_thread() const noexcept; + virtual ThreadState* thread_state() const noexcept; + void thread_state(ThreadState*) noexcept; + virtual OwnedObject g_switch(); + virtual int tp_traverse(visitproc visit, void* arg); + }; + + // Instantiate one on the stack to save the GC state, + // and then disable GC. When it goes out of scope, GC will be + // restored to its original state. Sadly, these APIs are only + // available on 3.10+; luckily, we only need them on 3.11+. +#if GREENLET_PY310 + class GCDisabledGuard + { + private: + int was_enabled = 0; + public: + GCDisabledGuard() + : was_enabled(PyGC_IsEnabled()) + { + PyGC_Disable(); + } + + ~GCDisabledGuard() + { + if (this->was_enabled) { + PyGC_Enable(); + } + } + }; +#endif + + OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept; + + //TODO: Greenlet::g_switch() should call this automatically on its + //return value. As it is, the module code is calling it. + static inline OwnedObject + single_result(const OwnedObject& results) + { + if (results + && PyTuple_Check(results.borrow()) + && PyTuple_GET_SIZE(results.borrow()) == 1) { + PyObject* result = PyTuple_GET_ITEM(results.borrow(), 0); + assert(result); + return OwnedObject::owning(result); + } + return results; + } + + + static OwnedObject + g_handle_exit(const OwnedObject& greenlet_result); + + + template + void operator<<(const PyThreadState *const lhs, T& rhs) + { + rhs.operator<<(lhs); + } + +} // namespace greenlet ; + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/TGreenletGlobals.cpp b/venv/lib/python3.8/site-packages/greenlet/TGreenletGlobals.cpp new file mode 100644 index 000000000..0087d2ff3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TGreenletGlobals.cpp @@ -0,0 +1,94 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of GreenletGlobals. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_GREENLET_GLOBALS +#define T_GREENLET_GLOBALS + +#include "greenlet_refs.hpp" +#include "greenlet_exceptions.hpp" +#include "greenlet_thread_support.hpp" +#include "greenlet_internal.hpp" + +namespace greenlet { + +// This encapsulates what were previously module global "constants" +// established at init time. +// This is a step towards Python3 style module state that allows +// reloading. +// +// In an earlier iteration of this code, we used placement new to be +// able to allocate this object statically still, so that references +// to its members don't incur an extra pointer indirection. +// But under some scenarios, that could result in crashes at +// shutdown because apparently the destructor was getting run twice? +class GreenletGlobals +{ + +public: + const greenlet::refs::ImmortalEventName event_switch; + const greenlet::refs::ImmortalEventName event_throw; + const greenlet::refs::ImmortalException PyExc_GreenletError; + const greenlet::refs::ImmortalException PyExc_GreenletExit; + const greenlet::refs::ImmortalObject empty_tuple; + const greenlet::refs::ImmortalObject empty_dict; + const greenlet::refs::ImmortalString str_run; + Mutex* const thread_states_to_destroy_lock; + greenlet::cleanup_queue_t thread_states_to_destroy; + + GreenletGlobals() : + event_switch("switch"), + event_throw("throw"), + PyExc_GreenletError("greenlet.error"), + PyExc_GreenletExit("greenlet.GreenletExit", PyExc_BaseException), + empty_tuple(Require(PyTuple_New(0))), + empty_dict(Require(PyDict_New())), + str_run("run"), + thread_states_to_destroy_lock(new Mutex()) + {} + + ~GreenletGlobals() + { + // This object is (currently) effectively immortal, and not + // just because of those placement new tricks; if we try to + // deallocate the static object we allocated, and overwrote, + // we would be doing so at C++ teardown time, which is after + // the final Python GIL is released, and we can't use the API + // then. + // (The members will still be destructed, but they also don't + // do any deallocation.) + } + + void queue_to_destroy(ThreadState* ts) const + { + // we're currently accessed through a static const object, + // implicitly marking our members as const, so code can't just + // call push_back (or pop_back) without casting away the + // const. + // + // Do that for callers. + greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy); + q.push_back(ts); + } + + ThreadState* take_next_to_destroy() const + { + greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy); + ThreadState* result = q.back(); + q.pop_back(); + return result; + } +}; + +}; // namespace greenlet + +static const greenlet::GreenletGlobals* mod_globs; + +#endif // T_GREENLET_GLOBALS diff --git a/venv/lib/python3.8/site-packages/greenlet/TMainGreenlet.cpp b/venv/lib/python3.8/site-packages/greenlet/TMainGreenlet.cpp new file mode 100644 index 000000000..a2a9cfe4b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TMainGreenlet.cpp @@ -0,0 +1,153 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::MainGreenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_MAIN_GREENLET_CPP +#define T_MAIN_GREENLET_CPP + +#include "TGreenlet.hpp" + + + +// Protected by the GIL. Incremented when we create a main greenlet, +// in a new thread, decremented when it is destroyed. +static Py_ssize_t G_TOTAL_MAIN_GREENLETS; + +namespace greenlet { +greenlet::PythonAllocator MainGreenlet::allocator; + +void* MainGreenlet::operator new(size_t UNUSED(count)) +{ + return allocator.allocate(1); +} + + +void MainGreenlet::operator delete(void* ptr) +{ + return allocator.deallocate(static_cast(ptr), + 1); +} + + +MainGreenlet::MainGreenlet(PyGreenlet* p, ThreadState* state) + : Greenlet(p, StackState::make_main()), + _self(p), + _thread_state(state) +{ + G_TOTAL_MAIN_GREENLETS++; +} + +MainGreenlet::~MainGreenlet() +{ + G_TOTAL_MAIN_GREENLETS--; + this->tp_clear(); +} + +ThreadState* +MainGreenlet::thread_state() const noexcept +{ + return this->_thread_state; +} + +void +MainGreenlet::thread_state(ThreadState* t) noexcept +{ + assert(!t); + this->_thread_state = t; +} + + +const BorrowedMainGreenlet +MainGreenlet::main_greenlet() const +{ + return this->_self; +} + +BorrowedMainGreenlet +MainGreenlet::find_main_greenlet_in_lineage() const +{ + return BorrowedMainGreenlet(this->_self); +} + +bool +MainGreenlet::was_running_in_dead_thread() const noexcept +{ + return !this->_thread_state; +} + +OwnedObject +MainGreenlet::g_switch() +{ + try { + this->check_switch_allowed(); + } + catch (const PyErrOccurred&) { + this->release_args(); + throw; + } + + switchstack_result_t err = this->g_switchstack(); + if (err.status < 0) { + // XXX: This code path is untested, but it is shared + // with the UserGreenlet path that is tested. + return this->on_switchstack_or_initialstub_failure( + this, + err, + true, // target was me + false // was initial stub + ); + } + + return err.the_new_current_greenlet->g_switch_finish(err); +} + +int +MainGreenlet::tp_traverse(visitproc visit, void* arg) +{ + if (this->_thread_state) { + // we've already traversed main, (self), don't do it again. + int result = this->_thread_state->tp_traverse(visit, arg, false); + if (result) { + return result; + } + } + return Greenlet::tp_traverse(visit, arg); +} + +const OwnedObject& +MainGreenlet::run() const +{ + throw AttributeError("Main greenlets do not have a run attribute."); +} + +void +MainGreenlet::run(const BorrowedObject UNUSED(nrun)) +{ + throw AttributeError("Main greenlets do not have a run attribute."); +} + +void +MainGreenlet::parent(const BorrowedObject raw_new_parent) +{ + if (!raw_new_parent) { + throw AttributeError("can't delete attribute"); + } + throw AttributeError("cannot set the parent of a main greenlet"); +} + +const OwnedGreenlet +MainGreenlet::parent() const +{ + return OwnedGreenlet(); // null becomes None +} + +}; // namespace greenlet + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/TPythonState.cpp b/venv/lib/python3.8/site-packages/greenlet/TPythonState.cpp new file mode 100644 index 000000000..cc5dff51b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TPythonState.cpp @@ -0,0 +1,393 @@ +#ifndef GREENLET_PYTHON_STATE_CPP +#define GREENLET_PYTHON_STATE_CPP + +#include +#include "TGreenlet.hpp" + +namespace greenlet { + +PythonState::PythonState() + : _top_frame() +#if GREENLET_USE_CFRAME + ,cframe(nullptr) + ,use_tracing(0) +#endif +#if GREENLET_PY312 + ,py_recursion_depth(0) + ,c_recursion_depth(0) +#else + ,recursion_depth(0) +#endif +#if GREENLET_PY313 + ,delete_later(nullptr) +#else + ,trash_delete_nesting(0) +#endif +#if GREENLET_PY311 + ,current_frame(nullptr) + ,datastack_chunk(nullptr) + ,datastack_top(nullptr) + ,datastack_limit(nullptr) +#endif +{ +#if GREENLET_USE_CFRAME + /* + The PyThreadState->cframe pointer usually points to memory on + the stack, alloceted in a call into PyEval_EvalFrameDefault. + + Initially, before any evaluation begins, it points to the + initial PyThreadState object's ``root_cframe`` object, which is + statically allocated for the lifetime of the thread. + + A greenlet can last for longer than a call to + PyEval_EvalFrameDefault, so we can't set its ``cframe`` pointer + to be the current ``PyThreadState->cframe``; nor could we use + one from the greenlet parent for the same reason. Yet a further + no: we can't allocate one scoped to the greenlet and then + destroy it when the greenlet is deallocated, because inside the + interpreter the _PyCFrame objects form a linked list, and that too + can result in accessing memory beyond its dynamic lifetime (if + the greenlet doesn't actually finish before it dies, its entry + could still be in the list). + + Using the ``root_cframe`` is problematic, though, because its + members are never modified by the interpreter and are set to 0, + meaning that its ``use_tracing`` flag is never updated. We don't + want to modify that value in the ``root_cframe`` ourself: it + *shouldn't* matter much because we should probably never get + back to the point where that's the only cframe on the stack; + even if it did matter, the major consequence of an incorrect + value for ``use_tracing`` is that if its true the interpreter + does some extra work --- however, it's just good code hygiene. + + Our solution: before a greenlet runs, after its initial + creation, it uses the ``root_cframe`` just to have something to + put there. However, once the greenlet is actually switched to + for the first time, ``g_initialstub`` (which doesn't actually + "return" while the greenlet is running) stores a new _PyCFrame on + its local stack, and copies the appropriate values from the + currently running _PyCFrame; this is then made the _PyCFrame for the + newly-minted greenlet. ``g_initialstub`` then proceeds to call + ``glet.run()``, which results in ``PyEval_...`` adding the + _PyCFrame to the list. Switches continue as normal. Finally, when + the greenlet finishes, the call to ``glet.run()`` returns and + the _PyCFrame is taken out of the linked list and the stack value + is now unused and free to expire. + + XXX: I think we can do better. If we're deallocing in the same + thread, can't we traverse the list and unlink our frame? + Can we just keep a reference to the thread state in case we + dealloc in another thread? (Is that even possible if we're still + running and haven't returned from g_initialstub?) + */ + this->cframe = &PyThreadState_GET()->root_cframe; +#endif +} + + +inline void PythonState::may_switch_away() noexcept +{ +#if GREENLET_PY311 + // PyThreadState_GetFrame is probably going to have to allocate a + // new frame object. That may trigger garbage collection. Because + // we call this during the early phases of a switch (it doesn't + // matter to which greenlet, as this has a global effect), if a GC + // triggers a switch away, two things can happen, both bad: + // - We might not get switched back to, halting forward progress. + // this is pathological, but possible. + // - We might get switched back to with a different set of + // arguments or a throw instead of a switch. That would corrupt + // our state (specifically, PyErr_Occurred() and this->args() + // would no longer agree). + // + // Thus, when we call this API, we need to have GC disabled. + // This method serves as a bottleneck we call when maybe beginning + // a switch. In this way, it is always safe -- no risk of GC -- to + // use ``_GetFrame()`` whenever we need to, just as it was in + // <=3.10 (because subsequent calls will be cached and not + // allocate memory). + + GCDisabledGuard no_gc; + Py_XDECREF(PyThreadState_GetFrame(PyThreadState_GET())); +#endif +} + +void PythonState::operator<<(const PyThreadState *const tstate) noexcept +{ + this->_context.steal(tstate->context); +#if GREENLET_USE_CFRAME + /* + IMPORTANT: ``cframe`` is a pointer into the STACK. Thus, because + the call to ``slp_switch()`` changes the contents of the stack, + you cannot read from ``ts_current->cframe`` after that call and + necessarily get the same values you get from reading it here. + Anything you need to restore from now to then must be saved in a + global/threadlocal variable (because we can't use stack + variables here either). For things that need to persist across + the switch, use `will_switch_from`. + */ + this->cframe = tstate->cframe; + #if !GREENLET_PY312 + this->use_tracing = tstate->cframe->use_tracing; + #endif +#endif // GREENLET_USE_CFRAME +#if GREENLET_PY311 + #if GREENLET_PY312 + this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; + this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining; + #else // not 312 + this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; + #endif // GREENLET_PY312 + #if GREENLET_PY313 + this->current_frame = tstate->current_frame; + #elif GREENLET_USE_CFRAME + this->current_frame = tstate->cframe->current_frame; + #endif + this->datastack_chunk = tstate->datastack_chunk; + this->datastack_top = tstate->datastack_top; + this->datastack_limit = tstate->datastack_limit; + + PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate); + Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new + // reference. + this->_top_frame.steal(frame); + #if GREENLET_PY313 + this->delete_later = Py_XNewRef(tstate->delete_later); + #elif GREENLET_PY312 + this->trash_delete_nesting = tstate->trash.delete_nesting; + #else // not 312 + this->trash_delete_nesting = tstate->trash_delete_nesting; + #endif // GREENLET_PY312 +#else // Not 311 + this->recursion_depth = tstate->recursion_depth; + this->_top_frame.steal(tstate->frame); + this->trash_delete_nesting = tstate->trash_delete_nesting; +#endif // GREENLET_PY311 +} + +#if GREENLET_PY312 +void GREENLET_NOINLINE(PythonState::unexpose_frames)() +{ + if (!this->top_frame()) { + return; + } + + // See GreenletState::expose_frames() and the comment on frames_were_exposed + // for more information about this logic. + _PyInterpreterFrame *iframe = this->_top_frame->f_frame; + while (iframe != nullptr) { + _PyInterpreterFrame *prev_exposed = iframe->previous; + assert(iframe->frame_obj); + memcpy(&iframe->previous, &iframe->frame_obj->_f_frame_data[0], + sizeof(void *)); + iframe = prev_exposed; + } +} +#else +void PythonState::unexpose_frames() +{} +#endif + +void PythonState::operator>>(PyThreadState *const tstate) noexcept +{ + tstate->context = this->_context.relinquish_ownership(); + /* Incrementing this value invalidates the contextvars cache, + which would otherwise remain valid across switches */ + tstate->context_ver++; +#if GREENLET_USE_CFRAME + tstate->cframe = this->cframe; + /* + If we were tracing, we need to keep tracing. + There should never be the possibility of hitting the + root_cframe here. See note above about why we can't + just copy this from ``origin->cframe->use_tracing``. + */ + #if !GREENLET_PY312 + tstate->cframe->use_tracing = this->use_tracing; + #endif +#endif // GREENLET_USE_CFRAME +#if GREENLET_PY311 + #if GREENLET_PY312 + tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth; + tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth; + this->unexpose_frames(); + #else // \/ 3.11 + tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth; + #endif // GREENLET_PY312 + #if GREENLET_PY313 + tstate->current_frame = this->current_frame; + #elif GREENLET_USE_CFRAME + tstate->cframe->current_frame = this->current_frame; + #endif + tstate->datastack_chunk = this->datastack_chunk; + tstate->datastack_top = this->datastack_top; + tstate->datastack_limit = this->datastack_limit; + this->_top_frame.relinquish_ownership(); + #if GREENLET_PY313 + Py_XDECREF(tstate->delete_later); + tstate->delete_later = this->delete_later; + Py_CLEAR(this->delete_later); + #elif GREENLET_PY312 + tstate->trash.delete_nesting = this->trash_delete_nesting; + #else // not 3.12 + tstate->trash_delete_nesting = this->trash_delete_nesting; + #endif // GREENLET_PY312 +#else // not 3.11 + tstate->frame = this->_top_frame.relinquish_ownership(); + tstate->recursion_depth = this->recursion_depth; + tstate->trash_delete_nesting = this->trash_delete_nesting; +#endif // GREENLET_PY311 +} + +inline void PythonState::will_switch_from(PyThreadState *const origin_tstate) noexcept +{ +#if GREENLET_USE_CFRAME && !GREENLET_PY312 + // The weird thing is, we don't actually save this for an + // effect on the current greenlet, it's saved for an + // effect on the target greenlet. That is, we want + // continuity of this setting across the greenlet switch. + this->use_tracing = origin_tstate->cframe->use_tracing; +#endif +} + +void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept +{ + this->_top_frame = nullptr; +#if GREENLET_PY312 + this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; + // XXX: TODO: Comment from a reviewer: + // Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``? + // But to me it looks more like that might not be the right + // initialization either? + this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; +#elif GREENLET_PY311 + this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; +#else + this->recursion_depth = tstate->recursion_depth; +#endif +} +// TODO: Better state management about when we own the top frame. +int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) noexcept +{ + Py_VISIT(this->_context.borrow()); + if (own_top_frame) { + Py_VISIT(this->_top_frame.borrow()); + } + return 0; +} + +void PythonState::tp_clear(bool own_top_frame) noexcept +{ + PythonStateContext::tp_clear(); + // If we get here owning a frame, + // we got dealloc'd without being finished. We may or may not be + // in the same thread. + if (own_top_frame) { + this->_top_frame.CLEAR(); + } +} + +#if GREENLET_USE_CFRAME +void PythonState::set_new_cframe(_PyCFrame& frame) noexcept +{ + frame = *PyThreadState_GET()->cframe; + /* Make the target greenlet refer to the stack value. */ + this->cframe = &frame; + /* + And restore the link to the previous frame so this one gets + unliked appropriately. + */ + this->cframe->previous = &PyThreadState_GET()->root_cframe; +} +#endif + +const PythonState::OwnedFrame& PythonState::top_frame() const noexcept +{ + return this->_top_frame; +} + +void PythonState::did_finish(PyThreadState* tstate) noexcept +{ +#if GREENLET_PY311 + // See https://github.com/gevent/gevent/issues/1924 and + // https://github.com/python-greenlet/greenlet/issues/328. In + // short, Python 3.11 allocates memory for frames as a sort of + // linked list that's kept as part of PyThreadState in the + // ``datastack_chunk`` member and friends. These are saved and + // restored as part of switching greenlets. + // + // When we initially switch to a greenlet, we set those to NULL. + // That causes the frame management code to treat this like a + // brand new thread and start a fresh list of chunks, beginning + // with a new "root" chunk. As we make calls in this greenlet, + // those chunks get added, and as calls return, they get popped. + // But the frame code (pystate.c) is careful to make sure that the + // root chunk never gets popped. + // + // Thus, when a greenlet exits for the last time, there will be at + // least a single root chunk that we must be responsible for + // deallocating. + // + // The complex part is that these chunks are allocated and freed + // using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public + // functions, and they aren't exported for linking. It so happens + // that we know they are just thin wrappers around the Arena + // allocator, so we can use that directly to deallocate in a + // compatible way. + // + // CAUTION: Check this implementation detail on every major version. + // + // It might be nice to be able to do this in our destructor, but + // can we be sure that no one else is using that memory? Plus, as + // described below, our pointers may not even be valid anymore. As + // a special case, there is one time that we know we can do this, + // and that's from the destructor of the associated UserGreenlet + // (NOT main greenlet) + PyObjectArenaAllocator alloc; + _PyStackChunk* chunk = nullptr; + if (tstate) { + // We really did finish, we can never be switched to again. + chunk = tstate->datastack_chunk; + // Unfortunately, we can't do much sanity checking. Our + // this->datastack_chunk pointer is out of date (evaluation may + // have popped down through it already) so we can't verify that + // we deallocate it. I don't think we can even check datastack_top + // for the same reason. + + PyObject_GetArenaAllocator(&alloc); + tstate->datastack_chunk = nullptr; + tstate->datastack_limit = nullptr; + tstate->datastack_top = nullptr; + + } + else if (this->datastack_chunk) { + // The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're + // still holding a stack chunk, it's garbage because we know + // we can never switch back to let cPython clean it up. + // Because the last time we got switched away from, and we + // haven't run since then, we know our chain is valid and can + // be dealloced. + chunk = this->datastack_chunk; + PyObject_GetArenaAllocator(&alloc); + } + + if (alloc.free && chunk) { + // In case the arena mechanism has been torn down already. + while (chunk) { + _PyStackChunk *prev = chunk->previous; + chunk->previous = nullptr; + alloc.free(alloc.ctx, chunk, chunk->size); + chunk = prev; + } + } + + this->datastack_chunk = nullptr; + this->datastack_limit = nullptr; + this->datastack_top = nullptr; +#endif +} + + +}; // namespace greenlet + +#endif // GREENLET_PYTHON_STATE_CPP diff --git a/venv/lib/python3.8/site-packages/greenlet/TStackState.cpp b/venv/lib/python3.8/site-packages/greenlet/TStackState.cpp new file mode 100644 index 000000000..9743ab51b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TStackState.cpp @@ -0,0 +1,265 @@ +#ifndef GREENLET_STACK_STATE_CPP +#define GREENLET_STACK_STATE_CPP + +#include "TGreenlet.hpp" + +namespace greenlet { + +#ifdef GREENLET_USE_STDIO +#include +using std::cerr; +using std::endl; + +std::ostream& operator<<(std::ostream& os, const StackState& s) +{ + os << "StackState(stack_start=" << (void*)s._stack_start + << ", stack_stop=" << (void*)s.stack_stop + << ", stack_copy=" << (void*)s.stack_copy + << ", stack_saved=" << s._stack_saved + << ", stack_prev=" << s.stack_prev + << ", addr=" << &s + << ")"; + return os; +} +#endif + +StackState::StackState(void* mark, StackState& current) + : _stack_start(nullptr), + stack_stop((char*)mark), + stack_copy(nullptr), + _stack_saved(0), + /* Skip a dying greenlet */ + stack_prev(current._stack_start + ? ¤t + : current.stack_prev) +{ +} + +StackState::StackState() + : _stack_start(nullptr), + stack_stop(nullptr), + stack_copy(nullptr), + _stack_saved(0), + stack_prev(nullptr) +{ +} + +StackState::StackState(const StackState& other) +// can't use a delegating constructor because of +// MSVC for Python 2.7 + : _stack_start(nullptr), + stack_stop(nullptr), + stack_copy(nullptr), + _stack_saved(0), + stack_prev(nullptr) +{ + this->operator=(other); +} + +StackState& StackState::operator=(const StackState& other) +{ + if (&other == this) { + return *this; + } + if (other._stack_saved) { + throw std::runtime_error("Refusing to steal memory."); + } + + //If we have memory allocated, dispose of it + this->free_stack_copy(); + + this->_stack_start = other._stack_start; + this->stack_stop = other.stack_stop; + this->stack_copy = other.stack_copy; + this->_stack_saved = other._stack_saved; + this->stack_prev = other.stack_prev; + return *this; +} + +inline void StackState::free_stack_copy() noexcept +{ + PyMem_Free(this->stack_copy); + this->stack_copy = nullptr; + this->_stack_saved = 0; +} + +inline void StackState::copy_heap_to_stack(const StackState& current) noexcept +{ + + /* Restore the heap copy back into the C stack */ + if (this->_stack_saved != 0) { + memcpy(this->_stack_start, this->stack_copy, this->_stack_saved); + this->free_stack_copy(); + } + StackState* owner = const_cast(¤t); + if (!owner->_stack_start) { + owner = owner->stack_prev; /* greenlet is dying, skip it */ + } + while (owner && owner->stack_stop <= this->stack_stop) { + // cerr << "\tOwner: " << owner << endl; + owner = owner->stack_prev; /* find greenlet with more stack */ + } + this->stack_prev = owner; + // cerr << "\tFinished with: " << *this << endl; +} + +inline int StackState::copy_stack_to_heap_up_to(const char* const stop) noexcept +{ + /* Save more of g's stack into the heap -- at least up to 'stop' + g->stack_stop |________| + | | + | __ stop . . . . . + | | ==> . . + |________| _______ + | | | | + | | | | + g->stack_start | | |_______| g->stack_copy + */ + intptr_t sz1 = this->_stack_saved; + intptr_t sz2 = stop - this->_stack_start; + assert(this->_stack_start); + if (sz2 > sz1) { + char* c = (char*)PyMem_Realloc(this->stack_copy, sz2); + if (!c) { + PyErr_NoMemory(); + return -1; + } + memcpy(c + sz1, this->_stack_start + sz1, sz2 - sz1); + this->stack_copy = c; + this->_stack_saved = sz2; + } + return 0; +} + +inline int StackState::copy_stack_to_heap(char* const stackref, + const StackState& current) noexcept +{ + /* must free all the C stack up to target_stop */ + const char* const target_stop = this->stack_stop; + + StackState* owner = const_cast(¤t); + assert(owner->_stack_saved == 0); // everything is present on the stack + if (!owner->_stack_start) { + owner = owner->stack_prev; /* not saved if dying */ + } + else { + owner->_stack_start = stackref; + } + + while (owner->stack_stop < target_stop) { + /* ts_current is entierely within the area to free */ + if (owner->copy_stack_to_heap_up_to(owner->stack_stop)) { + return -1; /* XXX */ + } + owner = owner->stack_prev; + } + if (owner != this) { + if (owner->copy_stack_to_heap_up_to(target_stop)) { + return -1; /* XXX */ + } + } + return 0; +} + +inline bool StackState::started() const noexcept +{ + return this->stack_stop != nullptr; +} + +inline bool StackState::main() const noexcept +{ + return this->stack_stop == (char*)-1; +} + +inline bool StackState::active() const noexcept +{ + return this->_stack_start != nullptr; +} + +inline void StackState::set_active() noexcept +{ + assert(this->_stack_start == nullptr); + this->_stack_start = (char*)1; +} + +inline void StackState::set_inactive() noexcept +{ + this->_stack_start = nullptr; + // XXX: What if we still have memory out there? + // That case is actually triggered by + // test_issue251_issue252_explicit_reference_not_collectable (greenlet.tests.test_leaks.TestLeaks) + // and + // test_issue251_issue252_need_to_collect_in_background + // (greenlet.tests.test_leaks.TestLeaks) + // + // Those objects never get deallocated, so the destructor never + // runs. + // It *seems* safe to clean up the memory here? + if (this->_stack_saved) { + this->free_stack_copy(); + } +} + +inline intptr_t StackState::stack_saved() const noexcept +{ + return this->_stack_saved; +} + +inline char* StackState::stack_start() const noexcept +{ + return this->_stack_start; +} + + +inline StackState StackState::make_main() noexcept +{ + StackState s; + s._stack_start = (char*)1; + s.stack_stop = (char*)-1; + return s; +} + +StackState::~StackState() +{ + if (this->_stack_saved != 0) { + this->free_stack_copy(); + } +} + +void StackState::copy_from_stack(void* vdest, const void* vsrc, size_t n) const +{ + char* dest = static_cast(vdest); + const char* src = static_cast(vsrc); + if (src + n <= this->_stack_start + || src >= this->_stack_start + this->_stack_saved + || this->_stack_saved == 0) { + // Nothing we're copying was spilled from the stack + memcpy(dest, src, n); + return; + } + + if (src < this->_stack_start) { + // Copy the part before the saved stack. + // We know src + n > _stack_start due to the test above. + const size_t nbefore = this->_stack_start - src; + memcpy(dest, src, nbefore); + dest += nbefore; + src += nbefore; + n -= nbefore; + } + // We know src >= _stack_start after the before-copy, and + // src < _stack_start + _stack_saved due to the first if condition + size_t nspilled = std::min(n, this->_stack_start + this->_stack_saved - src); + memcpy(dest, this->stack_copy + (src - this->_stack_start), nspilled); + dest += nspilled; + src += nspilled; + n -= nspilled; + if (n > 0) { + // Copy the part after the saved stack + memcpy(dest, src, n); + } +} + +}; // namespace greenlet + +#endif // GREENLET_STACK_STATE_CPP diff --git a/venv/lib/python3.8/site-packages/greenlet/TThreadState.hpp b/venv/lib/python3.8/site-packages/greenlet/TThreadState.hpp new file mode 100644 index 000000000..e4e6f6cba --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TThreadState.hpp @@ -0,0 +1,497 @@ +#ifndef GREENLET_THREAD_STATE_HPP +#define GREENLET_THREAD_STATE_HPP + +#include +#include + +#include "greenlet_internal.hpp" +#include "greenlet_refs.hpp" +#include "greenlet_thread_support.hpp" + +using greenlet::refs::BorrowedObject; +using greenlet::refs::BorrowedGreenlet; +using greenlet::refs::BorrowedMainGreenlet; +using greenlet::refs::OwnedMainGreenlet; +using greenlet::refs::OwnedObject; +using greenlet::refs::OwnedGreenlet; +using greenlet::refs::OwnedList; +using greenlet::refs::PyErrFetchParam; +using greenlet::refs::PyArgParseParam; +using greenlet::refs::ImmortalString; +using greenlet::refs::CreatedModule; +using greenlet::refs::PyErrPieces; +using greenlet::refs::NewReference; + +namespace greenlet { +/** + * Thread-local state of greenlets. + * + * Each native thread will get exactly one of these objects, + * automatically accessed through the best available thread-local + * mechanism the compiler supports (``thread_local`` for C++11 + * compilers or ``__thread``/``declspec(thread)`` for older GCC/clang + * or MSVC, respectively.) + * + * Previously, we kept thread-local state mostly in a bunch of + * ``static volatile`` variables in the main greenlet file.. This had + * the problem of requiring extra checks, loops, and great care + * accessing these variables if we potentially invoked any Python code + * that could release the GIL, because the state could change out from + * under us. Making the variables thread-local solves this problem. + * + * When we detected that a greenlet API accessing the current greenlet + * was invoked from a different thread than the greenlet belonged to, + * we stored a reference to the greenlet in the Python thread + * dictionary for the thread the greenlet belonged to. This could lead + * to memory leaks if the thread then exited (because of a reference + * cycle, as greenlets referred to the thread dictionary, and deleting + * non-current greenlets leaked their frame plus perhaps arguments on + * the C stack). If a thread exited while still having running + * greenlet objects (perhaps that had just switched back to the main + * greenlet), and did not invoke one of the greenlet APIs *in that + * thread, immediately before it exited, without some other thread + * then being invoked*, such a leak was guaranteed. + * + * This can be partly solved by using compiler thread-local variables + * instead of the Python thread dictionary, thus avoiding a cycle. + * + * To fully solve this problem, we need a reliable way to know that a + * thread is done and we should clean up the main greenlet. On POSIX, + * we can use the destructor function of ``pthread_key_create``, but + * there's nothing similar on Windows; a C++11 thread local object + * reliably invokes its destructor when the thread it belongs to exits + * (non-C++11 compilers offer ``__thread`` or ``declspec(thread)`` to + * create thread-local variables, but they can't hold C++ objects that + * invoke destructors; the C++11 version is the most portable solution + * I found). When the thread exits, we can drop references and + * otherwise manipulate greenlets and frames that we know can no + * longer be switched to. For compilers that don't support C++11 + * thread locals, we have a solution that uses the python thread + * dictionary, though it may not collect everything as promptly as + * other compilers do, if some other library is using the thread + * dictionary and has a cycle or extra reference. + * + * There are two small wrinkles. The first is that when the thread + * exits, it is too late to actually invoke Python APIs: the Python + * thread state is gone, and the GIL is released. To solve *this* + * problem, our destructor uses ``Py_AddPendingCall`` to transfer the + * destruction work to the main thread. (This is not an issue for the + * dictionary solution.) + * + * The second is that once the thread exits, the thread local object + * is invalid and we can't even access a pointer to it, so we can't + * pass it to ``Py_AddPendingCall``. This is handled by actually using + * a second object that's thread local (ThreadStateCreator) and having + * it dynamically allocate this object so it can live until the + * pending call runs. + */ + + + +class ThreadState { +private: + // As of commit 08ad1dd7012b101db953f492e0021fb08634afad + // this class needed 56 bytes in o Py_DEBUG build + // on 64-bit macOS 11. + // Adding the vector takes us up to 80 bytes () + + /* Strong reference to the main greenlet */ + OwnedMainGreenlet main_greenlet; + + /* Strong reference to the current greenlet. */ + OwnedGreenlet current_greenlet; + + /* Strong reference to the trace function, if any. */ + OwnedObject tracefunc; + + typedef std::vector > deleteme_t; + /* A vector of raw PyGreenlet pointers representing things that need + deleted when this thread is running. The vector owns the + references, but you need to manually INCREF/DECREF as you use + them. We don't use a vector because we + make copy of this vector, and that would become O(n) as all the + refcounts are incremented in the copy. + */ + deleteme_t deleteme; + +#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED + void* exception_state; +#endif + + static std::clock_t _clocks_used_doing_gc; + static ImmortalString get_referrers_name; + static PythonAllocator allocator; + + G_NO_COPIES_OF_CLS(ThreadState); + + + // Allocates a main greenlet for the thread state. If this fails, + // exits the process. Called only during constructing a ThreadState. + MainGreenlet* alloc_main() + { + PyGreenlet* gmain; + + /* create the main greenlet for this thread */ + gmain = reinterpret_cast(PyType_GenericAlloc(&PyGreenlet_Type, 0)); + if (gmain == NULL) { + throw PyFatalError("alloc_main failed to alloc"); //exits the process + } + + MainGreenlet* const main = new MainGreenlet(gmain, this); + + assert(Py_REFCNT(gmain) == 1); + assert(gmain->pimpl == main); + return main; + } + + +public: + static void* operator new(size_t UNUSED(count)) + { + return ThreadState::allocator.allocate(1); + } + + static void operator delete(void* ptr) + { + return ThreadState::allocator.deallocate(static_cast(ptr), + 1); + } + + static void init() + { + ThreadState::get_referrers_name = "get_referrers"; + ThreadState::_clocks_used_doing_gc = 0; + } + + ThreadState() + { + +#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED + this->exception_state = slp_get_exception_state(); +#endif + + // XXX: Potentially dangerous, exposing a not fully + // constructed object. + MainGreenlet* const main = this->alloc_main(); + this->main_greenlet = OwnedMainGreenlet::consuming( + main->self() + ); + assert(this->main_greenlet); + this->current_greenlet = main->self(); + // The main greenlet starts with 1 refs: The returned one. We + // then copied it to the current greenlet. + assert(this->main_greenlet.REFCNT() == 2); + } + + inline void restore_exception_state() + { +#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED + // It's probably important this be inlined and only call C + // functions to avoid adding an SEH frame. + slp_set_exception_state(this->exception_state); +#endif + } + + inline bool has_main_greenlet() const noexcept + { + return bool(this->main_greenlet); + } + + // Called from the ThreadStateCreator when we're in non-standard + // threading mode. In that case, there is an object in the Python + // thread state dictionary that points to us. The main greenlet + // also traverses into us, in which case it's crucial not to + // traverse back into the main greenlet. + int tp_traverse(visitproc visit, void* arg, bool traverse_main=true) + { + if (traverse_main) { + Py_VISIT(main_greenlet.borrow_o()); + } + if (traverse_main || current_greenlet != main_greenlet) { + Py_VISIT(current_greenlet.borrow_o()); + } + Py_VISIT(tracefunc.borrow()); + return 0; + } + + inline BorrowedMainGreenlet borrow_main_greenlet() const noexcept + { + assert(this->main_greenlet); + assert(this->main_greenlet.REFCNT() >= 2); + return this->main_greenlet; + }; + + inline OwnedMainGreenlet get_main_greenlet() const noexcept + { + return this->main_greenlet; + } + + /** + * In addition to returning a new reference to the currunt + * greenlet, this performs any maintenance needed. + */ + inline OwnedGreenlet get_current() + { + /* green_dealloc() cannot delete greenlets from other threads, so + it stores them in the thread dict; delete them now. */ + this->clear_deleteme_list(); + //assert(this->current_greenlet->main_greenlet == this->main_greenlet); + //assert(this->main_greenlet->main_greenlet == this->main_greenlet); + return this->current_greenlet; + } + + /** + * As for non-const get_current(); + */ + inline BorrowedGreenlet borrow_current() + { + this->clear_deleteme_list(); + return this->current_greenlet; + } + + /** + * Does no maintenance. + */ + inline OwnedGreenlet get_current() const + { + return this->current_greenlet; + } + + template + inline bool is_current(const refs::PyObjectPointer& obj) const + { + return this->current_greenlet.borrow_o() == obj.borrow_o(); + } + + inline void set_current(const OwnedGreenlet& target) + { + this->current_greenlet = target; + } + +private: + /** + * Deref and remove the greenlets from the deleteme list. Must be + * holding the GIL. + * + * If *murder* is true, then we must be called from a different + * thread than the one that these greenlets were running in. + * In that case, if the greenlet was actually running, we destroy + * the frame reference and otherwise make it appear dead before + * proceeding; otherwise, we would try (and fail) to raise an + * exception in it and wind up right back in this list. + */ + inline void clear_deleteme_list(const bool murder=false) + { + if (!this->deleteme.empty()) { + // It's possible we could add items to this list while + // running Python code if there's a thread switch, so we + // need to defensively copy it before that can happen. + deleteme_t copy = this->deleteme; + this->deleteme.clear(); // in case things come back on the list + for(deleteme_t::iterator it = copy.begin(), end = copy.end(); + it != end; + ++it ) { + PyGreenlet* to_del = *it; + if (murder) { + // Force each greenlet to appear dead; we can't raise an + // exception into it anymore anyway. + to_del->pimpl->murder_in_place(); + } + + // The only reference to these greenlets should be in + // this list, decreffing them should let them be + // deleted again, triggering calls to green_dealloc() + // in the correct thread (if we're not murdering). + // This may run arbitrary Python code and switch + // threads or greenlets! + Py_DECREF(to_del); + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(nullptr); + PyErr_Clear(); + } + } + } + } + +public: + + /** + * Returns a new reference, or a false object. + */ + inline OwnedObject get_tracefunc() const + { + return tracefunc; + }; + + + inline void set_tracefunc(BorrowedObject tracefunc) + { + assert(tracefunc); + if (tracefunc == BorrowedObject(Py_None)) { + this->tracefunc.CLEAR(); + } + else { + this->tracefunc = tracefunc; + } + } + + /** + * Given a reference to a greenlet that some other thread + * attempted to delete (has a refcount of 0) store it for later + * deletion when the thread this state belongs to is current. + */ + inline void delete_when_thread_running(PyGreenlet* to_del) + { + Py_INCREF(to_del); + this->deleteme.push_back(to_del); + } + + /** + * Set to std::clock_t(-1) to disable. + */ + inline static std::clock_t& clocks_used_doing_gc() + { + return ThreadState::_clocks_used_doing_gc; + } + + ~ThreadState() + { + if (!PyInterpreterState_Head()) { + // We shouldn't get here (our callers protect us) + // but if we do, all we can do is bail early. + return; + } + + // We should not have an "origin" greenlet; that only exists + // for the temporary time during a switch, which should not + // be in progress as the thread dies. + //assert(!this->switching_state.origin); + + this->tracefunc.CLEAR(); + + // Forcibly GC as much as we can. + this->clear_deleteme_list(true); + + // The pending call did this. + assert(this->main_greenlet->thread_state() == nullptr); + + // If the main greenlet is the current greenlet, + // then we "fell off the end" and the thread died. + // It's possible that there is some other greenlet that + // switched to us, leaving a reference to the main greenlet + // on the stack, somewhere uncollectible. Try to detect that. + if (this->current_greenlet == this->main_greenlet && this->current_greenlet) { + assert(this->current_greenlet->is_currently_running_in_some_thread()); + // Drop one reference we hold. + this->current_greenlet.CLEAR(); + assert(!this->current_greenlet); + // Only our reference to the main greenlet should be left, + // But hold onto the pointer in case we need to do extra cleanup. + PyGreenlet* old_main_greenlet = this->main_greenlet.borrow(); + Py_ssize_t cnt = this->main_greenlet.REFCNT(); + this->main_greenlet.CLEAR(); + if (ThreadState::_clocks_used_doing_gc != std::clock_t(-1) + && cnt == 2 && Py_REFCNT(old_main_greenlet) == 1) { + // Highly likely that the reference is somewhere on + // the stack, not reachable by GC. Verify. + // XXX: This is O(n) in the total number of objects. + // TODO: Add a way to disable this at runtime, and + // another way to report on it. + std::clock_t begin = std::clock(); + NewReference gc(PyImport_ImportModule("gc")); + if (gc) { + OwnedObject get_referrers = gc.PyRequireAttr(ThreadState::get_referrers_name); + OwnedList refs(get_referrers.PyCall(old_main_greenlet)); + if (refs && refs.empty()) { + assert(refs.REFCNT() == 1); + // We found nothing! So we left a dangling + // reference: Probably the last thing some + // other greenlet did was call + // 'getcurrent().parent.switch()' to switch + // back to us. Clean it up. This will be the + // case on CPython 3.7 and newer, as they use + // an internal calling conversion that avoids + // creating method objects and storing them on + // the stack. + Py_DECREF(old_main_greenlet); + } + else if (refs + && refs.size() == 1 + && PyCFunction_Check(refs.at(0)) + && Py_REFCNT(refs.at(0)) == 2) { + assert(refs.REFCNT() == 1); + // Ok, we found a C method that refers to the + // main greenlet, and its only referenced + // twice, once in the list we just created, + // once from...somewhere else. If we can't + // find where else, then this is a leak. + // This happens in older versions of CPython + // that create a bound method object somewhere + // on the stack that we'll never get back to. + if (PyCFunction_GetFunction(refs.at(0).borrow()) == (PyCFunction)green_switch) { + BorrowedObject function_w = refs.at(0); + refs.clear(); // destroy the reference + // from the list. + // back to one reference. Can *it* be + // found? + assert(function_w.REFCNT() == 1); + refs = get_referrers.PyCall(function_w); + if (refs && refs.empty()) { + // Nope, it can't be found so it won't + // ever be GC'd. Drop it. + Py_CLEAR(function_w); + } + } + } + std::clock_t end = std::clock(); + ThreadState::_clocks_used_doing_gc += (end - begin); + } + } + } + + // We need to make sure this greenlet appears to be dead, + // because otherwise deallocing it would fail to raise an + // exception in it (the thread is dead) and put it back in our + // deleteme list. + if (this->current_greenlet) { + this->current_greenlet->murder_in_place(); + this->current_greenlet.CLEAR(); + } + + if (this->main_greenlet) { + // Couldn't have been the main greenlet that was running + // when the thread exited (because we already cleared this + // pointer if it was). This shouldn't be possible? + + // If the main greenlet was current when the thread died (it + // should be, right?) then we cleared its self pointer above + // when we cleared the current greenlet's main greenlet pointer. + // assert(this->main_greenlet->main_greenlet == this->main_greenlet + // || !this->main_greenlet->main_greenlet); + // // self reference, probably gone + // this->main_greenlet->main_greenlet.CLEAR(); + + // This will actually go away when the ivar is destructed. + this->main_greenlet.CLEAR(); + } + + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(NULL); + PyErr_Clear(); + } + + } + +}; + +ImmortalString ThreadState::get_referrers_name(nullptr); +PythonAllocator ThreadState::allocator; +std::clock_t ThreadState::_clocks_used_doing_gc(0); + + + + + +}; // namespace greenlet + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/TThreadStateCreator.hpp b/venv/lib/python3.8/site-packages/greenlet/TThreadStateCreator.hpp new file mode 100644 index 000000000..2ec7ab55b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TThreadStateCreator.hpp @@ -0,0 +1,102 @@ +#ifndef GREENLET_THREAD_STATE_CREATOR_HPP +#define GREENLET_THREAD_STATE_CREATOR_HPP + +#include +#include + +#include "greenlet_internal.hpp" +#include "greenlet_refs.hpp" +#include "greenlet_thread_support.hpp" + +#include "TThreadState.hpp" + +namespace greenlet { + + +typedef void (*ThreadStateDestructor)(ThreadState* const); + +template +class ThreadStateCreator +{ +private: + // Initialized to 1, and, if still 1, created on access. + // Set to 0 on destruction. + ThreadState* _state; + G_NO_COPIES_OF_CLS(ThreadStateCreator); + + inline bool has_initialized_state() const noexcept + { + return this->_state != (ThreadState*)1; + } + + inline bool has_state() const noexcept + { + return this->has_initialized_state() && this->_state != nullptr; + } + +public: + + // Only one of these, auto created per thread. + // Constructing the state constructs the MainGreenlet. + ThreadStateCreator() : + _state((ThreadState*)1) + { + } + + ~ThreadStateCreator() + { + if (this->has_state()) { + Destructor(this->_state); + } + + this->_state = nullptr; + } + + inline ThreadState& state() + { + // The main greenlet will own this pointer when it is created, + // which will be right after this. The plan is to give every + // greenlet a pointer to the main greenlet for the thread it + // runs in; if we are doing something cross-thread, we need to + // access the pointer from the main greenlet. Deleting the + // thread, and hence the thread-local storage, will delete the + // state pointer in the main greenlet. + if (!this->has_initialized_state()) { + // XXX: Assuming allocation never fails + this->_state = new ThreadState; + // For non-standard threading, we need to store an object + // in the Python thread state dictionary so that it can be + // DECREF'd when the thread ends (ideally; the dict could + // last longer) and clean this object up. + } + if (!this->_state) { + throw std::runtime_error("Accessing state after destruction."); + } + return *this->_state; + } + + operator ThreadState&() + { + return this->state(); + } + + operator ThreadState*() + { + return &this->state(); + } + + inline int tp_traverse(visitproc visit, void* arg) + { + if (this->has_state()) { + return this->_state->tp_traverse(visit, arg); + } + return 0; + } + +}; + + + +}; // namespace greenlet + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/TThreadStateDestroy.cpp b/venv/lib/python3.8/site-packages/greenlet/TThreadStateDestroy.cpp new file mode 100644 index 000000000..37fcc8c11 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TThreadStateDestroy.cpp @@ -0,0 +1,258 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of the ThreadState destructors. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_THREADSTATE_DESTROY +#define T_THREADSTATE_DESTROY + +#include "TGreenlet.hpp" + +#include "greenlet_thread_support.hpp" +#include "greenlet_cpython_add_pending.hpp" +#include "greenlet_compiler_compat.hpp" +#include "TGreenletGlobals.cpp" +#include "TThreadState.hpp" +#include "TThreadStateCreator.hpp" + +namespace greenlet { + +extern "C" { + +struct ThreadState_DestroyNoGIL +{ + /** + This function uses the same lock that the PendingCallback does + */ + static void + MarkGreenletDeadAndQueueCleanup(ThreadState* const state) + { +#if GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK + return; +#endif + // We are *NOT* holding the GIL. Our thread is in the middle + // of its death throes and the Python thread state is already + // gone so we can't use most Python APIs. One that is safe is + // ``Py_AddPendingCall``, unless the interpreter itself has + // been torn down. There is a limited number of calls that can + // be queued: 32 (NPENDINGCALLS) in CPython 3.10, so we + // coalesce these calls using our own queue. + + if (!MarkGreenletDeadIfNeeded(state)) { + // No state, or no greenlet + return; + } + + // XXX: Because we don't have the GIL, this is a race condition. + if (!PyInterpreterState_Head()) { + // We have to leak the thread state, if the + // interpreter has shut down when we're getting + // deallocated, we can't run the cleanup code that + // deleting it would imply. + return; + } + + AddToCleanupQueue(state); + + } + +private: + + // If the state has an allocated main greenlet: + // - mark the greenlet as dead by disassociating it from the state; + // - return 1 + // Otherwise, return 0. + static bool + MarkGreenletDeadIfNeeded(ThreadState* const state) + { + if (state && state->has_main_greenlet()) { + // mark the thread as dead ASAP. + // this is racy! If we try to throw or switch to a + // greenlet from this thread from some other thread before + // we clear the state pointer, it won't realize the state + // is dead which can crash the process. + PyGreenlet* p(state->borrow_main_greenlet().borrow()); + assert(p->pimpl->thread_state() == state || p->pimpl->thread_state() == nullptr); + dynamic_cast(p->pimpl)->thread_state(nullptr); + return true; + } + return false; + } + + static void + AddToCleanupQueue(ThreadState* const state) + { + assert(state && state->has_main_greenlet()); + + // NOTE: Because we're not holding the GIL here, some other + // Python thread could run and call ``os.fork()``, which would + // be bad if that happened while we are holding the cleanup + // lock (it wouldn't function in the child process). + // Make a best effort to try to keep the duration we hold the + // lock short. + // TODO: On platforms that support it, use ``pthread_atfork`` to + // drop this lock. + LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); + + mod_globs->queue_to_destroy(state); + if (mod_globs->thread_states_to_destroy.size() == 1) { + // We added the first item to the queue. We need to schedule + // the cleanup. + + // A size greater than 1 means that we have already added the pending call, + // and in fact, it may be executing now. + // If it is executing, our lock makes sure that it will see the item we just added + // to the queue on its next iteration (after we release the lock) + // + // A size of 1 means there is no pending call, OR the pending call is + // currently executing, has dropped the lock, and is deleting the last item + // from the queue; its next iteration will go ahead and delete the item we just added. + // And the pending call we schedule here will have no work to do. + int result = AddPendingCall( + PendingCallback_DestroyQueueWithGIL, + nullptr); + if (result < 0) { + // Hmm, what can we do here? + fprintf(stderr, + "greenlet: WARNING: failed in call to Py_AddPendingCall; " + "expect a memory leak.\n"); + } + } + } + + static int + PendingCallback_DestroyQueueWithGIL(void* UNUSED(arg)) + { + // We're holding the GIL here, so no Python code should be able to + // run to call ``os.fork()``. + while (1) { + ThreadState* to_destroy; + { + LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); + if (mod_globs->thread_states_to_destroy.empty()) { + break; + } + to_destroy = mod_globs->take_next_to_destroy(); + } + assert(to_destroy); + assert(to_destroy->has_main_greenlet()); + // Drop the lock while we do the actual deletion. + // This allows other calls to MarkGreenletDeadAndQueueCleanup + // to enter and add to our queue. + DestroyOneWithGIL(to_destroy); + } + return 0; + } + + static void + DestroyOneWithGIL(const ThreadState* const state) + { + // Holding the GIL. + // Passed a non-shared pointer to the actual thread state. + // state -> main greenlet + assert(state->has_main_greenlet()); + PyGreenlet* main(state->borrow_main_greenlet()); + // When we need to do cross-thread operations, we check this. + // A NULL value means the thread died some time ago. + // We do this here, rather than in a Python dealloc function + // for the greenlet, in case there's still a reference out + // there. + dynamic_cast(main->pimpl)->thread_state(nullptr); + + delete state; // Deleting this runs the destructor, DECREFs the main greenlet. + } + + // ensure this is actually defined. + static_assert(GREENLET_BROKEN_PY_ADD_PENDING == 1 || GREENLET_BROKEN_PY_ADD_PENDING == 0, + "GREENLET_BROKEN_PY_ADD_PENDING not defined correctly."); + +#if GREENLET_BROKEN_PY_ADD_PENDING + static int _push_pending_call(struct _pending_calls *pending, + int (*func)(void *), void *arg) + { + int i = pending->last; + int j = (i + 1) % NPENDINGCALLS; + if (j == pending->first) { + return -1; /* Queue full */ + } + pending->calls[i].func = func; + pending->calls[i].arg = arg; + pending->last = j; + return 0; + } + + static int AddPendingCall(int (*func)(void *), void *arg) + { + _PyRuntimeState *runtime = &_PyRuntime; + if (!runtime) { + // obviously impossible + return 0; + } + struct _pending_calls *pending = &runtime->ceval.pending; + if (!pending->lock) { + return 0; + } + int result = 0; + PyThread_acquire_lock(pending->lock, WAIT_LOCK); + if (!pending->finishing) { + result = _push_pending_call(pending, func, arg); + } + PyThread_release_lock(pending->lock); + SIGNAL_PENDING_CALLS(&runtime->ceval); + return result; + } +#else + // Python < 3.8 or >= 3.9 + static int AddPendingCall(int (*func)(void*), void* arg) + { + // If the interpreter is in the middle of finalizing, we can't add a + // pending call. Trying to do so will end up in a SIGSEGV, as + // Py_AddPendingCall will not be able to get the interpreter and will + // try to dereference a NULL pointer. It's possible this can still + // segfault if we happen to get context switched, and maybe we should + // just always implement our own AddPendingCall, but I'd like to see if + // this works first +#if GREENLET_PY313 + if (Py_IsFinalizing()) { +#else + if (_Py_IsFinalizing()) { +#endif +#ifdef GREENLET_DEBUG + // No need to log in the general case. Yes, we'll leak, + // but we're shutting down so it should be ok. + fprintf(stderr, + "greenlet: WARNING: Interpreter is finalizing. Ignoring " + "call to Py_AddPendingCall; \n"); +#endif + return 0; + } + return Py_AddPendingCall(func, arg); + } +#endif + + + + +}; +}; + +}; // namespace greenlet + +// The intent when GET_THREAD_STATE() is needed multiple times in a +// function is to take a reference to its return value in a local +// variable, to avoid the thread-local indirection. On some platforms +// (macOS), accessing a thread-local involves a function call (plus an +// initial function call in each function that uses a thread local); +// in contrast, static volatile variables are at some pre-computed +// offset. +typedef greenlet::ThreadStateCreator ThreadStateCreator; +static thread_local ThreadStateCreator g_thread_state_global; +#define GET_THREAD_STATE() g_thread_state_global + +#endif //T_THREADSTATE_DESTROY diff --git a/venv/lib/python3.8/site-packages/greenlet/TUserGreenlet.cpp b/venv/lib/python3.8/site-packages/greenlet/TUserGreenlet.cpp new file mode 100644 index 000000000..73a81330d --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/TUserGreenlet.cpp @@ -0,0 +1,662 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::UserGreenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_USER_GREENLET_CPP +#define T_USER_GREENLET_CPP + +#include "greenlet_internal.hpp" +#include "TGreenlet.hpp" + +#include "TThreadStateDestroy.cpp" + + +namespace greenlet { +using greenlet::refs::BorrowedMainGreenlet; +greenlet::PythonAllocator UserGreenlet::allocator; + +void* UserGreenlet::operator new(size_t UNUSED(count)) +{ + return allocator.allocate(1); +} + + +void UserGreenlet::operator delete(void* ptr) +{ + return allocator.deallocate(static_cast(ptr), + 1); +} + + +UserGreenlet::UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent) + : Greenlet(p), _parent(the_parent) +{ +} + +UserGreenlet::~UserGreenlet() +{ + // Python 3.11: If we don't clear out the raw frame datastack + // when deleting an unfinished greenlet, + // TestLeaks.test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main fails. + this->python_state.did_finish(nullptr); + this->tp_clear(); +} + + +const BorrowedMainGreenlet +UserGreenlet::main_greenlet() const +{ + return this->_main_greenlet; +} + + +BorrowedMainGreenlet +UserGreenlet::find_main_greenlet_in_lineage() const +{ + if (this->started()) { + assert(this->_main_greenlet); + return BorrowedMainGreenlet(this->_main_greenlet); + } + + if (!this->_parent) { + /* garbage collected greenlet in chain */ + // XXX: WHAT? + return BorrowedMainGreenlet(nullptr); + } + + return this->_parent->find_main_greenlet_in_lineage(); +} + + +/** + * CAUTION: This will allocate memory and may trigger garbage + * collection and arbitrary Python code. + */ +OwnedObject +UserGreenlet::throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state) +{ + /* The dying greenlet cannot be a parent of ts_current + because the 'parent' field chain would hold a + reference */ + UserGreenlet::ParentIsCurrentGuard with_current_parent(this, current_thread_state); + + // We don't care about the return value, only whether an + // exception happened. Whether or not an exception happens, + // we need to restore the parent in case the greenlet gets + // resurrected. + return Greenlet::throw_GreenletExit_during_dealloc(current_thread_state); +} + +ThreadState* +UserGreenlet::thread_state() const noexcept +{ + // TODO: maybe make this throw, if the thread state isn't there? + // if (!this->main_greenlet) { + // throw std::runtime_error("No thread state"); // TODO: Better exception + // } + if (!this->_main_greenlet) { + return nullptr; + } + return this->_main_greenlet->thread_state(); +} + + +bool +UserGreenlet::was_running_in_dead_thread() const noexcept +{ + return this->_main_greenlet && !this->thread_state(); +} + +OwnedObject +UserGreenlet::g_switch() +{ + assert(this->args() || PyErr_Occurred()); + + try { + this->check_switch_allowed(); + } + catch (const PyErrOccurred&) { + this->release_args(); + throw; + } + + // Switching greenlets used to attempt to clean out ones that need + // deleted *if* we detected a thread switch. Should it still do + // that? + // An issue is that if we delete a greenlet from another thread, + // it gets queued to this thread, and ``kill_greenlet()`` switches + // back into the greenlet + + /* find the real target by ignoring dead greenlets, + and if necessary starting a greenlet. */ + switchstack_result_t err; + Greenlet* target = this; + // TODO: probably cleaner to handle the case where we do + // switch to ourself separately from the other cases. + // This can probably even further be simplified if we keep + // track of the switching_state we're going for and just call + // into g_switch() if it's not ourself. The main problem with that + // is that we would be using more stack space. + bool target_was_me = true; + bool was_initial_stub = false; + while (target) { + if (target->active()) { + if (!target_was_me) { + target->args() <<= this->args(); + assert(!this->args()); + } + err = target->g_switchstack(); + break; + } + if (!target->started()) { + // We never encounter a main greenlet that's not started. + assert(!target->main()); + UserGreenlet* real_target = static_cast(target); + assert(real_target); + void* dummymarker; + was_initial_stub = true; + if (!target_was_me) { + target->args() <<= this->args(); + assert(!this->args()); + } + try { + // This can only throw back to us while we're + // still in this greenlet. Once the new greenlet + // is bootstrapped, it has its own exception state. + err = real_target->g_initialstub(&dummymarker); + } + catch (const PyErrOccurred&) { + this->release_args(); + throw; + } + catch (const GreenletStartedWhileInPython&) { + // The greenlet was started sometime before this + // greenlet actually switched to it, i.e., + // "concurrent" calls to switch() or throw(). + // We need to retry the switch. + // Note that the current greenlet has been reset + // to this one (or we wouldn't be running!) + continue; + } + break; + } + + target = target->parent(); + target_was_me = false; + } + // The ``this`` pointer and all other stack or register based + // variables are invalid now, at least where things succeed + // above. + // But this one, probably not so much? It's not clear if it's + // safe to throw an exception at this point. + + if (err.status < 0) { + // If we get here, either g_initialstub() + // failed, or g_switchstack() failed. Either one of those + // cases SHOULD leave us in the original greenlet with a valid + // stack. + return this->on_switchstack_or_initialstub_failure(target, err, target_was_me, was_initial_stub); + } + + // err.the_new_current_greenlet would be the same as ``target``, + // if target wasn't probably corrupt. + return err.the_new_current_greenlet->g_switch_finish(err); +} + + + +Greenlet::switchstack_result_t +UserGreenlet::g_initialstub(void* mark) +{ + OwnedObject run; + + // We need to grab a reference to the current switch arguments + // in case we're entered concurrently during the call to + // GetAttr() and have to try again. + // We'll restore them when we return in that case. + // Scope them tightly to avoid ref leaks. + { + SwitchingArgs args(this->args()); + + /* save exception in case getattr clears it */ + PyErrPieces saved; + + /* + self.run is the object to call in the new greenlet. + This could run arbitrary python code and switch greenlets! + */ + run = this->self().PyRequireAttr(mod_globs->str_run); + /* restore saved exception */ + saved.PyErrRestore(); + + + /* recheck that it's safe to switch in case greenlet reparented anywhere above */ + this->check_switch_allowed(); + + /* by the time we got here another start could happen elsewhere, + * that means it should now be a regular switch. + * This can happen if the Python code is a subclass that implements + * __getattribute__ or __getattr__, or makes ``run`` a descriptor; + * all of those can run arbitrary code that switches back into + * this greenlet. + */ + if (this->stack_state.started()) { + // the successful switch cleared these out, we need to + // restore our version. They will be copied on up to the + // next target. + assert(!this->args()); + this->args() <<= args; + throw GreenletStartedWhileInPython(); + } + } + + // Sweet, if we got here, we have the go-ahead and will switch + // greenlets. + // Nothing we do from here on out should allow for a thread or + // greenlet switch: No arbitrary calls to Python, including + // decref'ing + +#if GREENLET_USE_CFRAME + /* OK, we need it, we're about to switch greenlets, save the state. */ + /* + See green_new(). This is a stack-allocated variable used + while *self* is in PyObject_Call(). + We want to defer copying the state info until we're sure + we need it and are in a stable place to do so. + */ + _PyCFrame trace_info; + + this->python_state.set_new_cframe(trace_info); +#endif + /* start the greenlet */ + ThreadState& thread_state = GET_THREAD_STATE().state(); + this->stack_state = StackState(mark, + thread_state.borrow_current()->stack_state); + this->python_state.set_initial_state(PyThreadState_GET()); + this->exception_state.clear(); + this->_main_greenlet = thread_state.get_main_greenlet(); + + /* perform the initial switch */ + switchstack_result_t err = this->g_switchstack(); + /* returns twice! + The 1st time with ``err == 1``: we are in the new greenlet. + This one owns a greenlet that used to be current. + The 2nd time with ``err <= 0``: back in the caller's + greenlet; this happens if the child finishes or switches + explicitly to us. Either way, the ``err`` variable is + created twice at the same memory location, but possibly + having different ``origin`` values. Note that it's not + constructed for the second time until the switch actually happens. + */ + if (err.status == 1) { + // In the new greenlet. + + // This never returns! Calling inner_bootstrap steals + // the contents of our run object within this stack frame, so + // it is not valid to do anything with it. + try { + this->inner_bootstrap(err.origin_greenlet.relinquish_ownership(), + run.relinquish_ownership()); + } + // Getting a C++ exception here isn't good. It's probably a + // bug in the underlying greenlet, meaning it's probably a + // C++ extension. We're going to abort anyway, but try to + // display some nice information *if* possible. Some obscure + // platforms don't properly support this (old 32-bit Arm, see see + // https://github.com/python-greenlet/greenlet/issues/385); that's not + // great, but should usually be OK because, as mentioned above, we're + // terminating anyway. + // + // The catching is tested by + // ``test_cpp.CPPTests.test_unhandled_exception_in_greenlet_aborts``. + // + // PyErrOccurred can theoretically be thrown by + // inner_bootstrap() -> g_switch_finish(), but that should + // never make it back to here. It is a std::exception and + // would be caught if it is. + catch (const std::exception& e) { + std::string base = "greenlet: Unhandled C++ exception: "; + base += e.what(); + Py_FatalError(base.c_str()); + } + catch (...) { + // Some compilers/runtimes use exceptions internally. + // It appears that GCC on Linux with libstdc++ throws an + // exception internally at process shutdown time to unwind + // stacks and clean up resources. Depending on exactly + // where we are when the process exits, that could result + // in an unknown exception getting here. If we + // Py_FatalError() or abort() here, we interfere with + // orderly process shutdown. Throwing the exception on up + // is the right thing to do. + // + // gevent's ``examples/dns_mass_resolve.py`` demonstrates this. +#ifndef NDEBUG + fprintf(stderr, + "greenlet: inner_bootstrap threw unknown exception; " + "is the process terminating?\n"); +#endif + throw; + } + Py_FatalError("greenlet: inner_bootstrap returned with no exception.\n"); + } + + + // In contrast, notice that we're keeping the origin greenlet + // around as an owned reference; we need it to call the trace + // function for the switch back into the parent. It was only + // captured at the time the switch actually happened, though, + // so we haven't been keeping an extra reference around this + // whole time. + + /* back in the parent */ + if (err.status < 0) { + /* start failed badly, restore greenlet state */ + this->stack_state = StackState(); + this->_main_greenlet.CLEAR(); + // CAUTION: This may run arbitrary Python code. + run.CLEAR(); // inner_bootstrap didn't run, we own the reference. + } + + // In the success case, the spawned code (inner_bootstrap) will + // take care of decrefing this, so we relinquish ownership so as + // to not double-decref. + + run.relinquish_ownership(); + + return err; +} + + +void +UserGreenlet::inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run) +{ + // The arguments here would be another great place for move. + // As it is, we take them as a reference so that when we clear + // them we clear what's on the stack above us. Do that NOW, and + // without using a C++ RAII object, + // so there's no way that exiting the parent frame can clear it, + // or we clear it unexpectedly. This arises in the context of the + // interpreter shutting down. See https://github.com/python-greenlet/greenlet/issues/325 + //PyObject* run = _run.relinquish_ownership(); + + /* in the new greenlet */ + assert(this->thread_state()->borrow_current() == BorrowedGreenlet(this->_self)); + // C++ exceptions cannot propagate to the parent greenlet from + // here. (TODO: Do we need a catch(...) clause, perhaps on the + // function itself? ALl we could do is terminate the program.) + // NOTE: On 32-bit Windows, the call chain is extremely + // important here in ways that are subtle, having to do with + // the depth of the SEH list. The call to restore it MUST NOT + // add a new SEH handler to the list, or we'll restore it to + // the wrong thing. + this->thread_state()->restore_exception_state(); + /* stack variables from above are no good and also will not unwind! */ + // EXCEPT: That can't be true, we access run, among others, here. + + this->stack_state.set_active(); /* running */ + + // We're about to possibly run Python code again, which + // could switch back/away to/from us, so we need to grab the + // arguments locally. + SwitchingArgs args; + args <<= this->args(); + assert(!this->args()); + + // XXX: We could clear this much earlier, right? + // Or would that introduce the possibility of running Python + // code when we don't want to? + // CAUTION: This may run arbitrary Python code. + this->_run_callable.CLEAR(); + + + // The first switch we need to manually call the trace + // function here instead of in g_switch_finish, because we + // never return there. + if (OwnedObject tracefunc = this->thread_state()->get_tracefunc()) { + OwnedGreenlet trace_origin; + trace_origin = origin_greenlet; + try { + g_calltrace(tracefunc, + args ? mod_globs->event_switch : mod_globs->event_throw, + trace_origin, + this->_self); + } + catch (const PyErrOccurred&) { + /* Turn trace errors into switch throws */ + args.CLEAR(); + } + } + + // We no longer need the origin, it was only here for + // tracing. + // We may never actually exit this stack frame so we need + // to explicitly clear it. + // This could run Python code and switch. + Py_CLEAR(origin_greenlet); + + OwnedObject result; + if (!args) { + /* pending exception */ + result = NULL; + } + else { + /* call g.run(*args, **kwargs) */ + // This could result in further switches + try { + //result = run.PyCall(args.args(), args.kwargs()); + // CAUTION: Just invoking this, before the function even + // runs, may cause memory allocations, which may trigger + // GC, which may run arbitrary Python code. + result = OwnedObject::consuming(PyObject_Call(run, args.args().borrow(), args.kwargs().borrow())); + } + catch (...) { + // Unhandled C++ exception! + + // If we declare ourselves as noexcept, if we don't catch + // this here, most platforms will just abort() the + // process. But on 64-bit Windows with older versions of + // the C runtime, this can actually corrupt memory and + // just return. We see this when compiling with the + // Windows 7.0 SDK targeting Windows Server 2008, but not + // when using the Appveyor Visual Studio 2019 image. So + // this currently only affects Python 2.7 on Windows 64. + // That is, the tests pass and the runtime aborts + // everywhere else. + // + // However, if we catch it and try to continue with a + // Python error, then all Windows 64 bit platforms corrupt + // memory. So all we can do is manually abort, hopefully + // with a good error message. (Note that the above was + // tested WITHOUT the `/EHr` switch being used at compile + // time, so MSVC may have "optimized" out important + // checking. Using that switch, we may be in a better + // place in terms of memory corruption.) But sometimes it + // can't be caught here at all, which is confusing but not + // terribly surprising; so again, the G_NOEXCEPT_WIN32 + // plus "/EHr". + // + // Hopefully the basic C stdlib is still functional enough + // for us to at least print an error. + // + // It gets more complicated than that, though, on some + // platforms, specifically at least Linux/gcc/libstdc++. They use + // an exception to unwind the stack when a background + // thread exits. (See comments about noexcept.) So this + // may not actually represent anything untoward. On those + // platforms we allow throws of this to propagate, or + // attempt to anyway. +# if defined(WIN32) || defined(_WIN32) + Py_FatalError( + "greenlet: Unhandled C++ exception from a greenlet run function. " + "Because memory is likely corrupted, terminating process."); + std::abort(); +#else + throw; +#endif + } + } + // These lines may run arbitrary code + args.CLEAR(); + Py_CLEAR(run); + + if (!result + && mod_globs->PyExc_GreenletExit.PyExceptionMatches() + && (this->args())) { + // This can happen, for example, if our only reference + // goes away after we switch back to the parent. + // See test_dealloc_switch_args_not_lost + PyErrPieces clear_error; + result <<= this->args(); + result = single_result(result); + } + this->release_args(); + this->python_state.did_finish(PyThreadState_GET()); + + result = g_handle_exit(result); + assert(this->thread_state()->borrow_current() == this->_self); + + /* jump back to parent */ + this->stack_state.set_inactive(); /* dead */ + + + // TODO: Can we decref some things here? Release our main greenlet + // and maybe parent? + for (Greenlet* parent = this->_parent; + parent; + parent = parent->parent()) { + // We need to somewhere consume a reference to + // the result; in most cases we'll never have control + // back in this stack frame again. Calling + // green_switch actually adds another reference! + // This would probably be clearer with a specific API + // to hand results to the parent. + parent->args() <<= result; + assert(!result); + // The parent greenlet now owns the result; in the + // typical case we'll never get back here to assign to + // result and thus release the reference. + try { + result = parent->g_switch(); + } + catch (const PyErrOccurred&) { + // Ignore, keep passing the error on up. + } + + /* Return here means switch to parent failed, + * in which case we throw *current* exception + * to the next parent in chain. + */ + assert(!result); + } + /* We ran out of parents, cannot continue */ + PyErr_WriteUnraisable(this->self().borrow_o()); + Py_FatalError("greenlet: ran out of parent greenlets while propagating exception; " + "cannot continue"); + std::abort(); +} + +void +UserGreenlet::run(const BorrowedObject nrun) +{ + if (this->started()) { + throw AttributeError( + "run cannot be set " + "after the start of the greenlet"); + } + this->_run_callable = nrun; +} + +const OwnedGreenlet +UserGreenlet::parent() const +{ + return this->_parent; +} + +void +UserGreenlet::parent(const BorrowedObject raw_new_parent) +{ + if (!raw_new_parent) { + throw AttributeError("can't delete attribute"); + } + + BorrowedMainGreenlet main_greenlet_of_new_parent; + BorrowedGreenlet new_parent(raw_new_parent.borrow()); // could + // throw + // TypeError! + for (BorrowedGreenlet p = new_parent; p; p = p->parent()) { + if (p == this->self()) { + throw ValueError("cyclic parent chain"); + } + main_greenlet_of_new_parent = p->main_greenlet(); + } + + if (!main_greenlet_of_new_parent) { + throw ValueError("parent must not be garbage collected"); + } + + if (this->started() + && this->_main_greenlet != main_greenlet_of_new_parent) { + throw ValueError("parent cannot be on a different thread"); + } + + this->_parent = new_parent; +} + +void +UserGreenlet::murder_in_place() +{ + this->_main_greenlet.CLEAR(); + Greenlet::murder_in_place(); +} + +bool +UserGreenlet::belongs_to_thread(const ThreadState* thread_state) const +{ + return Greenlet::belongs_to_thread(thread_state) && this->_main_greenlet == thread_state->borrow_main_greenlet(); +} + + +int +UserGreenlet::tp_traverse(visitproc visit, void* arg) +{ + Py_VISIT(this->_parent.borrow_o()); + Py_VISIT(this->_main_greenlet.borrow_o()); + Py_VISIT(this->_run_callable.borrow_o()); + + return Greenlet::tp_traverse(visit, arg); +} + +int +UserGreenlet::tp_clear() +{ + Greenlet::tp_clear(); + this->_parent.CLEAR(); + this->_main_greenlet.CLEAR(); + this->_run_callable.CLEAR(); + return 0; +} + +UserGreenlet::ParentIsCurrentGuard::ParentIsCurrentGuard(UserGreenlet* p, + const ThreadState& thread_state) + : oldparent(p->_parent), + greenlet(p) +{ + p->_parent = thread_state.get_current(); +} + +UserGreenlet::ParentIsCurrentGuard::~ParentIsCurrentGuard() +{ + this->greenlet->_parent = oldparent; + oldparent.CLEAR(); +} + +}; //namespace greenlet +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/__init__.py b/venv/lib/python3.8/site-packages/greenlet/__init__.py new file mode 100644 index 000000000..b2dcc9bda --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/__init__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +The root of the greenlet package. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__all__ = [ + '__version__', + '_C_API', + + 'GreenletExit', + 'error', + + 'getcurrent', + 'greenlet', + + 'gettrace', + 'settrace', +] + +# pylint:disable=no-name-in-module + +### +# Metadata +### +__version__ = '3.1.1' +from ._greenlet import _C_API # pylint:disable=no-name-in-module + +### +# Exceptions +### +from ._greenlet import GreenletExit +from ._greenlet import error + +### +# greenlets +### +from ._greenlet import getcurrent +from ._greenlet import greenlet + +### +# tracing +### +try: + from ._greenlet import gettrace + from ._greenlet import settrace +except ImportError: + # Tracing wasn't supported. + # XXX: The option to disable it was removed in 1.0, + # so this branch should be dead code. + pass + +### +# Constants +# These constants aren't documented and aren't recommended. +# In 1.0, USE_GC and USE_TRACING are always true, and USE_CONTEXT_VARS +# is the same as ``sys.version_info[:2] >= 3.7`` +### +from ._greenlet import GREENLET_USE_CONTEXT_VARS # pylint:disable=unused-import +from ._greenlet import GREENLET_USE_GC # pylint:disable=unused-import +from ._greenlet import GREENLET_USE_TRACING # pylint:disable=unused-import + +# Controlling the use of the gc module. Provisional API for this greenlet +# implementation in 2.0. +from ._greenlet import CLOCKS_PER_SEC # pylint:disable=unused-import +from ._greenlet import enable_optional_cleanup # pylint:disable=unused-import +from ._greenlet import get_clocks_used_doing_optional_cleanup # pylint:disable=unused-import + +# Other APIS in the _greenlet module are for test support. diff --git a/venv/lib/python3.8/site-packages/greenlet/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc71b5dd6c6cf37cd101b05ee0727399329d8c84 GIT binary patch literal 1036 zcmZXS%WB&|6ow^Pwq^N#$*oPnP$+m|i;ztzr8Kz0ga#+rPMgkVkUb}kuq=tu*ff5l zLf@dO4$E0Y|nuxgNBTExPd3acR( zNI`KO*(SE)26B-U6*rMfq@-(`L;OVxqcSP0%noveRCMjD7Pd?cstcR{)HV5j(?a7) zeqk4DYNV#>_mJzPu6Q50K^lq=kej5b_yux{v=qNY-XS~4hhwetYV+Ej+(4U0k!VNP zZGk-J0AT>49ecBdH;0~mhbCzqJ*|4vBnnmnsK1OOE^x_W{@PFcC`7Kr+z$o4UWGG( zD`}C^HE@}vG|kaIJ-IkdOM~s-yWf40W`XmFr}iAgY{fZ*BDEgBj|4(+Zw9HAY-y7{ z?j7}x?sT;wj;+Og@%+Oin~G84VUiaBqvFF`7`g87%$?B7u}k~s!-;z}pb$Tbe&gZt}3M_r7oo*rHS%Gu<#*p zKEWD-C|+W_oHM)=PJAnFqOcnucdxO#Qy;prZwuhg8p5>`_|t94aqq2@_yW4yyOlVP zyLTw{LtjwZi*Hlw{W4;!06xg0C+|?qOi9N#W9WwQWc)Rore4ZoMhydNYI?=YS*qHU F`VY^01?~U< literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/_greenlet.cpython-38-x86_64-linux-gnu.so b/venv/lib/python3.8/site-packages/greenlet/_greenlet.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..0f3d5c794ca14c5aa4236b707e088702aacb428f GIT binary patch literal 1420664 zcmeFa33wD$);C@O0s+)SBog-q1A+@CKqR1O2MBZ|NQOlb)FdPUB9O$S13^%N2};{W zGmZ=68n+Qq#*8|Odjt{GQKPuvG7@p&mJt&-#AVF?ch0@leY%oXXWsY!eb4hfQ#_Tb z^SkGsd+xdCp1V|4vnn%tQn#2G%Q?DRr(0plvb-i?|6Pd=3$S#n5B?6Z1~YGuP)g^U zq$@P$T&)C6QyH;zTd!&NwemZSJ6D;H%d0n9x~}Wz=zPvq=Ie-OsW>))iDTXC+y>A~3-XSLUB9eY6a`aGX?h;_gL zG4 zht)H|nz^ofLQJn5SUV=qYjqpy>D}xFLm%J5)q|`a6VJ4)Bu`#-EFjBkC7g9#x0s~0 ztDm>57?0IIuHG8ekU)|K46D9&g7>nRrq$h2JXVHP&GkfADesmOS6$yNwujYV^|;eB z$ja&Vr+x|DP8x6dUa0QYEpR|>jOQw=M`J>Q6|;Jv6<63V=YRuybhA7eRzkw~YS?Lf zk68@|Ty%bSZ~s43UzQ0q*dCYQdMOb&R^hx1XD!Y(I4{RZ-3^tt0yyh%UXAk_ob@=b z#d#gh>v7(Ila6)z68d`+?rt{Jt+?K1?i+Bu1LvJM@4`vP-TK0R@4?@{;JjC0bh}>A z_u>A2LqA}y8*%+B&cES&*kF&C>!Y|nj`ImaKW(nh;QB1i=WuSpxeX^BFY1f+60R@f z{0Gig3`Un%&HZb*zK-(^L+>zGx{&j|E$DY}|E{3l!~F+_Zozf8x&PQ)>GBy)h115_ zit|gHZ8*QeNyj((V(l|my6iXiKjQim&YulUmtSypI5ZqgglnrCJDihMed6dDA#&sahV+}pnT!-L#oVg#0 z>oAYbeN`u!^|-*Nq)yl0(y_ERmFja{?#>H2&Bd7A&NjZJUOxc}^H+uu2R^25h{+_HIS z^X+$hKg|DbkJH}S`txziKkJ)w-f_u)S$g#R;&0E`xn$ewCzqW2z&+pAR@9 zL#|35zi@PNT(6_P`RlPCz4PZmKUvFb>i+P|(kZV`DSm5Z&pD5u7}sm?)kCKK?Xp*< z{^iCq<{Ws!_KJ+m$7dZoF}K?ZW!J8n)O$$5+pGU_SjNK*FJ2HRZ;Suz(O;&nzTl5% zpPpa%*0;Tm+C25~Sr^}C&ChyjMfI%YjU#=_KE7@EC$~IPvHi!_Vx~@<{>LvL{iW&q zDfh4W`z>3N8#nJ5aEBGxIVry|>FaL?jPE-v?a8_w&)&Ud;Jv*L8-Me(l!YA^U7Yae zGd5Kd((%6KcRVui!+SPvQFBk5(scBxdp;a|^tUe`JLiXM-&p-@ z{)&q|v*(_6*Jt-^ygse!D>Z4+yEUiZy6ww7RrwEGx3%zsSI6#{b?vGl`zG&RJY`4P zvJT(vgARYJ_Wu0~)3;yrLVo`%Z#eb%C9~e|eb354z2o{nv+bLG+@nL@Pw;el{Y`fsL#S8Cx=ktM^ z?ymKY{NU`#SwHv|ezzp8x>ohvJ?_U_Tc7;7s-|s3=DaP>E;;vyd^5EtA@t) z+BIs&_DR1yxbB!E9^GAOfs6Kl5ot>jccI!1Pt^W_bj=FJteqqWB-=9+S?7(}Uy3?8;Xj)OdD5v1w zcZWS!dg=@F9)EDpisPre_^+Sl?)!E?<#{!8dR5->NX;j6Pix!zr|OSNdN<~;ekkVQ zZO6`eV8Gn>hHd=EMc2GEJ#*gfdp54U#<w>`AU%!=Z&&Tx9wp9y6D6MPj1nj7qO{j#$8~N0Uhto;^qCYT-r7;dyN9FL zrzVP@?22Na8=~k#zJnq<9ebnLp*>2Rtct>aNtAwF97WG7qV&g?VE?ZA`$bXguqMj9 zA_g_}bgHdoT^OZ(5w5!AKNkJ5tM;`K&%4qyCW=2F%Kq8cYBrk;UmM+~@wm{}^6b%o z$KWwZSX*2DE!DdlBkK)57yZ2-cES=q*T5ePUTg3&gP(5jmoScyoSZl&xLj!0gK0ZdnlO39G*7_i<@i82JLUv0xc7s?xjsic`*Xye9jc^8% z?=gDD8vesE{!)Ft3$=W7Pd*-i0ZIP$URqB_eo=odr`c>IWf}gWVOmaowU+a#zF0GQ zX*qE&J%2EE@S1&U#}0jszqQcj6pk_aV(kP!>Dg%7dEmBYzq? zGRbK-)<#lH_7rIsTmt|HtQOrzxM;pi0pi9fM1Y(Jg#!zH~q22^v4Xt zKi2Rk7=9z(>V2%SkLh<;8UA@tgWJm$HyTkd?$>!*f^GP}KxiQQRJr^i6%NMyy)^$o zQ}A)g8gDk`t~PQSqQr;2rr#wPKld2^w@tq&GyS5%;0L3 z;xYEmF>+vDKK^X{#~ruNHuD*;na?!ld#Lom&%O4ooVFUZsKIU@gHa0q36p~Uw64582%Q+@5G;`UOGPbjQ);2 zV~l;g#y$v-d^ABXlAmY%$KgM8u-4x_f4CgEh`;4!o!_G`md}h|-klm4Xz<&Q)BKiK z8-Si0(Xj%7i{w=QQ_~lt|I_i5i6?m`o=nlOb(on4y5}1wMVX&?O?~q|(gLn9a?Uk& zbI(tDnD}Fv{%F+1Locmo)7x4AnuCvvjhsAV|C0^=IHt%{ujV&3;P^=;`Xl*6o{^92 ze9XdM<~Q~>c`UjuAN!Gy_}%ld4AW07V^7CEyAVH#-#x$jtMOa+yybeMf1XSK;|6Lu-lbZy z^So{W9F^>oe2dmEPhYGpM*l{mKf)RxXPbFTgKIv}+sJp%f38DuBtOOY|FK3+4`cs4 zV}GrcW!;YUrMSA#6~|K0-$_oHPjh~Q_Mqdy19ZLI?J^fQ@i)Dp`5gH_82@iH{{Ny* zwlYn9b4-0P4dCNJ)R*Km8~vR)c?K#%xaAr*1E#-tO&rTLa;`Q0S?%)A+s!!DQRaKyOt~Icx$(nwe`zv)>)3xC>P`ON?wW`Eg!o3ddw#gv^b7a= zu+Fq^nQ31%6Cc-^@xr~nj^TdP-)eOE;p>Q#)b4q$I{!OHP6fsV^4k`}@5~#ok1`*- z{RBOZU8>?ltSU2G=~Tb&!_R_enE0_Tz+Nc<}O%LT9RAg z&oB4q=32Q~GpFVj7L^w*D6a4qmCu|yVR30m(aijLi;Hwx*D1LLRr$H|i%aqs7hep< zIcH5tUr=6DRI<3ppFY{#j2JP$w7j4ww_67}T~fHzG$aaxX3}Dav2)8zqf6Wm$ejZh2)%NpZ;n%2kM}IW_F0^W#t)QT?yu7H;G|tSk&k8qB zBnNkr8UECfa5{hSk|N#nIyY)$u8Hn)$IM(_R)m5}%R6NiN$y;!DQD*|u8fi|BC%7> zQ5k-JdGWkT^xZB?O?5MLk}x7?`J{Y*{$iOw!cY+mY^SghBhorqBOKX6cZf*k{-VNj z78Wlq$|}*_s*BbM@s9}Ib(J$xn-J@-XX>P8s;f~`xKkD`&RM#ILT9M9q6IZeI4Po$ zIweM0sZ+AMiQEjow_Z{6jU0&}U%D*U36hyr#s1vF%5n;>h^!RVGN*SjZg3yUol)eU z0Us+_VCCj4&z-3GEu>Wyv%*eF|O-6tY zGb-o#aYt&v5_qjGqkKW-lA;n4A%^4J$wh>O3}l3c=o#8xaJ(7*(Ycs6RHde-j+mEU zfofN9J7g6gP^A_u%rDRNm**GzE3yhQXXN8g=9t{6xdoU8AR0_xLe4wGKO!ZU22Qko z#d1tomgvA%Ow*dGs@$@o@`_TLl=zqDE={p=XO%3YQG0q(1vKaUsbie{i%SVgwSWaf;{E#=mwve+1|*i1Y+DXNszPEw?* zavDTopL#t{)3`&xhPg#m1x024;!>(*L`Zb?SPDatE*@FB!+c{@dC~le)Y0Qh(E!Ve3a1y%$0W3*peV~R)a?AiY%)|TrhjLZPAi+Ru&7{B zQF&(0(o7s=!?2+uN;st~ACml;|2u_96H9M!Of1w02UIlXf8g$1sZN77WKl5tw>uBF zqL?9swac`Y6fG$zTOMJi#t_m>?n z+$QWA(Ol7Sc&Ee9!(y%kV+vMWrgx%rhmMSIGH3`fx}kFN%PWewoia)arxYz;R$5+I zkxK!6W<;B4i;bC3TH-IN@@H1%7x;zMa+aF}J-*0(YfQ}}%%mcyixTsfbed{LVWX)Z z{D=G~HwPXCf<`I~nmWo&B?A;y7?|aER(r%KX#%(It~eMGW`4Cv{==oL3^1u9BNm%_ zWfjc@e%JCe)H_3SkddQlD}dMLSTGkX$}KI&^0e4roWHojUpX&#etz-d%JQPjoEa&( z6ogW8N2Eyhaty4C{ki_kdGm~!oh1s9n7z#4K^o0cS{`Bh#WOOehvM_7Y0FA}Z3IZ^ zas;6KsUv5w@|f!&vQ%WER2?~JS0ZQmjG~JwX-r|CjVNEcR4RK+PH|B|QAHH5i(qjt zHbQnx9a&UWR$5V%J0JdARH54{WbVjqhmvKPOQ4&X>rLg+IYPHc_QJ&&@xuI*FwXu8 zw`>0+hiueo^F$~n9gY=~-7_-QYDJ=_k4Qo9#kiYQF@YATCH~2k`Q^@1RgTrN%qW@M zhE8=&synOdV(3EIQK7(qrI){GaRuV>$XtI#q%~b!X$6am^39G_*laE)jL^J^6P=-R zeIk`9hcdsZOKPgWjAqD7G08=joKzg1Q!dS%F%o-uVlI;E(lH%*yQ~Wwoc4m)CaGza zCBN#(5zM12iu_KrF#By?Su`TgZ&YDkL>o>ARns&!PPxv!8>YF9L(`xzx0R(z4}<45dpbIu|TLpUs)#+5^<{ zp#NwBW~>?>+5ac9v`fpik82Bsrgk*D$5sv=SQOE&7Of@2OBAy%!GfS1Rvu52S=z`l z+0LSkJbG3Y8h4S9g_$n)jm+aJ3OJ>Ol~_zn;4P;S0C+2GIv&<62jQ#;XO-wFFg+u4 z)-)x{Xihh)q`0885SwRM2$h%ccA?&B)VgD{P!^ASY^z}?cNQ#EAg^Zhu434-5mUI- z^aU7qv3r88E6fCPv4e*-99_2N1NPRK9AT)s%} zX!7<>d6C)R!M58NRA>nv_xX!xE=rmsIG2`FlelK~{{t^bn~!2isJep9sZB}`RD)_SBIP?1-T}YjB=}MwH=ROn1&JZPRrxa&OWj`bOFR6W$q`^!#tg zbhlEfTgtwsqVZXGg6Qa_d6Y{#gr;?mKe^3c>EvaiYP0hg`!A~;E5b_A{7ru z~nHaNl|%mK?X+-bj!siOw!i4rPR{b_`n@lTBK+!PZhpZP>+2(KZ~oDz?%=)D%53VIS=@54HUU0 z7m-EIb|X4_R4&&RS0vxe8Nv-B-#4QUh{anfUerOa$)8ueG?iZ3sVK(thx`h>+e@z( z6_?B}Wkw+<@@aQO39dC>L|RubuL!BB5pSz;neuI|&Z#1A*x^EVSo3sx;m=)>5OWu% zpeXVic8HvBb=Fu$O?T1mxqYYfPLHr7Y&kL;(Sf$bcweVe(Vdgen$l^Heblmr`F8G4@ z$|wRySd%AA$Q=o`$=O-sC*+P8J~DJSVt88UK6R8eVfO5d@maa4!$%GuEr~IjY4&W0 z88tjLRis6vj7XIvy~Q8X#lLR&K01t8A|Jh-xZC)Z_G-` zaZFMEZdPy3ML8WgRF1ya98S(2E-4PKud$p*^YjVHioy3CG1dV%wf0&^r-ay$#*woA zOa0%nfa8PT;+rY@&d7r^XUKAVxpjb*YCe=C-Hyb)lYU5XF}|$pV~r8%-9&oj;l=o} zrk6D#l-|wiXVOz3|GGckfG?BctZ4?*{IP<+r}e(k2kh3e*B^vFeXTi$e>u~=tp$e8 z-+v=M5bA094ZY*RMfk+0hgD+}rN8PMjLDS*A3vakvcZR2@5-dR@RqxbeO&lBV^5z8Z#Lg|&UWE>Moyj!KhD^5p$pG5-;-6j@RmEZKGiNf z!)Y%U?lE%Kx$rzAr@@8SU$5n_ci|~U&L$Ub-KP1QT=*QL&vqA{@|yPloi04@RxPK+ zg>N$YC>PE@%nVWcU3iQ6-p(^1(toZt`V4j9%|@RT7vA)$*3;+0?=W(5TzI{aGuwsd z7&*#?H<|Ajo01~+e95t!3-=f~%`QCQ6|JXo;XQBC_H1|IIp(|e{Vv>kOUp?(EK;8p z>$IFC7v606JubZ7;OQ=WhpCs>h1Z+!Df3+T2Zn#43ojd^^(k}V4F+H9!uJ?C^)9^e zb6wv?7ygCe-{is*jQuV19jO!FZZ&Zr&V_rw(DIX9c;3BQe~$~l%E%e&!W+NUa?)M6 z=N>J`>%xC9`ukjXi}90c7w-7US{LrL`z9B@)AYw?7v7k!^~pIR(tpa0KC@kTneqQT z7hZ4lT<5}_{=D6VmzfWqce?OA^CRNQg|`_0pKX5E#%ae*M$a-A?$m3Y3s0aQBFE9- z!W&FKZ+GF=4Vr(y3wP{b9TjOe#|}eXxZ{5*F1*Ejx1H|7o%piQg*$eua^Wqii^E=;5yK(1t z+}!wVA^%`XG)JDmo!=y&q^2nN&M3Ige7{YirJVM7?GKpbM&M;aj`Mr^RBn~Py+Y6W zDERs)_-vt%lv8$4SRaWi<0n+4#McV>9i*T+8U)@;=($PYS2@hMHVb^Uz*_`947lTm zO5nQ$|9*k@5an8;zV!bPI7dnp+-H7oi&!PUN3@s36NH>GLVl9K-*pPbb)ETLLSmKp zP{IGR;P(oAppc&@aF4*tgq{*#DEJ={^2-E%roh)l!JDGsDhi$;>?!4^N5QLwoC8F? z_6z@%_*%h#gy8oIeu>u${)a?;*9-h!fqR4;i8l)V9|V7ru)oAN3I2M)-z@OI3EU^- zNPMT@-yryv&{N_qf_4#^VHjqLAYe_#cHn>x4cMZx``I;zNa; zLxudILXN~!1pob_+&qCV6?j<`d~FoGJ_^1r3f>R}Umpc;ih^&Cf-e-f`N=`9U4n=w z60Z{cmkK+01i!?q1;3fva;~9*U*e5|f1{AUN#IQa&l7Sa-Yock6#P|!U*aC~d#2 z$~8X;#i?-u4+{Pyfj=Pd6oH#5FQ=vpe4pU=3Y>nc$2oig4>%yMIRft~_-6~;S$a{n zg#y1-$SD)J*@EHJ27%N67vvo41>Wp{xHbxWv%vF2dr5qg;5R?f!?_j;eu*~;{=Z`e@XDK6Z{fy z7yM?6kaIN%eu+1WaZuv>g&eab!E)9MITE)7ewt{P#wd84z@HTL+7ty(5IFrmTh7rG z1y2%qy5Qd)1y2|F8G>I$!F>Y1&QS)}b;6z!&k_7z3I5pvzg*zW0{>j#EdqaE;FgJR z2@=D7f{1&qA}vYaqPkX!z;_7#bb;fi(L#q$;Ma#x#3g}0Eb!R^ZxMK&z@HO%gV}!| z9VNa{@YDa3<{VW5KhyzntrmE!;9o27D+PbO!10rEp`$_I_$ji`v0mU$hfu^rfe#XR zlfZ`ve7nGp6L_=0Qv|+K;MD?e5%@@fw+s9ffm_0FYXu%B@KXhzAaLjZlcVIJ0#|}R zMc^k0`=<+hvf%d$+$ZoHfoBVRw!o(fJWt?H34EczX9~PZ;13GCTHs@a{%ZyPo#3w* z_&k9(2z-!`vtHm=3;srd-z4x&0$(KXCV`g;e7nFa1-?_@%LLvc@Jj@)1pcDHn?*mx zPZfraGO-?!c#GiA3UQ;K3Vw;V2)s`4?-zKvz%B7RjQIjjGXH@r#jv*p?h!cs-;K^O zRN&nl5Z4ree=P8Hfy;TRSKxArJ77Cp9!knW_ z;PU^etP;4hw@Z1d1>V~cg6mp=-!1TZf&V1%bpr1r@CJdm34Fc4`wF~K;0FkNlfVxY zc$2`_3w*o49}{@9zz-7mPJyowc#FXQDR3q5c!9SIT#niM1%9yLw+`0cM}FH+;Bf+% z>&*m#JHMSw*^&g_U&!$YJW=371unBx0PvD1zq+tFa z@WTZ@Ti{0sJWt@xZ-r9wLVZas+f&U`-*9rU{ zfj0>JuL55$aQZ)9oug6UGOlhC_yED*B=BTM2(H@&K3L$*0v{^yodQ2z;4K0_LEuW@ zCknh>;3o-uzrec*{jGjs|Nl(z#|eD6z!L<1vcQuB{+_@+0#6nAP=SvSc#6Q~x;S0n zqXfTK;AsN)3H)n;=Lmd{z-J5m7-640fsYpa3k80<;4c&Sr-Hvq;O_{$THwzLe67IK z1zs=kF#=yF@Ua4K5O|ExbG^Xd6#R_>j~D!#1a1rdCV@XG@a+QME%0W6)Bm6B96JR* zTHq}LKh0srRSA5Yz}p3Wy1@4fe3QVfL&E-lhQQ+lzEzi0 zzgFNW0v|8%bb-_VIqe)?flqKiTzvwcDDWJC|3l!j1wKjOc>*6I%3Ub%DT2RD;5!9g zCGal=UM=u%1-@3`X9>Js;L`-YPT)BLZxHw&1ioJ2(*@or@EHQ%B=EfgZxZ+{fo~W1 z*#d7C_&EaKDRBA!jJF7Uw%}I+KUd)G0-q!B{Q{pWaI1gV|IZV6oWRc)c!I#21fC@D z3k2>Fcz5AHLj|5I_)`R)C-8KEA0p&<1%8L%_X)f};5h=P|HIumW($0y1LB$|@Irwv z6!><5mkGQ`;8g;jFYs!CFA(@zfiDzzy}*kFzE0p53cNw!jRIdU@Wlde6!_l-zDeLq z1l}a@YXrVs;3WcY7WiEP-zo4?fwu^}LEuW@F9^I{;Jror?icvWg5NUldD8szi0uM&8Q!2crfbb((iaIe5$5%_;^|7(H&THwDH_^$>2 zYk~h-;J+65f7Jrt#2@{mujY$5UoiG@{1=D5HBJ6*9nHR)=i|0;l{?aQ1MV373I4_( z?WpPBDy=#jZAMQx(Cq>Og}+%EYbB$KSXp- zqN|zSKr~G+?J}nCBsz}hJf?3VnzsJ!9Hy@&nzrz5FVj~NO=y8RabgXa-VTjO>M({qTXEpfY<>FGrGBf5#{Gl{0HZo84` z@kIA0x`FAjMAO!`UC;CgqG?Opu4ejpq6ZLN#`GYflZeh^`f#FYi`vd%x*ySp6YXWX z57D$GZKp8ZjcD46wmnS$cq!DIZvPk6e>c${qFb1LkLZC! zH#7YP(SwL?V)`Ybk0rX1>CHsbR4%7(M07RN8;G7vbQ#lk673^8kLjC;<R8>1&BTlV~r~R}xKI zY<3FMml2&!w1?@7iJnSy0@LM0)0UZSF?}J?(}-^Wk=vhW+9I=Cn4Uv4ZH?K@Oiw48 zw#4ivrq3jrw!-X2rpFUKljsJf#}Z9jUv@pyBZ#IgFT0xQt%JwqdhiKZevQwDuMl@|z*&e2UTnU=CsO$u$zb2Ztplplj&xxij zCcFIyZhxZlh;Cu}J)-l8Zf5!oqURCa#Pmx<7ZBaZ^k$-IE68qO`U#?GtH-Wq`XQp{ z6J5>p2BH@bUB>jCMAHCd=P`W~(ZxjPFnul27ZUAd`bwe~5uL*HWkl1KjqPFjVxpH2 zoxpTC(X{1aTTEX_G;OKa?fbd?i7q3$h3Pp&)7FRG%=C1k%ZYAc`b?r}3&d_@dOXpz z^EY=w(EgF+GTA+VZgTm_D570s0c66PW&*=u3&VnEssTRYbRc&+SiiHPJ0hzen_HqMMn1gXqhM zZesc+qHBn5WO_5vwL~{C{RGi#h^}Y)A)+rQx|-sw>EnsMk?1m}2NAuF z=sczmC;HDs=P=!m=$nZ4GTn#hn~6?gx*O5A5ba_5$BRMVN^}C#UlV;B(H7I66MZ|; z?cZ_x6Wu^`3)AlreFxFaOus?&okTY={Swi45#7l2W}@#Vx`F8@h`xvDdeHHY_83gU z;``EF1K(xL%nE!v%NO|93}0Y}Z`e1OuO|2c=OxGaYJQ4edPM3QzB+$$oO%w^6<=^B z;6SG33(QJr?e7cr0#8h3&npjIo6wrzTl2oZuP?AIJMft=@a{Kv`+^g}9h?R3%(xeN zbh9w;O_+Or#(7(a6LQ+rQ|JTPfjuf2!ZT)N*Oew@%$zj?8YE>0PWG+YS9yT1ZrtSW z@IY-IIDLVie1Y$wt?B{a@dZeHV5_fYfBaGpD+yf#yVL;EG|)W@NvuI&0_j?_Kc=#G z#+5yi*MfwuP5!>wfi~9F4(WP`FEGJTHHlQsIDgBmXA@wj898bf8q8Oh*#0e^Uj?>c zO8-nbru2bbzF>;lfyoSz^9Tth?sXE#Zq~m|orr89`Kz@2 z`jGq!95E@XLd*a8B9>q0B&2;zcSe3`SpGyK{~rXc+2<7D3!c;m87hxbhfrC;N(=S5 z^hc`CHdIL+h*DaQQ2XF1CUGPy!-?%AD;T?6t3YzN?rC{Kg)cCsw*u!%K1*ewktZW# zJsVNoLXzte4-8dt1xeE;tk?1n)ABbs32By)e--2#>G?*w4Tyzj!%b7vSk`@1vT;>3 zus_*Dt`PIJ>PJQle2HdP6TZQ1aAmS5d(8L#gUC-(e060pY6TfCm{?mzc1}x!62zTI z#S%A+gbbCb1XBO>1y4)P9ucc>4nunDv)UYW-$F!P z;xAuYR#xD77s)DGO zdK)8a&Grua6U=kTQv+{NA4?&9Vd{WteM3D=87WIYHJh^BWwP|fR9M}B#CjpNT#X{0 zRp3c|-+mS>TK!kah;@la{p6^>u{5OqW-1^UI|T^r`GJ#=HbScZG$e)V#ix%!Usk@oTGCQAGi!NG2%l=qS1c)J>|^?T3t?wkQ@AtS+QSWe%=t|!MjlP4U>-#zMBq8Onq|w*J zk{r^tF0JRjQ24rl)k?fZ7hk5^RL$n%xfM^M=76xj1A>FMsRKBOgyZqgx{s*qGy}R0 zcdYnYn!)($RwgI;>J}#_s9KiUrdINGmnt;$VqCKWEv+x<4ziIHs!QzI?sSl!F+1e` zUFK2M+E`gy+wqGzMQtFBP*8%R`I8U*Hi@o4;?zNm>{9!*)~WBSTiW3LdpHn5x9nhH zGDfxz|M9+>lVjr7jKsQ31z;w7818f!K#Qv(WVwK{2V%luAA}?_@ZXFn{){WpcNjW= ztFFy16b6G2a}2&!jU$DEv0pBt-qV&{SBQ8@Q5Zg3j{3l77a=JdCL5~zRHmg~ByGu( z%&xA4E}VzCvIF0%Dng(@8DICPg{38vk5p{sH% zOm#ywTK`CKb^TtlCh2F-g+aCahoK)@K074;%&`0`wEP?`KdWo`ccO0UKonvR3kkh| zg>u!lVMtR)v!K{gk`PR^AP;tf%KM4|@DKrep9?`WxCD0CPr`Cqz5XsqZ<39Jv4gdw zM{&`T9*~ma$zf?|N1xc=Eq=|?7&2a)8UO=i)eMiRjHBi|j9fAqnzaUWS!|!Ww6DJ* zZs`5I>d6ip`xpriCi=KEjCpCNhf2fHc8BixT6I-H3L~PW_{Oy9aOUOF_GN0vz)qUo z`4_QTwe+33+R;k*4(e+#R2tj! zwth%LR-zQO4WwNGrG*l;LwZw8`q|!$Y{tg76W@cN(e?)VnW|{a9=~Q(FA5c(Qqh=F zeW5Nz(e|ZIxy_^%9K%17byJO~KXleuw5iWOm-YSv^40g;hHdJ#h@{sk=>tyU zO2ts6?g@*Fucdbjq$S!&|2CfEagNtu&(@E$6CO+cP?xrZrvmJRtI2M`*bxiZ3Af;) z+xzkPqP>rVNG)JDl%q8U!V34jJJ}&)zaehS7dboiy^BNH(f9rd4RrRqT-ghB_D6K~ z%ux1IW%k>Uowph7Z!kBuPoY0H&5}coeIC2{PP-M2pbHwy1*O$u$YD*NSL;v*R5VK$ z^&T#|qRmC3q9a3^{$NK|^l|dTU~C_q{btTi75!riPenFef#t69$LR8}q~d~!M}*2hSeEan@(-o* zU!-X>{OmF%{j3&xsB3hOQVSxIiYdvDBzr5njuSItNOkn9Awan|H0uhk3u*cqy^2-Ue~x^nl|xr=n}eYoffeK9Xwp&V-Cg$RRo0sT1#s?e%Tk0UiO7(emQ^1uBo zQoBdkk7HxC{2M~@uM+axL*{;zbEjE*Lyp1YZ7_B#u?G{+=KM6`Wry-(#CtF#pwd-* znlAowoqa&4_ya@Pd#a1LCxyM@7%VT;%1@z)x49E?1HYJnN_?fLBfk2p^+Cv?=dn0b$=O zrv4p_eL`oSg6z7fGEDZYKu>iKHD_oLS_W&_189l2fn4b}>gEk}cOBiG0i|_GXHZE& z>{Xc%-9cU-jLp#{eT0ji)V!0Y70tp%mKsY%(O9Y)amSn1dhD}%kTt$Sv0C~^pAci( z39X?j*3BAGzH&Z7Xy<0ZQ7^JF++--Q4Q4Eu_0Q>KumD1ml1GE3jcT=@#b&*ZV zks(u5fckbYaXRatHsu0NptVo}ErYOrTAZAu?uJn6p4lH!J$8lGKD5!S?ggm6CQmBI zgDrC2IL@z5hiYLV=vL}PLNFR8^L39p5?3v+^{+H_3cS_2QyaRQR{7?S9F_m9H6xYZ z=8lzkJ0Zceb?0}f@>ftuod>cs4^&pgP}D5xcnO(cAA)e+`flqx^=t~Y6D~uxYbMvhUh8(qARAQcx~ol~wT=g| zj)^yNooPe2=sc?0E~;}k-IJaqZQ-Y^G!oLzBo?zj^b49&OM1SAYMVGXB?<}@XZLzTL?bn{EBi06EAZT(sl~X3dzYR z#OzXI*oE5EbAVb~TkY08MBDKOsP$1{!j8nsT{3a=Vw7zQ#!o9c~g zJ;~1F_G?r7$s6mQtf#K;8mO}jS{FXU`D>5ZVZwR`R&iD@0X zjIVA{{mByWOSW;^5N%_1kdwn#cTzQ)M!iC8fiKNO`(zRvj7^_IP1+Wk?|$fx{FiuVR??}HKbZF_Nv*D^*v3OqMpZ9*O&Cu^{qEpoLUMaQ#}ttS#c&Zlc=NrTyujq57_WpX>WBsS-?kLbrgrQ@a4w_X^Y8r4ZJszH_*Q zHnki4x`cFHLTb2#NxFn(x`f%L1h10=-uw^(m+Bz4kT>U3WrDF6oKxs6ib&D)IrXD|af%v(qac{8^+e9RL{9zu7>co42Ai~O_3 zn0gWAyn_83L19a4nvOrUfV1i@NmB2l4!B8ByWxYliBm7aKotJ-!9v$Dqz#s&aq5`& zbTKrZYSaCT5KLFzH;uDZ^(7>jcp6zDn06GqOPh*ya-cIUYk}#WXHkxGvHro>lh2NH zu3g%1sY&mKn)cETD!)yg18Mf@L~y@KqO>_Wjd<6Z7A_-v0ON3Xt#Gzh_y*`;CcPUV z49zx|$_^%efeR`P)pySVz)Mg};4-0!7QpqZOPduIP^<+Ut_9p55^#qU(1WY81w(V| zK_ovwx9U~ahD`eiHABr8*sXm9p_$t9>zx=u#_fV~HA}3R_}Z<&Q}^26S6Ei-(^{8R z?~)19UesZjHaP!D#ROxorlNz1$AolAl)6-rP*yGh6v%tkB*y0nLP8ilv0?Qc`ri9b-;!L;{CZ+W=+k4dv& z>=)A`6~2W#GxnE}oK<&el4?aR+$5;4k%F5z^)B1=dvy_%qlreAX{+bB8|_i&0&m64 zmq&{uK~PQZ=0rBcVWx61#CJ2Q+xitX(QYH=VsIM0Pg?l_^nQ`5XQJj-b%NG=6zgrG zCOX_FXkarQv%)lzrkJ|~bqecF{pLtkevf(tX%uCzVr6!z{#rlvGds%nDj9U^CUR5s zk_DWpO-)0l*1I@1YUdeFs@0_(_l}7U&#Tkk0uoGILh1$6&O(z=DW^cH&hay)*Cj4N z4q62VV`uz^ovRWH-wJeBfy-uAANE`D(s6!6Tc+Q zgK4j6+p5PPT<5rib0nVqwqyP`r-jUa5#+-B$CI^#iBCd=Hvi*b37LN}`wM1tZ`tFV z7GQIXM4jwo$mXoQ-57fdt!88)ggaBYA3JS z|2k`^8X;QEeS_2*H}k9*OT`1I-h^z(SWB;%A>mX?*hf*KZc`i}brV_{uhBdVoK4xL zs$*;ar(rzCf!wmTsX=jta2-S0?+^Ds$&mmD-5v zK;o)dk#5Cbzu7*SS}!>6@u?`eu91{MPrZ3kjP*h_?yVT#wrT>Y-s%x(%pL$8)i$WY zZ`Idzkgr?R zVbHeqGwn6mb%T=-ic}HG;5W7S(aSdC48}HR>n`9-YO1+Acc?f@rn%s2tQmUz+td~` z7v%njGG~zq=;g0Z)$wRGsH1(PtujkxH4`)HQ_XTvpJljF)47%RsKb%9wE^LREA_Ve z9DWUIYT!FAuXT;aTU%8QO4Szs3yVxV0~UwlL-p^lprqg(#A^V{*_RW?ldXekr*fs* z)Lk&2&Rooy)8@TyqR3XY>@{c@Ok59EW80gXE~o*^%(@S*U7C z$c$&H^V(Wo>rT?1htIrV6fZ}H^*ln|3WUs@dhE-e-bsZm%BZ;%fw`#euonZ zrKo{MjiKr|$Z5SvHBpfiu_+rk(KgJk_Ha3jA-=$->MbCu1%|S>;?-x0%UqRL37N#L zzC}F;qV+m5!Zw(Sytbx|ULZS{biA85PsMA{(2mDOJyhIE?T31-;T&zM4p;j-bOnlK z4Ir}WRwbzgus?1RREb97)GXMLrU!eGLf5S!V^0AKNgPR6*b)1|>=`SQ(|tAD64Vi7 z{WrOvVe)gt8wh?2+4RoD_$AI5yJIpsznIC_paN(g3iq?AZ=#xB6wYs?0L0Xb3p2r(rtvbV?mYsc5eFH45IO#=dM6MR&j9RW`1gP7M(3j`I1%2Qb#BuNzYVe_i>TE|Mn>`O=)O3`j zmXOkD%6Yu-Nqs%nrPuw(^h+IQ< z_y97)6A`Kp+IaFr8uRw(nG*cxd#HkHnhC;-kPV6^2!BPv_HtNXH}^H9U|rf;Ojph3 z&8^!=VB)I~2v>P9Q;*0uviWG@OU5fE?P>1PVt-5Cj8^#!>e{^^*UUYzNzdh#P@6ELz0z4m26UKc->&Lp-&*?H|zdsQMyqb9N zLVoX#miS|;Ns}$+3jXJqKkW&+4Ex20dFOLqp5c3Le|KNZ3%=KX@nfipU19{pb+n+W z6T|uG``hYq*CVf0dBQAT&A8p;DG~)f_0xw17o3X8{e3UMdkSHs7h-8y9CJR(4wX;& z?PW+b`lL2(sh>Z8xMNO?vc2$D(>EPCW%#6FF5+MObxpoCZ^U1>xp#9-dkp%>6%@Q6 zQrjOi#jh}3^DL!X$Og;{J90W11NEx%Kz>JFS-3!V`SFilafg&g-*UW(Z#lk$RzLZI ztCHw#(B8hl+Z}JMX{tQd7hK^XBnfioW(Rg--vZw__=4U%vOMgT+Qi>yg#5)9ob2({ zRQa#~X^MZeX$y&l%MRW`u7ncwr_DpCZGu%p)k)m;^mz!j<1siO@AG&XzjN;STXef{ zhBf>AXZwPa;`UYF58XZf6H!W%ucjv!Y0IE+lfS30W@wDRZ)y{ND9v^WY*S5WsMc1r zc1H$^t4j+~zrnk9o6wDT)M;%-;%=Qd-6YLd818?e{7;ATH|qSUI)5LXzro3WVMP8J)cZzo-%RtKPUWtO^Wh7W z-fHu69UXA}Lw&&saKoCa9H+~1zf?1tjFskYk@b=C+Q&(GG&+H=U=!`Eut^t?iFBODxbYFzulAb^d0~&(ueeyuhC4bc z6F_GXO}&HSfc?`mXpV4uWXuG|y^sJ$4IXtgTXbO#^@&5s@i(Gm#JvI{syyEb zRkMB1b#zzxC=%mk=u@;*^hgY<9RCn)-avhkZ2y7Atx&-C1#hJIjS%l2rQN5OukOG} zKayK|%Y0+DR}S_CFZNK_rXit;h68v}sQ)5P0x-TFU|KjSYXIV@NEMD6%hpzNX-LLzJ~%xwFb?KsRGs0^F))Z z$0ybLH1&(;Js1d($CA!Q_Mes7fe=NX2qyWq^++a>stSgq`2-eXOfMw5DZ4H^IW8;E z@;Ki4w4R_ph>^!XPkQ0`Hf)cIjPV5*(-(qACaan)D3>O8pVJEAJG3&|?J%_4nHZx@ zU&jY|p?cIjpG0F-#ApnCO}wgEh3>N^K%-IMU!*TW%Ij2PI0hnW_ZVOBX&QHZfm`{4 zPKhxGqd+{h78j%Kp-DRvy@lJ2V+S@ptc_Bodna?u{g4MayTkxHwXMTKX>;t@a+#}f+ zn3Pq>(E#G!>R* z<^5o5no>Uc6y}LEjMih}Jnn|g&|)N7z#d1wA9jA&m&PYw@R@aNyHnLG$Vdx8)RYG* z8tSFX)Wbw*0dg622lFoRKqX%=)5;F!%~s=JSIhuoGiTB5Lk|hsGCJODwRKzyq8_dPw0A-x`LEFD;7iKfZRVG3YC>2D?8^$?zR+J>pQ1o#bIkSp(K1z}ou0 z{R>9(dQ;zRc6LJVr|~*`t=Ws6 zty53BR$+FI&xG+Q_LFAGh^AiCMc!(ZV1-5j7#eXd zPR#|UHaZVVY6|54zDrd<2t=Ap}2H3bw_NrU?6h>bP3TI>>T=R zUk#p{TK@TAe9l1E3P3c<7is0n7) z?Jy;c6h}mkiEM9YT>tfI;&Wz8jiDIejE_&zj^yJfdT21rM@1ql{8ywpL*!^ zNJmF)Q)OS&U(YeTu+9>gvQ70MNn`G(xuuyh^4tdN!QkyY;Y3(zVFfY3)`vp`=8FjU zw4M*KJ?^lve+94Lu^MLQ{RFeXO?UQ5&;tYDM=0G=&*S6SnSoa`W@QGpI+0Wl-V@2u zA!HQ-dph+#T0EyiTXY3Xn`Q&{I0zL!Y;A0YI-EcaNO8R zh*6tAt*t#)^I$37f#=HGGI{E&lZIll%X07>oAw3O0ByX=@+=7DK{gXCG;iWKbVyd< ze830?e z69J*JJ1OQ959#wPioig?G4?|>^&uqDn=sqdL*zDV_Qu!J|BOfzG**@soQ3EEH|3hL zc83}ktpK)rD2h&{*a}xZjCzTeo4{+SzNByP60%eLnl}+3?T_#$)ShH!PP&a_C-eCa zkF@7HsO^k*Aq!$v(3J)Ek{Hr)4Lz__b?9;S8@O}wJF3(c5~FOkHPb1>d6dD&8LG)g z$$mayk){pzKWZb#P+5Wg$x{PGx+y1o|kJRb* z{qPSF&wPQ$XwnMj)$#M$2I7A-%)cU%A6*+E@?=bjvx5t;(}?%W8&Mfd$zpvqnO00z z@Tp`fGAr-^U&)E}N-xtFq&WfB$9f~24B$st#;h3iRx_w)XpUCFQTd@7gA~IC|2_d! z>m#U)(RV%R+cT{1%V>C6zu@Imzi;A8CzavP)zu&{4T(P(;a6C zi|_<#!g{+E)r*ja$;{a#YH}R@Orvdp_=6{v`Qj&Tjz8Fl=OXRDQBEhSf2S96t9WJ= z+8-W-FwPOJH|D>*V~h=$6HZQRodR2*8gVi%Txb2_0PXiE22)03ZdpdG??kZ z?*f^jI4plIkIfJScvg3f%rp(&Cc*KvQrSWuWJlC#Ak*Kdr z6dnL?fC32iLnyunu!W9?nk&i>t}#r08UN^Z_^16nYGuYZZVurOv?(E0Kv@68Wd|Q5 zmz#<{aXh+nT-KONELdOan+jZeg46ED=zvEL2%rU(l5xJh3w_&}=kXNA)Ff;n_OG8Y z>3IvilKVPszBongK)p_<9?3Hrjegld=ukpQOT(*CY){Z`5W*KJVCSKDcYSA>CmrEN zi0ID=-rBvDiKdZhexp|WwW9-|AI}lAtM3l^1_D6kn`#aiT3>Pe3(>`S%0bIkv_%4* z%DzBC;vdD6kQHSr%M1^+9Nog};o~@VJgFyM9sYxT+iK{Z4(=M*zkAX#0VUEs9h{hk zM8^`8NcVK;_(T&muE)b0^{mabhq?=N>+|*MX1d-U>L0$k$#F7(uzmyX=U(B#3kPMY z&x1t3@J;?cKJ%=>i=j34_2V$5VnFzpyTAEr#$sZ``vZ7*gMKzSftB4hIf<^={|cPr z*%EdTUvRpIA>;oh-bG{*@o!7m{$qaePxjSZnuKuqUHqCKd5MizE>7J5^-bJC0K{Cx zJk*IlxI1Psc%Y9?9?ZlZ2cD;(W8;c?I}fGm9fge^th6ZKUW}F&D{?&VS(Ri}_706t z)WNX#m!KZL4|+hZG`3z(Uj#d9FQV5A@OTMNi-N1FeA~PpHWm5-#lk8kXb@Tf*DRxs zKyX@Ulx%)qW^$WQ0m=RSaO#OWXKlOKjS^*bpMyA*gR!}U}Hb*{PgYz|jCUbDP+Ac3Na zeF0*fu&%b(9|l8S>OLR!yKQA!+T?mQUT0Fr(=`~qb1nI`{^Lbr)Sh<;>qPN4?5}DH z`4{PWE#dX-Z>^7q?R5guTdxY)gX_1z$@9#;o$5!sVEzopt<&)&nkFqPt;a2^C z79i{*^kjHcI`Fb*Fx$1}o^SsXHsx^x8;M)&Q?X!?PorT!Dj$(IY8}xZQJ1BpmmY5y zk>3(Fk1%@PM|S@rU48u*B+%nxvtL&^k)BNPvtU2%%U_wS!vm?v8|Ky8Q}N)+--7h9 zSRqmPaHZ3>Sj`I906&YyRey;TFjfDB4cDfYQX4zfRtkL(0_{OsVCzPz`!p<9us@k` zwy&;7A3f^*j9q>VJ+I7|NoPYWv(9jXz!lwrAH(OwCljhINt7n zxU6CS%np2$jkg?!?Nt{cEi2d`nrzFW2Q=9+cq8O(UrcjrP1wKAtQ&O#5-_-b&SPxG z1@;)^3;9>ZIkYi^HLNf2jdl?Yg8OZCcBU-ON@IyQCQxWaNhDr?gN;ZQ(BX zl}o($QTr1_g^Y!l$&8`mYezscvnJbxTUwK~-2)GSErXL_v6KuX!J9 zDiUO`0C&hALKeg3iA8n6!xV1DZ>YPH1?WGLBLmWc|7xR5W-3I@PtV#mDV=IcYs57Z zSpKD6S6i)_37ksNLt&u1eYIZP>G(}*r$cQjYE56H@ONurf2P2dMCq^#X^-$7!$0iq z%o_u{pp5$NZe6)fT5ZJ25!z?LkAz}qsQB8wltfOn=1p3I9)*UP&tD_2hcJ6KbV2-6 zHxqHHRvGhal=UvF6Q#KyfE2ZWy8!Gp851}Bo*4aLNl4!|<%3A>32XNKj5~PIDMo$% zF&+~ab}0|gENZdbaDIC4ERIy_#TYkj?XkFb`#KdMhd-D!1Ux)Y6tR ziA6+Ezyd1l%K$BNq0qeF-`dx8&PiI-`}sWY`~GA4=~>sF_g;Igwbx#I+9NOcNmPC) z2%@T6Ztv}&KEr)J?u{SlZmfY|Aj~&f5{OJn@t%VS5jj0b%pl*Qp}*fL*Xf!5rA@iD zR-1aB1iiu26efLw3|3iDe+&K#FN?5U5%yFbeQt^JYaxV2R-`rs^Aw5kWfa3bkhxS) z%xAF8w3+omyY~<3LilN5jtXP;2*k=FRz10=)|Q2l*XfCK4rBDPmEk{q&(@S?O3IW< z-eH)H1Xkh>Fo{ib27}X|nTs(lY}M39_g(san5xvlFbXs z&ygnU$HVYWUoC;9Pvw7G!TqNeK`p0xZ96?BK6)4&2|Tc8@Z`VvJ_p zP@j>2oWMejmUM(q(={UtFzscXTheoH;)nJ0Dk`SY>1Ea?3)d}*62xn=Gy$cX! z$eH^}G4?ychIgU}T^e&oT0sa{<|cl5If@+5RLZ7B6(|`D-l65w57u5@p|6k1>-+`% z0NIk|Bi~0|`)O8Qxh1RV+{TQ)>(0YHG&j-FP$YDb(GVnHza<|pqTYwPTwHm-E;`Gs zZ-zL`tVy0sHo^r*D$4)Hr>=6bzR(8qBMF?{^EPsvJSrIRq~K$btw7vse~IVik9YLzHmQ=BL~T!sqQ?9nsI(7a|H z2ko-GFPS>I*nNdY<*uS$lyMtbxzEeJHmTho$9(%03jhc@1jdJGM#2R z9fv-kyHUEflE~WYJC4*^7EfgQoDruoRnLat(P2IO5NKuN~vHc@~$!ZMJ`Uw z-}ewsN6jea{8aZxY+T&;41AK4lPsjpwJNBn^n7My*W3hczH=WxD?9e}bXXSeIhj=E zSr+eJXgRPq*(B%ba!sA~(;^;j>AA^tbt$_ipli)2E{S*heWqJ%bk+hOTRzwvE)FVj zr&)bzuLkPgs$m)>lPTsOVnVoSv=Nc5`$f7?Y7fiA8mRpz5^QJPK(nh0;PE z`atcH7P6KvH!iG?QC`ot@d|F@C)2(mxmKoLH+p{6{q;Jcelmle@Q9~)XvWKECu<^rKiyUL9NZ1m=8V?{Lp`fW$MmVJb!7PY^>TG+|6h3csBki zHq%s6kBqMplrxqIFXiXn^OiKy-f3TB0Lv^=cfCAc>USArPzEVBL2_WpluH+US6c1; zMyWrsQFD`f|c zxMMyf&u6b!_4EI701N7D9#ai_r=6jkqgHcJ+t+o!oO~b0Ku29m+;1)YuKm!1KSF&s zO@8{D+3J1SXH)7f|B!4xCI8)I@<;RcJ+aeBV)`Uk{vnyXOSjPt4@7;tYsY>vr%tc< z?8@Bh>h0b1>`#(ZuvBZ||Ev--O(z=`|r-Y%q2BJQ*+2hkti}KxC)u zn#zidZ6}C#|5rW;P3NCRcxCE6*wYgYN6Oe2vPqf5T=_RVF;Ci4^z;Ie?!Q4_L}FRl zeKZVyT|-AY|F5!c^iEviqa@SXIzKGWz5jKe$t`A5&cEBoT9ApwwaUc$V;JiwY$>+W zXLp_Oyb;8DV~2Na>S$X*OWYlz>fKk1t`|0eOQqK)b{bE?^Z)BsgQqTWqrG6==)co91&-sv2M-D3WfopwR3&K)&Xo_K_ z{t;c{-m+8U+;?@g8fGX&p=FG7??Fcz5sT=f-|$We*h<=9a-X~R8jrM-#Um(UXdlO? z3!pO+a$(1}57bnLZ0?WSQfIZ+e1x5~6_8SM4z4mRRyF1W+Z}%j z$7Y%$Ot#Yu)(k#R&7~OxBi@e!ilR7^bJ- zZvD*E&+Ga*m`)0s2-Qsh=x19o+y#dz$u|{pw$ja&^Uqp|xf#h5+=i$P_R-Hz^fN|3 ztMzk%e!iiftMzlGey)&`_ho(kLQ>1u>rmx&jDChAKm&7bCK&s&Vv}69jcJa-9Br|?1^TodIBVRRQ>wX=X1V$^t>?qd>%GLR!z^0$7htswq z_t`K=I}@T!;-*|B+$mv@CVTPjt4+SxwCx-UghNVV@$R&I)B9elD%iB`OdBs%RC+Pf zwy)m=LHlW2ya&e0%+^+hvo+0w;@$Orw)R1xW^40p5XsKgisIcJD#j~){w~ezHJa`^ z$y``~5@#YmqUoBRHKOgBN$uz7Yg?_Z#Ib!@Y;k8wA&kVk|J$MR?OJgrp3}a z`>$U0StGB`mn|){M3O5!~QU<;Q2E{zIJY+oRne+K=%T>v~!?f`;wzMnwu!PCTt)*ESbM z_OggDHp*&0>TGdO%LA)L`fkQuaf3Hpe8*h$g*QmmWX%?0`@O+@ z`0y9)ki^Kh`+3Sg!nMUT|g-a zN3g11ETG;MV9^9&ziXx=@wq+B{O7>_LT?WRhcy*xL+w`g4FZyrpN)6FL_wKFR{Ewr zA@vQJ9iHowP@uL86<>p}uyLDcpNXQE5S{mRvQCCHu+@GBMM8{&SevNsuGPPajx#QjUSffxeyWd23$s+JRL{X7$ zPE@4NA<>32dWWBEr=?IlxGUx#@g8O+M8tmR`%6ak{TO>Hx(3)d{}*y`hpbW-pEE=r zZr}4DPnMxsuIu4ebA+VXy7!a3?46#hNYAV4KOky%y39mYcb-D0Ru#0r!mZ(pK~YJr z`joNnMuuHhQY!7jk}*#tduMM?cD*|)e)%a@LK|DcDki@f@3})|r$-E{NR7a{y_OSM z8#}j#0=r2{tc*gA}Jugucc@^~yroZvz;J($P##~_Z_Z_~q zje>R0Du1lLn~m#Gfvn}d^OGD-NcQg~y;%gxvQXvZ=i1MxOfM-)j@<*fP%(MqrGGlGvf4bDo^OEIg5RP$ zxXb8XT{)aGJv|7$_@nn4nm{V%pZq-;e#`mABfMrRyeB0^wz$7B zePW+w6^C*zV|~vK-zB>)Vjs>4C@IZ&F=I|@Q%v55WZp)15|f=IG+_$~3|ph(x6Cab z@u#L@_6Ty0F+(MHfSrN<{>=WT@^dRv1AYk5qoS(TB=Ev4keX|^=Z$!#jYX${v7Id9|5Ns{{1bHbNF{Byb|Mp~obr4>J~I*+y#x=#r+(ankM%xopF31F)KL=e?;Zvn!4~2LzheC|E-W-(#b63{2Lw9 zMI(tmw~t1=kUqW&F5G(}buZtwkC5X(lI>5r7wG=}&AEKGf3oXj@grsonUu>=9tU-C|@|6TCQ>l4Y|<)hrA(uLBOCMr|MoQJ`|ebI2{WlW!P=|&N{RCUqYovDrJMXQ>Hp!DHm ziEneazvt-n_nCC#hsih#!Os%g8jdz+G{v8a7j=f@mHl zVTrCkbWXoijLU}0%qcG$s*y{Bbn8v%*70s$Is+vxIc9S*#T?HR?0gA6ZXd+rrLQ!T z!0RD2u!@F#uBqilZzck_$<~L0LtciZTvc|4lJ|O5776lO>ScTq>a*-dkUr}=w^sCy z+f$bailu+U)!B!}a?E zq~9N)9!8&9w;TQbHS~MwQ(!o+GLL<++1`)$?35_f-a}uG^m#8ty*zg}(EY(D2=|!2 z3zEp8&Opb{u=ek(TcZ0UNtlLyUW=u`wh&$djOc6SJ*(B%SU+}j9KA^+2L*5Ch}0}i z-A7)el)ADaitp}Rp7jE6(SB%7Hn8|<`w72A4cXOXGjQ^_=u&ffV^o(N>_t%z_NEf- zPe!@=!aM$Sw-7e?a1K{SCR4MvRra!%C$((5`yI4K&XdA|5^-_8<*`0w zlqA6D7-2#~6?<@2k_Zt#?Z$J**N*v);{9$7Olo{tL}%JU}lmQjVLZmngO^w5O-d zrLYNa$45^uauvp5njpoye|{Z|uI_j3BKFju?Yx=2g!p=gI6pL)HK>5d8vN8U96Fkb zwaW%3ZPx|?v5`?;gUyOe#v^>jZn$9P)F^W~w2URDfcw*; zpQP_fd8OU;8wFe6nVJ{jlfn8YDzjsDcv`go2JxHQht~Grm%*o~emnIKKoLSp6|*~h zGxFzY1y&G963Oj{arfUW_IvZ2!okMi7a_EzY}`K%-5WyqQ-kY+4}iJrJE%C*^!%wS zp$Q>PhUdn)|6Boa0xHw1MVA#_?}NUb469ml?fx|Vq>~hA30i5!Y;pl-87juyU&#y$ zg|lOsS2lk}0u(kkb6FH0n}w2`(OWr@ka3)bs6@4yyt#9pR*qQEeMR=VtrcB6vDZC{ zR^b}0|1pEPdFmrn2$72BZX4UYJCXq6SB2+_fKT&G z>V&t^9UuocL;V`Ad@p3N+Y5na5bRgRY>&f6_e!hq>uqPXlwop_+rpk`SB2R=DxrCk z^{X~gA_Dd|39wFHCIQy^z>3si#a;sVlM&aLx6io1XY68v5v^6o++HD?kRI-Zz-Qs0 zv9Cg+FT&K7DPxr2@9*d06YcI>ALNV&?qM(y9Php0XJvMU$%&IH!h4AVP%+^CN{jlx zCwcrQzJeP3ROjFf1>P^OPw49|MPuD~eX!*iPN zBmbA=!FC1!G2%ACh}J$#9vsTksqT**|F%516HNR&^5Ah=h zcDzH#(~b)8dj`LZ?D(F9YVWiH1*{iaq;qs!&JcJV_gqyVy#!6xwt*D5NygJCb8Kfq zYO^TGy5Sr$(W+{?xY!*`gxQl{=7KFENORv)9<21``x|=9@?K{K6U@`n`_@=aTGq|p zhO-hs>B}n^iL-oo-t)WV!w#w+lp=9M{f-)~Bb}B|Q=r_IPSFuehY99p;~|oYCcMJ2 z9eC7VRh^|U-JBA(HLp674>?bA9wGe78yR1-fi@s)w|DJlFgJ+ zc&j*Ku-^R)OH-lV{g`;i7AE4|`F#l!fX zHx`*Q8XZ3=%x{`m0)C_Uaxs;%!Et7hD3Of+%QBvh7pS`3WrVfT^Q9yYHm`@7D3&QCA2oZaJs*}+kZ zUqpZ#Lkax8h^2Sh`!CQlw?pWaDI?$DEYH8mb^kzNzbH@JV~4EQj*wM+#6iif`ZBvocpr z4qEcKjCyGJ{fI4YPrPOcT`hen4E{Pfka#qSZDir3XKSfj@Drc7M~z`)x0X?N9ru*a zQzETFVN_5DaeqqE;Bp}$v)^VH`FjI~j2!v<2#6Rgf4>8b-+L2%=3)#1+CR}d?VRUu zj$V=@eXkTJ^iG?mfbB@0LDF{>@DC*zA7jvZ-N)(e!SXnFU0mx!?t2S_9xpd;-0i&#-4KHle?AEcTs&?UWn{+Ida+-X8f7O4E=a{eGWZ5NM3i=QS+?4 zezDe^k-WZwrVo+V9aJuo*NaIU$?L|iMDqG;Aajts9?cv-lGi;HkdfEh4Q>CIse zvpZVwU(4%ChCjELRn^PuXkY&S8}b@GfORU#Yp>OLd0jC`UZ=rzaQhH>{X;64!zW%| zKl*!z zyy7QawqW6|s1_w~)sF6Rjd!mVFKF*qx&~Qp*N&x^B((H=gb9sHbu#}~7D<+E_Ejz? zu-oo~AeTyJU<;MQEVh_!XN~Ci<#P7sA1n}`;kcR(R~%Pm@5Iptj-Mx>t5kJA5utl{ zx3ltF=gm$9vs2r-Y$vtOGcmI9-5N-Kt^0_`xNAdlh+d_9%H!8=3_gywog+S281-1t5NyxwG@68Ztisr3zs~oU>#eNlyy8>^Wp^*zJ|if&!4c?Z&nMw z4hFs6*#&kw)`WY0n?>}!m$GE`s`~+bsp;EC>dntzspPISL9`?hpaIzYpm$oupJ9RF zkX@*XcbrFgC&_cGlBQQmJMfF>m+MPY;yilVcC6clNg#f5f5-WB#LjvRpdVPA9y9>F zF2yUT<2%%orv-WuH6zy^=Eb-BSCTKAqH^Bz{2N7^p)i_{_D(xR8GXbuDhutjS5fQC zd=Un5Kl_{{)oH4+%(O>bV9rR6-AmUITB6W!o#tjLV{+`YBDb2v(S-5r%Og4V5Ymm4 z0?|UvCRcGDxrEy??q*VJ^F?kUnxL}NpVAEP66S=wkL{pI(pSwh} zILh+#K1;ViFf&`^UKBLW<{abfIC|--r|6{$tDwD)8Z?0(DW$*kQ^bl*d#bJdH@Xh1 zTysG``vteUg}C~!q6Ymd-Dmj@Pz45Dm3)~Zt`9c$s9EcSFNe^!apJxxbbmdBmxS;U zpr6Nw_SW!LO51>+Sx7E}XnF7edfgu)ZFMWnt-A`isv4XuZB^YqB46Nsg0%m5+Y^D2 zL~B|!&6}pj!6i)9!cFY9HnLJpRpjt~Q{o;XG<5FoE`$!WGld<2n|wWa(e`;pRP~*| z9SQ@0`yL3eo+Fc}^L(MnhIg2xS~W2fmuD zvwYRAyU$k=cQ9XFNzu7{<<5i5>@b%g8$yVRdRG(Wx{uG*?EzF~nAD&xvv$r_Nm&xp66o=~ zGvXYS7_EL(?*ubY)~&{1W1}?adNm8V$PU;?)3p{ z6>u>DgC-ws6m64_>x6@lH)c}fim6Lsjs9Wk@SBSxws=~E($!tOoje&)e1$7g-}Vvc za1QKnhm)iCgC|~!t(DR?A)q5OlvYUH&8f|h-8jvPm2!o3_APi=GLz(zM) zc+bug?wM!KND5Sgk0JAfN{07no=~R5DSuJM$jc%bpz%&O2y#)EWWXjD%#jROrj3y9 z0tx`iFjwF}O^6vx3iHgMJFZUT`!&?61Y< zS$+=JoMqNI7biLE20^j$fL&a=u!KLio_dMOW)vsQ{VUvZRmlat-Du$)f1GaEO)OCNcW!4?JK==i`m%4F7z$hNy+fcRg~^@BUD|^LTQ$*Mz51d zscVo*wOg)CK~{zqI}Wj+rechj^*n=2kQJvPLHLxJlc1*JsvN%DR}%#SN^|^JP%*!f z9KVhDx$9JGEG+4GMfxBfmQrK7%Es|V)^Hx>o;domN;k=bXk}U||LxGVNm8n!QED00 zuzRZZqcbYH#^-e;yOxl?>*6wh(x9U%HN8|CrV+MwnF+CoqY&S+kCeGv&nDZh4U2Q6 zPoF610oGRr=hu0w3lm^&N-9{p10^I7{` zAN&lDhj5HgYlu~%n*(N<_t)--X85x;||Yv*gN+I160Cq z_(nhp+0dIdRg}VlzE$1-jc3xf1#WfkR*B8+^aNWHT%lswfCd{cO0re8GF^voc*&K0 z+T;Od{(5Zvz4eegmgyo_fOmn1&0KpPi~E99i=xk+3lhB{Z>`*mHsvvj^;{SFG9g?I*ixX7~P4*x${-zIC9lkGx=rp0&%* z(4`3A78$+ONq!1#qr_E)woya`(JX_ZC2uIHjFup6tG3%g>HRM;=yWR@SDrpt?hoUn z0-NF5du)HC%lm(9dUJZ(gN&TnarCDfb~}!4kYs(+#;%ph*N<0TKFKX@g>{JUka6_t z1wwA*S?07F=p+|gaD_3FQx%yqT}hUauFBZ!EYFLJsHMZYqHRB0SU^+*n1v z4a$YfgJ&)kitJPS8N<)k?B}xVXD6SUQ?7z5!ib;8E0_#wv-XlmKjpl!zuow)Iq=J3 z*FmuosOr{!?$Ok)s*>`%5&L$!+(wWDj+i@+{(vj78TFikZpf!>Oqv(?vk6F8dqR?T z*Fg^F9~DVY%Oaf1l!Nb>^<<6N3jM0{7vKv4lz>lGq2m#|!w+@|Nul>l+Jr;T(EI1Y z^9zf2S%paOu&`pUC-y0cRk09 zo7Wn-{}zk}hZs%3m~*(2apojjAeGc(zC1|^78@4Pw{>A-YC0Ynjd_PvW+u4K#wjC? zkRNH)T3>*uEt%dHvhhCT4aO6yDnmr;4&nx1>`ao4GSxA7&qPRo(P;py33Ecc07 z_Y&N`{igc5^A!GQCVaxvbb2r{<5oZQ*glQkc{H4+X=PaR^?%iHZoyZaf7)<%2!2?@ z8N3u^Xwv&YWw@=;og>_jAc4rIiRC z;bCk@4mbBoe;`zc>WXzm&%5t~>v0qvY~?GG4~NR*vl)E3edInN>)vq&?j7XjUgN9( zJMJIy*bB;W&*`g3Uw_os??@y8vEpJJef-#s-@2prC+4u3?4{YyAM%O*i;3}eW`@xj z%-pLGu(0y{Kv`?$Gp~&o<_x%-rYGqv4H=OPaM#j|Ty?n{H^-7WVclM4 zv(c`6Z)lJv3iEnngeip;!wIoTg>C`hg+#L!g~uFNyG0t!APRDby(^h+jdy<@PtH$d z9>X{==O}?^hARrx1n!~;o1_hy$#mB$Q3mXl+obezw&X(!UJ>UjDcz_<&H{Hk=$24H zGiK=j%w8mid3|$B$t>pnjxHlL{aHa-NxYTJwu$SKDe|gvYkeeX@RQuHYO+^IZ+TXAk##3Du*JK_S{uU~JP%d}m8gauA)mCs z+~~LMNf?n5io_8A2sB^G?jEEmJz+g}h#{YQDNVQ?dYW2l>%vvi7;Yee*dSr`=~Q;1 zwaK>I(^f(QeQ)85>J_y4ycH3t1eS*c#n+SG>Y%9IfH7M^#0Z6pS@gukxJ#v zZ31yidGYQa+L=55+J$Zy&JmDvtcJV2e0Vz~TG?CI>VBj)rP#avlKQ2}y`(O%mw4S> zSNeHam9^PykXm1@NNXx2qCS9All6nzoT5YvH}e|tL`2U}7DH_f$e-?8)l+T*YjoHp z-aQ%~V~HnW_7W>;faZ%1!D#06Fq69qh%|Qyd4PnP5R?h=`&UFf>Pg^X0>l2=^)jLF zT1uT~$AK%N1=WY^-pMa z^B9C7Aj;KWfL>TY+{SL*HwAX$$O}9Oli@`4!QTrDP#?8EW;VY$a<+_Yl8n>F29Fkczck{ZF_`-~~rKO<@|(OU*uYT7W_Q>789T4kN4d@}3f%BJTpU;t?--gD`&f!3u7l`JX@&4IT-;?||pZA6?Q zddc5=GeBu|g|wSW8Qg0-i7vZpGO-Mg#?KK+MI{}jlJK-a~lv@TV;rizm51!AiD5U{btmZJrloT5+34 zEM=Y*;J@%W+JF!$w>t_Yx8vO(!24+6@$N19Z*k}lHq*$`GxJ@=l^Xccv|B8&in=bc_8Mk`3R9**L;~?-| z+vVa!$k9)_Yu>?)wPAD6#8+r1_Z=SZK4&|D5y|<+O~=bUR8A?A`SS6>%N_Fhsrkgn zYxdjL$WXrApunY>dJ2!L@Ddfu@W;rcN(?UNLA=}oCA>GPmzyK!gs@IFf(5Mp@K6{%f{%pmM?)C*$#>WJ!Lb%*O8S6B$C&?QunJ#{DEe&kiGy2w zX1mSg<*=YH`y7IwhVIXWX%_=?PsC>KgYlNN%U*s;EWEt|cJuyn_e~4QY`5RkUA&TP^+#V_L|_uHRo+Kli$brytk+z; z5Z2vx@tARQjRM`t7F9BwKa!3MldhJVKfDf+7wolIWLJ~yy|#EY0cKaXfgW+fg`@6u zXhw3oMl@4H3}hR`2obp_PZ3$(RlHK3L|LpTR$>^X#NDPS>6+1n=Q_f^Ka#Ozg*e>nE6dd%`q*z$gP^HeDj}wK($+*45edt z@vW322Qhs`Ovs+gyiKBY*vX_KbTL}Gzo2)mGS=k|J<{{Ip!eWa*ZNFQ@GO&?lp|Fv z(lx!(G%z=XGT!=GqlYs!jL1d?~-oETh%x(EV7HG}LF8%ex|F3cP=7`7BU;?X=Y?1M~AvH-c=vf7hlLQU5I*nHTq$vAR4ZO1H|uzN!$|1sXJ z13nxFwv9w|wy0Ns$uWc2n|3GdPWByptduDhOS$#fPg!hqf)c3dK9geTMU#~KslGgI_>HVFr zdi>AMdv$h-yl*QOPv~xv*^}SuEa{&c*>UEGxS59Oe!V9lPj@OctLWNLFnjWgdT)iZ zX4*^hq&p7M=-(XmpK7ZdBe%_aHFq6_iF#|iyO?^qD?X`4F2d&}m*(3}Mw)gh_Q5`K zALh+>TopMpU*(%41~0kisepua?~+zGPc?eZJdlC+yZ`=#=<{OUcblZFPp~o2c;_nh1~=Jec8qBL=Go=5bg*J^`GLiIBT&wYUSy|3o^GRJ!NWU3{cd_05vAUBEb$e2-ji#* zd&T?0ytfc|-tAe4mr(RGWigq$NBEZKb@KcuG0+?3UM2T_u%5YJa?9m@!-vBU9BA9M z0KGS*w3Ujx-Rihf^~+#|XU#ORb7K`@DG@@tQhvAK98%$H@CvF2R0p4rSX^TKVba6M z>fo6A8?WV4#nZlzK!@yJ>IwaM%p7c-6TbXqqj|iim&!=ly8(I%R!RnVy;t;QjU2s8 zrMmxzXf|DY>pc?fM+EF`(?0VH(T44xOZFZ(^(VVsZ<>2DDV`)n)SsF9#=E8X`HKEU zRF$oLpeIv8`Ij@cCR0}$X9vewpR80vHQsK9sDobLZ~x@J$q~Ab#&ft`NW;1KAs0>t zcH!jSk;5CAWpXig0Ua*BTmEs@j2U3yvv z;?DMU%qnt=l{j3=JaKWfQ1&(J-S_O072Df2`r~5Ng(d}hD8SUMHN~sR)V*~QS)@;~ zA@0X`MOYd$ z6ZlP(h3G$xl^B<26*kcy><@t$J#k;pkNF09-9#nUSQ;A?W#~qvRolaI+%=-(Cw)<*yH{P|ZlOv!qeJ;!@(rG3wv(yP z4a$EDUG1*c*9jCX-s+a)714X@*x)IkYm?7vs!TsWC;rWE@w91M>P-A9G+p#~ow!9RrV`Y?P+2-^(W zyy3#oE3wEYJt>4WA?yGKpV!YF`uT_VHZ?i8J4_qF`$G4o5T-oLQLJfcS#Sy7x%(Hw ztD|k7x-E~FcCYO`-NZku=B-|9>}ZV4c6SRgx@tks4QeCY)J~qlx`JLxrZ@|DKU$~) z!WU!dSIg-oaH?C^JT{ zdQk#!iiSc;z(4bB?hg^Nq1;LF5X+@SuAY=emFl|BfVfbOMeD7aKdI44_Z0|SmdF<% zC0*U>U%(y7qC=HgLc6wqaA70W8WroHbz9u&iXXmlFWYnxji=huM89e?1orvULNeHk z(1bz2(|2%*f(*u4vGyxRnl(~f(ELdhnr{$%0U z$tKynODq#}8^TDDNV_d`TWt|>YjA50%)RBh3$C|b7x8IRnwaSdYoa!XQjFy%#D76q zm^lgi)F=g6<_bC|3}T;$E;c8~V}+op3Suzi1hf`Xzzk2e(&zc^G0_qsqjEj(3O!9| zMKOPbCll6$#31#{(&Z^|^hFF{ZXz|#=(pkd^;!iSW=+Z2GBRE0m$vj7V-Q;t*-MeS zrsj2iI!gejk@W{Bk;6~a0Ne9vcfO9-#I$!>pr8|ur)Q8N7#Fv=`$Ru{_D*vO+n+Ge zGgGrtn>1-4Q74&g-TF?APiSjY!hN0CVwK*-X*_eo6bLvR;0^@vy+=m_M)M& zZ?DvdF)UnxrsP#t{alL5x$kTV)e076$7RNUlIM*7KiK{FFPV8e(lZ+WDJLPnWhUvv z{Uc8UEao_bzcOa3 zL_qHTlm9u+#t6vu9XFo*filb%x$hee{dUQr8&GUJgz@Mq>bmO&l+VrPfP-|BXT~bW zt?my%>GpoRwBLckHg(9?FCA;MbnGVj`A#E!zXRoG>N2AE4Jc!2s-dR;2de7}RImR! z_q4!$+IG_4w`araD#`&qQTE2N5r(j*TZN%z~G+H@eDBB zbMis{(MgO2Xw>G`n^WDA1$m6?oY&l;qMVu zpBIcBlmh)af8CJyKOPi+2PB&@_z&+A|2O|NxIPtw;@_}K{0T$yKV(q+ zCA-9bY)JkAF|zHQIyk-^Oy(eLbZ?;BX9BU)`FD{C(hzCwyM7P%ItevTWJfSZmbbXC zS+-kUAwnUG5IZ8*$;P^S`@}m8xxH8DYyCVk)Xb@JQ^rt0?}4dO6~O%)`G;6hTX-1H zjknPw|8`VUF2YVm0TaKgFC)P{+;JwStqKFm3TD3ouSh@e_b1tD0bQWTy8@NzPl)+A zcgL`2doS(kK5q8o(X4B!o9_7Q{37~%|)3uFj?>N*EWVPI7rW8-_~gq z&D-!ZWapc3Tw(66`bnT>HgvT zU`L`hd8Gq^-7(&70@S-B;V~z9?tNZ#KcY*Mckcb37vt`KI6Hq?BKMk%8$@2sh!@GD z!@+r$_-wq+{{rE;{QjA=CM(eGp0K0 zmw%a_?LT*rHjB@Hue|R6)AGXl&iLo)Ct17F=Q2Cmq$~Kk-Ye)hzfiI-MIsIvpI#d#9W`5#siahpkarzEB~77JHp()+>(G6bx{&5%9z3X4CcUi%yoOLQnMv!jxUC(h zr$;2tNR9ZIEQA%4|IoR0cHW!G)O5JLzIc*MhI3~7`>=PY{h?j)OMJV3Pz!t7Zt}~r zE9!RWa3^w!oy_Uv1mae=0ovjkN!B+ziYGybvg2m&KN*QZ$W1s}&4EYKb}sP5iOX;FZp|<-He=SKR}PT6AgJ67b*UJ zpsyr322rQ(b%+1Np3$)8Whh%#6=0b3nX|%{oH-~U{W=MNIdm{JjG=ZCH=~Uf# z&lAKBp64ekBD|eqPm_ol%JO?NJb#Ztw`1#0ct13Qln5$(PvZ*h;efOPy&dJ-3&IS= z2j!qRyeG5#s4SX?@PqErj8HwKn{TurtAF?PR6Vz=d$do~eFi!TzQmX9KmYLisr|be z;r-ORdsid6r$z5-%0&&@Gm9B~70*{Ha~2zENvg?8Rl0*)BzM!Bb;SIGdx z^3Q0ySnHRJ1K+2FO6?`J{lt}ekBn7Fq{9rmwlv=TMYv9Lf#nG@#J5DJC0D-ymp_d%q?mmD2vxza!2MFzUFvd_@w3dtlg(V94F($T|3q~s z{GNh!KijMBddUy1No{gl39&W8Rg}sl|KYxnVZ0I`kFHZ<@$M=g1anfUO`<-3{7<$C zPO7_y)fiSGkmTz$pH7b1mdty=mc@L;&sbJ=WKcWWYzs(_$VHQw!@+IPX0+2S(&qkK zqf(o)_KYDQB+Gcu5^R9M8nYma-)Wb+%qQLDrJr_TDL_ zFE0j-HWS$NEq!ef!xdXQ^JiW!6d(AF>%xGEGZZD92_(dT=&#$A4=5kXh*dP%Ejg4T z&+Pw{FY@lg6xB`#(cA0YrADIpk1GH4fz?s@6X(ezEao>FE|D{(?nnyi|5<-pg?8!t z`+aszNc!-ZN{I?z8{7zum>#;A;nr#KAelO~#&r7I9;0adz zM4hCV@Stl{2c|Ync-wtd4Flt!w1#eVNBY{%$8N#K)GFoGgqI`*RwlJz8bQhQsWr)| zr_PV}$ks!xI#dhyI;EaBVXbCxR=)#BM{y^gI=^#P|6jeFPfy(2Bjb8b5m1|_=`uZ0 zv&rRIZML`{3m;(MYSlA+;6s|ehedXLmv^76-D!C13svTGqRFiV zccIadgL=Kwwy|pMev0RyyAHk#W^JA46_U)8AphO9R3lhS|Aq5fzYcaL zqDiJ?MMr<$;FhW$k&zcc^K6lKk5UBOmx|N~FOfT33TveN%smI^D}!)gJBpIM(e84( zcF;Ws`3K8r=kGaKK^aI+T^8)fjj5bfYV*7fg)!9%Y)Xo@mU z_>5fcmHh=QU6r^beJ4T_8xxJ5ftA{O|uvLZ3b)TC4ahx z79+(>d|($yQO6`SbD40394T^7lFZ)Qa?cW`uL8H8N{O=0BbMNaB6zs$r`E!E+~dIh zwW>nCewd_gb#ZYP@E!8&UnB4D>E{;x{7OH+_EK*Xy@#N!IZ_pq$>pFmzKYz!;I23J zJg^RQ@|5uTzVdDZ%?n%5mfcIg8jRMhmupC};}{hEn~jlqA@7yWPh zv}gK)ZwFVt;O`(Fg&P%olq7SHMdUgl_fcQY9#j(Q(iWa8Jw6*rV>fPa*(}K(0#P*K zP^;*lj7s0pV@VOdIsa_!)k+omAWP1hyfuam=V)hB!7r8X@ReWsHPvUUdm_a4&r}KA z6KRa>E;!cr7wTtU{ct<}R`)H-VfVR<{}iXe^4a2EA>Zue13Ju{dPnXbDZ^98;YjFs z_Y=}3x;8MZhgZaC2>`tdLzZX#In*e_`qag>sTV@698Ul10p^StzgrdAH0X(kFI+9Y zw4B^oW z&APf73PXI~n^5{C=zF z_i!@O|9zW3o=6k>H!&Z7$(86c?eR%f!s}$ZsT?f44}rnCS$^L^)5Px^`;XMm(a@qh zSBUAoxX2x$c555YG2W-ZjR-q>jY8mpOF{y8Q&dX&e$NOq)jPctKXssb)R<@vxP=b8 z`|wFmY@lzHd^X7}1-EY9dkEDVp@gzT(jgmMvx-gUFZ-%ua}$v6;~QS=j`f8FRYtHJ zjF&*!j~WH1d7t1@9*UGV_8+JpC~b?o$8z85P9pK>3G_(S}ILelvA8j}CRr~cwO z*V7kqZUoyZTbZ7#9OLwSm6>G5Lr$|N|NVaeB3>?f;Pm=to_);QoJ_qQ|Jpj-&>!YIaZxtbE$KVx5w6-*G3VU!=;2;>t0vx=*OL(j$ude`fN7 zSpj`AqS$>-_`qMI&)51Nl74dMcHi_bNo=JKD0cs_I0qmK)+O`S8-`7fIT{>rZ13CD+ zig1o?E`L!1o(#y~&mo1KW|5lwA?G(Us?6UbuUIytv zm1V+Y^hbTkOI;Iui3&#RkLklNv+CVhgBqn#`nUIpL|i`%l(#wrmj)c0BDGD|G{k20zg*hUeKG&(L(`n0J!B%h1_(a$EGPB$?fN8Vtr% za$Du(b@84%NoVw33eLgQeFU~`PflLn`7#sJ$-Kz2qhls>yX2TH$=-9xN4HLIBT;3q zR=dgMevCV~Bl^W^)U}a}$ShU0y>jvgo&Cx`Z>Q`!W6>Y6Y%oE?TH(2Mo2-`cZf&b@ zukNev(DV%^tw0)7aIL`XRKrkdkVSBE-K%@(E|U8>F!y@$v414jy)azk!yD!Cmf?Hy z?ku;U=$*9tbdT}t$*vc8clL!nDtd<>l!W9m@1zZj+8XcvEOarXy;neDNa<|>I$C5G z0gX@!7g+_Q!UfYRi-<<4vj$)tN19{T*LSAJr73=PY zfY?z`k%|S+@fnSG?oea;5$xar4Z(H`ngKQ370D@>&kiD2YGk+o`mIc3CGtDW*H!R( zEavgxoez{H>3mPy1I@C~bdEIxX*Ppr5F6PHNGh{a>doNSWRRuZP^MM(7Mn~9mJ1)f z)0Q#Zxv$V?G2Zz-;yqh~&>RkG_|M;eFz_D?{09U7!N7ko@E;8P2Lu1Xz<)6C{|5%n zXl!a~Y+jV;Slp0U)Yj0@+|c+u7cbsBLOWv^6YjXlrP$YiLg_uD!4!abaz9 zWBcNU`tiQD(`%cXTRIZ;4Ww&GENp43Yp8B-YOQWx*4Rgz+dFF8ENw0l!-N@}(v*&dN$c zPOEKfYN$_iv?M$x6BJUnq&?Btj8|=@rNX$?VPRt%`F1oe4cqrG($kz~b$e93us36| zSs@LK_r;csfdD!>+nTA%ne!?u6Li_)mQGsVoM>2H*U)M`13ID%Iu^IJEF)FN;>Nc6 zL~Yxm&ZRN#`E&zwIBsCa{sUKlS+8W3u2qP}sVagB(pt)Z5f z5u&Fg!k%erS$2F=!-Wk^i7dO6j2WMp3pH9_i^^NuS{BqUXj(y;ZEc;cV7a^wPHb1# z5eZ~?Dy(nFuu0evCm+)uA{fd~9MhU8Y3XRJpLR^^m}A=gzfTz|K0F@Cs;BBcS}bhf zA_zaCWMh`7T{M((sB%PEu;cFVKt9K2#6^@-hBsqTIbyIV^#7=ws9sYNZIG%cmTbb# zio)6x%NE0B!g6cvqS_8&HA7ERBX(Cc8k-}Ei_%Vkw3-{*su#4hbb$NX)-0chMWD89 zR^tnE`+r{Z;@akVG57SbW3!Q_B&!_|qh8+mu+^u(YLZ#dyUzYl_KE^=#O#nD4JlAC32;X{NPN zC(c~n*kJ@=V|80YTYGG`eLYC#&6*w?Nzu~XKPhGy}G=rB38GeuBovu;<38LjG{5Rx}~n#2GoU_0nI#PXN4j* zmMHNkgcTYQs$*h__Re-84gnkWaLfkfSVG>SZVA6ON#H~+9e80wY)Br7*jXiKoi!%b z-cb+EGJ~2Bg>0_3v0w?BPa;;B(~bzSG5XbpDIqoUQ5_Z@@nCgbOJ}pkdq+zLys2hp zxUD_b&@3w7m38Me*SFN!sE7z)IB#fch7fGfiiP@9u3q#ZDpge1@zF7M_mBofD*UAT8t z`zg3$7t)ZHwkcDpXICS%j118Q8rmX#BdYS0#JS~Xp9$elNrV*2z$Fo-YE`YMF0Zez z@@Cl{oNYNP*BPaZ%@<^r4iIWrl@v0T4)k2s#p;0ZfFdKi;tZ+>Dc;;)4=dyD( zN>(&itw8l_&aEuj>?Zx77}@X%AD;*Qqe7xcIpHUr+QwkeJUB*9_{XK=4-U;Of5M5C z44W0rjUBlS{ukpUnkpk)1zfnOp{=m+Y}0j3p+44zRVe|-GTPaoN@t_5F%pH&mgc4v zxqY9-YW8bsOMUf+NP-e{hbAju<8EQ$OcNN=TI(CzZG?=5eWZg8`-zs;22@~1toHGR za~c{FH8pDAzt$H@#?;iXf z+FBMNgJA6Tg!C9DWYVrO4O$dUHN%#TFFdtj5gL^u5I&j8maL0sgH53p<k6X=W|0aenCLRqY99{`t9&6{*`SLf(`~kvxUivZMWTtR z7_$Y-o0-8v1pI{!g?o%3pP}f-BfU4X;d;Db6T1y_jQ!Q=S zkA-Bj3>+*b$A(-PNsdCW<}o(JP~n9dhQ}pVv~*gL5z#AQl$+Ob>@1vZy(ayF7jqoq+7Llky;#IY(u4Sn-OPg6!($e;ZrVF8flJ>@>jo^q#G`Xh6 zCelXKVx2Kl3pLMfSYErdwFwQGDD80H;+ACz6pD6+3$l>*tH!h?7B|$kj!%3hY!~Wc z!_rn}f-RcS&>3cNSgN64+2B{-UbtWdq@*v0YklQvyAXj$yhEl?z$S zR2G3N+TMnuirP>JbELHou^sqxs7C#Egaoor{o}NY#Hb4$*$oV108vzenSomp5TS zAfLJ-Vr%P^XC#`;ilXr;J7nZ`Z$?!vku25r>**LIIwRK8fp?x<& ziii-bM|Idn!HuL`jo0;+)0r(;m~1c+?nD-9cp>A&;uhvER!_yLLsJiDvu$V>MxR7|3wRAT5d1HjhhC(w)qk@IC zMa5FT)GX3?6cLg~X3233zYMd-9k*nezQz<5dcyiAWQFMjQ9p>%Y~*i732pOYpCR5z zL^3&trV5v7YK#2#h5NxSG(12X3tH+|Xk4kOVenfxo=kSBo?75b&sh~vf7@(3QIZ*W ztOg?gWzbRB)-lI2PRQc6paFVn&=l!H(T)aZDJ2=++g;rmMw^-nsXKZZlVq>ui3`G! z!HXtOLO-V$vW?F{c!IV^_5y_Tu ziQG9?PGnS}x(@14n-f*cHDvWOT;M~`C4vgD37P}2t1w7#Oot&$W#jXc-kBr6M0lp9X z@f$k_TH~?U@w_**7I-?aa#Sq#DDV%!e&D##u~`0FI|u544Zt^nJ;0{@VzC$B-Z^md z!Liu5cXkdW*o1sC@H${6a3wocKgq7MNgV4s7kC%&sdsk{oOD<$c3J+wz#YJyoW`Ad zcq~@5*TBGSz!G37M*t=k3=DL$z5i6;cHr5-TNA8#0gI1{#l8W&4fsRg0Usq_U@Oo8 z*8)fFJuq0!xmG#acSp$Xv@_x9z|q*anLoF7^=cDPR>F{wt4*#R}Oa zeG_moyRZ{uW3jKX-LGa`EcOGS118uqI^y_PtcFuMHS}vMumji!>;tX_t^wW(yaTuv z_y}+t@Ht?NgH>+=tAOL!uUP}Ub#KxEp91Q<+nc~v-~rIfYT!8Ft-vzibHMX~Rh*q$ z1zZcf4Vd6`;$~nC@O|KFU|RqE^Fum)&QRaJ_js<9;>)iqY2mw zTm@VUTmy`8(aD>@&A>|NGsc08HNacB?&ARHw2!Mw`hc5(4*<6T{{}4kIQ_6c^upCc zmB7AF5Dr`od-Zb zfop-wfh$j;-{cQ`85k=g9(-R0JRi6XcpGqYIsA+qSUHRS0LGG(11vk8c*ujQGwEMo z>3r&kJQ#I8{ec{aT>w7-tAKw1uKe7h)FJz*68da$kcR`Ox=u>WN%f{SD|9xcOV~6)^T)(yw%!F@!PhH+bAf%p#lY3TOMtfm`+%E)Yk=E;y54cr-Q))> z1wIF?0=@}s1r{I=Rs)NHn}KD(*gf=dM)jM zuhs%r!xv@i@TdQ4fSW}Zzys*twZLcS&uzeMz|#9@2Xeg?*a}<=ycW3mVRrn|pQCJRp@zr2gij^!;Mm3Qcfy$bsBVlgGB z$XM%N7~7Cxv5})r8(DOEe4k|nD`Tg8Wa@Dz7Sr?O#s3-n-+suzz#j78PozAb|8L@# z0LEsF9Mv`K^pT*@;&%WQUvB*=k5&9v{9M0pyfkhs_vSbom}LMkcz3FDM*YG;8FjQ%4q@vCs68MdkKMH03pl zwjV}1ed|w9cLA5&HZTCNKlx1(j)wSE;+GiWSA*YSL;O1ME5eVi&*Zl%%rDpP+xV3Z ziFYf0ClB%4fM0Tm-&6R_&GA#8yo_HBey|`3)F<<;PZU-#jAI@j5>`yust*Y(BkbEB z5_Udew|+?2a>6$3CQNj9Enznm4Gc_CJ?Wjxhn+F9sB4c|BNMeFi=d@)YOOloMZC4d zTc&vUw-k;nn92Alx_yeUV(4VL!pNg*ST&(l`-lof^V+!PcNt-q5Z0_Pt82OH3Y`s0LT30-+wy&4irYlowf{z3m3bX; zMZa^EFZFAt_NUP{)%`ZYl7xLiVbrH4totMQm!ji}UG=B9&*7&t$3x@lPV$k5;^lkjT9epG{IANV zZwSARZ`HAP7-I8E<#+k;@{uK7`7=hA4eJ_Nf(LaaLRi;Tq`Qr}%u~9mk)sxY_>rS3 zjn0(sX8!ApV=r*{l0?`)GideMg+^n^ea`V875h|-95r1Dg|h;9{>onr44kHPWu!B_ z%^D;NJ;uuSA=Knrg8J9+Jv@Pr(KK!7QX7zO_@7Un=SOi0b{}UYajqjy5Ala@V{n3o zFBdLy@>xTiGU~QMah@U0X9vfbfxq7N`ZjSSZ-;Ln4)wok*r_9ndiOYWWa3KU^m1@o z+O_A5ku}3^C>&Xe4>cu*M=newzIJ<31@K2U_NV@TAHOpEu9lCrlhHtOrgT_)hIf~Z-p8z*lj z><;pLU156>cE)bnSgtnC8rgcmu1%c2&#=`a3)bKN$ZsDlAKBWq=jHiV4)5LLs$mK8 zD}c}6`0LJrUn=k4pe*D@z)!MfIJ7beUK2m4Jzc{V7~Kg+NzzvmcIE%W-h0PKRlI$} zGv}PyNpg~qwh2ojh9XUn&?7~95m0&&ks?J@6tEyF0!k5-5)n|O_g)l4Kon3wI*0-a zs9-@vr6?jOkmq~N%%0s1@ps?P^WM+A+``v7_k7u&3OYNBYwO+7?DJ*kZ zMB1Y=`33kO*c(82ZQv>mD8_-hMbr&7jTX8@&`*L}+(CZ~y~jweK!hKtL1_PtJxA&V z)7B9>myw?;ZRg>UV(Jcn^0Fo7eJ5ZO(|_YfJC&D&^#yhk7_~+1BPJ!Lovw!6lnj$6 zYhX7|KIhGoli>dbKUAK)j^D66raWQjPaN3$2#3z_puNx>=x$W3L)(5B{$x^IKOklCEr*Ly&RBb##o8h)6ZoWCFb}5q_5uG-ZO%G%Q@7GsHMb@a zt%>3q1iOip4`lZ(iZ`r50;jT>4$hUrxw*A)?92y8rWe1w~8ZpCol(CGaPHI6UOd_ZY%t@P_rJ)8v$JauPk`Gh=)8LZ#AU8v?2AS z$Yax&Qmdv+^?FfN|64+(qr|)m`t-IJ=K>a;s+?dWyqsuQI+dqb8o73Jo+qrCtH$8v zuvu`0p2s<}VNLiF!5U+rb~I&97^;Kzn5|BI6BQ^E!79=e!>(P`oIQms6V8s zs2Y2E!seAc@eD%PrLcI&$FT^zfUry)(}%)lBkUT&sO`~*!qy@z3VToC>D!4g2VvzE zOOO2y`WxEAYJ@PSC0exVQ$l^fpx|T)IMt!b;MW14=F?7b@O3%djS^@SUJjM9*YqZM zsN(8Fb-F)vCn2mgj-6jH!wc2vyW=x4k2Hf}Q@2I%kY$6r7)^5zFG$M{d(azuMgI}-uTfl+=;TF&PIi83%!mwcDA8+RQYA* zADLae{Gv)kWwRLZi~v6i$C`^&ss7@@;PixL0y+rZSn#N>n)1VP7aajsBWO*k{#*j* z3OK>~gC2-Qp<-P^SQSrCtI-UU>Rc)Oq+tItSmqQ~6F&(EyOVVAp|FN8>&p5wcwh064_pkAKFu;{Gbo<&wxJ| zVMTH52-a)&Ojt2o#YhG(Ga-xB%&*;_2hJ(GBQrL3^!{k{jRaYoq zn!rX`V3ZCgJ80v6g_CX_=+*>|ZBV1D=j;^!2WLX`T&i@Z!*30mZo%VWSb2iDR86Jq~2y^a|o!4qL5o?BrlG6KNhreHGP}mOZeA z^D(ACJFxYE0TB~WvPaiA9PIN@d`=(eXh_WmGLf)Nuy+?QU7vcw@n%?KM5*@M2f8PK zuOQvZ&Ya5Z^UfBv5Qe7BBZHn)rlO-RNR^919~pPD}KoD*RY!FsaIRgabA=9H^jXF4a9jK z7~-EmbCL11<{0NRi7z!)U_L$yYmV&UvT=MjyB~(OYz%u?e1PH&n@!c4YAoz+J8>iT zXNv0$*sB}gZMYoe8!N^+!1p*8p5Wn5x^~LC@6f285&UxOP`=?s*(L zd$iBtd!w}Y#n_jL+Na0Nx-M#8=#<)S26Vf@&fK5OI$;|57Fs9N1U?M-h}yZiO(~w? zI-Y#S**d-QObKbDh3s#EerMRHG&(KFejRWAPWApI@TXwE2<1&RZU3fy0h9N2tY47z zdG-acuD?03p{n;a(f3?HzQ>X6->B>(>pkUhH*h|mXY%+WI3|xXfei+B1z24iJ6{58 z9-nZZ*K4A#B78PD#Zg8DsLpNxCr=-fr_MH}I(vgU8KfysC)3zP3(hB8%|Ejc7hRj% zxg6^aN>4u>SH%2v8TuD&aj5w#_|ym1fldSD?@;Pj?*1?Rqs^DE^7R69{7l%(fxRV^ zr#Nrl8LY_nYE83C^}gxZXo!)Mw!2@m-Bkk#T0B^UWtt z-PdQKNU9>v%CPOce{-DFCd*<;u%(&VmsfKs%u_KW(OiJq#8a>_dzonyLt(?TiL$_Q zfNg`WTJt^xEPS4jiM%y)18VPyXorvO2&{09y}RV{z=12Zjw#)Ak$X zS;w}?GaDfDsy=3-%XkI$E07IF_OZPd+&DuRSxaYi*0V!rv0^#<7iDEz8=R6#lpT}#QFu!Qkj;*wDcET2gt;+LmeO5WpJN_#)~Gv zYr;Og4%;cN?T7arsA3lSwoG`h$BQPe)gt^HYj=u=rsnez&pC?cR*EMyUQj%%f%m*O zH#eT<5$QUff9lU$TH9|PO3j!_*WJq?uP#tLMJS$@5#>s8Wgt_dTAR2E{h!Cx6LwYx zl(XD{aJ^C(?`e~1g2sQZ{HHxEhmUDgdg({tZ)CV$ZCx+B2(+zHmR%qsr(mN>6Car9P)E@J!re zFo4ohUHcF|FU~V&QU5$A|9(#QIrCw!Ir^L%u*!h{d?z?%JLl$JCQglD zx^ewttc|uFc1>mZO-aJMB_ffk?NeRMKtFZ}@lK)f(7rjHHG!{3oO39h#r|9A-1VQ* zITrS=U>v$eX()4ZI{PAgH8_{L<>sEH^35mChLLkvOs1`Q@q?Pg599aeH})g%sTpQB zRZ~vvz@Vn+!bf||M0}ap#_ERo)mifCPfVsFvtC`(Zvalqp1HX_$fxt*z`{*!Qq^6m zc=zAjCNWv0xW>ZH*NCeL<@a@pD{?+XWwIKawh!gzmLY$rOmMZg14b7rGz@j@AnBZ4W2XO~wuT839iMs>0? zIE$C$=B}bXk@_;^i<#SEKQL4$aX~Jk0ycK-c*GqMb~n;THdn)D+edJ}4cYX{U(F$~ zH#^R`scp^X_Uil-qV{StDW9;7J_+x&dxg@H3_s&%N33C}FDwO4-@drtfjDV8EvAgs zo-k%SqiFw)u06H1w%%OErr+%kJMZ8njLRrqRX&mZF7+Riz@dAKN`%#?@UjZ6tJOS- zMy=!dPFYYXGGXs~*gK0jaT$#{xl|`2mes0$L={H=i~EzxQ(LaB(-$76%2%Y%tmPXZ zi>xjAMymU)j<9#_+>P7~l+I>1=PAt_27@#D$=uxEQMS(g!8q^EXFh1^ByQ@U^sI$m zr}H;*yHHvDO}1A@lo!>LFTg1~6xZlu!J%Cz)V-V1qxL3L9yX27vFw+8NYKse23pFg zc+29FV;$V{TAIpbGtGr^tnfF<(q#Fid}Bmqr#d$s z@r}kkTR%`b7a=~RAzdOje_CVxhkSrfcBTBA zOEWhc>RFjskG+ByiQ?EP44=a1WGyX>C1y_6$W;@xrs_gsGZ(`$>kHj7xEDBqbZ=0( zsjW4&&KnJE6YQ3ydHT=)WqCD5hfW)W`4m@k*RDx3_?rt7UAijAONi?o+^hUF<*Q5O zI4q+6kgpeR!+a6Z;IEtvB$p5~IIAG=B)NE#v)9QV@_AY!7ctMj%sCSM0_s&m3JdVq zBI04Yk!Z$;f~O(YS7CA7i78r%=$(V-1vRHRog>{*7;wn0Bir{eW?7cv!ftKY?J0P; z-ISmmW7i3u8@rcK>2s&~c}#i>Y>MzA?fT$6+p;2i^9qCbQppx(hPDl7oXYe9SeucZL!~GAJ`PIOC5t$ zri55V89It1M|-jtDMOENaol33xL9Yg36{W(SZ|X)nU2+xj{mD~RGO&B%IQMP%Z*Bf zoucAyWz*F*qoW@~*@*?puww#M**?rW-wIA;G0tTz#AKIs7c*Qo&$36m_5n*=kFihK z;*S_M%^`5HL+I;*LO${l`n^wpxlB&x2zn!$g1(3@kK>PHisBc!jw-|2)~E8hg(uyP zxVnmi+ye!bykweK%I#)iF1LT>;tk85XwmULOI)+;-_-G+CChPEp+(8^lvQYXvfN-78j&oAIfdR#mfM{|ofiAhQBmZ-v0ljO_M;&hU`F1qlABzZkre3IlY zk14!9N&X%qK1gyWxM_%elPms8a@WQd-jgh^#ELD+@~im5hm+;}0%A>)J2pWqPLf|G ziU~=$6l}eKJb%G@-g=%r&(_yP^~E=L+M71NX8FaRTvivgWmhrQmLs@W%jHrYv&fNX z5bs*M9eiI5BbFSDECjK%;&x)MXr?V zr1(q9gHnu%l7C1sB1*m=C4P!=PezGZ!u=~sEQyx0qQ(4Z`B$`95+i5Di1{&cb4<+l zG4fbU%qy;3=!)rncaJM_ZTCl4e3u$GHdeeA8@(zv=7$8iA1RKLzr~7o`sk4=NCJi=vPw3+EjN{ zs@N#pSJGnUr{ygftfT5+K8J7aw7Yq&3aEUI#7fD#iw)8qEXGTFo+Wlk`<&&UC+*43 zWR8==LhP6JY9Gj3d~`TM5=T{awio@qs_R9>6$_pBGH!p)#b1_vmDBMJYLIVP;xo(M zZ&3hgy-7N{OG?QAUR(UgaTDG*oGs;*@Jr2+x>v}k0Axu~Pd8B=mRuBbkw-98j^tvh z#SZYMih64i@jYh^!Fd!Fjs}~noW0M*-<*BJ@9<(IONCGJn(rmD%h3UB*Z-Ix-c4lt z6UFc(_FJMr2pBPaUx%mbO-JfGV^#P|brXd7R@)W77IdV}F4z^{@&(T-Ka2P;@HRx50rPjUnvEqP7gFvnN!ix_s*hZJm=IA0Z0M0w`8r9BU$k^mGDRmf(rXHBGM zE}Dv`FwT$Q>_IVDgC&$wR0R;#(_2`gw=#vcxwI`_2+Le$VcL-z|>+ zsLGpyX<`*;<)GYL9ObNs_=vNa91~!eZ#odc7~N2XtcsTUDUyw$>akIZ<5BFm6lbH@MJaxWVw0i-9&gwY^?*ui z^RxljqT?s|EFYUeRc)m$R{3bw@P-e!;E6>(cGgaR*~eaU!~`E(<_H}Bt$x=Fftv}w z5EODn9V27Ii|{8Z!q2G)tE#x#6vuLRH+t_Vl;ItxqSzFB1jorjM|>H@-gU&MQEZzd zc15x8ob;cf*egP8jbihJz%e4gFKRsaQ_)Q$JgUHkC{>nC;C4^(F304OGYsHpwU{oQ zLi3|JVSG9&ezyNKxBJ$+$dTO`XFAp(tQN13CSKuoP4N=9JBmHreu0j@=VFRwkFmsf z%ie8?OO}1!qVN%vJ$mv`ko0r8N)U#i@1xj3>yFo=*05S zz&a^kW8rhv65mSpl>_2AM;wyux+6Z4Y^)H+P~%1VZpjV_u~D+KLg09VuMK|Z$O7-E z+K)n75o~ApOPn6m3}$jH)?vRj%_i#(<41h#n3aOx)wVe9W7}TJE@Fto(2V+&sXQYc*m5xwoBd#$+Ay!`kiQ}Bj8=T369@% z3gH*-yesW}j#w(~6ONcA?Z2J$8$KJYWsJ1n7Xrtlee`=${r>7JFex}3v6?ELTD)Q* z@fNo$h^qE_>UA(CZL{sOTzp~KIhNRN+3#9(j9RhMwwKsqqor33*Rpj!(mj=T3*4pJ zQ7pIFLt>GQ;U@iehb`ygONVXe0>_iA`|z7%`%gLSN2*_=9r3rrW>Ud^;E0ieeeQ^1 zf=#9tPWe%nS1EK0TIt=W_H}JkE{0V_x$73&NJEvb-Yi@3if}*8ja!LVEqge>eVAo`!s%F( zQWOXC=QJ_Mt|9u{4i=%G6Zrhl>;~0~~S&n^09j~&Hfi`OPQO7=Gi}{ZI z7AlU#&pWuBfExD#%TjUN#~Y@JsZqQx?DlH)auj>f^39Kub1X47O1^F{;0U_l(8+w? zEjT{z!}OA^mbc;f7)>?VDH*^qOzLSao`FP#qD~QW{0yq!S;;SR)*A;;CBMPhL`uOD zE;7PY*mCe^E1l0d|Co0G56vJvGf96>a1AKy)I0C&$zR2XnyrY4&ROHiT@76+fr3ld=79d@N3Un~LXq z#j#YDi%DxLn-DLyr!q`h*QBzo@vU$Nz{CVGFO}^{q_{36inFO=bW%I$j!mYJVJT#7 zX9|UH_Y?DHe}QGG?A25{rfPz|44<7e@S=eoA9MyA*yQ)`#D7afQG<5MKhn zs|$$nY3%(1Vib~@K*u==War%^;_pZjH&WT*Byl;FeV-&2rm^A4=`+*V(qu6OXtKcZ zj1>C4kWytOtocdzD?jNDOC@WVi344dTHwPpwk?&=A5z7RH2b1LExm@Nyqk{A$J}Y{ zc*8o#k!Pu6&p{4wcAe7%VUv~gqK{#{j7NXwIN}w-b~s`WIvYo96*OwRBxs&=T(I*( zfEhB}fb8aSd4V70*g?Qy!2)Rdees=P>gOrWj?y78HO+({qd9*eaUp8@M_kN|rmSBW z&3@!q<*+}o!4S<}w#3KLY`)d%a5PP8zlyfsb{6q(quB-O;@;eZNDYchsD@irI-?9qhaBAQ55|Si>BXk(FNwj=+0?SCCZZuy!f54*;*X5 zP(c{mgIS(se~TF@x2L1MST^=a?lRpOYXF^pKmX>yzd7)44*Z(~|K`BIIq+`|{F?*+ z=D@!>@NW+McXMFN9?i_zt>rLt&ZJ(y{*#@FSoG6pg^`<#^iS2{g^m0t{eQ#%&4GV& z;NKkh|4R<&UW>8gxN1tD;Q84+=a=%FTjeA9NqNpQ%sFm@)1MqI==l=*^cksR3g}a8 z@acXRw~ML|-K#|(+?}L8R}7BpQtA_BBwo?jj}G57-8MFl)aOD;EwPKOKJ>mo`p``! z^jTv3!>w8BQ_x84G3if&HW(ZY=skV(!4)d?!PR#4!Tz)QjDAMTV0h;J`aI}oALF=j zLp?)5j2pORk7Q;i`lSszeeR{hoWIpU<5$gjSAF{L@4q?lKbiv<++dB5G+WiQ-$q8Z zH?p^p1C1PI}}*gBS#rI#mI$5t}}9nkw=U?ZRBMmeUnZ6MwT$Ls*#P1Y;R<5BL^Be%E&23E;MqT zkvoh$V&rKfFB|EbV&XTlgppN^Y-D76BYPV;(8y6nPBC(!k?V}yVdN1bPaApJNZ%VK zej`g5S=Go!Mz%Myw~+&l9A)GbBNrOE&d41`9x?K?k(Z71y=me%vV@UUjcjCOdn0=r zInc;aMouwup^@v1++pMqBTpN7*+}116TguqjI3&8BO}`z+1tp0MvgLaijfPATxaAC zBaaw)+Q`dB`rb0}8(G50szx?4vb~YLjT~s?C?mC%n}37ztDBL?5G&6*67vG}IqA>8 ze~#4t=dgzh(>&un_x8$Q8x?Cj$DJm_y7g9q2Q7Xp#`C+F$&T4p_b@Q^a+ z%_qAVyrfB2w@)=5jQ`|ZtzXyRnR)PT2G7CuQu<`&!EslN3h!j}vks_J7My=|!_DdR zS#R`N9{rQ_GSC0UXS}h0(AeLyKYyh;hL>Y_E&qX+zxMzA&@Fra@5k}~MGU)x^~dCQ z!oK{y@V%wWyX6ssAI_imJf3Bs&q0Id`~&Z^V0oX;-%c>}gyHqSQ`?F+5emQkSDs(sVN^u4;kb0 zPvw&k(uh1Nq|4)EVJ){5{O^@dL1QIv`J5~nnREZQ$|o`r|9sZCe*Nm1r5fMcu5@OF zig#4Jqe5oItcn#YRH#@XvsCL|Ju{nhf64&ms^NBaHO{KMLNXfHEzT=-ufXEXo_bKy zSe%!`%(^et$ox3Ly`uhR&nQ1mf{AOFk@<0Y8~vdCKjj2{P5%!*2Yr3o#7Qq8)-{A; z#du=;jGd-VS&o@E{#jn1pX{ycL$Fp{$aBsfio_H0oM)QzWNnVs$#dQ<&-t)C=d<&i zZ^?6h(wyIB>?icm{shnKx3=X`da^MiTL*~8jiBV)g%Ie*riA2jFLCW8_l)dzU| zR(-O}`7`ExwK)&w$3b(RY48i?yn;E;>Z|RQGUwgQc@UqS=lrBOuN%~VOzYG8gXmLd zpgyN}{Lp9EGx{KCZ;Lsn_fXKMKfP26A9{wJKAA)Hxq7}A=Q+dl`43vKVK3BiFW6Vq$d#1 zZ078CH>U0mC{<_JF3R+`bGP86Y=xDu=HC)eQ>3o@i{rO41^GX*e*S~8=WzZCzE0LU zGzH>Vx4i9f_E_&>hj%8}uy z{4Zt6G1$o7&ZRR12K|R)uK;UX6fLX(?t)D4e<5!{5_9;}F);dNa$Urb!*k|?dMud` zR^A7$e@Mk!$&R0buJh}IKz_Aw8JzGy3AAPZn-vR}z@PYZItgID+|`aVD%>9UT6Dm6 z@B>v~=6$((AD$0D$Y^|>R+M@=(y1>y`8a0@{$=qWBWMSwGyh8K{z?{}k7Ddy(s-3L zl*N?Wj= z(($W&tXvjrJpcFdzIeuHR4wYkWQ4U=+CRxEm_ZT0I^a4)=?s5ZYu{-opOc?|glB+p zPVe3BLMWw=mA`m9GH7^}WXeEIy(*7Z=87^Lv@t|ghHCtR8KvLh@(Hws=#PxmlJZ$Ky;B(WMTY9NYYTsG;A2QCoYJO~-pOK1iD7F=${ zqPjb<0THw6M<+NzdD6s-9-;b}- zR%x)hk&iW23vA8{Xy&E>LBZHuh{X1zblM5Hsg`^=p%vYg9TcH#{<`4OHY3+uQX z)Q~egr)DfDe^u^xACKbq@8R+Q#u<7)^ByRMv#^9Yo7otqh};g$asm*t-4 zjJmQFL{i?<536zRD)94MK3k2mkAUsNS6AR?skzliM%(fK&gDIqQU8Hm^Kj3IipkX1 z52#69;j(rqizWK-03yEHoJVKrs`Iifd42`bSPhI?K^D75VF{KjR03EVgm(#Ix>wW6 z8J3)f@smFVaHvO69&185OMZz7F~@^+Y-t3+B%a0xO})#K_o4{-7SImoN2Pqy)Rva) zhc?WA22J34TqvVLSsLzUPfI>=4E%I(vpl{k9i1H^FJ@Zu>^aVOBhXrT)S*-cI_`~@ z{N`m49s>A`N7#l@K0Lhm~hrMOJ=MEYYiA5dn}5~u=ZC$F&46A z@!?>73C8z9mL3li&y)k9m@Q{@#{;y`<9Gy;gf<>YwJ}X6V)TdqKhRX$AwVTMPdtYV3O=b}C@ zw&l_}dQj!>f$#y5RDYM)xBwEi#+J3~Vwgi+=SP8k6*R&$zR8xKf2P|n{}b4CPydl# zkN0H$?Y5kQ{8jpC(Tv@KugVyv@%^^^9lMy+;CVG*wFp=7m;a7Bi_uxsj(B2$C*bTC zw!9U&O&s16KaUVukA+_m`Q<7fg~;C`w_oLo!|^O4-h2?WoOtq93?;wVa(We{9Krl; z{JclFGN$p%w%qiqj)Q*+>?q+nXBtt?;4Q0L6HUr50{b(Bm&82LAMMBqC`gsE#2AeJ z_!|9qN=&jN3t@gwY2X!rRSV(Ykbk#0a><`2{#L*`gz!1k-IsJ^^d3Df@BzS{3E|~w z&T)q$FJb?US{Hu}*c%}{vo-v$?8r9h27epadmf%X2Qw9aeMfdgI`}?>e?^#@RH??Q zYNBSfa-=Mer(}Ky<3Smp)#=QgsUu0 ze}IxW(vb_FgZnKJet%F`cdwc-!I5<_n(_evp9vCJ`s0`%`lmQ@4rb9DZ_Qw@dsyOM zNV9*IBTr>nYN+HZfUO~1*Sfbbz3{(St=brvO^mq*C#xK}YB5(FehkDPf@JMU`Prl~ ztm5q+wO-PZcsQ%II$ZwmGD) z@S;M#@sZL064*B({e53Tzk-l`mzr+oDq(TK#Hv9kQITp0IU>!F3WHGEBk8VBks1mq zs~J*#5bpIzy6aPR4%0u51A+c{P8WdoNs(R<@^@^o zs{ZjE5I%_@g@!SfsOYZ<`79Q;ihc^ziyqw@#SHUJAsz#2|46*l<@NaM|lLDZx?A|0HYvZs+j7)!b{f>nk@PUbb)_twWa&~% zNMI}z-%uZ5=}cEP@00SgQO3{eV70xO8D1Hc&HyRT2vd6=2Y+~^PFRgp^pR4Y%QVi+ z0CmO9be5=?Go^fgfpO*&uueoW^XA9fQZDRZ^5Zh7zQkZE>Bua~n$SFcVquk`iCZv2 z`!7hzuw+zKx*UkLJT(;o{V>gRqU7$2CV~!NJ?Jqh0z)boB}@Kh3UM%k$3~DW#Y{i^ zDZD5fC2NetpfDGV#X%NJEcg^g~f{WCIh$1yEzqH|pev=y_A|QZj@a2fT6bm^|5^Dc~en6TCO>2GU{dU=j5XkS~Qx1kCtuMn|SE0 z&xiBTQAwd6<6G-!xjkTX&VwJFa&z*jm$2x^=yZvepT(IRE(U(Bd~{SJp&z3&DBAzq z0uxUM@aZ+%I-5PFrFxzAi8N{387)7>dXsW^B$%^2w#ui_k1F@}eyaBSqUG~{>l|AT z`k{POLgm0&_>EZuAT6XlC37+4N?GAkPBuABj=MPJ7 zH2|&ie6+l8B3fr1(3%ldO*ttsEHQvNj{mP{c^&f@iiY2ly zF>*&)S3^kcuX&LR4EnT^h5iKL+aykC``T75zO| z?)t?T{tZ+MGsiq>V~L8n+m);DG|9>UtE|Tib9Tl7ID5>MQ-d8wBe2?g%rIvaGuM@E z?lLy}gEi7)=5z2zRmx*hvRI-bEsB+o?Kh-)Aaw9Z zo;frBQ1p*t<+;g*J^<95f1oQr!ksxBi%G2B0_97g^sc8!IcJdV#JBk#y}x+?q-*bXzL9$Q_{MV)r#Am$+{$xy;?EpjI-lUWi|9PZEvn>eZfzwe zy4{p~-5sptB==1vC%bEvoZ^0} z9joL%cfOMQ-AzUwSMq@Smyv;$WaprpZDa=}KXnHhIa$d=?phLa*zwn1Os-5CjI(14Tr z1eaa6=|28f!rvlw^+PVR4BB;09f^yOjX9%`s3Hbu88mZEoxo*k6lbJSE(lpf)IMa) z*$Mq=T&~}XJEcKtO(Z%9kA#*Lsf%!x*wUnD@*}CNS;J*=qNOZN_ed%_$`F=Of%0fG zm#22?{Ckf`3lSY_()J`GY0Zn!=;x>Kj8+tan;ouUkwpRD6ItbT#=vJ_XBU@r^AJ^5 z2Zf{TXk)p&SHIpYbsv{U&ta3DjD3qGO6sRv?rEt5Dg-r{Hyp9}vgO_Lv(Q${X1RYW zS;3XF>Aa$wtYl@k(`q`e;`Uaus{4eJ)!ZRU-s%1YZ{o{VcYW%-hMS~hO}DU;ce#g@ ztnF@8vaY*Z@$0!ql&tT5t7HTBoRW>)R}`u%kG_{lJV``#7 z#bvv@iZfPhYYWEWTFPcvdGlVyFmmJToP{|9wUH%KaIIk&f?o|n)*lMrLaLqQ+uuWV zHn3$uTwlghceTXTE~J)*g+FPq;ugtA=4*?58bap~ zky651-$L*qnSi}S;%~rrvxlnQi@Fe83_?1pPoScR9qe|;jI0ucz==|g@hbi4vfxZ) z>z~lPPTEOG71Pix`AK>3lAv-6z6#P;R!4HWavD27X%TWOG7`tTvfNQuRy+NWnj;f-|;|9+3mZbd`FL%>Co>w z1V8Bg$1?&SidjuzYRbsU(!M^wsci;p+hh@7t&S9aMvfaw)t5=-EGE+)C@ zwc@_%hO&9d8iCQ3r&o&)B>llz`ke({zl}4Mdk4@bmgw59Kig25!&sA1sD-zmi@s$I z{HtE}F6>!m^>jxfhg8vKJrk#j)_*&q7;e$sP+89w&Bh7m#X7~|Y>51^1l;b9poj2v zHoy+vGOVzd5=)*(Dexfx#sooYGcmOCNZENK2s42#@o=iL27gr+!B!Lh5ZL|*+#-MQ zx=t#vthZ%};uhNuvz-5g@Jk+5MWycA$a*qv4-v}KAfEMj991Ij=P2mHbX7TPog8pl zYx1%Ptcb7kDLJ}^(^ccFjj|u=J6YzqFVKAlLAtfChwMy7HR!kz9`} zYfAeSKsg>cFU)BX$$-?pyMH! zuHk0wm))Bg?Ms0E3c+*aoy@{?1vl#pdEjmIdX&nFfU-j{4W(Hp zWZXOhw*u551XH8VIw|kR-C|0+KcImjm@aW<{V4xL^`ZfZPXIL8gH_j)^+eoJDxku2 zg){3{S^RNL;qQaDBgoa|zJ}v^W!5G6$5F1#@Na;hjpP)jOOaVO zO{6P{SpxSK%{MlR0W0m{s{hRDuPcq(%FBx7atzjv#NjRR)7~Se3v7I3C2@Hhbvk_! z{tRK%6tu(Xy-}003UOIM-Twjb%{&C!ZOkgnWufJQS4#=v)<&&uHg^6yJVJrz>O%G$zOe(7uZX&yAmhNT2s^&3C zS+Bse8gm(S-7KkFfzaC{dFHGzdNVF(1{dtZK%Mmubmg&eTRGF3%hxBFcs4+3o2RI{ z2DLYl)t<|w`;Et6gYdgY3ON%-@5JTHx0v07xZAO~$2U(_=p9qi)?1)iuXBlGo!fQ6 zZ{_JwDvif9k<5C7qZc$S<594l_n6ergfrj5M6tSIP6lhK$0TRNvqBl3!R0z!6eOqk zHc&smnXWfBv*vJlANmc&JO|bdj~V7HZGmPj;PMxAN{U&qEVh2|)mahdtYR+a@?#7! zidhe=4jwbFvx>fq%U!9)@BmO>yqT^yHnUc7IV90Ar-8N1V}?0PTcBB(Gap4&RxWG@ z>#)ZRb5=3mLuu78HqR3aLxWCMUS}2keJ)Gp7{i4@z2j!O-q_5-jC}Iz#&8p`I(p17 zXK4#GYa5sE?lU%@0P7`>8Ro2F?&Pu??#orlnhw_69y71AioTc2Xv{DbeGjO|Z>H;w z%`Du#n1dlzG5-Kd+!4%$FlT8CH0v;zal4GOw}MsPV}?1am|x)jNeuPM=G|a*@tAp? zRrC|MX%`*6q7MRf^v!g=v6=N9mm7XJF3bh%9gi92ENy{io#C=~b;H~X)(MXp=B#4= z%;h_64D&KrE=Jb;tAV29?#kHyhJG8URc@v;b)RO|RW8pQ)*Vhekiwek{U_0Mvm7g_ zWG1?c`}bfEG)q{1%A6=~{S?~HV}45A;~!x?eS|kGg4F$Gr5uIHwY>9eoL#e0hS1Lp z6?7K}{u2o)L+EF<3fi%qu?qW~lp*xDWG{ERlD*y4 zN zquwR`LGGLha)?_`$>DB$B}ce@l^p55tmMn?G$lv7Yn6P}J*MP1_q37|+-r~v zc+z9&0@J7QSh}{@gzorHpU>lHU6DbHmGlE#Vq2Acl7(3szE(kOV_QCOSrfViJAEdP zqp7){z$Ub6NngX&+*Yf9#bre-mq|SvN+ETNPFa&yMWC^Q$I+^%JFRolQFH3<$2-A2 zHtFgZGiW-LUd)m|RJ9oS@FI~4)5dy602f2kD_XK5CR*gnbPsAmQ`7W0T+L0J(5>|8 zi+CK>q9zrpA;OP%l-dKXUiNFu(kqpB*H=WVr5l(lRn_aflo^=rRjQu!DQxQbd!;G` zsZ#P3s-l%@B>xN~s`79w_bc6%xE5&%z(#3&o!2P8=*S|DzSX%>HEp+;QmB{sFs2H; z891$i8cfX_D>as7OL9hv{1HI?34Vi;t0vc#n#l$C>S*{FKodMzPp&JqkSnTL$_>5* z*eVY%(Y2x43uk^R^~djYEcW=)8Wsz5@5EW@KgPSZugI!&M$@66FA($^ z---tys@9G>R2eIqk8vDTwUfIICn;+Oa#po-+%!}kJbXb=)h-E>zQe4b5~lw6IovqpfL2VqwE&rbMr=I*h38)E+tp}jq%bv-;8P_;)~>T1Tw$PgeS z2(1sZteA@MQ8^8RF`tUyS)QVX9+Ty_At%=&cykc4?hdK;mkmDx{yDI3Je<~m#`IHi z;0QuP&*h4{FFN2HGbhhdQSuX+jU|=r?af_m5FLS!iU3FSQawW4Om;=HhBB?Ce z#qA!zSIaur(4mr}A9xsUVMpo4S8bbc3}HBEP>2HF482gIUK_V^gI zH$R>C?Ec`BtlC3%IaI~xX&bEyICNl;c6JjW&#pqJBecgi2%!UmWZTt%(00r~l z7Rb}osz8*>RSvG_Ec34-W=^?9m0wD5;CUtt+S zT6IHO3i_7YKLR3hXHTn25v+c*57-ZXtNk13DQRSn{S*+=$?LNfQz0z{{lV=OfJp0_r`5C9p#CiTTf4>sS}8R< zm=zlN+HMGhbg~Gc1D5e3N6pCM_6c+(6jRHfk~M0hhnMB{?$5Q8{e!9o|9k~Jt)M{j8Z?LwfLREq-v-aw?_dYtqg*6S~rzL`3>gwpaNQp{%A(kOlz-7tHK9y`-i1k zwR2F_I!1V({{5a|*`v9AXtGZ0U{9Y?ZtxMPRmcm9ak{6kNX?&)NlKy%KH;`YkZI-+)-l7<{^Zv;eI{X<&)u| zJPmoCrd3x5RZW?k=nqeeS$18_Y^X-;3F@;91r@gJ@&-Chkm_hP6tL_z2I81m8G|Kp z+edbF%f2&1k31O!skCP(sH$a`F;F#vv_S<`wCpAZY8^7z4+BMZJImfvSsQ#TWKcmZ zEqk+pMu!Y4sEK9w9;B~t%?lZ%XO^@3TlPULd}z1q!;nD*J!;w84D@Blpn`f>_6Y-B z3K_h38U{zfc~n9&SfHktWCgv1+%QlXf^?D>13e!ysG!M~{f2?2gbXI$4})v)@0lj9bwPusWE8&2vK@>$l!7BctHxJ_ zFGJ!jox;mO)5hUem_KLlv+S3fX*{)-$pK?u;kzum!QC3KN;qYMv9ECaqtsfA=@fr^ z!YMC|{fVn!|3}L%Jx${S38zYB>?`~`%dYvB#-|V-W?$joSat!7@MM1-;bHb?mPZ}q zwv9QcPJbTz3di3ml{EQtIgfpXU$yLCP5z|b6>0wfrr+7=w*A2-U7%G75A$E)X|`Py zTMOi0d&0x~S9r2*pZ`$f1M}FY*O+8ivF+Z)G(IJdeT7%B?H!pKUzf+e!pqrqSzN`S z_>bhZPb2R~_S(~iCJ9j*01NJ~NAI`&3Dq%|m{wQMqK$Q1|w?4-w**F#zgy5!hj z10t=JA+4fVB4o!1d%Vz-to=bP)%c|84CTdK8iF@*mK_3X>A!{#N^ds z>@8%E7k1keZK;2RC6=M^SCE&caC0JzunY$;oXwsq>>k*BAO}|G(O39PVgF<%IS2FT zHyT6vhd*+~6&BL}BSPO~ha$Da{}}hzOOitMkW#Ptnm$gEnoE1a2A$otiA0$f`e7N0 zv`*Ofrs_iON+d7i4E_!(9NvpVxyVa$D*No#fJp0VNb5Js zjix?(YiGU0OljcdhJqUT?8)dVY0+7pAW~-;Us7&#_SqjS)>=(NS_?P5>DaeXs>c3jKOp{vKD#OANmPb42oI~<3ZLt< zD>v1y-|y+`yjoWnsovXun2v#9#W)rKKm9^8x}+uF{IzAtoHltsDj#)EkR2x zLqWTJcFEq_iQ_?imT`>A>ZH#;6Rq>_a!5--Cw%rLK;&#%frNHaoeD*vnYACT4 z2vVt5`wtlkKL@KTbgs1us_G4hjLGSU)sc42K<(=QPhT(a6wahQ>{;#bYo5NwTig%* zRB87rtm9uE)MptAN|N>yfXM4lf~x2?@qZ23rKG)dq1O5~q@|$Z(w^B&M|j=Siof&0 zlyl>-Wprncl>XH?WX}{np3ZxvNF{rx#6ceC^l*ZHjZE!(6CP-cC*VHIMRRC-U<~%v z+NPi{x7t?h$C=%B6Z##eZSALVrG>>;ujw9|1$)(NxnC=ZJDQf#c^$X#orte`UAK&q z_1uO^)_1!r*}#24$%gLhN;Yy=DcRWFu4D`M*4bpIrF$IbukqgBfE+2mgPXN~}(i3@L4eVBv zv$WbN0syn#;Y3WUTc7|gQd+zk0&C&xTt~EQw+8POyHQuHHVE$$#H=Xr@h{S}znIn{ zc>u0s#FK@mA_>^LgMZa~Aj(o{ZK9`Ry4{sRaKi|NrQI(URK%tzUj?;yNi~JczQb$A z!fa=8VKa6D`Cb4=gP`7T+^yBSi24&yK)(XK5+qo+l8F=QPLZ^(vGqo9b{j%?65_rE zUloNW75lv`e0WgMpEL4`z_NolD}3rYJm@PHT+kD6-U?6$4=y&lF*KhPiyni6eG&YG z2bITaho!Z@^>&JM7autZ#OK5;mn#mR>=E&BIeadA{4h+U?T`5cz(|YE()Jf!idw|C z5VZoOhE?BvE3Dxjv^A*Y!F$kZ?FQ*5kj@ja8WAx8jG_DVTfEe%!{Hihmm@pOXS5&7!0Ka1Yut3l3Zc$A=u!8e~MNdH0|l z{)j7!{8o@|BO;~5y9aH`WewK?Qr|<1@ddccZH@H<))>4yg8SmD?x<=59E|egI^P*ssi)4)o-s$!fb$hjg zVDL`2^{~5uSEzsnOV{O=-h&HO!RvCA*WPuxoohJj3hlA@T5kZcX5uJxU5*U-3C#bx zTAiJ*X3rtq#dp7LEd#a zDh}^b968;cszQ3QWevK>*^^N4s)0@>9-f9UA)4=L#K$9t$= z!$T{9cCnpXp;zz^pp(w=-Ee^*ZB zJ*;mr9m+*K0(55;d8Cntty4B<0fZJ0BGyI)kDds{0haqIW+8y;d+-*5_wb}Sz@M`^ zmcWSUVLJ%Jy~&6vK(!#yDf;4gc;4V7c|J0}SPPUQWg6>B99Eftz!<9^=D;&yWj!J<3S59 z&tcb=*94`pM{`5bw8iaer8(;i>>&?Vxvbsj$mJg;@njURu^vwP;lAP#fOW$;n*+kz z9*NRo>6j)^X7uIq0l2aQginJc-3{Ge_9fs?@{|E6Yu5hEV4OX}<&mwNQTw<8wv7g) za?fNz3a*RWl9-P{$vb87Zt#J%ubck^v(Z3XYcU=dy%d2mQ2EOg!7{yeub}-Mz!)D z1qLj6bQ@CfJHl@SF{>yMYSEn_fxWyC6$tkes&z_QGIIffO18nRkN7Hk79ANyx?v=+ zn-`+8=Y9gxRb-VddE+|-bwkM0k&2}#gLJfXV@F^MPon{Z`-!Ku+gkGNPoO;$Axk{% zJIGa&(uXYBya}arE8x8zr7}^pjYlnc7~5+6TY$fL1hQdOCsqyR=RiyL#WUKnEeZ!; zoj$UnJk;f=na^7?dk|I^U^U8zsR~TTJl>M8kAiQVLF*eyRWWPU981ofhBUqe#zc?x zJ8HhpobE<2wHS;wL6&ae>VAY0d&PN+vpVpY?*)D|$gxyzsn?4Iu3B<@ zZ9}>U!ZnYi@=KEn*pj6iQffQKir}lWgM4D?^rTJzFHgIW0;j71Z0r$KeV}4Btk$;t zVhvaifYHZeDKoX`ww%CrUaQ^&5Y&sw0zGZn=^M>N7th#~pbD!+?-dMu!sUyvdF;n+ zIqXGE;va&!jo6JSa`l?Bz#v=xgmHx8Y;~Ytd`W z0=szD6(m@{$1E_`mXon_MkHPqKNX3stkt4B)B`(tmFpq)6k9q4EJfm-!0bkB<(qm@ zSzxv;KZNhf+H=5$dDb+()Rt}LbFEKX_OnC!bT@tAU0Ys3ZJ-R|8-Z;J;dDEFV6!cM zz;#819|LwWgwsv%fnB!DDR1om1&rYumhq47f)9LV%O=>4phWUQz%oNP-3A{x4!oA` z33zp2bwW7Z*%dft%e9!)Q+?o_fOYe5^$N1U1sjtAU;`2UB4L!1syo5`UAPny5RQDX zI|y$8ToggDbp4_il?CuVwI0}0;vazVagfCl=|yFM3XXI(85h0+_8sB62Bd?*7TC)HyjU>?P z#sZTa`FA(%A^#Zoe&U2&Rh-3+EP)+Ca*6+lpWlcRib%b3EU?y*4-YklV(-Ju4_{rs zHNM4>A7HAYjFbgdk#L=z^q@dsuOruXH5t$xSnH6!!oP6j*-A#gFR&*<`t%x*z>kj1 z`V5mn8UV%un;6np_%DvE_zP!hrMw*2JD$FJnO5MMBRA7HvlHQ;1$9kb*+UM-3EA!s z82<_2C6CaG`U~}*p}=iIw(buLF`e*YAbfS|3)3r>N{kfWV}+->lG1?g48j(*Fdd6} z7gM0Gkhh|66|)UkJtCPZI?Ws+8jT+ z1+EI&_;%Ba9s%`K1YN&=Dd6|XvjIc;8w3}djJn>a6w!}VojcgP5K+=w?itrGb?``73%QYXle* zB3LYa&@^O!6c?wDz{~=G?|KB9YM2tPLWSg7B4#ek>;dDX$D*PLkFh*jb%`e|G4c$; zFN4Oh-yhaw65FC@DY4lS_2TuVbSoW%B0&<~@q^paOLXDlHte-2<28Ua3*x#)=yHA= z)4f1vDIY1ROV8>B*3tz}SVa+8>}3vp=MIJyjr)`$PKW5PAWk)*KTs zdn+@{eH<$R|F_V8Ja7x{n%#Pqv&Snvf}NzYwNNRrb$EL>^oQp0)gO_oym%Kp){U<- z6O$-!>+lTjIpDPs+%yPTOB8Ol4%ZHYxvsz-3F47khpRyLwhouRr+GXFF`|g%&jqDGSCcf0j{d{e z;qAX5iVEP=^)$$cQuDSBpBc-QMcxUdZbYP%cw2|RmD3Rp0y4xyi`5v4t-}>o92S*) z8G_%(SM3PV!J_QBVBhgqXjGz*(7xkXtUdXC2=9uo z^DS8m?mHI1e1JcN;Gsdt`jv2R-?1N7^}i#8^S8iQ5XmZL9^O~7r+11KSm}SwpOr9|{%ExQ7|iL% zYafew1kf0(%Q-+_bM}fwuXq5r#2utLBHEtjaYx1+f>$NF)~(3Me97y7a&^&_b=?P# z^xj!|@3>(ws5emYXkNP!*dT$0efU}lbx;@Shy~PR3@hUX<3w|tb9u{SntLOHYtbnS zv?_w`W@cPVoal!butoFKemtx5V9+o{qpj=B(ve(#-jJ)98xhGt>}Ip{H7@DCSV~2A z4^l75Nbh<(6Kj9sxf(L&!Q(jx(d<%=xMu=WgW68ru6?JMb5)K8F?P| zq}d)nR_IgTg4vrlqs@eCnWj2*&P(m{RDwm{{GXz=l!2R|met33J78egSzCQV81;B+ z*AL+^m>RO@ZJ|X0tBOr8NFyHD@)eMYVKn7R-6$ewO{t$*R!?Eo2-EzlQHtg6etAA>UjMvwcbDypj^Op92XU0UdrlU<1pq1YNKyuu zPgw%9^{=p~twHK$3(=d}@IpPxt!W1VeXZcuHoQd7bl|Pbfc{c&dmCP^KM{^wQ>c=G zjf>R@)>*`UQ4cj(RaDoWv{1RuDp&YNXqlf{q+H>j;{Ihf?v$$+m8M+bR~o}! zK*|_E;|U&2uyTb{zxXL#xx%OH7ff7>#P?JN+ZCSnCRWx106r&>3sbJ}pKHS)M)Fk) z(#KM1$`yVci@eqk7XjEfYd%+al_xbV5m@;kTne#W;g?22!yAE;sb~}IV&DqD^E65U zHeBI60?HM>`d6G`fGt!w<{29Zi!MvDT;Z)tLk&PUYmww|DOY$l zbij6nFTn#IG(#MiSUzk{>b-of@VDT`F9xiV!YM~SSNO|Vi1j95WGPmuM3khKGju0Vx_EWLO8Y zaZriM%Hs;JQAWBwZXtg@S9pKi(IQuP!~j6?RCn7Iz8G(@AWXT!sRwLV__bf~r5t4I zL0Rb_&+bL)&|p_MO%^|Kl`A~9t`x8=I4_H00bC#D3U?+8F7L?ypj!@NxWZ>PKt;s` z5HDkMzKq(~uJFl;m^crS?t$&knEo0Olq;OZLHG$(uJAV&LtsUu)Kz)t$jlh*3MW81 zdf^Hmxe>AkA!Sl<#y6;gWU-bjoNYHh@nmke!oPYFxz{0Oi^{#6s%pE!9RV1G;tRkh z6=e-kqzt&ik8gvFKL9>Z1d^e@ORVaaEBx#poZklFyo62m2}$7^=5vM5fNs?QtIcDW zJYce9xWb3w?w{xf+L&M}mn>Oug_nB<^_vOCn~L=SnlC#?xx!aMGsOpBd~C7g47Xh2 zpW`I@0ctEx0>5B!bnY$X3LgXyEtBqp5R#j>rSj_^0l32V?SQ|XBozfAMUkjZwkv!G zo{|-H0cI)!>jMp|?F!#Z-&E)i#t6k?G0GKwWk|g?oIf&R?H{}W+(#e!I1=tKFO}WDNeQoAn z1#E+z-*Sc54Urur4gmYy#w}O)EtnMCH@^V8YU7qGd>ZBzHA1+C;3SC6te@oyf472J zehFa7Hg37X9Ulo^2}A>6&1^g|6U#bW;eW%8PWpfs<)HlvSFZ44_`|Mv9_g;FJRSWzcyUqTk(tOsE*Ay2? zlw~@0^C=5e(Ci1x6~6a#tOV-;ZVe__uJHH%5whH`fPYUMyQ-Ee9FLgEf^GnR5G+Tz z!dIS^O%*Y?N__&G)NkbqAGlezOQZp-Nx1Az%N71CF3huuJGKFLSxU*c3`_ze&q^Jc?CWB4bp$G@|wD0xx$Oq#em)f z_(&1OLw^bj2^k*!L_1hd8%N4$T z1X#1dcs+<^yTX?r2VodPxvcERyXCcRwLVPj3jUy@gASkmGE*!Ye#)GKYaR>2XZU72dv!TvYYf z!CFsDwIJkkh402KKpy$e!IJG`Q%bqQTOTq-{Yr!*JYJS7{Chl|r$0ct8)tOccCt8? zD}1hy>ZTV3o}xI^mC6-<{Tr!1dIR9?f;c)8l`A}KJiZQUYs!q7Z#LJti)~|!D$evUYrpFrv(WYpJI1N!Ro~s894beegW8Jh0}{O z*fZm&j}pWywkHWgh^a5`kc`~T7y%f;V1q&|5xpB#-Rp|Ck`kOEl`4wr)6Bx)kL zu?6W57&pEDgO3C62CTn@n=V>=r+RIJT>FEFP{tuji zBFcf2p>mKA&ZfNoBL=~b4_tJ_t~(K_CCdAM&nF8U4P=}``8v60B6xlcHA;`5p@3_> zFg$&gCrw43$oV1V_Kf zDw%H9Xz3>&!Lt_gV`xS6_zXSu1w1u&F=9`1lrR=DCQ~WvQ6qgTRkU2EPk~=3L{Gbm zVod%uWb1c`tO9Xs5LsW|1WhPbnIv8b`TYyD>IewmStMESBZ8&;5mLPoq+A7f+al;6 zQIY1;|9?iGoup}dsc#}s(|ByMUQ9Cfe2b^gt{Ys*QgePsm!gK6pSmIajUAATX|wxT zYi_g-*@jw)P7u-;Kd_J(H79KopPS}dyEhHAo zMm%utvoA#W+oHlKMaQT&S zP@ghT({wueKc-FYT$I@toY5)=3E^yN+JyENEK$q`=`|u!OVqUa7Yj8N^Dedz6w1?P zGb}>QpN4j%FQ=ivv}yMorVXXo(`H>6M6M#|b;{k9WLwjw2dPE@WQoMaklv4QHEm`= z(qN=>Jd=cLvs# z>EJ9@IYS}|k+HZd zQHfG)71`V#5g*bGY|euu+ftD%_!vYPBv-Z|{cFONihPX67fvEYh*n^94rVD85l?}U zYk`)E{DR7+uTMhZP!R__2G*_j&z+zz1$`A|(eWsNT18540sTWHAFv=jjB!&%4jx0+ z)4+bQ@L&~*23e`d061}oCqgISA^@ASFkqdIl!_Fm43>%{*|~d$&~;ETP(>5$vQ%z5 zQYwMZ5#fE|r6Xa5kkPyQkm|D{%20c4vr4 zEm11+DP9sr@ZZ?}Q7Eg(a10G@JfIiSH_}i*Mc%+rRH77HMSl4QkqSsp!{)3`vMm*9 zR0&XXBzLeNy&>UBMOM5nwB|?=VmKJ%gIP*N3|Q=9wgp-$;zmNs>QX2HC*R|UJnrQC z(Nd(FCtrHeH(t!2IYMZO(_LXHZx@2tvgf?MNmkyU@|?IYB-0}{GVjmH^qOxiMZ8Bj zj(cmX9Kol0dS1;cjsDW5r98mvQ>cvLDo1b`WcFHRuyvl|dXrjo&inTN$U)D%SvmO8 zmXwr}$bs9FwU=>>6A<7U|O7?eQ;i22xQOR;l?w#3ajzFmA?P=*?pT6_pkyiu?EbUhY^>|J^O?{_tK^=pA2h9KqSMv>zR)%!nLC}XPt;>6rLtxS zew08yi^BK+=}8AI;YSd`{a!xIa|iiLb)0J6f^K_Vramrz9wBA@xb0L;xIRyj z`Qj%-$P=kDFBhpb9+ZY#(|g>ZE_6-8G)$_Z&7H3GLvV{4mmy`ERhVLfvlsIFIbBVT$_jl+od4%mwMExJ$XJ-#Bs=OwQwT*$ zQP=PRbuB$0mZzdz6RvG)%{Xu$x?aTTb2?V8bjaX_(svMaO5vTBIvf+x$Z;(WgMl^? zr+q4`Elw&l)#U4^`G{U#ucfA;Mj#(vkAF;oJl`UdmawYGog|MW$@5%|GNj~PRx5%f zYpK;p=sef88&c>+ix(_ZOL&-!zU?Sn`{&E?Ei=XJ7P_av-9nmSYYZjpyt6f?XVr7K zLa6OSy(o275T?V9AUf}tJI|69c6Pw;e3c)>aoX!kxO`br-PZGm3&l|B_JKb7=zsQgsY z#n4)BQ~EEV-8uX1I&}e$W1@G_$o_|B-7jc{-RDKc1 zB|@vck0sx`5^G4ICf@eQ5y(0Z>!Dc@QV#WMTP;O?2d0;|@-(=;1b^&gNXVmExc;G1 z-hmW-Gj{mb8?2o|y{plx>?f=@eFf&3kODtS58Qi1C#IzL&c#Py#)tg+o%G&+5saqt zH(=Je(sAibtLI+?@1V@QD}~X(>wJr&z-_L}%P`s-GSqtI2 z-m?5u{s7FEu8B2qD-N7Y;?P0f68QTsk6aCLmxQ3vkKrV6k3j3f-b#c{G3(zrH zS#h;ei{qpkpu0M88L3G)PFY8Pw~8C%>x*6peA*wt*gD&gf#*8g8|;YWclBmW48QeV#mg0=7k1h}^A#NQu=X$a%S}<1L8PXpC$n4qh*)!H z$f|>qBoc8pFNsaEBNv|n-o_cSsT$rzhWw&7u!e+l~4w zgp{IGH5AzpZPNTytBhN{P4)Q=(v|wKt!1mv#lC3v1qi!L0!z|(EA=Up3G5-#J+GLU zo2zeP4_PX<`V__=>$%(-AT^>aRC$?2sZZ8Pm>AT8o*?8BiTcYVS?cp!5+0?b z7Q6()+35z7vhm<0Z`jD(M;?^?jMK-l7 zxDA|;vvMb5^s}z?L7lT2;Pq&@0-$%|%+ZhR)3n;DPY_8@>H8N7!3s}*U!;|O1Nx7` zTr)gphV+(&9<8xcEB&%L@^8)~Ne;hkLap@UVM@)RWHLN?r&jvqznd$!xzay>MxMW{ zmHyr_Gj6W*B^|*lef@>OD?Pov!G9iQSt~ug1j1U;SE6`pMPG;Gly!5_8(7h+qP5nF zUI!AC!LQl>^3G^63eIj?(Z?Z|nv!Ht=ULIi*F)kchpl|0QeTjwX zO^BeTWTl`f+4!Jfrq~bq5z5kv+18XS#cXRzUV*oZNa6-|cNJSr$%SQbN+k`7!Z^iZ zla*6bG95VoXSE`*niigKO5QGv(I6omK?yezI13pA>;((YHzj+2ZnkR)2y2K$ z?b7MUo|5t0xG!<(M=Sd8<;`-Bf^?d)P|wONYD#_(VHSQ9ghxcO<*F6^+yGNj0E1W@ zTV6@llfaIzoM{XNIzp?`b;7y^;yV$X07OJaPlLjpqHz9 zusP>3+ftty%(m2L94_C9Br0H+rr1h-a#{-QEoyBWU>ykO$|?2v1E=ffskEnojkEB4 z>Qi}|DP#c%ODvMDKK*cU&qB5V+iT(Z)TfQv1L6z_mxx5|(&@-npNZT!OMO;dFw6C! z>jT)h@-mB39~b@|%Y|10p%#&Bxk`Pm;$GEhkh52jBV4B*4A%PcWzQ1kX^Us(H zx1f-P&jeu+k!-nYX0`m-l(ZRy-T5S0GbeH;9DX9=fx+u21k}UNZdPHb6e4;uC zbu5xxm`N$}s1McZx$J1HKD9e~$er$a9XFNeKoiX<{dKbVCPT(7k`vVgZHvWDV{i*b zRxAUsL4k!8c=AK!+znu#1#0ccy&Hc{$xZ=Z(F*KW;~{y0yiDZObVimr;pdf1%hsM4uiF9LTJh2VAovK3fefzvYp z4hJyS0?kTixw7KzO8bC{#n>$+S{<(RO2}RjgMt~{Y~{BqO{vd)Sc`hPF;{v7&RkYc zQ+kXKdH(?GE@f>_mCodP5jX>`K2Z=k{MaPeQGw4r1Xv2dQx<4ey1A? z5|k{Wb>~X&gzO@85y)UaE5B7~N`3AKRKQ)kHdngz61&orJ~$Oc(@S#RrK|&}(nGmk z1diGX!Jhy)qQFrKY=%E9in9PNSfE+y`mPUgC2v)FEKuPsEJsifcylV;i5A z!*xdu58n|SJG{?et!_<@IJLRD(=}koNX<{!&w}=WeaKd~mTMu7hegz_=Xkc5{6n!6 zQ{Tt0@s(m4aKw$mUEA@>6hA?M=fJPq(Dj4N-@-vg2;CQu`CCTQu_p7k;t1#8jQoMl zpw?~eDhx&^J>+NyJf-EP8=w5?yH29On=^4t9??$1Js-#L7&`aB#9K81f2Az$pUX&r z1waCREGYF##Ed~wU~xfRt5-5^Bf+{W?I?0IJBu8p5;_sw_#UXG4Q~7y*kJX_6q-Qk z4?5vEIlzr70JF7TN<4LcAm=pFD@9EqP9?nEqF&|bMRe?ece!3_^xJft4i&Cf#n?{A ztKe0xmlnN`j?cjks#n!GO2-+4a9l0=3?27*1Wvl~D;@8p<3>?jZpv;PH+*Q+7VzJSqx4sGxBrc$koOl9r`{(L|7RSypB5!N#?5z4$V(Ww z0ZRSc=#_N*D#SGiiJnZy^B}HaNYrc``{@nDfi>vqMzON(Z=hQn1!Q}hq7NIz$u>Sh zzcq^IcBTB;1y9H}_P84@g90qviyMfcUXrF6EAbd|W3T^xMBMnNPy%>OG9!i(Z~q~o zHOVSQ$C}Z<9U|>G;zpfGP~g||xPET$pyQMQSa(V{8STDF>Bg4QxRQP* zg2vKmBrE~)EF*4X<3dEnG2%5Q)JJ4KBN4`wa712rkg6DlKMn$9q>#}S^=SyPQ~vhjCa&NNr-VDL24(b>t;QuIUOfAs}6CmOL2D|lVzSLmV-K0cZ9{Q zgcxeDY@OZ2g?eA=>|8ypEK#U=+C>qGb9dbXg6la?L{RKA5!RX*q^DX^GY67Jh0`z$ zqExOX9bpiuKazG4VPhnm8E(mbx%j~p+b~O_n7V0nvHSd0-^4gMQCo*z32bn>m11cobLtHknTy_xR&(x zAE?a)gWf#9B^X+yzl+;{u`RUQNZ%nY^}sck#=XBoY{n#&5$6uE|6f9AaNRq^;g(h; zLJRDE5A)E7p-jWvt$bq1c09$i3YQVW*(%ae8Ao%(``;lgDw;TRM5WP)L>GLBhTwCN zSeROco2yh8Xh|!>6^=YkE$|iz@;h|I0?TmRx`3bL`nynq0aK%mD=-$vDWAcPIF(t! zO&@19R(*rpNR>>1?)&&NK@|^;pWB#p36V68L>T8o5UI+MSfex?*qPNhl4yMX4GaX=s#0q`y&f5h9Jb*29dr4-sj?k&(vBP{7Qlp@Yzbu|~~CKw3o6 zsd|#JViF=P!|6=_g7F1Tl$ovEDUdeVxPhfVvrTvnM5YpXhBv@iESZ_ ziRVI>JYITaD>7>ocZ{aMLRa)?}oK3XgWm$5Q|8RpmZazzy!a6Et%zT@W7`LNE%=c-FO;;_A+Bf!gzQ%H$~AvTC|c^(O@i>w26~5ko^!d;YIFINf2jlyDAsjIpmeaL% z9&=^=a3B7!R*)l3qjDKULOJ3#KEs0-?YtcE8oRy*62_4TyA}Y2_rnj5R)MFn8(^E0^qa>IW_9mOvkM<@{G3lRx=soWip@$gz%i z2L-T&z(?GX=47K`OxpMwxSwzhD-hKl+vIv!S+kxIY~i#}7#(2%sLsjRAAvDJ@L7Fm zFe52B6_xy+D><7WyOAs*xgz232sV#42IVzZ<0g2XC2Nyl`{?V(5R&TS6qPlVtA0JW z1n#Ef*Kl&nsxK{K_q(V9&1x^`=Cyt1bn9mvBjVMG<%zlM*Jf4;;%A_ij1lySOh@))2RKyyD?G$X<(o>H%W-8*_m}6Oc z1lzXc5Xw*JB=jwQSbl`XxEB|peW65Lr$mI@#Xs^oMYzVpThd97s5zZ3ULs;J$Z_tm zV8pqnU>{@dWy=1dlupkD#<^!;A7k!i%HF9MzZg*3$4Xa5i?evsZa}&(&fzrHK2EwU z+;D@mkC$$WKBqBk$+3T9VG4x8_0b_E5DMEVu%@BVIy@C9M#psqfm1e6osLr-;8X}a zB7P0HM>XlXZxqWOANo>6hbGc(Qib?U<#A!+HvcC6$cx|v)Hsk+utn&r40z;JeyX1Uv-KT>t9#kO;V(YO{~VOpOfu|~O3h&13x zq7gv?8gitxv5^Ec;z)U86A5U{k*daS7{wi%aHO_T1}jj1v!JX@fb^O-uSqL+n}h47`kv z0)AH$)&t}l-w$711PvkqhjKaH^xoZZ0s$f>l5}CQEDV2JH-rKpTxN9@^M+LH8E~_n*_55 zTI^`)_IU3nHF^JLq&|>mh;w#wRj7aGM>6@QN>18{r(rvvW*G&ySdx=>g(CH;JOdWB zlPkl=(fKDSqo$Rt(-Epn6;GamQR}o_B)`2J_-0whOGKb-(!C_Fn0klcDTPat zpl`JZbc9Asp++pNfr@O`+3+Z0F|_2})G5EqPN|}jpD8dy)4G&nui8A(d;mCMj)>cuJe3Oooc1Z4D-ZII14)O(!<_Rh0GKGq=R2xgl)0XxM#KG zjB`k@EGu<72$S=Pt%+xrzXpl#a;GIteFHf@;%RrEGLY$qSNmK8V~XsRSlAviH+OlQ z`(}BXSfT>$Z%?DWjuTakyDf71%Y)sV*F{$IHi+`12nV4d5hx|by(b=rU2z!aenQax zX$S2m@Sect%#P!Ij2rU^nf9Z|8265f#QX;PxuPjVb`1|Lu>(&B>CaC=Br^0(eRKt_ zYjji`64PV);D%Y(n2-YK!~x&qyB1v&T<_x2CO#4OvjzAFC9srCS4tr>0bNH@{4Awo$SEbIo9k8?D-+kHm+R;u_-U!= z0W77Ts{$57vqcM$@sb#Uzf*J_l0f|;5FOTYxYl)OJT*{gzl!#_s_Htd6xEcXohkZs zVJZ>>@ryw3?nJ-95iQOkZMFEF_jEshwJ3;wk8$6{_+Z2;(^%2IIj6*#>9>*M>sHD; zdpU*4zV|pR>m#45uUjQA`Hp?vDtpNx?CVy=`!DmWc>Sp|Ud{}gm|>0?jyJ=F9Hw~> zv)rm)@_YNbRr8Xg+t)4KOP+0Cw;JAiP#Fq0F~5eFeA~WmwY=oF_I0c6B_Fk~TRks1 zrG4G%d&vRq>(;<~g2P5$@*w-VHTIGt*w?M8w+4sJyyW)vb!+Z@n!^@e^5^=xwe*tT z*4M4Im)x_yZf(5R5C(4b6I%BHfrFTN{sB9YFdrYJ>OS1y;|cJS_INFv{GfqLu+w`i zh#(KLFT14d6olL<2)R=ba;G5VPC>|>f{;5UyOj3=H5TE04oiCrRiSukZ+Q;Oc$=DG zZw`~Z6U^{U4wJpR%DXIftp-KU%EGu!|L92Gi=9U4eu~B zoWo&;ccU48ZibgQtnC%mNltBVf*Gc9SjU@bhJ(#;8i#egE6wm@GyI;zdfuC67>-kw zFT0*MiNpHd24z`MWK$!{3(RmEhb_G)%#c3D?#piFr7y2jSc=2eUizlG zFT1t38;5PY^tE(fb{j8!8r_$j>3z=(zv8g1_fIqQ)FwR3TigsYIBe(bY=)yaZ10_8 zh8sBS;5}%DmpJU`El`K#bo3^3*vZ@442N;p**nJ!H*(m;d&~@PnPFUA%HPGCVTL_8 z?CPCphOcwj&AZhMk8#-Dd({l-d(^({9$xzRG==n~Xmp&^^A$>#Im)+A#AB?7O zEr-3l`^=EO`|Qik@zO`0DWoqt`?7m`>GRDL(zlv@*?qk9VP*>HE6l#^zFzv|GKKVg zWnXqbFMUUuLi%2^FT1~&zKcvDegD{(J-|!fIi`@lXY9)!=%r5njwAZ*OxuiOP}?nkiOxYGIk0q^q!U7 zhdzc0-z@>}k*gvfgfQyB?CP0jJ_r%xUIxNtL9~>n{c$t8*I;+mPw=V)A-~((Ygj=C zqFDtGYS^#GiBe?k-4B~bGv?revDe6upO95ETKw-z&L4WTKK>STGllq92fQBg90T!rOOO|ec&I88AEXE zLgZ|Upj6|=DLe+c+0_Hbny~<$_nZ$R$%c$IdcOr^3kym#dUz;On533AHsDrv&W?!c zsD63liwrz$x|3;DjZYVW_F-fvAhiwLdDe11ikyK+W8>FwM0WXWAAzN2bchA*W8bGh zIvTGxL*&y!4S{4Er*OwRXJ2e{MEVF`O{`bH0e$hR7o0vu}VLH9A7tGUFKsBFCfYF6UaK z_Unjz%gx?sOzMfqsi+(K=*~^ewuqbwyNq)780Qy2+K-XfAnlN`ky?6zn|;h!0$)td zFOhKv;0!Wq!PS#F0PhtWEQQ zi2Tmugh$!;!Whf>Bm5PV>K1LBJrMgdsv_h?h&H34EIHSrY9SUY+AJ@Kvi_1|B-(U& z3D|XCQ(&bj4IM!dZsb3k)dmI}k+T?5o_Al$m zShVOX#2!Y`z067YOaBuHb2P!|G{p7!hznut$K;rU_{`f7|0U~)27o7i6p^K$AR^&z@1rWC5~g<>#%1o=^JxphCn~u3lE=F=rFU6S}f8Q zn`KhV^+ZKxhu>uCJ))Ay;enQ!ghelbFsG0oDKYMN>|4@gOR@=1e+$A~e-$RwAOh92 zwMEc*#s})}K*i_zx-zR5v8bHqf>>sW@$lz_zvi3D)R`7_wW8WR6Ue9p>ca5NOxZfrI zv|fY`UKYNPbG%78sOxC)uydHg%g2MdJe;~Nuot`V6@F5yz?@b%UgYDXEBJh zVwf2tUC@}f{r@sSU@~&8XyK4$aGz7L@EzWOtW{EO)GMgwYI*!8e5>JW{G@b&=_A2e zE9)`57mnA-<7V;TtoQeV+!%L%s~B1ebXxW8vheA*&7f}Z&t&R6i@HWpS*>!qB!hJ{ zk>=rB`o@n@QhB`)O_!97#e~*6r*uSj%-ylQM)lQl%7pUhjUC!+pq7)w3xa!WE?&l) zlPssD(TmO|DIAG4KKTHVvV2}kG@?*IPC1SgH*Q@6Qa+SMsq8YxL?4Y2l)*g z$WGxS9)K8kZ=?p!{eq&ZM!d@zKEVDXLCciQ)&fs=@GH%fY2`BI@);jGRo;4u@I1EIH?tVIN~yas>ASsXF~Q5^FpU z9na~{kwjw-t?&c*oLJg;33otq28IuUwDQIe3lJH^k*dZ^iJ;|jq_*+SAVdbsmEV~A zBO*f>X>A;&JVRMDp1}y92E#a#ZIoI8+S45AZ^XmPku#hl!;BxE1~P&pV~xFWKt{5( zNygBch>YS z~teoHD+}-`R4W=g4{E4p|J7 zIda+fc@-ira^x@L#|?-~;mF-KJEkM@5=U@~PQl8QGmYDTQ}mTk2!ENe2+?L7om^*d zEEbQ7et;3qdBt%L>M*Qz&a8;1rr=>DILwVYbe5YPM&|&{_@fpgb2yXRSe68NbE9bS zk1!Vc5qXs(v4)OE#&hOzMEoNzrwLm%4CFga=pHRSIu{ld3UPUE8p}Ng|7QmZk-MU^y>)MUHt7d3-k-PFf0P4w9-f%wfuZZ(AVDa1(Z*xo~Pv_3JM zClNwdlh7}6Ahd621et&=w69kR9ow(ZNUd*}_ZoW2J$3{|eBOT%iC{T>!%u=KeV};wMJ^=i?h((KH;E+d{{)kvJtcP2<>gEzzfCj4xI|& z2RBlGH+>zERF0s(XM@wPiensTDT^zjNBg&PpQ1c;@zB4$dGT-*c4Pkzp%($fYJuYv zdn)R}`?wQ9+?&|U`xZGj*T790mADZ}Ij>@b21He$<6(&dJrO-&PC%^Igiez6acIg5`T zibsmZgCquiMz%qv9j!sYe`lctzPO>NR^W%PaU3OW1X8Hp=?SD1G4A)lOxavc*GlK6 zL^!@QE1mn4Lt3KmVQI@P8}0!RVYI?so!oLhIv>RvGax0md>ECQs4e56igA;G07=WO z;Mhx&hgU+z+?o;fNV4w${?A{FwF%du(1qL#9(X+aT)!i_I=5NGLE`P+h(eni;W*Ze zCj#KL;AcNd8y#?&l-p812x9zL9!M*$PF166Aw*h7e1t-48?$bJ)+T|3H8#R=A0an0 zf=sy9Ms5y}wxML+b~N6{*`k1M_T&6S)?s z7N%>1W7urViSLBBeuZ;4?-cLOqkIK%so;;mE^zT@e1OyyPR4|F6+J9P3D9CT>Om=e9)b_69xZ%2#+X1wkeuau zkC7vAjWxWIy9T<{IEWD&UM2E4jx}RoJ4Dhr;x-OkN2F@t6p#qxJydjfb%QDyYq(xO zME*G<(KvAgw3-F!ZfdPZ7&qyf z{~{6TOJIH7{!^F}fo|7u>RzQf-V#4yeEzL!XvUThCk(zVG`AM1~3cpMKjV{;|})0t4Xes##-<6UqA|A9lQ z7zlhAaZ)LN;SrUQ{Xq0?ylI5`PD}?h+lJ}yt*#?s2Chjs_gX;j+3@?Zy5>6S+qx3B zGog{If~SfNFy&llqAp=3a`pn>;3ELo%(@N1WO1GM z6@{_Pa0x)kHeBXCz`q!u;OdJ`BceW_rZzki&sn*y#1w>@RmA9s96$qXc=Tkz*Ah~j zp9;~D-HO1!o!tj?md7$w_v?CckP*{P{NIb z6Aw+!Mc~&Iht00S%=tUv7PO9Q;l`;u8XNb3-jL;dhQ+{jGhsWP5@1dW@HE9~ZgXxW zx|^`#J+rJuz}G5H?Dx<(+L^?A3EKvl z#JwPWt%%)FpiVoj*v;or`-i^ia8Br#K)6mM)=$1Fj%}7%3t&=Oq5LNgVwD+p!#FRuoYq$D_Vo zKXpPjD_h#s3&CR4#IPz01CVg@V+99B*JTAI?b1EqRjB; zge;mr7Uk+K64$5Ws|_HDGACdUVw0`me3CR+B#y-YtC-XhgieZ7h6YTMhKt0kF{Y#u zAWTps*}jjW(e{^wzZ;%_67vBr4JJe`Mjc!;g?A=aH(n5Sg7Aq&(jtpuiFM5v-t8_Z z8P)fDU_TN*iJBO%)5t6po|Oerz%8U}7&dN;{9|i;!W|Ia7QP}Nib4PrEP@vKen|+Q zC=zds7qXqLfpx)FvLowA{f_lCI?Wbz)IJ5t_$`&)^kW*zE@>$e4QfW0Qux2GYk(eF z7gSSk23@8FCs+5l!APkhv;~m8oXW3=h@90eD5GnL9#u0OE!Yn5Q$^q@6wid=y5|oA zgi`>2Rs>!bkHFUCG{eBhuZd!!$jj z6;zbyqB`ZS30asH#Lj>@uSK|K>7mgW{7RIhiDxO%HCOi(o+PxVkz*X?YD`Vzg+?|X z_{UrECk!ON27FmC=WTt&$l;pnRh|Dr>W*iT^K}n@HsS!d#}uCzIN8|O^~9EN5Rgo9 z1%z9{q&1=~E{I*L^@uw~g!UONw_a4+*-1925!iv+behaeCtdc_3Y78DO`7hY_Ge*H+>AGX$Sk z2J0bZo`IR6FQDlmM*#Tt@+cT7G06KQwmh78ma$WK8q`A@s{*X22&_8O#HX0OLw^UF zX(86X#t&1?!a7pVeJnyV&Y)(Lgql&JYp?Lk#Qd3wY>O!C3aZ(=cFlk%7ZBP9NZAB@ zS1>1@W$Y84T7RMJ;{Z=t1TB8W5VZV=@NMb|>`$aWv@rSjnVdV!Ix2k0<-sygz2ewp zHE4LG{!bHLRCfC_sCFd_wi-zPB~CByqz0}JZTN#SmiS#X%2;ZI%Q!Itc_&c*{Un(Q zma+7xKAs~)s#pm8P2wCyOc_guo;2;5ZGd*!uxTu{9U#BDE4~AC#)eH}Y40@CoVf|; zo(-GEQhEndc=TDQJvLdkGM4DgQfv)X0#;4og-v5AcZmsS0_tePrm^%O$~2aq1~kTo zO=IcgF4I_=189K_o5oV#2jf2=k+oq+dNoFHRqHry_3ER6>~ zO>u&ZrPq_p7B2_>j^ePkTE^17L8hDoz`s+R{~Aj(mzu)<1nHq7{?}L<@&wlUXlQR? zY*Igijip14gfy0_f>4V{te{wd=+XptwxL!T<2*X10ZgNe^O)z9l*crBZv~1NjhxR@ z?$c@tDAVYZdqQglOuUI*KGO)EXo>A4jHiw=jY94s&rzg*hm8f>rjaKCr^KsBzinap zOrvUlBeNIR95L8rt$v}IXPZU_`jD+Md1mP?61z;mN5*Kf((4>)3g*ctNidD>*E2~2 zKp3e=_0co7X;kJdlQav2#fl``XPHKCZ9)~_2l!zyL77JNyyoKZEeNMAl5HBDhbNd9 zmz%)u5q_PTs7#}s2ho-YR4g7FwPH=((d{={pHGNT@iXd2Rn|m&6ke6$IxRfSr$eQ}f5H=t*qry3 zVoJgf!gCF)yC@H^ibc>Q_LK0eD-NtV(mPm~9>R5J=@*433Emtr7~oh%2(t;7h3B>1 zXxuD-ixnZ72y$Lr5hZ(H$N$dJ3b7RgofiwKW!FTh?umHm2H4^#u+vIF5fUItH$|xy z;aJy+bQ6R}io~Tz(ruAArJG3#zz!*nP4+|yThc?3xNM?Hss%z5MPhxG>gd$H)yA3X zmIJ~7i-gbYB;$k}sz-N7FwZxWflaq@KAA=6aVP%8MK@JX z#`r#9hZWu|65~1J1p4U=(k@Xtjipp&QyuDOS@wD&PTTZb3b)XCG|FEK%FwkT9s3JF zNdhZ!QQMxiOXJudgI&hVuB7AT^w6#sp+obpBD08JPOOT$=WCc*qAh5husL^AeI-^! z_cVP6%%Mns*1}Bv=gjH4XY2}Oo&)d=Mc~thlu$$W+$8H^6Tn@HkY|Kz>%}tf!PQ4y zb^?T-6p5uovh7}9_nd}5iEa0PK@ez!^DFLG?)S!e^v8AaVtEo053Hz-U#5OF1 zGyAg_FH;-kbj57Efmelye*qTw z#gZO88yjv!WfB0EQiR~y_yQa!)D%$MLwp0UDgT-nAXy#T^Y zMGBscF}2|qCP`~R*sMswv+=K2Owt!1oK&RX+4xl%bA7xH!X1lb&&FcCO|6K;V8meK zKG!0x*?1?~)QSqg(g5gnic0%v}OdvJIQ|^pkfqX%N>3)YOJedpa3Un6U61Km!!4?CBzK zny@`R8Q64%TlVw=VOr}e0IjuQ)1F>k-?XRq0{YB`O?!Ik)22Or9?&HlHtp$v(4__X z2$1WtYPe}nCu8|!=VM_&#ckNMrw8h$-B}Y*V+#(pr!PHga(V*KRh%H_WAVi%XA1B+ ziW6i{ABP^Wt-KES7R3p&rcua(+#@yQwLl?CGkwlAy&^{DECQd-}_|INK2BxdM#m zma?b&Ho>}61nDKPv0&St9*T;H>PT;BVfpOo2P2TVE5Lq=(4S_WZBM_u0Z+uhIU%I! z(OV?8SdVX!f+Xf6&(h$0k_3DD>}r#=6NLSW#OGm2f~zs%Z*o%O! z*s!@iuW4oqcl?3pKe5TO)%r{ybL8yBfR$0WwLY(yX~J~@HMU`MeU2<9cGbe=5*^9+ey-}VRmh!4Q_%G%oVM7w2c*wKl5Xg+E8a0{k_1AkBJm;}$#-oY z>9J+Dnvz<9&{>hB;mIVIBR2hIQ_@HfCMuG=l3`M)Beq%%a}8b$!rO|(>$EH`$`O0_ zyea8p5WZ3*+wmcpagNw?Jx%6iusa%RxvLXmo ziNva^CPZZ@SjHdQ&5MI){=$T02Jo<-(&0q#(Q;ERKv-9h*Y;P;A<$Kb@pL4UZq@1aofHwX_EDfr^x)M9f|#G(O( zu{kyJFiSHK7Y8pmOf#=4uv#{5T^zK&Y~me(byxU*E)McscQrs&GCpic9f$Yme1kld zCY=T1 z{alG$`kHu_5?vd0PXM`iKF>XQQ;jo(E_J|mhqA9S#ca??+5s;B2f9({hHczIs2*Z7qHHGExSszSE(3$zZl2qu^-DN+;K}S z;e5kFnb@tmp@fn*(0**rH>sLTuuSYzaO3kmiKf6?6Nf%fq?YiHCzuK~0MJkyHka_0 z8_e9(0L`*ta|v&WN0hkUYXH4#!{!n`t+XlpAfPW4td{WPnr8M(z@#zv zjKck`C$Mp6=)p^P3!HYixHJ&zDpK$g{*}X&)D?t&iWIzrfA)td>3I<5D$-+?@P5lo zaT~y*n{TpW|6Rf-)Pv$51K|vjSW|4^@ zt>Jvi*lvEuB$WiAf+7hlOt$m-?{FC6B&it)?G;J3&oZ`u9R@-!z%juDWo$4x`=L&Vj}5nmjR zK7RD*6JQ4vKCtIV&6VXSvK)_I(M%FQ0Q${>($V=t5JuDcUrV zf@Zum8aWLsQFbG|p>p*C@Xq zg7rj_F1rqRNMG0>NO=$V&R|YF%UJF348}QL90mBDMbIM0J_Y&f9Y)3@=o)MpaShll z8?T1B;M(l)^eklJ;i#M+8&^V;v-3ko++zGsgBl~s0ZUc*5|Kp>*z52dI*-EX{o9<; z(wS_OltG_*a=Sirgtml3-+Ue+o)TSOIy^t&CV+Sm`DRnz6l%FVPjJnTJB-9uX3f@t z@V-UTq&NGdBjP704A<;1u%m>hQO`v-!NX9lvkuSDv*ua)H(=KkenrqDH^awj+B2WI zG*JLmi^L{tFcC?bWHf+%#f;KadBs>wjA>#tS~03BT3**etd@5|Nf`~dYFeV}f+Ljv zAlZffpy2w&5krvHMZ-hju1hlJ?y`F}y5m^o^^G9>0zB`wFYx6gPcb;Z9$? zS7?!_YYa9n0c~u6;t#`LCQ4d~W*algOr?Qs;S4-eo~ zQ}5~kYGlKvUE2uHC-Lbj8&F>xHtpKQubP(UL_kw)*tBaWzh&CBZvlE+!OE`fP~6P^ zF|hp#x9r;PlgzWnIY2+#uxZy8!@|!ekNbf1hpJ_!T|2pwIb-4h6;-gZYisT`?Xl{> z>e#qt*Y;jux+b~+>uKYbUE89pX^lPy>?I4&XV=y`ZJLM6K-i#2!FKI?m{Dwv9suE( zA_d#E6Y$&+ldgbpOOb-@T79%h3WvJHVdKFzZ|hjDi3d|mQe_ZmMU^DEUNH%-3A!o9 z#dQK{hV{?A1CWEqTCk()10?DEx2>I;V0RbVgmV`;W z&#@fK0G?NUwijkuZ=?^j1lxA)ZuknsB&5%`GcLv z#}DJKAL=i@N1ltp`IKE-D%B)C1i^#;kWKy`N48y?xm`#}#gJT9k!1TUySCBmC<-qT z)mjA;lwBKvOH^J6@a}zWs712v+6wri1mn|z%_jUREi=lljl!}j$iUxd<(12?dS12p za@61;z%MLC431j5KU!xl-S+|M zaA=vZxpX&d3x^Tqjt5lKhRvn>moetrn+~Y94Vz1MI=l>Qk8}dmL&0k4PU&Q>&Gh1n zi3+!t?&RyH^hJQ)vSD-SZu`CIRo)J0w+)+1ck$^$dX-NAI;&u{bf+ya7pL36?%TMv zbpHy|gk2JaaLJi~P4-NrwRG35VCJs^td51}Te@F8Yp!cuKFg1}g21bg6V zR5C`sMk^uR|AXJn9C)>KSI&g^PmuEn@9bX**JE{h2LM5xfZR5^kxxhsNS9iVkN zlz`Y(5kt?hxx3u?8CKr!BA%W)i>OW+J%5U@s_lgK5aOK3?%Y8$S3-Y_u!8t^RshMx zEr|cBYT|cASkz=Z0S~NY5Ux{c`aWCIBM}xJj~85kHTE$~e$-2rSwIg9X^1=EU@cP2 z!z8Ia`6)bJy;wh7q8vraCgAjjUaqpvoc$siZK@fCrN9C#Kg9cJ;8jsI65Zfwt%vBn zafRQdA+D=EZFPhG2&=^t=xL|BrolK6;lTaaoZnI5@o9v00oETYMKaQ>SePteiHMqr z>h;tkht3A0iG}iC@c3SuPOan}{>G@6z&L){7?Q=aG^Cz7!gH>_rcFVSgH)GVlv}|R#hL` zJQC4n!uUK!XpQxuPu>N&x$v*><0E@|F{+K8ATGl6)T6_MRt%f04H**pMCuJmiV+*B zqpE^XFNmZa6|MGxTTHx5|Ii{$72UxYV6kMEMbf{j^*AAh|Abqm-$B%55T+C93N<{w zI}La@;a`p?K*Um{ud)Pa@tikJh^R1NA0hpqg~RPr?)7QIs!e4N{jO#T(U@7 z?g5|2TUO@TiI}?tkWPHn~d)NjA$Sn?M4ggn$$U5wL(*5K&M-QA8=C zD2N~m1W<~K1r+QBP_g%dwdQPyFlgBgDeHr`# z@b-=P!?73Deba)4Kc zVDq5;k^_Cc@2!1`mz6O*?m&+KSQmjRzCEAZ{lnUkWPp7a`iD7Q9{1d<^6aRc;r%Ll zv(S=_eU7tAl`A*Jcen$<91`Ikv<}Ee z6h>|9KnVw$4Pc=_t}JMV#1!9n9vUC8U1}*mqf~yc0B~&xGMnvjCrmbpHMY$@ivaLw zl4K*N?I}Pn2VwUXhCBWr?G;{ zuOlw7FLxT+BppWRjqq&y-q{=O6*YAd`+y!AN^mfU9>!;E%k!zWhJrG;RN1z-oFV(LS9Q)JA~|@eN&6$ zsn9sj44>7K-~#*cu^hq~6oFzT>K%mGVWFt0xwJF1x9oGz^LIStSz@`%AAa6uXy@AJ z=mM#^B9}^>hGjr>!hc}f`}U(@;9j%#foJ%C3dX;-G6$@>5zbY>$aPgGAAtNaR1-9q z*L%OeK^u{%@tS!}7Z`J5lJ527`4m9Aw>Ur3lGqHywz$0Cxs6`_UQ`f{IhUz3+Y`pF z5wnSy6_QM}^P1lLGJp{<4-Elk#&7VF*P~UI*r_2SY^FsuDP48%>lSdZL!zefO{La6 zWED%aOU$7yyAh$gCAgdkBJVmpbG=)|fM2sS+asVn5C6?AG46zsM5hX!Fk;-N+8YvDAtGc}Mqhb-?-C0sL|o@t_6;fx{h`@`Ee z;=4{ojTRXb+ZLX76<0eB-ccdnwAAyK=-zaDIz{;tv?-gv79X+>Z6VA7r(vEOAKV zC(@KwQH*`aikKLac!R3r8Khp51ix_pMd#9_O* zd4QCdr8lS+KC)C{-<_+In9@{LW05;;CUJs`ly={r8Z&WP;f2Su0@9q%E#h3-rp8&d zD6y~~7c&;}goxGB)$nImteBLo;OE0SP3&cwLsl#_miTZkFrPo}90k@_mb4RN8-?kLkZKanT z8Dv|xGHZ6k<%t{Y74814G~J#uWatoB2ZU@>BU2LpOYG0AyqySncF1x=JFk8RJ#h~7 zK%#$)tXvAdGboVuCdl`Mtl)wJ(FYTQ=fi&n-fJO0=(4wTqo;>bYaU>s zcL%cjYxuuyiK$smBawJG_18T3&C2=s5-zwp*X?h)=@R&BO1m-ccKw6x;P;O(%GhjT zu3Pp<>LMx@*Q+`N&{zRaC27;r^|~HSb*02O_H0=5b8OdXTbuf&e^&ZZSXV^snuFD+ zdo1?%=_TB<#0D4f zYosdiVdD7|Hm*YI-cZs6tWAkYjB)I9klzhiLGQrr_y|B>Bz8Fw`R@Sy8G&5NE#0u` zo61iH*`QO|oa(d^=d3d=FO@AnR(8HR%kBZIPmX=bzOer+dl98>%ry+wVIkWSiJcU} zG_qw&y7HF}aNwJo`r`BGPq9s}{T zP`3!+R)!L7ZOTw??FQ(ZLJg)^GyREdd=)t$c&t>_Wcge&9<14I#?>P%B$FbCV!To zi95ZfchUu&1^854-WRaEpJaM4=!bvta@HrHTn7J!Ein~N$K77j(KPn{y@vcz*`bm_=%G#FayrPT|*_zXLVXDY9}U_ozY*!5^l zL~rnT%)Pw?jaB(m864XMA`4(@l(l*7?AKHu_9DpFMV7cZ2=(}xV`wT=N87PEI@@s- z_EBKZh0x&ohHqL)3Om`SX+gsAo5Owu?gwG@buu$t1CP8Z_E07RTtQ;o;b5tjeA@<> zg=rjFy(4Egj?>lS++X+I5C8C zz{P;hjr0XWxV)a8eXbqX>J>G1pgRDp4z*>6a2j6b?dsWc<0cx1I`De{KZ-O4uwx6T=OJ(+uN_d<1zWhrS3Uw7jQj?_qjcanFQ3t*O;C-Re2Hj*g z8qGV2hhcss(bP%24SHiJVce8V-cbAN+tKZbIT*(uo2Izn^4-%j{LRORzhRf&%APA8 z60#~Otk)S_uL$dk?Ty}$?to`o+jkfvq=@OttgF^cO0vCgewK;%%8>;(*3iZ%e=EnO z^&FYf7n!ZCJR+$F$1ff9E0Lp$HF7H|A5AZ}a^mhwm?JD*eFWiYMHi4l*$P|#1+n__ zmw0kk*n)$RnN^|9{lW8EIlIGpW@8?*O0@FC_#tiB(1qZSGEYPCpq2=_$MA=ZVgpHE zia{$Dq;U-}`?sHpiUmm?0P`AJm-~{7SG{bAA7XO zF2io}+Os@Wc}4Ly?0vU2&cbWsrHtL}S%A)qbl1F3u=4)mbMN5}Tu=Dch}E(eIxE)} zpUyDNJ-O>(JtwxCAaY}@%I6ZrE3;kQO|X6!+gJO8E0&;m}OF9D8#+{zs$UKRdvCtHVl9JQr4^5Y@;_^jGg{l zPcF5sixt`F7r#iX-8OmwrP@wkNIqA!cd=SK{jI}@)zr@0r`*P+4~8@xmp1{Vso7Pm zUh%uX!mgtspAfQwe5iT99!0x1x)3|;neZ+Ue;T5mD?Crx*Iq)wekBtcXPcgzLIBeM zw~=M#=WOPEs?xGkd3?&RZ{wN#R_#xrHR$x~6jU}YYVs^ub6D5WWK8$Bm_%t@lDO;< zj)6gqg_HN|E~J&mP0O;=iD@WF!K#kf4WwHo9);ZtL2YIst)RwJ%_7yW#Ft zQ*)txldI52G4BKMSg0b%PRfsa#Wf{~PWLq98p(lM+K~f`W;Dow+gM@v1<@Fsyy|`= z-Vra#f&J*RrD3&>*bSsxC6-rk0t&1hLw1w{Pn}@gJm)YthlkvM$$_q|JTnvi=@G_N zOK$8CT;N37+)LqJ6C$Du97EwZDOK*PZHBj-E>652^hJ-Rddl|bf;J#l*5P_ebg)SD zw%0~g+vy{B))?a*uenlOI!hW7?|O-+DKd@Pv-E|_Q!Vcuuq){V{ZaUP(g5uar8E3q zTVjG1AeJfUc5(26pdAjNK?@M8&Y!lBTQN!Gpr@x~W9@C<-?`cIA)gbebgiZ<`!CoB z=W~m?*nL(byo!vlHv{`$2n}-NW=|91u@h~pCArR&1HTFIeS!DZmE02GPPW(njw1R} z4)Z5`zc#$&py#8$RP0>4_vqY|zQc9^*F{*T?^e&@&ainOJADa<*$4iJf9O+7AKT3q zj>`3<9JU_Z+(@76vBn146?C@49;<_00p!{oOg+}v5WD!mY`aH~)xp*Qc`gT2k2SWv zt)K}|W^%!W4)zt0A0k+dgY9TP(E?V2@>7ZnZoj()!_}jW9bo$&M=eEv1T}e1O%HH= zLacetYDzOU#Wr~^+uX8(;BW_pmq>v8I`%-@DP4HnTE`zdhxQ+t zp!Pe%E}IT{Kz!WwK_d~nE#l1`KNZ$N@li}A#h!@BXV@=0c&7D&_}IIH&S2~V@m#5! z+l51dT3;H!i@B88uOhn^@C-ZtRAXA-Ui3K?i{>|CRhZ=Ii&x&+JB7lxc>}0ZT0c~@ zmZforYQQwT*B?#StxdAvDjvAfwKp`UHol}fV&>f>k+rc zCEp=_ehs&8Gvx2YQu?Om8d=skK57H30t_#~Iacs!wS4SG_&v+Vy=q?*>;Qo~ zCqzWmx}FJ>Qhi;R*F3Enpi|)`JRw}@G25W><2em3^woQr0Yb%Esd-ly`f0-py@USG z>##nE*bSsxB?cTt`w#1nkR4sR#WKx<;SZ#OXNxVMsXwZJGWPxlu$7M!BoR7&)dpQbjl#(Q! z2d@WSc7#*VqQ|vJ9Q(>Gv}fx)_pZnl_SkLd2PK{iqAP9CHWd^X97L<>Mkn8*t3lgT zkk0pRCK+F=bFP-CP|}HKy+rpq=q;gl#s$gerXlX|`V01gSA0evy12)o)eZzVJj4dq z>z4Kk+O}!J{S24(9CkXmIU$w`R4bPS)plK{Y;TyAONY~{VP2iC+N4D2^dn0W@H<>v@xmndsQpI)9gQdz$S`ZH4aBTZf~9Y13x{7?2Jqt1h2-wU}xawa9} z>_g|GyRgXVZrd%=y|?I(e27?iR~`af-UoZ3_)9$Fn9Xm3xjl@25%YU7k6MPwh0PNi z$Y?3FHBwdBE3pkX)gB4>m{7n3teq2MM#5SE`K*xTtzeQQ%kQ6PM#NqTZ&k$Cj6!gk zfs(xvd+q>e9e~%j1g&F;mDRLg;+@;DXfuF(Obaf_X%fh-+y%>bGe%+as5scKm38GI zILtaxS9#-~=SnY&?*up47La;hFBvWVX%S8PWfu#M?W;K7`9#s5^6xyE9@QxE&_H2;=RDd6Aek zVium2xcFK4-@yAl;=4`RGme43X*rr@g)Jmar#if7QD=WZ^IBARqOZ}s8a9(2lK==#cofGi`t0ulki})vLRKc)U zhuqLAQD=KED6fupR_(#%hgOMx@_vZD59)J?-X*IZc2`on6K*ilV|JomGE;2(1!5LjkiVK%6Jh zXJplD&MGl~eGtXx!dMY8-*HxnS$K5f!tW4X1@FO#@2skE{K<*OCL#Y4ymuqMdkSXk zRl)myA0WIp4Es&U4XqaS1FO{s??_uXg+i>Q2G4nJ`GSvKXsc;TFLR`Qz4x2aN&0DD zpM^;2gAN&Avj`bq^932d4Sw3!hbmHPG%~(6x}|;X_fF|F{EQRK_&Pg32R)$zl6z?j3cC7IHgmg(!M(vQD@_U zKt;w8KuDdA+CwvfDV_6_@pa%5A7b1LLB}$seRn>k&YR4*VA|K|lqntTNT%rGblz&( ze_ufzjGFd!#!5_}*7)kqjKR=~Y7&5+2Ysff4Ivdst>uFzpO7H1se7(}2 zc7954)2DsCmz~m!>KR|}vu7M3<3IbH!ul=>pRR$P(hJ!cU+-;a93k!N9p04QR!;kR zt2w22j??~71@&TX+Sfa%DZTcZ@%46W#u3V&;HfIxwtU!1>JVFgwh}lTOktu~T8%(q zqR6ct6()+^%y?mEm zHSN2Xs`NTv#@B0q8AnKG!>4+JX#Y$~8)GuQw$G$}?JP-YD@@wgt0pPEwL)2@QB3JI zD$25gdeMZES|DEpN-E*BuMG$(Z9vKR+NY9ngp6O%Tuf0?+UJq>wL9<7=}* z+W9H1O=tZ59YKCd%a9pg>&O{j8I^Y7l$JYr{2PJL{_Tvf8RU$w{ool#CNBu5w3j*U zt8blBM?LLp^)IDm$BeHf%Zwvre636j>_I&70d}SbMCzd(RrrsmIpZDV*h5Mj1)^A>AiF#Vkg$X+uQMGh$j!8uywP< zTU7V};q%0M76DT?N7S<&^#)PpA@R8+lC&c;W+aus;}Ng!%p$0>9rf*q+EL$snNeVy zXx#p@$j(y?ZBlI(NlW>ALO6lfMcAN z|1o=#8U>m1N-0ZedOYoG@;;^M?ty&~t2(5E!iiW>w$yc+&x%@Jecz(nAeSEUBIU4O z$+IiCZKhnT`dwq^!|C+IEo|DU-`D>}uq&uy>jyW}rvy#uK^r-~V2JxTSW)SLU&GnY zMM_Pl%sUbq>bKnyq0fBJ)DLrC;WMS)6mY{`#4jzKfQAEHq{wu-e-~rEsXxenM?(78 zSpC87lVa@MVDnD>NT=K{-J2IM>kn~}CZ&7a4{TJ{hHhsNIW!~rveK)`iTcBwg3+aY zo+L6l^8nW{p|l0Y)sLy(3~XNM9!AT?v&oT7{aa z%|tab#~gR0sXxwr2G2L8{f1-JL}!&L{hlhJeo|S*^IE}dudFzxR5z{2eYmZDvV)bD zb|9JhX$9w_u53R)LF9PHDf9Mw`VL|zxELp$r2V7sg$27)-`m70GHzL(pE|OQ;n;N-(wll#my_3@*>gTvflhR|AF(+2&>Wfg=zBLz? zS3kd~a2E>F43227u0OrW9y_Cb9FY1mDs@fSQgvo!@Vokxv!*Zl8-1s^h+n!?kyBlyN$Khy z$SibsyQs7tQ|r$v$RnMy(&409e{O-y-l6o!K19xUU&k9z`pdCIE^4By8?xWeid^ip z4Y8luBgdKgOIxb^^Fj5t-;&a@R?6)Gl#pvzvUDjSeVD$=l#gQuzkWqb**l2+HEurireSD~=7 zG_B53VPz_Tw-W89Itj3ntxj}}24-jr63KDF}sW;*258ixov z!w(Cw-Vqd}`z^!=j_@TxI^Ir`R*Oj^=nPc5Dwc`Gl;$C+@m29^ z&O76)9p{U+(-Hj65f;u6A@H@@mEzSSa4ly_lL%>FYfdSxmSub`sipltNjL3(dxHG8 z&qrn_$Gn@IO#7*eAnfZ1;=O1ez?@HPQR|Va;YagghJG*FCn0}oTmC{rl86n7$hSiN z%oe^&i=2oJ$dc*VQdsFOV^!)#r~71Q;I{QLQ{-~Zcenm)qNa+|ZgC-{$?lA=nfZ(( zq+K|r$@75)%|QQMyN+<9Lf@IhIS*-HFXpFx9T+gMkd@z9DyaiQ5-~s9GNt{VB$?{G ztCT)S{hW4w#@9S9l~)I{O6x=!7fkz_X-*yD2*YQ~RuR&^=6h3`siyU$23>EvP(eP6 zibQPJ=$a2kAa$_=ZI~k|`5D)pW0*rerL-3(m<@YzH&!ig!x&LC#entiX)=kdd2zy;pjrgRLpc!p;TuW(Z@Gc}I4J`1g9H zPdV6zY{rjjL>cv0du94ybs}~J+eq}g+tUX%{YSh)?hy^KeuYIXf;=v28RT(MRglL; z)j=Nf@A9~)b&$tJH9;O1wGHyPs9li9MeTz;E~*XkxTr&r$3=BP9_s?n+dZ2Po*8)l zIvVL?OHYPGZe=aJ$2<2@+Sqx88lYl$Hg^h3(t%MWWr0!rJELruzI#VM6&P=O2gchzf$?@nXMACC;~<^lCXVik!00`)7cd-{ptp#MV}3J_kRR40fNRDAQo3!(YX~3q4$K)y_qu?^oGBHZf`|T z#5P>jKy?4lfg4YNdQ6r& zI!kpII~ZW_bPUU*Eg&{<`4BF5mK7%vGnX|mY!)PUxfh$%(Sjxp0lLD9j$tdZsjW$P zrg(KWzcwt-D$w)tK&T*L20pL>n_euv$;obD&yjw6=^L^eLsXpWCp>B@Mie)(88IkR zEavJrY7c2h*$RCzn&h)8GX5hx-f3S)8>h2#PPZ*mJt*C2|Fg3c7KI6*B~*dhT#@nj zb%Uxo=Rg?j2;wnSs1|XwBfRdK$YGa2nCb}PrTzI5S>OmKI2{k12jM(N5HI7uK2NwQ zXUMHe3zFxmd z={2uGE|~H4LKw3=7&yrJ89%{8mv(-t&0b;_LKx)yjQ@~h%1`xk%(adwKjTkZCQ13J zLmhJ_!9l)!ChL}qDL*yaF*Vtq(!~BCUqL2HnNenxj-S%Z`XCp~_?m-H`gJH`_Kf7gLMZo3T(E?eU7*#v+}_r zhMQon8}lRUQK!4N>v!IQ6|9@om0ro$j^A{mhl($$Kwc{$d{5?UW9B5R1Dp^7Ox=T{ ztvRu!qb?Fvy$n;QzQyd99rapKotK@iPzkMn{Qy={g6}NXJ$5@Y?p>W_?+NTyUR?50 z<}{8qbr*kP%}J$B`WXL>yIn>lei z2x@di!5UWGfD&Wo7dd{V_zkQagt7&VoITryEEe)N7}M;RlHEdOX6lX)k?8Lw2Z-Vl z?YMhW7J9wlzE2KC$C|07>o^CrP05)e>0rv7XX-8)%6g$%bxYGY2m*rm69NX_M(sXMR(QrA2F{t;g}M;dqh2Ct-Kazu}E+|=#)3=33ioZOia zKP&gvEtId~67}SB+2TUxJ}><+R&^ntPpom$YlYI4k2M|3Ps7-wyfQ`K3bmTL%9qHq zYDfJhM;*BzcQ1Hi8}Ew_mAoJe)Ba?t)0C)HGYS1Lhh;D#-EfW198OC?z*Joh>+8>q z*_GIS4O#A9nW;A%&vN(Bl5wy+H;GVp$r>naTo#`cQiHyBdn{j z9`GvGQaH-1?~81hnt2}d_9f4RNHaD5FpuF8CGSR*x;C6LH@3Ue_`Rq>^|xmkxG|2J z7!p!fd*?4DiK3FJ+j29ey<5^oESCxOmzPoR?$SbUDb}}WgTc+5!ROh#KPRJ~uq8tx z?8El)7Qv4AX`-FBlI(CH=eY_k?Jt7Gx94dIPixYl@9)18o1TkD3Ov5}z?Sq+Y|jPzgZUE{B=@sF5UC~D4wt{n z^EsiI>55SN0&kc-05WZ^8HTg_5MfYlX5kV)-=4E2FA$jQ?S70>DZGQxE|}{j6emKl zF`7*@1=nUTtltN56SKo@SBQkbfUhh!t{JmpyATJUO;j(Enh{-@F;rKPb z<`VW+MKDV_gl& zdm*I13BB=t+wexbs+Jvv@Aez}UHM-+FEHoh;E!sggK7%NcZ%_WH(XOyEFq49@P#&i zRqlNNFeSef$l177&3T$am}jO4*1Dr@1NCm$cjC~s+sL@NJ)5R6R|wQCU7;lRg|h+B z8@T4J8o<0rUR3$R7bK*cm7K0iGdmG3sUuYvd(=#RMgb3__Vr^*av@}p2T6%!xn z4OyqFJq1{0oWUf{_}e!S@*|-zAqm-ifb;{r3ogDJ5&!9Paw#RY7V7EM+KoVtK;Uo* zy+OoZKRqYZXltQLZzuaHkVOcbA))t)6s*Z+)}rOE;`f+=cfTg@Wf?v^$ZXM2UM`R9xnYdQ3UK)>NtdmK9oVFiA zeGK+$T$vOVZHCgWfMSVj`t%cVlQ%#60Gc82(}%ZxKncAIXDpCIa2MGx8sNMJ67JGX z*=8@(xj3ta%EMDT+-%%}KySAN4%PIUOX8+&l-4|5)RApQjU=traPB0XTX0myoP1Bo z_>)MPP(|yY>;q*_T&wjrasEx1l(`*#6{oy>3iWE(%W?L7B25x>8*q(oT*6MbDAh0& zK4|4O-tz5(F<~>XQQ$}7?3+YRA+SJ^XNlZE;5wZD`3X5~xlpLBBF`(fbrh)eU|*1M zIgwuo{G><`krs`36&{CW=c3qT^C%Z`d4*SA5Krx)%((*C?nv~<`8SY$C_}l+N=GvF zu_C($$T46i;^O}k-V_vOfr&Fzg!l8#86lCTudcLa=WO11<{RCS2|Z3#q94M!00rma z%=RUe$?7l3hC{d;Ng_`|ehk;Nc4*v8h%^iV)B1$k1;FgiaN>=*YPoHR^dqnnuFRnZ zLTP;fUeQ>*v!I`WYkmTe3$yxo<1!oCTh{Vx{cP=E>Ancmlfa+A*+oRYBCr`3pG&0Z zD!i6WsCsZkw-6aY-~gOya%xtihJQz(N{?taW|y*L4H%o%WHU<<^6tQFbWs0%;5600 zTq?Srcx08E{=VeImn&JK&8nd>Qclc`?>P!dXT3tgmIbzu=E$@_8F)* zz`jP18+g`2NL~P$-ombi`XTK1al!K#V)7%jGYhY*Q7rR_HzsQhgc4a7K99Lj)2slN zIx8z6l;cWdWeDe_$ZQUT@X+GjA@mnq7Kfm`C=|i7)*lUF0xqyV#OFu&hSqnUl1($L zO~h>WWwuJxP}ej&Aq%o##88)#~C9XCwV)(WMBG&IYbx=FNbvO zAt07;>1W|eb~!c&XGdWvQ6G$&231D-SY!(F#9R|dUR z%_J)SVMi}#?p;uilRV}o=u$U8{3Cstn+EYFX}>OWuf>=UPL=ZyxXir^#9u)F$eOD+ zZBX+#$$4+!Idki`G*XKu+D z%#}RmE~C?#LxiC)eVKa$h+{~5qRf2`V?vM@g+jQ@eH+BNK$m9C)h23oao3Qhw>_RS zcRkbR4a}86Bf7KcpZ}r>fuU`)O6~HEP_5)2MCIMMrox9frW8Pu!V@8^x*6kKT&uEg zs9m$_I3_!Q*$z+-hJ668pnaCDDGNQj3}49U9$Bj9JD|qC$5DIsrT?s?N6ui=`B?h* z*}|2S5I?*8K&Z~t^HIJOS0Woi+}0A9?d(|(^f}xfL$fNg_9$f(_tgG*Ht0`4y*C@r zW#au?W}?!5ccL+u@!xl=Ol-$Qot1n^5p6lHsqi7rn$aXf3XMb92|%XeTqcGvFN8Ux zZ-adkuHe9b&BXElory}i+nubgk=p33GI4om!)GY}80RuE#BD8s+0LGJ#WcCNJ*NG8 zChFA)-o3a5_1+phmx)(xnTblf@)2V`1IOO{&0r^08NP^3Zt9P+xo5K#9Vw&DktX*_B*rvC%@RS;JETd6VjfPkq zo`XLD#MIW0rAs=+sUFrq^&lvLYBY{96~Cs7`ewCP{MsteUh(TTq4s`gKS|n8-%|T3 zX|8mk{O%UffvET zOEQ0NI`iC?STj6#k%#x@B9klI7}G*@wKGA%xr0opoxT9A$3ts(SoY$h3~oPc5r=WL zv8H-y2hX&s&g*bLMpd7^E7eGKUOyLEiSkyh@`_};sqH8e7o#k&>{Q#*9mSDsi&2*< z@vFncol&)a4FbP9!q<51Rybo9VC>VvzY^lDGj^4X?Q;}aFyb_xCah^c^xNQ+j-0Xi z2~65PuSV?ZC+~2PJ}y%G5|;POOS_1(yl37vvb~IquhsHIPT8!!N#UF5Lb~tS%?@&)MjoT*OvQh0b z(@^~+QZ9$eXB2dJTIs*Ikyl0M6g2Bz&91BiMlzTv77Pe%RFqqBEY8}prn0@W^G2IZA&D}RtLgYw$Dh|3qg`{p7pU-r%Gcn+{`chz#^ zhvXI4)u^l-F73yo-Q|lzhxww2N!~P&kv06-O?b}4ouKphOg2rYWL|;m7LI!Dd=w2} z(^JwFQfrb!2Xp+1#MXvlrjPE!nnOtMM`%%I9SHsZkXf}ybVH))#IOKTpjzIe6h={? zDj>&p!gGdwbGymb6lbR&xZH&?^6Yj)K$_aqb)^@9{r@d!N9MVzAjr@o^Ln|mb`8oXWb9NZP(2lTL&bajtxl~TB-5u2L$ zABl@gAigc0sZEXJ8crnmkD92fO-XpHsqG`geIY`1NL0N9RYOzTJbT?#cTH`zvMpPK z>8zb~h{nFgi>%w`c+T2!kKZQP0$#x}St&gorb5B^*rvAR6hKb_a@nHAL)XT%|Eu(j{_cSpYuf+SMq!iedm!UU zps|g3EH;>lTh_F<#-J3B4Qf(P&Hc6#tk@}fp$2hdoD5I^L*EUK4eg++qI6t4Jr^Bx-KFDpSJ=B}q)sd8 z?sAFrYJsJu_DCi1V3>peVG=H!O+w*p5K~Yn$b^- z{IzXe#w8G;5PfaCtm~RhnaIxhU`=vj4KF(&;ML%{%+rJK{fK9(r0MeJoGHM+<`&{Y z##`W-9oNbI|0lam14t^P+2FVC}FDbvlzgF?$LYw4~$Z5&W`w1_SGgX3oay|Vu z#=aWR#|1LUx%Je4M7_><4&HssWP~j)BOCbF0%lxjliUqCtyX%Y@FF>m_#(P-7e2Dg z|M9rEKqh(mdDQ=eVvF${{1V-_~8ZIVrq(+aTH0xyy?Rf1d< z^Gs*{yW`>lnc6FkqI^vw=pOy@?`=hED6VcN(#b{i=o8t|MfB*G|6o(6V2w$Bek41t zkd9r9=Te&h-8~ECGi;`PHH9wY-Xy5qW;rRn3M}ekwD0vk`>-dFTZ9W#TxpM*{vExD`X1KfjBJ_EppBd7R5SCHebLqMHY#6D}@+c#M##-9!D>-3YqGE6%Zff_`f&4BsDe><~Pc zcyOgdxzeK{dGkZMt~4so6J2?hjK-bk^1lKXmqdIT9z&7$n6Mz^J%Z;HZ|IJKqxjr6 zA98TxnJUQ~!-D@y{J)EfOCWw1o~gY-H?r~Z4Q?cfQzOo_CAnsGX12Rp+enwU6X*am z!e(-y5w`uxF=4ei!fyW@5uW?eoDPD3HyqE|_o-`5rDRDJU%=&m5-u(f$Ena-o=hlq z30~CWbaJ^i>CR?qlT30iDue3;z5~x;hAY2nFW4U|zkwD#d}%1r!{;L5!{;L5!&mH@ z!}o`NTgt`#ejoJ8(xg)G($FUo!QWQ%;@EM>d1D|a()Db~#hhR2R?YTs4IWgG%Q zDWP%Ac3gtS^)T4(n$)$G zi*Rm@I<8e~lT6WTX$dVh7|)5T`%|6ThdtB_Z7X$Dsxv{Pn<88*{h5Wfzv@v_yLX)2 znU5&+DZGJZTc}4=J%10xU$b>CvM`A3popnGU!9&?kZ@g0(SlCTc}_CuSDn`*z|QXm z_HnR51+=Z#-;G;pCn?pRqg3B+nCjcMt4ynQslIJ@b9N@5i*flgxy)vG?lKSLGEMDj zS1U&CbhX_jOa5%x^!1x-jY11v6Zf`m;AF0 zJ1^jS%R^Av9og<}lgnGTAj~dQSR0pHa|{3X;^GpBFTpdl56;8T7YK%3zpM2CBv1Ru zn0KLi-{ZOKI+eoXYRtE6MNoPqRrEJA;w&09!^I^K--Kt9S9>fWL-V%9bMoaoM5RYx zeKb`P89bVmIsUaW6&F6<4$mY{oXdhfA@5i`C*E(%?w|z9==;s%UmKy~LYrhIa@zIe zU4|FQnJPi*erFDY|GRNR*^?fr)|0$*@Em;XmdOaaoV#-+|JqU)7uqCW zW80zj&v|R`A~{nf$N(;FJ^!!c;sTlEXMb@0guLJI9Q?7K(S5Aq>P?l_pClosw&nX?$NA#xkz>`jAI$^y+)Oajc)) z20Zlg<5}>6KW_nI{5J9Y>t~lAcM9MA(mbo!K^L*Fc8o8@xKo(6or2q;jLt~Adu{7! zljs}9f*>r0Y-#1_7FBGcmPS09k!y(66cmB<4G?#9J86Dbq6D4#xKO$AwwYqZ_M zoUU9bSjwf>J-#5l1ikTo!1(;jF>HOzCd{nLSBx7b6m0XL5?&2!Bnl70#cwBK3XV86 zxDx{wP$0E+;*CoK0y&tt40GPks>(C-P(_Ap=buDc>tNaY@lHk8$vD$!=ruWXE(FK@ zknBATNjnDEZ3x_g^Izt+hbeebD3mE5&t{p=fxH6tC0u+X5e(DFDHO{$Yh>8tw$lM~ zQGccktK_&6;8tU#xh+`T!IcS?uc@}i*=uG!Lb)@`wz4ZBZv^x<&fZF7+m>wn#pP>u zHWcujK*i3s($0lEsfx}y&Q2q8F@dGHxTb5%q_}7ip;xN8`M4$@I?HKV2D70DL&j$9UNj65Lyfy6t&T8tgKY@NYehCeTe~qrO`fG0ETekJ)`CQj{Sd)MqA-NGm9wczL zB6}05s-f89?9N1{5SW0w*lNyITFQU1nfMt*;&bF3D{m~`#a7e&LbN(2uT{L7`x8&f z%g3|7!rOqJXK`f$6>S4~1HFvrars)#s6E*_%V~Y3WVgJGJp%GI%vN2Fvj-9RiNIHi z3?ee29q-rR%7o9?f=#G`%$AB;R6Gs&0YG=+?Ei>t)83f&IIFdrlL*YjWf}e*-eS18p(RY8mWW z0$1RopSxrIqD**^D`@w=ZG9mWcgMa5{53AFJLc}xHg)v>aPCeWNnjiNgs*$3zD)Di+`D55*Hfxufh zcc((>yg)j8r?%-#X5!qPnn+;0BDzzn3EYE=e)-p(#tzyj6Q*!c?W|X7pMhM^g*$|^ z4-x53U|U7*CUOvg194@-=WE!%G^@gxHIT|rw#j%G$hI}!LXdNC@f4AKO~Qw0R$@U{ z9Awk+w;TCB2|@7rrkRaezQolDYi?R?4ZtOL4AoimSOY$xo)SF9TX^0G?BdVh8DAHsaeZr6EM%X*RyMdElZ2qq`#}T%p*uhfJ2;1L9+)BYg)~zfCOF;+O&YFG+ z)*MF$oX0$qYN(BfarUNbhqqO`=h$W z3!wei)E(X8+|(W2;$Z6T>q)e|Qg`VV>XNrXzh3lJThf1ekKzo;dp)FUQY;wS9B6xd zOvF@4=G{k(`IP_fadAn+pTaZAx8CPg@w~(u=ybw7=wi3E#)nx3lY7fb49o8&I!`3h9T zR0BBMbazd%Z~_ge=x;WpyGe%fxwgfy@!44yA{|r&Omg6%#(V_u?gEZYi zx47}?%<1FeLX$g(lYK zvoS~Ue=II85HGfH^G+n>ordS&x_PEbkfFcQ$n$?0E-sL%-FF{~)UyPm`Kq9DNP3yv ztW(nK8%!DAxP(d=aO@*Ir`$~$Cfl4te@1j|&yMJ5n#{~hUaRYtl3zp5AH}vc_|0Ooq}nSP$9}oIRV!V+0<;#pe?#6Sim>p-$U#cW{lg z#XZ!O3pH|C7>sU?L2q39-AyBnZpLL3rniy}r9&_yo$F+TMmmS1d@L?I(pfSuxE=pG z(y73pld{V4%;-b?}B|BmwhcDp=s(+EZ;1ZVT^PN7o)xacz#bb;H)O3Hxu|2S0-4# z)||U#ubJKjB#m_9OCZ|?cx@PIw8rIYpG63yuk#s|$%~9|w7p_4`0kYKi-=4nG6@%7 zM5JK*tZKcFt)_Zfa48AB%&{oCK~vDCvC2{ztQR;gM8UZ@_X5Xy0xu|{7dU<=@GCA~ zv(BLg-V~EP;Y%B>doi_&b1!X-A}~@B%{`w+;8YxCQ?vb7MHXBk3+SWXbQTtT0BaSf z8zuHCk&@nQyu-zxBw`=HJCxV~xO}bQhiW_{HR`O|qai;E|9+euMWl5fY{JEd5V8I7 zCKEdXm#>X9p^BJOq28?Bf&6<3{?jr^(L}C&Sll{ z1g79zR)wl!PF0jux5B?s0?MjS34El8va0niWIfJh*&zf*;L2o2k+SS!V&~!t-p^k0 zX!m}|Fz>{mtmZdf$^>DQ9bJ)gedI2Xe?;y}oNY~HMqgu&!P#;mUlG`ZD_aUazZ+%m z$E*sbnINf;Y==W0)X$iKI6HvI3>=|ki;0=MAuwb3G!#^^67>fhM3&6~``0sNOF z@C%r~3H+{zwqf+#)tDZ*g5R>~X18pIm&MfWaK>#sQZYS@uC;RON43+`7Jd&cY71SX zq^B=vl!B)5Z(Esb8vnMts%d10pz)e`!3s{Y`(SJ#FJj6;m%xu;Oft3~6}IR*HKa#< zjPd(!)yMb(llBJe4G$&mmZmBxc%(Vc5&x5Lae++o*}ZvtoRD`4o=bS4gA0aOZy}cP1 zK7J*hNglL;7isuGcE4Ak120f`|@sc_OOtP$kUIt)0{yaLpgUB;2Q$?a3Zl?xKzEv$^heV~q+bQgw z&IHTO%U?#ryG>f4sna&hI{3*?6C==bp;c_gVq>mG^%`8>4R{<%jPKorm+|k%PbqG` zbLiw3P3vyuYc)b_#&ae#Mi~Q|X(sta>;1SA!`2KmC%?*j@p+7Z>t*mFY)Z-G`p2c~ znlak?ro@R>$&OZU(9c7nKQ6Ac;_u^`AFPsW_8P~bh#|#XUhDo`@3(o*$l58V^XSfj z5e~52`d0E*>dG^r#?Hrcb_5G(1IgE$AlV1c&*rN#f2(|TD{|R*|G`MOUh zDsxnqLCLdt99>M*YeJA??(tKK=kj%GV_G-J-fno#gmDD^JzuMkJXqM{8e&sQ=D+e) zcGNSwy_ElDxVX}a_rxP#mE?LVs~bd<;#|HuBa=7KZao7%_5og$Kdxh+O4uruEvhv@ z-w2d{K?6{SS8m{1KdJ~YXMib%9qZT-Q+*L5-Bg}!5r95IT`z$KHw2LtEcCvgewDOL z@+lOJ66VMd6Wr1>VV(fVyC|e@Pm%2K0Jd<2P*?EFc>_xoj(y2tCzbE>qThbX}}T*JBp|7YOhLYw3e zCQeXY56R3L#(rF7O`SZsd ze#kgi2$ldPiwI9T+m&W12+L(B+NZi(6Wp)u=)c{{|s;>=lx>}R5XAJQdjlGj7G7ejB1=dL}V z>nmpEqSu6Uce%BPoQnB_q5WeQ>ZS&}P&Z|Fp-wEYW|VYH4|R0efdbU+@9(*&Yi(0^ zRrGN@76t`7JA~=dO)~K(ub7K|ZAgEir|6I6^TMX+Z-jKIjh3^UQ=v!8*>06S*$6{6 zN#f6j65U+pF0n-c=rhzdz64NfJeLzWwF_;_0Myy?#-2cXhM>2wDM%ZTTTD(S&@>#r zcUTCLCQ~JuqdsPtga7HcxIir68?K*_cQ&3&SUOFWARiW4b2K z5HGl6G%uz~kmIhk=6U{K!NmnKNqw*C146Mzmo|V0TcdTX?Z0o0);F-q5Qy#8Q0o8U zZ3T`We2-Gz-ADE+WZMlh`ZmjM!}=SYfz&8g{T5FXzsuW=2sk6Q;* zb6gM^r3lTEKB!WAy3~8i{zZNCx!kSPN1RB#?2FVZVtUTv@!@QVB&tn&JeR*~c~Z8S z0)6NpJ*!np__r@<|M~BaiwhpF#52iNJl^{eicP?C(y9)E0`ga|*TN)U2Q*!vvl@be z@|f=hdoUK+NuSd6dS#ajcuxq_(;3uE-+9CKkU2ejZo=k+{dK~v4{^)&boV^t1BNDh zRem7W=8(0kSjjVZ@P89MZ+TcQ99awB6r!6 z`oAbINqwPD#}s*s8%mlgDOxv;ZIJw5g^LShlKS}I9fV?U;<<$CBlBMC`(NhigNC0- zN=G?GQvWyR=}U<^*~#nJP|{RMQNf;k0hj+>adCmzG)*-=gi!2YJeN>Vm)%c~U;-rX ztdM?`ni#hQXD{1(GLc>fAIlb;LAJq*h3BUDRJd%8v z>SHaz*e7@{$y}9mxZU%8>XM*3JkmB7gkIjHhk1tDq>xEp=ANOGs53k4v6_+B@b)jg zNO;brOur286|_5pEn7<`m3rwlE>7r%k2v2-SM`nuA^GhW$Pz>sWFcod$hh=-Z!W*?ME)f-KAkLp2G!w zj)7|bA$fB{y4u00_m}MmtPDE>RT5uCFMBEfSK{Ilo^OxEZ#@MMW0z1AZzt%zgclXX z|5Ul7)YQMv)C~XgW=MrGOvbyPT|Rv!<0`2;bXWQexW)ESVkMAUuO(nwDEi_r?3GpY z5)c>R@~`+lZkA-Vq3Tz3dXq{wYVdiQ6x4A<=DtmKYEoIOc> zsrD3-^v=d}*W~VHm8=^{ZG1WZ*Wls;ndJXAutY#8_8^{vf9@*o4-aJgBf4hc9X;4l zRt;S9_YIklRrvd=rM=Rq)&5;zFC`Smd@N>h0A~juFc- zCjC+SyV1dy=V7h5kB5l=@wm7I;yO-=&U!lzjU?oqi0ABS=VXM_H_^}L|9o6rAk#BV z>qZlH4UyPwcuvB#Zi{U46=pBIvPZ;wBjn{atNrcm;~MPb@;9026X=@|)LxHg@>wQX z{u154Dy#Tb(HGgCD2~Uct$5On7B^#H6dNIUt5VEMVtP-U~hyl*O{;Fnt;}gNwxoe zytnEuMg6pY?=HFCSn|Os93h6@*a3L%&S-I*G51VU{zqQTG9o6~oyzubfo6xGTxENV z-AkX8GqtD4#8&`dqN~`W4&xezK>De}xMh_eaUv7djz&E?MR?EP0uD`7>YKyVgN*^_ zK>tnjylb+i_g68jZNGxHrVli?aIGcUt=~Dhc&g3r=j5FeMNIOAN$h_{DyF@2QBIiT zeCXOI7n`^xz2|Z4{}lb?E$QbS&HhhF-t`UXL5>ddDpx8;t0c3w4`0CM|1n%#67fs% zn9-sFeTh))9Xw}9@E+(gdbXcK^7!mrKv%cc4MKPHx>l(Qsgg*$X)GZEsl>%45dR*} zB)8wl{3kT8UnuV(6SVIeZI1${Ktk6X$qO+2kHWR}dpu`k zuKPaD8=llvCjTZYdI)q-2+C!}Pqu$sK;7DMl5G4JTkh(K+Dnj#O#!{-pub=UFC?M{n`bK8*S5tyS$3eIW*_u$-iq)?jP zHPYISWVIcscn@ZiaBf@D90D^H(YB;}39Q1EjRx+Md)bm5oGu zlKkNpb0l7t%5`Q#P6DrdiY>550O^maez=mQ4e*?X^-uh%g?1%~i9nAM++}hW6x#}} zBa2=od(Q)Uj^Nsu5$eo~LZLM}vPe5M9sqhD&i#_tjAm;9O1w|;Trtp_SPhDgY}jiM zJ2v_M8y6SaBoXL&IrSY8H?ykBKWN0aMBjnDwxIiZP+xy1sNXR=B&%xEEMalfL zd$*%RA3fEW`TSpzEe*N8n0FHwDs>-o9UF0r?Y_h=!?hlaXIdzF#m(GJMXLv}V2E>l zvD>oR*q{qP>Wl3Ozdz3P#pV+@Q4#gU9wG1`uGzC$$*?b$*@%8D@iKVsn)GNheN2E@W)XIiZ|2L!29}Z$ei6rj} zC4<+=wuQb<^z~cPS3%bSjj^8^(xdjdmNtafkM}hu?`E#K5YJu2OPFq|Bvaak0OYo~ zxIm_Mfd*p(2)1EFwTtbl@lh}q>}PBAbzhTgLa*om5V6sCPNVuxCi(m(b_j|-Go-sF zAld(8V-||OBBbYP-LLH)u5nsq^Fa;v;(_%-q28idSd@w=q102 z24_0|qbFekzg(gJ8LE}K_+FGu++y3G*m=0tyWyD@ir#(=g;LSJAa=&NLVs*l8*A0@ zqe6cq{Bbx}==FKd6^f|PZzS+8uG!+OWLW60CsX1qnR(6d-0f1Pm?~LUc&stiklW$n z0?qpU1@|5x*MGBrik3JA}MVcu_j0O1RY=as4y@ zf8yc-vBGRY-G*6h@Elz8C|jAegzo6Jg6$Z>g0kI=UhY8ACx`T0FPCW;nY-mQO!8JV z&5^{yP$JND5}M8z{icwf(-h4yKMd$Tf!2ngh02iRekffp`r9GhDXl$1PwltR^wjpY zy@Q#Zo#}i34be;8p1tNu20*Tp!YZ|!`KXf64opM01JVr_mq6SnWRjanZ5KkZz3^O~ z1()!@pV&19$*T|P_o4ScpOPwxZ=g9jlmF$oxP<51qwrfFjYm@F{>V%aLGJ}Tmy}fg zf0UgEm=wkK_Pb|xc6MQx1r%HrBq)dp0TEOb5pzc6VpjB;BZ^`|MMY6DfC*Go%Kc}U$`P+34f2g`Qz0kYqbIb9^s_wkL|>GK1g%#A-v9k|9Yphy>q#sw=gG5okE5$1jHII$r;^2Jt;OLm_5wBq{S|7%?@}J8pmCI+0c;AIgYl3SGzFzw zC&?cyG<0Y-1#QakJey7CuI7*J1NF)FAzyCKj)7WwL?52H;r|1mAVFawfB2dk`{Ze? z{QLZg`erH0s(PLT5~=F0Kq@`dFrd&`x6PBKMsvTtE#rW5&`m!`AkyCjE(-o5UY$ zw|zZ!{uA5^?vU$o!oMMPQZ4&&Nid!NcY%Twg>(3mEOlIqB$2L41{(n*xx zcjpW6{C@}(MB)WV?Z|&&2Lcpo=@x7QZ{+TXv_%AM)g z6S;r#99g<{nrc>Dyg&Ao&xYiZTK$<#_D`Q&()j=i`lsFBVQr%p`nBC5mkc?O>j2ht zZDgTOSN`NDPB^tsPctT;TL1WSBn3kq)ybHHsAK->cav&Q-=<`9PKb>_I*)}rFm47d z)I--4qm{LT^X%~an(y8d`rTHy#GP9cE$8x+2-!XIq=m-iTcII592l&|z*MF8-AAi5 zP+M-pwI8qnn`->7%Tj;PLbv~2Te5s}?M}+o?(%PH(Zc}yz}Fi9l3G1K;G#UQ4rC}n z`~#vrKalE4g7GlNfmF{HJc;o*Xsd61yRs@;adS_vXx{)&&w}<9{4b=@D|%d0h?Z;W zrgpX0^`uKfYridzdH_Bhby_9|GtKGL9LUP`L9L#~SrqnFA?ZbVyVG;6&SXy&o~J0C z%^xSHcb6~dp`F91;tZgXbIt;H;@&LU1 zK&r=D7GS&wYR7!f`|U%EcveO&s!Q9waxX2KL{WM!fAo^+ZJA|#<_DhA zmgz50)_}YkNEfrP6UGiwC++n}j?1f~m5FKWH&r{SvHfA4gy00#poez;g>kKFXsgF| zytP%)iWU4^Om~Cv0^H}sr^N@+e&1wiy>*nhd2H%F{tf@r$rpKM)ip{OU-PpEWp; zp=X~K3N-S+WD86s!Kw+x>eicTq9TuRwCD_wDi(PJ;~_P%WsroQ)zKHPk{0F!LIJ0D zu*%Q<^)(}Ds$WDUpMVzjEKy+QDm3ku&o4FOK{R^4sLc+zbkM>ch6lV66|Jagn=Ug$8a>~$5B$ABQlqz0w2mEeD{Loe*EU@>)!Qh} zfjmxvq(<+hXcrWmGQnyUB<)s6Tcw9UxdZm?Al-+B*D+oL)vq2=67vAaAylwZ+BN-| z)#;9eG!lHy!ax?*2kCn({1amzP^*^<`UZKEGip5>e1-O2FN)5GbB;tSvG6R$(^}B0 z2fxPn67ZV*&|Wa)kdlHyO{q}C5Za`M#X z{bxya+O_y0&uP2^=X9TVT!*uB(>Gcl@(}xWcSP6FGPZiL zTK#70bsi3_UUMk3F|by@a*MaR*P+(xx4hK{L)jk`-w2eyZ}pRbp;mtrTV3}@SglsC z$ksJM{eN3t-163}HT_UzY??Wb-wZh<_GSdlOy6GdxF6`v0!r?siPxw%2k|RoPUG}F zjL>$?+FPH1b}A(g{)79PARWcRro;H=GpN=tn7M}c_oFG?ncK{My=)fccLV$Nvdy^5 zxjv{>nB#(KUPX!E(%dTJ=CBooHuu802b4mag)HgbsYNNStW74MRFv|sN6Mp=`dx@z zfki2OFnR%tQe4?0Zru-CyYeVyH~70qqbS8SEu-a{CQ-`i@W%i?7Js>{SRqq`-=M)3 zf6YKLT`~)8X44-FZRW9RE@&Y~>s8f9%QnLwIocJuj4@}M?8=Q&RaLEc$VF|E#o9^4 zL#0^AYqwY^E+`i2Ngb<$Mu{vI8phI4V6l*e8eVhTxD9U(p~k~%jY5slXifwcYFvjg z4P?QTSFm)_`EU!ScG7`YF!dsw=Ot1wr9A4lpjP3LYuY74&?qG8A(6tO4m_Q*9I&ux zFvcK|R4W8K>6ff&+%1uc5Udn_9RlkhF|+u`sZ~*wNoin+e=dZ4z6mI?@ZTGikIi$9 zhVwf#&@Ou{{C93u1-BIbTa0u@XfqQaZB|7$K1xp&Bh6v!_X3NN261(@KCl?+shfSv z3lI?_?IVq1q|s1D0gI7d#&`}CpAUSGk@U?bkCA4DGEGCrQ%#sW7l+k8d79+!VgA(8^bptj4ga+UlLT2=4OO%`CWkJIxNbPuh1LR}r`Qydf8pkLLN&plad&6~~)CEOOJ z>z#Vl_I`n$%bV_ncqlM$S~Z+!9au1L>KalPXrebA2j^s9-gJ$9xMBh}6}cdvxW(q} zqtSifljT#9Gx%Y?W;?EIDw2!#V!jEhR#4IKXK1=y^mI0S11P!Z`7FJv<|zB%%3`#< zIr4t{%KduxeM##9^Xpq)=F0v0Nccxd zBfsvNmeKMiHu39M!oM8w*;v_!N5U>^71-vkAA>Lln7ejHi5sZquIJGubJyR%|5_E- z$X&ar5G~(8Q`hZVGS?j!Y~tB>N3p9^nP-0?%G}B}Tk`BFpU zfqd}F2PH{2U_N*ojIDtAU{~`kf7J59Cur}<2aketv_$g3cVXP21^M7NFy@1#;jhs0 zav$vLctj=kE87Fn2P<9sCl#0vc2=&EAAE2R=-q+&V5jFR=Ap}^Rw1*) z!?|uxF>*_xzBBR_+-CmOiTu_stx`p^=h7_s*G`8rn}d>n-Ik@Tf%#Wg@&k;`nj^Q_ z??q}+cK=xTBSGe0om!K$9+COiw;`6Y`+B17N?`u=QHql}Uq6HYsWkGh zu4xf1-(yYuYg=7t0zT$nmkRyyv%>uAdJxtD=3kw`*L9osoy*}qFVT19o)3b5fGRfs z>eNCM`39Q$SKpHP*SH}6dKq&Iw51m<7s z4yQ*T^RHe(O{~CMh<`m_<3j$mAN;<+{OcYVy8-jBuCI;y+Ry^|*I}wa{&g(;(^Y|P zBFw;;4l@7RA?!{rse=_~%_jbJ9*VhAW&X8Gl(`kQ8?T&yg8YLCf`45l9=Jh_L=F7$ zFVjA|4{!wMCouo|D8?*c{&llkyxuX0xW?U78u`~YoINdp`PTz5hJ)hPfxjgGIv|wE zzwSXb!M`3ER{szFwNK~@_Z5TXyS?y8u3O_gnkfHz2Q9i?O_YC~kMSxn|LU@QWd-hE z|A70O#PY9Q>3nCauBJC*o*E#SX0hxat6q>|i zRl)Kx5dG^S$e&1%`PWS&&#hKL68&qNqu6`E{A+)VwSoCpmvFb18l!*Rhw?ookbk`z zV~Q4Z``|H*Il%m@tGOSITK@G-?FsqUFX1ebNd9%zqq#5!=3lqR*cK!WJB5~)`&VDb z{l3_*tUJ-a9*6i?VE)xvxk`TUuh&4mN_5>maC$ykVP7TgU!Q{d1kk70J+xt-0%O>ML?1@{`u?_t**%LoH}`spfiCQUn)4w-lBML|zs z_p!aghGc=7Pu_N0c%|H}lg&ZJ1v#j_sA3P$D4}_&vsfAnO82q7_O?lDjscG4t@-R# zIhI@C%>d?D=3&eQmU?x~Qpq4Rawh+hNY3O}I6q4yXVQ68lB@tSXX2`MamU2dy@@l? zFTiXjRdyfSWv!!3Mf%`Im)8)m%Txr1g6uLC<6_ld_pwh1Bag3?#W865=#3tM`;hpw zROxl!WXot}eERD5u@^!A#3VJ!Zn&bPjS7aFMPADsha52Z=|K@!lq^?hXji{c9zHrZ z7gf$J3mv%!|G{!)u#~2roKb8hOz*kLNq3 zz`W>Kj8j1I&A?xh7kws_$%{^-n&3tM2&?}GFM4k1N-lA$+>7q@CQXzVeTf#mpeD+T zevk1jFfZz|d}RghMVCK;Zw3JKqJuC7f`*I3mdm`Tui_csP}msunfCD<&1CYT2O{4e zm=|?su0kU(x~4Spq8GwHUm9Jdx+Wj3c#7af_jwDAyy(5~?~z7c)HPMniW?WK4PajM9~e6U^P(81;k`tjcl;1M{8`(`zQQ;K<0;C)G`^u)@-r2k$VIF4@)QF!)13cD3o$LKOK1nqF=Cmdu&Q1v&Gv)O9*AKYk0w z3{bk-^bJ(U4OpM}gPR=u9*vTloQGyEFgN)t#?K(T+Vl!)Vg=qpf`Z>vfr5gSPGO_K zf`Y9uwg7gu>H6BJ_X%48!sDty0m9MnN2me?2$L}`16hFZVA!2}F)CPbCTkKP%trCB zRGELD9c6B*NFTpXyAbk6Ccx#%Pd*(FoPK7FasRW%y)HJFJrxQtH#rkyhF0jIv&*8J zoQ(!Id8agTlYc?^MH)SHHgpW>Cs4dH@R#H!$A>bx$!Dl0xXDMt>i?UYY_A)tFNdDw zqUC#?UX5Kq3*{xRp+Q#x^OBEaJPOQ9x;|f_FSwWd9PT1uUb6LR^c^(36*ioC$@aRR z>Z?kYdr8{zpATt^oMeC0YXftVF3Cl)OXMVnN+Tz^H~c-Nk&|>yK3Z{@;3Uub0F9jF zIQVBuBPZz^9!3rAjvAcgP-)~OZ-;*?$eiT+fr|4C7A5wHPV#lguSt+O$&Vw?EkB3i zrRHC-{{ZGByPeJrJ77-IB`M#tDRYwBQ@%AYCwU&mxmu8uyaVHQU{2E2IaA9?Zlb*> zC;2v<*MT|7#xpqIfjP+yFb066;oH#iawqBQcoa1D%er53lKUh6Com`JEPmgjIX^hb z^P!FxT~5+T{D?&}x;x2xpxy=aX+P}ll7vTTn@Jd9>wSbX(erRkYOk)KQhRt%&HSp@ zR8v9h)VKBm)u{*7?THPi)>cqUezm>)suNpAYpJXk33XomAC8NB?7MXBZBX*DEyr>+ zf%#akq%Kwx>M++?^D(t3qq`RT-XLkA=e1qbDq3-%<8SXrRLbt|j%rt6{&oz;DZu=# z*U}JciJLC>x6|R@AdUR3Yg$LkHBJ2OT=*{nKIU(K3J20p6!W(~L--MxzjX%R25NTD zbv4#{A^Snu-R@^{FBoM0)~P&k+-w6){jG1w{B2y2za2_lI|B2!qcKhdC4cMHR>uuk zpZJ5ny;F^nznz9=DlmWhEXLCy^S53>O{~Co0si(-jS>0V@8N%|3Uv2$xwE*I2j*{G zUmNv4^UL4%RR!|5gW(SX=5G(eH~?h+Hu)`UG80a)3fYQg&qe8dd>2TS`P)L2xfN8= z-`)fHE)xWQTOTAmc*oSnIdIS?G*ABa6KL-O^S1|#_U&XzlQ0UH+wha4>w#eVUh58L( z{?>`P3W@yfsITpIb? z9pMiFnZI2r^nxfeSUv)xzdaH1@e*YIc9qC;%g>?cZ>PbY3e4X=jqxNff9n$N0G9jP zZz=yu0$r4@c`lcM)>> z`CFG1DkO}s8H})!zjbPL1-0aF>-AW$6Kj&WnhVyheeWCg+aHvSWz(ksdzR@dj8B0j z%Un|%)mbC)ojfJ5*5~C;+PN(%xFsiT31uhNC9O$Vs-hpiq$TpqgU{pq1trfsf~6yX zd1hDES8PHTwbN*hdFILRF9X@tuZtRzA#6^>&A%x3#5}#ZjORdNj~fq-dv1_aO)g`9fc2f2nOk*gRpOS4^zqBs!s`E7p-2ForbJ3~| z&8NFV5BQp<%j51#ONRsVxZ^O+1m7<$9)xJ9%wisY`H8C?5p@j zb0~C=t9}2BCNjC)-;w_c%;h>WSD}&1?Jtd7u72RAD=?Sqntaqm_le6rcrhBe+-=}* zC5>FJYpS9ZHMrdV(#Yi=1OI4{x!jSV8@$^sSUxtQ%bf!G3JEfodwk@%)hbA$%Y7X7 zqrhD5XBeLXbGa@d53JL{^T11A#JLX4B{a^J;xTMKfzxr=F@&~R30dAZB=b^MlE>{r&E=yF#@+yj`)bylvDA6)K^P=|;v zm+SO=w8FlMF84&J#{+$eyM{KDKdn@sUQtM%4F&n6Gk0w|_3JbJTUyqRyN1!*BGTPH2r3t{Rvm{2OKO@>hIvg4QfhpOG0lBBJE(80CU(aIWX zT>FKGw)z2Zrxl~+Gm|&N z+;!_PZMF0b);`TvXM^-57KTislfXjUF)3#qAAVRgShmQY!d1Dh?SCu}ah<`>h&JOk z3vs=;kPN30s_4z1Id}?j=dt0rpcLXJlau66HAgqJUCFx-nr%Xn>zOpiLfjr`x`Qml zb!tsAmCcE`>QCh%?hu&U0Sj@D#TW@J#C2tD<>tym+^gVUDUA|Nu4xf1-(yWe+$Z2a z2KZQrJC~+ei2EfgKL-}#o)>z_H;kJ1mk{?@x?v&i@|QFBfh@#zDvtI<`r9PL_1Z1O zjSC8Kw?w}=Xq3o8+@n}J5|lz*uerLSIfS^abqp2aPC|1jun_kVjE6uL;(7%&NiViS zh~`9vaxxD8jp2NvSGrZyRlMj`Hr5-G&(4`*#)A?}_SyMrvmy(;dx2`8dz z65^f->kKio5Z9?yiCZet$3xs1kf)n~l@{Voi-%+OiP;do?iU)R5ceI3^MHl8dtOQ8 z4lKmo^E#`QKZ_9&;_fAlLfjcpZU7eIeuD84DDE5h9^#h2AYjK(rcm}+stLKHabdNG zvL^Z4FTd5wFWnWp((9zGE3f}Xv*egMO<{Bbb4%Mf;o`|LIWM2g77xd?sd#csli*wmGRNe^LX`M!jE?CM$PY=7Ii~w! zm)xp|20wwl5SU}Czlv*AV2;TpM1yLVJEs1WuLaC89fNVS7UY;N$Cw1nF}a#(5VaiB z+S*HUOpn2NL?St+>Uw6e-e37D&(UJ3NEujad?P$AJ|zBbp^MSfhqdwR32g` z_1`?pn>c>UwworMM{9q#Kjlw$XFFbS^Aat345T$ITzn1hW&r75Xm*~;-T>A2l~q8H zN(F`U@++k8usVM&FKvgDzQn>)EIbI(Cs`OjjYr-p!D+=|YOu z_!r-+2T85o8t_+WC*Ot@@#1{r{eM8zYXkO%v@5VL;1_Q}k$p~P4!E{{chU6PfZL!< z2gSNT@h{+y=Hzbr+JIj}_@a~lX!P01U)MVbb@E@At>#AnO}yk+2NbUl3jYE=O;3g| zVkev$3K~OO|G{4G`WXc+^t$q~p_JSI!SeH~a9O_1dOAXSefcl+<|kmU?plGFsROWA zcWs8T2`F9^^m=<>xJpkNywV@G!xc;lj2pJ)q$&DtUV*SE<6HOUTuvb${p=yPyxG?p zJH_8+DtH;hzE0n7{{W_qo#WZ|5o(Oa&I;Ej$XZ7hs@*o>KD};6ugo()5`9VaDojoA! z0&MK8Im5c6Kd0E(nIxLV&SWSTfnwi4`TMccH83=GZbKi&&aPp##?FFl^?w;V_urni zref?I9IEoMGm*Vs8aqdXQc?rKDjz$ebc8f^KB6}t02@0sgyz-2#!fGco}f57=$nik zuhhoQX@QZA9pBB(#?I@dr<&+7pZXOU6x~Z>hkczcXy2V*`${*bWH+|GiyE^Y3zIO; z(L!$)PGEC~f?B;s(JQD*rcyy+Tz-X>Qfkoa6xZVP?X6ST2HAkHC4L(q5N(3m55m5{ z2HB+;J1jQfCTWwJta{&?Bx{0fz-f(2~?hA30C^V2H72)@A`bL3Cg!%Bkr9*&qkc@=Vl{LKf4t^HrBR@V{I`#Nk3>mu|NYg)`ro9 zp=y)H+W8n~YC&VI9UH0#HrBiXuJOlYV{L*)tH#wiYzj4xP!96u`!stMiFlh#U?e@-i7}*$i~_sajebMCK+q^kJ;M8dhtpJ(R8fFJo=PaD>ap znmNX0LxE3jM{uZ0<8FmenkH_w3Z0T;+)0O3<8C~?IS1Idn~8Co7W7WbH!$Xd;_9KM zCgaX4wV~EKFtVZMySdp=n^iW{#xZP;jzg{2X>9VN_D8p-O*d20>P}h;(yLjx4`YTF zE@k10ySVcU8YSs-A8YaxC!cER0k5tqRvfyU6i&zwTf3Uq*2l+GoLg#ybDO0HoLEdI zut8PSvzA6GJuvj{B-s&^(gUZnGzM6Dz?C%>&Cyl=bu`D)12f=H2U&W+MJ3Tk2n zz6;WvzLfF%`fB#g~Ioq3p!q{3a&D5)^wKGMLzk_t~?JOYZh1pbne3R6Ovrq5@o zCYwHsFNf8dK3SE$sPJ!-3N7@q=qp24a?z>}ttM>v&z0k5<&I zpKg@u-Mrht-2$X~Tklwm(v8)qa>gj~CB&{CcQW@BNHD~53G|HGG zBaH0HoR(;mJ=qKX9?~d#;+m>xMGcvh3#CyeQvQjtj~0|kIUD0F zV3`zGbAtf2GAXBO?Aj8 z73(8j4_GF}S=@_h&JUTC{h;nEx-uzFBBRxe?kWEBpq>l#u}q2+31XW`be@c2h?O!a z&Zw^7mNF@||G+&vk@wEA=L#q0S9q3RoK8=mXcim46QqZ+u*pO86{P=U;WLZ{z~V%& zp=DOX=^<${`eD*uz~aOoFh19U;>0T-;meA^;zU>1$CgxOUAAkI)mqUO#fjZ!ll1~w zoH#%Bf36x$ocODViW9eiv<0v@vEB73?9=)Tixa1ara1BJ*~|x^xLlz8eVkYm7@Fwr zLLcJ94Z~_pbPKc9|0PZw9~2dF;s;^Q?3lGRxt1ePiW3)wQu5xx^3zIj;z>GMiW6U< zH!lN=6Mx3|Q45L_JI~=ejG*{a(3i)FbzZ6B#EYp+^V%rRiQfY`3mJWnHwziRD7zlE z$*}eY=F9iO;o;>_?DoP*`Pw}dIqF^S+t7|JfW6y&45jvN_w!hDHn4ZQyE>Q5l=|B6 z`*zN;{yho#QDFV6ew2$bP~0V?JiUL}^jy3!H0QjnvkXS>gyEq8_tQ|2OBS%D^yBsi zFHbWS;O<6sn*j@OH+zgw7Fd9L`r~ASK%*p016;4JDpt&f?4$FueVg(C*SV!eIJa4V z>%?L*Vk~=uiW+#Mz6H2rpI{7wQh<9WOEZB5xUQ_JXpY_gqB$1ez6JjckOjCdYLQH3 zb9Vo$ZFzv3dlDwF0C#1KZomRuSGKI&TzP=IJ^XE@(fu#i)I}@qh4L>N9|!+fz{djI zZ^Lo(^S}b!Yam<&EWmXJ&vXZ_SF8uFPoql~;JyI=IaO={u2Xq5zu5+w2DrW@3vlCt z0^HxI>la`F?ut((Nf%HGaJ^a{=x^340^AjsqEQNPw?wl!umJZcj3Yr7;JSWG<-y8}2@Xv3X?E!f=6NCV_CKC0$y$1eU8u$uuFMu}| zSb*E+83e!r+`%__?XAzW0QVAU6yT18ayYO6_i2nrL2>h&GX9bR-2S0V^Zw0Llg<0Z z!@}zSA;4`Dx{^!Wit#4hx~3gXRDj#=*(7NLEWmYUzEa}>?)GrE1{UCs!#Go71-LG* zidNj92e@~@y;WibxQj480mT(UOP3JfdJ*Z0Kq?P#bB%OD0dAY;@EyPcTxaGgGzxG> zEQ3Y??z-^%OQQhSHTh`85k`PJtvwnAxck80TN(wpuBnPv)DYl~kVXOSIq=7UEWlkc zbb}WV2g}D!yj6Gy*iQ=zaK~eu11!LGHSbPDtpInp_MQUVd*Iw5kpkRrF&1k<0q!y{upy8%^b0L7 z4{&`Q7Z|Z$*&c|u3O7W&0k8nqS-DDn2yhRCy1(cOaGjoyR#tiGR^f$E&j93vj!=#6uFm z0$i`5Wmba+xGz!u1h4>itGRsj9$0|e>SY2FU;(bH>tjQ!vMyVITgOjTS%5ne{%s%& zaJPv^WPut@fcv(H3UFVC^a`*5_p2}&@vPEW7T}&NngZOV=0O3)?*irT1KbbFCb~<| zhXA*6M%GSEbWdih|4V@T?QNN&JixW2`*0d}27A2})LtI8$sO@v`KhF!c8HFUg4(_4 z!5+YZzB4dR(}IH9n=x(z#T$d(gT7N}L&J$TWGzwBz0S9w2=`cO_6YZupv)p%-{s9B z+~1cN;SLSQho8ZqYl?6;ROqM(_jMZe3a|)wHl-HfF7pamEMO6?tGVwlwMV$lvCi%Q zd0SweorrNhC~mRD2)C{fnxhDJ7!A=8?49&HFzf{um4#)OSlGK)VCRy~b(Xy_mc%8&pNrooJ-;x(%<>4^RqwH(+T1u&~#aH5HpsG&`E+SlGKi{C|Qh>~#@& zT{b6TmyYFO@A)vt0}Ffa#Fz;z>~&?!%FUIBy>G#PLmGv>uBnTb@3AI%-P{|j7DCv2 zP}pUDrdZg!Duf=u!d_>jiCd=TkHX&L>5_%LyTBi&iY@GQDsPu-wt=Q$uW!l1-ngK! z_blof3oPut4dWJ23VXfU>bL=K6=CmkE6^x~y|1E~2Q2LU9phJ!g}q)uO{~Co0b%be zsz70H_c!_WB(Sh|8;q@hg}tt?jr!Wq0_An5r~-w($G|^Y6)5bz0%J1B!rr68o=Fq8 z3SL8#yzZkYW=oZYy`!Sct)Pl|-A^DdGyx+~dEHZj1UDWA{>vKp3VWA-ir4|B_P#)io&gs2Ix}CX@vyh}4tH>Yg}obMYyd3mb#Yat*u&m~ z;qEW7!rp5!t^vhMLQ9tr_IeRdKnGG|Stow!N+%TdK0`H60SkMb$s5+2X%zMzD2>A2 zZ{U9|jly1M@`m-Gi6e}#_wp`i6!tFrZjvkmEbMhAZ&+`xA?!U+8ild14;Prn%UPK13Su(0=DjC+8Ey)NPHt_;l>_I^P5yAtRt ziYvUw<37N`-inz@|-kcx8-cIl1?SX~8PT~#g&FG%j-5lzsKpzWx zomi-l=sekO#qzM%xz!ciW?`=rYm=$$IfcE`y0PaJ_MXJX4+j?ZuC;&(0$AAF?gLH+ zU}3M<&@!vR!`{~^e*sw7JM=^HWWd7SejnkAfQ7xTu8$2Z4}0?~(H7-(7sLMwWMS{4 zahEMtqjmpZM1{S}d`tiZEbP7FMilnxb&iF-$B3q|cPA*@g5ouS^7moy`GKK{Zan&I zqN{s6tky*LP`3KNguVMs&)QNR_S*gbQ6a!wJ~Z|OjzB2{J|&d$ZQEe^X=V5Sb+i-$ zUrleO0E-GA!Z0X)tVw{o3;bb#kL7jW3&+jR0}F7+KsW_hfa?tI zF$AtxtN?dcx?} zs^bQ{RRp-dsZk1WS4GnUSb)1D#t@JNxL!d`tiW4HfP1&bg#z5s@K00)%Ii+UmJ0q$aquRs>yeiC*kubB^4!E0y|;5L3vlK}@>4=#KW zWo`vk3~)DqJir8uLs7WiI@ak0e8_qw<$TJ_;L z6g=>K1nz^t0^gr7egMT5e`dTT1ioIBCT^7nzTd7vC+uqEmu&?45g|C8oCD6wLUzZdrB=U!w z_bd;5om*YOZ5H@CkssV)&nfVo(u+N(!1p;e{ur>pcfdCs2w;KlV;Bzt3w*tXmRSuR z_zw6M0kFXL8jML=&;t!Cf5(XoEbw)8eQan|)@950{!XVX@Vy-VB#;HZ>%=|W`aBv< z;QQQ~5Eb~&hBOOU;Jf4Pp)Py32M<{vE}8=0-=X{niaQ6&-v_>%28JfOvFJnKJ36e^ zMECD(^?wO`&kc(5z}NDZ)FyJl~itjKpZjhY24@?AiwMZU=o{0J0lE%J3WKO=cQYL9%KW1U?OK|f%fJrQFR zDE3%lEABv}W*m?P6 zs(8%3Iq@|2*8RxX2KH4v=T#+h#bdsmt6ib_wlADMAe(P@41Sh(Bn8XIZcH@n0eLqG zviWxB$aBjFNt|!T!ag0?d^-bUI&A4>CK2gLdIGsK?) zn{S=PYeJgy!+hKJXGRyW`L;wVSy}y_Xjl*GIzXSgZ$eC>i=`77C_3r6MQ~I&Cs_1b z1bQTMk2oLoVt1sU?%lI5dqY9XepEOdRO=~Br?Hcu6+BRxO8FFEfr@?*{S65esJH;P zCV>YhRlm?gV8O{ijP*hBfY68#oK#Ib`_yXnrC!gic_EM6sfCN$AK#A_YL-6&?S86E zkFEWPp{ra_OsAl#S9Fl5aGy6NYs%!d;fyd0gxoDc% zH-j<=6h8=*zn|G(3JlHcd!Y|Ad-ZKu&6?RKWUK$n%>Iv{sF>Mj)3|Nf)1{fcZ=iDe z2dh=KwL~-faylxS*(cJ23xQ2`|Himm3!2$=@$wER4i5SzGrL!+nY|B{`OLm~AluCT z$?e%Dvzgs@d9#^)wNg&Qhf==*J8%zW$f$gQRnkqtERL0 z<7B&(eGkOujx{=DKxg%Oq$k7xJ*xti%x>KoH`>29pgjatuv$zY8VifHh6&wo>PrV8~lLr9k8^4YjROt2)$F<;BFlo zr42e1d2c7Mw817AgFzMyd=|I5Ks~BUJH9KGj{KpJ4i?egBId+=()to3mi=h)MX)A_ znY33<;rm#pTcJ_PDXgzv?FMTWf(In3UyTUm&3Ie}QTG?u*?Sp`MebvJ};6)N(2)J`~C?utWO~4lN%`^!dcW?~MkI?+E4Z zHe+J2MBfe6a-G^#t+(eowKkcl%|R@EJLv^j&jCyFeT%UeSd!0G{MKmmO_eA4maQez z3oOa!Onz&$nWjmSZxH-}z`CVh0dO|I>e@`Fxp9N_naASyz?{Lax;EpMrBjtv`aKpe zpxdgbb_4d0vPy@t@%@2il^(@-09aOObo9BcFSV@FV{F{AN?luUR|;5GXx_IWu3W z@u+MO+=alRvMw$8f-SK0k&CM;#U7z;1$Q%G5!&e(V?eQOXx|djM_$BW38codPV|=B z)TPQzRC6P+^pP`pOHwn9E?chI5REQd=E8qb8eO(HGas!uhj9t>@<24Yg!u{n57Nk8 zxuz;wQN!iTHPUDXTcM7*4`i1!%Y-Iz1_Uegr<6Y06!OLrWS27?BF`<4jN;|YVXzMY zb~$qq#spxOGcLiGGd5%T=t0Wwl|Yv>KV$r;1*MNV)~Ctxz%FN8&EtZobvbjP_MR?h zHiI)5*yYR_7^i7Lmow8bZU9Ncs-flOmovVOM0V^~wg=+n%u9%00CqX!tXw5OT+aLm z^?T8kK5}|KTE2Vza;9S|ItcW!^pQ(=(POg~>HI&7Ay#@^&>7Vg+*10e_)C~X@>^x! zU9(?a zQ1!_|)y&TRrhja9v3jyLtU(RT)*uUA8-tUpN(Rwr!j*peZURv^sSbp+KPcU#x|F4h zL9N`7EBV?2O1Yt<2Xj1Ha-`G0D4zxK0bs7^1B~~9xguBA#5%mKO@6SfrZqbjm@9Hk zZH11Q!6i zAh;Lf9@Sv?g*t|T!zmH0Si#Rli{Czg`@Zb1*quubW?&ht>bb=;g(i3Q5n<$6T~L zdQpEb-;A~|gf^O{9<5zV_ALcj3M%vA){dZfSlr7g#9c z;;KrqhcW}Ba;P2BSF8Z)!&Lhdg?7RqcI6lCXv)hbA0C^H=PUcf?`voOX23uRnFvO-gr zza;H;%5RoHq0AQ;pJ_p%Oq=#(>w$$buI5X6s1?fWroE?7W<5ClfQ2&0VH~Rkg)&nx zt^oW1X=rbGDC6t+tzL_=(cEtzfN&%!vPx`JB@W$dfHPUL;xlbLPWr|s|Dnmwl+>0mbA z4_J=$9gMkJ&{un>Hu5YYupFt^(9&v%Inu3`W#jd zhL+z6tKN#X=n=5hmQRyaK$b5(IQD-yjaI(&H4&9B-4W7u!1ASMgxm7Witxyn9w(ae zr58Xs8x+qDl)ulHjtUITdKaM2uG#8tExYAEJ6ru<@}-a5l#QSAe5pMG<~N{vWaY^@ z9D!2qw0Fq9r-@sBS}AurOh-$((>LkO>%fA?HoZ&v>%iL7a|NzwfVJrej6*>2pr&o|EOcV`^V~CYYEi9D`##&#D0!AU5#I*v zexCElwZfA(nYtZ3d6UI(z7S8|#Cg16Ks-FiylvshgRI_()&aYp=e(+9v3Pimsp83N z>;z{Aka>+~Lg>K5&cO;}x^w}2BIM&G$h?N%cVNebIAW@8q`%pm)9tfN>;YF+-vLz zbqLU>h0ebp<8g4SE>qZ;K3T&@=i&)yjswLcoa*x#=%vfUx<0>Ts!>~~$)$)iO`OW~ zv1Qr%Tf=&?lXbg?^?H@`6JdSLh@@_4xO~2I8$5lszEC%4a08OAN!ILsY@*E&+eB@C z3}3v^$KJ}gUTKEm2e-R@7`oOyBj+bhI5jDD3$v__iW)gm(j1#kh7aW~&70R}{pgkb zORhlMdN&HD(fO-E-3Ni83GvjdW?kMl{2tanNW~3l*tAWE8y|pvbXddnnti$+$jZ90 zVXZ2AzO1ry%Jo!!a;W^WGZEjMwpo?K@^X6KqnzYNP|sF7PsMtrt?bf1IXzX6wm&>c z=P%RkXTDpw?dPyQZP)3wBd=t?jS8ft@lC3ffNWy8YvBvq1; z-Cl%#oo%GoOIhth<3_I9(KmA4%G$_4Dq9cq`t~Spgk86h)}6%iabWP%gc)sasMSky z?_>FHt((&o-pxyWF^Dh1viucjuNNQg6gI__7Oa+GqiyafjAN?>1tLjt{ZKlMVafo@ zf0xp@o6{!_U{e#bL-sW6RcuF0>}PGXZC-5Ky{``q%2(Kd&s zOS4Z=y#|%SS8HMs-YQ!PuA@Hv-unaDGV{ zMqtv|BEMYE+nenH?rW+2%i#L-A=#n>d1yM*+k6l;9{{?j%}(QC2pxvd&Ms=F7x9>b zibC5iLOCI}%{kSPoo$t;;nlX4LL>a>aD1)_&DDr4t{%!Km)*g-Ii1#;>15mNr0Fuu zPnvBzYXGnE=@ty!ZP$~%{h|Ecc$@9p_4%C>eov;6HbXLd)p^)EG>DU2%~Q-Qt#8%M zg_%xS^FTi-)^8N&LArAf*lHWxQ=5TpG!5n^?oz+XUIa#5pXigb*H5$=*zs-}R6mLx zJepY~`{)siu7CBol#;?&O88nuDBEazn+shEugq6a`L4O-&`RG`a& zH>Z)t69~{cOj06tM8%wc5z-FRpL}-oXyhp(ob2P>%nsaaMJf#7{tPQp!jxZ-uNl@{xt#x zKJ9KfFD1H{&_WRuOR=-Glyrg7#m9+Z!U1{|@u+E^8Q-LM8T#}1o zmlV{BR)ltQ;64OWeaG<+j9)X1C#|1>yP*6`(0R}E z-VWAn&~E{4H#nvwDK)|+ExW9lZVlr#sIQ3r^&mqC_#a&j3{lPPzQ-9j-)H0Qlv!RRy z=_D5BW6TA0-vquA97CaMg0^?K%Zb+2+?~~S1E=|6p_KK-!^8S^J7iHb0dP{cOz6Pk zZPVoXj9i-^)PV_;4^cM5rEZPDx%}y>&tT~N&71wD2w1qo59@#6#BAIv-QN1sARdQ(4UEEUnqwnI9Q@xS+MEBx0I{c zQlUd`1DhTuAh)wk`-E=beK6{9eP?g1y<62*D`KC zs^VUmIbglL*em~pbr0LU3)Hk6SE{tCLQ?o59h`QB@)q1TK)Ngoe_{L~@l_*B;zFhP zt#sY=ZYbS*l9mGL&EjKh32HRmd&jDy<&Cw6K{fRs4R?gZn))YUTndW2ggp|be&1X5 zE7LbGYQz#6Z-tUg(J!ED)c}hjq8(&%C5}?5??4RqD?#Akk;A{ZW9auOC<2X?L`Uxcgp_&9q zZjJOSR^LzgJrXQr;X914L8B=VzE3*epyn6Y)5JRG+&K@B}Vt-n$=>r}Z1)*Zk`+heij+3@12 z@-^k30~>9t^yfiOV599|jQv4Fp=`9_F1<6&4oMfCGaCn2#T5n{;heGa<#uYh2^6;t z#d~ooXxvWRgBa3n~N zVPP`HWm-6pg@-X_fuy)>sNjPev(Arfr2TtyE_RJIkICA0ckyC7w#4|<<vK>Ob=rPuLl$F26^;IM~y^@9H)}!~Jg}%1YKZtov zU$A_VE$ou6@eNJ33y+Q9ZwTzd;{c5PKyj1hvVNA|S+CKA;RS?Wf#N5BVYlE^FQkI; zfKUCRs~Ky7$Q>sROq)UNZlkk>gzN3}CyHN!`UGfHY1evI*J?vs1{H_0iaCSXXtHGT z+ST&bUe2heTr*FW59H-yL0R7R#2yCQHk9O&L1@}+oX*#7*Da*21`tz%q|T=YB}TrY zSjSb7`M%+}YJD|njIh4jSz-NhVIA{D@q#jL7#Cziw!%lY>1NpwU4W%i_8aZlkZzU@ zIhO{E1-&fO(yfm&B#T&n6J!~Zl`YGXn>c9j<4jL&R%<(V5TXD2JjVdGTDF>W#_A!7 zvPFH@rwYmK%BtCC0-J2Ja=UE~qQ~n}lHY(P2)$TX1!HAU{mt!M<_8Vl6is2rsx8wR z#?uhWx0B#6IHNF*kw7EO1zbo;ATOy)+N&tPQUYDlK87&|6laCn%P(n@_AO|Hm*k=q znx3@jvD_H7yAjrBRPrfEEJ^f6Z0l_GZ?)y#gl9>jTSzNqpT(4rBzl07!q{tBTbvcj z{w7J(VG`$5thn=PR`FrSAzzh}F3KT!aV}BuN<)?}<&V55A4yAvhLAtn@L*~w<&V}O zIg@?HsG7kY9;e*$M{Nc&sDb5=oW|`#Xr4b>hvI&~@<)4N?4bqakIuz79W;ub<&Or3 zEiEB`v|T7-I?ft#>?j(c{89T2nM;7>kDSJsAeGz;EPv$0T!kd&kA8vs1F(~A?ZJe3pmI=0G;A8nCSJWg^)$Wi#+8xHO87s>l zEue7qV?+LEEG1)r<&RDX?cz(iTxOF$+V==LqWsaLP#yq{DzN-f_f2@@6IlM}ER55E z<&PftxA$v-YB{HV&f(Oe{LySEvw-D~T5ZZ$0CkT9zVb(xK$U+f<&WlvQss|s38i?i z;a&&kD8R<%B~fCLxsdG4`aw5ZF401O4o|JvTJtME>iyJ!O%ADF9gdk z`{K3YDR4)F(zT*fOL7;*YsG&dxK^Tgt>~g$#g=0J=xGE`N@UlHkA(&`yD+i*k(c2t zV+XRM#zo5U^t3F0nmOpYyRfQzxkJ@g=2nCiu>W{HD zu>6sW$!&DcT$XRI;Q6Dy;O+q|e{=@MX~6PFE~%~*>-kPi{WIWBmst6umoQ!c#kt+H zZ7nH(9u&hk;!ezY|)^7Z`(8kw01p_anf^@<*RW$!t~Y`J>ib zQYEm9;%_1+S+ne-_;|`k0J|vu5aSIkCRnGuAzX zx*2Apj;8m@A1$DQH-P1joJN)nTFf79vo%)%!170}d$E6&KYD;QGl4Ze7q&_n&x6jWmYj23P)ojZtw+vACna{@O$lLk`(gN3g3zHYYR&TC+L9 z@<(1q_|5(!fArlk%mXohbUKxs0xW;zT%HlpzV`glY|3W=%OCxU@uLLFAGsi(2|Rza z&USPOSpMi}j3Yp!>a_gPOqOl|mOtto+MXnCmFJHRps|)e`V{_ufSo7Zf`&X!u>2A- z=8x)eUoAn%AN7e_8Ka(&KYCj=D}U4%RxenEABtoCXbUQ|9FY4y_G52^_Z7Y% ztgsx=$p}vZmIHEHE^&(r?4ume+0tA!jy}Mj2C^KGQ>!XP?+#xN4^yv1*I~snsQv5S^nr;Dmoij{>W*0qj~=5c8YHW zmOpw8;}tC^fAlNH&!ADYTK=fVj@%Xj#oExXX8EJzL$4I%EK3_`rGlJu!)oP^24<@( z^G8zyN%^B;k;L0$!lpbD(k{#rNP5~upPW7SBCC`B@M*Jpugi{S7bt>ROKJ5vlx}9hNu-T$_U~xqgtcuy9H{3OWKK1iSwQUkL3wm?$-n+5a zNi_CLN_K%Z6r>-sa2du$pjKHA*N{)@!`Zo2x?Il_m}-^zC@AA$nGPp$TN#pKyn=Rt zb^S$ZdL9(F3|sfEpR9J(4!e$b>cn;p(l)kBJ54x|Z5&Qfx*vajK>i)5R@%(TB&)Qo zBhwXm>K4^d@_fK@XB%N`2pUCct=!o`EFA!BcHS*)j~Dg?t9*8@V%s)5pAUaLu>HMv zY@=Hh`}$|NN z)^8uyP27Sv$&_)kP3aM`d9&Fy%UjZPL=}5=%8r+N(scRdlpr0)UO5d|I&e(54U|i! zLT=Ns?o&2k>Aqcab2tjBc%|Xvk;iAo zyf_z$WjVjKPKH{_`OOYnBa=W?c(3~?x18S_bYniqo*4_YywN=8_ZP)~0L%Gx+nL)( zz;b>&VQdK+Rcm-=EL5?CoZtM|h?!X<4mp=bDChSZb$ku7XT}1JU0%)9b7SGCQ=lp5 zcg8L}q6V^O#$sDVi#fk}ls_+yJu?oL~3d*vG(f ze*0qV1&W^sdS%YfOSl|wRwb>zMXgrOZz`2c0haS~Vy;3GbAIo^eG8cTS#Ea%I8gj8 zY$2S>rDw*xfUCpoV9p<--9`L14Bb#*IX|b7^Me+1ekW6Yk~qrwIftB|IG*#nj`C^1 za(>RKitKQplxw?Pn^w;6Iq1&-%lWy4Q5ZVm63_X43w5#RdS=W?)zNag=lqu4gU4|J zAItfXc0*?3^Me#O-~DolZxT^LrS| z-Jnqgmhe-!D*p0G9LHd@t_)gSuP8 z{Uqi5&W5V#ag=6aeXpzT@ldLq-_%gb`r@wlWV^$2ehR_rI)n}==l5ntuFW@lW~@)( zDChT0s8Bh-jl=pj^trqV@&fqax@$s16b|>uI0}cK537~)8x*BUai7pWk95_(?E2m} z#LfD!NzbH^^BZ&q`%2gM7qM$50K2~5JP?z_t%~dWd*R*#O4s*JCCmtN7sc!QcM!ZK zQM|r)YOYe`*Z1k(+(MAZuJ5-Gb^FC6$3`E^`FUBIxRn+1JmSm&htboroS!rDksJ5Q z%mGJ?Wv?jb*PU&z1T5$0lBxT<8zXSGRiUyYR^IEu@M!9l+YpELL{GNpKIIx_b^YFmpdCu=E z%D(^>UL6!#OW++Wj}BulW2t=!7yuv3`5hf4GgYnU{02hl2P{-RDRT6-rx+^VK>5|c z0?k(cgbXau9E~vwG;Gy5+kKYv8(OxY?OV?8yjXXyT6W^h0f(GL?{yjf4i&r#Ea&Gm zGNaH+Iluk5R05XsTTF@N{2pe_-M|_@A#OnBG3VEJe-06_#vg%kh!zwj--a;*6#JL8 z*F!*>Z8^Uwq1JrjmJcX$enZF61hSktogyP9^SaF7i2HZv=ZvWOKswKx;N9SkBMO*j+6+KzFb4$=S>UG3R#{ zm7ESN=jU7kG`KP6_c-OVf#v-E#Q0SL<@{X0?O6#t=ePcWbO~6_ZxqJSpivad`Q6RZ zOkkHEGvjV`%iqYS%a7e?tX)-p0sk{#=gB=m!weiO|5|xGPud*B@CD(j^0By;;c6&X zl`pAgT~)3Ft1qz8_H?Xy0=#&t98LKVz((6G7}K?&(e?$#r=US!MAau;ReEQd9TLm= z<$|kXud)%(`Sm%NEH)^%2*n}im!CNERO+>}?X%$g2wSAQudpX^g`I6@pgc|LZv(|M zhPs@!grPgmH0j*Zv>g?s>!Gpf=xzX(FmzfjaZA0@M+w9GwLK*aUxNPv$P$K5t*R7} zFs#vrlra1e!S}!th8+(fRtJ_a+!$jckQC>K_Itu`v_CYqAQbRg%B=mns#+!e4n}w& zDEjX4q~B;xww|uj6foM8e!iFR`BcVb(@&~Q4~f}{hY>)8f|k*_R!cp6yF6b z>Gw9qn_5uPFFBNFyjW-y-I9JiS+hDQ{u-L^Nxv-3P}e;iY(+V(xC*3|igMNotCjRy zD_dQe^xH0wl=Ry=l9J-6u--!u%RAseF%JlfZ$W1ssb)9Y6$*^?&j0P2@2 zqj6T((d#d}PMHL4RZ2dG@;*qruy86t^GML*@#~4aL)E2?xa;cBG41;3c~meDTK;gv zpvCEDl{D35Y8z?Evg=o^u$wd!psfnO2dLSDu)w!djFx90mOUfiIvojR2e{jV!XYe> zC%l3>=Ek186=myJwM(Z%IUdO<-;A^PrJC5W(6l4NX0CyIRcSLx$Nub-sqC;m>tN`t ztv!PtzZJLKx}KfcCGED-#dPcnie5zhI7lb5u)`6=jiANyw8cfaXk{DcBpjAoZ{Yb< zunM%7;lBiu7FA`UswIgw*zE#Dt)Tsk;78DQb#{+$s1PmR&~p23oc4p!=}6o&sM(!E z?eywsIeqzyNQfN)Wiz;&h`)RpzgWrdowiK-Kso&XF?J?!K2`t!zxVT*&wS=HX3W^9 zkX;cPq^y}pc9AH>H*KT^m9$&hRLDprA(XUHw5R1ODwPzKgchYut5n+S|9rpid7sZ` zX8eBt-{wUI+?z!iE-tT)Z+;c&q83(GjStdCZgep~QkdSwMR9`b%ssc^6 zT#|QuqF-*?jJB!d$#}Qvj|S~BOWNjWXtz?3TR@OnbsZKNf9XoObVB^7lxOD_qQY1C z^D@k5K=@w{hK$H9D5ra@m-!+vEaPg8;v(@_v-@ zl0M{XR9aKtmFf?>uS#UE&uD^FP*R!3>lYpve#Gg?FfIb&77nU`e2^_?EQj1+j|fZ; zTZA`pdK~d*fp97Z-xK&66bU7JE+f%yU;fepq%IE|giAQxiq>r|E&MPC7ZQ-MAbU8^ z9ur{XN1*mmVV!U?r?(UTo(NpT!GY4pK;j$@iiB4K_P}J$%5?R>Z+9)N%UCp{tY%ONlqS#o~%237-->DEk)5DwB!V zD8tJj$nM3RWW@Ynj}U36@K#Reqt_)sIE{nJ1TK~nmva!F&!6Na_)&7ENQ4TPaOT)c z8H+*PhdGe5*%w|*t=v@zgOVzgdt`V(_&BGFnawr_;S%8xm>~zZarb2B!)g69GV;~ZQ~ zV3HjC0e>BV=YjjB&b^n(n=3#1CgrcwkVmmRPc9>>_Lc0u0J5u7VdJ51!aLyT?4s~c zPLC)491u3-U@d{?K;8Bn?1Wa>fk3sZm>U3oNA%!PR#=r?k8frD>R1l?6VVkkJf4HP zr}AeY(S;)k{LJaSAXf|>U)GI^9rK|ot`>FLhE)zGIW=}1Fo`8KpcCt(2y~VMnOKh^ za3(0&>r&E*b-~N-sCXbfY_mJ4%0XX}l*vEw`!xI~K{Wd{q1=i>%zm?PLr7-7I}!K@=;{?DsGNEkTgICZnwR!Ai<{z=ynM`zEJ{LO21W?jtSBvU0FfIAgeom4hcZc!R*}ph#r0D>2?QaV>=(OEr?YCajh?jMHI5{`>}WuZY&*pvM&E z=OFtT`lv~9-H~Fwy;fp7r^8MBnFeVp2-kD)E`d#;NO*;xai-=}0wC{5m-90Wp-GA^ zLnwBlf<*nO-^esse>p=Kd5q0_2)@Ko^a10;WH<`SX{358s49b)v0^?Hv6(+zS2>ii z>_({5gf3&6(c?a3bjG((#EPgSr(&zijD&d2UipKgf%E zp!UAR)hzD8)ZS+tww7^hOIUQTb}|c>-ZvDP6F}7ajLIvt<%Q^d*NTwzzDWpN3AFbu zBd}Bs#93n_fej!?R(AWaGwd<;^eE1FDJFhyk04|(@;zE^z-g%*9U5d{$K zPW8Q+1f~Ji_e>}jSjc*X=zFc^lYr=ZpMZ}+r0*FuYkxvr`Yb?5^t~Kz@ob>_p2_eX zRDXS=tW4iK3~EcD`ks-pE6}~ZHx%v(!WZ?$_(>nKdek<3?<%-gfV946LOB(L%IJGW zOI4ulN8dAYB~Q-O_a;l*h`zU!g4_)veXohXbc?xkGWC7)F792g?`?tk8c=<23`)@% zK=nP7j0G05dW*jI+(J_E`rh*hJquLdGkUzDU}??iBIotJ&ym>$RNphXtO|0b@8#df z>>Q}RXY@n`y6JoEptc44RNpg+WCe)>oW3^#_9-f%`ko1-g8FaLc&6{of^iK{eXr3? zVNe&SzGnit!D10GeQz@H7X#Jz{vq%OC=!b5du>uSdewgmDbx3kn$8P9K=r+=30w|T z-!p+Q5CO06eNFrqA|U!+^BGKNL8R{qPxZZ0PTdUhj&YfazV|p?LWWq@WRo2$MTObF zzBk-Su>rai6+NcGBKn;SUr&+hV?c*5$-Hb6R66}el1%G+7SH^j_%iyQMe#)!qRQ)g7Mlo4 zBw_TueNtALz&t`39s-fRXTC%s=il0H@Zfwd2*_O>3cP2Q(>U`-b4Zy%7N&6PZ3xJRNpgc_Bue?>w5*a@Q4Dc z?~NmHt{jNIx01kvK=nP7jz`i?-^-c9TLmB#eeY}nXUc)-dk+w}2e_Z=d*`@b#6B$S zxF!1DE%H1OeeYkWe*)F_j2Bm)*Y`Tz8U`JJ>U(DpI2CjkeXqM~7@Oay?zf28g@B*x ze#PWD^lqp7Z6!j!Ae7eqMiQ;M-!y60t?x;iK6>8B01IlZiYVtsP6X| zfmL!Ky59~0+d#pME~QBK8{v*r_Z#C5MEC1SlH8M``;B#{GjzZ4t_4x`qy^Fa-d{=) zME6VF&XP3H*|+g{1ItO+g^=0zV^VDCye;7$3ZmJ!QL`!vG5e0)gOJR=hafN*=n4wPid=zizJ94(@v`>iJMBv9SYq%pnD5x#6^|?q_VKOTNU>h4H%Id!+gXY{xa*;O~)ujxE2L_l>vqi0p1o9;INYCjPDazyvL_tEGe9Ws?h zdZYHl%aND>v?m%L2S6ujWs;uQMP}I26F0zrO>)wW8;qJ&QHY*6LxiL!{)E7fKzm~C zJJG&?_QY-kN`(6I(c`cR%LT%F%*hCjg=LmHz&;%g3aM=npx z@E1#CLmve!@gPIN+EZVc-j^`>aL#dHajz1^a|y8Ay0NmEg^$&!L}!{?@uo!_(Iob z7qKJc0@nLL{+nor*SUL;S1W|Prp*S};pfDRf^sGZKj2_Kf$1V6s^XnqW)m{85kp>i z92rp+PhQBq08~{pS}Z7emvQ*FhC3dECaU6}P<{ZaD$c%(86!|t(Pa4gm&l+h{&GJ; zqADJ;2=^qQs-p2QXuuOyai;KOu5}!o{y-;2#>N z=K)nAO(+&vh`yjdQ6VpQfCNN^+z0<>>A`9;G-}rVgu2Xp5Ft?^>n=fY1*$@tOrnB} zsgTD&?FLkZGq63g*!(0qCy%!=|ffzuR`7ew-lsRNE6DbAXG+$G+L?xZ9gic zkt=y}rV6=M+D25!XDG;1AW|XaV`lPW-IGfvv*{`;7&E*I`8CXsfU1y_?#Aj2RE0Fj zn0^q=>MZ|7h5Yv+Qt>L}+X!s}szRDzyrN)f%{N8PtB`*n^BYhV(&Vx#$e9Y+@SZTJ z4^)LTdZGf|RLDM1djWo`LYhRff`qA%7s4K|5~@O)Kq{E>9@RD#aw&{EfU1z~mtxcb zszRDTZcw}x0aGDwCH^L$Dr7b8ngUQH6jdRgOb=HgWh&%gNXG$HA?Fg91yqGJ0U8|v zuR{Jy{GTFl(HI8!Wh{4sNQD%hs*n$G>K>5ys>@VV$X}PIRmko6QHR;T3i*kXN(RO6 zQ&d!=GwDt`tnDM!y+DUGolA=Oh2z4wdlVqic zXG7RZ;+JP6DJrBTN&85Wbo#=F>2}^^Xz@5x5MM@xw5Yg7l~*Ax7GI5RB#a8#frNGL z^F3ww8bm6j5%Ys$A({%==6-a0pep220(XM63fbZTZXr-4LaIWZ`yj6j097I7tg4WE z^6aLb!l?$PLJoO|4g*w$e1gC#IS>_cAAvnURY;S@V289=4Ue3-z4av&v2;05utIf~IO?-Xn}+fS;<30o&5gT4j|A^4n%F7NMHgeINqfc zsf|tCk*bZY-GQi$lStC3jYqiC8EWGm*J=z=6jvtd1u|a|wQ(_-+y!*9Zah9C?Ic{O z)nxd!6k8_iufl%?M3Z%+^2v?zLQK{>tU^d8>w6IR9_VDfXcgTR=w!Vsfi56Wwb2qu z28$`HR~tt`I1{MaXdHI96%J}+zemYN)W)0P%mAu3K1JXOIS`ZK2L!f*B9T$G@t1UY zcaTgOwK3;WE+UM=d@ zP+}I7*PS5j!@=(aevpHsIhgb$k7`gPB88`Orr;^=aZosxgHZ%d0{K^PAm5{vAFH2` zbLX~5O8U1ElX#ls1T#1&AnU` zNSDcUZp?R{kQ?&^-Q;enJ1>gruX=t+Vsda*(2!Sr5)%p32@1Npn|Cs@{2EB1=lMd{ z|A0a-Y@r1{i|oz-THu=r+yIJ%p#}bsqpd&-++sDlAaokMIHu3SP`qz%P2F5+5_3 z5gvaybFsIjjyxKpf9T?OJ&i72IEztYMss28qIlat&3YQfxKE;JR z8P>_qa=n3?yU+0=7V^E{-Z+^Gce3zh-@|c^&p_xxtRL&od(tn1yA<%#%@;28B88+~ zbxyqz)It5QH^O=iWQ(EOXlVP;#JC-bal6uU42Ga?cMirAQ+N!4zMSa+qSsY?9Ttn^ z68B#8eBy5d`rO}5;Bz^U=YF&2@ks&6g-%kQ`&^Cq?2{suE$?_2F<}r+5EPdSo{HOh zYC#ig=u(Yhm)5g`f+w6v^S)I5qWU*c)vH5UL=jtu{EmtKaS;bRV;Ax2gXJQQqby@U z{-0R=+c-LR5KH(~Z>Zk@c9?o85j+p&S&&_WzG*bJCxw<;9<~gda(Wl>p8z^%E0>Ve zp@cNb)}f%ki;l|sNDWklLNuyl*F3c?N%y8I8WHwyiBp*1R@pVDJH?5Gv50XMoCDr4=gbA08huLbz z+8|i+A|Idt6DFQ^9_PBqxm(sU5d{+_oX)9!a%$Q-`VcsJ3MqfMkq<=8znt98s4@6Y zcH+meJb%EeiQuD{6q|=e5G;_`7AL$;#jhY{#Yx0}juccFq`*dq->fC_e#q&RVj--1-E8M{F3BnQ%`Vcr4bI}MZsWRiCi{OctF`EKh=1l9n3McKsCW$_8vcU!aGpvoXjFeN>TKu3`L#^sl} z-9V7@&?A@+T}(RUcL#YZeqi_vlq*T$a-gH9Y~dXJEaaVqZ1h|T_io|K==qCJ-$M4J z_oL@)aMuI*X+!_+Wh~?y`uli({})_C??vWYpbgz^Bh>}k&?XiOEacmXhJNOG*U;C( zp9Hj_-zBgKXhWMA(+XD>pMW)V%{O_F0&VC~1WpIZ?08fUYiJ&sOuD3@EhZhR(9las z;clP}UCE`yYJdw>Xy`ZKzAk)e=t9S$R4!DZp?`$?1CXCKbagLdp?VrgkNzQ^|KxvN zLm&877*q$^(B}{s1+<|}EEZVEw-XKBP!frR-nYfUzgZ1d|;C>@~ zY3Rco3ts{ks?g9?H**z${8Fc}hi+FdGlhXDu{U04@oTirIHJyi*AAq{KEe{2*!I46&l~u1;h+Th)=QuY~6D98v!4WBqPtIbI@E(K_ppa4khzOwn7lT*5XO8-NxZ#{!&Cr{yf>t!#9v_A;awflk#DTM2-I z!xNH$y)H>0wT@~mcX{D6B6+pRI;gp{*k+=7Bh>>0DS2EO5#Jg|9J~&qJfd!ZbT!Z$ z`eOn+KuR7_Cd3Me2yqktB&o|y+~9pI96)d4O9@N>x=E7>r6M7_aaR-nxCqFjsLD1b zBOvcpSAMy1KO}8Y3AlNaeOztJ+|b!Fa+(Ac!xWdWn|KhZ^#}UuxN&&W3x^vwy>a5hglJ z`i7W9-j@R{%nJlogXF6j`MYi-rN}*yF1;{|?!k}o&xQ1mKZ!yt^ zR#~B}{R&-eLI)$%1e6s@?pNq36FLro{wgFl<0fA`3)OVxOH|LQyYLl?CpYFL2wV&j zGI!J`tkE$HxFozFH|IqnC^zRqgyxH&JYn_8YIH`p@`84Qu0!BO5t17;`Gupo8&J2g z(FdwU;0@>~zG=A^6Ea1lY2;3___z->&8X!)>**hISLxlfn~c8%IWnS~V3rSgLGG-C zjP80zRsNU}1nAAwmOyKeBSY}dzGxOIvaIcvI0^1h;ma-Yx8w5!bD;vq_qWGYaIXM* zd)!T6u^h*GoF`X8etD~1rN;I{ z#X??K(Wcc`5gsIqFnP_gBf9LUI#xMo{A+G;Y2^L+NR*bmc70uxL|76uspU$(%%v*Z z;tWc~Z}4)R9YJ2Xem{dm;scY6&-_%P{%FhTVJ9Iu{gBS-b$7ausgl)_oCf-we#pqF zTL$TW&gq|woQ6db)G6;6*Pc@3>Hn96k|#O|dw*P}pL+4Hbvdw-bvKvqr+Df^64Z-- z4VP}JT)r8O<1W6sq^Ph(;27_O4-|z}dd~d_E)(Hf%L$8ykuBZsN}qG76IQ{E2yGBS zE1Bg=7YKryvVC_Y*I8KFbs-j&^jG!F#`zm4fqVgOFNy2{>76@_&91=ZiaHbE?M_(sj?qDgBwtRO3@z(+!aMludK@McV ziS6RC#X-Rn`x|h2xa8<_k{kto*$R9tg57`?*mz8Ygl7ewLj09L3tUQ!7I-yho)k$L zEl=_VUXxkipNZcCw7{)C!;%cNz+(xV0}AdfS77a`r)NdCKGLqh+EpJ#;9;PJH4Zkz z%))+4{8vB=D=}Kwrk`Uw1zOmPv)sTE)W0{Qbw?6E9B5(hByhVNNb7DV@E$0bnTRCS zC@MEV&CKhZzT$ds@Q2&uKIuYo+3S3PD=7%_tXJ;7Iv%`1G-H>H?+nqpKZS1H)#?~` zw?+eV5R5*-ov_DHRTj(iQ{)B1T#S4|z2HCYK)<`5_g*Y2uq=4CMxfk++!j|KS@10Q z*-6TaQNcz3IgADM-nuxq=f|i%x>i9jT+rk#Td3<@PZsJ1P_J%4zmrq7w|?7vC&%x} zxjUbwA$oU}Rk`6`vWqIHdGMRO^tv}n7DJK?+2_Qo4yyeTg$>)m+64Vgz{fQmOX~7> zg}TfO`?~m#FTf^50i{`g3Wwkm>mqbncV<{pi*?=RYD_5X#uZ*L@BLUX;lhi@a#lC6 zto;>>o@814;~+}j{7|Xkxx0C^g348o4bQ-id-3R?YU^sJea711?2@BPNaw)!SY*_? z9(kZJVzYbYpYaxx-4@X$KoNjd^~eKH+Dz3hAm*p9ne2nqn;hKlXbe@*66?YvM{R=l zIZ9&k}Fz7B{z-hBE4QZ2|4PS=-LIr9!ckwu*ym)txQ7ZAUadTyE~3eLS$mW zlQ%sHN!>zyCi#?#yOYwZVd^_cX(>^klFA2?(hZJdPbM}e#f7s)RJvvTnDVuD44ENR zp?$gb^4vb#otEcz_vkbaQ!U2+ry5Bsjy<=pqMrHx(hr|?+41P5oK-v2Ui>!2Y(z1S z{Enps5Z2`24g$B!K|Ti?39JXX;v8TJ#C<5~+Ay&>tp28SiUrUP`ktWz1gSwNz?)sJ zK~O5K1Mxb^_7tK9L+TImWm0&-({?@9z^l9@zpK#Xax8|j03;_m%Kvyd^3HS&X~dz( zM~#@A?@mi2%4qbzHeisnBIovv=ocY0NT)FQt!a~wd^-lm(2kvJzyA&;9YV}@O8FiL zdvQ?x2Og3j?8?DN0>gne$sXS%B_xn|BdoqsQjjLO3(lQDn`Ei)SX1E9B*zO)nq)ha zcR}(2NBN(dWUs5cbcb`0kLn-f&CTdX4{&z5$R zF;_>+-(0n0frYI5RP9=r_byeF(Xz=NCOsg?m)=vkWt0$OinMjt!nMMamO2T}2|!!w zQCCTB=BTBv6q>Zutx#?T$)_CUe{QLJ9YZ?L9OPZ+`M{n2zjdB`8H#JVQZoSepwuOa zyS7lAhlu%vqI?LlW#BLxlND%*7ch>>pi%EfR-u5-5sbrb8p3%Ljy#G-5I+n=GcaOw zjxe7ybAiqgOcLiJk+d0@adeKb1@dN~bA%du8F@gmy(_*mpvpVP`qR2ue+q;8$7=2-JGa4U`fadG=F!A?G9)G!3%^VPeq+RahV|dT&s+sg4$$xaV>zKB3eKHR# zojl}Kb}=$}=*L+RM5LgaJFoMInrF_%Ch2cH3z_>#)z%}1Z{-?|h4nAx`xE56b!l28 z9WoJ~NZrCOpmg|&6v&)Eyh7j7Pfawpu|Mfd>@-X^d~gyju* zD`MP-ys$j<)&y4c$Tpu_Fu$aEal2CG&la^Ve>r27;jdw85<_?vcIQ9a6D%~`Mu6#JEC|HEu2E zhV5fj1LWuSiw7NXB#wTih*xb|`;YBhwIhky3}pidn{&|QS1t_*>vAxiz*LYc>aQh` zPKtFx{pB=`rSc^;(SMV_@xTLV{nrGud=e(;_1~ir=psVikIIA+6@^Uyy#j$tL`e1D zuC9J%du*<_QCUd5&-lcRV z-H*O;Lwi`RB!>OyhY^#wb_r*bhr77{Wk32ST*A0sYQl~8>_*)0M{m)wz(Unr)%gbH zI>{-$AN?km8;VhN-B_{L2V954*;w&!n)Xki`_UVXj|D-q{pcJ2PT_&>N8g7)FFCmQ zAzqUpFcuUEJ=%|cyUWRW&c||!rBL|~v+Mj9z7SKQLj3U+g^>N|>;J(T2GITJjmDi? zgF@Vw2tR}-`_bPIsv8)7iT_bJvO%$N=%OU0d#cNa$d36FUmc*E z5?@W=N|5}<6|drc^cI6f)^TjKqjXP6>103pw@73I(EaF*$jQ?pD<$^q}UOl*eMyp^Q>@{)ke`e)KPs$csQ1 z@J%GaIGdgemDeuL5?7)+y!IqG9+E?!T#0=dh8 z?niGlhHGekKl;0gUo4z|A7ET}d0?m(j_pVP8u9Ca?niGNR{9*<-4e17d{@a@_M_he z{d=G<+!&E%@Ny!yAAPNVSWpD|!i|x#E70wQo1>t12K;nCdXr!y0V%iJkNzweXGE-M zKl+tKuKE(^{8zXkN{P7%=zjFoUAbtSn%oeN9R9^lDkA&Q?}D-o6iI~cM?d^umP3H< zN57fC2B7=V-|>Vs#bQb2#{3?VitI;!P!J0a1iBync?3p*yoHW0^Yfye(JU^#AN}Jl zR`#PWcCpOylD(gdaw)SPeG69s*^hoqL@xKF5AwP>j_gN2)g>tV(GPa#my|1l(`52y zxq^t2@N~oxHT^kvTK1#g{iaW79@%^Pses`Y z=-$&OI${u5$QH5v-qRnz-44=EaHH}{ixW2?{{#=iSn#h1`6swhV-G5CGG-E${W5hkzx5~;+|9ncT$bR&F$+tJq{pd|3 ztAdEm&>J!z8xMCZ(EaFdBQQsVWj}fo#;?(p&GPg6(LWFOSrL}~=szW}Q-o!>H<4@~ z^1?Dhm!Us51{dhie;9$5AbGCKYk&LETO7lN+&7P=x_^ea>_>kFqC+Gt`37b6cr3^T zMZ(hk=%;gZ4bc7QEfv(ST0Du^kAB@QBtshG8#udxIu#g?C$R9$8>m`VET{t18|YG3 zTAsfyWG^6iXOZ@Bj{yACVt=(4nIg&l?z~0LfN}~@O<}s{tbyZAk{gI$2UJsNn}`L6 z0@W0j5SR}NPHr5H{-P-qoSAVT`RfkZ3w+u^5skRA(;uIai0nrnX5%qOBDx>F(NMsl zshZQ7_r5NGGY05>^lOOG{pfdbW*gALFZLNoyjQM=Vf`BdwD5BX%#s6HfPaU;TOipp zqr9eFP;4C&mb+BrfrYF?u^;_spOch~2{m$JK{cRb!b&F+1QxQ}+m8ty;kF0qF~O*q zZ=JXa`7z;C1V)IE9}|paPYaQ%MtyNUlC=zjE#QnG0l0`_Z3DGpJ+L2w1~__O|zZ$|K?VTV*csbAa}?%>*{ef%LX2yls#V3c6(U zHXfH&nR1sz_oLtI9${!0WMcc#pGYc$K=Kb4y`TN)w|mJoBKdUUJE?`-ZTBO&Or$?8 zE3J!yx*xqgI2rCD*%eQ7#3>Mb(zQq#}y_k!r9CZlFm z6yl|(J48rcYO0+V3l0GKQd2hqC2}AyHJwdh6bO>9xbnM~nqq!G`b{n(2A(? z%0_=TBRB&jt$A!ULieLLIT-@{e)QH#tk9Df`_WI^O=7Yi{Szeg7|{LbjmDRfB+muW ze)QXkejn(5^r$w$KXT9&J*EiB2EOlafH#ht7}v1?;5t)2TeAPD6AX{vVd+Jv6R3aK=-3J88#Dgnb@PN z4x30E{|z#_i9|sxXbN;cdShmL8C&v@4JE$*8X4VC0)Bs>yFQpqvZ9Q=g*_4O1dzG8 z#7GHkUwTzGY)9<{(be^X<*-m*B2VXA^BMF`fo1yC416>71o88-z_B|B;I6 z<`PKn2GQmcg|Xl~pqopWP%N-ebPF=fvbn^Y(we%t1pJ>tw7G;)v-T&{UGV-CV***%j!uZ9n>R;f@i$Y%XE^qz_s0>CGkJmV)%= z5+;;WL8#2;5=Ki^pzUXK2_skXWbD@Zlh^0#eMj5q<`NX-DG+TgvBzJ!k#*4TAddZk zf~Gf@fcX)KHkYUx3oZq^xr9mLZ&0pTWpjz)-;+vua|wht0o`1}=y;Ws7c8xLsK})^ zmq6w>pt`QHF+r4*^P5Xliv{(8>bge4-?ALtblpBsdjWpBxrFi9Oud|h-&_Lrc$LVO z{pgLB3JUAecz$yU7f-qiA#Is@T za9k0JLFVQXM$HWh>qSXc-$RnRxdha)piGjLBAyLlD~Vs8k)&)cVM)?Hk|dq}e3DFW zE@AP^w7JBADAhnWmyol%xx`|36(G@d8u-m6YSUprw7CRm^3CEk+#hx>c)bjK`8qZ z-bdgbInd1|2z(0MPdAq+cI8LkBn{bIqUm1fy1B$bv7jT+%_WS7z6sB7E^!<2vw&_c z@g;#zfNn0a5}Ix<(Y_ukCg7)=OUM)OYx2H8y<&A}-;CBW(u|9*qX>uSF>*)k;1O+j7Q>h_+mC(*soVr~XEq zphEA0;*aOy20NnjjI}|~t0~t86xX{&czxz$IDMJp=RvnQhwwxgRij#WHUl|0790$^ zy((Jo=#;#*KY-(YAaAnEdLU!;A~KDtqHc9NlTjnG;-8A=Qlpuv0`iB*+ zdo#HNJgp%~(HqN({Ncpp5_EK8atRK0RrHtO?2JqBM@MShSuep9y#!zRT6Wf2R%*`e z0HPXoZYMQ5o#T^1RHLJ`MlW;xBFNj9aS66-F<1ooU%Jkf*_)y!)=O|#_`LXR6-V~{ zo+0giL=lEY@r)@fZrWxu&bNm$-jY-CC%xp{5i@A_6FD~s6xVAf=LX2RG3?k;xDTsZvtP*jq_wym!IlW|MD?j|BK0hE=A?@#7QlUWFVzR0Ls z!aY9q@zkfVUA=<8oLj<~huOWbm>Ap=_K-s2+9jM#PH=Jmi(A5TE@51wG~pws<;uP_ zi!i~~yn-(mxoB1fU8tJt%eW<+Oc^rV5*EAM&?0D5*4I8JUfmL2q8@93x+NG5jRBgu zC458lZlG=nRa!C!1nQR1gFrh_B=pEF;X#+ve%unC^o1ym3NbqsXQqi2wr4BS0qT}u zG_H1U3UOcJj=#xH+!AsQ!vzVbTY}N(JkY#b!imHW5{}rZjFS~{#K14Msinj(25Ou7 zl)wid`K&8mMYjZt!4cST3S3K5x&i-?khmorR?HL!akhhG(P--uhnNo}bJD3S^rUW7Pww39^wR27!faYV1vr zgWw(q(k4iw;_U6jO~{)dCn7LGguDsTsF*X#3z-RWJ_7SZNGITjI$GugTxvT0J~oVV zap{mWg8Z<3&7r0lZVASXdnt26hI?C_RuQ*^HRSsOP`3mV$*LgY-4Z^By9=mWLhca= z3dAkJgxRXWmD2L_ZV7GR7K2dS5>6yAM1*CyH<4@~^1|kpFd6PuBK)yD2MF8^lI>ky z`*TaMxG*ST;)Pqn_AF{GZVB5FeO=NLw}g9+#H9iD zWJyNc5}tzd1W>mGo}MU3Z8Bq-Mg|RRIoPVK=N0&gfTwt zfuv2>nVq2aVkq530v`f(OE4O(1Wh!sP}~wabz*t|)Gc8TG3u5umoqbg7XEynfy5`w z=RdzQ?GLo@JqdJ|11bD80@r}#(v0#(GjS<(G$vf;QjG@|@?*l|Ew0F>3Nj|VMl$Px zjtN&eksz>;-QIpo_#W=JAU!4+HLM`y$AoHKa7+Q|F~NlB&@NBQNnLtHDQ}O!5hA2x z!gY>TZcI?O1dGGTj6oW=gvvaM(ryW-lE^7Q-4cw;Z3x%9B`hR<9#FT0tpwf?0dY$( z0j6pqU~UODj-pCH-4c!?&=(X5Mcoo+a&#k5w}hF#SuNz(4RA}Cn&eW5Tf)om*8#mx zZgDdB#=B63wS%wWeg&Lc!d*U>ky23H681^TvfoRUt~^J9_O^R`%BAqUTS8yrdjjok z*AbX12h!VKCh#ICcsQfC@wl|gl)EJAmaxxxCbHw}kT@N!$`H^rRs9 zusfeQ71HlneA+S^&Uvm&`P%ilx)!yT?^zs95t{>bOE6kIsLv#@&TkFp)`TW*2`ix7 z1Jo_y(C)MnP`3n=;RR8biH%v}maw}L35Z+50{C}|jC?oRnAtvLx3Rb-Ocfb%OIQd0 zMWAj8CX=ivWA?kR;eG`&-4ed=HMdYkR*_pmm1DSzfo5Z@SYRQ~H=AC2xJLk&P2`p^ zg$678*~FiG&O49PRq#L6264`IZ(F*BV|{hd$)wj z$D(2YbxSaQvLfF;o7fI+Tab23Frl0ZLS@_%jFzfEE9;h!YD=Y!Tqzho5LFFh>x0rB z;)*bw%ufc9D?)ZFe~UQcwgR4)-W6dM%&9Nv>o8v2hwS^*h}DhP_zb$x*XJX*CGf?1|wxI2W|Sqf>uCX4ki*93)JPn1VRacYanOqOlFTi-b!5};vs$M1jB`wTFl1oayV_{U>GhC9BB}s8L$nMAFACyUwPaz7= zhNWYOF9GJgAg%_MBxC*wQ0e{elVsY}z~Wh5Aij*Nfkm-=K~#BH1B*=rC6X|%24NM- zDi8UqDZ?ZXxf&RePaz7?Tn%1<@B&a*gCqKLT|wH_-~o>2fFco6SA#DGU_AlRrw}=- zt_D-mH!Y_cn5)6F$Dw}#bv39x5ThATSA*dMh5~gpFln}5K-#++JWl+>BG8SSyZRv7 z2B@pS5CX>mbu}>QcqHvy4OS3+pYX-iApdxV2%xS80|@j2?x(H>3tjm+JA`LM!?3|@ zc}9q;-qqkw;(r0^YS4c$Q&pg@1|y-VtHE8ISpfK{t3fFdL#lC6 zkL9WUJ`t~jw5!1;ksC?W)DzGIfw~&J=#~l@Ol=q@L5Lbg^Nm3?6xEj3TPG`6peC1AVrdL+{oa6hUUC zb%!uN1Uf4<91lsx*B}vD_JVZlyNnf26L*2%I6Q?C9nyotAR-|Ax5gKt3k~Z>HI)l4bC8N zDku`3x*FWe(ISu+9u^Ii;%X41=rV-jtyR#&4WVUS4UCPY$jSN3Vy*^zNcDT5t_H@U z9Oe1W)u8Ak7K?$p8W@QdEJrt2gA%Am30+(bjFja=RvL3PI2-CH5dGBEV2zt-<7z;a zy{o~kuxeNqn%k8K2nrxq#S< zVl_q*+kPb44oDron4N~(L^nELeuA5GW31M1)FQjWe5M2F54g@6x~cY(_#lKmy~5Y|E)-D0Xedr zzsc~5kC%>QQnt1KUvU4>TvGBf`pULext&txt77%C4~z%;HhO0oJ^B?6Q?u`v_d=J`e(b)JTogA|REP%- zrVwKHO`XpBm!zY1U!&nkc`}8#FHu?>n%I3OLAw;F-PdS1QbO}~-;Knt7tZF12)G=x zB97U84?KgBAL!!$Nd%4u$!i?BqTSbGcmv#VGVH#`)}eG__q~@ymH@T;8j;22aw6XD zy9@5e!q>e{9E-h9%Ja?c+u%&5X+Z71BM1xw$y*%Vjba&gUyEVcvs{w2=>{pa*nQ`d z$XuXyUn9mUh}W+I?%B#mE4XOI!{W?7kMkb}P}v{H3lm7h1+3 zf!ci^aE;4d2?x9H#r3F&*nRJYawjN~2(|m>j^?!|&ZynDl!F;S?Y`~V!q5GNlgf?x z?;b=dV)uO=$}2$azC~wKdXU$}@#izMz7MKAm(1uIte<3`;4*-IwDk4#bD7bDB4I_Zr*m`- zP&=@t!bF33u>+qj$%q~J8#udx+JTM7)I@k@2d;J=120hfRUd855gEp<4aBbls;{>_AI}n?`uY+A^FhIrPEzf_ zYcdWbf3+^P^l6VIZMxaKM$}%cOW{~vPbU$z0~-x(2%5J8UjSzeP&;saCSlR*>73aH zwD7Hc1`_Y>z{AFIiGUV<4uM&6Ad3y}5O@nDAIvCkH0DXEqcNhRi(?ll7qXV-_4GN7 zse-2h=Wn34x1%+J#L3k1i1~yYLgluM&aJFJsSe0^filp{QNB<%O}J z8Bn|M0N>0OGGB-UcHwzVxHMuHJ`4UCK<}HuP9_X2WG|EY`{pLNHvkkAd0l;^&!tof zie0!e&7gMSRj^h7?R{tYl-I!XcHwV`{}O2LYjP2jZlJyIECQ#2f(1@em%4(aRi@k} zQM>Rg_bB6?J~A=8@I$0>FG$XH(cUgxiuM@YeV2I2`lllKbYg+jLhio5ko-fWCzqAh z5Bllr>h@q|_={wR%OqY_(HczPSp#$(!)UR4B^4}rJ%6kBx zBDfPIt$EBQK8c&JmC4DFFv;w~)=H>>B!*gb1R9#!g;N)k5Kz0Y(HO)fPiGfyMRW_G zcHt8U94`lA7ruu?;*K}W6zOr+5#QO#}POm1j%NuTsCaTr3-X7 zL5qn87P9p?R(lREm2_oYW**7Rm85c|@msp&*c{zgSW;xg2%&uuDF|E4W`sCanIP7!OK(E(4`8&y5xJZb`}?5q03Dv7j>0J~Q5Tzcs?!9X9NF zs4MnLI1huUuE+ZXEo4Q=`8F)N(uB4n@V*Mks*zFSK9ni+sR+rcQPySH)_|@WnM_*R zWc;d8Gq?u>*P?~K-={;73`J|vtA%UPK}Z}Yxk!r|lRL>vM>4)eC&Hbexnw{8dR8#r z@px<9wN$u)(|5yI0CbhiI0?_Tvd8`jYrZWdk@|iI{~Mt7H5ts9By1hD zdeaS7E(15b)OIq=vSrk`C06A^=~B|eJ5spzQb6rfT^un8b_#KJtjc^No(bu6A!>8| z=5K&>$X&S6v5p~YuG(BTBQOJKa~YF;0lc)kaMoOp!CfVp1oOgkU0Gx>I)ntNQYs$Y zY6H@sXxZ4F{;~{Q60y`Fd%0@}Ie(HnFK-9EmvLSiNrr^^;hJZt?eWpF@y?A=p$?7y zv4p%9`S!Y6e0;3V@#sAtQp9Z_ndRO=lF!O;1yb?U+^Bq#Xzn3Yyib&9=@(II?R=sS z6;RvfgTUeVU+0?YdoSoCMZt44RlA>TV2XQU?H2m zK`;LJFs_7*!+*m66{IKEM(0ylE(sI#V{yAHV?k?>wk{Zz$wql0n^ccO;9L<>>%u%o zD>DQ~N0CM;uwEM{TUQn>iu_kky z1ld<`S?mm!;ZC$=|5%goMow>py#W--*}_{m^D{?#Km+*#;={gZn}|khTa>p2GU{H6p7pLgZ>cE2+xUx7rjM9OxAflFzvO z?UL)NfetSgL-|-jq-2NeC5?=Id)GKo+-@D=KW)fF_U-LU2E8Sde;;8=C2!{#8T|^f z-?w)>q_INOeS2SaDY_L|z2AdWmp`)ti@|p5@_K=*7M?)V62#|#urCK^PQ~>A6bY@( zIh>Ji?iOA`;FhcL)&RL=tqtE{JsrYwnVITnX*;YbYl4{{;1a3k2CZHc4}<>0-e+K33W@9yGj;1GTi(cR8T~5EttiRvu5$ z)zVhoB}2;-#S4~$h;f#-#xCxEv9xvYjj}UJx#{Na$a_nh#j}3lnAKbt=MBr}B&+ns z&#$`dxRb}LN_g}UG>2^bJd>uF4%E_SG+g1KnWgPZVeK`qlDk~-Dq7ksCJZc;VQE`=B&8Ee z+tD}1f-XQUZAQc)!X;}W-qJQ1?iIopZOT}2AM$*&v^@{^8K7F#?*x7U$-ORlH@s(9 z+AM}W8_E@dHf`6QQj4YS$eS1gfLhv&$o@j*M7*W#Vz?IowY04wumU8*&e08A!O~_C zEC)pw^VAN~TrBE*huk+nEp0~QQ4h^q+Nw{->;u%&W*qbe;h3eZ6Y(8@TH1_*2aGF` zjrv%)+Dq1AX*(VINT8NBBeKX_PQ)y2H$t5zbg{GiR!H_Y+H(pCkk4C2vu z9GRr&jd!tPX=~!>)mNtCVI_@QkLXr?z~vwq(-v(7_FjPM!fIsvh=Iy zt7^U0iMEne^uaXEAfT)0eH}3fEMyDfeii*vxD!EoqHk2@P)^*0{B&<20`o;krh7U( z7?sOiUdSf;YY|u@LORhO>}WRYi&o^KH!LlV6=q)nX@)?SYk#4p8J0C;#68z0`fz{k zOsj}x?Mw3g9H?c@M6xP~c*|PeEM{pyEo-d_90t^~X2P_zE1Tu#->@78_e2pE%i1IY zSBkI<_a>6C%(C_) z@jn2y5R7r9<>d<(GOL20jMcn_CneygmbHt#NUU2Z&z+ z)I3ma4)pDzz;wzYeu8s z6DX#=VcGvy+<}2w)&>%zmbC{svj}M6XZs8!-don{-G&b>(83QRFhmZ-vNn&vZ6NuN zla#{Ch+uchd{J{<9BQr$`7z<~79Vz{3Nj|_Aen7I$ApEB7z7rw+uM%`!R^eN;HSp~ zqw+OnCvHN1OlX2oBaj{wjEbX4c_AAU`XJCtgmg@}*U`$232I5RIMgrN4@=s39z|(O z+65$XK2S@Vaq;|+wl+)JBg8)h)RMNFz~>?$Z&;cDZ=i{QS<()kOO=3H(oQ2V0u%{F zEoqB5nh(^Hw!$~7g)-l;+*QJ*5JhDx{4GH5lSiFQ7+Ao>d*Jz7Wd!lVfcCbz1m?(r^tP=8-T?)PGIvMPDpT%~ zs3q+S_o!m2{bIdm^nR&3k5>Rda*vDlmb9nUk|u9D+QScZN+h37oFfx$v4)LB@;so{ zFrzW|c4^7|DnGG?Ws8hh!)}8=2SnB|qh?hU!WuS2gv1*590IF>TElh`_(TrG8kV|~ z!h;|=%jIg;FnQBatzmcg;!KIkzf6)9YuI53wv+^xIEq=r{2QVcf$=Mn{ZwRSq#26b z2_pM|lXWd8>$pKH%TtB`|AwV8*}IO!u%t~v15-=d6(n>SP)nN8;z~11+G3*b0%}Qn zg}_U4AeOXm2akfrms$hqrSb&F&@V9+NA^ zL5oX=qV{Azr=DTkgL^R6HD&od6a(Sgq__#>h@sE~Sw!_Uk3_|27>d#GSGd0bTD3M= zUF>-2kX1G|ARIcJtil%14q6by<|=p9jz@U0-V0eMQ>ekQ2z?H%I|4_8oXa0g%kYN1 zlQx+|=kRF#Tr4XEMOaqrOeF3@URb6|@`<0P;65S3vU2wUf$bo9oy*Hs z@S;{u)na|hqR>8=T*ZzH8=oizZ$eb|Lbd`2g{?SfMW6);Qs1#2bGxs4iIbWsQn?TZ zLmC9af8bq8V4_MbqdX>s+oebi3={QPmeFr8E`l^4L?4wVR=>fxi!&bs9l!5!dGhi) z@qV^Y_bwdhfsWrN5*RE8GJek`Fb5=8I=$Ft3$FU9U5TKJyU%#9U5eBz%)dm6RG+AA zh;Eg%YH^TSM2`i@c#HF zqVQVUVLYc3qr&_WVao$oR|H@6mr&yU!?hH#n}M5yzUv!(v7|D_eSG8@7>@zHFxz}e zJ0+#MtS!pMuQ`iRN<~xiF6qcmNbE%(yTG&u1J+Ypjpm&5gawC0;{L@rw-& z!=b&Y$Vwu+uiX!$;W|#{$oCUXwqh#MdlFJeEnI2|7ZzyYS`#=-PWMm_kwUM2fq^d5hT|+fpQ&pyzjUL|GKs0#!RXU zPIei~L6c|P1MXC^pgYUreUNvbOHlgx#3(`@Z(q67^5nWdI&J;@j?8{O#rN}lP9n3P z2L->nJjMDLEDvjS!01!#%!}uUfhen=8HkEwjpd-%-LaqqM7E&fSh3iGR&wG&aC101 z$Y~R+y{8us)O&bGfOAI|VupFKZ`{4Ia(Jvpy)E>x*=OgjW~jc-HAgyR#=V?&u|qFG zEPNGO7ZU3P+HB?^92n%vNOzN$wUBlm{;(}kV!6o5muJsJ?sUKi?F4QW+X&D5*=O^a z*2b<0YC7S4$VK0HZ{}jueAUVwsQId(V`jV6%6X9O4_Aa6#&JtDip*=_DB7yN+F~2U z>K=bMQ&ss^CebZttff1tC9P4cv$S4^D7*Xbib|l_t#AjL9X8r8$01xNx-y$+Klxn{ z{o^X#JHW0|ohY-p)b~aZU8Op)W}44Zw&MY6e0$d=5(HBYAnAi*c?0^>U}A!u2>OSPbTC z$1&xEQZ~GT^qe7X(cCcj)>mU?RE^F%B*PU%HNF?S6DX3H!Y4U%I!FHjYB1gBC3i|u zoxyZI(enVm)TvDIes?U!`~$U_n%_!W>x_FXv=@MWf7W=s1OqQ&-=7VZv04d7zdu__ zjDCN%gtHxi4(ze_++_^bh@|a+G>iD@K*!Ek3A`c)GO+I<@I6S@b&_uEjL$xao0wlK zYZU9_%fq?f(KQRBuDPd1K$kgUuyvWj*n!$V@1vM(ob*U#MN?A`)gtB z2HucsxEGTO%8w1kYW^yP6bGA}4 zbyqSOR7qd0NXEM>6~k=>xHpC_#U;lVX(UC;Ix1Fs*zpwUe4@fL`ExSVp&)xVGgc$V z0wE{Hg^k$9n6F6_e>(_&h4U_fO`u39h51BRe~6he(3OsEzFZc{Ty6+a4Q|dd@hTnpsaLsqgJ}oaR;nhf!??O@hR_w=O<)u6aOaA z`!;Vm#Q=KW_9M^>6kO*d<-TPkw91sZ=7G*XTDm)j2L>wuSsi1kl~NX&f0UBa3`tt% zA0H8TA0)dvfpYVYLuUJlb0f!)3B`I6a}(z?-D#On{2rbD|4b-KokZq@qRC~hYKPD_ zW%ThcAQdchbe(Cu8$Kqk4DslBwuf;qT(D!P4M_25Kx(@J7d#M+J@Hsm8GByh#0%i) z`n7}L4n$h(i?G?ztU?Euf$v4#76k}SPIj!$At%si@?@*^FrNM(J-8T|S6CtY!DSGn z{zCMFixCsSP9ZY5RFza^aG4HqnvkO>+i;h^4KC>?o5k<~x8qo6q?B!jke(Y{e&;pi z@s7fsP)DnAys2*L;L^Z=myI8 zFJsQ0o#1u=jElXNvsb?7S;!{ixdf4XmLCz08GvuDyr zrn{Ol*NNJ_Lt-hX!}p$O3^b44en?Y>+p{r6@Oe zCn$1b&?%BDGS0hmk*UKXFbzw}Y_P8yr4DG87+SXzw>~5TT0)$@i%YsTVpALbDr%3l`$&8+jZ46*iI?#7Zi= z97H$lU*TbL!+yt!-5~c+I-w=Ppze~h!sq-G9yE&t!w%36S;g1QK+es$ucVpjkXA8B zogcOi7eN^Sw;u?CE9<&+AMHn%SrPVQXvz68szE4D%h8nl$FR0ed309uV|cI>6!y9a zus;jKlbHd9-+K0EVZ&OxAj}V2q!_g}h3Y_8xG^flCDA`du)f2rK{iF{Ur(7Pg2my) z)6NsYLuafFf3LJ&`KQ^b#GeB6 zX*Q3*ZE_&9&}{^^fP#TeGJ2Zjo$U^vCBMVSO1(|Lx!v8s$?GzrHR>&kqIs7NrikmJ zt5x4!$U}VdZt~5$E^6L-Pp~KjqJ~@-UB+>oI2WX($(p%D(7tGpoDE@Ot<+wcX*rzx zK~T`ak(No9nI9g#m}|T`y2fkXh;rxs5&eY=MxPuHehF2tTOH~)4dIX+@k2K%qk$7L zHXS|AzX&|9sSkLS8^a03gH0~=#ph6rD~S39 z(&r!?&%p!FU=Rf1Ssb)n&6|)Q9L~W_1O|fKHm{|V%kl{`_tIbDyLf)_2$Jp%?Yrmj z(g9%!2i2cvNCUaD{A}e(CuLUX#Kw511;a^THnij64+OddeHnp^fi7TK0@*$(pMYP2 zhWmsFi`wuJf$gB+aMu#@q~U@*M2p&~bf~h`=B@aF;WJR`yujzpfqv+vgG+*C2^aE{ zY%{qZ1Gk&--n@?v2;^ zb_{JZj;KrE^#CdHy*3_0mGG)%H5@D#LHwsZ=OgDpcCEDW#2~k`~F5 zb|ob3|L^m>=Q;P9(fxdWGv|HJ?|II1&U@aozvnc0gPqVpv~GO86AFTiE6Pnd%@eKa zoa5rr6M+*&D0L&T|G|gUQsQxNqUnj}LY#rub&xIx;oV$(k7Eo{Hn#X>16<4*H8`MwyWWID!F#DgKCf{B@pVxWu<^o7$C=md@N za+T+ctXuKF8Mq0W-9G9wM4L^}ya8jGXp(slh` zDEXq6lVl~S0{?Mg(b1H!S|#d_kQ9?j^)jR`1wl&cDI+F>qO&97gXck%x@i%l`9NRj zX6Uxw&P6V^d%V!OQbm~rtpl;iUgE3Jb~(`1U&f&^EgW9l%p_X_7a|E$ zM#^V*gHn%?w;DNrrj+oP6kUHlaefo8W$1qzgfDY3{M}e^5zstnLgYaavOM?;{+og3 z!C5O9s|3YD(L7jvC2Q@0=0Q_Ijt~{(!80XX$%8%MoB%Wr8ZS54058jaU7*&=Sx*S( zpvCguaHNNd2FZJEd<=WR@vZlJ@V^^q>wP(n7v(})@5jFv3yucqtxi(%VEW$j8S=q! z0j;A+5-zEB{snYzmvUoTDAO-XV(Ba;r|SrEq*bO>n9<`E1w_8e)0 z5lhLc<{$U2{Tx+d*=ryVK(ei7*Lw+g8px9((*zUVx-keTuA=A2FFz({gsoo zGBCe3&n?&;MVe%BE}vfeCYs$yDuhj;%|^jPAlO5Q%6czCOH6150`F81VlZnjLO+|( zP6W1#P)gp1nh%*dy(DAvW*m4SCdr#ovYMteNbcu2ACoc2%JIo_M22i{SQ#5ATtTBEN!q&fLLtTJJ+%SkW5v>u^U~EuG^y z^3mrK(kV_y!D<(X45h7(RD?_|TkLdE6pfgy@Ovd=8oDl_D6y&Kf*V|1WhkwV zmk0{(clRZLR?&R|D1F}D?(3I_Z~|0@Y}keDF5W~?TDwENoP|Y{-Zp)dGh!=I*xnzx{OY9j(IS! z=Y$8C@G1n~6X9;k@5}*94SJ6d723&Dhk>1wDS=-SA=B5Jb8{Y z$_C6wh~Ys>mIqwsyr|iUGuC#WAlANx*UiX}1JROzzCo(Z`b2PzM&T1{S1MRff{QZW-haht@bhX6Nr|nnXx^%b39OEU-8B+ z%{F#C{x<+M_B9+Y%Y_&lTgy}BBK^0Mv_V;kV6=9j%PJhBzR6v_dCK9hGEU&&yjkJM# z7|>3`88}V@>5pAl(sbgdQL%eu80Txluz8GGHW}igas%>XM4?1wK90FSqhez53JQEw zK7hMQge5AwaqJRdncXqrgm+o!ei2#8rwnuejmj}Njt0rEv!cR-k3Kup2$>HrefbD6 z=DOP=G#D+Hh+2uzR2)-)M##kC6%_agy$E-S2up-E;n*O;5+M^#cvpo8<$nejXoOng zXaSPD_8Oslj58mS&2ge!N)CM_tbHKk(_t~RQ_yk}2=cRqGUC}n=bF%H1V&a6%G+y5 zcbm{$1RfEgf@-c8J%RSoYP{tY9L8VT5aU=0BtKdmG7KIM`@|ewd;J{`gK%sQ_eO%?exO^9F^Te;-cj&%`UfferfL<;%d2@mT!q zB=mhlI8`%V^yN@wWQb`vGDAVodI1_b_6>q+s4H5?#Xa1<8|0Qg#)6G)QLxNC!%5YN zw+Ij7_RILc2#V!yqrSZY&;*!w+MDQ zkY7rgNB8?kw!~(#U56=`W3yD1zry?3c{hbQ+blMxBXFt;No`y>D+-a1a-7Q+L*$JP3rl%>wPwB%fw3Y~YA;(m zyyP*BV31~>3%O-4roDE~j5MB>Ar!=&Zs|T<7Tpg!2BjtL_7q3e{pwTkqAAv|YwZZq zsd+t%^q<|QRbtzK>juP z6z*CezkErNOTCP_SeBqaQm0j~t6b^?-!OBJT&Bb{I>WwBMS&BPMA|CJDkbLO2(|{A zNW*aq0a{|3SiGVFOR&Xo7m2WpVf}>Tdyu@!g=Yyyj^$ZODPlfsd+0b;8u|n~;9GVb z0-9iB9E&r9oXe^lpJ2V=_5}P=*T2Ex0bXQAl!N+TcMVZ}7_V!g4F};gF1F$LN-l2a z;@0mt%K{Y2A|BH~N8)Idev_VXwJ5-+@%J>^fEK{l;urz605+L;&=DCafSZVr6u=+A zc~68SjnY3L01Bo$GbN3xLUnI;vg{{`Nf2Zez?yQ#XCtd}DIq1X);s5+_Z*-pXRJg8 zzE8RFaBl+qG~W04lp7-P{x*HFCLHm83f7}Q!>uro{0Ieop&O{J@Z|ZRTF9B-mCpcEgg_zpymsrpd6x7KwX|Xevhm!m( z>%Mz?5rEe&=(4ppT;3AG{_&AH{SWQ$Rt(tX$K9$V##^L{RWa*8P(+_tW+E zY}7>QW@LJG1fL4SCgVrBbZh!2D-(gH+l4sJ2gO3ubX&sJ!$8x`)G!HsLX>WQOY%#) zJ@^;n0zlJkmyd0)6SLCobNtr;mu{hN#Vvy6)9vuTF$ie7<@*<4Mvts?EB%LJ2xz)J zfMcp$NEY7jU%mka1$nN;EpOWuL{`#`k@WQJa_N&hj7(Y01%qupNuO9)H@u$POHi}` zqy|t98ixhH!f6uLKVLHVaJ)_nSV{=$UB^YsUL@T}9J{&m6VQk>@sXW@f6gNm+wy_i z&@dic2s9!;;Mgb^5|LrCcrXa0M`uN(;MgqdPaK^oL&j&_&y*p#Vs}d)x0b!JyJ=XW zHvf(8KY`jj2_Ln&G#(H31!{9wZ}SHHz0E!F?*`Q7`*GYY7h?0TIDQ7nZF?;zq^M}x zCnp%>ieolLkTy0w+M~yyp&eem6Y=0gP$->*r(O7z{_s+tR$6pDG%24aL%9`b;U}Kj zM_A3Bl|YSO;*Bpk3FB>4GMy6-DgibAL>$M-g&2P;j+;U9!g9vzN}ZsgSY(#dQbZ{h zsWq#SS_#yeNqDF=RdR_2&^fOwU6g~sT-8Vo`FYE_!#xhDWkYcckqfcxUL4avdYseZ zIxJCYu5>*BiXVcO)SP0exg4R_KT(;-4 zMEe}Cwt4a3P@uiKdmWENg_n~|)P8s@Jn7X9gL4@u7N4Tk+?mGJT>z_OCSi(`rK9g; zH<;{8uwD>Z@ln~YxZ0GFwQ%XnJJ}+!Pr^;5$Z8;(h3FTg=eltEFzFV3Qe=0SM|OVJ z0M0uwtkG9Ab!E+IOSH5A(W?H(y}9Pn6ylEvJugDCs{b?uP64{A-Px9HzU8W{LD}Slfk}dW+Z?krsGFtnu`Ch+pD$Qa&CUND`ib9KB=tPN3gTAwqH zz@%;TGXZUukY)YTRfS}DP%Jd9ewfay;k+w2nXC zS;E&2@O-~wD*jV|tK+|N+W68}wAp%qS71CVnxt*?oj06{G}AWPHysb^04)zE;us|t zQXV!biU$oqI&?Njd6>R4>p~JqEA8DrHoefDDhx}uN}@=meIFX`23i4%uU3Gcb7w73 z%Tn$k@Tn{Qz5=XXB_31(YFSSl-Q_|oyB)_wke=-%T}iE99S`&=X0B$@!_>U@uw+=N z+g2n0KIpQXi}FmZS$5&O6KKsc_&>G!nx)P@@t_t^dpqJdN-o6S3vmnp$uU`pm&sQx z7P|6WZe545`lVx47q{HNT)tR3B&`3v#6#ZDDd?R9qH*SiJ}DQGlzE4Q``4QYQO22H zgR}(bIJ5ESAUFwAF5}F{-i(ZlGnZ722lYWvCgaTG9F@;;^>^R|H|E?GN*9nPgDDdo zi=L@-Rd;+p{(KSK3xN)#=#C&p<++vv{sQT#E-ZQb<(DTz6K&nijH`y(Z-d19R^tdspndgjIF14NGKpit%quuG zezc15iswl%?YCbF|6)-f=1jpc38Xt^o1-HVUEQ4^FqcZJ8#PQyCb0wVZ zzT{X3_cI_r?fmxeG8>{S({!F>8F@!?s?%J6t1Ye~CN}lG=*Qh&K;PYSyoDX{_wT}O z_)h@(F1&@~b-9ptA+JU}NPzUyPSU-*srN|xBKLkULMGFw2ZbdyZ$VCW`#c_*Za|BE z?avQ;(LNu8w3YENMLU}cbCJ0LH}FqnL%O7glY47|RBcIRQiqZI+*7!J5lvxQIi&&ym%IHp39O4Cq-S({S7c z^dwyqW1hvSG6jCY&1-O%im)uW`3A=p5tf~DO(gDJUf34iRH;uCf$o&s8poj^`JM~Q z7U0N(aWC2K%yq{r!ExcOT8kB`hc7o5+8` z{X;`ZNw)0ebJ7OMN$K#2m5-yj4_^kf^A@D!LtTe-UA+S!(SY4{CzsKxc4v z5vVI)&4VV8qu5jR%e*_GpabBk%Bptb#l0KYpeXi0^Okr)Ux73Sc02xuypf<#3H zvfft|NGf~-f3ql%RM@vMO;Mn!UXVcCyC(Qcs+!+2Z2`W z#$&r^;XM*>t=0Nw{J#Vmj|NTXuYlzAy~HD5LNkxhyL`(087~<=)rEXEN=^rwnkK_X z6;7>}F)JQ~dmm6MKF6^ZBp>@PE6gJd%;h!y8Q=F0v!YH@UQwV{7?UN4|7FGLa8Cwm z#oajW0Ld5kX2oTsg?X^v!%_VE{a3vBpgU1C3fdZ!d{&q2YdLcO!5o2&Z!KB90{>`Lh#o#iC66M|SP>TFvF{`X^pzm}nKdzCy_+ zpmwG9Ml}-<-?ag@(7yt`-O!0MxF&IC_I*z7wfnSK4bem$&QRc-=~O zJG-ty$yGq@nt|g15TwMedR{}xPF^vj`iqpL(HoGK0k!3O9N&Uu>0WF}T}Ih>M zAJG&fQpsrukHTx8=7bIOy9ML1*Nv0#FT%2Tp?>M!2JT_PmvM?iyv4>d3uz|K!!ZY>J2)*iG9VV)z0kX?XDZgeVb*_${A!@qAMNx6fw?MJzZ>o@ z;fwXhdF#zp!TNd!P=W!qzB7)Fav|1VgyTYx?zb1~?Oqs|E8F_96vLYKQ<0wn)cRAs z_2#Nz{qu026~0)1mbczq6|7$mcO6jc;|Jmpi1p2JGy~~Dds<&$Z}|jEqofzXQLuWK;z_*x5QRC&81RHA_ep`7o*Ylh2~FlISv1Q!jbL$jl-m-aAdsV zTW-(6{~@4T`ftJUB}i6v=2bk+#e6s^%W={!rLk`0{V0;tTv{E>LLr{48Zqh-}KWKhR^GA>hz)$s>V zB;z|xTg8LMKpR3P!dk2HR@+$50J!G@Z3xZ6@c>93?C9AYox8F%FqiICh%L0r>9^^fVXaBz(Z`K?R)Vk}E;W zX)b-C_Xc{Ji-}}xH4!__`I`A(F2cAAR}s~7Zi5s=edS`w-jg&pe>AAV?*8KDat zM^1C;7I9*7n#%}x|3Y^!`!pBYjRljOA#yy+XwL}>K63YEmFV5k{jehkPdNKzNhj5T zY-(;HWLyPm7>AoYQFF#j(na<(KeaU-0HAxCU+s(v0(04jlHb#OB;0F3=A$vAh82YT z7}i4w%mg}y^%johK+6XcW@VTQ+d|aOE~I?e4tJXf%g19i+b|mkl4Bg*#;~F(wdhzE z^J593k5CkK&f8c)%(ISlF-F{TtzixKy;%e+$GUVP$PPe{bup1d1rdL&%K*6N13lJd zB95Cy`2JxTi*hOoe-^e27ekSAv=)f49P6?M$A=;;<-UcS>s@B7u7{O6;19UFMOcn? zY0#Fzc96W?rNiEibumAB)A9m6nX39Q;&QA@A4EF?J=P`Fjur?P#lq5KUGC)0M4-pI zn3h;j@;g-$$GY6{5R!7NOWxs>bOKpUYP@*FvmH4O#J@SvX@_afTAB&YmG5${pLXa2 zw-?~2U(L_*BE2N|-&v@01)*IB<#M2d#E*K;BsjkHzXksFj(&=%;2I97sm z=K)dWFRlOd30W5sz8>rHqSw6&-DI72BoXDXg@fA@GoZ)17>!i=12gSdm(loN4fI%- zA^7OAE+2B|ZIP6w^im&y`1^^G`jp};w`6@ zk#XnpWyInGUZ0IBDI;=@qHF_NMyzneATXD`vA&GhA8u2SDI<)^T0kdmLcWaXiNJ|K z%ZTAPh5{`kOe9uO*v7dZfID4;rHojH<5iIS$c1HPgcREH!2aQ^0`uc_BRx6hCoCw%4FSGHVDFR!rqVB=* zIVcv29`jPKBNM(rH$PqHli6JM4&+=O7QXWcT4Zv+Km5Kx-U@s>TA;bFdMD=Bf#$ycIC_Kh zVOhD4dekCQ?vdy*FITuaj86&(gm*~apZm~qCrFNQ-u{@EY0~r2V^U_hN|82zNIsXd zQeq*;qs{X5qR#Q4GDtqO@Kpc`LUnTD6elpOh&c4Y?A zYtv(1cA;q}&|_YVMtcmJ9rIGB3;7r5F)y8PbdU=<=4Akm^FguD^_Z8bTulMV7o2T& z%!{Ng&7X$uVM{Y-BsO4W$aLf(?zSBB@=$a;YP@9~^HOk}BgqFbFL+XrJlWm1&t9}R zsIi031K;+YRdcBo{K@;UTF!i85l#z(=dt%$5cRg-cQQecaYa&jA2x3%GSb`L41c4_ z$fkkDjCq&up?}!?0g;h;vcjWT>Z9C|XOg!z^`itYJ0;!?&w36TO9> zli`PX*)oycRrtNgeuq!~F=RXt)R0_qt>>A`XV~8g>pzZ+j%i_~~IJ__MmDYhx?_&Bo4N~Lgm?*xuoSMCpjYq-7w)R659zH({<@3JOO-pwVI7h$37&i@SbCqd@S z7?aCrFggFZ|271E6d{@JOn&DAvN;r8Gf^XUUl^EA#%1GCm~he)D5wi7tjeFJ$HjyF zK#lH`Gi6 z$!#vU?4h+9vK5_qWL(Z5Hv4XzkeIB773}jg%~#{nH+Sl+MIWA2CtLwzJ7H~;z?X4R z^LYC0Ah~{$^2N2YFm#XqF$7JoVbJ=6en^g5@ zk@*zX#qbA#yg$~mQqK`HE)z*D4)+f$eL@Ka_cjS5*hgyZytS4L8CP^CZ_<8RV?PJ^ z83{lZOdE->`n+I7_citxa6i`of?@YGhd4mrks&K09v@ZdFPxry%3k0Bb!E%n!tRti zpyk}Jc{2wbuwq#d+{X17F#MdMoW?VtoosI1=matu7=y$vzDo0?rxKVVJQ1<#qO-E^Pi zwP<}GwA9^+yETB}h(k7G;kb2Q|c2 z^Pwl;I5vTn*S&iF)6VW)DA_4m#qPR2<3VkpJA#-Ld9<9ok3$!@oq)z+5RQQ&EO9Uq zdKBe_Ek2Xr-X_8lpJ#9^2FV$FjgM^ZVLlWyj^pxKkk_~LD2b132=WC=J_kXKY#A2j zSCkoQGKpUCAO?aOvX8}^j+t@!vf-4}BO)Wqi<`r5=4IA<8FSf-o_A*HXC~7JelL}g z{aQDB8FTsgoY}bYGlU~sw_XMR3Xv(yCxiduWkyT>JFCI2XW_NM=V3SxfN*~_%Xh2uZb({N2URy6jr>MQh59;Vhvhjd5)CXK}z1a3a@kp!{e;>F;ww zYSa~v^Z@6hn;Bf}F0`pRHmvUS>1?vwKSmdE6fASmdy59);S@pAoQuvnaoaJRN7Gq; z>@Jfjp4elCk}Mvc8k>AdQ1F@4F`GC0WT!)VLed9HU+yf?U1*LgdmsodhtlgpifGPG z4ZqkxinR`<3Ru-jR?yzMhEjANW+(Gn*J`yo2red3E&wezc9VO$75q}Jm&m;a(&TL2 zNlM%5eMtjwZ}`6;uWc1kIGD@xK@HjJ;@2ioJc2BjMl8r{*CPEq&+9N)Uqb%^>?y-e z`Ci(~bTOISek{}m(Y$kMQwt|J1mljVHyvK#h?%mQ#(Y}h!@To#__%rJ70&NJ=AC6? z`6#D249ZmTVb$J3{5gDT)VL<&9rMJhk*NWb%il;ynaSmCE;JfDsA6*Yk|o4nCYKK+ z9?gMHE*p*48=6fn_rtpn(8=Yiaa<`EGP%3}#{-~P=+WfzA1;;Ya$kG>}$>j27C-H3n(8*<^v7{K9pIlyw{}SQIB!h7{ zsX#b3xm5n;mhQ*@fj{D7lfZ&u67E&7N9NQUN}ww$=Z&d-2x7Da@l;?9kHBBtS);IMKZa3 z8%ibstyYc5egWk~)C&2KUn~|m;1?X?!HYfOFdT5%|$>amv4>8sd%}`<+B_|CYNVBg)+JPKX?C)awbsO$OmBs zKRPpIbah?C2|ES_&72)F=e*O42FYITz73peJ*#uf7En%Lt^$`}%rWnj6qos5 zfX*KcbG8M6xvaJ0=Z}`aeGX(=I!0yW%88qhZ|QuAz!xIqTRKLKRTQ$8PU1{jnILNE zT;p`_+0u#Tl+BMFBYgy-s4`JevJ@2~=TK7DIF-M~V6DF6+B7ZPMz4}8GN;^(aGL;~ zQ#K-7nwAssbILv7o&a=Cc^Hn%L|EpOO@w`x%M06_@_lgc5n-8AUW(&o5ticKL~^~$ zjP-NM-@^S`gk?^-@GOd3kR0#AvN`3bT^JNLA#UbJb4A{pCsPAoLtN&RyC8Z9&^hIT zvw7n|v9NSb`ChJW13IT{TKK574K)#S%HK%h$((XY83RK==ah}dTP!@AQ|^ZUF+k_W z?sV3&$&GX6yPWFh#x8|>G2o|j$`5*xGEr-D%G01s0y;4^*K=mT@pH<-Ig}1?bg=9q zF3Nxomi>g|TaeB>Agb7PPI;fK3khFmrk?k@H=vuWQ-2vsWM=AwbBP(yIc1}fN^LRI z=9C}C{{f(L%A@hoIpyFyn)V_o1AQ;~0K`AXHd`10XDCqP7vXqJE@Y%{8;&1Aa%c05 z@iw%H*;*#N?X)s^?OeW0SbRX<>!^}4q1E|3Eudw>N=FO=bJ^?d%Y>8R_6M0V!KiGq z=fq9OmkHM*aJ2~eGQp^9cUoS^%7mE+JSali`&r{?<;nz|LpDF&DAJEP#O9EPqh&BiUgEsZny(WgZXQ7AkVksS(IWX=PL<`@A#=!YAh}GW$2p44 zAzw#^(>K*Tm_v?aPZU{sQ-4J6dy&0;&ye-IY@I_klj!M2vW0IVD|5(IE+psr6 zAbGh9)#i||D`pPa(oBkg>ue6$VoD)>xb|2cl?WBn^7;TxtANfS8!fIhn?wE$?_Yt= zA=ewkumI3Gp;Q>bvkT{*$|zTepk`|^(HmZpauzKGBLn0N;$teQn5 z&qJ%byHVqhpzV)Ojf-eMg0LYMQ*lfH`LEArqe7=T6NnXbb?b$VM!b#82hegZCW;_` z%uN}YTrX{}ajo`=`jtgy60`&0?+^0MpjlwyB)!YajAc91VNm+Q?G17+;({TGd(m;B zkGZ+LSo=iva4wXq5xLTWF&3v~I!Vv}p9^Lt+y^tk1ohgGCPhcE7P&1Bd23T+iSgw= zgP~jj_j2La$>Jv~@(&Ixg{`1G0CzgbX~u>2+bod03PP2tHq7Zh9GdjkjF$2) zvxWXz&fhSiU+R#owBgf^_H@-wiXGWT(xVezZxHx05Tu^Ckw&3^Tpb;8z)Fl;gID+# ze|EwA9)z!OF<~&p5J>$vG$Y9}YLV<7rp{P_WSle%|GX98MHhS@}wHe z#T;3ioclj|-XPdV641Ag zIWVkT5Z3P<3H4eazuY&oqM8aui}fdjwZrw?UIgc15U%0k=pk$d0&<64mI)(2C^`y( ziQxg^1a8m3|1J<-&qagFC`dtZ36$KsFXkL>Z_gx=x;kta&gAylkWL5T0xllHaUaM% zV^BtbX#@o79K~L1qqtpZDCI8*hjGyzM;DND5f{b6Ycvs8!9o|<7xa5i;E_vpF^W26 zzN^>WuvgeN%2i|+DZ<`#QpsScn0RWe!Sl#!vGo*zECjhyY#A>v;#pDk3;tWx7&xeu zqRObGRfkAr!F#BbBI=-F4C#SAR91?3RxDkL{~+K>ztkiucvBe$WulVYe*r3UX5!WG z&3Tz1W9nb2s32zzcN>06fa`|N+tg&Hp~G* zZa3Z(Q&Si$b&(3Ea{Ffp-+*u&7iSM=%o*fd%|&=Ae?H*qMNlk4MKigR9>D_N%jqyI z;6m=^4!waLo7_`?)DgSO!dl^CZhr-NJqTxVar6~b%^;l0#f>;df!u23RFh_3R-}3B zio(9!{sRBCBG8$OBd=sq5QMF{7>VOb(C{NNph+__Bhr6X39Io^eT?@i;m5gXeHBYj zK==pzD{x!}+%I+U>@cX_EGn#+xI;(XMq$JHtDs+n*ZWZ42D#OUu<;mlhgYjpZgJS0 z+l{ZLp9#W7T#Udm1k`KG#Uf}$?Qp!woi_l#)?KNdHsG;|Fp7@hBD^LZ?7*{8A1>;h z&7YD^$d%!kkJns~FI}-N-b`~@FTCy{VU>kqvTQu~KKzwH8_PR!Y?ljZEZ4o3X+n_h z;WX(YjC85Hl3k|@4t5u^{N_VcN`r{Ght}?PirZv9S=e}ryG0#^xpmuxsq;T1AZe8z zLm)>3ZIv1?<|SO(Ypb-wYGfqC4u*dTh+3s4l&B~~tMoPzl2++m2uuaqDt#Hpi*g~Y z(k(bPgCKV)2AR5KFdAEHeGyBB?{T}}I&1`~IY<~MFIXg;!C`vl2Pk_NuS4Oq0^!SC zoQdOfxpOO_zmAZo^jRdWvFcG!qN3t-L_(du0MiQ_kr`#gEn zq-fzIRp->OX3l5a4p;H#$dP|xxC)u$h+2%Fc}VjQgajwGQd7kh#8CHsLJzitQ<;N)&c5~ zLYFelNbHVNj!yX&O8GVr>IFiVa?VHz?}}`piN2soC}rO5P;Uj%FIRG43)j$QF|f4l z$bp*sUV!y9(A;Mn4z6%)Q!IU=mn6qZ?kgA#A4Iv&sELX~Q8sQEY^U*OERD72Qj43`ef4+S55y zSqW4y({;NVBY9;OD@e|BKG}Oz&J0UylXP2h7eOuiA$s_0T^F?zf|1?jC$gA{4}-iuX} zhxu{U_%H(OIV{Nkr0Ct6c(wVp7WY?GzJc5E+xVkVkY^gS`B=Q(t@5CF&D+!K1@U^m zaEW-mZe9}cpv?^Nda7`Zc*%+_pC&<@>&0t9;WqJ-)nZv;ohx4R3T1&Ys^c`Xf|;YiE>k$SIJJzj=U82xFbh#|EQhEBQc@o0CLn( z^{3;$rOIG6yH+t57Zuzhs+RAes_;|9FX|#9J%?R=aF9Qu@{&dBZWZKTq3(7)$){JR zCG(O3e~7A+3fGD%c~m|HB1=v>N-fdQ&vK!+7m=QY@(APA_~S)__hTQ6-w}n)xgGGR z!|A!7xmQnO+GrdTjiBXg@?IQqnCO0*>p7rMLO;XjrIwOWy4JoCMVeXqji)^Z(idcB zmV&aG5{n>^k)7#cP&BeL)ZLbmomZpVlq$SfsraRL^Wf!IFy4`5Wam>)3JUIU_hpjn zm(2a-eeS*$JTm#!<{63bL19gz863zTpBT+FjtA{8aSYl^GJN+&5cJ~z3>SmOYKA1z zIPR*7+lb)=5SDP~T^w(MV_tUxlGv_jjmn6A7;udkuwOV0fnXMYya7RbQG4lILGT9u zOPr1pALvT%4izz0F1I;Jw9vuiG6+7;($egc3waQ=>q{vaWr^A?uhJ-q{O-~q*cbW+ z79q;7UG$GAb>Qw$Q5W_f`i=bS7w+I+-pzEpLGWrgkypC@VPWohFjqNwl1hK!^ikZs zPD-c~2rTUXYk16yNd0W>NPG4y9gH#@Vk%U(>%>59b zvMo_Kr}*$r`oiT?3GX;8>;&3Aaz`LC4NqIh2B_Sa3sT|o9It@x%;8XKg$?JeBa&&n ziYL(80S*6VeSig&P+qEg!?4mq8bx@Y1L{@dq67s+)p0E5&LW^=(>t(gl9sTkMLa3Wk43+ zSVaT5xfs5j7Dm7GRz>@K)Fff`%q7QP7rYHSxAok04Ta>l+IZj#4ZIn245 z2eN8&r1Cibm}accdQ6w0;bPEM*1#gubj7kDSjG+6@Nh;bt6;f%m}K4Rw-OkLo@nK; zgFMj-xN!~`_>&9ACPnfIrK_xstzQ)C;@BCm9spy*9ggZ3eFee5)K`&xInKl09T z1C;b_l%yaCW`->#!~dPT3}r){WFN;5r$7B=Rzeyu|fWpy4B^ zz0%1C!Hl+;_-}Fe5_enQPRkgCi@6w%V=yQdM$tRmd6TPUK))}%-D}u@zswCihy^+~ z6i#Be0iNdjsg5UIYI-$x(83XL+5pW;m*cohE~LBrG>#`g`e`Q_<@@AVcbUDWCTMf9 zq{;Te<%Aty#~&>%<&|&NLCTly9lxh|3EIq&O!!LfR-wf=WYaW-UO7Z*t73-;?}glI zQ0iyXf;QvCXISn*+>Lk5_G+dYhP9J44%+k*drm1lNoZx+w4lw2q9GMt#@*nWJ-lXx zLLH8wcksXE?^1;%H<2E~{g<@(6%@*yrKp!51re5R0?n4?n1mD1*x@HXDF1NXp;3zC8FvlyuN|61%z9;NKYZPK#*MPlvZBFK)sGH zFfDv6dd(x_vh`r~^OH+NYkyeXQPLIUrLW2mIbO+WH4$A|wI7rjaPI|SJubH6_z@(( zcJ%CAP`I^sPLTVQCJZSvNm8bNxLpGN2cIr?P&R=Ul^q4+_2YzO4R>RY2Eawt*$e)N zN|L6rM*PtnRhb+WG>|8-vsElOg#Yb8j*xgUr&MF1YpdKdxZ1EOGUJNq9}jZ&X7^&w zY7+)Sv15SPGo(!rd<^Po$$5+4XD8Jc|m!R>$Q#oYH=siM?eF*5QlE& zCILOd#VnBSx<^389g&U#X`_LB3+HQ~f$Zx886ttSi2_OAMe+g-A zAGSvzL7PF6)J=1qAx2@#uiVp-lx-$;M!lxZrOG%^YKnSI+e$?-Cj1BPQBwHV+h5E6 zvEidGZL`Y!F=0crMYIEy*5W0{va-Eh#w$#h*i(pH~wN=UC^s~9E?Hw_W z7TL_Oh0v?#j=`hM(GT*uw?`|4e;F#N;ti;|m9>ti- zqCK6vfxAVHQR21#$=xhtR>!2ws+VprWfpC(Han_vnI9HxiStVHNAtW`60{f5v6Nix zxZTSswt6v~eg=0{tShE<67%}+&Ah(-Td*7n?&z9P`9%{*f8q4OU%QCc>JkPwpqRzm7I!MV{RMQiSIFyvfOW?AB$vQZn0bPTdxQEmeBxkvh z-H-|+vHa3cySrQVA^rMZON!izp0X8DO4!g&IQ<^(>ceVDijZ@5g}>0BU&P%5hYTMY zBx@cZ`8rq=uHm*gw{476*s>=Y>)()hdi{(kRhr9KMQ6-93ha}AFDp+SoyDc)1d~PiO120I?Dkk&zvm>g?x>UZ#N z$E*6iJZ})*#KpNd&H^bJa5Eu>xe?0wCERyAdUcR(2Al^#w0g&gmG0BIrNDQTg)=AP$R_}Cxbp+5B z+MQnW26(=Ob`}1Y11+4N!m&^;q;TGW<7bfG;v`)Q%_3tXi6f#1o4V&A@~NZJzoJ4) zOo>(7BF4CvaTGolAk5INwL5BmTZW@+kgufSR}k$40pj6H6bU z;sNQG_AoKXJDVcI?$NJszO`KzOxzI*bz#szWG?`D`%;h?E8$(n*ClxeL%9X+1kj~R z7T;J!M|1NDd>4vFnG;&!<6|!0*RrL0AH)3!Xx#q9@w;3|-1dEtxPkQAJ>$kZI8WZe zaj|d>FH-OufBZYxc!+p(+raBs(xDry@v-A#?LvkaLV4@1Sw&0l2da)or0sd}Hi8lz zrxHwe5U%GUHiLIpF23VpHW&AU)FxiG8*@>lzmCW!ZjL2;{s`}PyeiG4(FVe8Ty)2A zEXe(QQbver0EBWjhNp(xMYTzi3s5f43G!aO*J)=?^?)r}|^ z3pB0X_VG4Xg|wOvcP`MMmh&sU(2OXtnm7I#3#FX@6yi#t<@}(Bc{hPBzBC~!XURpB z^ICjAivMh&#rN+xeiebW^dwAx0R<7T;`@l%Y%B}3_`Vs(ji6X4T719D)f+%ha}%*CB^iwqD>cB z!Wu5xq|DgvlV^x%vySxJ_%8!mPSlt~9S^jeI0;7|knWv~UQa2e(`RN~FlK5t$|El% zxn18Vd61*ItPnL}f|L}WyS*VZFhnx-I(bgAo^l$&-34-`*fbvJSxUrY^|BO|ui*a@ zs3%2bK+{2s$FI5bl}Jid|MelX#NV%8E}Y8)02Q+m^_ranV1D-hSI%bNFjY>h5Um0JH}E-EqTUG_B{v^|9h6 zzhJ)9pljiL2($(jU#&rF&Sz^*ph;TvwF`pPi6p5Vci0>M6M-h_L>%MfLXz}d9B+d3 zHN?Ky6uu;}y9OTjGx~48leF(sH)Qaf*j{TRNgGF*a7e18JxyN*#+_7oaCfiLah0 zHI6%Df!cDsw`EASEwAGL5>Q+I#POS4h%HAv&cGE&pShAs#Hf)x?OH>#kOIPd=LQh)J_*Ot{+f zu84Ro8atZwSB29qQgdbGO|Cl)V}czNJnN#oaHPu;;q>rVm+M~WObtxzt^sYeop5RF z(2Xp6Ni1M&f}8ofWTA^w5O|l(fG7SJ8+Of3;+x>Bt?Q7v3heU=AC{U-!n;hS?iV$} zk6|o<`V7cf$wmH%CrBq3PPTMpbI$3pUdxC|cmuRw5!wOx5|*pRPAKEbbMX|R@I)xJ z7gDx^oTItOduMWn%1NhA+zYjdH4QgH>43nIBJ>Ft`hAU28MH4iBvb9OXXE(@oGU`| zeNFNJN3-v0Tmw1CKmJ}8?0UP+<;-SbW30#5Bx=r`(8BTjxfzw?K;C~ zWG&x1C{Ms$2&6;kf*HULJ5dQ~`K9k+EC4q6- zK(8Zt_QF_$m*8qiaML3CR6t9DrTA(|u#P*j1&Y3X6J1F3Ve$8+QjI6bG(g|JUN}yW z3wiq{;kX5)XFEw(D%}&_>u#(eUTZOdxapFGV-{q2*v;Uf@T>ZyDple50FRqNzUupQjnzH%HzF+`)cgkj zSD+u%?Bkd$b#*Swss4kSx=&Lffy@UrMr9FjIU)Z+O(z68h>-uF#;CE1Lbj4*00QTW zkhHavwViHjKDz9!XVmTM{5YY-xvWK)I4RcjHi>H28La0(+07u(<0jj9p;aU$NqES! zMtmC5Q$oy_B{@erV&GjqA^XH~9@&j{S(mdJfiFO2U5*K6ROAHFPnOt}ilD5^Y4Hq; zLO`@Gr?WT9T=oe3x}5%ydxK1B8Z{P}%L`d*-j2XT5z^E=-i2bRDGdjrD=iuG;;b`A zs;{1!Mn{bM9jm2P^Cr690NRN)W~_pYZ<+lF_j{n7SmT>!ZKYLtmRApGC-zLA=;o@> ziIsgK4h8xmpN`{HxsVrmERO3z`a-A0y~t5(>&_=#e1c$p6sgxOQja6_C`jJ#IM!4f ziAXB7nL|6ha7PjD7b~p8OBj~m^$~)rKv0HIV;hu}CE zq~v2z6G%h?pYY_hCM4*e5C6GuC#B=#u?D z9!K<1klF9U$kZ#4{r);1Kw2e4-S1<&3(NNVxK5sbO7`F~A4X_hSj+xG%5S{&8#;ak zg|cA+udo9$ekBs^JpOg#x-|)iF4Z2FbA8J0`*E4qg^&%!i*8i};0oo7^i( zWe2Yz=ol$v1uFWc*=jhrvAr zPz3dUkk#CtmvOmJ&WbghE}=|^b|w<1gYb4P#^JaTXsgAwAljSnw#h?%Y`Ql7G=$YYrVCSZhF7#6|K&dJA0W_<#v9fQ^v==eZ~muVaV9UqwL)25@SV0_??e~^^%f%D;< z3v_(Ic(lFY`SF39@gE0>whZIW^V)`pHpU0?B-%1QumIL$qD?x@3w-h{5^Xj{#lGe^Do8>ts~j z4zz526~{{;`LYuzSGIP%!^&1wyUVL>`502QC*QxyzEx!R(d)!iLCy>nBdy!q7 zQTz5L4jb?_L7AxS9!r%5i6Gp7*Ij6t0>ZUie1&7Zs6A!xYWKU_tSy-3ER|BfJ<6hz zzhINQElbY^N4IT#+UR67dBQ5rRJ?4U(^T+dmZBRoirQz@D$_H!uXVRY|9gAtpO&qE zwAVk*NywtNuf0T2aJ#!N;}QS3`!v*&v$O6?6{q7AFVaYEMbmiIN71WEw^V=O^vhzd z!7M$-gI{HC3gpR{=0}dpqEzRqtF3Pa6bh4t2%|$9VJ%X#h=XJz(otmt5oRH>!733 zu7nY12qm-VuuSiGXjVt1+cH+{p|0(3o9U=r6E>GkpB62riv(tbvh5yskCEM>+P_6{ z1kYVx*`ojOv6HmH;c&OCvI6p1-u^~h)U z$WHQ2#zT4dEPDC_6z7fTe;eF?79^D8MPlh!Y z!VRy7Hur75egg%kI+hISOv9Hvn3%j_WNwhWILk-xe!|@#ZEAJ9uOW`3+sxjcb-`iF z3^TrPT=d$-ysOBJAr`kKFh;g4`Wizv0o}65WN3joX_N8O#QVKN_YY{5Z>&T`zHGE4 z)5Qbeo(nYR&BE~jNWSaTXIJ?_stau&^PyEp`X=Uu#ba|YRpuu*A-P^u_2#1PyF6T= z6_^RpGDOH%U}xZe3eXDdE*w)pu~4)E`-rO#fX+{T=qw5XbLG3_lFIB7u|X=cKj7~M zn)^R-GPE?EE62(B+}~gYg8_ij#`Ibp6Ll4J&%CdlIIBmT%K}e48W#7+C2-j@?`RZt z272=DARGgMel}rZi3$pQXLmB(+eKI!ZqMLYEW$F$WWqV#Wug0q+j_X`L|7Vb@s&70 zdYdy=%MaHrwR_CDJ73=|B`W=sL`Ax#&5>^=dgODFzBqaV{cOab(|ldh zLb|8dz`qJa@i%J13t6bXd-@RMnLy+J7LMgW<8Q+32vW{uAOCG|e-dGdfA#kmWCqE< zv*IuHhkK$EdB@F*9hb|+BJo&QYl0-hJbCwn>926W7S@QdjseHaah|d$ieIu!@jD@*u}?T~{eVc_;RB_S9Gr zjm*i>MZM*$RCXSWK0wO<-?T(3gLibT2&+L;7&I;imlEpKDSyvw>H zi3?MeMC1opW1x=)!9J@gRIYR3WL#FAB`z)rH^G<>cdqd1c-+aFEiYiML$eGk%O{i+ zgU25ru?pnf%3EwqTD(r$Wa@U|i_(Xo{08?|5Z=c{{nhl@LB8yII?<s*#VzQlVD z?lVAssYkG-zk9|kOPLqtj^8C+W#WC7`8vT|&CLDmI^mDX_Ag;e^IooV{DXy>vh*YU}_8Sm&+ zG@sMHVCYfpi5H@y+HXbqIH2Z@aOTB>GPpv~HH)9&|1nVWs(r+4C{XkI;phdD9eeNP zh3d3{2Ocl55dVrod=CCppdlXB-Rkucy2uGA8sc#uGb9T%#IZO=$%Q1yG90gfWI|#S-4-6>#KpEm0sxK9_vLDrKNY zJ|BbQXrLPxTDTdTy#hP(c`)2dL|DdOrs9|ak{z7E*~@G7_;2%I9>h^3!Q|(U|9%k# zO8~`z9RF=RhLxR!Z$FU=y(_y^_!@~#KvSX7r^Eb5J;3Q> z<`@Y%{(GxLP>%nuzm7=_pnG;3kJV&6H-G&1S@@p@G#>ZjxC)@^Vc29hhBfDK90L}ONpKtA@G zhKME(=`IsZ68_l;o(YP@M~%3Ft1-Y?@uioY;biBV74u-t0cyp2I97n>C`LLAuJGmKE50B}Z>Jrl^*N zV03uu2h4!^f_^@gMM)nW_hzJPo=%fNW-6Xhi6Tj(!UGhQ@I8jEt6 z!7cD7fS_yv8vf@VL|sR{vnJn`F2v&rC<{SeDP8WbP&Q)xx>%?1x2SNE5?nj`} z;aq6j!KiUBp0*g}+G@#hjnv`~cJ}<1wBC&zrUXcbwx1 zNMV^{n(d5Y(Bc`i4dOw_{8C>SCju>M=6l0?!SRLQEc~YfEoye)_(3kDs5$$4Mj$}C z%%!3f&*_V@E;Lc3TfEro9*u5!!SZVp8|fC$M8ktXyTyy})o$@y+*uCPvKPJd2C?1- za(>4D2cVWU`yn2b0<~-qjtf9CnPrR>R6M6P>l4A-UTM*@n3Rm=jH6>19*5WCh|U9A zC>f7-1-zV1@k-x|=}Rf?w!--ivH{+EwdI$bU4;^UqX5gdIyOcYFfJW~< zX9>%VMbe@-2mgnFMsFjIFXTcJzTsB%f#e^3GWDWFPdmA;DE`#PFJLfV?4DJ}*?j@B z=K!_ac)Ux(GrMQwKNG0kW%#JwTe!1PB&F6|@9mx>l4f^1{jJ6TR1 zoK(U6V#C@7rOR9`^+MzmRlqG)y1Q_rk-|<(UkHOI8!>kBOK(Ops>P}`Ka8Ob0)7Jw zoaIJ$YDQrOCH36YO?r;=(OH$p+=U*ZlK%`te(4h;t7G0(&8Z-aefy&(d=pypZKM|n zQV%h(d$1Ftk@zC7QoUH+R~kTEf>(b?y+QZ{7q{b>2(+AR?Sz702tuh^VV(K)5t4Fp zC7icKNXp6NFO0l^Vxeg{*_EqKK&LWH4Ij5Vg?-w$SAleC zk4#n(LsBwFc8XKS+rt{~(}t%)#-HT!XxD|v2U!&yCp8u9P}&gOQ4zN z4jdCfvCuTr?BMEWpp~nsp_wXHkx|M-jbxMqcd?ly(2Vk=3yxRKxoon?XOup0djXeG zUiAi+yc}he9I0qEEtY$2z3}rH z(;r7~kgmSxgVAE+cdyM{)_qUZh&8-L?2=;RZshL-(JIHJkAS(X$tyyaQn=}C(2EEx z0lLc3n6bcICgW#@Hp2Z9bdm2*%CmHqDED_hwFX++7db0f=;K^|rvgiq=5U(v z@dw`s0t|b7EmNwF7V|D!jggodHXJRX$TnREAhEwlr9OCr1Ao0xFP1yVI)&F8L)?JZ z8Ibw_J~i_keT^2>*olI*unn z!)zh;?nNl|cd_#C{hQHAp!}kL;=w;$Xg$8c8BW)N?|@Q4Sh}T@K%^eu1aCdiB3ah* z)dj^u(<1pSuFe2@5QwRv_m005$yyxWfd3fb=)u=6C`vpyz6hR&{~VwtqjbUkaXh|R z5R<~N$(8##lRkpC0;owx2W)Z(iuJ=}>4Gih>M@`unHmNUAI>s~1PQ7EH7V(42I?Mnzjb(11bY8&`VqhL9i5mU*lhi|8SsX*ONFF{GYHhfwQ^#|NrN6@7%d_$Ba8n z7+ZExvZW%bp#@1u8%f%=D@)qYBBDa62sKKj2&KMhQCf)>5p61oqNu5~Z~y1}{WTvw#v%-libd4;7Q@#d{c@HXs!xmpb~L9Q%x z8jq0yyhN9Hgg#Z0OC!{{but@}DX2%4WWf1|Jgvw)GItOSDm0~yyBwyPzhuoGF zt(DA+mG(%%i+|e}ajBI?AbPHZC8LXGEI-!;B|_6$>0xXS0PQGRFdT6eb*PnYmSCi# z_ywHLfp!$vJ59Kp*_rI{o39e`Sk=u1wyHZpDYkdVSNU}Qjk7BYp|JKzGuk|?h9p{JdU8`K@Hz8CrC&qG3p%R+F14>=-cv# zk9e~vW&NKTEX7un<=E$mvk!(|Vi5(VV3-V&r)0$`bx5>3C?!>2UazP`v6lB3D*O?q z7q>!Os=xJ!z5}%SGaiEscvAhf7M{H5)mS7f1p1;c!B=1OKDhJ(s(M`)OAu_v-@oWL z<3Ap#>aSpUQ7oc*oQ1c4uq3a|%DW2HUn3W2JZOj(QvInqy%0SDMAaZZs?JlmEC#C1 z0bZRl{6(G2Bdo$|R#H~9M2%h;x`XtCS!&2@n%{#*(9rLMAKK2Tm$uR5M-#_X6N*#t*!L<0RxgYQ|4|Xe;pBvu2!05L19= zLVeagYJv3Q$3-uLbZ*ikvuY_RfKkmDFqK9=)V-B_$kdE&an00>-}#7#;O=Y2D~V#H zEVIZ{Sc+k>SmY`Eh~Ybs+?@3kq3 zF4^tt(!hrhoT;H`&o6&vbTjivsKY^efFndVGwUJLw(?r@V}jS2>* z`blGuko~C1E-q}_SsWdk4Dxr}%N)GVC^NOxq*Gn42&=DXPf~g2K~DnfE{ih1Aafap zi$PH#J_|8C3i73i{=$V8c$1$Q;E>Kj2~Fm9-i7}T(Ak9TPKJ4AXYw;fc3|gcxVwRt znYyec)Ry%GDKkf4=nB{?y1_|jb^cNxaLeT6p z$N3u#DRDW|WIuiD9nMuM)WhPp;rs`J;X1X%kl=r9L5oeLR zf(Lqmpx{a8-fBB{zdLtZTMi12bWcpaT`|@9^3)3YI(PY|>f*>f75`v*R9b^(8=S(g zI1#!O1>ZY2Io$GbCa7kAIQR0%P3OqdaW{I_sqQ>yd5B2U*E@^Mdl#&6774RWhbR|h zhva(aF1aGhn-7GA4Os$)F@&rSXZOD@>!d~<)>dvO^V`bp%Q+u3k-HuiG-QGwjMQ@) zAGrehC=v$~$I)RuIFM7Bvp0Y@C8mN9LN^%7j`U zEDJH*4{FNVtI06YMIg?+4 zW?A;(1Cfzs+0pPvNdU4BZw`i8Ai3CyxKb_!F50SV9uy2m@w582RrfUlcm-%H^Q2=j zf_J7ImkQti{u=Ifz)ust%!`=GP9mjtrQ?!ulJFYLtQ5-ZiX?mjhUXe@A4&j{@Yc+Xv;fIBoUkQa8UQm98i(+g?9FZZ*&ipGM<|lM^U* zamlvVZXJ=4ZLc>p&k4o?-Rk%vhUb88S1>X9Clv!=t7DM_AX^>(f&YgDAX^8txT9^ZRKGOF{BW7hhI&8tArH^Wb2Jqqrnxw!QvD06zj+TiNz% zJdUqaOa!^mqZ3g}E;L{=psq{^$mZWuF`NuE7fg)fD-{DE7ivlXk_)ro-z@=1F1(Ln zJ>=?q+%k-h2st( zYRQG?ka!wsF8qSwN1(Z2VmXxqAQ$RM0Fny_Gu%EvhTM`1!!cY6G#4y@MCAaiEG&h) zScD}PzQgbhNEVzJ)smIUf_YFWJ4&Upus?(6{eau{TKWOy4JS6elVteF-zGp zXPdg2y?ixYc+77gA=_TJN&;ou>&b-HAL#6d@!0esJlpDdH~x13O~-l+?|@{}e@TaK zdo>RRVUFTwoY?kyL1!0pF{8&c(A+d8_v2KQF)jMT?IR;j$;~MkCWB;$|I)%d=nXoG zFQtFPo0OXt?-0lupjsG{&R|6u(<03Pvx*EdMT=e-x`Sl*f73#?y_!eNoBT6n+iSBf z1SH#DZzhllAlmk7LK*RFp@Am!I0B0+3FZB3OjAtgLj>LzA>C?viPM`MZ!hz}*|fSw zh_cl*WQ_U`#1iq*O`gqhX$p|Ni2>S;PL}b3ldW+mva;c|H>{pOH+hc7a05u*yk{oK zcl;tPra6yTFit{Z!)reYNwjzj!3Cg1d{m3~u&o15i-)}yOPuU1)8Y?UzW~+Z07i%H zKyu-KY4N<*qFh2^)9U-C#i(2=O0CfJ&c#PeIXB%ESFo0_dbm+d#hqVHZ zU+}#I{|jWGCxgcM80LvZ1_0YJYz67UQ=&S`EhnmG-}%tWon|Ka&2~Fp z5Hx%>OS22`KUX-ib>}x9R$n+?vw8TJ0o6=Kh98MVG~1i8L>-X+>*UIs#ZQXd!eE@l zNS=LXnw^QvV4#}iIUb+p$kuE&{xgIlnicx6HfL+L5&v~SH7lZ*UjS6Iz8H=L>0Ntj zR$%XzEN|4Jl9qnF?z736wqy(+uVTw-t3dM;zyRh;u!{T#*S{cgysjbc$1V zh?yfr^R+1XwPUL6Nq>XnHjy4wQCbdc<;rd; z^Wm-%C#~_yDe^-=^8AXD<RwJ z53r2b{8Yd){GS9m6_DF&T87A_E~1v)UL*IZ?1oe>L>d0)%5hu^k+lpV+K|63ny&T?UhpsC9_{A zx<6voQavC)jn_DO>?1(_ZBJ&xWQ(Q`U1JOq^J8_c=|-5dp;f>6t_(M)5?8*sdp8W8 zbk5QYF*hia$O-7_?gZ2VudptE4rI{LMg|_$vD}DZ49M@46-BaA6g94nrM^L(usgI3 z@Ye&rYIbKPx|A2PX^QeEgqPP`Tej(Ua3J4#@Op^w^|7k= zNS42eSGa;d7E|~nmfi%_Q@Y;^uuQ;k11J%}_V3~nXC9^TlIh&#R>GfQUab`nioeep zb0m@n3Lm-Wg~{Q zVmTX&^n@CK{Bh*#RX%NIvMt;-9t>-pcN8*rLmL7Aa_O#2!ZH)X-5`p;#I;{c_(=Ts zhe?h9We6{bgv9?RhF`@Z@gGD}-yY;1L|7)xs+&l2Lq#|c=gaZGM7jjMu`I+eUo53q zzQnKvB+qv7y07w45UA(fVpLGLoCh+=rH!KY23h+{dW~f5d-yb@o!?v9`ZZX3U^p7& zZ^@F0d1;&2tWhPba)ku&8?;N|Un~KXW4Rl{oghfbgTLL!+Lu^s6omE5L`t6g3y_`{ zVybS*=;T4b~UJ3~C-p46}TgX0kW4_}An=68u0|Xz9{vpyz+PA$ z!>~XCNOpBGZf74Ouco%H%rIITteb6;&zv5;-Ir1@yv6XG>^#c8zvf*4+o5KzDiK=t@9kxFAP2=90*})P|##mq`h1pRESDmE*0;aeSJ{F z^IX0r98sy0`zsbbC$;;T1|3z?z zf`km@b+BhbIhBQM0RIpI_luAW_L9q;W;SUStug0+v6LJ9oma-xBubt>q(hEH>F=R^ zN?4mf{#weY$;7Et~ zGV<;lldlU@l&K!pnlCa^5A=uM4+MFgN7INnYR2T!6V#|4?i==mauwv!A`s;D$NG+w z2)xO&xz@Hq(5o1Nyr zQQ##tb$Of3Eo1WOuN$uwwt|vR+nEQdO9f$krZ9azL=rVtR1KwmXa}{8(50R*65n&G zK$q_tOFeN4)RTbxQc}e1g`r6!k@7B_A^l7#R%78^3sO?7jK{*YWF=r;;iph(TVkssKX|C!?lO@IEbPT77MT*Id7_J9Fdb-m#yO@|8 zHyrr1M5!j@qhhiE;YWZL6Jv1-x+32dlXXzn3SEkck+_kf0^N$qZ%}^$@=HE)QdGOX zcbwL#Zk$pmtL|3J*osNobH3?Prw`SdEV@UVBWXqV1|{OFi|swI9Sw9OX@`fkYOKu7BxXtUaaWW!UVys}M)QDt8E7K;w$P7@Za-z2Yg)xD%I z(jVh93ds>5^(>7@jgzAgsI#CYk~wvf!dr#cy>MoO67eZ|7nj$uNxMrUC$cFoYbIM0 zmvDMoINu<#185M%G=0@TkZkOvk7s6qwliniRGCkpPcuX36?C<*rhI33UmuN`q6}w< z{d@EKB^LSaa7O|?NCGaD{kI1@8Qu~}{XcszI{}{T*}VYHV4!<&^JzE!#-e+6O@_$^ zWNgpwJSa1yd67N7M^Ynq1iA;r`1B~=g3q4a=EuX6;6P}YbQH^_{Rl<&gWLjt640XA#b=3`EK7XRd>rm#KrJQ9@I9PF5VS=L z*$<-2?jOK-4`?~;?L!>`$IoX~r+%p_b&Slk9gCrdSY$ry77PB)Qp*JNI$)6)>0lI*asg^%Wuq_0-N5+Dol@ZO!la-Rqk|#Uid<`^D z&i2}y$x5TolWM$-Y2fl?gwK>j+rW zt;AQiJN=1EInXCL)2D1I{(igFL^5uSWS%_9kcaV$SVa4qv5W-;XJtLfUvQ9KEt6dZ zqqE%8XyV+HuV%SBH_2Yv_B_{R1nuBjlF-8K`zuDe8i;m_;iKwz#-$Tb^_O_{hvDzl zAA^o+?$JoO>O2idCCtl438voLHDR=OjXvGq8OL{y2~?vy8@!c~c}o z`@^}?*`kS@haHV!6wq^-CKInLW9Kr<;NA<+EIXD=eA%(YL^FwH6%wykj3xKKV=0IG zt;V9~OHHr5$`YhSV<;i2nqZG~q$=|*9i?y&1N?N8jzv)=D3^ytwk|pu4TL65?xRp10=h@HFP_@;evQkQKug+q_Z)*@9R9xPZAuUgB^Y^8 z24ff~7Aa}>Vz>t+XH+QoQAukgoq(yna4aEEG*WH0AoVGTb_|;~jZdM^0#utjy*8V( zwHbu}DL}Qk3&ZVV5p6bNSO=05D`+G0)0UAodrkV+MH%@gYG_8*8Nf3F%|^Qs@i+MJ z8ppZEB4|C%Im&x2J&OWsf1j+*Sq!2TkKwqlaqo}At>4aQBA2fQLD$V&Ik6=P&eMcmB%O6oH@SUY^6Lr781(P6z!19@^k<29cU?+4*7EI(oR z9^~fVo)Mx)hfs|!iF#opoDW2@jYyYZxgNt9P$HC~*0{Wk?RlWH#TGB?2@=Gt#D4Yh z(%ItlsjM7>Yn=Pgs1gWAc!r*)7!7gPHo}H++T!g#g)f2&^K-dAx-592cWgjeC zF>Dsg-dGwAVjKhV*Dx%!;9}lnPpihnu<2Y0`a@`g;12{GM010T9ySej+}&1nCu_TM zw2t%G6keN0(`%fmKbP|$jS}-Fi!Y~pSf`ulxCYt{#C09WlhLKg@TqqffyvbPBULMu zQRbs?=Zk)UPPJ@uN>YfOsf82C=@F(zQg(Gr{4Nsj038#X40C{AlCg}5zrah!#J^(r zS$HxgHXc_L36C*xBjL%IxB&sz2RbG;9$(879%JI~Bz2g- zdt&GgbWCj0Om!nIW8(FaVi^-(4(AdPk}>h)7#52~#>5*jya$5xKTg~1F|oO^k;%w=Q6K1-*yx<3tPs47iF-lq3FKE`HO9BUC4$-HM^@QQ z#fS1>_1EkR_v$T)C{nD`&0UF<{h>knb;r=%obS1)_2Pm;J(_iby(&}LGMV#HwWCR& zp2oTyRfYlGVRNIS(bPfnC$R3pe+JN9B`Ywz0Ft&NGJB^`s^wtz5WCinJ3-bFS*@^9 z8L<_Pn#)O1*LCDt1H2ml)8aaO5;x+*8Bgcel?0IwOmmNmls-4)5{J~I_l3bS-vF0I zA$O{CG4jjss#){ujDO5CZC6dx(v~|Vs;01l!e%oQLD^|h=qv}Vrwn$hcbTNH13ud^ zdTccN`7#U_gXC&QcMIat#~by?viXF8nM?(=K8M`aS7I@NEC4zWZ^T$75&wGZ zg1ZCg>(TNYdR-v-q0=J!^(g#>Cu<&IFfMwSQ+PIEVn!l32f4FAZgVU~iz%&k)mSp@ zfb%W*PZG{Qk4Q%;D#P)J(~*V5!bihkDgKXxnx#B_w2)lrWJhjoO60V1(zga4YohF3QkzE5$%xD*OvZ3+T-Ozed%vbjk}>kOk-w zHo&zHjNTC|IOsKUD%ij>&IL>`y^duJJ|jS3S68F3z^85Et3rFbY!3n;vqg{Xe6(q!#vBca?}cnM`MEW_tUDA$AVZY)n?SON-8 za(o#=c7-b4WOp|Wb@|24S1RNI&XpPwpu9Kla70eUOY6}E;K#s} zLR(Okx>SBMS8?pZ1(-J@Xx6~FuXb)@M1YesvY5--e!=F~R4lk2xh(e zXEFt3j>Z_34KGgIg#6x^gAnK-LjGusQDc>bY@f`j2n-M*JsPv03)_yyxG991!yaaE ziD3vxc6N%|#~NL374AdY%#V?Xyf;szF5>kH55wmUL?=jC2V&X(LfTMJ zBCMibxIB$*F-ZM*QzjIag78oL8n)gknHNrALmI7M4WJhdSZwi#H`CpHa2WnwfX)$i z_s`EvR#N>O;aPCc0Q_{~w7(ZAm*{_U6Q{R9xe4gFeURt0eLrim@MrwL1v+jYLJ#C5 zpyT$f7(NB*dryzvU74>+&(E?*{Do_%$S?L`4atADe}?AF)matY<4Eg>3UDLDwHxPbwXc)ISy!@aJ?h)-BxF^*W2%r8wqzf z$kYi&WlUQ^$kz$?ATUFOe4Sv_SY;ur6JA7Mxd>^UFv*2op-w224{n(s9Ypf4-jn;6 zQLq#8y)FID)|Ujb1?YFSjLQWUlGo?N8t7+AnqJ0q6VT6;48|}J=x0hyfNrG-*mt&O z<3B?Lwq40U2E#f~A{6~h$zGS!i~(I=xZP)~nfzfGK2uUGZ=uYk9}E8&pzq1uPKHss zGttWG&%Y;^!MzmF<(Bn@c|MjhQIw;76D4Fh+BXB%bfBf}5g+necz#}e9sX|vEo}wE zQ3GgcI~GF^kRF>=+Gs8qC8K^nJOFP;LRqC!F_SUIof+v{%)= zZhyE&k+(%emnCXZ52^T5h%OcJ_@3grPp)tx&)XVQ>bpqxeUZHjpO29H0OU`h)-oEa z>@G5ww?XZXWA%oM%p7Rp2>MRQ1o^$PgqS9&DD+V*{jvyM2dxD{%|SQa2bMaFi2K$&t*+CFx2NlH>droD0`(lvkD#1QYI&lbk6OL;{bc) z9CgxC@g;Xem2yxp(2-;YGeLbVDP$%zInTLg_k~i6_-x0gr$nMG<)U9s?eOs8tI+&; zyr!Y!4WO_%i^rh)Ba;1V;}%y!+ZUe>qj-WKY$P-cYeD|253=vZg`Mk7d9L8ps~Tbz0slNalfQzM)Y=)s^HNbz?*=3yU`nT^6`OQr1GgI71C4+7ydESoT_1^IQ+#e`zs3m68-;Um{uR=0 zky?c1kTL8k2DyXI&j?WYB2f1*uHYJr^E~{^KsX%BRt%d!;sPus!fQMkQ$Yt8*(X%c z{dwe4o6=`Y9`9OqH}MsAwA@Ka6?VLnN(Mtj#R0JfE2N_!)z)!i>9c@bskV%l7xApB zx&{AlK5n67j5B${WW$O2C4gN|(ceq~GuvT2lHi zBFMyCyc(^1BoiguaqzEFRFK$!YoiZonN5^W(cvXaKwsg?FD1PN^W`>e3Ha35-gkFb;$hu4>I%XBPtZe)xBa`&R7 znlxP=qPXy5~b>V~T8g%-{f`*%EBN~T|8qpSqSK>7Q>WLtCFA{9LxbkXu z%`HjnhjZANKX*gA102yYi)hg_?t~-1gN~wU>PsBk2*2oPEFUBCE@*r_minjjXB^%| zLof`Sz{)PjmqS}0dlk)OM@{M-5LSCa)RhaY#=^fAXjge2hWo`L1G6_VtOn^XT}Zm? zB)#0(+A_v0c*9xb8i|brDZQb#LHcdyoWhxcC%xHVpr$9F>XRJn6sFFThE%$6DqHL#&L3g&BbY-Nl)}P2xZV+{6jf%aZ5Z&2nA|&0}BN6BZv^#qthV#WD z-PzkQOa(#i3KX(HsO!FDUvP&Lv@b9$k@e4PKwa`3!_F zU@5qngn{rWEbTC~0VN`nTZM*^8w6bXN+p;Z!s0|5ndxlIpFuEB644r19>MS+$bCjy z0H2v6Rd+yGEAbJ|;adLefb@k(t;BNBBr01_BD|ulxQxO!3>4IMVaTmCx1+jLpPaZ% zA9{MEk@S1@mv)Cdxlmpr)E7Z@slbfIx@SfH_^P4QW80yADRimN zjKq>>1v+(GD0N$UG7T}PE_Ixd*g0E)F6&)V-yHnMR*`c8v& zD$vqroS0|35VZ8IkP<7U??(97OXxb7G9d;vE($M1=}XhK($cp8fk%LrzI7PZibYD_ zZy0_7LGl3?fE!HNSTd-->A_5@lRnjc66Cj<`ccb9w{T(zB;=!(`Y}t(pFA&Pc_QDX zlwPHN%(4eUM}z2Nmi1kx@|ro54GsLqEH8z8vBczOL5v!!EM&7F(-F8`gmf09nG3tZ zkUPIa8AqSI24=D>Wf0W0lwiYo;-|Fo>MP8nMEzRjbs3wMSTs!bD%_MI*Zz8 z3QZNzSyT&#nJ4^Z7PYwq6H=JphqE52?#3g_g=e#Z0=2;-{*=rDz2w6lN7z!z21}P#rcN)powXjIQ>VtGa{|v#oqmM>I-pahrCZ5V znL6!p2fNgOYX6##LHzyH=?eUx2de$=7=9Luw9to4XE+LyrEXnE6n6Evu?5v}!y{3Y3VC?x&uI?S6pl&{_0@Tt?7nJK2pQI|{EiV67Bc@hKXM z%WiBxM6v@P;J~R{SSMeb1DXeYoD>_2@%MW* zPsG0;&^(xeVX{~x58lDB2Bb&s`OxxZ&*%Ub8VfScw2$NqznqK1>IpjL(N&grArFqY zHcQ5sx}UTYz6S%<@jS2NkZc_X<39+fjyGVqPAsD10t}CUNB2jMYv#ub8owq&%-(Q;YC5SFP(cI z=O!mg`BDc_HFFt!miGw?+B!E;?mFilDaW#9Gb^TdW=VYHijsUHeyMXGP8@n_wctzV zD=V(+BHvWJt=~0M!)4!Bkym`$sxd)9edj3mnf>EK44T=~tihOxR7dT7v(MZnoBgs~ z(~I0Jp>DQW>MVM7+H=mLJ8$abRcEW(qOjZEEaMPBUo;D6Kf;qOopd5sDs>Elo3LC1 zM(Xuull68p`5tn&CJ*pt+>(~hmh3fylfZDgZM_V)JMsrE4uW1Y>Ar$)RVU*%UEOZE zhqFkaYx!J0Rm(tc@Q*!N4V3FbcfO*pS`d0u^~fQ}8B_OssCskg-?Nyj0M+H@P!oxJ zlZn_ZqD^MgnuCI$TsqS4@eENjX>RKRW8m&gmVlfk zO-_^1tlXB`Y7YBefTr1q4A?7(SepAm?E?xrIfA75Xach|cgu3mO0$KhY3}Ygn zMbmuV+0JGwInPRx3V4j^^PP`|a7ngMVem3n@f;PknrzG?+j1T|Y>5n-YvD5nbw`1) zDwZu6J_5O;7ly$ZPKKdBGO40in{W!weeWfIL3k6E`55j8sUcYc%yqE!GVy)bq=$^_ z41>fX zNiD-L8>AoHBdL-o`GCV_pLOMgm-A4Y$0Skm{e~?FZ3cVta)O34g;#%r@sjiC-hk-H zn1z=N`YNx@2aI*~$XI(g`vN@zR)%3Z&?95t`!r2O#*U1&eGooS{sauy0zDG-ljpAx zK1ap|$h3nTi3%Q~Ruv&R5><-j5KtmCJu)^0+hm|+*n(l_w+qmhVU9P_zvsGa4}x;E@JGgqALfw&eW+C&Ckz_ilU06uLPU5Vipkgm}qyZrLn$N|sVKFYoXCr8Gvl!+0^pDOd|=>W~2UKqNA5}|4SOvW|= zX#Q9*nE&823k)VEhi|G9JU_) zx%dwO$wC*5>(R%jEs@%|wY9y{2Z;9KX%|)cCB)yBF~OiCqbv-Aar6OmCf6yth4>G{ z=V`)y3gpVBTBETe3RJV1BReun=i!l^ec!(m+=j^8<(e2gj&=(SN64$~6CvE8z5 z@ZTTkZrKwt^ashCoNg5wn3`Xpo%zMRDauFsUU$_Uc?&^(59?ZjxfYv$QQV3En?pph_+2U>lF)T^VD8!_=50cjCUBELx8rN zMx$2*&9|JR@gD)SKwwzDkvPdMQ<$T2}HB}_7<=lz?cA(|F`C^(;pym8r3}=G$ zofXQtHk=Q}wZ@!o>7MtO49+IY#J;T#RG z8nY(aaQ+FW9B4y$+Y&l*KpW1l{cAh~8EZIy$GaRTf8^tAFap{zeCzpBg-^qI`xJQ6 zFsy>}GSG&h))QRW3QB~g4a14p`T=biESNAbQ+C79K!TKp;a2#Qfi?_3`s^~36iNB{ z&fgPomjKr={Nt5fffl}D_z=bhpwE=UL>dMc1;Ftgzv87lHK5P5H-?^Kk-F_qo)L>E{1=AbK)UUo3TwmJ z+l9unai%6(T(TO@!=KIx4h7n9R%EGu=iz$}(1vrc*UwB=vw2-_fjbGPu8(6_EEduA zLk#bO^!YB7tcFvDmDl;eOG$jrtd_~?NXoFXa2W=H3@hhjxEJWKa+Z^!u|viWE89N9 z{wJWr%4HZ90UcJF09#xohZ$DhcRM^8R(5=rF%^i0l}5zQl7kR+Sa|`2!9a(VKV#Sl zbXaKu%*u&?4J*ezN7W2;SXuBqUvL2>LeXL6ZP+FOeRIlu4w%WmISebWyn|5W&3PUE zt3cnJ2Yn8hsnVPC4cr~T4J()WSV~1vhLzt+$TF;~zMNYcfELDOKICEW{M&OZ{yl&` z;F~a9FBW;guVZ)_q#Nu}7?YM5YxUi^;-IDsD_?N2#J#DxE2KHIT9n*L(9(6vdjX|D zo=k?C2xr5bu!;C?R9m>Mg)hTRV`U_b?;Frl;GP7II3kO0ENx83;X6h`l*V*}&q6cV z*&g4pJ_z>#pxLw#ksOA4DU0d^8JB#pPt6r7M*g#fqf$NAARb8qjQe2g4e%NVcV4qSglK z@vdd`*{0>l(#wGcmvZaVlfXADf&B=e572UCM2=fk5b=q<7VZ@w`Ci5Bly5)=1=jnO z!|b0EiB8QizieH=Kb)I&)q?`dvDuq>n0H4KdYne4VuOMYT(;Ee!55peyid@5rTv)h zj)%t!H6EUlb8XPBPCOitGnW5BGpj!@bz+$~H#y~H9(scFm1CXzy&4rYaZjF{Wszh+ z2-d}R*JsfOk+tte)DxqB%q`0oQFU(lxo+IWLLR*dYJT|;r*9pXI@XzzPAcc}j#Uv0 zcf;!VGSw{zs!K>eI%eQaHZ7TR`N8^G)idD^2G!rC^KA6EH%av-Kl?=L9H_G-_#h~* zyLTAKms!IJ{HY@I;i5CM<)1fTDHgZ2^8-@UcV^aX`CG`o0@}`6N}&CZjCJDZ`71fW z;oz9(le{;lwCGfi?_5nQZFU=0`F%%Fl6CgM6q!lC@hB(Q%~(l-I8(oIo7wihUz zJ@=lhr}rXhdJZ&tk$V=6OD|_XQr@w8xDnSs@%|Mg-ola^^*GZ@E=)F5!AqPP>$^iV zxDl^IUuB>Rg1m#U^>D=kX&K>Ih08VOcbFH zu;>9Kiz!}NNH!zLD*R#u9upy1g-`Z%s@dwhlzI6TKTL)$CX0fX`J)*sD=1a^?a#-v z=0NBo`FC}rSg?)%*q-yA0wANSafj(r`;HJz(&@gGCp zE)BUh&+TxSZJpH95s@-FXO0WG=WE<`19a2?y>4Z=bEIh@IP{$`8#%ddWjHdI0=?iwJoUPjrMN5xs>>v=%aCkccHzGRs4n}g zVv8$KUCzUB7D#W{Qx`oNHPx#!HB#kZ$zfROSn%U6HNsIryIeRNeb9%o2-a-Dem+2I<%KR5vI%&N;|OpH4(g z3Q~M{Isfzsr#;;Z%j3btk*`$ZGsFKmv0=w1f(cGc%G%LyXL(3iS^$f$r|d6)7Odfq zFZ=rs3v!ybIKBg4AllWBXV$z+tuH8*_lHs7+VyVRpnok%L`b3$50OR#QC2kQT0jbMLuVr=CkjGCMV7>Uc+uZpeN3a2Kf&jft@&i4F5-j zBYSa-!~BSF?8Nyl{C5D|d(-kw-bavR`>DQHd!9I#D-6shW3qP}?Hb)jK=N_f^9ke} zpl7#DB<6)pM759)5Iq9-L7->1w_*4kB#WIE*=M(F&S5-a9vM>uR}JLsv)4^!gtH7* z%eSbvLAV&pTnsaTZUQs;m=DQ`2Z_G1qwbPa$gro%+YF6BkUwi`CMsq?oL;_Xi%=7Z z^$BPPB5(i*AHi}ahJip&(wi79UBv+85a=U3BrjZsWgUX6LAtS{%P4`CL%wX-(m4g* zlyXT(93M;RSg6Z8%#Hya3$=E9t{8A8lBGXC%W?+X(*Zwy{m-U#kA6caFgY#5S0st@ z`ricWMvxrlC~jv&eE39)O~L1`(2nOSsV&Gvl{&8?Z_x=sGYj{Z$SXdad@302LXsCT z9aZ}Bh9<{3cWcvSgGh@Dc@4wPu=XD|LOw^fudF|FO1%&6Vl4eU(%bq;;q29r+xGpA zWYlhXGOg4Sv|k9SAHvj|(YYw2f?!VFy(fjIK?x>>{rDGN-h;pS@veJEC&ce-d_MN0 zk8xuDW{EQ5@1NI#y{E*xVq>I@b-KgZx8Co182#vL?Lv(mppi^H8<&@~K1=G;>*&n$ zzH1q?1D$y`3ajH#{LJ&&_zwXoxwFAIBw9GU>sJt^may4y?g2Xc{3?bOAV{urO1gn) zbXi0HHY7oo9&el-C1HH@kBY3rJePz`rCiKU5Wj=A=iR_|tDE<0`e6`sewUUL)RZs8 zntaTgTtGp5aw_ov+QE@>y7{$ncMoOR#mr=hO#p^lSKZ%aIGp>|wHryFRfLou8Ul577x)L$vH z(XU{RV+`rXTq%JOcUT&vpUrYE*zGKG&P`TDO+PNan~{A}*SPasumvH7RDN;-u7Q^b zk}2n&{XX>nY#$DMKx%4nMzTDnBOlCqO!t$ZdqLZ?*t!zGp@lrAPq2RsG;=yTdD?2p zobfqt6R2i+&UzjT(9Ag=!?7UQ+wm)8`KnCjv@?xJ#X7osN%9tR?_W z`z9zK08RV8?~%0gq&@CE(%!ix zcN0SzwlzvLm8ky0*~iV#Dhp#t!*I}c{+Fb7ShF`@p=-Z^bb%3PTz(#I?WHPx82c=6 z-V_(!zAn6d3GJ|b*C9Nj@I?w`^{QMBVkADjRe>HKaZq?Di~}~ZV+w@runfm=p;($> z`3S>0kf?{H=17LY76$h=yU=o6e^T;K5TSF9|-rNIGj13vmBFXy=K4mYE}`rLsSR4=`B9QZF(yk+qH<*$`c6aq4xjMp`tOpk#RJSoy-NbeD2i$4~bQ6~mp6n z@tKVhQ$dM%G+u*C@wm7&wIA9J z%etmRza8k2Wg{jkiP&Xb&p~}g=yGJ)=(*lxG1!siEl@WDetKD#N#q3$Z$)o+Sy$>a zvJJ>jFY6kD=eYmTLdj)a?eS?1^s=sJTvC{OhQnoDPc0@Pa#`2qP=T^s=rKTrJBe>o!8Uxv;K;BA0c&4`n^j%eq>A&in=_7~uGFS=YDZoK)`7 zBrwfb!7%45TgP(=NPKZme(BupvaV}MUcoyq0diT_;So6%uW(t{Hph|6x=wZh%4J;z(z0v~#|m;AH29=r@+O=6mNZ-BD_Nxsk_W$e8?L zw8v}Rw}gC=8+q;_Pi6qUk>_`(R1lcSW^(&Z4EZP?A7GrXG^9BP3x#P+?(GZ|O2W+9}6UDu1{>X$>jOx~0n)anJR+vZV7{ zkCQ8BJ zZs~dr!z&^z^}LDXdXtaYZt2e7RTq~{mb+zKy`{_iIG-Xf%oC~V zOA(jzWk)005$O4{Uoh+fCBoA4WtVK_5Ean#Wflq-pWrX&%Wi)HNjYEkHk>zso-Z?A zJmT5;vY+wa4Wb<-PFu!a&SVoMelDo)HcpZPetOodkr!z=h3M_9+0jrA13H_#ujdSb z;}^vj;r|fO+1#36vWgCLHupvhV?cU?laz_@^tLRE#IJKvoqgD4giSGfLo!k3qP7yi zCZJQ%Mq_q{F{q!4KH)0{tU#xthv1`A(XZq3JW%Bi_YsJ{pT+O8oks*z`B4}y7mG|q z{}00xAh|3{U#TA4v)4MIyYq_&X0igsEnQ2WBq*s9ekGWnfz}DVoCt?YoT*ZsP=5zU zy+Nif5U1%aQjCt^0BOyQpEm|)?|b3A1Q^^1!!rz%!eFI%X(F=!haOd()J{VC1R1% zwi`n^NI&Kzqcx+XC8ok7(ObG~JjBq2NO))TeK}+&=fFX7qie!!?si%k4O^fWZCR^F z!bNgfqEvd=ax=kXByR?KHr!~jz)S?7Ke?G;lcanO_0tpZmw@POxKa5mm{Y(DaW;HE ziRe0nK1ASspl8E_U3^AHEa&0V97|IWB(HM;xU=E0QFhVRBp>qBNdIdjSlKapEP}^? zmCBS3+N?XMx)Y}INc>(>+oI+^pdXMG5jnRy`<|~CT>89(Djn8GqDW@ z>4%(d?y`b{?k`8#EB%^ti5&H$Uo*ft%k5l~Bj>1BmUTN<@=`~VSvpCUbi(p~<`DU0 zKcqC1Rcj1f&->{a)Xbsxc@GNB0M%vf+?W}Y$<+I#rmmyE2z5Eob@Y5$M>m;VZ?YFq z*3=J`cw|j|D?(d9w5C4AD`zIFKt)K`)KlN_6hUT9-Kep^OkT*=)LSFaN`(BHx>4hm zg=|f|KLY(kNY~WwcB)x7K?;ui@@3ZG5$73rlO;Bm4x8-zEQytAgVBUG3IsLf1Ce{V zM-+II?I(%-l+w+3@_oqr;g@-t+q{gKyi9rOc8N!1*28~SWc2e3%e_p-6zLwT9;UuR zDD(LRWAITyFBNe!pI^`qMeOk28B-?EF3%CD|9-JCSW@v)F3`;PizP?C_1i& z=Ma!4DW{w^1q7)SYXtu|B9)vYUi&=6Bk}44=>QOR#4-`X^Y z92bFne)CAei(XHe9HjeEu#fXSh=I~j=W8FCWsj7Our6|(>=BlJI>Cg)z)ba=CX$(( zUqE^JoZuD|*$nh6mBx%!l9A6S949jJX~Ey;nepsd|u%=k&(|U^n>3AM4wmK&r!L;*O{Vdh{!&#FbeVr5s1E0d7$T+ zsj3t4pIVp$dnV9NEo{W_9?(xMm|S{M)Y1|UDJO-v+FNkod+fiGs) zCdSRJ#LcQrf5%z^6W zX$|d^upJwx<gE--z?vcv_zJCn4{_O=Ku>&^WiV&BMCNDbd~WiF1 z*UC2IekjC7yLsVQxPRlC_nO;MCq6;{Q*hn~ry>`G^~b~62jH3=ME~7TkH@j?=TS5n z>V-y?T`J*;>tiE=c$b#_55aOm(ElyuCcy|QMRAwOIW3E)?Gpy`g!(jpbmO0dn6>Fu zB<{X}o2dC;v?J2P)=0zI)n2Cvi2OVr?QGDjw&TbqR9F2R-PpOwWHW9o;mW303HgC5 zf}jIoY~CJ)+`{MQr519P-w^@@|2WPmZ*a*Ez7~3(3te_t`Ou3mB4O9U3g+;~CyX79 z@qMBljjytmGrmtp$uo8|N^}ER?U&uHWq-(yMo~7D5UwHe3qW`imi~WmU<8Cyu~hw& z`#eG6Ku(SDOqQypTx@j`h^R-!Xr&7z9GuP<;=EyFjjNtTqAe@Dc$V z_-(>}qX@{ruhrk&bp~`@&;*#55&;|ZU4s8mpmW48V0acJm%AiX82lB?aPC9;B!U{z zlh?N~dk1`(&U7r2L8TVva7GE-42BbfS zCsHP4{X}=y#T(l0x=uLDmXIR%n%SgrIJ*T!O?TJD>*(&fze(@!p!$dGjx&19n@liX zkKJ{DLSc8E9Dxll|BAo)@os*1UEUmK$?LfI{8I1?w44{_q`B0Ph)aPM+=CA!q~gF4aMVM0u*5$wrdRsh zio?yI**i}7afu-6G!%T~+&XQE2XB~rQIPz~xi4l%vb=B6Zsf$MAn%_#==oa_M}(k< z9ad-Q-%z&W#Ey(LdXwtEsqDkS(pqx8o$R%Vb&sWFvrW4my|MI;v)hZ2gr-ue=NB)9`fcjQEoJh{*fEYan;UfYZ zC*6y_jufG=IFnN>i2W4S7_x>m%2D@{knI^jkGdO^inoG{Kk9xc+|GcZzof68kEJjA z#C~CjhDM=iei^K@K(qZEXEt%VSEJxy=O!0tJr}vhhp)n0-qoPl;m-S=jL6KezjKsg zP#$?C$gC#AkP6!ct>?{+Qt09ck_(+fc7ys}Y;N^17&nG@WVU=Bx8t$Dq4&mc-_ts9 zO<|1FiJ+q_yTslPOD{c$Ivs~|#PN+~IDQbeDW-7W8t1?)=-A`|#6OH26FJO<;CR~T zA7OqRR_ibIGPA*5*V3L{`7WRK8d(#cPr{lXSD~kJ|4Upwco>C42HlB zrj(B}dKxHlWvuFbk|@~-z8UFHL9~0^gsS|j(0eA7%;P?C5ai2NFcT{DrtFwxmw70A z!CE1(ZzZAhzX}~@Lj4fvBSNXS-(f|}B9wS0><~`F`D6Uw7lDabic`#a0sTgy3DAEP0a+iFjr?8k?+A2X#|R9UgX9hu zy4%;`*2q$~qs0$i+oJJkOSzr*4%*JcYYw6_L9TrC*Lbw%@DfjO%0v#ue2oA5Ko7+X z!$%Lr)Xt|Z1=_(k$yiV(lGedDj&|_-L+%5#gMSBxDInS7@Tize2VdS#x3rgPK>4lv zqCLO0qW9IY!UScsQTY!UFV3IhRrSEe;orD^NQmnt)P_#dzJRT}AyQdiz-A9OsXx_6 zP;^t2F-OWvAQ{uN3R~1b@*Kz6NKT+&4p*Jz(%Jve<>@8N|DA=9j>JBg5haEXnmH*M z(q5(Bx+ZXxbCwa@!pPay1pa-*nlu$<94YxD3j{&x0$#9ReQ_#9`Ph~)QJ0D&9F13R z6z>Ugo0E&i<6Ekd$kSp?!VWl3!~a%LBCd@~ap9LM0bl5pWa7jHtbwk9ywUop4g58^6T1n0{7{xce@@p#2)k!nMrBTo68uSScOlptr%Mg_8}HS-|X^Ow|}O#tt1(Ty5b)!8NhfK>Z8GCWpI$C8;p z$7>CK4_;5adK4ytqd{1T4SE-x=^`XG=<^t!1^WC< zh})1v$ZF87_-_#bd5-y2>EwXq9H)Nv&bYh>@Bn3Vf-X?%Ify)Ks#l3{rfn2y?qsxU~ zk=PA17djM?kwA070$}&0)5-@xE_9RtBo{`&9|klRmS9*2G#5-PQ8@t1g>tw%MOey0 zt7?g$1xPOcuUs$>R^A+?QZ8IT07C)01^=YzFQMdJv5713|H2ZVHgRnGyj5bDI<|eW z8fJ`5-Ml&xyz0UWgNEZMtAE7n2k(=>yYZS%Xj6ffY2)#QT;V+wKS+B8@8Q1|XgZ46 zTU`K>>;5Gj`4XFXa0Jd#eAfOEZ+Mf%+?PO(1)7`2j8&E~EvCY~1*jHpVps)|TmDN6 z^I)y3vKCDWK5*$TC`JRpFVxP);r0Nk#Y7A@g5(eXrp0CCg?Yrh$v@LS z<0V~1i?PsNCy-Y`ke@A-5ziJHWkTN~u(Og--oM7Qz=Ue<#j-O9@;eZ3P2bR)$(G6z zf5mI<5}|I;4nv>|DC&jfEDUEz2&tQ>otnB3Xho&SAa(GENX@}(BBYx@I33Gl7#4tJ zD<_cMw53FsgM3I!B&5INb%#qx68{DS*Mk!AQ6>Jw_B(J|9PVW|JK5!?MWY(*G6t%} z(HM>d$?g@jU`i%XE&BP8impS8f8zCiGc7Jb@FGwmKB~o?*zN#Mi-BIY)X5(4k;{u` zVJ!oy#b+2k0m(BfXdykY)MZri7da35NfJ_!llnwL8jDw|CgT#I8~cn$UrBhc#0zz| zQg{515RP2wa+wcnGaSFM?>hXi1=@jr4#U%8ks0zoF#H12w;rD9K$~V)`_PK6k2JgD zW2f2SwHT%Y)oh&OvC&v~rrEXlUnv~XY`hO^7#y$J)A%n1s@eA#c8NtaYgdv8_6O-} z|3$MYKD08YSv9Ig&9jlnTn1FL>0Yz7*_tiJe}QmBvzb0Djt6Bl+kyW!pqe$QO~C@H z*;yD)2kDFV)U3eXt4Z{0I-!qtg``WLGp&3~E_3%$ufJK|3UZiVpNQjJtFKVU;2d*B zCTLyXxe}IC`jnYP)b{&0SGqpvEM?{?N&gDG9znAQK(d|VxDiKu+I*8XAIADl`bU%g z8_8cqx^qQo*>jdFahpc0Ryt{ow|^Z53?ON``FQot$JARiTUA_!u|rOV1mA>pf0CZv z$9O%2=>0(N{crAgVbG9Hbt0Kl=XT-Az5iR`Yyl6m zk%z2OCXZMl@p9EL?onX~f{Sa(1oW18m7Wk|gZQIUpFIxVW069pjvk~ZF_!ao?yeU#nvBmH}rtyVdrrY24+AJO;`_ zxkEqpKzQ&Q9rq0$hULq`PKR%yXr}Js6WvWn5y-oh!K2CK zd6UW1JA$u8%!ASw?r|WTh2;tiBSF6O=#F&}2HuqKB9u?qeEc~P%BcH3_;Vxx>8Zbp z;bo9M$q7qWCAzoaR3Ds~d_`j4vDt~_*C5)1?D*8G&J^umU?^;RkgGK%CLq6jIf-(% zW5&J7wpzxPvy)Ix9LTowgOKR}bldq*ua}u@tnQDZoC2jTg9tnzLb~mImuqU4E8^p-TEOADgUJ{d9Yp%CzasihV3A|%SpOwxxn<0WuITrKpL&6@tf8|wj)j}4O(wp5hW<{V>)gKXK~k3 z4d@}{eEE9DSB}Exi=D|9*kfzMy7S@cDdg7s@Hq*fr;v>quPoD{UU=QBw9rudfr4@u zs7yCaBQR}RIfeXNmOF0l6mr@^)`_UU9Y^P$4t^=nUo`h5cjoNpGK9`Jm0OG&@31e? zR?ba6MAY+uj-HLjG!i_2R&ovgZvd@ZzQ^z#NVau>X3q$vCX*ubhy`U5+hbwzTN2w$ zyh>Uyb^}_bj7ML&@Z2jXS3>u)0Z zI!He1ByD!xZBz1i~Hf00o7s^hSx!|#=mJXU^V&W zJlHPjOp&_OvXjHT9uqCjgZ3SP>;gf4wh&jA|BKKT6RNo%uO8srmsv3-y^!fnOf`mn z?b3TF0-b=S*MiThB;(V2HQXzJruS(KOF^>Pf2G$vC@ue@`W(?h()%NUd=H|Tg>IfY ztf6L1jf1~|C^HN7T2aLS9VK`7yfS!x{C*Pt#|uX~r^h-DRXrR(exHi}EkN6t)fiqE zi?lKM`y&a`uQ^HA#^{{K@jkSmk!rU8yQbN3$Q&cG^6{9Hy=HCUdCex`f1_|jvq3(r zA#l8AFXR6LP|g0t@T*uvvo5V!_yp%&(3HP5Zu}ar< z7yNG|0O`6Gw_%MJBuBg0vtHFB#t?(KH*>lajm5De5(fcY={81&Yl%%98#ek;qLr2I z!SDxxNa%VOT@aWFAN?hc3!Ai%#EQ@j2wbN^atYcbFJz`-6Y3Kd-X=nFsBb<34*~5^ z-{EuBOxBu8~NBv?-$<_^ABP@c~SgQZ_YA9L(7xE&sa_DhFE1j2919WS6qm zZCT(4TFQ)*8 zAqWBrQltt<5wW14*uVlJf?z|Mq9|a)ilAUYR7AiI2-pQd5gQ`*f&~#3R221xVt+s9 z?3}qbiT~$$@AG_}E&FNPdbxG(0dNPdN0+#-|i&GtiOBlww}p zEAeDQpHgaPi?!h}pzup~l&G15{9Z0BHm~NCTkeedm|RUK(XOzc-V>F&n@fe z{LJj;Fg;I7DHFgB=pP7zJekYB;gw9XK_xafEG=gmqVw95Q5XcYKNvISwN1y*ai_w) z3}~qrBP;}|%~_@9{OPWA`LkW*BVrKcc#_a2NbPp#IeUimTZkV^F!j@@r(7J5{)h9r z;hH)>Jm;)&L2P8W@BJ7m-s5nug8AXtD7Mc`VlQaN-2$}Pei$>Wb0bSdo4L<&9?y#c zX)!R^0m#24N#}-*u9i1_#IP{qK!z!xhK2PA>wu2Vx42UAN{N&FMS@|W9T{XUlwBzO z08)24svDKX9vImbECClyN6EG;BxD%W;cvc64Fi+xoXhYRUHEidsntS9WiLXh)p=o) zQ{;sssny=?xzPiyRZoP2f!68)rxXMxW!I{PWFYsPv*C}D45X9JM3@0m%bc>+PFDK5 ztAQo3Vy7tni@(pH^b|Zi6IUx#`C3Jh@Z@V1x5AkT^lKIKN!71Gd_&CVKnM1Bd^7AK-G2e1Z$}=h z03Fz;AzUf}8Q9k%tOA9J&!wpNeGgy&q585M{nX`_6O13(t_3jN}6;50c#?pdU!tiLe8d3Qa$dvhP8OpfO0x9o@2t1!b)% zFJD_bglsb1x2`{oN!fRA*M0T&+uSkPK2mZT6&57qGYz6!`s4MkVDbIp1VUhIl6Y{xq@YAE^^-d3Q7qM>bRic7XijouY4%ZiJp zT~vi9tO`$Xs3jn(0;%Ov1ugpNzUVWFm!c=+qcU3L7Co@P11<7sgb^V1O4el|MHoi` zxHHO)ezD6e@RALEbgw37@1aHIroV)29s)YOGM$XJ>G%o2cDUO>LfmNdc4|toDhKkD zfbyP+paF=S!K|;?xT?-z(W(E~x4;@{Hws$)cdi4m5%ylsA4~Ti{E)Ugg`7_U`oWK_ zK8MO&-xcP-y#;8yK7;U-1f(l$Mc4vTzqwqbD@eP#BFaY#2V{Du6mddW*ic?GkxR9D zFK#py?%h1EFhx4lR}qb|ZN}#RYSZ)YRV@Pj!b01C#!*q2+AHWN49` z7+Unge~BZ#2()L7_qk1lIXM$%VTgZK^}tZyF$f_3fKWh__O)rUkWKZxppjwbqvc(SyuFooe+l0 zYVk%y)0>&o^$yD?#Rwew9Dzf^TH7`SLFYDoY7ja)ERjb~vWw;@@_m3Ti%ys4Ub35{ ze?{VHYH}^;8*3Nk8$H#kA59ORJCrAmAT3kp0Ka_s;p|F2F(`-sMN5kr_`tr{Z9!Tl z_QqrC0Z*pL?S&`vrgPwo20Cw=jW847QtWbe7r4xm^W?o4i|2Og7`%F-zgWYt?cb8C zyeqSTOkM+d^5sBdX0)A35E~ZTXSwLe*8_io|Dzl{uNMY@)-L-XOL7Mq7Hjd1C=GzN z*I|i3-ay29?G&cCw__&mf}XycRcsfRf)QL=sb$!5=|8ByNm36~yMk~%fvE_Wfl{Fr zeL~D4Lid5Z8Mr!IHgPZ6L^m-lY&7X_vYZF)HTbW9AiaYe4)xWTFD-vX*!T`Pc5n~= z=a6=R@HcpM4#!vsN`+QbKyr6NU4ULvNBe9xNj7XSR9&)>OKKvV@jx%BfsV&1AI4qY zE~z_7zXMPxxuj0>xwVERn+jbaxyfwi1z4*kH!)eA;d2`*xz%TjBDtCUMfx6~Lr#Yy z*c1@xkTVfsEGV4oG-b%)+*t7|+%8hqHWgxZF@2X)XDY&hV+Vxk52b)&(RzyPp8$ax zC?4=7G-lLWEWVPkp2DlId4OXT~TQelh|Wh6zDJ6$S2>GRnogo=g;jglXvwe_S>vF45v1pcPNnucS1~qFP?lUc*@#NOi9@XYRN7q*o)`C)@=@8qz zA65jQ{lT)q)qr%l^;9Ptz4d56uym$Hq)R`@p2@!B$`=GvMcanaGst}i&~yKxZ;@q^ zgPr@kNWTNxYc z94fOp?i!!h8uDVZr-NeDQ8vfzK>ZE`x;d`VunR)7&2dj9`DCD*<6eO=;pXaiz_W*-xaAjfXO@1mtc*P6LIXjB11?&8R$!}cOl#cQkz}zs&0;J zDHs|Zr^vN5rF$q&MhWhd-;&AKKsU!VB4)-)Dq7MOv%;X+F}y7U^ohK&;#K*!IqnF! z!+<`KUx08oNPXwBcOD4Q#}RaMTub4RU!@{Y>H~7fNbc@i$mA2Cvv(uLs;GElSc7Ak zcLBZ24@Nivr2ceA$hye~x;d^Ta8Ha*=AeY=-p&hl=*ed$ zxWbaPzO$PLy=-Q4E5Q(;^dv7*g!gAqJ0DPR^Xo8vx6$|9hf ztS^7EgI@><+K3>v#sc@{j%#bp1dq_js3~Y>wO8#m{ze`q)|v5`CoY8&^zu;{9;Mktg1# zxN^uA055vgAa#L@ukc!!?uTgy;@;y9X`V^-9;@?Wisli=q_=6BO+X!!`Z^*rcPH5j z**hlv26s2eI3^ht->*ta-Z81(30z|!np zTuTcBlcGf3BlkyYnzcu+G2)(E;K*#+_){*eB75YH;JCwq?vZOMY$N3gVJd!)-0R_9 z3v`d%We7_|S@y^^6<&F)tZaMaegOA9QIe1O$thdrF-Pw zLud}rJ#sA<=Aop^9=T7X(F~`vQ|}-ySD@Bpgeek`N#=_P&x2H$Ro-Zf(~NXXxWnZd z4@|O_W{=zts*#n935in~9f6JscR3YyEpbwnF`)(AW*{>r7?toiIR6# zG-|A>l8p)DQ5Y*qIwmY~c~=?}bdOw1V>K~~6ST7BXl_NBJ#z0PliPsqk!#$Ta{V5; zACdkZ&^>aCPorM|-6Pi&;*o+ZoPU_aHanyr1$2+x>k+1dQaQfvk^3s4mw@h(`>1bL zlgt7pOW05I$wij1;Se?qKu52WD1gF2=m&)Izt&wG^%eQG6Epn@!&L9YMi`+H{2LKF1 zE`u*9m>SngDZhLux`ofVXtb999?4d=$UPCo<3Z{`N3kt(Cv%bMKw}9QWFpQP zneC!wr`*S*Xmws?Z?k3WMy9{iLylydpG_uOU9o$+=`PlEC)wb}Dz@9>MU+-Xx3ehM za1hOS{1mybld6nv_rbjvWJWimGUKVFc|6<%w-?gfE{--hCLQ<)S2ZzAI3e z2Gq{?0K#I>R!n^1@u~A%9@(a%boxt-aIOOc!F>8#tZ7(~u1OwcB)y6L>maN{p#E?S zpdhu$DcI-3)E_z>Z#LU?si4p*Dchnn>F4Mz$W{6*#_;^(T%0&O^m6f>ko=V{UOMui zC|)}9ZWk?m^m<2^qtcP5W`Bcj=tx(D{M=R1iI92xeO`kPNV<3_h>VEELu!VBd`Kz& z6@UDmOWj}kmvg_f>>W(IZC<<{ITmUFgZjm2{0_MX+Z~1TWG_I!1Jh+R&j>I#dXZ$g zZg_O#nVc2S>$J5r@9Ty8v71fF2*!GFgl=*3PVwp6#I1Gyl%8YAR9t3ybvu`XF4|kZ z#yS+@X`q)H`WKgqo&S2mSAha~X`($PihjFLkKNtr*jk)3xj5g4@-EPe(`YfTQ03zM z6YlRIb8(tdyo!>)ILk)zzz$?CPE*RMs$>^u9~63vl3twMTn#E+oVDfRw6u)mDoagU zK1WS6*XC$4JPYWxX?iiAgX#He^Cq}A0><+tx3Vzy?O67WwEkg>8qto=k+K@nN>EUo zMPn8Ut=^vUXUd?pAf^3TyrK!hMg;Cbm;=)57KJ`*4Efv9sUMdAQ3CI6l8Q#*I{?y` zU!CD$QGmBs*PI473vVV#9pSVG$x~R@SW%M^?}~U)IF9JkNIwN6Mibz%*4(TtbG?#1 zO(6S{@Bt`Sp>hSt&CWVqm4CiF%p-6g${Yp*BlR6hf!lUMkdBr#ZB#@Gk>FUQU)$ z&VMO&ySWZZ)uG*w!Xi*}f11a#=Ow>N2ikjB@2~@mjZim$+>rQoVW)rUS zpzMbGi}34a@l#d#ZP);)EtGm^vyKOe<^2_IdgQw&Uy3`4=ZqG`4^dzY_2k!}j^kY}y*yK-_e4t#2`Fm5i_ecs~;m>%O zXMyl}0(s}q6G7hhV>6mDpNExauOq|so>Fp&)1KiUa1TT2P*7VM$qpI!l2@Er`(@FS zb_%6^#-MW^&~`FCzI#>a+*K=X6iPeY4)s={OM4lKJ$Ne7%_z49>T1BRW{qny8mTH8 zd(EsJHiWSq_SdSBd^xKL(!nNaynf-{;b%neeJ&R_2;U>H4B-)w9CtzH2zkMnZ&A2D z+&{dI=wC_yUKFMgm~u|?Mc<)yao8xlm*}Syhe=VIB(dXSjoY%B=I}z4R`g z!!{eqAoX!c)YY6dKkQ_uPY1Jyon9&!BZVClYdC}srGwdU@*N6vFf(3m#IwO{0qJ)G zH<+bmFf(diu*PM%T(XowYzx#+DrZ?O;@QB}-~z5VU|G&&;%`~fLf=zLx_z~}FzzL( z(aR5J4#~cLq*p7-PrOHLqmNj|m}*>;WK-{Nh~<~QDJzu;f@FMhtigd~tc#{oD8or0 zNFK(uWZ4u1V}wXMg|msC2jO-QPA9Mh;X{zPm_T?ge~ui>q5zbNQqjG{{6pw_P=5&l ziA|2ZlD%x)Szr_Uwn?A^f#wtNi35$^r7PO8d0-^kf7S?VaXC&Q`4SMu39LqVMgo7p z{}4e>4Z_2Xs|$yqMQ; zr4D3O|DF_ER`nC%j|b7J-jsMx%N@omv8vyv0ZOu}zZ->lKv(ta5!OjyBq=))z5_vW z4F$4HQbExV^!HC=sc<9FwI(rKgY^Aq7$-OAA)NEV!j4j(*GcLMrwa&I6BvnbrUV`* za09}1pj32{)wm6G^W_~Q*<2A8Ck`MwEa%TtFqeyJ34z4L2q5`1ebThJiqNV%D6EtC znCNg5fBHh|Ddd+4%tn|AN`+UnjhOcdZ36iN+>xJV&}>4{WyNtBrllKeNBzcq<%UVx z@wfBv+@C&^d<%~WQ{f0GjV{3w3Tn!rWh@?o`Wi+WdtFsMl(Fk@sC|VlW0;Y67+Q(W zcoxcdb^+A$g)ZZok$5gzi7xxm%NTbD)Z0MxOG*#?-|{HH+)QSB^g!)>FTr{qXzw#l z%(Gn%+WQ8|JX(5R?a91w2BO|)N_+;+9l|To`xc0j^uF#WbOqY`Mj#BAfb_l_5v~V8 z>K~VZvnk0;Rr0DS?_*n4Q(nq<$Eo*wZGA0!C3!3d33)AB_uw?0T(4tuYkAAMu`D0t z)$GkEeIy0QJ#rRrrTTMiQm#9Ye>FRHDP)jYQW-Vo)xDA}srE&oi74rk>J)dBN=vGO z1F>P8?@KVBej9tB=nW}pA5!|Ee*_2$8f=Ms9udq_t2 z)eUQ**W@yWUJzCza0J3(AT`Tz&A6dwwYF4skxPkr$+dM5d$4Go6iuo}M%oLDQmtbW-7;!!Ff+h?qvlw^&#KME~?t`SX#2Sfh+Mu)OB;y|d!09_**DOrW?*N7Lxy-4`7 z{4svYOIDA%wnn@c?mZy0Ml_||DoWL|*N8?-SE2pK8qvtrf-#&0nFD_=?ICN#S2*~3 z5Umlf@~3OQBerfv;rtr$PnbJ_t`Q%(f>{^PHKJ+80+Xz+vPP`8FS+1t_%4j~K-Y*@T}595x<)hwRy(3#Ys4=|{{ZM3 zaro6-@1S%O6kQ|U;>$na7qo1RxDe8OplihK2wQ-z5lw-6APRnsc-(a689>*Fvk`6p z(Hc>Bx<>qmsJ}q|Vs}tkBi5!%Fx_!$#6#*tBjbOs5nph6sbIbombK3nXzQTWRq5hmenGj4P2j-z8P4SGTpK) zSv>rYQgVG9&>V(eBU(D|%8*`Rjc7@DPLou5jcBRdek5boh$oS;PJ-%R%Qrefv_>>y zK`>v4wniKRVGz(Y;!0Lf0zeYUn z1}+ewYs8fZ%OoIc#N7x#0$n4THV%_$`!(WGH!=eSx<-5y;Xw(=8u15&?LgOvrp-sq zMcb_r51qlX8R!~uF~R}~$Qtn*gfD>m=^8Q5*&%r3yqmiSYs3%bE+T8hgKxrv3FsQp zcsyx_S8S6Cy?Bx2rvZjJa8v@bzsjkt+qT_et&$)X+T8nKrzmPy%b z#8G6YYsBZ_uL8P8+=j4K0=Lb^t5;{viqypb&38nJ_m&RQd$ z6cEkQO)00U60`Kf z_D4x(=~tt0CD2*=5`;%2AhY!M5Z(cSt`RM}RM48T`ZeNT5dHwVMl?=tFjP3K5l?PO zX0k@y=Vs0h&^2Ozgrg)NYsB#gV?n9t=o;}<*DQQ^S+c3HMw}0GuBeubXF`Io1Lzvj zw0Po*R>d{q0k_~_4Rnn-8Q~&ODm+~yt|s&p$RFd5ENjHWDY~r@+q)sN;u_J|tV(@} zqZ6~2tr63+nG6G6BN_|8z{-5LMr;H10HMno(MWMG*;%(W;vlFe3SHKSM(1N!m2$T= z;xwpJK=ji!;#Rl*Vw{nwtgR6rg>^sB-e(;4p>=F4hxEP+rN>I|`v?ACl6&5fgpA79 zR4Xgd`(#@Q?R`z>V0Qu9`;JEFCjsew6A;FMAoab=;J?<0R#hyTuF?%$;cSgKk38mr z%o@>{ST~(cb*CfCy}k!f09hlxh|*dqfUXg*byPOia8gm0QaFrdGL4bg{&?LK(sEL;qtR};kYQ@+D%?y`140KsjWL|?7rjsv>}TVV1NedKp$kYo@L#QzE3f%J;%j75E!RegV|i^wyo}Buhtq+289naX)TC;T@4bw! zfqONOUz3*3+&PHmmhoURamTp0b2D*;r@KX}JY&ya?gFc}#eZ0*@m+3X)qn zH6<=*UM=LLr2hkghGfN&RqOA#i3VKBYXe~o4X=OUyt@0Xz2@Sl7Gj~_Au;o zCr_q1O4t_(H`y@hY*{XM;1W+%MSOo=s|kIycrKB{UG5zM<<+wFhK$`k9Zp4 zaZvb@>y7rxjF$IpH{vm#~V@L50Rnk@8biFI*K;KmhKXGxt)Ba7Y zv+d^z(Myj({!n*^6aVldqSSQ!tXrH7CEo^d@edj1lllocdW)YFZ1!chT^~R;k!Zb68~QZ31PEcXeTbY&k>h>6437Q z;-B~OXm%Gy7ZCYlT&eF}-c5L}#Oc?juB$2Eoco0EyAr2+g|yggXiRjIc}e>);k-`k9>U&eC9Qmq$gv39P;E zVeSEnS`p}l&;tZDKg7h=$`v5yC7;3fUBa(njD&lpDE~;{CWISBIVf1qsCvFH{uUZ3 zc76M{y*cE&q^yRt9H`05XxN@V<(|K@Jkbl9n7oddPho(Xyk0|C4bm=Ca(!_#sOXG87~#_Z1p&g^tr$-_0(O$sA*q6 zyW+jO#EYn`1?nzg{Jg4sTTy-scYEeAktwW<_kk;#!Y=adIh8aqg-z>5WyBO#X93qP zP*d0y2$ujgg_%+;Fv)cxS*3PaB&{Zptds0&$lx z9VWA`oOU2Hg&hR7Gf-2Qk&;#D-V`2Da`mOFIo296m~7#=^$eYGo{=rN)=3D zMoU+r{f8;c$kl>PoCR6xe=F@Frm$rkd63HH8^HUR7~s?O!{i=S^X|(fI|aDa`b8s_2<1 zto|at{RGq$W^_DkDxGRGh4q2j8}L(8nDJ6oG|UusF6^^aLrr0(kPenfZ9aKzVjp!0;3cFq0CEl(< zzWn~%6!yH+O9ey8Q%pQF(AL2$d=UR|^47u3cr1H`XM@?na1H`yw-QsBQS*XjF3T?@ zOEHCA3Uv~wkY%-qX9L%>q(7OJrI^AjOKP%}qjP<9>B>R9Da_J&Tu*uhQss%3%CH+mrZ6KG1Ve>rrm$`g;U5Ro6t)y$5y+UrIzEiO36zSG zn!+YN!V8l?O<@wNrm$s2)QlgOJ5k;gHuO=pjR9&3TZ8bN1jG~;F5yWQ0X2o0Hak+F z?M-2W;0y$63VRyiaS4bi><@(h0X2o0HjmUq+nK_SUCJE~s3~kY!V(FHDeM=7oxuIn z6!wPeMZ7mo9%2gnMeZVE3OjNclWCx)FyrxQM|ffi3&mYx9_e#IC~gz$5!L`Ti3MNM zZsImk=P_1HfSClK zlI+wpb|Cz=K&P~W5KfeUOlhYfOaX;8T~3i{>??Qr)HL>!3y5j#0kU+av0q(umT7F0 zYqc<#PjSUC(YYrF64Thj9OOZu#tGx`{LX2(QtJckS_h*e#))^}zXc-Wgi&*tvv&F=NH5@||gHG1P@Z7t@&0<6g3}Zl3aI{{j&8L{rMCszguRDN524 z??mAapgr+LgtZb7GugKY+d+`p;L2OsOeUrPYk-uON?qaV?W(O-ff`TJd4O64Iw5ob zxndPC-CQr3uD1}J3io7D77M|Z2$zFAu@LO^XWu06C4sfzjb7-8wctVc_e%!iDX|e@ z11Q|>lrz?XJw7{=s#pttLGve|)`GtsANK|)<+yr#YeDH#%;kXo^d&+(>NF5z6N5;y zhXk=3n#(pu>dD^?or8dS@+TdUtJFzWP5)x?Ft|fO#*^Qu7z-;Yc~Ac7C|o5<{xut; z#;Pir+sPv+JS0l$c2eEtUD>=S#z0HUNF`A_1hE?$4DCY}VjE0|ZBT83#*cYTM^}^I zB$_6&3Fe7S(8zqVy>j)i5B@FfDE7g(DCwJ0TCor6)(S?)1G2KB*$96{;eWo0wH>WO z71cIqDLevo96OcXHdyy*p6UVZFAW_l3{0xhUwXjp4)_&Za4WtqUS&RGChgX|FO|85 zlu!(>qoAGvk{8j4#*GKUt(PB`CvGA-yqZ5xL0T@v-gjn*IZ90Y6gCbA6J78OmLH&R zY@Uj6B1nEPDWi~x6zq-7<)l9%3SYsAJ&S>!K&c#D-`G5nm}7u$EVG}h2;T`MUEb2X z@K7|xe!39Oe4rc57!O7zx!lXvs`8q(T@66Ji{UjDo!7qej7#RwaVt zfL3jw&#;JeZxpDB!@!W!s`t`(((zx3R&jtnZ z0gkuav2ionAI5|_za361JVVNRP~HNKWQSFw;m|EK_nnQxHP{Y;Zmwz+N)AOfS3Os< zlFe0*f^#^~O-&_LH#NPUnCU=I(pFav+{8%to0`5v`dXkT>0g9DB_Jp1;I)kVps)`H zF8Mjjt!}Qm-Q~u0D`jaGmT5VzfIkI9o4i^%UL@vupyl}8mt#|QIsPX7ccA6y_(CF( z2Rd4g3lYYF!uA!*!D9v4AFh^*jeDsuDsB+Fv~)73SvQk;nhc%eZp#6YUCJ685)GKCvlg=F5}#InAN151aV%(Xb)X*~-8D|CbShqI!)ZTxm8 zTo&a1yLap6%^1Clc{A`JI`$`$!O#5h8}c>3ekL7bVAHktP`~0b>HKt}cpiT|Cm48i zbsXV|+O#2Rg)Xi!!gA$+dFlS~b^OUH`HRGV zNz|s-otiKCkHlVj-SIqCJB)kC@j-F#0_HhH21Wg{vdGxhyNbrNbQrWFUETnbU!CY5 z7ylSyL(%_{YJ3Blyy`f|k>bXoLqBkFzw2o`KK=`zv7rvR*Kj3B{tLRtf?MDRj`xhu zzioe)|6nfcqd~#)V_26uUJ$J5N{coRzdnj#_eD|)`r`;{$+TWKuBcP0^^)XMNvVH6 zeP9;$M|&5? z)0n6TiGPS~w1A^p5>NCc>7Z)iGGad?r;kDX7YIl~(TfNT)-y~3E#aZQg!8jYcoONy z0WIOp2s0!gC43uUBPd){v4q^bnjOna_CCK&{=W$%||%|vU;UTj8V)a_1r zKdN^7(a>c3wX2{^1G@d1B+ke(;KZW~V zuppE!J>nf+R^k7e-vcfp>C&O(kpCJ}R4KyY1on9yYYYen63E?vGbMq7x9KBoO5L~+ z>_R_Qf4?sVK-)sf<(4d>}?=_5r&_XdOh8PMU}h_NavemGCO!F3IE zINuMUIY{+zN5~$|wGfs-6ICt5CM(1VrN=|`v{h4Gm12pOADD_FvbG?QgBo3~WSrcop zUJ*;a5#QuASBz6!T(qGUx34B6UCITPC;l_}OYK%t3dvod zu0L-Dyj;7(06QT%>Oe$LfAoZW9~T@h;$WyDqJvi z^n&3c5q76%*eZwrV>m_Oyk-MAL!$LOXJ-WnFD7u@n>>L8O&)Yj`#O=*GUI5$Ctcik zE-w4Cr;`4>OaC`ZXMk(rVh-o>NuBf`t=_C*3#K6F!)l(loLkQ7CNsK)LCe#}jzsQq zClibAV}=sHW^dtiKmP~hm&z9Qs`noql&_33YPMCf*eqGdr!LFqw*ICE$7;#+x_o|#3`4Gwg~lS z0u}Paqy(|-@|>cC8cxIGi^HYFmc7Ho0`M!?c`py?9ShGrSmiSc z{wJ0Alax`g&IFBC%}sb39(&O0e79HlI|%4O3il}m_gW%!0iY#L8JFLtm!0Gr_NV(!Y_%f z`5t^wDj5}hPt0sWGeN;?6y!N~AktVw)*0U9Q9013QbZ`Pf%jwIW$}1B4qoZ^vAtWZ zt5o#DOD~LiahuDf9fN{ZG}k+hgij}W<=1teej@q5PD=0hu`z)3*_UK!tj(c`*Fi_& zO(N+|ICDVqhA|l)uTKe29@O1O^e?3E1c|u>=rvcM_kllh(QBGzNjQt>BR}AY4bbe2 z!;8qmk%x4W-F>9r1zdJPP3aO}_?9xsEKzh1?Ha$KvK!Qtu3@B%qS5Il3F#&^rF$4H z?j=uiU8T{7yrc+j3*|f*KO<>d^QSZ~zIUr1DxDMJ8_7s!-Hg`{sc8A^2H^|D9znK; zfKrJp+DObDgl+)^zuuZrN(D1TX@2{=260YuZsRR|C|f}z8U8KrYD%m3Y3=ZRV#_|_ ziUNr@2&5;_BLYKXbbrF+7Us*$`$*j)xFP zzBVgEm8}VLKjyRMAn`eY{EOUSWlO>< z2Sqm=S>}#(-P*P)l-s=gbi*pKeMi4CmUrb36!jWRon*tRUS!-I=!R8Biv>cn4XY-T zd=bzMtDZtwDFNBA>Su%>K&jAm!>aC^@uLL!cA3B4pHGPnj*3ZTA@iHKLrUIn z{(t0Mcz{!oyjyj2flOh8W=rM1+2~}uZw_iPh&x?(y|JBHEH}2!(G7b-4ZUFp&2ABe zL%hOaZA77S|1m-NfW7oS8#J3Nl#@NBypvFZ;(m=8%!!Tt!5^LXNW5QbcW$%xU(VFk zp=LAqjPu3^^`?iVqn?&80`z6>UTcr`I-rOiv zvXPG;^>-~12J;`K&Blfe?x4zQqkH%h9zlV~Mn|gJ3U?6m70{LEP^TRPn@IPz!p5I6 z!~$J;o{BI?0c-Q$+Ih3P2GHbq%Rubys)w#~9L4mV=qnA=LzE%XK8vWxuf0?fn}{p~LC zM;J<)_}l$CoI;Dg-C1Z41?q42F2V+&{&uDm3rx~p!yoatD;a{0_}g{=f|t=jEz;l?_ytaJfOaJ zMvqrjoLPIU=y~6}7U(ns>U(E;IaTz`_wHDz1B5QGp&Fg>u+nKa-@A*UUIh55@15~d zRW!`^ZUOB1s*#kficBFLj9EtGneW}ZFkS)bdpBYmzEMDZ?@S>tD0&P9^Syh7^aVhD z@Amr&Q#L3Siu&Ho@a117TIPFqHl&e2eeaec+z-_E&J^&xLc#mq)%luN5PzQ{TI_L_G`g=evV`%8-2LuxP$9h55!GZbbZV&%39c78{cAVt_%dE@#Ixb6AL)mIY|lHR;;-nkJXx|7&%5iOUR^oMY7x(duvbW5mzAY>-dUEk zk7P-wUrCl3&pS)U_l)!ko_CgnV;M=6J?|`)6{=*6=iR4LR&gImZfDK`BF{S`76e7u zHN5BDVGw!&^}Jh%Fc)Mz@5*;@34u~kQqQ{&2)zi@^G;&b^KQF43#|N!@}76kf6KWA z>UsAM!XFY4&%1-a!{HdH=bdTCRNH&r%^`gjP|v$}5jIIcJnu@r=S~UK^Uk#6k+$=^ zJDcQ@Kt1msLRc&T@x1#E;Va;N>UozqFS_f-g87tRJnzmu6S{cb_1?+y5vb>#@#4z! zo_DiJzZt0K-D-rVK;(J1l6Di%yTTusGy#5*=N$>1MmW#A!$~*@WO?3+o_OBvA$k{3 z&%17}m~3<7q+(YK@w`iroqFD#@FONNpwo;?5GF}LrWyAk+zV3uoW4vmWL_+5jlvQa zsSbFJTtFP~nv*ju*}tf%i_UVuJIO_b!F)F*##qq=e?+uRiI*8s)bv)ipb&@N* zI^fNb(o4VH4gVM5-c5B>-c7Bngxk|LQJRKQ{arXg19f}qgV0+7;`Ve7!e|hv1D<7) z3N}$z?|^p;gjpbZH`N^|HwYewg9F}gBgsr0@ScV9G*Ab;Z3tT>AP#uxpJ-K3Dmu}- zslL3eC7TKkcL$Iia?_vgUu10dfAJ-blDz}o-Q+qCr~{s{VpaLh0dGCj zbwU>hJfpKEO{MJ30q+N>-wRzF@Qjq>rKl`A^y&UZwdu#TK=e}wynWqLG7L%xMj-rXBCyGPMEIE?Cr)o+$Rh8(8=ZTW^#Ai`>8fZ`a65$I8 zNKefB9}_qbq}sUhR&u~ge|TS}0dNuL#MYYq)wVJylAfUq`!a}h>?v}|T< zt;R+!C?y(&^|ncQWE10u;oJ{`Waz5}IZuH$hJL!sW~Ve_vg zzxzqL8O{t)e;ENuEqV-L6EPcrE~Z*LZQPAW_lv2#-$(~K${m8xLjp3&U5PLi6eirl z&=ynE$U4`>VTyZ_vP=&<6`qaWO(ea7&N>jJ2NUY<4$A{6Azl+UT_D7>NQ&*@xg5m$ z7Z8|AYSCDPuEcf%TIfE$(91~og`PSgRZxn!;!v%wgIJAK==uPoIk`eLC3t9=NYD! zkt~D6(|poeKD*u;?*2eO41N~E2vPoRSwH}j3?g_oDFxTD9iVkHz2$QQY&1s z-BUz;UdJ`phCD4P<|S95px(`4$tzML`BwO^Wb;1|bP&74fTys@UrWP??V(6)4+s9q zYk#0fY!Ds)!cQ5b+i=dFa>r(eMYJ%eoc_@yGVMpw6L1!Tu(t&MCIHe8zQNtyDe;MF ztTc%~V>Qag!F!UV8UHXr1bH)7W-`z58dmzm1F^jqpUX^Z9<-X*-kRZNayPClyl#U* zx6E0=E!7>IEf3`2WVCTC88ss*tjnL;|Kbe^!kPr8B8&y;?Q=4kScGu3D7hq-zUn+Q ztC4s3FX`n$GAIM#uLSxc^Z_+5x+tTWlXdi{cqOkiPTT||yqZ7f!#`Jal1$mHoCz=a z3e7K1)Q1t);?HgH=Ky|rohgZ_Wh7IR0zX56!@kg-M`e|$)o)KVTT`E+4hTDm`U<4a zVldm1X(_4iv{;*h^HCp5(&=F`7y?q;9cKoQVdY}Gl%u6*--4%%OQ2(j^5wDWk4ZCc zASqnKA3IXGlE8IjbtMQNCh!r$JD^k)+rC9ieAew#%9qW?E3=e8fB0gR@qwe_pK|6& zsXCIfe=Hd^17RxyqY*}cpx`O$Yf3y85v5;B)``-3QkKAZNO><|=QLgpUYN?;>jHQ? zNZAEvI|x4^Fgni5xgdO#z($1iAUT*}-!e#O1|^^3*nAn$ZF7=A3lLsF;8uhg5*RKD z2tR|gXy-6FkACt^s*(j^>Z36nUve0nNCqbW&0#jeObJL18xdXu$qt+u%Ygz*4)=#8 z;b5Y3lgS_lghvtRh0t9BJqS!jxCo@)A0Hk2gx+#0kJD2bXj`kdl`di_b(x~r}@(--PMD29~O;X9AG05AR zrNeE;Y0KSJekIkyYBxy+zeDQ>{|FH7A}|i&0uZF-l$ZFUj^U^!`CqI^qhwGY*6HwvNCveC zT!V16WRP0vj<}uY1zePB1?G&(WyFI7b;V7Mo)DF4$mPmO$7jy8Vg|Vq|d@~)ylOv z)K$^FqVsr&lS%p#(q<5jC2&kS8T17O%YJ5V;VQ@HB~hw=d%icgH{{`P=qH7DODJ}xqz2l=nK%;b9tzmSg%bK{s!|Kj51gAI9H+FCzB-qF=D7MSGk{4~rzQeXK5`K1(f640rJG2>Nrx`sV!Yb22(HuWdRdb>qcvPDJN3Xba&l5S{*6I&rV#2Zs7#%}{hs zh4wo9S4BsL0xMCDm%L8Ru+Ezk(2-%`XZX87kb4k!<3oIfOw!8or=(`sB0L<*UimZ< zKw}`m!<|OpB|lC!2&;#qq4a>=9fYS77=bVxB;REeH)SRr^x2Yq!t!tr(KnHPy(oM~ zU<1Ny68Mb3PY6GPbOA;g(`J7VwCnB)>pU+l(VC>a3z9)?5bi_ZM1T-2kTt<>4`EhdUg!@Ce59ND7O&KtZAM;YghQFqIDC5OzP+t+cj2lLe zd&%hXrkIQyKSBKw$WObNU5TtTCPWvIbr%^x*2%#v-Dh#xX88cQzN4i*B(UC58 zF#K+!BVEjN;$HIIg)TPyB6OsSjetK~bfk-!PL7wn4qdFR=tvj45&rcc>SAxWYA{)I zlC^>FVk;pp7lo*cz3q7>Rq0}zVSfa)i-m>B;9mmT#Y}~TVs;m60jDX@E;bV3ObJLA zy8+=kpk2(gv9Y=?RxYh4UF>lCjvkX#RvJ4Wbc(<@VlvoW) z0m51Y_C;s{^zJaqk18hl!Mb|<#C$or+#LqO?+^6uU`(EzJ8iFHcZVC{UJDY}EN4+K zcLk%yyyTVauJAGnFN%^ZKvNgEyzOql(*}O25iWX_Ye)Mus?9ypi#PCMrXz8d=uNca zk@sK9`d}4F;Y$7_Y9@ofIZ|>SB{MST9de>wyh~yQ(cweNinl9Q1hFtV&zoLU`1j06O4nQb^|P()^PaNt64g&MVLU` z86fXPPKFgYYXZrVWbhAx)I6tPU#pU-2!135{!xAYRL77x%tkVc<}mqd zT(rz#WH0V$%EO$9jf%48F+FS^lj=4x8j$Vdu}qMnmbl)ZXOnzO933|9CFfjQLSG^@ zN<1*hllzAS`hwhWDmM)1ON7Q^Om&AbzJFQnTDa4J{?g(OZ&W7K&qr5q+si10xaO-b zyu_+6{4&%Y0cwvjt!k>}UGj~i_NZ?me+|?g)uJ||DoE`dA2p3VDu}LtEy$|3EvAOpXOXUN1^!)EqBm zDYa-e1*HMdE<<6mD%B@6&gX9uUOM`#w=it#h2D3U)@DvqmLkKb$fB(9zd&m7QzUi)t&1zFv*wPs(Uls znIKbLqq5j_>R!pJ`y>jFi;@oJ3;yrwT2dI8h2G>1;|v_s9|7|SF-9JgF=5%(&|3<-_@1D+mbSp zukOT!ToWsDRT23E7vL26J?iyesMHvsY0p=mh z1xe}NU-|}EL*@yq^ed#V16t{%+^~%p21>Mw#`b3X7ot{l`+rw7LC>_Ln3syIq>qLB z=3GHZi=lNPzfK^v$7OZ9yRlE<#&BHNIv$EYVvx6#E^m~$ml|4@PlTO@UP+c~p$#Xy zVIX{szzl>NfKHVxgPbZEB*J)eohm&8_X&`Ysgh2S^4t(hA)I8+xe2C72g^}qiu5Z= zKg)r{R&UIVFGDy{N<*PWT zq*5;W|C}<7p9p*BUQNxeCFxi4{y`2lmB6ri$zU+(a4UfxYl7g^tK^Xf zf%l1h8|1yuEuoD&CT}e{$yO|hZt)u1w9%kdub&L^L7-pD?%UNX$QhrAT_Ac&1s7EoupL$XLn#8lFNZRGR{lI zTQ*1r&4E_@Y=n_OOKJ*O^+e$iSJI`VKP()%l^Q3h9ADBMq<;mpq>URUgN8s$dIrJ} zkQ)1+B@NPYCq2R!brVOf_hfir8#zyM_q-q7`#_+#eE+?*w_Ww|7DW|Dxt3G2Caf{@8i-R#IuX(_AbB{$vGKU^!%OTRuNIz2^y8#21@%V}=s`-+ zXoTW&Y?VO!p=t7{R5Wc<)6+>m6=-emM7T`?Qrpc4AAnTHO8qdJqy$Ns=)Uh~mt)9P z%B?ScHf+o+2ShJ^N_k#}dwaMrSbh z<~Mne@5^J7zc;L9cE1ox!+j_h$d&RNE9NERCzi8etW4MshuatQUZ2G`mey+=sTW9& z(!(rAUgB_NF|KcO<7*vnNAXsmbzFn68l--4x;BLtJAwRiCsGJ|WX>eP-7R@ss6SlP z`|3*+-;&98K=%@7fh#ez$d8dtPSY!4@lYO7tFu6jCUg=I$wD>N;?XSK1}HzZN!cbUj3I9heAdEKt`0NmbW@mBcIq+7T8yZ5{~Y@$R3( z|3Lb;KpUx9(`2v@&_+57VK^v!@BFA%(n$Q$UoYlSo6C)daO76+jj;F#Da-#zdIX&Z z0FS{a*%Q9dQ-!!WZ2Gmhg{vwRS~jQp)R$$8l;z7Xaq0}pBHmf0&5}Vapgu9ii+K&HxBSE>rrizb zh)>LM@Q($NPmC$$R8_(!=5kRIpO{Nfm;}@(W&y%{35ZY3D+udBpx#-Q9nPFDa$Nhm z+%5>;1NF}O!naNj;ozM$_eKsQ-dU}hGpz>doplkycnOGi)^da;pj33!JL_wo%~Z(- z@2pV1F1HKP_o5|Vnd{PmNeECct?>wBfO=_}HpXtW#Y-!cugonceTgW@*XI60*dqb) z*6OfdGH44*XTn!+t?`7$fI_j7%TK(u3XgYZu%e5WQJA0lVyjNzU9=t{tA~KPXc;S3 zmG4}%-huj-(8Wc|=uCPmWp6H8e?Z+MbaBx#5>wntbTLbQ1EbOY%sD{xQ>%qhlffEV zK)T@$>4q{09s%=EpaZ(3>cDpmF;@b;B+{-~*o&TYKiIuM`f8w;#9s(|Bp{bW*Op8U zK;fB{E(zJcr_jZPLD{;fEOT$7EV6;mRCFc--N?syG3CidKDx{IYSNzpx}lHcpd0%9 zMa&-2l!3a;mv;c^zPue;(F=fmXUJVlYiN`bo)eb@nfsvXonnt-Gf^+&j!nAt!J+};;>S$2WnCVeB& z0_V5pHUzZ5{SXcVg;!NBu)I&yV@%Yiv5rcC&kPeK%r^8rqM0b&0QA$B#$(f-m!dQA z$Ib9$aC!~SdZ2@o@p635u5an!v`HF6U7p+EB>4D@23WvCo=-|}S%?K(EPDTj>lYBGVw@pqX zt5bjuPR5E=<-5V@DyUZoT?Qwk$Gv3P+u-yF)Q5yFgOky7ykvA6oZf-@7Kna2IGGZc z5-lKu)77_d+GKG03+5i6gOjA{;MAo(cLt!>&KQ5T%GPIH?Bhuv1N7Qig7A<8YMB7;-g4!jHpbZ{~rlMy+p4NhZ7KL_aGBq=&L zEg|M1pyj>Zmv?G*dAF0k4QP4GJ0^n$K+8J};WSX#tzvnDTp66^_yU_$?%GxguY=S= zloxwN8;2jdfH=n=%E%_q9dM4f`HMKm z7rx*IMo}pI%B?GIy^6LuH?fB^#`;Q@V6j{HqpyX2(jn`$(4e6#-3piEJ;48mKtkBT zLt*E}Jl-oV4`cu?{;izd3xxD%1yaA?A@vPg$vdGz z@duF7w?dLv4K=54dWiCq#LHWSaoM{tbA(sU!e^##*jn~(>)d9#9IsJWI;V`cVF!=m z-POin({bn6Yp9LG&JyU{M$6SStgq#gquxsHvg={|)(W{dzK{I!!nO_DOA#)FB%hjz zFELV7w3CRJ?{miGTL!*pL2+l|Z|qI%v!{6dLeV)4-{#TOC(98s}%v*SDKz903L z*8fD#U(isL&OC-!0QmnKNQla`@Fo71ujda9{MslwBG+(CJ`$r1FjI6J3jHrkf@MTE z1qmSq#Y;v6!GTDz?)*{uSxOhm6DPAr3IC4=2_Xd?YeDCm#v=&CCh)oer8_` z;@fS;#=?JjQz{|spyT~bU=`)d*5t93{880^*)%0fRp{?M8wM}&Uj`z5ohmr@)veKR zD6^y3F8-))j1HBhEBr~H@d)+~9FRdm*j!wrY1w$Qf%3Nr(JjY}9~;!0yRV$);KNu) z;zf-2B(3>?A34c$nx1%eC#h~8j9}a3?6^-Kb%X=g%lJ&e=GwY!Ps8>(el?AR(%4TbV?-yj&o{~;hDBtAoQ7^5Ci>_Yx1eQ%`;Y{D7i&}donvb*Ujv!0tW;fxD|#;s}%(%>^wddWV7 zjoVg}+#e&eM?tTy2TNQ@63$M--+5OKWYmlse(jtUnVfPw@l3}g&*}G)r}xPJgaUpH)FVIl zAeJaVo!#wd>^N$%>et?Z#iwH5pU`{L~|VC`Bt5k9>8eALeR@ zQ@IowXZqu$g~XXYA^F4>4v@`DKXgP=g1vzKv4b-Lm5QJV55SRAz5Y7P=w0VfdVZ|w49pY~xeU>PQOZ_tlPf0+uw;^l= z>JV?oPDa}15TEXjB^Rhed~1YO5)g;@!3ZY-`Kd4VG*@k2ANS`$Qv-al_nJ?;i7)mP zR4$b~#20%Z!UD;ow!E=mMaW2zI&Jt<&S1%)Bdk~9zbqNZ+Y3J+d@mWO1N|Idf&tR- zzZL!|O5#9YdNAW0PzU;M2nT_*IMAC8whU=CoaWbwk~qx|gELf=#A$vy!c`!0^?%48 z$)p@tRvhTF{Nf)$;~~+C{Nk5*HIsY|_oNP>uc}}C`zX9CN-67p&O@Dd4VH$wJ!>qt zjk$^C$~!35^C`4Dgk>!6qv*RRK@T2xYf*yQ;)iM_hn^4jha)1%3{~~ zt`~gH8sZ6m@MSb+^1ym0-AA2%C8ZTdZwA7h1TICm5G3<%&gd{0%L(Y3s24UOdIRa} zMWK{H-JZBdf>NOrwIXITq2VA;YHx>)c?tI;{gvFA*ze8-ltJFcc^v)a3* z7goB6t8uMAKU<_M^Aa`Wi&bGyl0HH4Ll7QFphYii`5@evz=a6oB+!7s3WTK~?{x+x z%Z|4be8aeMT@&upP_q6A+IR4GfFQMXbCi78<=j@n6g8po+o#W3%mJgS_eFd&zVv4D!Fj z{|)fdH&jfmMle$fSr%(Km_n+baDzj5paFus$+u+k&q$VkNrPDTn?&h4XosS3h$@|# zrGyvre4|JIr8$NyLg{>H=b>;8sQD;o*_e1SSJIK4SDu7%E7aK_SKezg67NG+qStK~ zD+=Ysw$*T-0||M-twLkr3tmZy#tP-dwy)uD6CHWMO@4Z_H+F6nJ*m%ZsZZZq*|nk% z{t6(-lfl50s(VQ@@H zrUCVWHX9ky0GTx`_U0E_&4CcE;>?4(}{b@ z>)-|7Np$o@3;4T0@GmP;D;d$ z1?mNFDq*mu3EjY6w78b^t3*Lwv{;X@P6FZu{~f{(pkDB%9aHVPylBx(T2H*-YxZR{ z0_p|dAK@qoh!^~Lgs~tf{Kb_#+Y8>}c)iG_2JYv*;O{{BHlSYc#)?(tJ1_XvP@faJ zc)=Sz?j_4!EV}Zd#g|aO0P@owmVa+VVfyJLmIwMvc1YwtOns$?OCb+gKV8trT1)ty%A{d`4!=RKzom=Pz))T^`5fB z8Lxr%o>LJ{mVor0%Mqpm?LDT=qzrB8JtL%*r1v}m=K)cYcPMrsd?NwrJ=Kq3L<2!# zV^`kn-eYkr!(FO*KKkC%0p$aM_8w!!s`6d$ISuM5LYLlS^thKSd+R+{L%kBnPkT>0 zR}Qv4bW&YS)O+`#EHbtCEJ5WFpuNXbc(+7amEN=R5qQ#j-huO$@TB(`k2gAmNALMs zdZYB7J#c;%p7b8$vEbMj9=&I!@TB+DKaxcUh@$J(o*NOa2iki~1xFw$m-U{Nq%RW%8GC+0_)!AVdusQ?#QTz0(3ak_ zP+CcPPd7N7f%cw@5iXK|^qzYV?gE(OUCFb1kHs;0@b!vX!1tbYD8B%-_ZTZymG64b z4yfMHTo^^U8RA3~Yql!hcV!@RQdCH~bB|;8Tfz*&TR)6| zYknnaCF_SJ=Xm1zE>rTnYdLv(>qk2prwvf+#}I^*fli1l6AX)$jwVKpMU+e@M04Ta z4kGJ^QB%P(>46ZBSjyqN^_|(&h<JiS3Bz2j3Q(G7%eSS(fV-%>em6aemsG&5|oOY)0!N$wsUnL4Q`31S0Flg-$7s_l;UV-d{#3y!E3A)I0&2f7 z#T-@i){pU|j}-;6U)+gshXlla@e;y|pn^8GAE~`rKfWb>yC{fujR8D=1FC%xLT8}X z4?A`;(l+bIP|{Bm1=$W@I>J>F5bMWd2up$d)cP^jRXYq!N@yZYu(gJK4^yol@1ydr z&HowfmlDvj^TkRQ0qq@gx(;iE!zj&>Pujf+g(*G zAvNd(ij*Hu zM+IsXISpYjP^*Y#7Yj_XCN9A$(q6I_tH`bJXM-U1gFF6zSw-G*Y5%8Hq;$UJK7hg} zd!;XXk^_iUWEGh|4b&>~H^T2gts%1@&b7s>q%|2S9dJ z#KU>F+)A%J`Jb+eOawa~%B_lcsFv(&3;9)%#o!l8rq-&6M;FzTQ}y1U{Ag9=UIg!k zl2s8e)aWmT{zNF(8+dHvde~fVU{^&vFk26(RgpfjPOXZ(Pb%KEDbYaP3pGzUpLv8D zO_Y!Z?U@nm`aq_z+?7tfRgtb~v3brfanLqCycb29vvxb&{$R;y2I7%n%B43GW+U_OK}NHYS>R_%Mw#?7QOcz<%uI$#Mwwg={&Fapne=o? zV%VAH%-CPLD)K1cwGv2XCOf7$&oxQ}OmqMGiO)fQ3fY-S>5=R?L3U>1MHuyLN2~KQ zlRkv^gzU`Zc$jH&XlAk$=0eEMOuTexr8P6zU8Sd)$%A0-m5}DOzrt*mLo<`MM{&#w zGL83gDbJsoct74Cn&vCXPC7H$8{vVFotb!4SRe0ZCKG{<7hE$F506sLm6cu*Yga`s z0D2yzpPknpv(oY=Fh@ErT@~3Y-ueOZD+{|Fku{K=*Lo2q1S(am>b(2G$*SG~^OkV3 zDv#qGtioYQ58Ve&mJ}ULaY43k^tb}ENI0zLJmF+LUBPsL66+b2rqy#v+O1IX@{&UV zA1r~ydMcfSF`lbuJtu*l4O#2C66OlXT8|eA%-VeGd5-X>B%pb1(J_D_YdzgyxNI_v58dF0{xbztnm*Ap8Mj zt;eIn`gmtO#m6#~fUNa+cvKJWt)~~z9*}<4dQM$wd93Fo$F)v8bniZf$)~lRBM~_Q zvex58SW|P>^X!A*WIZQ?nIoL6$KxVX-ZilveY(|J&(&bA6i)q+$FZg+9M!8v%bUfy8>wN^zd6XFW}iV+$6t*0VFrPLQ=8FT$Fd(&hV} zk%S*D0a?#7n9Jl)-}4B}!;rNeFU^`7(z2fWR7$d*FTi{vAz4qykxbAaYdwQu_J>U4 zr7q?9*5m!?<Jl`-l~Z2p0q0RaDM22Toqa2qR3wrY4O}+ycIItIP_eS z7Dk-?%ldJ=BzOzd`y};UsOVRYt$1LL^CHD}@ZO|`Z$>5>MKwTVuFQ>dhYe%z6Mgn2 z6aUj^Pkigqhu;=hf#ie3UERWt{I!i(jrPvR_o4%R2vqr0@HW zt)eZG?|5XpcV}M~RQLfGd(f%XUirt}`b)*NA>Sih%UiUuCH>0nRmW_`;2TK37kTOe z6$|fiJQTy(q~dj9g9AM7LNJSAZ5r>Yk7LQFFy)L_hkm+G3eYnMBnQ@z0 z=nT)zSDbe=Q4v2h zb|8{V;KFCPq$x=v*_NKjicS5FV0uEaB;pL5$$rThwcX*u{kfzHIRA*VTXW7o@?WRP#DE6f?lgEXe6N51=?62)F9|?`^)UEfPNFG$rCO`($vKo`|mp}vWht3 zZrTzYb;-w=?C)Fh`a${cS=;|T{13t_tjzaMbbh`4mD1T(A|r}JGn4HWt4;7Q;DnQU=$o>IUWwc=vo`XDGDn{LMIyyV}jw*PbZ50L*L z@Be#W@Ye*7U24n`_>K1d+o|8VCYW`ZG1Ksy?fv&l{JUMwiCg>@d;hVC|3~;=fuBBJ zXVY_v{BH^t?Z>RUD6%JZBYj!0{8C;^iqG~lm4KUqeLe>8RhR(uDZ2#lRk)o6+!!ob z17LF!-ry;YTEEKZwB@fYR&d7DWLN(i2fk5zE;iyrr8j#n{yp&u_sRF?&J!O}u)Vdb zwvre{?Htbk(NHXW+>eXN{`)tw4;RhmV%6?1wObY)`Kzu&37+yDZ2VNz@M7Vv^SIvN z!g;vN0Tw)^30T1%66}RqFbSFLhXCGD;HN&&#+7{>|4n#>?Vip{L%*YHc!zbg)hKRl zN6i#lZrPeK;H5dY5d@9q5+kI7EO ze-FIE!}9%Q4=+T?_tn};;#0um_u|=*Z;OpQ7M0q!o9s*Y-oof%sg@4IXaY?7 z&X6gDPlW6PQmbIDmO~FneGc<6)VPb2bPq^no0Elsq$4_#OPU2cHjN)}W7F(Un9q2D zj?V{~>}&YXlmFSZ{qM#9V!XmDYx%bothlu?xAFfTC>F?M`vQLwF8YCsO~Yml#%A>- z`wOE^qgF5^x%o2d*hv=F@3>9sMohK|f`3YIo9F5Yp5p|Y{>1s@bD~`u+Y0}BGhf;R z9356);`IEePLBrep*}kM1QHdJIJ3UQ+^l14u8*GtSIy)w=EZ&V@oXouqA(GD~2RqWRS(!0_ky%;V9ne6HK>n$*m z-hz|oKMH@n?I+UP6w}D;h?BSD^ip~B>-8mfrMQ38+I9{mI{+EITrqsEzD)0^M{;MB zzW!Gk^Dh7OdcxSgj!`dWG@bWi*cSZ`t9IHxYH6SA$-er7F%4fJDb2aqq+DY2eKY>s z;1%xY{ZCQ8t#RESb{i+-`sqzlcz?KC(tSxm4c}56!T$+REb(}IUHWk`*FPB;g;Fx<|9+DgIm4f(nx7`^s7mPGtJ7Y64j&^xZ9 z0bapgHufvuFy=tK!V2%dvts9Yz<6rdH7Og3T(yD2Wc;58#S(}Q=VG#Vd~eM8aN*Tl zY_6Jk`D4TFG_NylC3D{&ydaJL4@0p8;-y?n_V38Q02hADrMCQ0Vc(7#7q^wnun`mh z|Nn&S2V-M>Le^yec$-af-ro}S;$r3hL7W{XRn9i?OF@sRILeP*M>q0Pu)EDo_Dv)P zNTQ;?#Ir$XYa3SL*`RMC@cYh6r_ABV@;cs;1;rAG z^?eW?!8wX{QpW8eSoUv%cmyXNyN%jPw=uj}2rAY4JkP@;`RxHT{#%nXz}nY&&IK|X zvaj=e4(17{(2lPVsPj6{QH!Y^`zu-CH0u1!a+D=2MJ10qZ>-qb?@~|Eb({~ z+H(;SrQq!&NAZBIYCUpP_G*$PiqUb2n{pa(#LD%Qbb9E<1Nc%v9cZ0HH$Ip{VGcOK zDMjbEuc0$Nbn_<({2j7~Zn}=8(ja^2#)~qjs1uvc(ro!MHK_fzap1>7$)Ot$Ei`M% zw15u-%pJN}1oAw{9=dq}<{8NDyLw4JyH_{5{6jZIm5fdxyYK3e#r0%r?Ys5_-yPy- z58aq1`G;=0xafEn1^zm8bH7Tc_Mw{*h#wBwLpKXx=0hzd@E3<}`lcC}4}e2A!(XAW z^c{p%NL?#6I&||q%(GB(=;lBt#FvjGw5j=!S4l;B=;jB2-$_!B9JL;oF|8nbY{m-~ zSi$tr%^rmB4%uTf<6$c0(4m{fFbknt(k!(p_x{k$U4-8u0Uf$|59S>?BwaY3jakSZ zy76&ys87=V(9QOQZwJ{!H;2L;EQb!=%!N4#($5~c+23WGcaNm?U_*82<~)kP9=f>- zk(JV+LpM*sJRu$S(2Y+5uY*d9-6{B?-~}nrp_^~OekUeNK3g$S-K62z`IG>1&4%hURkPg?J4(3z|>2S>rF#mvz{rpB% z8dH^|YCUH35~vr1Ob*n{N}--h@=C;u7ftvYgTY3F|=3p9ua2*#k8P zPU5r=WDnF_2D4NSJ!bSe%*&8HP~+8w#&f=ib)e=9sn&s-Mw2;03Yo$OUHpG_pymb_ z_8$+_oWrVT@+)IqJ>Ij)OK%f_4%F<3@?MZVP%{JOc*q{8@iIJSgp5vHT=FJ0pB|{W z3E+*A)MG|3!>ogB$L=K?8qfLIYaOWh1^iEv(SaI|EJ-;pQ>$ZdGbLlTg!tKQKQG2( zMoMIP*zFM#X}A6M1U5iWd!WVxb1`}SI#Bbogmj>0A_C*BkPg&%p(y22A&TS4w-C~S zn#Bk#gzSMDFT;oH(?UOQZd}7M0>nmsCmkz)xm+9`y{0O-jflSBcZ}2u5{`hs9ByCo99|ORkV#> z@1aP?9j-&kHIO|}L9BlV86AW8 z9{jhGQ6{}ilya#Io7z`MMwx6rjW_#4$)@&IE=jz_I?b7j$bZje7r;A9AlcMjmEt^C zZ&Q0D=wl(fseL-ksgT{&_9B5fqbj;F$Zl$T>Cj4R zQ+uIGuO~}LJA&y2*-h=~FjaEsNW{f37eS`+oi641o7&!wC9yPLF8}^}E)O7lA7nSR zJu0k^cSj;N0R2F4ZEAaXlyZr3_ITZan#}PW;)3)m)q`xWxO7mvNsoKP2jo#!)eTq| z$R306u+Wq@qa?AUC*B7qOBxTRLO5BH$3><}IIJfUPS$e;nB~IBdOWVctP~E5Submr z#XJt?Q7Ew(59E7vbxX`!%$I;amw?IW!L(v66WHH6srJXN8cnBaA!{XlV0uH=O1uD5 zICi~KD><6*BP1XzxddjZ9I}!JVeW^lm3ZmUO3O;_RE}jOpMd#LLOS};uA27=Le@(5 zh1nZ2jkj>A&9@To$ERD;yd*hFk3LL7cp_x2#G}IccxNSxfLz|xC0kY8EM+laggS&Q*N&d#Y+v6x6WSxi`iYRFoQ$MNB8 z;jow;KLjU>*$zx+;bbu$7nw!EVKI?#vY11_41*Gj`6RVy&$*=L4$hnic$Nf`KIY3b zcb==)$6N(^C1kDVX_zM=Ydv0sJ|^FKekA-`3CMbS%*dE-khPxSFo!_adb~7!4AQcm zP0E$5=VUM^N=VjoKg_*ysE_#&=6%RC{>7y{-+H`XU_4jv*j3R?5)WDH@u;vq-dRsi zpxp(R^>}!ca$ddfV@3cy9MZ33rc0rgLsnw|?%<5SdpwzV^zj zEhGQnOv&%B*!WEiYPM&E!>+XK(S}y4U*VrF7Qcv6jc4bxSVIX81{~To)y~`XVx?6d z!0V#p4~^1A?J-vMy3)ZE0yE?u3+f_;KH z-!7|}wvvBy7*` z{uogS#2<1o*}@%YfD3(B6&c$ROME(f4a0(YI zzdn_p+VPd%dWrFPB*voJ%Bqwd7)ZjS#2?{hgz5x7rkZGX7 zv4=(}XC=63F!`~BG&sH;fi;jF9DA99dNO`+{3g)XAv-ws@VFj49UT7-_BSXuIQBx^ zzw2bi502Z<&X{tj_Tbo~i|WZKKP^8cKN=kGhu}U?GD-464a^>twtx=;%uSMx2RQ|@ zlcbwqu7~U-$xAk@A6@>t7`_1inPfCc@-ihU=Zn17B&qB~9@d8V73-P)pIkXGoKhkg zV1K6EXn?&lu$=_8_W}=O^pF6%HU12s?ghsJI$A*c>H!bT>Glax5sXDBHz)AeFinTY z=FJIoC-T6el=DEHm%r4fWC5v|uaso^GyV5M4a~YE$&KO~yn_i`ZUl8L6dZ%2@FdR8 zLBXLo_Jr9DYAK=Kv+;=*|6^YJ5!h?#T3iKx^XtxKEEn)BSBK9jhYtuj38@pL=5-u* z!mNRk@9=sdmWU*@srdmCQsMmz%ug0~5bFRQS77ERxK&?(t0tuH941~+Fb>B}FstM^ z62~7fKSOo}&nw`S#3*R~hWifhn7KT#1KAb4$6+3lLo0aAPo{H#8t9X-4br@M4kh-d zFW#0aE&4=kHTdHp`>EJVV3tBj{AZ;a)+v6!g7^r4`z4_j#76Uo4zlqNhS?jkD~Mj2 z$&U=ox(LI<-1vX9Vtnb3GHop7m9``wgXU3#V1+lfle^>?-utFjq>*F2{PI(0Gm}VSg;gw${C4 zD-=8p{z)m&a_o078>OIdm5VXA2+4RrP06o7n$kHx@S)o@VdbUvp48)O^j<+oiv@IT zQ1Evg!(oQXaSo2FVU|NJ9%PI#I4up&`O;`Kw9O|<!<6$P&3`N2 z!d6c9R9^fFF`U}LMWCY-eh4!wAZh9OOdpCv-~}%E+eh3+LaQ!B8UM#`Ru+ zb6*bP7Yn!{FWyLcHLl;8xOal=`ho|B^+Ytr|MMGUG{!#`{LzxptkBCuDVLfqRcae=U@Z{fE0;U~!I13)9aQp9lP`1d_4;ktxn| z^~U}`fc_4$WB)Bq<1HwV9s7F`w%)Lpbmq7_;k!U~?0*u>Y&kS@TnV!RvSWWQ&CCaB zjs3qw$NrZ9T?*-E*B89$@!j&INj;{ap{(jbVE03Iw&-D@ zS*w!7l6nazOZo`Rhr-E{JT5Zd3y1Yod<#z2Q+S3(SKwql9#>$>sZY%odkH6t*&fVx zP+~C~T{-bxnz|+CE#_dr2T35Ym>*M|=jvI^OwcuuwU}ivmqFHIya*k?(&bmA9wYo= z323%x&g7AIIb<;%acl!wi}BLDFB@rDOgoj5Eam_(`$N`Z&W1Tt4$T&ChFJ}n#s-&u z`4;2-2yvl)I+t3^D+sTLti^Z~6REm*XEB?B{w%mG#)G1i^Xk3DY;_jz0)g}^EOI*Y zr)AmYJMcOx@)`b{OP%S+-#RAK8BI7Y`HE4|_nZ!cVhP0hET`$4VLH~RtYH_t!~R^X ziZ2G`A42Fandm8FM)Lo|@kudtv}w|G<}rK&$?s1>_kc=;_qYU24FB-38-c8X3V(5` ze8;P4=Dslp()Cj{&>QQi06Oqs4`x+TTg@{LpYgxk$!0BB$tdP_W0UbIx;H@k?oeC5 zd%)n0p3MKih6i&=bKB*3j?6bY*q-Z4=Awsm(Oj;}rfu;*-1>JJ&?JO+CxH~-b4^1t zhX1-f#!?!W>$1HrJL|GH7n8k>D0MdsS97To<)FMM2kmC3k8P#y?y}H4$p06hSi-Sx zuCZ>nCVM^#-+>Et(xQ&Sz48?9wVO47{!*fM_m%&Mw3|olo0gnDNFPXDr?Yu-911?c zaX!rHa(s{DXP9rGlIBkOa;m2iIo&YrNeynarlh+I)h+f^7dpq@fEBvC3srqzp30iS zeVxCzYn|E1P9cjA0SaH`l4dcfn%%dvO9^e2@cTKn`zimwhhhoDPjNBXLy-RiE-d1N zV=8aj3fGjmcq_cyLa{)+7kW?d(dJ;6VtSRi&7uPd>ay#;4P;dBX|2 zgRn-7zpdOIK@ThURyg^6zUy>zyL%ru$@{K%0upXp&H^wCTK&Yor+p{?(?%=|Nk8Oy^_df2}kv z?1S*pL8}X->B7X(^Fo1c2u?d4)D}gzfxAC=h-+wHkNvM;+9d^M`C%-VJ`axHHpxHQ z{ji^~-nj%He-4uasKt9%Ld!CM8SmOwi5Xo)Wg6ZgiO=*mj|Z5L*_XD5{-gM z<-tcFJ_q}$@aC~#`61|DpD68^rbe4o!7T(eIhT$Q>U}?s2GRaoCyQ%Ww2<5$PR^v9 zpS?9%9DLsJ&D)m05VXoQ70xX>t512xI0P=9J%d06pnrf#%oOT$UtIJ*ieX0bL|19n5(E<~~W4W^ldfgcw&!=%Xg>e?dsopbcO?kdUT9nG4wS zfs!SIcT=Ht68fRw2nlJ)pc|MjkX4j!zmb5Z?AtG*ydgVf zpA9nuDn5!3FI{LxNty}&e8qn9n6<5!fD-v+Z<4o&+1g!v6(aHuZf^Rym4ry%{bXkiyD`Yy?s9U;45aSY5+5I_5#o$zw8 z`gyJ-acYrZsVrKH1g9V|Pb&1Do$Fw(f$Z@gFU4?MCBVYKP7>0>z;j@pk&qS!zK8i1 zGQ~=D0~c##)=G-wKVy-KwY*|e+5GQp;zG%c)uZ`pYns|5ky=u)RxtqZZW2fq2^u*Z z;Q11kDJCvLs?49PJ zrvq;B8$i9Z>uo@9LcyQ3wt|b8twHu2VON+gkX=dj8baeamul1e>22(bT89B0Dp|d# z^+cFilGTc;mxxl%%lgIQD}XMStX@s>49rsyzrA)O%e%S6b5_>=%Jf?E%>cfEigoup zBF)|?Wses-&s6qw=4jbcUS0+j9^)|n%#pO&ccYI@g=tjwpkh<1!5&l$*8ZLMjUfFp zWZ#iK|6;ZTAbVi(T9~V$mJ+lF7Q@B+x4#t&wJSyQoML0v5>B^R@NW{OZt+=!o|YDM zi{HU)gp!paFBBTj6}xQHMeZX7>LQz6LeWCDi`)ffXNZA4QJ(qX;P zV2+iL4(nYGbFmdt7wLs~0RZ=5y2z^}q%QIUFz;BLUf}L=9OO=LAO8+crzzWB%F+U4 zU*vux%qlr_s`68q4Up|3y#n?;QIMXh-1aiwp__Nt$5N!-ki#N&W{l2BJcf!%&3d}1c{HNLS1C> za;z7!U8I-d;USd(UF4AxQWx1B%=VD&A`gW*7)rXxJ6){2^-@xFk!K>6>mp}?oGG+j z)bnUMkuRe)DYz}WYc-Rp3?RHKdrUF1e3P{%5tLf{Dr70z}j-$hotE^>dn z9B8{pQ#jOtMlN8?saJk4T9iyZiFIM1XUQ*T{6EZ1PG%=5Wa#ubePK}eNNhF{*LP3@ zJ#XuAtUfpiC%}=5aO+3a@#-1e?kgxnsHiz_8SqG6cTrbnb>ce<=7K$2^$XQ15@^2p13QU39qqk0@)#|Mdhs2bJo{&v%?gW9GBK)a0P% zUkB*W)1Oni&0vANSJb*>$krq!%EK&}nG#kWyl_FvrNTZxSAbnEVdduun8%>P z&3~C6J;Lil_=0I#3obv0Zuy<^qeptbLCIH;X^}6(8=dOQY!f+|>{aX^K&Hi;S8=@RC2z;MvNHw!!WO@(B-C#Kj`!<6$$ zil+tJ921kI^}sfQ|5{OGzFd(L;)S`5X}175)W=HWwG3OJ;1@7^!|VdJ6r`+_;Ae3? z4K>hW(ZaLHI@rmYjzYQKt(6e{fL1nS&^IxqHJen~Tj&@Jc- zl(}2baObUC(3Y5Ao{|5DThK@+p<7VzRDuU)oPYi;=xyGS!z<9(2js!IA`-n7&T0}o z2@1Bu@d3njUp_f-#?tlhATqd5$xT}gBeEw zPVZ(X0h%qKdjG=Pokn*Tqd(y{YH!N#YInkO3;FO{M@;SaA;7#7KH#LnIp$lulN=@8 zk!gIN6I9)qe+9!XEilg>g3@;rJwuXzRN{^$r2RO8Zy-4D(9Q;v3Le+7G%-PV9e^yumt2rPi? z!>kQvQeIvzN;$^d_7@)=4*N~1I}O-V2t5IrqMdL}bCKmZzttq6;6NbX0p6&X_Qp~4 z?BX0WOvO_n8RP~a&2FT7fZ{iC6s^b5d`wZwr9!&-e*vT)0(~X)0gl2GT++^6=C>1U zW?Fp1g6sS=*`D)xE!eyGvj$}KS5U{H>lnzixR6`;d8v%&yfqgLZnb4YWUd8u2Kdt? z(^ST>(#e>V^IdPjZh^IrTfwe|n$DwAJUmRf6uw_speL5U2KpsrpJBE&;ANtEGVyWY zAX@`XR`Y-xl&b+RluMzH$aV>;fxZarD510lyihKksgSRMV-Yx7Lbe80)vkeL7)d28 z3_4uNx_ZEzW`nb^vabC&GJ9`k8SkScJ^QA~dZ(0R-9_;3 zH!(zmf`@S|f;mTy=Wu)k^DZ>%4II}w4ZehSDP^4T9sxaX=2$OeM*V^79tUGv-Tp=k zR`5w=4g+$iU`>^#Cmf6+t>gRxHH3V&%RbJX6tvGDtdIPeK-SC z{vAxl3U_ia6}S~GQ?IXcJ_p+pSr`oeF)|z0a)8PqE7S5=Q$`BM5ZvH4juDWMU=ofM zIF>-cEF8UWX8|25KK43Rf}IAQ?jHg$2uuH4_MjD1k?bT4xJk8;vjaGplV`|f0*-=eYs{`ubSTtmmk4u;hq|4-O!BK zZ^gL^l@~+7gE&UtMIQ#)!PTu_CFM`YgGkY)pyiAv;54|p1I(>ZZgAzntiS+H2Up($ z`bxlbaOHt4&CBul2~i=gyn#j8PBbPo)a2tDtj<*9VXr9$5XEeo4Ep>7CtwL*Gj+`3fA zb1ouNgwPZT={0gkAaEFDKRN4>Q7Y|a(ofFL0ehk%NhWsRyI5$3DVN9)%l5A*77e1V zLEMi4B8iET><=n#CMX)JV-CpEEW1t zRRS=04)3k*lVI7#;! ztoOn@niBdVQ!#cM^t;;GSg`Dm?g&3haM$};B!t>Ohok))TOa!taW& zQ*Lw&VcG*cUjUiXM{#|c%9OR|9P2-~cu}}l2)Pu@#ljVLIW92&OmTY%7X~|bZb2eu zUda%c9kS$CMNs=cI~NlYpGJr&T;{^|OvY3!d(Th6<8*s1zJYj(9fYm(MDwt-J0O1# zK`mOGlnW12WY$I3VUI2~imC%BQ>qWF|0C7)A-XyYZ1XR|gFb{jMou1q+Ks=F5BoV7 zI|#tqT-+l#2j8y<{sIb4#!>bl4@5x03>-Vb^nqFm-hL@Qg||5so05!$MLm_M(wi@4 z%{Wa&8Ot&x;>K;z^8g_e&@l!o4N>7?O#6VfSry;51+X$g?gp|3%1k>e2a7B$en03P z%*8wN5H$m3Gz;>om=2;U-Z$9A&U_9f{2(aMxO*|oxlrL_E_rSSdl=C7srP~2%^DqSm6=?y%_P2&OhL-ICSh8goBh5OQ4a<6C-C2)rn}YT zL~@DnA`RR12!?>@^az6tsOfifNj?T%*5}gW;k-sD_#MQ7K!-s5+BL~bQ;}Jxbf23I z+TvRSVtRryJ3T>3m383b#qAlnJ&9vEA(uj>d(uR|aS^dN3#QF2eJ*ZIMh+t6Eg-Kz zEfr8U0-x@W()&SWV{zO8b1PJOHYzCp62@&$X8IH=ll9U#zA_h zWSGP2jNvmtlY5u$f!rTT#=Fq#XyzMSDE@_yIDdbdR3puj0uJNp_x*va29kDE6-WL- zN8KWOnvyLY=F+DNOz#8}7F}XGc6I)TI6pn8q9+O5Jfmcwi%<)Y6BCT~z;1Hh3s84T zDryR!asK{6OuPKnLazb#5#=~PYizS-i&NTAJbF9unH0_GP?6RWd$_cjl=GwY;@5&^ zcG$Zb>oe2*w-b^ zkA&yi3NS6IR^XiS@1cfE<6#wUX^Eso5?MyXU%T|<11*q1KGiHN| z8lIU`T2N0U{w5e0vW*xjL^6^=@n3JEW-&Iz-w=Thn8i{B3V1$O{BAMCjjz7fZ5 zFt({}bkSsBkX__lsYyv`V)nZ$6CEj2&q=w-x_$ zYs7aaWQQl1LPNohI6i@S8)_+3*${k=ev&~cRJ`eooEADb!sE?B_Zq1Q&g8jiF!w?k z?LPR}=;VaEl<|^w8$Kp{17wGN!_xZmoG&BoIs`g^So9S7Ab$3ZrN^WqYo+#Is67GL z4nVpJs*|vlDaLf-$SMyX+M(Lzg#R6~L$&B>)`aBHQ0;gelc2`UU8B-at#QXZhvF}N zj*k3{SPi<0*US&xEepiFoAat{m5N?`HpLBpUw-g`d7tE8ZX!nkWKtaX#@(7uXN+gF3(a$kWG9A%t$$u_^V;AfC@J|NhMx=oNp=W6HaqsQOfy} zZga=>7nP$*eZr@x`53Z&!dXtl7|;2Zn)V3|o&yWz`UDT<=#3NiLTR7S1A%T5O8W#4 z4eJZ}K4CZlhe*iw35%TWI(WVw^!MKF$wolM$JGB0rM6k^D& zUAon2ELF-GyL4p~gKa9^Ky`tvwXI1tuLPGimGOjEK-Su>gjp_!tZf6#dr;$zPBNL* z6#C56sgibTGyWFKTe66RHe=h%wl6YPhYF{;c6J<10lSSB-#U+Ml%BPUnT!jf$T}l= zPuzYR(y^3~SxC-=8Z10B2V-Nw3Ao}ht=;cpM69aBPJ6T8{BJvM;gI4{<)o#kyft((>l8byljk^Pid~r)QG> zCX6y>EIW5VSxZ7rN71QJyI#yoJd93TaW3l>9D?r+1pfmH_Q$af=0!OM;P?q<6Vy`h zvSaYsW*w@a!sRY)Zmr!dE|+xDOpc#MIR}vjQmIkS&d$5nPzIEVce27)9No&9M*Axq z>Ok6yRqiysZ}9U{-fQp1m5f9j=X*D%vE$$GNNNihi|$Xl_Jx{iyUZhVoR?|yUJJWb zHU;P;D7RJSWmqP4@%Yst?Rs@mJlZN-g3ux;*(%#1O`PY_F41q5-3|Co#guN9d1z=n zmkRl*018#>218w-Njaa{uyL?W zwiB7vB0?uLc7RL^z591lYVe-(8*JfE#rsJ{Zv!3%erPH)*;T7Co=auEFFwi3oC1EH zWExy>2ggBEnH-l`N!T(wVI3>8#HIZGZwjueH<59Ib}}J;8N4%se^tOxGJQ>*df>UY%dbm~zNI z)%}2tNXS^L{BxJ0sJ(`9)R?~(0I;^q)#>92=;o2Im}%y0zK9ICi>Kp za!)nCLJx;lF>t|TfR!q1ldQNy$oAh zE{mxQ53GKq`qTO00pNFok_T4bODn^3Y03HrR>uM!BZ1_B)z6)TF`naqko~1E$2u4E znUH;8^>LU-A^X6p7h&wug%>pV2Ua%{{<#G7!0Le4NFZb%SRD&92C@&VdTB;8Nb7;s zmsJvaVD)@3XG%zqIDY{1o*a5$wcvG5&_ky2PcG&853G7W%0JDQ%Z-19S{H=3h3o^X z9>r@t>f+r4t3!bvAh;e_^`I!_Tv??r$EpT;JfvUpk`bpjt|uZBdj=EBd&!8y>OC9o z9^&-pUNTZFyY&Vgm?}z6ivGPlcob2;KF!u{5`}8<= zH-y6`mvsdvoBRmOhr-=M{yh#$+zuRe_&4EXhlOvD6DYC6U0sU{jOScNae=C;pbvtq9i9hs4rJ}ni!f7Ey1X6UN%$=ikR2AjNdh5jhugz!2U$Dx z()tEuVuwemlw^m8fjLM*vcpv{*UBL~d>-am$TU9Gr99sby&vBuP4ktMK>FbH4+wt; zSv&Nous+_|VT-qTRX1eq(8D<&T}QotaC%pu{UH6U9eN-;0ChxUJ_mQpw?mI$7F`FM zv%`*lLBA5wq)Rp({VjNQ&adQJcZby;$71KNb*wL)zXn$SNcp-c%e7 zyUaC+k~R*YLDg@0py7b>&sh5>q)o{fC!v^{cK1WLj`N)N4vCi?z7bP-8q)=Na)VR3 z5`Wj#BZSkOtX9QHz$K|{LsxK0<&_R(Z(9E`m02gD^|5=?B$<-d&R^xXKGkGOj?Tk> zne#8K$iw?V=fXhS8Wpe6U%1d)!o(bdWiRZ8MQdy0G|c5Vs8m}V9>-vwVHMjNhxSk_ zAY{vTxJg0D_QustAMg4SZeW`?h12%N05H2jc6-CaScC!Qw>QQSJQlLs8|T5CBZsy( z?t!@#YAJZKy>XL^=`Xf7?sY+dS(_x{qWKsbJ{GcTT-U*@gbE*W$*aG;;X~Lj zaTtFDj@0ef3q{)A*o2brAiKTcfnhz7bbF)M`^@ekyMF2MY^&Fa!lqj5m(##bg6#Ij zjWDaA!WSI8{`Q6s35@6RQvJMAt?i93QSvEdw>La6tS6FgZ*2Pk@3MgG_C^KFNT~2# zmx_AZ8$JL7O)B%}-jZduc?ELIA-lcdVQgsuOSdZt}Lt%w>LHt{xxK` zH$0{wMJMgYXj|bWMXl|PW*^c)i?qGrMdFn6B7S>gSD^g_*Y<{ob2!W8*~9(z#u%WZ zAbxgx!{ds~N~POvZ=4O{%mkHeZ+uAL0lT{GjXMar8M50OpSXzF-k>vPdt+uFGNSE` z!VRp?0&A%NyS=dhpP7)|-e~XUe3>U;Wi=mZCoW?Ttc*uF(}2Dl53p5?k1xWf9_j_Te|f5?F~CM zwc8s$qS$yYow%>rV$_ZlkCrmG`IxQ}vP+pAUD^zp-o9;$aX z)D}vYGN&LgNkZvTriX_0h5SD)#upB2_)sHo_^T=u5 z_J+qssgyrk2=;=VC>3pQ+)cc9LUwz@ixkuoNw+uN1^YH+w>N@MSUZrgwl};mFXnVP z@bRVF8=b*-g6#IjfiOcLyS?E>=vV6s`|XVyu+t^1g^EjIE`|zsaqv0|7Iu5XhjD;| z+bY`|uk=M++Z)d#`jB*Kd*g&pIUE7C6v}RIe2eQd$Zl_VEup!UaBXiKDmB{P82TAg z7szgJcpNABg!9`QGYGGS?6U1Zm)gL1&Rc4_Y7o$y;A zoA`||U&^7xcl(OvYN+rFC+W90d`r>x#tau0rJOHmwl`|}p-O$iG}KIiY@aaOi5TO# zdVRu$V3$C-KEXqSdO~TRa4!OPODOFVyiizQ$oC2FBk-<-Y@cw7OIn>i!ESH(Fgi<6 zY;P2DE6Qze#9xzF$Zl_VEH`Vg>GsB6gbxr-+Z%IXW=lZZ8(x4rw*>t5#{GofEdgzB z`~>p@)KU<;y|KeLtcOB&d*f_;lb`4Ew>M7Q1ufd%m;`bqy&n>mAqB6@lOCScAqJp~Ckaz%SS@-XEmhDcIG8aJGP$+37fo zdbK+R=39;yOL%Vw@jC_87_4oyK7bc80YUb0$*R$Ai)=@z@PIlIYw6tXu=v|Dy=EeN zT7P7F_xv15T#7ODeOeKD(h=yB?+A zWlTe;sdha)FiJTu;&(uL1MLaf9gq<)hs&WIkhw4?L3Z=wMwsiN!r?Azzxko!HYLY6 zUrjv^ASFt-PH9eZ{u%;RB>vhHnd`i@Cvuj9yRVp~dm<%&cOXrSSEe9S66TGc?#cNV zHgNt*gNvWXEc5q5 zzMqdu+B_BJIDgnG>>Kvqv^xsa{az3LTF7?4Upu5Rp7T9+Iwbo9>_zL`|Xav_7bw)?+?V?5%=$>v2lj9l$OIe-RX1iQ{3I2j#dB z$0sl!K}F4`=d|TGudVfDUMGAgkcR&xPf#!r#||*P zTa%y})3bR%=Snuhu?pr|Ieta>|sfNc2__b@2lfP)@pnFDrZKw}$5OBTx9UUUPskMdNL=X$z? zgl)qh97sCUO!h?Q0LV6=aWG?{_G&W?u3N(fWvazH4!k(Y(iQpZ;&PC{KXs;*p zWIVF$DLii{bPZIb6EAzDT0Q5_Pp2cSH^IIR*?ZpaFu%#65mv{aSt)=T4{=(Od!Fg; zQ%G^BAd(YvIip{8#y?D&BT>6?Zy?RMh6`&Hkv zB=~+yV?KkH1WgXb6rm--oO^k18FX6EZbjSb=@mW3Pp_<;I=QkYWkyb~99P|aLRA&$ zqaNZ04lN1p<)v69zZ&FrUD2<{w5pmOBPUL+shmD_^yD5DRkLd*Oq<$mhweLf@7=S< z#HkgNXN;}vk@(`-W7?R%RaVq=pMZ4vimhwKI(+zq>6N3$9$GWHrqV`wTCnwsHnkwb zX7A_H*u8>Oofd4fqFpV(@O@@gR94kYoHjL4lc4J6Z}?%=mDB5J>a(Kvfuko*^}f55 z?=!24lva){ubefyqGs~!^3mnKfVMB6I;{pb3Fxyl$7Q=*-LQYFu~~^XO_jPtpq6(ubr1H-j?I?a7@C!mo+OSz*_{b(-pLkIi@y++$}Jk3 zh7DDYEgJ@hQL3$4R#liZi$jyfU8{_Tcc#eNpysf;QU^3Vu;o^H9me4)z*b#O?GZg}uP~x+xN@Pw zKe7Y4+`VDM&h}9*54J{)Ioc)NI@Q4(b0!^X)JRgt)~X0f@VGuUnvsbKb2QD@pU|Ug zN1jAH1}RgKUlRjb^`}Q4Yr(E8)r}mT9>eGcLI$7&E>hJ$=(cMK&RoPfJ4H z6YI*W0F3!tU39MgC)G~15^_%9Dr<7xu)6gz#kE7y;M?KUkjhk?(`py2+D=tA@ZHN) zTfoPs5s=-a1g6_tBBfT{om%j=R?A*$^1CJ1K4#=wrW(=AS|(|`n-h}QrZzgD;Z`)z zqzsIi1u`{iS-|Z4LR5hmb7GoMTikhNbW+*@kcF*m!<&=b2whv9n|27K>f~K?!$gM~ z?z~i~t!!_r^Ml&{Nx7WDxo|c81^ib{pBf}R!rwJeI4#(TF@rIu*AY!_w&w=aj%3cL z6YNXi%sT#wm~Z`OxzJp{Va(ZoDWN#dv11yw=5xX9msMuHZJcNGAk_JU zf*`+ny2@P$T6fl3Rc4UrZ!Tm|9dXMBq%A;3#B2i=4GN6 zSt)<#_V;&VmO6PAg73>N4z}V3ZpULvnICXn!pl}QR`BjiT}@egP|7azd8FD0`QhK? zzC=jgAbU?p%Vb%tv`=m1t8IC$5=i=$6<&?it%6;e+{mt|l`17;B{h{BU$~;a68YS% zwN`;&MNl%n$*ZrcEq*N7O=G&oc`1%-gLF(mzT|Tqnct89t3dtLxX_q?+C11pU&H(%Au;?yRHf%m9-U@~&I} zS(-{Wn?`VVouFE=-;)|jGKRhvWl1M}Us6ADQlN_C{-C+-Umgh3j*YmIj_tuZk<`5% zJd_K!HJ8^-KJ0=tGDRz9 zJP{NGyEoK)AeX-<9V+or8Tx+isl0kH=4tODC&oNeN6OD)jCrw&Woo#5zq)Vl{9{1En`(?$y9N3W? zI$rVZ39ZJwibmh}4a#rRuMx0cwobqFy6kQViOxnPo`dNt<#T<=5ty>0*VK^>e5 zOey^^=WlIS{<4gup>N1lnGLl~(wL8OLN?N5u<&uPqa7xGk`p2{3Hvm#BLI|W1n`+} z1Gp95rom*A{keS8p^x|dBIs+ieyJkO-LQalS?_a%WiRpEI_i95QE8mVgEYFih(Es$F4sdPRd7M*#%$Q4@C!pb^mf*r zOp7?*7}jYIl*8iC&FDzYl2D62jPb%;=abAYE(|*{{kkaBCf(8`Ev^>S+7#o(xfGIy z^iF$8Ua<>ysoX`rTI6$CxT`I;%fsAah%w82hHPamm&;|wb&xB3UTlw$jM=V0c|&&# zPSU-S6l?9~$|Tyn+w)bjk0RO6)wV7MWi|G=MplvLOmmoPbNM1ENq2Z%E>pyw^arb~ zJ;_ehni+iA+Orw8nB>NPeNG#`epKvloHx`7P403xCUxohv1HV0%<2T2SHpGtmz(M! z>W1H(i_PY&Zul*A!jpEu=q==yW7o>oOYC_A8){O-1Pnxx?#8?F$Ihm5(`l~x~~4!0inr5*~tKbK?_re^v; ztuhwyK?n4stcOx}D)g|;PM=n_y2rIFKljSE;coIsm`-1fd6WnuraZc5Kj!3;ru%q^ zmwCdePi`l*h73uLok^7WLMw-at@8dX`0AGx{+UlGii zdjfma!t!$UTDX;|H;a57&w7jfZy=i7|B}qQINr1x{j}#TT=^3b@Bg+H&Z~-dTv@P$ zpbWj6hC8+ISr0$$cAlm$o1hV^80=mIzcni58faG(3i5YwjNMS0KB33>ii&PE)22KyQ|1m-TM|h91m1Aa%|6heBPaLB{@9|f; zrJ&tbp2@F-S`Jqv`E^u1o4U#OP;K?lu9mB_|Gw<1s%s`ES@`ek+3!ZO0-C>Th{+B2 z`)+m{Q6Bl5c&TT-Z9!I-_vh|+WkxE9XNg3q{?(x(QB@=k11f9B5k+IgAL4z zNx5Cj{8&a$o;CrQaY;Hg-{)Zpm_jBA2kkrdFRY zv8H0e=rNNkbICq%+SnO6_g)9t-P~#)K4x_F#EOyCHPa_f9pAmCqC#n*-bPMjcUZ|C z37~T1xEWI`h+KiS1gsAcP9Iq_eKdxWK&orTj+`>1rgB!|9A7hGdbhEa<3`VztX*WD zYf}U>*{M}&NTT|OHJj`zuL??-wr|)>syIQ#FHUXLm7xvj+O=ZZ z)NvEXBSMi&1WUIevc$=iV)JvK)U<8sNi4Pxf@R#D*vH736Dv=!8YWDiHg#GqEfc57 zH&L%f=w#ftQ;$p zO{j~!X5ti^5qZ}!!9=q}WxZIVP8lR(RcNXlH*qTZrd7F!sGcdbguFtYHnn2L^y!sT zD^%*(Buz2z=qg$hM=tVwlC*RiH+h;YDA6eM=r*Rha`g0y35j3-F*7Dk zt|8jpxrLh(h~6h?R4R!8(+-uVtfo|Q9%gLnLuis8z+)<9qxg)itge`z4}qUJ zrD`&vXqY^0>Ui=uy|T)|$1p0&N7u}#np~NBNP_l~x|ML-Y3KV?&8`?-Rjr=fCIDlv zs2s!XCUNStj?BWjQU^s>HQQFM$9V^lR?V)QK7HDBl_62mThK^c!TAn~p!~a7qIYEV zjH;xJlkUouC~(9+^sW`t z#^#C+ui9mahe|)cXvE{OYNnhnOcdsL3prh>vubmu&qkR*zNqD=I-L2r}| z0A!*m!3sJ04>n;zkQrgG{q428y$;R<*|>}(#Dg=V;top7ZgGdUaWK9-?$9kRyFLzj zmzQ_XM0bZM$`%yI&7=S4Nd*o<5> zj^hI}$HWbiWL_8-i^&WrXpHovAa35wm8udEXUKYzBZt`4rn2}*z_-tXZ=Tg(oXv~_ zUJ^AR%RNw4oH;)xq5Vg6$GbRF8n?^@ulI*LGH#M-#Q*AAKDKmUp1V4ZIhRX8&ko4* zUKv}?wwM0y+T)7JTiz`v{;$TSXgB zPO3C+67f8Pt5h^@9jyrm4;}%YR#5(;YX$p)$i|J6+BiB# z0*^c<8<%E!#_@ELxTCY%nJT2{@0d+$+N&iWeNBraC$m<>rQ=oWG_cu4<=qR)r2Ce6 zuS@qpytk$mshk$ZTkGEtT?o`ailnvupIB4ioP*@&Dtg^ZuA;}XTpn#zp14+1 zEMsg3lC)PccXTEiXA4BO^k2pGkdN!9+Hnn5T%DtxlDG!{Wn6FnpK^7pk15xM+I$?O znA%6Zl9&$qtC$L!WTMARnz5~DQ<-Dq)-varaqInkTXkP}j#jJ3%f?$}f*RS#Rx-b4 z6c)ZK@E8*pZ()t?w))1lPNk4JLak4tL+x!wg}G0Uf-SJr=r=3Z zmT?rF7MBC)7;QeW1~;N-#Dn9AjyyVVP5{#^6MY{^_mOcIb+~}JkNtXLfRZkm0|}@p zXcZSmKM-;6Ocvj*q6=o4f=~yNgwR0rwm!@0tk~*ke|cg?30@F}*B4!S2A#uqfrL|dliSA69EX!sY|4!rGPl2nq3P6=1jLD)*l zFfO2FP<^_W-9h@2?t$9u54d3_YDo7G4Wh+lnyFSZQ4i|d-|ym9s&$H6?dPGn+%k02 zi=L$k(v>`&_}_&;zUQWGDaf>l%l2gyKn$Rl&KnVzX5myV6i9oYD}ozGpBg$NbySzn z3-BP!tB!R7$B}0lP3?hCzfjouVG$lnt3gT!zcE!L+Stt`TxS&aN6Gm4|y^-44gX7Yu zB(N6$U0sVW%|r`j^35~49dnC(og7uulSZZ8H9UJeN-RY~pdZ`~GfKK8#y1#Kv_w`t zK2xMYTzPa0n3Cv~lclXZ6WzvujLh{e zYI^~9x~LTrxW|teQOmhta6t=o0uNG1VRLGLP@FHS2rUq8G?o za|~e3$qNPx+98PBX0lc*9luSPyuas+=zc*^(1P~gtYzFQ6BLzOUD4Yo2hq_4;1@jy zLdxzs`MwcJLVl(z2wN%a1-L>+j@$*ZF=lX{%{Il5iS|$?>0wE3Ou~p(Bj`l^Z^a92 zIkkv3Vxr_pwYA!Yl}1-$UU)_uP7GG8pyRm++ityjT--AH=7jS5lFGX;P+w!m3gx!$ zq80P{M?X^{+h^1Oqqk13NgCAdG|6ZcS+Gt1?tp<&y*|QPmh9wLgNZS^KHzC7FP8qY zM199`WI)Z7fgf$Yt zA<5XeyGGA;jJxPH*EpU`#wq#b)3kQ^Z1Ux^@P8_wZKF-Td}ij#hv>Go2||vsAo_sU$2C=6KovaQomq};=a+HWUQEm7A*~a)M=Z;(hl#c{i6 zvn_x|nds3ut937^vAz#P!J?!%@0}Z{6Oi3$u7;Uxpf>VvthUi8$&X+SeWMd4xPA2U z$=x-tX_7BY#@|;}Hp{FYQ4l4zwDxspOY?PC(FlXP9A_@RpyT=a52WLaR05MpYBn{4b39rwxn|4)$?{y@R4PN^Y;@q()HZ#;>{=l&8c!k=UuKP$4Q}7F@pyJFMB>f57 zfznIYEx`rH9F4U|D!>+QS zICDRtjiNU(R~B$G_AaBfn|Dqg+`k}a2fT!-O;bE&P*4mr2m}yt49K7& zDJX(K0YN}4gP;NhL0|JwTu$w_jOw!rm)ADeylUVH5|{MUaTzS=WD)Aze4 zjVIj_1;mOBi*XcFa)sYk~)g3gqM(+DT5FJdj8c}H-I!`c<=*o`(U0vipJHYy$ zNB1W!ja!`Br9txO0my3T&W)dOnquZu&t66HFpgBrW_-ae7WY!zj1HUJ{mZmWy!?ae z^ngy5B>nrl3(R174$oeB256}G4IBLr46N%M`7GETZn=I|343u<%wC*Qf+;0XPR+!i^m6ey*)K?WBdyEKOnCHcQITvPS0G=m zbkAR*ON1Rouc0N|#=YP3?ay#ziqs+q5>GJ)4_tPpVTV}Xlj&s8VnxikSFqHK(v)dz z&$q&-GaMxV@ccya^cA4~O77H#;rr+)-DvzQ?DYm?d2?`qu)tvX*NW#|>v_`hEbqEf zmUn_=kP$fDNUOUfkmlR>dcwpCIo&mRPIrJcXXE+~7l9I**;mxYy$p&sVw2D!j`=BM zw-AOn(lkScl=K@^9vqZi!&rK3e;(d|`{TD~eMFLU+Zbh%HN_jmnM05<*oeiDiuU9a zMSJqsu}dFWlRkoh0T?`KMi3RN9*#MfC%^}u7YVmo74CwPXtN{u`hA-b zQ=mTRWE^$wJWtrYb>=Zp>qkY6rzEx+p6+U@_runLmL`gRoC*C{T)Y(RSQ#{G z@f`@wn*W6kT*nXqM17-pk?=i&7lZ!A91DJMf;{|5_*ey2l}BXaZLc}f*L=mSEb z&G!rCvj@4D2K;?2??FH#hcO|nxy;n}VRVtUnEBj>K%FO~$9h7#U(m)^jfMVAs?)Hz zRF?%T&M;1)Gb}1XwSNgxJ210pzQaRe)?$h_c{_LLI&JZTJ@CUQOJb45^VWm)UdEo= zNH4ZI*0}#F5{^3zcLBMk)f=sOXx}<1nC^K;=S>T=Z37fz+!|I zk0&v2!Y8_BX=pamWYpXhp zn;bk}4>M(0j}C1b6L}BI7`X)_&D%JXv{Z~9qIr?s-e;KemG1O_M91afs3YnfEDz|>wl|R0YsS*@C)lz287~13k#TsdIP23 zR}JW+1~!+j$gS3_kLqW=+c?^^NREJKC`&Zp6+(lcXyp0E;LRxN0gH4dn(o)3%$QwW zTF6$*!g>s=au+{p6VIQWC>Q|62=sJvt}=aB#A%7#lSb>t(<6&E+5KN3(Pr1#U)=M! zb~(r4+6PYCtnB$QLZ8@@y@s5F!Q0rMz)l2*fZ9`Pjr*2Lc&fVKwv8pvRi|mA>i4MT*LDKxJlw z>m)%!w@>9O07?9ndDz~E&~1$s4S1}^zXScQoxnWg$;1QYOiVm+SM!H<#U}0I-&L1L z!TB!D)|0KCp!NJ7dNIGssAv2-PtcYe+F!jXuN`d~I!1scB5b-9j$ol|BVn)-Lz8S~ zjZF{|TqCx4KZj~5)Gb;{w`d&y6Q-m|w(Qs%dGKTL{@`l?8!0KeB@=G4PuOx{xXEr| z%YM2Ju}ov0_EAM_GF>?NtAbIX)<}CS3-h!cd@n|QQfpgWLjKB zwu`b^O`}YD8euX~6SaAmQCg3qsz+Z5*nC#JseEqlU(=}f$f&(;KC2E#35K161zFgPmQ zab_4C(iZLratNjYo!)~?UOK!pC9F@+4?{^ROR8tO2djdnW(G9H{lX-w3mMh|iCv8m z2M^h-*3|`3f0T*ZBXNjg8z;+K%EmquB+|%~vrz|Vm}P_Qb_i^c&sJyJN%o6{cQa;O_12lOzZiCj!p_M z#K%=@CFE$IBQ1WR^_3WmG~@^^;Vdl~BW0khXropO=s175R?8T#D}-;o$n+qV65D(* zJ-ybtlY{<|@US6Vlb;NvkUtsIfwA4=M?rih@@QF56gjo35xZXJj+eAHl89Eo&pHk_ zGAcmjTJx~8o!;$~aEnibyS;~g=ZA7Hk~>42{k=`w)YYbKnI$Bw_HE%N=1XKBzgY8@ zwZnLedF7~+mR}#)+I?YpEa$DmUY}P`$|W(7DWFZ@`Mc!<7`4Gbh(l*CU&oN; z=d!fp@O@W?Q;!QLaOU3Y4e#FyC+J{s8ujPB(XrT@8~0nevL_7oLf@Y3d>Ec`V3hlB zKw6}=2^1?S4`A_>bR{CU>k8cDtf(_rjogs)#UAF>Gfi#xHNa}|m-6+A@3K3&c4>hw zd@82i|A8Sq+T@u1SMrv|BQddQub9T)owULxz(}HXY;QwY)6CGG13HW=Gre*>YUK_a zq-yuK)l&ZAm)cZl!GO5-~SXU`3TZNk}a4>x@$y_^uvHsrHdow1bcz@-lL zMC@x6(zIuxm)u7_5cp8MTezl6GdU(s#PI~Oe+4RGeLh1*** zPuY6>K8EcZMZ7%WxYlca68~lp=c?^Cpi-YTb`}Fr-wBym`%g z=q(I4i(P_W0w0zN4x+WV4Hh@eyF6TxEP;ILY*00DZ;7%}=qqwRiCqnTvK)xBJ6EzG zxOmGe{V7Pad4;4g(wwDr?8LgMCoq!!35pOHL!G{Y-#F`<#*}(p$~+S{0aa+y?t>-P z9U;$lln~azmADpI2YIcoI=Dq0011=4g5clEysyWHuCXCWJ8grtbMzgJ)@xW{5C@Ge z*y0yzey`F!o-7yT_%|=|PpY`GN z1MTOYVf*56OLPSCE=3f26aOd6my^t2jzI%rCZ_9=|w!(Ac0;k)1L@fZ{QmG~u3PJ3# z2xT=Yow{%$G-vOy9>!!3K2yJ49#9tjAwDa2z;Gh{0aIH_7iI8JFSLLIU{7yGH{7{K zL2(NBPi!=1_%++ylF=2q^oqCu$(ZU66Gn)Q#^Jo(!utINyxw7II%PCcfK!Ceuu0np^ z3f5x2f4H@@G=x*c;J2aM^c{Qk?;zb5EzkzznZ@ZbHK^1SQv1d%i&y&&s|ICgHSTb2 zo^+{x?>-UZ)?1KH2CYz6+_?=%S|JE|)A0JGPPPM^k7TlDw}l-r;k?*{!H8Sk4jON* zdw;yM^NJi+qfUn#mL5lljG5}1!L|%KsG$Q5iXP%I7589gH_Y^k3){h+W@0CuEky8= z>XKLo!$RQ3$0f)$g?mcDU1Rb-Jjr(uf6XmjVuV}`AOtuW%he!dt_CT=o7@&sZWXGh z20SS{P5~6~YI6hKc_Hz)g?cL3t-#V(9h|R@n>Fo{3t2 z8XNXwRDzNFgi%UW(=u!gT1D>o=E7_-dMvw;!-8f5g@==CcEkW z6mbW3pvHaE0mj9r-xl`|UW72`NseaOGoH3Ura*R{ut8#<4u)$`clySF3{`uUZj$X{ zF?4#2CmJ7H9LBZC^2Tu<9+>M6?xYpDb$p7OIC5(n*LirN(p$L$NmCHJ2hMVcosK%q zaxzWs9fLM0w;qg@2yLVgBV(?lvJxkCiv z3o8L}d#~C3l9Od;&>M4xpM(rXHf@@RsOCMY{8>+)TziyY%YG-FLSN7#x-}-H*GBt# zw(Anu*i!z;!VMEIRILLpP+l%yp}E0ku|;**0~L9i@INDu!Mcjau#O?v7K>KKP`l8dTSz88v9HgvIAyh#BBV0sEMzhijiG z`rogAIClCYy(ez7y6>dZv~affeI%U8H403Bw#DtgD4PZESU)4fuSzbD$C$?kY~n&zlEhpHPUfiE;*CTU)nK}-HhHH%RjeIz+5 z7cC7MO^%YT5IHKA%uuink<41%3CI8@xP3k#hG~%G*J@l~7+qoA^%*AyMTy)hVM;Mv zTO#=ZxEnaC!G10CW0VwXV;wi~&PiCBxVyRgJ7+>p$vL9d6nEzD=@j7I`s=EGLAoch zB?RGA5U2DPKxG2t0OqyB0VZD?lgG+vyvUG9^_ZxeuR+0MWjL=JR>OO&Xl8!vwDCK- z%X<)R1fE-Aw>FG+#fvdUd(K39j~)Cp4P%^T$74Ds{B&Etc)-;tObW4jF`UpRpiZO} z?@x8#1?3c)$krQ&^I5AG;#QZB*y>N#YKFTa@vu%}{tCf=X|U5>@>5E$j*iE`hWbd% zST9?Kg{zkhwgAAaE?5O11VrgRCXDg~ua)Q|1PzZG1{g8U(scetTyo&%;(lzF=qHxw za~r*@QgJ07Rjpf%li@*K(Gt+U{86Uy_qwBn&THLI`FF-4oOcN2RQFSV7quR@8{8^b zFi&WR@RRBfcP!lN)BWL;AJh7ZqisdABm=mM_5>{@M3fKA(L zZI`rER!>2nblfU6ov78R=C@_eFHmLB+Yq0$$do1)FAnzMB^VX_DB0taZT#%$ml?p& zZRiW@FHH8q#v^q@k=vY5B`<4~MG}|JN*qP|cYwTt*@!bK5pw-RhC97-t{dja<`5}0 zq>nI7cPzqck<8A=mG z&1nCEo#wdlNEBpjtwkL^rX8zw*H)+7heD*hlbG&TJYkV^=+P8&m7lB}EqT1vVE=+V z0m_*u1}D`dR%tVLU$tUWIrF1|M$ri^2N~RxLuFQy3(NDpGH_s!Si70rE?%8EQ{{aSt?)2(xCWBlo!wF3G4S$YF(jSG~(UiNdGR&|^D1y^* zCUN@;LB0ZBwl)qymJ0kyJ00`kn%mWryKUKkcd7qrYM_GPWOKK7U!@a;W<1%$c!rD3 zsC84t`zapTZKe=n*0!;?*j=BeMe!41IN!Zm9aPteNPZJHshXzK1Vs_did zadtnC%J7K5W#!10N04n<7YB$owu!_MXP ztlcJ}hwR0SaW0s4<3`}h??&JH04x(`bxL5?RxDYtJj|9^FibM9@q*3q;PoinM&;lY zhI8FB@oJseO_1*Rk9V2Xn(OWcA`QImU-r7L*5;f+axZAQUkbYnI8N-A;4s&{?0m+A zZ*X+g?9LwOT;#qOX6(3*Z9OYIt{g;Kz5==-nZi&gvvrHl<^9V>I+q(A%s`Vj8k$7L zX4Io2EG^-|{`gUhXoCWjy1p6F%K+*zJST_8SFgpo6qAoJFO$#{O5+W_KDQ+6^XMCD zeM~$Y0MUp^85;#GMd$<|SB#?8gDAf7P&~M4M|S}NOif>PYgp~tb74a++*BfWBbY>v zUoGw@uFE5_uR;az$BEor{ELyBIJ!bKj_eoi+voyPrWn#@;J=!Xi&iD7!Tpu`HKM?O z;oQ$e^nC}vq^oOF>Imr|Ifs$^>M|+XYTc97UGTKH+DRJ4f0i_dFb&)BLj!sqWGf5P z54%_Ma69+F@($nm?lL&lIeeleWY^&j*JPr{HN0$Ig4O}N`mKC(iv089bAOVYGh)7M zPkp4$eP`HCGzQboB!e)k`pBK$z_iutrM}n6?pio*|D(;AeH z+QfNYA7$Vq&aiJ>vJt<5#=^dFx*w+YTAJ=;vq#6|VfbP9b(USI>gP+>i}f!<<38+8 zLS`{keqMq5my^qdXdsI8+XW@kM&_snNd&fu7b0f}I5zlnZSF|xRqUssND$InvPm9*GQ`?uB6YH zm~sq(q>BjKK{(6Z4JB2QzR$tHhP%3YpQG<{T?`IC6n(eF{S`r1XyD2*iQm&8tv|$x zxY=FbgF(HRvz2j+uM$huqX(_-a&o|M113)C|57-r!emVDGXgz9#vG+EQ&vBoHTkZt zS`lC23E1=#GJp zH^TjmBBB0`L%2c7#Ja#l%rYo~BfX5Afuqphud=4QvQD@H?n)$;c})>J@-CFjySSDv zGK+s)Syhf?bsF4B#s||oiCEpA;8=Iu*SI9&H>SHy1&}+s*4}XYBg41cAI`r&+~NLk z=ljF<2g4ohdMD*vu89t}zD_Lk#YkW24)A7fY$ob=7xQ@D!5uEA=EE_#3d|oiAUg>| zY~_61hR{g_tio3Dt{<}^FL@Juk8*OV`wOdA>XOW{+_?I$LEM_*fF!0ErSbZ)Y@8*O zGpL!_qDe~f#OyvjJPRiigw*nRr&67`R*Z^aM(kbS$cEyV96W~u{GUI2YRk$@9fjvqU@bTXPtAK&*0flvwFAMJ~h)FCakfSyE(|lZnpEy zn8a{uh`BbmD)(GKz=y4zknXNQl-7N~@HrU)Wub^oP$OJO0Nsj6HLVVBe#0_GU{H^} zjqse5$>X0ez(S9&jtM=6;cRz%teyN$_ZUTg>;TusNbKec_2IMKcN2Z(pYba4>Lp=K zQ2;X9yQ@uC2>Sd-Ri0#jpe zfh(b$=H>J)>O+82Uqg3+u{jrX>fpN5G#ONIS5HV!jecHw|jal9ki{x`>x%$d^?Zz;2T+H z0?KJ0kN0sXbs^b3vHZjP)5v1(Guv>Xu>~a3)5aeaWh=Iibjlf#@VIdw3tQ{JzjV^LepKj_zU9dOA~T#?)-;!->V^ z5^CHJZ3(Tu0E!9XWR=V&aAY#kUmL0X=>wOfv8j$8V$gDp{N`E*m`s0H>{e8<5EZm`h8**w;c!BqQTH#+-i7~9UfJ%EWK1aT#WvkKU z2+-=2JbqcHAwDL5>*-^K0FUF6n@t3O0#qJ|W8hJ+K{|%vXW5$=MBDH8;s3|3;Ph-l z*mq;NLq|2*clY=NWu4o>oqtpg)Uzp$Mrc2XXPly_OK_0VOrSw=w}SQB(6DHAiJ1A3 z{k+kBV$yQY;wgr=zcs%aIQW&}%<1azDw@uq7nQC_St`0oyzm(GLE_Gd=9lD6=k$Bh z1gqTADFw`P2#mx{^15DF$@&g?Au-3P`PcdEXArfc=1hk^7`eSWd6RUYtV1nYidh%s zY|39qtH)XH&qwAm40w0T?$EkVxH~3s+DC3>Jhna?n|Fp{Y+zbRb%>;%S)q@dD-* zS_+K(0XB!C;sdN>@8bw3;%g@7TcL*|t)dZCWk8OnHxgo#;wF*Y6M-O0+;Oc*>n~^3$V}{`3Gl62nycQ_r zbmyvP5f6pfj{UYH+-5*~uMJ`lY|R|E zdU?=ihvq~(G;`d`9GZSxX{s%(qs?aS8Y=(9r0Zl-9K|SSrx@j&CX}NXR-HA+jPT)yT>nv8Z!pF=V?#l#UL$Y1KfIA}kT#(b6htZk+<){%Z zpb|kQT+k72N4gB?Bd6cXEBk^agAO2E?wAZ<2dnKfk&bU5MV;DBhS3}H9s7*=oo`J| z3gO2#j~;JVesa)b@5V!qedW=^AoPeOWkmWl2tD>nMr-IB6MEdYBJ|kl_wz~!9q6!Q zfMHE_&jf@Om&Ni!uv~g=DH$k6a(D#Wu^WL-51J=d5~0PFJwwo9e2~GDXpG6ybsOP_ z01+_o#78QDArWuP1VIT>ohZ&hSyPm_ur^xEzs&$z29Vj^Z7sUX^XtH+`RDD`TZl>Q zUtuN3WGNwgB{z-`5KdejH7@2qO3qT{Rs3&iKy0=yNV!hI7F`i*oZdLqlHSdOXdMJIjuchHx>f~)WWkMj;LOgqQBqqLE`7NuJ0I)jx zl>S}p-`vBlOaDh-b2# z;C+bd;$EJ#SW3v|6*N3ji>WBEi%MEFx`#CjaD#A37+r?zJQ;jaToT~xcO{GtRN@@N z;@_Ci$HSf7gGc!&aLMw&X7_5Q!A}#DS^+-|yu9&<88*Dfs#T1oZy83?vV`r9Y(mF4 zAmXjd61ERR*m5*@9hhw4v-Kcs5+FAFgz?#jRLW;7$bw`-UWWq?fUrnS)R6=Xz~@y` ze9ge^Cx{P{@oz%Z9463p%{(F^d)F8d#w|KCoEl!(~WZSJIlngbHh2bk<&}yS_ z2fmRhx0h^gTZ(nobkHQwGF4$eIBPSdcSO)hiJU%G)oB#L)%0?z8^i6zI|k&#B6fUI zf4IGpKx|9tb4fPayW7a{$>AkmMv9~eTJ#!}bEWnd+Z3__^RUq^k`^FEtHnY1_VG;J&25?r``=oWYSX& za%Xg`-5BTJYZf{yVx5=7nuW?(=XQooh@WC8%4s+Z6T~X04M80jkBvH3q=n)PP=DP! zDoFwLofTO)x7luv(z?!aJ9hDW4^vTp!=i62k@y^VH65~McM8<9o&+VlGsB&OCQ&zk z7oZ@t%*$s(=H@__XDDB?oQ;}|-0=p_+eii|Cyi!8MDg&l7x01NyR)gP%<~1RVN=7k zGCugaL_Bj3iQY!HHL9v?*!CW-54RSSnz2K@xvINLmD2=gPBD`(TuqzfeX0gH(J47m zcMT}qpDe#K-O*}%H9vxIzf8ElWYklXaJ1Gm?2S>=0gRGm`eaA3t%wGVa0}88*Fz>T zUzTti1u{H{{>zS|M3SRx?xi@UC})q=^l7w324DcZ3@&@O7P1iY$6g-$C5rM>!Y z%5|FSrY^VVOzx$4a{dD?P;DhBRA3Z$yn755hr~wNk9e9CuImND2tCF|$!6Zbbs7d60Y90^u?nldW7#=AFZX_w6?Okn~e)Ect%*vAXWmKX|}w7N_v8%c64(w_NE7^Ox|Gn718<-7TTr$_v9R zu5n$-vctT{wysgyLg~WK&2D+ib#S?UAgu8RyO)Xox)d_9TM#b)$W#ILNV$>c#Sdlu zP933ta;11l@?Y8XVe;UhNKLkl?9p3(SpEQ$@C()2#1*U$ZjILlJcUNEQGOlb$@JL$ z93Ys8x5<_GoXfLfaU=JqV_&uVWEWAYlIS{rlg8oJ9}cG<%SYys7_JMr(G#j>#C`}@ zS9cZME^NVgKEbMLgwYYE84XrusjIp53`Dep7~~Yo$(Ia~3PvO^pG|hFpP;Xqq7llv zElDny$_?~Z&rym{!<6635TW{%;R70g{>xd5}mC{(sGq6Qt}iWKRuq0%X|YO zB4)i~VN8jn`vfsZ9y7_3;NB`#!)}s*<-UXxl!{yow;mtx zK7q2R)5|?w85;BbS52VBV*@OJ-q&E)!?dM`%ips=m1B+E4aNt?2K$F!2z)6f>r+rbjD7tG#L?VB?QbRF{#3@TvBTD`7 zy)jw>BC~j8IY}BdW5q0tG-F%?D8M5TiHzswK~n%MuNWNSl_q=_7eUMGkqSacIbC48 zAJn7fN-~EdFNX$WnI3RyQd7masr;+90$_{63PeF?ArEfB8&Up+&2A=qO>t8aVJ9hb zMWlRLguKHT16$;Nuw1SKLVdTVIG!=7m3EuFQqmyl(WQzC`z}WL>F)N!QMQhzxAJ!9Tt%X4;+9jH@~qQHDfOu; zCb(kP|5i1edb__cRcR4j&1fQ#AaP7+?!N=un+E=R2_DGsFT3>sJbGea^`5<>VE$wRmT-|khATZRDE2M`I}VT zOXhD#%%k)HX&5P4{ zqm3*nn-psmH<E3P0 zfPH)wD&GcoxQcO3YHwpzP|=;0Qi|_7718D=!E+{d&r>3cd}<3%awLsd`6ugZO?Q@w znCSH=MN~G5==G-h)FbIkdC{vf^(o@Rzqa}mLH9hD_8IbtW6e}1Zw;?V)oPo(RbG)= zBARh@EZ|o90NEv_q+jH|yR1Z!D$L5c_g1B^IZT(*sQwB}pe+AF%)i~rBjy%lEn(#_ z`v5&RU|Ay5rsbt?h||&YM!Wj&=vWOh#_jzS`Ox(VqhQTMZY;sOBba%JN+R}=b31%s zBa>&lGZ7@!?h8j#b&6}${U}++=ih=|MA4#C-I*zh zVwUy2yQn|bz7ipy*S_wy5dxLu*V0v?UyE$y1sxfI7o+J5{}r6=Hcq@}I^0L8&%)qk zZ$!Ld3yrbVXC$qVT#Wed)oh(Ssi6s8iUobRDV~g z#Dh&xb!i91k+@C0$N zw?XcTl*aPAHzpNBxiP-O8IEP$#A+Ors#Z<2YFbixsd`T|fvvydBG#aKODM@SjHH*^ zMvEx82tOIEb2-zELdy@{E*7$^Bv<1m&~q865Bexvz|!`@-%oWhJnZY&IytEfV_?S; z2qTWyK~HeHG5<=^7%pJ2_3ruQS=I7hfM=A(?~cf@qa|7S@Q{so8W2itd6JZcg;aB5 z_s^so?BR{>MX2NgGF>;h6H{G2#p9jJ<9a6`Da_%?D<}I`B=T7%$a|M3*^X&Pp;r2B zaJRvL&@Zv1e6qoPe_7n;Z>r)xVVEMO5hINN z!OiA3SY4E^D~;FgPh`}`zc`964|lM?$?s#$Q5qLo4M0z(h1>WBN;jk{_jGrsK55ry zxsK>~3#NM9@+8?ef0KOE@)d~RN)zmLT`I7!4N6H{bf${JD1hvdWl|$;>b_OoffMpn zcV%~%3b!-qHB3^GTz6TnGwb^Cr0bVp`@~Pta!WO2G}UQU=&r`;hNikAi8i6#%AlYu z2>Roa3<}jr4g_Ae2lucdo9pUje`8ngFz#BYnW)>=wS3N5(=&Sd;1OBVqWR6kNjxr* zeN#_bO0Uv2pW$wa=Z}}5&FV&*RhnQ_YJG-Vem30xXj_jTFU#1Zu46qc<jbBXg7S@^)R)gHP_&Ra^d^U3KYRyY98?N{VmtCTO{5x+iU)%z6JZL4inKv%5lU za>i~TGN@ZthQ^p1D+>PDA07HdG?KG?x1BG8!2@Qg19NsY`t#CBeSF9@V8B z#W=jNByw*+kLp6SN97*XLWMgy*57i+F!A8W2s=peVlkl;nzs$F8zM$j*Z-Qtkr2G%>)w|yEyYaE_IeVV?R^ZB1R2~_W+9OnqD3m9FuhO-Jehsfi{j)g zC5POTpCLIO2nUC%T^7ER8Cy5~Jz3IN@HYV?0~7ooAJ(KiICDc0eR==f{u86b9q;GGgER9?^Z2;Nx_$6t?%2;QlT z)Ra*~TPATuXnC0SD(k^6zLnD!QIvZ~Xzc08Ulh>8b!=`{Y4H^GBVJW~kXT|m^ldR& z`MfBEH$IbQOmTEUR3C1=e}~u}>5L~^{8xj8Z~EF7>cgGOX$(I^<8-UuxAz)o&i)-W z5DQ)QG56(I=6b1{AXwwf&oiYl1edHd#u4|eAd!W=OVn<`MF%1v86tAW>SMYz@uTrH z+Sa*K^O*cOQZ3)by{RqWW`gsCWr*Ml>yNcm_qB>>led#_BEC*J(346+5zxy^%_QWF zyuE$WqYrQCrITDFgA%L}l3F2v1q^QQ0Mc-Kb^?499?ff31mCDcIIf7;kN4+_2 zaPV#}i)_eA+6dLAEefFw{L#@FP>^6o8=D z7OX<}>tfo??qtt25Jnl~V#W+kVtimG^x?rwoSHbEt=us#kcZbacN~2-$9k))>|5S{ zt{ijB@_fbTg<8%v_w}W_BC@ZN zv)&7JIAd`ou?_s!nbrRif@@WqIE3cob?CyF(tb;7Bcrsu1;#tKI-Q|VJ?QNTY$zsF z*gNi9adL_Wmm`5n5c-DQnCI??=+vKDRKA5?u7!LQ^^hlhqck(uUN~I{;-kt24}H7< zJW=azirS-tr1~Kq`Xt8X8|r|#KQWZTt*&yn&}rQ!ydm#X)J%p#8U|gosKgd>LTXMU z6593F(DEOX78W(39-YF!C=6Glk|l*zxQV;EItR+UYZ(b=_{59Mc{+npU3Uz0zlK!oN~+7q zC%e0(+${c|da{ygl7R=h(*t-M*gcigw7K5UGt}*X7OVnQylFDY{jdl3;1MnTG^PfG zq&UstLchoY8YQZ2VR~WoBU7aMj!{zDOrWvlB2(XY$gK{4;zXFgb zU-j|aYL1$Ff%1A?WUYY~BXCbI%dYX9$7hTe7h6Ne|6pN<9nZiz{|5OiN{M742p?8^ zuDgg_o_r*p~aliom;DHc&t9IZZzB6{lFhWd2ginx!N**9|u+~?v0z{*5tm|5aiY_Qe7NriLW#p<#% za&@Nispo7|=E;J>=M+1KLI>_2KsVV7IE|+DW0Ehj(8U$jjO7&)=a~!}?&w}G*xkyJ zk4Lkk`;ofr>}58bt9uwH$)&@$DCyr5ZcVPu^nSJm{Ro$>$$-r z$;&8|-ehb|%0*s9p(Igocs`@NXJc_>!jK?_V(lB1Ad1W!oR`vUFfn6`%Oh8@Mk!`- z(I|<*E$Ecwe5WE#kgY}Uh{@J1U<8pZ(*@OqTU-$ayM|kwuH)ZenFbwZXrW-d9+P}Y zkRQ^q{W#`$Z(2?fFSocoocIns0K+TS*hF_Rn6u4o*U8N(lwT4HQty7cyjNp@gZ&RM zhxnpq)r}FoD>5-x^SRC-`kp=hQu0_yu!J^vzlKzVk(<>o`z#m}+HmfQ0ZXnQX2Z9f zCenlF;w!wFV5eb8g({A5Xnrp$pmBXDfUkAi3Y~sPc+c&ea}x&&nD5M#dAe4J`qX>i zAThlp8-tAQ-y#03@$_)Yv~Wj5rdverH!1WeQ<^CDpp~BPzQ>Bz;Gp|6(Bu@jsvYcp zy?cU6<2BSeIv3N!6w~u<<^Ej*y~hTRw*E&T{Xq$lh9&EH^Ie5HRolqO?XGo#0vw>R zj$sv;yh>q_q3@F_(_<9^YknMllHRb2FHUXaxf&55lR-9*gJ_iF?5u>Ee{M$sl+lQuYS%qhiKaUoh+bBeVpqbQ?TY6kWlNV>l8_(uCeKUB6^_0Ejv zt21u4D7cT%p+U@z#1K~V{o*Tt++567|DBUu^j?{uK;YafieP0~c2VGbG0dVXK$h-* zootpT*#o{FCJH4xE{_!*7vOFlhi1OsI$%vWGVZJpqf7gB6|DJ~Jx5Hit0d!rzec7M7?M8(g30fo2K$7&RaM!>4R4gub#CWxUPFos=L!Y;SN((|DOUEc= z`WBL%%J;Dc7;+>kiYG#jrMC3a)I&nQ44xu>>gCiGm<;W0B;dh_&*Pe%)UwvSKeq79 zgTERC0-mgr_1qfHQ{7ltu`&&1#V*2?zgDtTzb>0(2PWy(Z_z&O5$2Ca;QZ-BU) zgnYJ}sFP3vx?kYp|563p<2r1FqT%ggMfhJ z9&hNpB6SYMN#o;JT{D=`DS08-5}Ss;(sA_)25NDyL>!5$lE*o%Nh zgd~vwzpa7md3{H!RvL zWQ#i-K;$w9T(IOg?MnD_x3IMMI)q`D$$ki*1nfFN_u0T*i$f4*y@kTsNXctpR*|F- z3r1Isl4VYU5j5tfMhxo=w}HnJXfA`E7)TLK@;O@S+!qBEV|eeCn3jPM@du#@ZzJvj zMcew59t73(-q(yMXo=g~=(s?y)Vh5Q<3_Fom0NZak_8&7yI;Bo_lW@KwI(mc|FnB` zUWz|(EK)p(%!@Yk%PVDT^r=3aXy~0mG`Td1<<-P<5tLRJrw9X)R_gIdleC zfW30^vO^#&&$Tq+{W%2pF(~=>Wt9uC*6q{B8P+KtBC_>ro652;npTu0T}GA&TK;{wtCRIe|3Bt(HC%p3fKKs=!M1 z&snsBz+te`L&@nW+!t(>kk9#WC7hv63$M!InL>7M$g;t!uo~|e<6=`H>0>F!a~h~o1vkXRP2-74^M1E$%IQ)qf6z%@W6_=h~xsepEI_h!<0ln#o8M(>MV>_D9z z(D|~`r9xqO3S8p2Cm(L%{jpFSnggmylKJ5oQPT;32?5~aQVT!8Gz;PeM6Hx@&5DIW zhsVPPvTWlbjt(r8s~HQN9?a}Z3Z7SU~y+S(KnK9|v$QN1cg+xvL9ChCp^ zmuEA1pRh7dgXCe_oKZn#`y_I*mv`T(A+g9utu10v=8vMAiKiCzc?dzn2|8TIoOcqE zjpH=#s#;^WlM#zv@u9cd=EOYIxIW|~KUl=!>Gp7h1!46XW3FPOFoEj^z`AM+tH1|DvO$be;R9%9bH=&AbvR|t>e%L~|+*f?=mhKXG;;E`# zH@FCd&K}xb+Vj^tIXvs+gv@L11TtTRn;&4cP269psj@cHXATc?O|~}Mxs^$FtjXC7 z2?Is9mIUsls{U}xZ}&5rNpWog_4g|b>6pi;)5atlsG1?g(L{H4kE#Y$6DWz7R1!3E z=m%rf+Fj!Ki+rx0t-|L@MSNtt@}-jN-I8p(ej|4Aoq3Lh$H|I20G&!5V_8Bg2fk8B z&8THj%9fciI8=#|@w~2~0LH4=7dk?9$`a)3id?y9T$Uh@wM{WWK7p+nhf5fr1fp&e zE~&^RY^y;8xp^^qi=Qp)H5af9eJ!S(iTIwxHLQ6toK2}mnE8^8_F~q0l+y67WjX7j z9C=<<`mutC7_YF(Z#WMz1C%oW6bUN78$EPnirQ9iq)_k--Z;vXmy$q@)!GhHrb*U zsAgWO3UaF{7l1TfJxvMTPQY0t)!ZyfmZK2*WOpwEpGM2ex)d-!fWYw~?vv!X9dwX!EcIH9A9b~7boWYx#!fjdxKNMnv)Dk_FwxynbS zTA3O`*g1YY?x}84n>4Dh4hNNH%rc^UEhTyS?YD)rT@DXpmZ&N21r*hm59h1PkPmB` zPU9W(s=ttRAM91HfxD+zq|d|i9AQUzrXP`9c^HhF!^opcs%Oa9#Wyd&648vN1wR&k zvL%1*8OU-6|GwFNc|TJ;G6A8xDYp0v%o^Z`byVo5#M72)%mrWs+tRRvtMumI z;pFGo+C7YMmQ5?@Ez9a2eIie^nsHD?u~xId2Ocbb!bJ3#^dYm#MOnZ9Qa%^hd zkpkH1uj+cGHQY@Hb*^NE#?`v3vQU7}xn3S8ju_THLaF?Iu{(L0qgq4PK zEo@{fX*iEnk~N%XR)PUVX{1a^a4^}pSV_Uv12{RiDU&G>#a-38v$|0HoTUZ-Cia8I zRuZ78(9*qOx+BiRdCk(GwD{$q!ae;-ZA+Xs%cbDN>7+&37vOu(JS>25hJrNoZiIAE zO4Mg1ugi~vdD66XlA(?x;axQDELB!p$lAe#v3Zp9UL3W zcS+r-A!u8S3{=vbu}VwU$zQEV-9S4~RlDfIRQG?n##*YbIKS}7(XM9Bx{c|)1?;sm zzGmhkHfey*LzFY5_vn?7QlDEbn>6U@saU>cgeBVBf(5p`LeaFSBT^p)iWM2ji}egO zreEOm)JlXfB+`A+1*n!oa0wGB<9g(&+o=7UHdVis-boIgCYz5M^||1`$RbACSKhYL)pCk0@oB%;Jlg4 zpq<@%w`Zb^&jLdwjtEH)L)TcvI2n(q@A}QKcqj~HkW-hBe0&guIS15ON&QVbIInPe z2N!!}s)CzN>AOGpkXO3XTK=(M?|>a1NQc1kMVbzecgsc{5ZPm$WXP@pf)M* zj1H$1{SPT-mYOrDMJ7`{D_`0z&OPvE3g2<2ks}}*B>YA!9Qqm*9g}au*wWmTP2oFW zJk1{`p#KCZUWV@381_*!;3wy=m_jM5O%Z0T3;|(3RAzybmP<@1TgTf*cw#z3%c3Dl z)944Aze-A*mDVgR#`e6EXeT8UTomBLZut93SrVn7oa`RvloQ_NUa68IZ#K>$-op7C z63>X0e39oe>NlSvR~|hF`@jq2WO9 zJk3BJ2^o^p;0*`InoWzAYvyKs*mk`|{Y>o$5xHQVR-uFBi?hr}Ij;d?!h@1O##zKJ z0kdPTk?!D+T1cE98eu-+aIXfziwnB*Xu|I;ne4~7S8D}%D%=R&~>!A_bL0^j=gQVY!mT8G4nL4lQ#E5FHF$w{ivFm?=1I=qcq)S zciDsvc1Vet{&qBvltTmc6`9>kwH&sj6{@IY84%P#u~KQCsK|rD|Jn_~@ zJgC!(f)z;L=YCCFdwd>6f9-KTSV50}#}!c>+J(PFIx4OVMZ@+ynDQQAJ(#Rn~6SOLA8jr9wxn`)++C2Km&@oDwa)r$j3kYZZQFEp(R$ad~(ah;ph-;Mu zGdHg^YjAN4^T^T9@e9TJ>5*S3sHwfGZwzW`_AfqmX0mu3izOf^)Np_I$j&+&;!Qg% zIgG{P#Bfm9^7~&DXh$IX+Ud5(e`{NO|)GeC%a{n1@C%qxOi&*y1>l znMP8y+Oj=xv^fnn`I&9Q>5C4hOw6gad3OjKG3j*i(z^`wyHF^pKAZG& zA9U>^dz+A{-8WE-Co%cf(>?T*&yW`98|8I$#6r#6}xrxDh4c zI(BKyq23ECZp@50(0=1%?^6zT@vOg6xWD=#Pf0&s$~s1HlE3YYIZ2}I2kTjN(8vjp z3JT4iA6xUPPmHPehDw{)g_9V2TcJ6~v|Xe04;Cd4o+MD=TiET2A0C`M{1Xp}-z#}| zO!AP}H+R~ghZiOfajZ=~SObRyKb}1Knq2*hzxLbYNj>l6*7jx#qipzOyf}=~57XUW z)z!v;GXq=&#cdZQZ5gy5H4%!mJx^`PpH|%V(4;NH9Q@4-lV{A&Pmm}I`Z-Pg5KrOz z@sqkcY0IPv{WQQ()}(sBT4n#a2cW26Xy}aV?9&y-8+hrj1~SwlLtuSbk@b;n^HrVUi!H^Sc}sEo{Pt@2@IB*hK04S2#So@uu^!803Z1=}&xn;3MqSGR=Dom$;iy@@9CTc~?zSu`dz^CWPmh5^YmxOk2y zt_gYTWK2rZeQzs6p4r;|*poB6x|M&~*}c-;-l4J=ANEJK*uw6-@9kv*Kb z70j!}WNa<4iNpqX?ZJo!_fKlFk84>M+DM9wdA)wIM=<7<*$>%lh8pc{E3|kE z$FmWXlpkdyG<0_c?)z<)HzIO>TZYi$g|>Ik3WEc}ch3iNELcMJhx$gWbbjW`_YW6>^W9AMV04-X=_eCcC&vYpiqEl~MONELm2K+8}&K z+zxB03*X^t<97e!+xdH{#k0U2mYy2_%(hELRA@b%TED}6VK+H|sL{>cas=7Ut-=FD z%i(_7Z9T~!M0e4i`)28>$t~2?%|u4r*<*g?Xy(ZRX!xL+ATMYz!oFI}bl<`uqt5*R zVRDY^Uyi9de!}7SmCLy6?DS4YDet}rLrU#FM|Sz@$?jQPnrr2J!c;k~WS#`=a0&=o z6XE(`;VJST)Glu>r14kOm~4e&p5aQ5)~j{y8dYq>`$HnGyNK@Xuss;|m-=Uz|46Za z#z#l;&mbGYu>Kiy2u+CBAf1&Xin{_KJ?{*nu{GGNzN2rGxWFt zMy74)RW21i>F!TH_ZVXs!4YNni8hDtlbO#0)|shx!Bq?|ayzI9tmFo$hKvEh27f>; zMQ6B~|Knro7JJIlI08OSA|XMb28TP91*wyv)_J}i%dUJQNsyYXRp3178K?vkn!9+BP z1H#SH+b2H-n3PcNUdxTzaLQJ2eMe(wek%*P(*b&TxSh=1WW`sk#FX?bJ$luy8>Oa_ z&L?yiq_8L{0}_SC!V>_b7I*YIl zIH!!5Nu=D@l5_gN#yY2(Dt;Y06yFTeS)-XH=X4_+y-|<>sA8N*hsm&P0Tb$zx?TRe z$(ElLZ~4>lmVdv@miuRH`BSgsmYP*z0PwrH*87jtv z3&}N|2^Z=hfq_(zWg;-1!YVB!D<2zkn7X%TUDUTa$IU&3IF#2`y!W)#jZyQtM}lln zQyquL7*+)|J~XR!FE8)99-O8QpX>=tFx`oFL*-Y|-%Pl_x{5KeTYz4ufg8a>kD5Vy z`Xx+ge;f6$x|?J_-QA{}vNqn0%Wfy3-D`)~;hDCN-V>Uwb(gMSSLtm>5N=k4LFRtv zwvxgNEPHyiE}Ux|)DCXlk@EAO>rSRfrm;Tjj?BnnA>ydJT{&*6nab2e$MTl$e<^LA zXveI`Mp$u>eWG0Qe=GOno`t(tg&dVuu(9W7iZSqW>U@&Buo^#;x$Tk z*{jYA4>%%h-Yq;JJv*IU+s^Q{NX{LH_pOcpiE40sRwU&7Fnf47W%n?fo<&p9C-&{t zqp~KWz1w|#1z2E?d#FkYqWF~w-_6YIPFJYMFo8BHZXwxuhK&UO{k|im^OUeRj*jFZ z@Q)A*zEvM9zS6XaME;c9I8#06{LHQ;*&}SOP2)ramsxy$2fY6K!dcirlsTOret+yv zmp#qTVwOX;*8Gm7=#FuBIV7z-!zDz%p}0GVb$4M5ktDy40nZ}*9Lp!mInGg|+;&uZ zVv3hRiUxMP-9xSqXoRVG%}So9)aWv$koL~^LaYpr`u ztR3zk=Tn2Ek0oUsbj0~q({`$(l)$r&3SCbl3QX2u`(>sNqZ$F&lDg6XYWs ze8Bd}S(2NBz?w4~qszZg_hftsYurd9InLum3he&p0sOJ7`w^ffPb|g^yoFbd^AerO zq69Q#KUPvx=xhAL=wmYtfXPZTs^t4VcYFYWw86fJ>1*6;rd&_sg_5zJ)xs(npy-UO z7xfU%88@ts1J@MBZ>TQaF05yU^kS3?JU&!q*0BmW90;tmMQaTNj|2k!mR2LgU6{Uvre?7q_Ny81`Mvy!a#7m*2qh1CAx!KVhj1&E12Uqzr?u zNA215mp!s;(rsBJh_5V@g4snjEzu#Q}W0CMX#TetGg(duFLmp7&PEf{RKS zetd+tpdEVaW!}=qKH917A~(vn;IYmhcDTPAI?R!BR7y7 z?S8$~L{g;u976geoGLWl>k+I?=Ko=kBf|~%l(xyaANME&$d;~tT^PuHi!oj1vXQ(l z1NRFQGA`Wug)n#p_wiac69)}+l~pJbh**%tV(vj0oim@!Uww?LnrIP_j=(kjh;Z(1 zz{auC9+*dET|CvFjl9P4&JEq|%K|qg4h^6TQ$#Q{J8Y#;f0}rR)X5oHG`p0{dUe*GEh3Ca`v)1>nA`V;nlaq^|1y|m!~)U(_WSrq zRT{_D75Qr557Ap38v+nk#{l@tiDu3|tXnlj3_(v!WjpYAk>npb%$6-}ILQlZ*-B#5jbCS|tdq+FcGya@qK?F8?HGd~L3{mt6FGd7Dp-re6_5yTwt&&jeX&N{wp zc40M>UD!92_w1xB?;M_67j6Okxn1;+XXL@RX8y*x&qpX0nM=Ge(?HCJNDmMv3=hq() zOLtyo>eKTE~gpvw%*Sc0Szqz_aK9kPXFxwEP8&aL&eCufk z_oHRX!7$(5SREtbb8#Q2ZE~CP$!Q)N`^SPJHP2&X-M)bccuoQ7r7VT^e0Liow_|Zl z&zh)DNhc6-z9la{h| zN95V9D{?SN?Bw$?c@v7#D$bG_U2Hfxe3lb}sXN~zjj_cRyfu>)dNmfF4oFmZx`wMu zlSPQ`(zUzSGV40u-Wu9ty~iNW=AGFQxw(iG^1Ynz+oL53-PzRvrJkTv?-taUoNoW* zSd$hvaCETR9Mt9BHwVdJ)Oc9F61v}G%;U=kug>q~^AOY&yV9Ew)H@-lrF-cg`@MYL zQ&zNE_SVTUy`O-)7gLx(qF3_5cwV71UhV^}7;fMmlck47xcd{apbMV_*lzg~<%ND9 zL(mqljDZ4*?yxSL-_yBH=BkNw^g_lj;x)@RuFNwIHj{EQ$eN)FLTpZg6_SZ*itX`o zEN0;h>Tre}eLdK%QxyK7YE*#w_i;6gnGOgMOote^8uGA6COm|oKu~kO&e$kVLPJ@y(_Nm%mK6nOE&-?iv_KSNs ztF(tF>0!TB*s14WnzP(P=d9?=d$Of?uqv>i`)%=3Z4PTb6z;gE{d?2@>6enxRrjO(9|FFt;yLVx4_uOh1!#-F5w~fKERoB7y%KlyWqeO{X64F zN5uXwvD!YwB`Bcs;bbHIPHoml$!nztKk(}m zEy-hJtGTF0RAI6{?eGp1YGU6eb?^`RP}p)G$jh+a+Zx*IIL;u)RZE zpQSqv7W-e<#@n}0SX3V{FIW%y96I~307)s51H%5ypMh)mO?k+vA(`xb*#?lLKQ%lg z?7;RWg^_fo8fD~)Z>?{xQl2%t2>@h-&3uP1O~_b-M?im;FW)_jaVw>JqVp+IYMhKdvvL_)yk=s&s29t8h(dClCjV7bxm+rPe~BG5|Bj~K;MiRkWI?L*mRgxj04@S3`^ipfn@S;oZt`a8CU+D<-tk{Kdk zOJ6xUSet2w;!KlHizmmdSX(fpj>Uw<3e0pEkTYK7{7nx|ZL(Jc?sA3i%V*`H9@SqZ4>gmV|;fyE}yT?kWv4dXvqk|XdTLs(hd*4EyZNj2!* z8vOQaQtwaRJs0C7oL4geIJzLvKOf1j>Zw=pu`( zCD`Bd7@JyTfFtR{wls2E_@5v0zjkEs3v}DXj_GzCJE!ZK=y*A(+oe>F4pU#je|-V3 z$*Pt(s?#8dC*~LB?qp(wivbRHpe}c7H$>n@GiT*ta!Sl3L_^^r|6wUHj1B&e0xwuT zo}-!ql82IW#l~3R_=2plN3eOz5-;s-s zoTZePglR6sd9mI*xzED_ffSeYc@x*nqr$m8Y~1l$AJ<>RKF_4+BIMha?Pr7W(1vnG z)QBibRP$t~|In4GfjCBPox-*#$}KA=v0aDh=VEJA6x-FOyMq%omc-hPl&F!@CWN+% zZ?uDtB~meEST5%5;g4e0x{&B#e=Y37DWkFIp)*A|uEKHadTSI@Gw0?gMe6vx;Ztdy zPY4_5v*EwvvwZ^qm(OlMT-Zp3SJ~oSsv%tXCz=QA`Q1n`B9u?Mz#|UvD>bPdlFlEU zP?Q`VR^+DYppEta@v_YDNl#*QRFWrtY^~$!W%Ef*l4Ny_^OITMj+FIrfd1c?DYG5$ z$hh6HMYpLC!fgeo_*^}ylt*n-nB5i0?Ct`!)gaGKTw`Ia5;AI1cNabNkEa}lvS?vA zu`AnQ?feEjpS0^U6xSrKhk?X)4ywG6M@+ZM89H=7f+?n;E4%S_zZrXW0MkaM_O|aP z8&anY`Bc2y7nlFoyiq@9+x&m{Qsj2Ag1MQZxg4Byzp>{c$i48j`4}8FANy_cv1j06 zwnycFPeTK-rBSm&^N2xJx+J0)twt5EsEBX4#s204apUq1WdYhkMP#{A>wdC46V5n9 z7D+s8af#vPWOq{R9bA&()17IrY{n~ivB&-P`$%+r0JNi@j(1cUL&>HZQr&fKn!`(i zph?O!Scgyv79-6LAT@GLD^UGRfC*WYydjVQ=}PrYjPdSPBuGm!DoF3pZP*~Yhi=8+ zo9X^Qeq*v1+e=qt?E951hXpZSMOy_iUcoR^?gAz|34B3KFNqo|?UdKokRI?Pa1rrb zo%+agFkvZoGq3A~mGLT-yudb4I-V0s^RM$+YNt<)n!EhY?A>Y0s}i}&?K$hBoaN*C zLRuZdcO1#)C@m^CG_Bp;!+elDoV!zigMHahsaNqSX+`9i(ZR(G4fr#IGVA54XXO*? zb%fiX?rX(==DTDYneJ{s9Q++ z)YMfOYJzwXNNj78pq4U;%RCKd@bOGXqu+&=Bhl`e*r9xS zy1Tm9CAqpUgQAtv>H^hf<-<)xQ){AotPzXPd9Ye6wo~ha@$g5182*HPH4Z}kq3y68 zWec4YAfcrmlMo%BA7|(OlE_cAnh9rDbXb-Vg|uFt!@e^ z(|tDGxkgJB;ao$hOdVhM-0)s^QDTfSVs-D*OisrpLm0CS^Tec_Nai63 zgUnU#lpelg$mrOPm>fzmVJX7?A+WWJ`3SwV3nw((DO zB!)IpH^p7UIfJw@uokEEWW(0s(8wRaDvX}cUD)l-Jio;rX{_7->8PMUN7hn@C~F8* zY{S&&pr`X<3AERwLlO*{x+`CoW2nq+?n;Ha97s$>GPfxO78R5NBh}@A%Nb8yjtwP@ z&y<__0CYxM_d+UY!QZ@@C~Vi?jTL=RjSyj`SJ5$_1^pt;BcOqa0*iVLI95#AdCSD_ zfvFy|o2S7^^RQ#Jo}Hs-9@g&xLL|wSqel*a)PjNSf~7$Zgoa5 z!z7=d>aNv^hm1OEz7&|?i2cL+2CyBDC6v3xI&3t zDtY4=n8()WG9p3rqQhZ&xYq!+T920A;{FI)qLTKe(Vy(COA2q*yGNFG0W%ke89&r) zGF0JG(!2cA7QESg)4zirReAn+@KAT4jJ_@IHfS(%!0^WBx?{bXbrTkEI)$Rd z*5A=+kVb~$Lb5_3UU2fCIF5qChk2Hqlk#cvtvRV->9;mgwEN6(SNBJb#ir2(^fguK zS$M|T`D02(y_&SOFi(2o1%GG*49TR<~Tkm}#2Mo;AZePIm+i#jY!8UCk_GDdrF_-M@DKykhNCs1D#M#63z+8Wp7ycW(+c5r zVuA7Bf)EWOVJT1s_dBgmySt+*NKKS}aJH#t7NO78e?;@@Xt2YRaQoVvv+@oRGNqNC z;QpKrNdJ`2xNQ0$_g%~Gv^jAnm#7$hw68V2o8UA_a4qD{d3>2T$&@?48h&7Nnz+l4 z;3Gb{n*7GfC08TYmu2gjE$n;jvY>Gq*o9QojR4jx6?b4zU>qyhJlQ?T;TDQl=g>pf znLnsGCdf^4sL)-wCqR`A_r#7?gBhs7fS0-cPmb)nrQGAt7*RpjyDH(G0`A$$A=w0G2E9J4~gN#2!JWK(c!aHa(~4YdNwK!w?&RM z?y{x25`FWC8Y@OQn*;g~DXo1zI+7@qTe4u7d~~(|C*dokj76-)XCX&6sqo)y7x0Vu zRTYeOGBc&PJ08?*DemH#_%V>^4(uUN7{j1B(c)da4|22v4R8#Nvgt`WMa>=n7a#0Ujl1)Iq+N5%(xfGma_T`I-n@|+K#XUyY8b8T7fr}cIhup7BfJ$+$E>$<3k*n+F z1Z-Uoor>#rbzmGu_bRLG(5%r$nNQ)>icy4p{%%e;rvv7t%^lsVksv$v^m(0{-1vT^ zE<3~E>`slweX^H9RN!}t64g>6pXrs<(x0oK=p0KUUGKhxN9w%oycVUWONuv^g6b!% zRgK&;Bv7dEskjc2M9Wj3+)L7@bNe(@YibtX(kbO!^S){(q})BRQu^#zDg8ITP#dS_ z136164SUom_aZujI-r&OfR?bCo`}mlNOXQLvT5t5J4o&LC40`w(-l~%ye?@_G7cxM z&1~WBVlSA0cQuzGMGSPSqEyKHRakmN*ndd%C>8w^lk;=oZlo(UD9bhaT78tcAxmf? zut=2W{?L?AFRaz@CchB8^Ir%bPLf zGMLZQE#@~aJ(90OKXN|z5mO--zEW-x#2Tp3Pp)VMO1LvJtHn&2FHb#_r70|yN^;~G zIl-zK-C%5FsXWT`F^g0e7?S>>F?)w-A^4OcM^G#pbdM57IiARo|Hs^$#z>ZB=V8&T zsb&wUp=44Vnie1xr zcO$d&SICL>&CXw8k9GsILx&3&t7>*g(|_S+r)md&Qi=Y2TT+qr&5k=s>7OOD^IR;{ z??KhCCECaqVh9NA{nr3!gCqba?`J;wfl77!%{!Q!2MbJVwa)nXQ?8KZHS+@_q1O6>hf#3P<-&O?If1W4({FJE# z{mhua8r73{gP&u{3=aiBRsyVcAzJ{7!Y_z3Y1;;ne?kU~|DGQX@H^?gKWO}+wr4-} z6$%!<&diZ}phL4_!KiQSA_eU~{MpZbhkkqK?=0~b0Kk9ty>H(6Cy4X`pI~zLoqyw} z-}%z7;@7{k_yKC4(DwGH-~FXekQ@9HcmA{YfDZpMxW)hZmmd9qoYDUmIp%vbB^$>B zJ}#^`-vht-?{Ph_ApStu24H%^HT@y{gfGE8`ug4XKnO-^3OU7pjA7=#eCLk>M)(Sx z(7%1fDJp#bzXJ^SD-br|uXl2yRv>|jXZ>BY=t%ta4~eDm?k|3ZOaDp2JMR9e=XZbg z`Q5wE2lzB*NPX$OpZ~Gq1N^nb6~2z{HUBC8o@RUh`+xb9B`));Gy_*C9Ahfx!3=Q1 ziT~I;7)<^rK&JUSKlRQx`P%;w#LOR)3yp{VUG*4P->+bm{x!hy^lAJCJ`OJOAL1hS z-@Wt8aH_tJiSGX&m0;5IAyI^>V)_#A{ytXZAN=e)h%)FZ|G^w(6u%4{_J94#J4HN%-{J6?EqZg)IUi=vH0a)=a?o5^gO*-!U z-xL~AQt#(M`zD~}5h9>pxbqL;!F~n2K0;(LxBwsVAsOs5l=}bW;W3a=hIBB=^%pP> z_MN+b7S(Q_-TkwGWd6vzEBVIhJ78quyPv)L7x5UdY+wH4RPxq=xB!Ns=*UAWV2}Ux zyNDeLiTNi0Sdvf-@*MEIsYgH17(9dn$9(Nwz(4OUarHlS_h;U{^Dn~#`xgZ8`XNxP zKaO^uaD#yE|0_Tdja>?ZviWzu z^ffU2`6a$_=SO!b&b^C2KVl#afBt=0y?+4N`C~Bdyyjp11)3@aYrK5-&;J6mzrXzM zU&nukcfb5O_UAc1*~bTe5kKQU;9~$Kz55kXrhormCA!TgcYo>P?!7)4)xVAan?Ca& za~ucxATBw~+dIGWJNO%1fX0Ji+B{3&Z(+rP;cK5^3BPvdf8e$F#XG2s+%V@0s-tt}dR(Jn7OqD0i&fo@+1Ohkch*Vxs zHIsps@%O&32%n^#cTcH0G~xABolfxvF{MDa+boT>MkpIUOrvKsZP@M~s8VWX`OEKpIz0Z07zWNz{$ya?BZMhIsa+JUB z%AXWn87G;Cf#X*Hm8dn*WFFO!^yJ?K3zveW|Kus0srQ%eLuznLfue*E}Y|k6YEB#NEc3`t^q~JI|K$?r^#2^xEz1!En-VAGfk`Bm3U*a(}p(Oy}e7 zsPF$V_y0I(%=7W|BF`49!C-iWU%uKuo9Fptl;akM*UR(i};~idRJG?y)2VA&W3n6zSZG#-0XM<_d9-^@zXb(!^J3HEb`A+-4R}3Im;F~-fuZ? zbjIW9eAyix^FlW!-Q^JLa^9WvM|rlqp7Gn7#o3;6E3#`QM^m2loZ69LqWI>y|UACbzdrp7Wp>)_XWjX{d!0@4%WRZS&X(uunt{A*_olP! z_F?bvd5;9<)$`!Zux)bf!|8B>^-R86b4S0H_eKv6ub<`1$5_<%(Q{gY=C)p3xn`U9 z!b;L$z<=Rw4*qVQ_g3@8a5~9)^Wk#X>yG$ItDlLT9=4?qLBru$PU$2--kPoKJ1nqR ztwml>YVCK2}sT`d#Re>$B*azz;g>$9El@zOGu6v497g*_s@g?)#9AT`l!wp@jp z(xr0P_U8F))a~W%@%ZRDws{#@X)|PU!@GymHBDK%2rmtt-DcbAy zK3@&zIWGBjzM3qD<2=i;R`bSeo?i^7tA)ImQNNDTZJwM%NB3ECxA8hqEl@P`d@=k! z-iN;DFLnNTa&gn=8y9l~G*SV_TcqFHr|RfOisU5w?BqUugg&sHbxxZ23wl1%3c+(l z3bxY9Vb6KT=z6fAW`V4Bwzp{(*r(mzX?`}G(0*nUGAOJq9@#_XtnmBq(;1qM`WbBS zZ~={bcuj|?m&5+T>}9>_WH3By;6G4AtKPEUGVYmmFMp4}_;uXm3(#M?8O(?}p4kbR zxfj#kn`f);ob{zIC(SBHGA%Xj2lN)Ju`zX8ipgA$8{^Xj)()1ef+?yb1kJ6(>m!(X zoS>v75;I>#=B(JP6MMGh%1heJnwF`uP2Ol<>!+6a2})tU%=_OxACB_QM7*;n_aW$u z%i*$j4*%hdHH+E#=63t4m(R$Ki7Uv*>wZSgQ-288Z!mxll{mZwTEFo&a0$JywzeJ7 z&z|HsG1Kc;(`TI*&HZe)TAXJy7+7+J;SazHLL}Y4SR6hbVk@A8ysIYO;llARM#C}7 z-X?YeKEX>m;c~P)aH!hti<9hlKRaHw?hmH(OB^NRlV;2AS^L#)#s>_F>X00W$!Rw3 z!tK1u#;ehCI2&E_IjC>5b~sKTi!olo-g--Ldmd}+{yZNICpcT!4S28ld~rUUk*ahS zg8c^3Tu_e#xW5Qty1h48su{l?Jh2_TqkopI-8o{5={W25dWc9o389VK&~RxLWERQ`<80Vf*ByF*&6R*6 z!)Z60c)D@-Dq{yn0@bbl@M1J-h#gOzgj?_~yT>}h z*~_%hX^l%x*E)lt{3Oz%-39)+p7c^r8AO<&AS1YnLj*jQUPoERTr2QwwwyP&ygzO) zF1s@hC>w5IWJ8JfUB4ty-Oi^zy2lR6^OBJK&^hDYCR%yN%8<{>e&lS_)=^(wi4{bI z0^Bj;F;YGF_c9O8R^j6)uNk<)%M8vmJ95JbT>fR}6S$Y`v0jt`5OmVF;oCl$y*^oQ zk@g`6y;0tslOA>a;0NTBwaM?t>DVBQX~VsH3R9nb7cq1$UXLt6-Syn<&dAcXAyT{K zs|~v&gcI{@1T_eBqA{Hb>~gQ413k(czT`eqv8^ji5|}FI(~S*9l8g!ywSFrqX7{N5 z^x)MAnMlNg(+Lm;hHkP;0BI_|V&%K(>M)?PSVCZAmDg&qz{pA9qG2Etx0H$Kj+PrT zt;)(Sr`dVlon@<8ww#iVA#X`N(d_{s0t(vaGe)^}U%W{++ zCd)>%b$Ctih8PZcxXLfR+g&V%XOlJs=dxl(T*{wVc-t1EsQWU;!tikk ze&lgB&Vl6i;ksN6`*Q@ga8}s~Gmm7B7*x&E9C5&5citWME)Ea1#levo11p|iXVZB< zpBG7YuZ>;Co*+Kpb5!@0pl=mCM-W@q?=HKzAa7WSWqIxZ(2g-_WzM$7(LU?(_P9f4 z<^WLF2_9Q*EW?r;sNC5Lc#1t`*vHVC*yp0-kOyp0f z4>;)=4WXuh)qBcqvI1Ku5gNyxT9p;P1vsa(qJNU*ontS(}t#7uz*}T`f z-)aGmZr=N~qr89b+uf!5#fN*pe(%Haay7}nb(SN%9`^9Z?!0&Yt*iar4|likeRy{7 z!`EB)KE#rBXBYVIVdo&dOt3G|MF!lkm-lmpe%C$XFh`Hsl|LWCh(T@+d!kU{ zWw{l9v$;zN#%$ib)MtgUDG_o4CWog$YK)xQ%t3SCQLhJ-aYgP_$CF4ZI=}T$vaROJ z?);4oLZ9HcKc4h|W0kM+14I{-)vSFOi)Xw2;2c3Hdn(wmv&{A07ApoQ2&I}$FU9hg znBrm`X3%&y$QzI07s5vc7(_yG>leRhVD$#{uOY6_exC#CcMc05w+$Q*_#D0{zH`@2 z7m}mH$H{J*@uJGHkOck&fkS+zu354OH*t0(ras0=n$Egs2#GUfCS{BBkKZb zWGh^KKVxshS)(Am-E8J?)1fjbaSwNNOg;k}rtD%m?CY~g(pL76+W*yO4~RA92xQ0UWG}^S(gV z9^Pek)`L#ES~f0wi{%t4DoLzEZ=42fFFO;GZf_vViX4OhPT7c9Ng}lg3e7sncOpj|Vqp8nxF%xc z>2(MCpOMHXC%sXBIhbX8>NkTKWwDP=vO}b^$*yd0Sl}JJB(L12Kt%i7y}6HYtoKG@ zGU%2IBK~Z4FIi8?i^UqkSshm#40#|CmO~6*vf1Du9Vcf8PsV>S>z)E|1jIpGv+NE> z!NKtk-xkytfX!RT-Qcb64`#g)Awz>}33J@P5v%3?4XX;X*akOhwoZ_}N7T+)Iuo-J z2a6H^W4!1~AzC$poR1EiLWQPUSy{^vXJ!h zkkC+f)UgaEc|;3g{!p7(V-7YyP~{~;j^`N2A%$UZn90gQ;PB58nfAAmP;D&7+4*#( zlz_Q(otJDrz-=beCG+Xb?XYlra3}J!Ie-aOx?T_cvH@Tr6;xQaOXHQ%ra|KF{;xBGx1&`2f00;#|3V{g(%}e7t^cMdK z;1p(HBoZ|VSx@FyA|FM9X`Lk~nh*RKYPU!DaV)kugD)#Rl-qXnNLU{;B?%^cD~!aCbjnfqrqw=EvwUB8niQ zeBI-SyRXD=Bl~nCZUgiY({3F)OemAB7F1IY)PNC)xWYflDc7am{J{hXvD0fhdHlxq z&#bvO@J{EN)-?u6R^73S6*mz6ub7{{Zhm^-{PYd;(}xWNP0H~gkeUR+?m)EJam+1h z5UC5DJopw}BI&ofk`kr5kCB9PPTU%_?Sp$9reQTdxJOY?kY)P``?IYkK@K*N*f;P) z`APq>|sTkIUXXg@v@8T>+DgafDgeje06v*{plaWU1pC3Ib_ zc6)1Yfe^X}P!Xvo1n5AbAm&W7;UJL6{BSaCxq-3iF}g|~pyx^eE#78)1qpftMwG8a zWP1f7a(;nQ5|z9x@(G=n2*OQ^Wm5T@x9@ReVC?oL0Tx}`>PMq~QL>0IeR8${^@!dA z;T{MPkq1oQ#{R5hc?Rpnn1e}W(}oZ@i_4g0H_z~3Ih~2*D@9owrXa$rB1$l)OZ*{i zNZZ(_tiR%#OytxlrAQA_auzlVV{x9LVtdQ0##=Wpr&(2GVts<5^#tBGwIfKI8ef~# z5_B}OTH+%Sy5Xv);wsbTE+g%L*pQ8#64>6B`a!b;)ty<#jgd-7aSeg^$`-?G=ZKKO zM3@1N6s=xkYC?p`JR%0x2locu(IWro;X~M-m9E}=sWDw)CLSp9VR%`jl;yelV(czf zr&vZ5h{1~meHTbBSPY{10Yu}G^T`FA3eKa6um|SPYFsPAvF4Dxj5VFA_7LGU)(VK% zbszDodkWMZR3JDPfIE^{B0~#CR0QuxDd1q4i};wWkDr`%o=@b5&Vx=jKwj3(nvS@F zUSpvC_v;Xk0|d%mM1ovvLW|=FMe^G=9CG(G2l>TqDw=l6XU!T^U;482mQ5v69f_{+ zU0j1(Iu@d8fcgYwI&zvQQiUy{5FuM!k57@vKD_Qgan7L-@Ndp6WZxE6kQG9CjVwi&T1ngMn)Jo0Mw!dc8cE3-LzY&}V;w+#pr*pbw3pHFd>Qnf(jS zPMEr@E^P$|orUBT6pUZ3utqkuhs*k`F4({@&EfSERE=c-1!!fl0yj{K61qVRf~GK> zaJ_Cu)kp)uU1}Ts9Ssjgfp2j*t_(@cUoUY6o)-$a)mAQ~fv^uP50pb}g2h@U1i8$t zKQ+o1SrjBycIlrZ*b=gQkwK*-$9+n2fcA?TR^BmlbE`0XzMG~h?i#o?VV-=rqecO8 z)G{H|zQL92Md+`l6Sd00WvfK~F+x{B0ORlh8*zc+x!lx#0fOYN9<Mc5UPLk^NXqAjFuuqk3h zkdd(UAU4x!q{0`Gu4dC>IaoCBNg?|7nM!kdEn3a-pW z%r{Z=2UZYD=+tb_B+m~l)Zz@8JEN=7+&IH9~TV5XfZAFK`SOyRoK4b z%33R(P4BC%2OJ~$v=vZTP%@i$&x{5S&7LDk9*(DVDf~g!{YEc%Dp#Z|olJuW9WNgs zSqS(7q*v6d(Al!@l-%-*A4stWbI>l;PrwKZQGVb)b^*J zY-~_&Gvd4ewnTz3TF?3*6`&6kOj9o)Q&~Hv9E|@cHae;V>$^KG1uV`+-z+`72k*hf z!)Dj?`e6M)ZxRyo2t^_vHK5^ap(R1Sg+4`(l5SPhe8Paoo6);Cz0J$P)*LG`ZY)B4 zgrHK;&12H@Nkp+3a3Mw8!3jI9T)DbVv}Rbc=uuZ1^c~{T39ib3ve&^5bCFC5J{B6P zq)-zp#mJ>}c;S{CqOV~{D+iT;XVBnI*qa0=2TfgQXK|rIC9H^Sj-t3tJv6+64PsJN z<6^h~R%h_Q#wE(ws9}W@GF-uU@Z;9q3mt!Ok09pO#lnJ9$bBm)YlFKSFSkIb7Iqr_ z#M*cRF7?+| zpDR{(1RS`;>d zPDEW1!%=kFYSij4qFE8n84HcEb#onKY+Z~1Q<|d(2DiXakP9l-iQ=8D7GPHNQ44mK zO}SAFvE-!O1b?kFF#(3QM*wmT1o`MWkSws*r9ywMLU-eAmpMi?8G97WqIMB=0ogMt zu6oSqy+)=~!@J^m-@&c@Ji6Cu!`AbkV&k26@O(wl9{}m7NJ=^F-lyF|`oMU)2*+)| z3%a~x;~`&hAH>ZP5At+Db+z}F&HH^+Q{afA6^s&%7+$0Ybo7pw)T!Vsn*u@T?H+o^ zfZ(c{DPjYc2VB4pnK7tF)^+GmC$Dq1A#6E4D4csGavoE(yS?iH}>M*JL}<4~BImm)ALbk>I>Cenlh;Z_VveIWlYFoJ zkE3sH5jtw(rMaUVu+Yrng!OGYHia6=l=rXg0S0M<%J3FwIjxi7VbM~V(=D&)XKN@n zB~@)0P4PEc_chAxrWrV>m9DK75{PS{LWUe+sa)k>V>1c>!WmUbK!&Kj;>YANkR`8l zlPXL<6-0uD&7A@s3vMmsw1GvO6CEtv>b13%(5^jQ(J%jb0+DV};d& ztxSCpw3h1Z0MABYptSlf9iDf&x_w3SiZQuV$j_4_zdY-X#|S?JFNC)6mM><;Pa|A9VO`u3EN#T%BW7 zFrWz)4DXCg=aLPqW3$E4AJa_`D2RI$GTg1N=BlxeBrQCeqH0t8;qb(Gf@rekgkb0_AagK#8d* zWry1E4rbgQhSGnBCmGt59q;Lm(D8S@YApmelAMJ zsyQF<>t0ilE^!sfbt~kTz|H0A{SdrzlNet_@2!hVv~-$q>}n6B*@|YMDmH2aQW=wevZ77^r(3Qbh*3XGQ&*%`@!lc07Fl$>o@U zjXt^^=w=tHJ=_3N1{st_pPx=J8D>E~qa2Ugs57S*lnL`2#fhLL>LaXNIQlAm-W`yl z442sqQ^F0Bxy6c)T^81&;E0MX_!dSJR6l`bm*+?fCr0+p7(%I_-uV% zBeRq-4znBRAN4%HzC_o@g-&#Q4;{=Cg_gUO$}CCsr4=7G4T=K3BlHbnSCI!=^d{)p z5q5w^_kS6h;oU0E&Zf&Uk{erYSc|=zaQXq>ute|+Gqu)bf$1i82scXR4wfOG?2#Fd zM9unwv_Jt$t`f#d*9vR3;G^m@CM{9$F~H{B=Fq|YR711$7eEYV*O|Ni)8Lx5{mdu_ z0}YtZ2VC2xeuNwof&yX%)Fk65PP*4nVMsRWJ8|}OlIyN7F0EhnII49u3~)t#p}cQH zCGj6Ngv`p+iuM$X3fvO@dJ5`MA)+;xo{Jb(-Wc_0YYlPEH(2&I>IoIoYu%_KYD12M zsZI-(pEIjfos(6m(2o1ou?PvdY*(o6ViwAd_l<4~{Y0-wVnm9vV#W!ih?FV!om|>X zpXLwUN{+WwchZejjtRUU^qU6B0;SxPz4e4z8N)zlR*5hvnfo)8(AiRBd)#IDqj$9eUF!b199+1|=hw68vQNA`YKILs){7)8 z^&<+%#ElS0A82$?NL=`V!mWpc&Swi08&TU0#4eprULhz1`fLG8w5uPXWVv&aeez^R z$Ygt)ln&Jza}i(rLj46pm1rzOSmnp;Du$;xZfI0PG}*W((_j8sj^Y}HqXdV{-r4b8 z3u5IK`LHoV8#>$1nYO8XOLZt?&G5yU8m99C$?SpZ#7c?oXV%-Z`^_EnE50H+n$+Dc z$P9{dBccyCo?cBL?Nq(bT|Bp1oJ%J8yQ#Pn2~{RJpv!@jxw*~iL0kPCy@Nf@AJ}oA zJ_u{0WC1r0K@DVVmn_=*2p)EjsRC4LPmj}VQ=#}Pv@e0wQjROjz<+1Hk@H3&?MkP` z@CC?h${{f0I*-xFVR8oh#uta1y(|5%bHDVE4T5D1aKXepKLtwR>uyhUoM-?ji^D!7 z98+}zEJw4@ek3}HI`QoEB22X*OnWG0KCmvN(IB|pSueI`CngHVsJh1?DiDd~ zg3+xUh2HFRfH4zVOmTh`^Bm_H1{e(4iIBz3>&WT^j7l1JL`e|#`Lv6z5*^-Fs}XNp zUHS#9C}#S;HFOo|N})Vt!qjTf@Pi;{!56 zHlr5pT>n)+f=1IRPx5XgD>} zAip<%s^)6*$+0KEAt=~wj3Hm4DtNWY&NZBG`Ug#g#cr$KP*!e%Fo#9dI>B7sPccHc zUK2>J$$N$qXf1H1O0jaKs86>Qfi~33an{+_DipUD{4A6&(uMBL>BwtG9Lbnb8f2gmrZk4|bUBiRwxmTVuETn;9 zC#(4axo!kdpdgZ-RddNzw(nyM_?Vxq#s~!HG+4hMGrbS%wiuA9-C3#->m=i#{J0Hs zg(44S_!Gdv`mB46j#|VZ)8)MkKzeG+q(=)&p@)Y!B}X-4i8Z~Bv9g=c*D7{PO5l#0 z=wIVba5EfXzxy(g+So(qa9VMP^>PG03R>}NATw?5ve~0SBIJEaaa3qp3RuQ;>XI12}Dktb)ah9=L|4;Hk~BU4FGA7gi#^U?S-G$2>w z;a}Y`bf(``V|^CXI%O&{_cNdN*RD}r15vAFXSs`&u�Y5S(dsPNEP4XV5XssYD&i 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 Uvo$w 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

    1Gn=`=%?m9JRe2^{uVR+4X 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 On|1k1ErqQ|=l=lOZ|zzD literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet.cpp b/venv/lib/python3.8/site-packages/greenlet/greenlet.cpp new file mode 100644 index 000000000..e8d92a00b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet.cpp @@ -0,0 +1,320 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/* Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#include +#include +#include +#include + + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" // PyMemberDef + +#include "greenlet_internal.hpp" +// Code after this point can assume access to things declared in stdint.h, +// including the fixed-width types. This goes for the platform-specific switch functions +// as well. +#include "greenlet_refs.hpp" +#include "greenlet_slp_switch.hpp" + +#include "greenlet_thread_support.hpp" +#include "TGreenlet.hpp" + +#include "TGreenletGlobals.cpp" + +#include "TGreenlet.cpp" +#include "TMainGreenlet.cpp" +#include "TUserGreenlet.cpp" +#include "TBrokenGreenlet.cpp" +#include "TExceptionState.cpp" +#include "TPythonState.cpp" +#include "TStackState.cpp" + +#include "TThreadState.hpp" +#include "TThreadStateCreator.hpp" +#include "TThreadStateDestroy.cpp" + +#include "PyGreenlet.cpp" +#include "PyGreenletUnswitchable.cpp" +#include "CObjects.cpp" + +using greenlet::LockGuard; +using greenlet::LockInitError; +using greenlet::PyErrOccurred; +using greenlet::Require; + +using greenlet::g_handle_exit; +using greenlet::single_result; + +using greenlet::Greenlet; +using greenlet::UserGreenlet; +using greenlet::MainGreenlet; +using greenlet::BrokenGreenlet; +using greenlet::ThreadState; +using greenlet::PythonState; + + + +// ******* Implementation of things from included files +template +greenlet::refs::_BorrowedGreenlet& greenlet::refs::_BorrowedGreenlet::operator=(const greenlet::refs::BorrowedObject& other) +{ + this->_set_raw_pointer(static_cast(other)); + return *this; +} + +template +inline greenlet::refs::_BorrowedGreenlet::operator Greenlet*() const noexcept +{ + if (!this->p) { + return nullptr; + } + return reinterpret_cast(this->p)->pimpl; +} + +template +greenlet::refs::_BorrowedGreenlet::_BorrowedGreenlet(const BorrowedObject& p) + : BorrowedReference(nullptr) +{ + + this->_set_raw_pointer(p.borrow()); +} + +template +inline greenlet::refs::_OwnedGreenlet::operator Greenlet*() const noexcept +{ + if (!this->p) { + return nullptr; + } + return reinterpret_cast(this->p)->pimpl; +} + + + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmissing-field-initializers" +# pragma clang diagnostic ignored "-Wwritable-strings" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +// warning: ISO C++ forbids converting a string constant to ‘char*’ +// (The python APIs aren't const correct and accept writable char*) +# pragma GCC diagnostic ignored "-Wwrite-strings" +#endif + + +/*********************************************************** + +A PyGreenlet is a range of C stack addresses that must be +saved and restored in such a way that the full range of the +stack contains valid data when we switch to it. + +Stack layout for a greenlet: + + | ^^^ | + | older data | + | | + stack_stop . |_______________| + . | | + . | greenlet data | + . | in stack | + . * |_______________| . . _____________ stack_copy + stack_saved + . | | | | + . | data | |greenlet data| + . | unrelated | | saved | + . | to | | in heap | + stack_start . | this | . . |_____________| stack_copy + | greenlet | + | | + | newer data | + | vvv | + + +Note that a greenlet's stack data is typically partly at its correct +place in the stack, and partly saved away in the heap, but always in +the above configuration: two blocks, the more recent one in the heap +and the older one still in the stack (either block may be empty). + +Greenlets are chained: each points to the previous greenlet, which is +the one that owns the data currently in the C stack above my +stack_stop. The currently running greenlet is the first element of +this chain. The main (initial) greenlet is the last one. Greenlets +whose stack is entirely in the heap can be skipped from the chain. + +The chain is not related to execution order, but only to the order +in which bits of C stack happen to belong to greenlets at a particular +point in time. + +The main greenlet doesn't have a stack_stop: it is responsible for the +complete rest of the C stack, and we don't know where it begins. We +use (char*) -1, the largest possible address. + +States: + stack_stop == NULL && stack_start == NULL: did not start yet + stack_stop != NULL && stack_start == NULL: already finished + stack_stop != NULL && stack_start != NULL: active + +The running greenlet's stack_start is undefined but not NULL. + + ***********************************************************/ + + + + +/***********************************************************/ + +/* Some functions must not be inlined: + * slp_restore_state, when inlined into slp_switch might cause + it to restore stack over its own local variables + * slp_save_state, when inlined would add its own local + variables to the saved stack, wasting space + * slp_switch, cannot be inlined for obvious reasons + * g_initialstub, when inlined would receive a pointer into its + own stack frame, leading to incomplete stack save/restore + +g_initialstub is a member function and declared virtual so that the +compiler always calls it through a vtable. + +slp_save_state and slp_restore_state are also member functions. They +are called from trampoline functions that themselves are declared as +not eligible for inlining. +*/ + +extern "C" { +static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref) +{ + return switching_thread_state->slp_save_state(stackref); +} +static void GREENLET_NOINLINE(slp_restore_state_trampoline)() +{ + switching_thread_state->slp_restore_state(); +} +} + + +/***********************************************************/ + + +#include "PyModule.cpp" + + + +static PyObject* +greenlet_internal_mod_init() noexcept +{ + static void* _PyGreenlet_API[PyGreenlet_API_pointers]; + + try { + CreatedModule m(greenlet_module_def); + + Require(PyType_Ready(&PyGreenlet_Type)); + Require(PyType_Ready(&PyGreenletUnswitchable_Type)); + + mod_globs = new greenlet::GreenletGlobals; + ThreadState::init(); + + m.PyAddObject("greenlet", PyGreenlet_Type); + m.PyAddObject("UnswitchableGreenlet", PyGreenletUnswitchable_Type); + m.PyAddObject("error", mod_globs->PyExc_GreenletError); + m.PyAddObject("GreenletExit", mod_globs->PyExc_GreenletExit); + + m.PyAddObject("GREENLET_USE_GC", 1); + m.PyAddObject("GREENLET_USE_TRACING", 1); + m.PyAddObject("GREENLET_USE_CONTEXT_VARS", 1L); + m.PyAddObject("GREENLET_USE_STANDARD_THREADING", 1L); + + OwnedObject clocks_per_sec = OwnedObject::consuming(PyLong_FromSsize_t(CLOCKS_PER_SEC)); + m.PyAddObject("CLOCKS_PER_SEC", clocks_per_sec); + + /* also publish module-level data as attributes of the greentype. */ + // XXX: This is weird, and enables a strange pattern of + // confusing the class greenlet with the module greenlet; with + // the exception of (possibly) ``getcurrent()``, this + // shouldn't be encouraged so don't add new items here. + for (const char* const* p = copy_on_greentype; *p; p++) { + OwnedObject o = m.PyRequireAttr(*p); + PyDict_SetItemString(PyGreenlet_Type.tp_dict, *p, o.borrow()); + } + + /* + * Expose C API + */ + + /* types */ + _PyGreenlet_API[PyGreenlet_Type_NUM] = (void*)&PyGreenlet_Type; + + /* exceptions */ + _PyGreenlet_API[PyExc_GreenletError_NUM] = (void*)mod_globs->PyExc_GreenletError; + _PyGreenlet_API[PyExc_GreenletExit_NUM] = (void*)mod_globs->PyExc_GreenletExit; + + /* methods */ + _PyGreenlet_API[PyGreenlet_New_NUM] = (void*)PyGreenlet_New; + _PyGreenlet_API[PyGreenlet_GetCurrent_NUM] = (void*)PyGreenlet_GetCurrent; + _PyGreenlet_API[PyGreenlet_Throw_NUM] = (void*)PyGreenlet_Throw; + _PyGreenlet_API[PyGreenlet_Switch_NUM] = (void*)PyGreenlet_Switch; + _PyGreenlet_API[PyGreenlet_SetParent_NUM] = (void*)PyGreenlet_SetParent; + + /* Previously macros, but now need to be functions externally. */ + _PyGreenlet_API[PyGreenlet_MAIN_NUM] = (void*)Extern_PyGreenlet_MAIN; + _PyGreenlet_API[PyGreenlet_STARTED_NUM] = (void*)Extern_PyGreenlet_STARTED; + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM] = (void*)Extern_PyGreenlet_ACTIVE; + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM] = (void*)Extern_PyGreenlet_GET_PARENT; + + /* XXX: Note that our module name is ``greenlet._greenlet``, but for + backwards compatibility with existing C code, we need the _C_API to + be directly in greenlet. + */ + const NewReference c_api_object(Require( + PyCapsule_New( + (void*)_PyGreenlet_API, + "greenlet._C_API", + NULL))); + m.PyAddObject("_C_API", c_api_object); + assert(c_api_object.REFCNT() == 2); + + // cerr << "Sizes:" + // << "\n\tGreenlet : " << sizeof(Greenlet) + // << "\n\tUserGreenlet : " << sizeof(UserGreenlet) + // << "\n\tMainGreenlet : " << sizeof(MainGreenlet) + // << "\n\tExceptionState : " << sizeof(greenlet::ExceptionState) + // << "\n\tPythonState : " << sizeof(greenlet::PythonState) + // << "\n\tStackState : " << sizeof(greenlet::StackState) + // << "\n\tSwitchingArgs : " << sizeof(greenlet::SwitchingArgs) + // << "\n\tOwnedObject : " << sizeof(greenlet::refs::OwnedObject) + // << "\n\tBorrowedObject : " << sizeof(greenlet::refs::BorrowedObject) + // << "\n\tPyGreenlet : " << sizeof(PyGreenlet) + // << endl; + + return m.borrow(); // But really it's the main reference. + } + catch (const LockInitError& e) { + PyErr_SetString(PyExc_MemoryError, e.what()); + return NULL; + } + catch (const PyErrOccurred&) { + return NULL; + } + +} + +extern "C" { + +PyMODINIT_FUNC +PyInit__greenlet(void) +{ + return greenlet_internal_mod_init(); +} + +}; // extern C + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet.h b/venv/lib/python3.8/site-packages/greenlet/greenlet.h new file mode 100644 index 000000000..d02a16e43 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet.h @@ -0,0 +1,164 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ + +/* Greenlet object interface */ + +#ifndef Py_GREENLETOBJECT_H +#define Py_GREENLETOBJECT_H + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is deprecated and undocumented. It does not change. */ +#define GREENLET_VERSION "1.0.0" + +#ifndef GREENLET_MODULE +#define implementation_ptr_t void* +#endif + +typedef struct _greenlet { + PyObject_HEAD + PyObject* weakreflist; + PyObject* dict; + implementation_ptr_t pimpl; +} PyGreenlet; + +#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) + + +/* C API functions */ + +/* Total number of symbols that are exported */ +#define PyGreenlet_API_pointers 12 + +#define PyGreenlet_Type_NUM 0 +#define PyExc_GreenletError_NUM 1 +#define PyExc_GreenletExit_NUM 2 + +#define PyGreenlet_New_NUM 3 +#define PyGreenlet_GetCurrent_NUM 4 +#define PyGreenlet_Throw_NUM 5 +#define PyGreenlet_Switch_NUM 6 +#define PyGreenlet_SetParent_NUM 7 + +#define PyGreenlet_MAIN_NUM 8 +#define PyGreenlet_STARTED_NUM 9 +#define PyGreenlet_ACTIVE_NUM 10 +#define PyGreenlet_GET_PARENT_NUM 11 + +#ifndef GREENLET_MODULE +/* This section is used by modules that uses the greenlet C API */ +static void** _PyGreenlet_API = NULL; + +# define PyGreenlet_Type \ + (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) + +# define PyExc_GreenletError \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) + +# define PyExc_GreenletExit \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) + +/* + * PyGreenlet_New(PyObject *args) + * + * greenlet.greenlet(run, parent=None) + */ +# define PyGreenlet_New \ + (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ + _PyGreenlet_API[PyGreenlet_New_NUM]) + +/* + * PyGreenlet_GetCurrent(void) + * + * greenlet.getcurrent() + */ +# define PyGreenlet_GetCurrent \ + (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) + +/* + * PyGreenlet_Throw( + * PyGreenlet *greenlet, + * PyObject *typ, + * PyObject *val, + * PyObject *tb) + * + * g.throw(...) + */ +# define PyGreenlet_Throw \ + (*(PyObject * (*)(PyGreenlet * self, \ + PyObject * typ, \ + PyObject * val, \ + PyObject * tb)) \ + _PyGreenlet_API[PyGreenlet_Throw_NUM]) + +/* + * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) + * + * g.switch(*args, **kwargs) + */ +# define PyGreenlet_Switch \ + (*(PyObject * \ + (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ + _PyGreenlet_API[PyGreenlet_Switch_NUM]) + +/* + * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) + * + * g.parent = new_parent + */ +# define PyGreenlet_SetParent \ + (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ + _PyGreenlet_API[PyGreenlet_SetParent_NUM]) + +/* + * PyGreenlet_GetParent(PyObject* greenlet) + * + * return greenlet.parent; + * + * This could return NULL even if there is no exception active. + * If it does not return NULL, you are responsible for decrementing the + * reference count. + */ +# define PyGreenlet_GetParent \ + (*(PyGreenlet* (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) + +/* + * deprecated, undocumented alias. + */ +# define PyGreenlet_GET_PARENT PyGreenlet_GetParent + +# define PyGreenlet_MAIN \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_MAIN_NUM]) + +# define PyGreenlet_STARTED \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_STARTED_NUM]) + +# define PyGreenlet_ACTIVE \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) + + + + +/* Macro that imports greenlet and initializes C API */ +/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we + keep the older definition to be sure older code that might have a copy of + the header still works. */ +# define PyGreenlet_Import() \ + { \ + _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ + } + +#endif /* GREENLET_MODULE */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_GREENLETOBJECT_H */ diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_allocator.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_allocator.hpp new file mode 100644 index 000000000..b452f5444 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_allocator.hpp @@ -0,0 +1,63 @@ +#ifndef GREENLET_ALLOCATOR_HPP +#define GREENLET_ALLOCATOR_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include "greenlet_compiler_compat.hpp" + + +namespace greenlet +{ + // This allocator is stateless; all instances are identical. + // It can *ONLY* be used when we're sure we're holding the GIL + // (Python's allocators require the GIL). + template + struct PythonAllocator : public std::allocator { + + PythonAllocator(const PythonAllocator& UNUSED(other)) + : std::allocator() + { + } + + PythonAllocator(const std::allocator other) + : std::allocator(other) + {} + + template + PythonAllocator(const std::allocator& other) + : std::allocator(other) + { + } + + PythonAllocator() : std::allocator() {} + + T* allocate(size_t number_objects, const void* UNUSED(hint)=0) + { + void* p; + if (number_objects == 1) + p = PyObject_Malloc(sizeof(T)); + else + p = PyMem_Malloc(sizeof(T) * number_objects); + return static_cast(p); + } + + void deallocate(T* t, size_t n) + { + void* p = t; + if (n == 1) { + PyObject_Free(p); + } + else + PyMem_Free(p); + } + // This member is deprecated in C++17 and removed in C++20 + template< class U > + struct rebind { + typedef PythonAllocator other; + }; + + }; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_compiler_compat.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_compiler_compat.hpp new file mode 100644 index 000000000..af24bd83e --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_compiler_compat.hpp @@ -0,0 +1,98 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef GREENLET_COMPILER_COMPAT_HPP +#define GREENLET_COMPILER_COMPAT_HPP + +/** + * Definitions to aid with compatibility with different compilers. + * + * .. caution:: Use extreme care with noexcept. + * Some compilers and runtimes, specifically gcc/libgcc/libstdc++ on + * Linux, implement stack unwinding by throwing an uncatchable + * exception, one that specifically does not appear to be an active + * exception to the rest of the runtime. If this happens while we're in a noexcept function, + * we have violated our dynamic exception contract, and so the runtime + * will call std::terminate(), which kills the process with the + * unhelpful message "terminate called without an active exception". + * + * This has happened in this scenario: A background thread is running + * a greenlet that has made a native call and released the GIL. + * Meanwhile, the main thread finishes and starts shutting down the + * interpreter. When the background thread is scheduled again and + * attempts to obtain the GIL, it notices that the interpreter is + * exiting and calls ``pthread_exit()``. This in turn starts to unwind + * the stack by throwing that exception. But we had the ``PyCall`` + * functions annotated as noexcept, so the runtime terminated us. + * + * #2 0x00007fab26fec2b7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6 + * #3 0x00007fab26febb3c in __gxx_personality_v0 () from /lib/x86_64-linux-gnu/libstdc++.so.6 + * #4 0x00007fab26f34de6 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1 + * #6 0x00007fab276a34c6 in __GI___pthread_unwind at ./nptl/unwind.c:130 + * #7 0x00007fab2769bd3a in __do_cancel () at ../sysdeps/nptl/pthreadP.h:280 + * #8 __GI___pthread_exit (value=value@entry=0x0) at ./nptl/pthread_exit.c:36 + * #9 0x000000000052e567 in PyThread_exit_thread () at ../Python/thread_pthread.h:370 + * #10 0x00000000004d60b5 in take_gil at ../Python/ceval_gil.h:224 + * #11 0x00000000004d65f9 in PyEval_RestoreThread at ../Python/ceval.c:467 + * #12 0x000000000060cce3 in setipaddr at ../Modules/socketmodule.c:1203 + * #13 0x00000000006101cd in socket_gethostbyname + */ + +#include + +# define G_NO_COPIES_OF_CLS(Cls) private: \ + Cls(const Cls& other) = delete; \ + Cls& operator=(const Cls& other) = delete + +# define G_NO_ASSIGNMENT_OF_CLS(Cls) private: \ + Cls& operator=(const Cls& other) = delete + +# define G_NO_COPY_CONSTRUCTOR_OF_CLS(Cls) private: \ + Cls(const Cls& other) = delete; + + +// CAUTION: MSVC is stupidly picky: +// +// "The compiler ignores, without warning, any __declspec keywords +// placed after * or & and in front of the variable identifier in a +// declaration." +// (https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160) +// +// So pointer return types must be handled differently (because of the +// trailing *), or you get inscrutable compiler warnings like "error +// C2059: syntax error: ''" +// +// In C++ 11, there is a standard syntax for attributes, and +// GCC defines an attribute to use with this: [[gnu:noinline]]. +// In the future, this is expected to become standard. + +#if defined(__GNUC__) || defined(__clang__) +/* We used to check for GCC 4+ or 3.4+, but those compilers are + laughably out of date. Just assume they support it. */ +# define GREENLET_NOINLINE(name) __attribute__((noinline)) name +# define GREENLET_NOINLINE_P(rtype, name) rtype __attribute__((noinline)) name +# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) +#elif defined(_MSC_VER) +/* We used to check for && (_MSC_VER >= 1300) but that's also out of date. */ +# define GREENLET_NOINLINE(name) __declspec(noinline) name +# define GREENLET_NOINLINE_P(rtype, name) __declspec(noinline) rtype name +# define UNUSED(x) UNUSED_ ## x +#endif + +#if defined(_MSC_VER) +# define G_NOEXCEPT_WIN32 noexcept +#else +# define G_NOEXCEPT_WIN32 +#endif + +#if defined(__GNUC__) && defined(__POWERPC__) && defined(__APPLE__) +// 32-bit PPC/MacOSX. Only known to be tested on unreleased versions +// of macOS 10.6 using a macports build gcc 14. It appears that +// running C++ destructors of thread-local variables is broken. + +// See https://github.com/python-greenlet/greenlet/pull/419 +# define GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK 1 +#else +# define GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK 0 +#endif + + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_add_pending.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_add_pending.hpp new file mode 100644 index 000000000..0d28efd3d --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_add_pending.hpp @@ -0,0 +1,172 @@ +#ifndef GREENLET_CPYTHON_ADD_PENDING_HPP +#define GREENLET_CPYTHON_ADD_PENDING_HPP + +#if (PY_VERSION_HEX >= 0x30800A0 && PY_VERSION_HEX < 0x3090000) && !(defined(_WIN32) || defined(WIN32)) +// XXX: From Python 3.8a3 [1] up until Python 3.9a6 [2][3], +// ``Py_AddPendingCall`` would try to produce a Python exception if +// the interpreter was in the beginning of shutting down when this +// function is called. However, ``Py_AddPendingCall`` doesn't require +// the GIL, and we are absolutely not holding it when we make that +// call. That means that trying to create the Python exception is +// using the C API in an undefined state; here the C API detects this +// and aborts the process with an error ("Fatal Python error: Python +// memory allocator called without holding the GIL": Add -> +// PyErr_SetString -> PyUnicode_New -> PyObject_Malloc). This arises +// (obviously) in multi-threaded programs and happens if one thread is +// exiting and cleaning up its thread-local data while the other +// thread is trying to shut down the interpreter. A crash on shutdown +// is still a crash and could result in data loss (e.g., daemon +// threads are still running, pending signal handlers may be present, +// buffers may not be flushed, there may be __del__ that need run, +// etc), so we have to work around it. +// +// Of course, we can (and do) check for whether the interpreter is +// shutting down before calling ``Py_AddPendingCall``, but that's a +// race condition since we don't hold the GIL, and so we may not +// actually get the right answer. Plus, ``Py_FinalizeEx`` actually +// calls ``_Py_FinishPendingCalls`` (which sets the pending->finishing +// flag, which is used to gate creating the exceptioen) *before* +// publishing any other data that would let us detect the shutdown +// (such as runtime->finalizing). So that point is moot. +// +// Our solution for those versions is to inline the same code, without +// the problematic bit that sets the exception. Unfortunately, all of +// the structure definitions are private/opaque, *and* we can't +// actually count on being able to include their definitions from +// ``internal/pycore_*``, because on some platforms those header files +// are incomplete (i.e., on macOS with macports 3.8, the includes are +// fine, but on Ubuntu jammy with 3.8 from ppa:deadsnakes or GitHub +// Actions 3.8 (I think it's Ubuntu 18.04), they con't be used; at +// least, I couldn't get them to work). So we need to define the +// structures and _PyRuntime data member ourself. Yet more +// unfortunately, _PyRuntime won't link on Windows, so we can only do +// this on other platforms. +// +// [1] https://github.com/python/cpython/commit/842a2f07f2f08a935ef470bfdaeef40f87490cfc +// [2] https://github.com/python/cpython/commit/cfc3c2f8b34d3864717ab584c5b6c260014ba55a +// [3] https://github.com/python/cpython/issues/81308 +# define GREENLET_BROKEN_PY_ADD_PENDING 1 + +// When defining these structures, the important thing is to get +// binary compatibility, i.e., structure layout. For that, we only +// need to define fields up to the ones we use; after that they're +// irrelevant UNLESS the structure is included in another structure +// *before* the structure we're interested in --- in that case, it +// must be complete. Ellipsis indicate elided trailing members. +// Pointer types are changed to void* to keep from having to define +// more structures. + +// From "internal/pycore_atomic.h" + +// There are several different definitions of this, including the +// plain ``int`` version, a ``volatile int`` and an ``_Atomic int`` +// I don't think any of those change the size/layout. +typedef struct _Py_atomic_int { + volatile int _value; +} _Py_atomic_int; + +// This needs too much infrastructure, so we just do a regular store. +#define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \ + (ATOMIC_VAL)->_value = NEW_VAL + + + +// From "internal/pycore_pymem.h" +#define NUM_GENERATIONS 3 + + +struct gc_generation { + PyGC_Head head; // We already have this defined. + int threshold; + int count; +}; +struct gc_generation_stats { + Py_ssize_t collections; + Py_ssize_t collected; + Py_ssize_t uncollectable; +}; + +struct _gc_runtime_state { + void *trash_delete_later; + int trash_delete_nesting; + int enabled; + int debug; + struct gc_generation generations[NUM_GENERATIONS]; + void *generation0; + struct gc_generation permanent_generation; + struct gc_generation_stats generation_stats[NUM_GENERATIONS]; + int collecting; + void *garbage; + void *callbacks; + Py_ssize_t long_lived_total; + Py_ssize_t long_lived_pending; +}; + +// From "internal/pycore_pystate.h" +struct _pending_calls { + int finishing; + PyThread_type_lock lock; + _Py_atomic_int calls_to_do; + int async_exc; +#define NPENDINGCALLS 32 + struct { + int (*func)(void *); + void *arg; + } calls[NPENDINGCALLS]; + int first; + int last; +}; + +struct _ceval_runtime_state { + int recursion_limit; + int tracing_possible; + _Py_atomic_int eval_breaker; + _Py_atomic_int gil_drop_request; + struct _pending_calls pending; + // ... +}; + +typedef struct pyruntimestate { + int preinitializing; + int preinitialized; + int core_initialized; + int initialized; + void *finalizing; + + struct pyinterpreters { + PyThread_type_lock mutex; + void *head; + void *main; + int64_t next_id; + } interpreters; + // XXX Remove this field once we have a tp_* slot. + struct _xidregistry { + PyThread_type_lock mutex; + void *head; + } xidregistry; + + unsigned long main_thread; + +#define NEXITFUNCS 32 + void (*exitfuncs[NEXITFUNCS])(void); + int nexitfuncs; + + struct _gc_runtime_state gc; + struct _ceval_runtime_state ceval; + // ... +} _PyRuntimeState; + +#define SIGNAL_PENDING_CALLS(ceval) \ + do { \ + _Py_atomic_store_relaxed(&(ceval)->pending.calls_to_do, 1); \ + _Py_atomic_store_relaxed(&(ceval)->eval_breaker, 1); \ + } while (0) + +extern _PyRuntimeState _PyRuntime; + +#else +# define GREENLET_BROKEN_PY_ADD_PENDING 0 +#endif + + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_compat.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_compat.hpp new file mode 100644 index 000000000..ce5fd882d --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_cpython_compat.hpp @@ -0,0 +1,142 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef GREENLET_CPYTHON_COMPAT_H +#define GREENLET_CPYTHON_COMPAT_H + +/** + * Helpers for compatibility with multiple versions of CPython. + */ + +#define PY_SSIZE_T_CLEAN +#include "Python.h" + + +#if PY_VERSION_HEX >= 0x30A00B1 +# define GREENLET_PY310 1 +#else +# define GREENLET_PY310 0 +#endif + +/* +Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member. +See https://github.com/python/cpython/pull/25276 +We have to save and restore this as well. + +Python 3.13 removed PyThreadState.cframe (GH-108035). +*/ +#if GREENLET_PY310 && PY_VERSION_HEX < 0x30D0000 +# define GREENLET_USE_CFRAME 1 +#else +# define GREENLET_USE_CFRAME 0 +#endif + + +#if PY_VERSION_HEX >= 0x30B00A4 +/* +Greenlet won't compile on anything older than Python 3.11 alpha 4 (see +https://bugs.python.org/issue46090). Summary of breaking internal changes: +- Python 3.11 alpha 1 changed how frame objects are represented internally. + - https://github.com/python/cpython/pull/30122 +- Python 3.11 alpha 3 changed how recursion limits are stored. + - https://github.com/python/cpython/pull/29524 +- Python 3.11 alpha 4 changed how exception state is stored. It also includes a + change to help greenlet save and restore the interpreter frame "data stack". + - https://github.com/python/cpython/pull/30122 + - https://github.com/python/cpython/pull/30234 +*/ +# define GREENLET_PY311 1 +#else +# define GREENLET_PY311 0 +#endif + + +#if PY_VERSION_HEX >= 0x30C0000 +# define GREENLET_PY312 1 +#else +# define GREENLET_PY312 0 +#endif + +#if PY_VERSION_HEX >= 0x30D0000 +# define GREENLET_PY313 1 +#else +# define GREENLET_PY313 0 +#endif + +#ifndef Py_SET_REFCNT +/* Py_REFCNT and Py_SIZE macros are converted to functions +https://bugs.python.org/issue39573 */ +# define Py_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt) +#endif + +#ifndef _Py_DEC_REFTOTAL +/* _Py_DEC_REFTOTAL macro has been removed from Python 3.9 by: + https://github.com/python/cpython/commit/49932fec62c616ec88da52642339d83ae719e924 + + The symbol we use to replace it was removed by at least 3.12. +*/ +# ifdef Py_REF_DEBUG +# if GREENLET_PY312 +# define _Py_DEC_REFTOTAL +# else +# define _Py_DEC_REFTOTAL _Py_RefTotal-- +# endif +# else +# define _Py_DEC_REFTOTAL +# endif +#endif +// Define these flags like Cython does if we're on an old version. +#ifndef Py_TPFLAGS_CHECKTYPES + #define Py_TPFLAGS_CHECKTYPES 0 +#endif +#ifndef Py_TPFLAGS_HAVE_INDEX + #define Py_TPFLAGS_HAVE_INDEX 0 +#endif +#ifndef Py_TPFLAGS_HAVE_NEWBUFFER + #define Py_TPFLAGS_HAVE_NEWBUFFER 0 +#endif + +#ifndef Py_TPFLAGS_HAVE_VERSION_TAG + #define Py_TPFLAGS_HAVE_VERSION_TAG 0 +#endif + +#define G_TPFLAGS_DEFAULT Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_HAVE_NEWBUFFER | Py_TPFLAGS_HAVE_GC + + +#if PY_VERSION_HEX < 0x03090000 +// The official version only became available in 3.9 +# define PyObject_GC_IsTracked(o) _PyObject_GC_IS_TRACKED(o) +#endif + + +// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_EnterTracing(PyThreadState *tstate) +{ + tstate->tracing++; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = 0; +#else + tstate->use_tracing = 0; +#endif +} +#endif + +// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) +{ + tstate->tracing--; + int use_tracing = (tstate->c_tracefunc != NULL + || tstate->c_profilefunc != NULL); +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = use_tracing; +#else + tstate->use_tracing = use_tracing; +#endif +} +#endif + +#if !defined(Py_C_RECURSION_LIMIT) && defined(C_RECURSION_LIMIT) +# define Py_C_RECURSION_LIMIT C_RECURSION_LIMIT +#endif + +#endif /* GREENLET_CPYTHON_COMPAT_H */ diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_exceptions.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_exceptions.hpp new file mode 100644 index 000000000..617f07c2f --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_exceptions.hpp @@ -0,0 +1,171 @@ +#ifndef GREENLET_EXCEPTIONS_HPP +#define GREENLET_EXCEPTIONS_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +namespace greenlet { + + class PyErrOccurred : public std::runtime_error + { + public: + + // CAUTION: In debug builds, may run arbitrary Python code. + static const PyErrOccurred + from_current() + { + assert(PyErr_Occurred()); +#ifndef NDEBUG + // This is not exception safe, and + // not necessarily safe in general (what if it switches?) + // But we only do this in debug mode, where we are in + // tight control of what exceptions are getting raised and + // can prevent those issues. + + // You can't call PyObject_Str with a pending exception. + PyObject* typ; + PyObject* val; + PyObject* tb; + + PyErr_Fetch(&typ, &val, &tb); + PyObject* typs = PyObject_Str(typ); + PyObject* vals = PyObject_Str(val ? val : typ); + const char* typ_msg = PyUnicode_AsUTF8(typs); + const char* val_msg = PyUnicode_AsUTF8(vals); + PyErr_Restore(typ, val, tb); + + std::string msg(typ_msg); + msg += ": "; + msg += val_msg; + PyErrOccurred ex(msg); + Py_XDECREF(typs); + Py_XDECREF(vals); + + return ex; +#else + return PyErrOccurred(); +#endif + } + + PyErrOccurred() : std::runtime_error("") + { + assert(PyErr_Occurred()); + } + + PyErrOccurred(const std::string& msg) : std::runtime_error(msg) + { + assert(PyErr_Occurred()); + } + + PyErrOccurred(PyObject* exc_kind, const char* const msg) + : std::runtime_error(msg) + { + PyErr_SetString(exc_kind, msg); + } + + PyErrOccurred(PyObject* exc_kind, const std::string msg) + : std::runtime_error(msg) + { + // This copies the c_str, so we don't have any lifetime + // issues to worry about. + PyErr_SetString(exc_kind, msg.c_str()); + } + + PyErrOccurred(PyObject* exc_kind, + const std::string msg, //This is the format + //string; that's not + //usually safe! + + PyObject* borrowed_obj_one, PyObject* borrowed_obj_two) + : std::runtime_error(msg) + { + + //This is designed specifically for the + //``check_switch_allowed`` function. + + // PyObject_Str and PyObject_Repr are safe to call with + // NULL pointers; they return the string "" in that + // case. + // This function always returns null. + PyErr_Format(exc_kind, + msg.c_str(), + borrowed_obj_one, borrowed_obj_two); + } + }; + + class TypeError : public PyErrOccurred + { + public: + TypeError(const char* const what) + : PyErrOccurred(PyExc_TypeError, what) + { + } + TypeError(const std::string what) + : PyErrOccurred(PyExc_TypeError, what) + { + } + }; + + class ValueError : public PyErrOccurred + { + public: + ValueError(const char* const what) + : PyErrOccurred(PyExc_ValueError, what) + { + } + }; + + class AttributeError : public PyErrOccurred + { + public: + AttributeError(const char* const what) + : PyErrOccurred(PyExc_AttributeError, what) + { + } + }; + + /** + * Calls `Py_FatalError` when constructed, so you can't actually + * throw this. It just makes static analysis easier. + */ + class PyFatalError : public std::runtime_error + { + public: + PyFatalError(const char* const msg) + : std::runtime_error(msg) + { + Py_FatalError(msg); + } + }; + + static inline PyObject* + Require(PyObject* p, const std::string& msg="") + { + if (!p) { + throw PyErrOccurred(msg); + } + return p; + }; + + static inline void + Require(const int retval) + { + if (retval < 0) { + throw PyErrOccurred(); + } + }; + + +}; +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_internal.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_internal.hpp new file mode 100644 index 000000000..f2b15d5fa --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_internal.hpp @@ -0,0 +1,107 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef GREENLET_INTERNAL_H +#define GREENLET_INTERNAL_H +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +/** + * Implementation helpers. + * + * C++ templates and inline functions should go here. + */ +#define PY_SSIZE_T_CLEAN +#include "greenlet_compiler_compat.hpp" +#include "greenlet_cpython_compat.hpp" +#include "greenlet_exceptions.hpp" +#include "TGreenlet.hpp" +#include "greenlet_allocator.hpp" + +#include +#include + +#define GREENLET_MODULE +struct _greenlet; +typedef struct _greenlet PyGreenlet; +namespace greenlet { + + class ThreadState; + // We can't use the PythonAllocator for this, because we push to it + // from the thread state destructor, which doesn't have the GIL, + // and Python's allocators can only be called with the GIL. + typedef std::vector cleanup_queue_t; + +}; + + +#define implementation_ptr_t greenlet::Greenlet* + + +#include "greenlet.h" + +void +greenlet::refs::MainGreenletExactChecker(void *p) +{ + if (!p) { + return; + } + // We control the class of the main greenlet exactly. + if (Py_TYPE(p) != &PyGreenlet_Type) { + std::string err("MainGreenlet: Expected exactly a greenlet, not a "); + err += Py_TYPE(p)->tp_name; + throw greenlet::TypeError(err); + } + + // Greenlets from dead threads no longer respond to main() with a + // true value; so in that case we need to perform an additional + // check. + Greenlet* g = static_cast(p)->pimpl; + if (g->main()) { + return; + } + if (!dynamic_cast(g)) { + std::string err("MainGreenlet: Expected exactly a main greenlet, not a "); + err += Py_TYPE(p)->tp_name; + throw greenlet::TypeError(err); + } +} + + + +template +inline greenlet::Greenlet* greenlet::refs::_OwnedGreenlet::operator->() const noexcept +{ + return reinterpret_cast(this->p)->pimpl; +} + +template +inline greenlet::Greenlet* greenlet::refs::_BorrowedGreenlet::operator->() const noexcept +{ + return reinterpret_cast(this->p)->pimpl; +} + +#include +#include + + +extern PyTypeObject PyGreenlet_Type; + + + +/** + * Forward declarations needed in multiple files. + */ +static PyObject* green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs); + + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + +#endif + +// Local Variables: +// flycheck-clang-include-path: ("../../include" "/opt/local/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10") +// End: diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_refs.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_refs.hpp new file mode 100644 index 000000000..b7e5e3f2a --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_refs.hpp @@ -0,0 +1,1118 @@ +#ifndef GREENLET_REFS_HPP +#define GREENLET_REFS_HPP + +#define PY_SSIZE_T_CLEAN +#include + +#include + +//#include "greenlet_internal.hpp" +#include "greenlet_compiler_compat.hpp" +#include "greenlet_cpython_compat.hpp" +#include "greenlet_exceptions.hpp" + +struct _greenlet; +struct _PyMainGreenlet; + +typedef struct _greenlet PyGreenlet; +extern PyTypeObject PyGreenlet_Type; + + +#ifdef GREENLET_USE_STDIO +#include +using std::cerr; +using std::endl; +#endif + +namespace greenlet +{ + class Greenlet; + + namespace refs + { + // Type checkers throw a TypeError if the argument is not + // null, and isn't of the required Python type. + // (We can't use most of the defined type checkers + // like PyList_Check, etc, directly, because they are + // implemented as macros.) + typedef void (*TypeChecker)(void*); + + void + NoOpChecker(void*) + { + return; + } + + void + GreenletChecker(void *p) + { + if (!p) { + return; + } + + PyTypeObject* typ = Py_TYPE(p); + // fast, common path. (PyObject_TypeCheck is a macro or + // static inline function, and it also does a + // direct comparison of the type pointers, but its fast + // path only handles one type) + if (typ == &PyGreenlet_Type) { + return; + } + + if (!PyObject_TypeCheck(p, &PyGreenlet_Type)) { + std::string err("GreenletChecker: Expected any type of greenlet, not "); + err += Py_TYPE(p)->tp_name; + throw TypeError(err); + } + } + + void + MainGreenletExactChecker(void *p); + + template + class PyObjectPointer; + + template + class OwnedReference; + + + template + class BorrowedReference; + + typedef BorrowedReference BorrowedObject; + typedef OwnedReference OwnedObject; + + class ImmortalObject; + class ImmortalString; + + template + class _OwnedGreenlet; + + typedef _OwnedGreenlet OwnedGreenlet; + typedef _OwnedGreenlet OwnedMainGreenlet; + + template + class _BorrowedGreenlet; + + typedef _BorrowedGreenlet BorrowedGreenlet; + + void + ContextExactChecker(void *p) + { + if (!p) { + return; + } + if (!PyContext_CheckExact(p)) { + throw TypeError( + "greenlet context must be a contextvars.Context or None" + ); + } + } + + typedef OwnedReference OwnedContext; + } +} + +namespace greenlet { + + + namespace refs { + // A set of classes to make reference counting rules in python + // code explicit. + // + // Rules of use: + // (1) Functions returning a new reference that the caller of the + // function is expected to dispose of should return a + // ``OwnedObject`` object. This object automatically releases its + // reference when it goes out of scope. It works like a ``std::shared_ptr`` + // and can be copied or used as a function parameter (but don't do + // that). Note that constructing a ``OwnedObject`` from a + // PyObject* steals the reference. + // (2) Parameters to functions should be either a + // ``OwnedObject&``, or, more generally, a ``PyObjectPointer&``. + // If the function needs to create its own new reference, it can + // do so by copying to a local ``OwnedObject``. + // (3) Functions returning an existing pointer that is NOT + // incref'd, and which the caller MUST NOT decref, + // should return a ``BorrowedObject``. + + // XXX: The following two paragraphs do not hold for all platforms. + // Notably, 32-bit PPC Linux passes structs by reference, not by + // value, so this actually doesn't work. (Although that's the only + // platform that doesn't work on.) DO NOT ATTEMPT IT. The + // unfortunate consequence of that is that the slots which we + // *know* are already type safe will wind up calling the type + // checker function (when we had the slots accepting + // BorrowedGreenlet, this was bypassed), so this slows us down. + // TODO: Optimize this again. + + // For a class with a single pointer member, whose constructor + // does nothing but copy a pointer parameter into the member, and + // which can then be converted back to the pointer type, compilers + // generate code that's the same as just passing the pointer. + // That is, func(BorrowedObject x) called like ``PyObject* p = + // ...; f(p)`` has 0 overhead. Similarly, they "unpack" to the + // pointer type with 0 overhead. + // + // If there are no virtual functions, no complex inheritance (maybe?) and + // no destructor, these can be directly used as parameters in + // Python callbacks like tp_init: the layout is the same as a + // single pointer. Only subclasses with trivial constructors that + // do nothing but set the single pointer member are safe to use + // that way. + + + // This is the base class for things that can be done with a + // PyObject pointer. It assumes nothing about memory management. + // NOTE: Nothing is virtual, so subclasses shouldn't add new + // storage fields or try to override these methods. + template + class PyObjectPointer + { + public: + typedef T PyType; + protected: + T* p; + public: + PyObjectPointer(T* it=nullptr) : p(it) + { + TC(p); + } + + // We don't allow automatic casting to PyObject* at this + // level, because then we could be passed to Py_DECREF/INCREF, + // but we want nothing to do with memory management. If you + // know better, then you can use the get() method, like on a + // std::shared_ptr. Except we name it borrow() to clarify that + // if this is a reference-tracked object, the pointer you get + // back will go away when the object does. + // TODO: This should probably not exist here, but be moved + // down to relevant sub-types. + + T* borrow() const noexcept + { + return this->p; + } + + PyObject* borrow_o() const noexcept + { + return reinterpret_cast(this->p); + } + + T* operator->() const noexcept + { + return this->p; + } + + bool is_None() const noexcept + { + return this->p == Py_None; + } + + PyObject* acquire_or_None() const noexcept + { + PyObject* result = this->p ? reinterpret_cast(this->p) : Py_None; + Py_INCREF(result); + return result; + } + + explicit operator bool() const noexcept + { + return this->p != nullptr; + } + + bool operator!() const noexcept + { + return this->p == nullptr; + } + + Py_ssize_t REFCNT() const noexcept + { + return p ? Py_REFCNT(p) : -42; + } + + PyTypeObject* TYPE() const noexcept + { + return p ? Py_TYPE(p) : nullptr; + } + + inline OwnedObject PyStr() const noexcept; + inline const std::string as_str() const noexcept; + inline OwnedObject PyGetAttr(const ImmortalObject& name) const noexcept; + inline OwnedObject PyRequireAttr(const char* const name) const; + inline OwnedObject PyRequireAttr(const ImmortalString& name) const; + inline OwnedObject PyCall(const BorrowedObject& arg) const; + inline OwnedObject PyCall(PyGreenlet* arg) const ; + inline OwnedObject PyCall(PyObject* arg) const ; + // PyObject_Call(this, args, kwargs); + inline OwnedObject PyCall(const BorrowedObject args, + const BorrowedObject kwargs) const; + inline OwnedObject PyCall(const OwnedObject& args, + const OwnedObject& kwargs) const; + + protected: + void _set_raw_pointer(void* t) + { + TC(t); + p = reinterpret_cast(t); + } + void* _get_raw_pointer() const + { + return p; + } + }; + +#ifdef GREENLET_USE_STDIO + template + std::ostream& operator<<(std::ostream& os, const PyObjectPointer& s) + { + const std::type_info& t = typeid(s); + os << t.name() + << "(addr=" << s.borrow() + << ", refcnt=" << s.REFCNT() + << ", value=" << s.as_str() + << ")"; + + return os; + } +#endif + + template + inline bool operator==(const PyObjectPointer& lhs, const PyObject* const rhs) noexcept + { + return static_cast(lhs.borrow_o()) == static_cast(rhs); + } + + template + inline bool operator==(const PyObjectPointer& lhs, const PyObjectPointer& rhs) noexcept + { + return lhs.borrow_o() == rhs.borrow_o(); + } + + template + inline bool operator!=(const PyObjectPointer& lhs, + const PyObjectPointer& rhs) noexcept + { + return lhs.borrow_o() != rhs.borrow_o(); + } + + template + class OwnedReference : public PyObjectPointer + { + private: + friend class OwnedList; + + protected: + explicit OwnedReference(T* it) : PyObjectPointer(it) + { + } + + public: + + // Constructors + + static OwnedReference consuming(PyObject* p) + { + return OwnedReference(reinterpret_cast(p)); + } + + static OwnedReference owning(T* p) + { + OwnedReference result(p); + Py_XINCREF(result.p); + return result; + } + + OwnedReference() : PyObjectPointer(nullptr) + {} + + explicit OwnedReference(const PyObjectPointer<>& other) + : PyObjectPointer(nullptr) + { + T* op = other.borrow(); + TC(op); + this->p = other.borrow(); + Py_XINCREF(this->p); + } + + // It would be good to make use of the C++11 distinction + // between move and copy operations, e.g., constructing from a + // pointer should be a move operation. + // In the common case of ``OwnedObject x = Py_SomeFunction()``, + // the call to the copy constructor will be elided completely. + OwnedReference(const OwnedReference& other) + : PyObjectPointer(other.p) + { + Py_XINCREF(this->p); + } + + static OwnedReference None() + { + Py_INCREF(Py_None); + return OwnedReference(Py_None); + } + + // We can assign from exactly our type without any extra checking + OwnedReference& operator=(const OwnedReference& other) + { + Py_XINCREF(other.p); + const T* tmp = this->p; + this->p = other.p; + Py_XDECREF(tmp); + return *this; + } + + OwnedReference& operator=(const BorrowedReference other) + { + return this->operator=(other.borrow()); + } + + OwnedReference& operator=(T* const other) + { + TC(other); + Py_XINCREF(other); + T* tmp = this->p; + this->p = other; + Py_XDECREF(tmp); + return *this; + } + + // We can assign from an arbitrary reference type + // if it passes our check. + template + OwnedReference& operator=(const OwnedReference& other) + { + X* op = other.borrow(); + TC(op); + return this->operator=(reinterpret_cast(op)); + } + + inline void steal(T* other) + { + assert(this->p == nullptr); + TC(other); + this->p = other; + } + + T* relinquish_ownership() + { + T* result = this->p; + this->p = nullptr; + return result; + } + + T* acquire() const + { + // Return a new reference. + // TODO: This may go away when we have reference objects + // throughout the code. + Py_XINCREF(this->p); + return this->p; + } + + // Nothing else declares a destructor, we're the leaf, so we + // should be able to get away without virtual. + ~OwnedReference() + { + Py_CLEAR(this->p); + } + + void CLEAR() + { + Py_CLEAR(this->p); + assert(this->p == nullptr); + } + }; + + static inline + void operator<<=(PyObject*& target, OwnedObject& o) + { + target = o.relinquish_ownership(); + } + + + class NewReference : public OwnedObject + { + private: + G_NO_COPIES_OF_CLS(NewReference); + public: + // Consumes the reference. Only use this + // for API return values. + NewReference(PyObject* it) : OwnedObject(it) + { + } + }; + + class NewDictReference : public NewReference + { + private: + G_NO_COPIES_OF_CLS(NewDictReference); + public: + NewDictReference() : NewReference(PyDict_New()) + { + if (!this->p) { + throw PyErrOccurred(); + } + } + + void SetItem(const char* const key, PyObject* value) + { + Require(PyDict_SetItemString(this->p, key, value)); + } + + void SetItem(const PyObjectPointer<>& key, PyObject* value) + { + Require(PyDict_SetItem(this->p, key.borrow_o(), value)); + } + }; + + template + class _OwnedGreenlet: public OwnedReference + { + private: + protected: + _OwnedGreenlet(T* it) : OwnedReference(it) + {} + + public: + _OwnedGreenlet() : OwnedReference() + {} + + _OwnedGreenlet(const _OwnedGreenlet& other) : OwnedReference(other) + { + } + _OwnedGreenlet(OwnedMainGreenlet& other) : + OwnedReference(reinterpret_cast(other.acquire())) + { + } + _OwnedGreenlet(const BorrowedGreenlet& other); + // Steals a reference. + static _OwnedGreenlet consuming(PyGreenlet* it) + { + return _OwnedGreenlet(reinterpret_cast(it)); + } + + inline _OwnedGreenlet& operator=(const OwnedGreenlet& other) + { + return this->operator=(other.borrow()); + } + + inline _OwnedGreenlet& operator=(const BorrowedGreenlet& other); + + _OwnedGreenlet& operator=(const OwnedMainGreenlet& other) + { + PyGreenlet* owned = other.acquire(); + Py_XDECREF(this->p); + this->p = reinterpret_cast(owned); + return *this; + } + + _OwnedGreenlet& operator=(T* const other) + { + OwnedReference::operator=(other); + return *this; + } + + T* relinquish_ownership() + { + T* result = this->p; + this->p = nullptr; + return result; + } + + PyObject* relinquish_ownership_o() + { + return reinterpret_cast(relinquish_ownership()); + } + + inline Greenlet* operator->() const noexcept; + inline operator Greenlet*() const noexcept; + }; + + template + class BorrowedReference : public PyObjectPointer + { + public: + // Allow implicit creation from PyObject* pointers as we + // transition to using these classes. Also allow automatic + // conversion to PyObject* for passing to C API calls and even + // for Py_INCREF/DECREF, because we ourselves do no memory management. + BorrowedReference(T* it) : PyObjectPointer(it) + {} + + BorrowedReference(const PyObjectPointer& ref) : PyObjectPointer(ref.borrow()) + {} + + BorrowedReference() : PyObjectPointer(nullptr) + {} + + operator T*() const + { + return this->p; + } + }; + + typedef BorrowedReference BorrowedObject; + //typedef BorrowedReference BorrowedGreenlet; + + template + class _BorrowedGreenlet : public BorrowedReference + { + public: + _BorrowedGreenlet() : + BorrowedReference(nullptr) + {} + + _BorrowedGreenlet(T* it) : + BorrowedReference(it) + {} + + _BorrowedGreenlet(const BorrowedObject& it); + + _BorrowedGreenlet(const OwnedGreenlet& it) : + BorrowedReference(it.borrow()) + {} + + _BorrowedGreenlet& operator=(const BorrowedObject& other); + + // We get one of these for PyGreenlet, but one for PyObject + // is handy as well + operator PyObject*() const + { + return reinterpret_cast(this->p); + } + Greenlet* operator->() const noexcept; + operator Greenlet*() const noexcept; + }; + + typedef _BorrowedGreenlet BorrowedGreenlet; + + template + _OwnedGreenlet::_OwnedGreenlet(const BorrowedGreenlet& other) + : OwnedReference(reinterpret_cast(other.borrow())) + { + Py_XINCREF(this->p); + } + + + class BorrowedMainGreenlet + : public _BorrowedGreenlet + { + public: + BorrowedMainGreenlet(const OwnedMainGreenlet& it) : + _BorrowedGreenlet(it.borrow()) + {} + BorrowedMainGreenlet(PyGreenlet* it=nullptr) + : _BorrowedGreenlet(it) + {} + }; + + template + _OwnedGreenlet& _OwnedGreenlet::operator=(const BorrowedGreenlet& other) + { + return this->operator=(other.borrow()); + } + + + class ImmortalObject : public PyObjectPointer<> + { + private: + G_NO_ASSIGNMENT_OF_CLS(ImmortalObject); + public: + explicit ImmortalObject(PyObject* it) : PyObjectPointer<>(it) + { + } + + ImmortalObject(const ImmortalObject& other) + : PyObjectPointer<>(other.p) + { + + } + + /** + * Become the new owner of the object. Does not change the + * reference count. + */ + ImmortalObject& operator=(PyObject* it) + { + assert(this->p == nullptr); + this->p = it; + return *this; + } + + static ImmortalObject consuming(PyObject* it) + { + return ImmortalObject(it); + } + + inline operator PyObject*() const + { + return this->p; + } + }; + + class ImmortalString : public ImmortalObject + { + private: + G_NO_COPIES_OF_CLS(ImmortalString); + const char* str; + public: + ImmortalString(const char* const str) : + ImmortalObject(str ? Require(PyUnicode_InternFromString(str)) : nullptr) + { + this->str = str; + } + + inline ImmortalString& operator=(const char* const str) + { + if (!this->p) { + this->p = Require(PyUnicode_InternFromString(str)); + this->str = str; + } + else { + assert(this->str == str); + } + return *this; + } + + inline operator std::string() const + { + return this->str; + } + + }; + + class ImmortalEventName : public ImmortalString + { + private: + G_NO_COPIES_OF_CLS(ImmortalEventName); + public: + ImmortalEventName(const char* const str) : ImmortalString(str) + {} + }; + + class ImmortalException : public ImmortalObject + { + private: + G_NO_COPIES_OF_CLS(ImmortalException); + public: + ImmortalException(const char* const name, PyObject* base=nullptr) : + ImmortalObject(name + // Python 2.7 isn't const correct + ? Require(PyErr_NewException((char*)name, base, nullptr)) + : nullptr) + {} + + inline bool PyExceptionMatches() const + { + return PyErr_ExceptionMatches(this->p) > 0; + } + + }; + + template + inline OwnedObject PyObjectPointer::PyStr() const noexcept + { + if (!this->p) { + return OwnedObject(); + } + return OwnedObject::consuming(PyObject_Str(reinterpret_cast(this->p))); + } + + template + inline const std::string PyObjectPointer::as_str() const noexcept + { + // NOTE: This is not Python exception safe. + if (this->p) { + // The Python APIs return a cached char* value that's only valid + // as long as the original object stays around, and we're + // about to (probably) toss it. Hence the copy to std::string. + OwnedObject py_str = this->PyStr(); + if (!py_str) { + return "(nil)"; + } + return PyUnicode_AsUTF8(py_str.borrow()); + } + return "(nil)"; + } + + template + inline OwnedObject PyObjectPointer::PyGetAttr(const ImmortalObject& name) const noexcept + { + assert(this->p); + return OwnedObject::consuming(PyObject_GetAttr(reinterpret_cast(this->p), name)); + } + + template + inline OwnedObject PyObjectPointer::PyRequireAttr(const char* const name) const + { + assert(this->p); + return OwnedObject::consuming(Require(PyObject_GetAttrString(this->p, name), name)); + } + + template + inline OwnedObject PyObjectPointer::PyRequireAttr(const ImmortalString& name) const + { + assert(this->p); + return OwnedObject::consuming(Require( + PyObject_GetAttr( + reinterpret_cast(this->p), + name + ), + name + )); + } + + template + inline OwnedObject PyObjectPointer::PyCall(const BorrowedObject& arg) const + { + return this->PyCall(arg.borrow()); + } + + template + inline OwnedObject PyObjectPointer::PyCall(PyGreenlet* arg) const + { + return this->PyCall(reinterpret_cast(arg)); + } + + template + inline OwnedObject PyObjectPointer::PyCall(PyObject* arg) const + { + assert(this->p); + return OwnedObject::consuming(PyObject_CallFunctionObjArgs(this->p, arg, NULL)); + } + + template + inline OwnedObject PyObjectPointer::PyCall(const BorrowedObject args, + const BorrowedObject kwargs) const + { + assert(this->p); + return OwnedObject::consuming(PyObject_Call(this->p, args, kwargs)); + } + + template + inline OwnedObject PyObjectPointer::PyCall(const OwnedObject& args, + const OwnedObject& kwargs) const + { + assert(this->p); + return OwnedObject::consuming(PyObject_Call(this->p, args.borrow(), kwargs.borrow())); + } + + inline void + ListChecker(void * p) + { + if (!p) { + return; + } + if (!PyList_Check(p)) { + throw TypeError("Expected a list"); + } + } + + class OwnedList : public OwnedReference + { + private: + G_NO_ASSIGNMENT_OF_CLS(OwnedList); + public: + // TODO: Would like to use move. + explicit OwnedList(const OwnedObject& other) + : OwnedReference(other) + { + } + + OwnedList& operator=(const OwnedObject& other) + { + if (other && PyList_Check(other.p)) { + // Valid list. Own a new reference to it, discard the + // reference to what we did own. + PyObject* new_ptr = other.p; + Py_INCREF(new_ptr); + Py_XDECREF(this->p); + this->p = new_ptr; + } + else { + // Either the other object was NULL (an error) or it + // wasn't a list. Either way, we're now invalidated. + Py_XDECREF(this->p); + this->p = nullptr; + } + return *this; + } + + inline bool empty() const + { + return PyList_GET_SIZE(p) == 0; + } + + inline Py_ssize_t size() const + { + return PyList_GET_SIZE(p); + } + + inline BorrowedObject at(const Py_ssize_t index) const + { + return PyList_GET_ITEM(p, index); + } + + inline void clear() + { + PyList_SetSlice(p, 0, PyList_GET_SIZE(p), NULL); + } + }; + + // Use this to represent the module object used at module init + // time. + // This could either be a borrowed (Py2) or new (Py3) reference; + // either way, we don't want to do any memory management + // on it here, Python itself will handle that. + // XXX: Actually, that's not quite right. On Python 3, if an + // exception occurs before we return to the interpreter, this will + // leak; but all previous versions also had that problem. + class CreatedModule : public PyObjectPointer<> + { + private: + G_NO_COPIES_OF_CLS(CreatedModule); + public: + CreatedModule(PyModuleDef& mod_def) : PyObjectPointer<>( + Require(PyModule_Create(&mod_def))) + { + } + + // PyAddObject(): Add a reference to the object to the module. + // On return, the reference count of the object is unchanged. + // + // The docs warn that PyModule_AddObject only steals the + // reference on success, so if it fails after we've incref'd + // or allocated, we're responsible for the decref. + void PyAddObject(const char* name, const long new_bool) + { + OwnedObject p = OwnedObject::consuming(Require(PyBool_FromLong(new_bool))); + this->PyAddObject(name, p); + } + + void PyAddObject(const char* name, const OwnedObject& new_object) + { + // The caller already owns a reference they will decref + // when their variable goes out of scope, we still need to + // incref/decref. + this->PyAddObject(name, new_object.borrow()); + } + + void PyAddObject(const char* name, const ImmortalObject& new_object) + { + this->PyAddObject(name, new_object.borrow()); + } + + void PyAddObject(const char* name, PyTypeObject& type) + { + this->PyAddObject(name, reinterpret_cast(&type)); + } + + void PyAddObject(const char* name, PyObject* new_object) + { + Py_INCREF(new_object); + try { + Require(PyModule_AddObject(this->p, name, new_object)); + } + catch (const PyErrOccurred&) { + Py_DECREF(p); + throw; + } + } + }; + + class PyErrFetchParam : public PyObjectPointer<> + { + // Not an owned object, because we can't be initialized with + // one, and we only sometimes acquire ownership. + private: + G_NO_COPIES_OF_CLS(PyErrFetchParam); + public: + // To allow declaring these and passing them to + // PyErr_Fetch we implement the empty constructor, + // and the address operator. + PyErrFetchParam() : PyObjectPointer<>(nullptr) + { + } + + PyObject** operator&() + { + return &this->p; + } + + // This allows us to pass one directly without the &, + // BUT it has higher precedence than the bool operator + // if it's not explicit. + operator PyObject**() + { + return &this->p; + } + + // We don't want to be able to pass these to Py_DECREF and + // such so we don't have the implicit PyObject* conversion. + + inline PyObject* relinquish_ownership() + { + PyObject* result = this->p; + this->p = nullptr; + return result; + } + + ~PyErrFetchParam() + { + Py_XDECREF(p); + } + }; + + class OwnedErrPiece : public OwnedObject + { + private: + + public: + // Unlike OwnedObject, this increments the refcount. + OwnedErrPiece(PyObject* p=nullptr) : OwnedObject(p) + { + this->acquire(); + } + + PyObject** operator&() + { + return &this->p; + } + + inline operator PyObject*() const + { + return this->p; + } + + operator PyTypeObject*() const + { + return reinterpret_cast(this->p); + } + }; + + class PyErrPieces + { + private: + OwnedErrPiece type; + OwnedErrPiece instance; + OwnedErrPiece traceback; + bool restored; + public: + // Takes new references; if we're destroyed before + // restoring the error, we drop the references. + PyErrPieces(PyObject* t, PyObject* v, PyObject* tb) : + type(t), + instance(v), + traceback(tb), + restored(0) + { + this->normalize(); + } + + PyErrPieces() : + restored(0) + { + // PyErr_Fetch transfers ownership to us, so + // we don't actually need to INCREF; but we *do* + // need to DECREF if we're not restored. + PyErrFetchParam t, v, tb; + PyErr_Fetch(&t, &v, &tb); + type.steal(t.relinquish_ownership()); + instance.steal(v.relinquish_ownership()); + traceback.steal(tb.relinquish_ownership()); + } + + void PyErrRestore() + { + // can only do this once + assert(!this->restored); + this->restored = true; + PyErr_Restore( + this->type.relinquish_ownership(), + this->instance.relinquish_ownership(), + this->traceback.relinquish_ownership()); + assert(!this->type && !this->instance && !this->traceback); + } + + private: + void normalize() + { + // First, check the traceback argument, replacing None, + // with NULL + if (traceback.is_None()) { + traceback = nullptr; + } + + if (traceback && !PyTraceBack_Check(traceback.borrow())) { + throw PyErrOccurred(PyExc_TypeError, + "throw() third argument must be a traceback object"); + } + + if (PyExceptionClass_Check(type)) { + // If we just had a type, we'll now have a type and + // instance. + // The type's refcount will have gone up by one + // because of the instance and the instance will have + // a refcount of one. Either way, we owned, and still + // do own, exactly one reference. + PyErr_NormalizeException(&type, &instance, &traceback); + + } + else if (PyExceptionInstance_Check(type)) { + /* Raising an instance --- usually that means an + object that is a subclass of BaseException, but on + Python 2, that can also mean an arbitrary old-style + object. The value should be a dummy. */ + if (instance && !instance.is_None()) { + throw PyErrOccurred( + PyExc_TypeError, + "instance exception may not have a separate value"); + } + /* Normalize to raise , */ + this->instance = this->type; + this->type = PyExceptionInstance_Class(instance.borrow()); + + /* + It would be tempting to do this: + + Py_ssize_t type_count = Py_REFCNT(Py_TYPE(instance.borrow())); + this->type = PyExceptionInstance_Class(instance.borrow()); + assert(this->type.REFCNT() == type_count + 1); + + But that doesn't work on Python 2 in the case of + old-style instances: The result of Py_TYPE is going to + be the global shared that all + old-style classes have, while the return of Instance_Class() + will be the Python-level class object. The two are unrelated. + */ + } + else { + /* Not something you can raise. throw() fails. */ + PyErr_Format(PyExc_TypeError, + "exceptions must be classes, or instances, not %s", + Py_TYPE(type.borrow())->tp_name); + throw PyErrOccurred(); + } + } + }; + + // PyArg_Parse's O argument returns a borrowed reference. + class PyArgParseParam : public BorrowedObject + { + private: + G_NO_COPIES_OF_CLS(PyArgParseParam); + public: + explicit PyArgParseParam(PyObject* p=nullptr) : BorrowedObject(p) + { + } + + inline PyObject** operator&() + { + return &this->p; + } + }; + +};}; + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_slp_switch.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_slp_switch.hpp new file mode 100644 index 000000000..bd4b7ae1a --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_slp_switch.hpp @@ -0,0 +1,99 @@ +#ifndef GREENLET_SLP_SWITCH_HPP +#define GREENLET_SLP_SWITCH_HPP + +#include "greenlet_compiler_compat.hpp" +#include "greenlet_refs.hpp" + +/* + * the following macros are spliced into the OS/compiler + * specific code, in order to simplify maintenance. + */ +// We can save about 10% of the time it takes to switch greenlets if +// we thread the thread state through the slp_save_state() and the +// following slp_restore_state() calls from +// slp_switch()->g_switchstack() (which already needs to access it). +// +// However: +// +// that requires changing the prototypes and implementations of the +// switching functions. If we just change the prototype of +// slp_switch() to accept the argument and update the macros, without +// changing the implementation of slp_switch(), we get crashes on +// 64-bit Linux and 32-bit x86 (for reasons that aren't 100% clear); +// on the other hand, 64-bit macOS seems to be fine. Also, 64-bit +// windows is an issue because slp_switch is written fully in assembly +// and currently ignores its argument so some code would have to be +// adjusted there to pass the argument on to the +// ``slp_save_state_asm()`` function (but interestingly, because of +// the calling convention, the extra argument is just ignored and +// things function fine, albeit slower, if we just modify +// ``slp_save_state_asm`()` to fetch the pointer to pass to the +// macro.) +// +// Our compromise is to use a *glabal*, untracked, weak, pointer +// to the necessary thread state during the process of switching only. +// This is safe because we're protected by the GIL, and if we're +// running this code, the thread isn't exiting. This also nets us a +// 10-12% speed improvement. + +static greenlet::Greenlet* volatile switching_thread_state = nullptr; + + +extern "C" { +static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref); +static void GREENLET_NOINLINE(slp_restore_state_trampoline)(); +} + + +#define SLP_SAVE_STATE(stackref, stsizediff) \ +do { \ + assert(switching_thread_state); \ + stackref += STACK_MAGIC; \ + if (slp_save_state_trampoline((char*)stackref)) \ + return -1; \ + if (!switching_thread_state->active()) \ + return 1; \ + stsizediff = switching_thread_state->stack_start() - (char*)stackref; \ +} while (0) + +#define SLP_RESTORE_STATE() slp_restore_state_trampoline() + +#define SLP_EVAL +extern "C" { +#define slp_switch GREENLET_NOINLINE(slp_switch) +#include "slp_platformselect.h" +} +#undef slp_switch + +#ifndef STACK_MAGIC +# error \ + "greenlet needs to be ported to this platform, or taught how to detect your compiler properly." +#endif /* !STACK_MAGIC */ + + + +#ifdef EXTERNAL_ASM +/* CCP addition: Make these functions, to be called from assembler. + * The token include file for the given platform should enable the + * EXTERNAL_ASM define so that this is included. + */ +extern "C" { +intptr_t +slp_save_state_asm(intptr_t* ref) +{ + intptr_t diff; + SLP_SAVE_STATE(ref, diff); + return diff; +} + +void +slp_restore_state_asm(void) +{ + SLP_RESTORE_STATE(); +} + +extern int slp_switch(void); +}; +#endif + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/greenlet_thread_support.hpp b/venv/lib/python3.8/site-packages/greenlet/greenlet_thread_support.hpp new file mode 100644 index 000000000..3ded7d2b7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/greenlet_thread_support.hpp @@ -0,0 +1,31 @@ +#ifndef GREENLET_THREAD_SUPPORT_HPP +#define GREENLET_THREAD_SUPPORT_HPP + +/** + * Defines various utility functions to help greenlet integrate well + * with threads. This used to be needed when we supported Python + * 2.7 on Windows, which used a very old compiler. We wrote an + * alternative implementation using Python APIs and POSIX or Windows + * APIs, but that's no longer needed. So this file is a shadow of its + * former self --- but may be needed in the future. + */ + +#include +#include +#include + +#include "greenlet_compiler_compat.hpp" + +namespace greenlet { + typedef std::mutex Mutex; + typedef std::lock_guard LockGuard; + class LockInitError : public std::runtime_error + { + public: + LockInitError(const char* what) : std::runtime_error(what) + {}; + }; +}; + + +#endif /* GREENLET_THREAD_SUPPORT_HPP */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/__init__.py b/venv/lib/python3.8/site-packages/greenlet/platform/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/platform/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50ec21b6d5b605b611b03eca0ac8b921c44c6ce4 GIT binary patch literal 210 zcmYk0zY4-I5XMt*5TW=S4(SbD97G%(#YLP$YOif*{*lB=eIwt)r*QQV+?-56&=0=f zad+Rr9rJufqULuHsXi0_mBnTg?I#g6IgmxRzY&+lKfEu{8HIpX>;+2L;(*dvg*w2F zS03ybI-{vAY2y$pfiyUN>e0b~dH^Y^Z=@`i;6*@c + * Add support for strange GCC caller-save decisions + * (ported from switch_aarch64_gcc.h) + * 18-Aug-11 Alexey Borzenkov + * Correctly save rbp, csr and cw + * 01-Apr-04 Hye-Shik Chang + * Ported from i386 to amd64. + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for spark + * 31-Avr-02 Armin Rigo + * Added ebx, esi and edi register-saves. + * 01-Mar-02 Samual M. Rushing + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +/* #define STACK_MAGIC 3 */ +/* the above works fine with gcc 2.96, but 2.95.3 wants this */ +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "r12", "r13", "r14", "r15" + +static int +slp_switch(void) +{ + int err; + void* rbp; + void* rbx; + unsigned int csr; + unsigned short cw; + /* This used to be declared 'register', but that does nothing in + modern compilers and is explicitly forbidden in some new + standards. */ + long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("fstcw %0" : "=m" (cw)); + __asm__ volatile ("stmxcsr %0" : "=m" (csr)); + __asm__ volatile ("movq %%rbp, %0" : "=m" (rbp)); + __asm__ volatile ("movq %%rbx, %0" : "=m" (rbx)); + __asm__ ("movq %%rsp, %0" : "=g" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addq %0, %%rsp\n" + "addq %0, %%rbp\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + __asm__ volatile ("xorq %%rax, %%rax" : "=a" (err)); + } + __asm__ volatile ("movq %0, %%rbx" : : "m" (rbx)); + __asm__ volatile ("movq %0, %%rbp" : : "m" (rbp)); + __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); + __asm__ volatile ("fldcw %0" : : "m" (cw)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_gcc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_gcc.h new file mode 100644 index 000000000..655003aa1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_gcc.h @@ -0,0 +1,79 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 14-Aug-06 File creation. Ported from Arm Thumb. Sylvain Baro + * 3-Sep-06 Commented out saving of r1-r3 (r4 already commented out) as I + * read that these do not need to be saved. Also added notes and + * errors related to the frame pointer. Richard Tew. + * + * NOTES + * + * It is not possible to detect if fp is used or not, so the supplied + * switch function needs to support it, so that you can remove it if + * it does not apply to you. + * + * POSSIBLE ERRORS + * + * "fp cannot be used in asm here" + * + * - Try commenting out "fp" in REGS_TO_SAVE. + * + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 +#define REG_SP "sp" +#define REG_SPSP "sp,sp" +#ifdef __thumb__ +#define REG_FP "r7" +#define REG_FPFP "r7,r7" +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r8", "r9", "r10", "r11", "lr" +#else +#define REG_FP "fp" +#define REG_FPFP "fp,fp" +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r7", "r8", "r9", "r10", "lr" +#endif +#if defined(__SOFTFP__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL +#elif defined(__VFP_FP__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "d8", "d9", "d10", "d11", \ + "d12", "d13", "d14", "d15" +#elif defined(__MAVERICK__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "mvf4", "mvf5", "mvf6", "mvf7", \ + "mvf8", "mvf9", "mvf10", "mvf11", \ + "mvf12", "mvf13", "mvf14", "mvf15" +#else +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "f4", "f5", "f6", "f7" +#endif + +static int +#ifdef __GNUC__ +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +slp_switch(void) +{ + void *fp; + int *stackref, stsizediff; + int result; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("mov r0," REG_FP "\n\tstr r0,%0" : "=m" (fp) : : "r0"); + __asm__ ("mov %0," REG_SP : "=r" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add " REG_SPSP ",%0\n" + "add " REG_FPFP ",%0\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("ldr r0,%1\n\tmov " REG_FP ",r0\n\tmov %0, #0" : "=r" (result) : "m" (fp) : "r0"); + __asm__ volatile ("" : : : REGS_TO_SAVE); + return result; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_ios.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_ios.h new file mode 100644 index 000000000..9e640e15f --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm32_ios.h @@ -0,0 +1,67 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 31-May-15 iOS support. Ported from arm32. Proton + * + * NOTES + * + * It is not possible to detect if fp is used or not, so the supplied + * switch function needs to support it, so that you can remove it if + * it does not apply to you. + * + * POSSIBLE ERRORS + * + * "fp cannot be used in asm here" + * + * - Try commenting out "fp" in REGS_TO_SAVE. + * + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 +#define REG_SP "sp" +#define REG_SPSP "sp,sp" +#define REG_FP "r7" +#define REG_FPFP "r7,r7" +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r8", "r10", "r11", "lr" +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "d8", "d9", "d10", "d11", \ + "d12", "d13", "d14", "d15" + +static int +#ifdef __GNUC__ +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +slp_switch(void) +{ + void *fp; + int *stackref, stsizediff, result; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("str " REG_FP ",%0" : "=m" (fp)); + __asm__ ("mov %0," REG_SP : "=r" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add " REG_SPSP ",%0\n" + "add " REG_FPFP ",%0\n" + : + : "r" (stsizediff) + : REGS_TO_SAVE /* Clobber registers, force compiler to + * recalculate address of void *fp from REG_SP or REG_FP */ + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ( + "ldr " REG_FP ", %1\n\t" + "mov %0, #0" + : "=r" (result) + : "m" (fp) + : REGS_TO_SAVE /* Force compiler to restore saved registers after this */ + ); + return result; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_masm.asm b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_masm.asm new file mode 100644 index 000000000..29f9c225e --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_masm.asm @@ -0,0 +1,53 @@ + AREA switch_arm64_masm, CODE, READONLY; + GLOBAL slp_switch [FUNC] + EXTERN slp_save_state_asm + EXTERN slp_restore_state_asm + +slp_switch + ; push callee saved registers to stack + stp x19, x20, [sp, #-16]! + stp x21, x22, [sp, #-16]! + stp x23, x24, [sp, #-16]! + stp x25, x26, [sp, #-16]! + stp x27, x28, [sp, #-16]! + stp x29, x30, [sp, #-16]! + stp d8, d9, [sp, #-16]! + stp d10, d11, [sp, #-16]! + stp d12, d13, [sp, #-16]! + stp d14, d15, [sp, #-16]! + + ; call slp_save_state_asm with stack pointer + mov x0, sp + bl slp_save_state_asm + + ; early return for return value of 1 and -1 + cmp x0, #-1 + b.eq RETURN + cmp x0, #1 + b.eq RETURN + + ; increment stack and frame pointer + add sp, sp, x0 + add x29, x29, x0 + + bl slp_restore_state_asm + + ; store return value for successful completion of routine + mov x0, #0 + +RETURN + ; pop registers from stack + ldp d14, d15, [sp], #16 + ldp d12, d13, [sp], #16 + ldp d10, d11, [sp], #16 + ldp d8, d9, [sp], #16 + ldp x29, x30, [sp], #16 + ldp x27, x28, [sp], #16 + ldp x25, x26, [sp], #16 + ldp x23, x24, [sp], #16 + ldp x21, x22, [sp], #16 + ldp x19, x20, [sp], #16 + + ret + + END diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_masm.obj b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_masm.obj new file mode 100644 index 0000000000000000000000000000000000000000..f6f220e4310baaa9756110685ce7d6a2bdf90c37 GIT binary patch literal 746 zcma)4PiqrF6n~qoo~*PNZ{i+=wji4b#Xu2~wiJpGk)*AMF07NyB(9n1#+i+!)I;v| zBKQG3?t1eB$T(lYgGb4+lu{_QmQrebldQB_4?cMF-uu0IZ{DA2e8@rZ+e^~30488W z`B{KPh%yV{HEIpyeum^wI#7P*HfX)ux?9U&c!SFK-$o|OFtKn{Q|a-#N>2inp0-tb zCRKXAtg2SolaoLv$Ll&ds_Epj?SH+8K{t?XSjKaFsEy%yh}=V70BaHj zEY5kWk_zcJo{$SSUL~=K(zW|tnhm$rA z<%dZ$q?>RX*18r{!azhaYR1lVb;g;mR-6h!#F>|p@;aje%0a|CZrE7s+SXuT>MS=Y ziQPiMY-5DD&5+S7^H03fy7qt7ir}L34kK|h68s-MU>{lXOqlr?!Y=`~WwviNenFS_ zZalVSHh-0FU4lj#X8u5`ODn6@#{f?dHE&%XdP~_I3;$RS9-(z*>>ydkm*f@oWlUn~ Qn+^;lsEi}=H#!Q3U&UU-WdHyG literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_msvc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_msvc.h new file mode 100644 index 000000000..7ab7f45be --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_arm64_msvc.h @@ -0,0 +1,17 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 21-Oct-21 Niyas Sait + * First version to enable win/arm64 support. + */ + +#define STACK_REFPLUS 1 +#define STACK_MAGIC 0 + +/* Use the generic support for an external assembly language slp_switch function. */ +#define EXTERNAL_ASM + +#ifdef SLP_EVAL +/* This always uses the external masm assembly file. */ +#endif \ No newline at end of file diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_csky_gcc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_csky_gcc.h new file mode 100644 index 000000000..ac469d3a0 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_csky_gcc.h @@ -0,0 +1,48 @@ +#ifdef SLP_EVAL +#define STACK_MAGIC 0 +#define REG_FP "r8" +#ifdef __CSKYABIV2__ +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r7", "r9", "r10", "r11", "r15",\ + "r16", "r17", "r18", "r19", "r20", "r21", "r22",\ + "r23", "r24", "r25" + +#if defined (__CSKY_HARD_FLOAT__) || (__CSKY_VDSP__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "vr8", "vr9", "vr10", "vr11", "vr12",\ + "vr13", "vr14", "vr15" +#else +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL +#endif +#else +#define REGS_TO_SAVE "r9", "r10", "r11", "r12", "r13", "r15" +#endif + + +static int +#ifdef __GNUC__ +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +slp_switch(void) +{ + int *stackref, stsizediff; + int result; + + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mov %0, sp" : "=r" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addu sp,%0\n" + "addu "REG_FP",%0\n" + : + : "r" (stsizediff) + ); + + SLP_RESTORE_STATE(); + } + __asm__ volatile ("movi %0, 0" : "=r" (result)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + + return result; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_loongarch64_linux.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_loongarch64_linux.h new file mode 100644 index 000000000..9eaf34ef4 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_loongarch64_linux.h @@ -0,0 +1,31 @@ +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "s0", "s1", "s2", "s3", "s4", "s5", \ + "s6", "s7", "s8", "fp", \ + "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31" + +static int +slp_switch(void) +{ + int ret; + long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("move %0, $sp" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add.d $sp, $sp, %0\n\t" + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("move %0, $zero" : "=r" (ret) : ); + return ret; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_m68k_gcc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_m68k_gcc.h new file mode 100644 index 000000000..da761c2da --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_m68k_gcc.h @@ -0,0 +1,38 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 2014-01-06 Andreas Schwab + * File created. + */ + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "%d2", "%d3", "%d4", "%d5", "%d6", "%d7", \ + "%a2", "%a3", "%a4" + +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + void *fp, *a5; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("move.l %%fp, %0" : "=m"(fp)); + __asm__ volatile ("move.l %%a5, %0" : "=m"(a5)); + __asm__ ("move.l %%sp, %0" : "=r"(stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ("add.l %0, %%sp; add.l %0, %%fp" : : "r"(stsizediff)); + SLP_RESTORE_STATE(); + __asm__ volatile ("clr.l %0" : "=g" (err)); + } + __asm__ volatile ("move.l %0, %%a5" : : "m"(a5)); + __asm__ volatile ("move.l %0, %%fp" : : "m"(fp)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + return err; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_mips_unix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_mips_unix.h new file mode 100644 index 000000000..b9003e947 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_mips_unix.h @@ -0,0 +1,64 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 20-Sep-14 Matt Madison + * Re-code the saving of the gp register for MIPS64. + * 05-Jan-08 Thiemo Seufer + * Ported from ppc. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "$16", "$17", "$18", "$19", "$20", "$21", "$22", \ + "$23", "$30" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; +#ifdef __mips64 + uint64_t gpsave; +#endif + __asm__ __volatile__ ("" : : : REGS_TO_SAVE); +#ifdef __mips64 + __asm__ __volatile__ ("sd $28,%0" : "=m" (gpsave) : : ); +#endif + __asm__ ("move %0, $29" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ __volatile__ ( +#ifdef __mips64 + "daddu $29, %0\n" +#else + "addu $29, %0\n" +#endif + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } +#ifdef __mips64 + __asm__ __volatile__ ("ld $28,%0" : : "m" (gpsave) : ); +#endif + __asm__ __volatile__ ("" : : : REGS_TO_SAVE); + __asm__ __volatile__ ("move %0, $0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_aix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_aix.h new file mode 100644 index 000000000..e7e0b8776 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_aix.h @@ -0,0 +1,103 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 16-Oct-20 Jesse Gorzinski + * Copied from Linux PPC64 implementation + * 04-Sep-18 Alexey Borzenkov + * Workaround a gcc bug using manual save/restore of r30 + * 21-Mar-18 Tulio Magno Quites Machado Filho + * Added r30 to the list of saved registers in order to fully comply with + * both ppc64 ELFv1 ABI and the ppc64le ELFv2 ABI, that classify this + * register as a nonvolatile register used for local variables. + * 21-Mar-18 Laszlo Boszormenyi + * Save r2 (TOC pointer) manually. + * 10-Dec-13 Ulrich Weigand + * Support ELFv2 ABI. Save float/vector registers. + * 09-Mar-12 Michael Ellerman + * 64-bit implementation, copied from 32-bit. + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + * 31-Jul-12 Trevor Bowen + * Changed memory constraints to register only. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 6 + +#if defined(__ALTIVEC__) +#define ALTIVEC_REGS \ + "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", \ + "v28", "v29", "v30", "v31", +#else +#define ALTIVEC_REGS +#endif + +#define REGS_TO_SAVE "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "r31", \ + "fr14", "fr15", "fr16", "fr17", "fr18", "fr19", "fr20", "fr21", \ + "fr22", "fr23", "fr24", "fr25", "fr26", "fr27", "fr28", "fr29", \ + "fr30", "fr31", \ + ALTIVEC_REGS \ + "cr2", "cr3", "cr4" + +static int +slp_switch(void) +{ + int err; + long *stackref, stsizediff; + void * toc; + void * r30; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("std 2, %0" : "=m" (toc)); + __asm__ volatile ("std 30, %0" : "=m" (r30)); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("ld 30, %0" : : "m" (r30)); + __asm__ volatile ("ld 2, %0" : : "m" (toc)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_linux.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_linux.h new file mode 100644 index 000000000..3c324d00c --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc64_linux.h @@ -0,0 +1,105 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 04-Sep-18 Alexey Borzenkov + * Workaround a gcc bug using manual save/restore of r30 + * 21-Mar-18 Tulio Magno Quites Machado Filho + * Added r30 to the list of saved registers in order to fully comply with + * both ppc64 ELFv1 ABI and the ppc64le ELFv2 ABI, that classify this + * register as a nonvolatile register used for local variables. + * 21-Mar-18 Laszlo Boszormenyi + * Save r2 (TOC pointer) manually. + * 10-Dec-13 Ulrich Weigand + * Support ELFv2 ABI. Save float/vector registers. + * 09-Mar-12 Michael Ellerman + * 64-bit implementation, copied from 32-bit. + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + * 31-Jul-12 Trevor Bowen + * Changed memory constraints to register only. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#if _CALL_ELF == 2 +#define STACK_MAGIC 4 +#else +#define STACK_MAGIC 6 +#endif + +#if defined(__ALTIVEC__) +#define ALTIVEC_REGS \ + "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", \ + "v28", "v29", "v30", "v31", +#else +#define ALTIVEC_REGS +#endif + +#define REGS_TO_SAVE "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "r31", \ + "fr14", "fr15", "fr16", "fr17", "fr18", "fr19", "fr20", "fr21", \ + "fr22", "fr23", "fr24", "fr25", "fr26", "fr27", "fr28", "fr29", \ + "fr30", "fr31", \ + ALTIVEC_REGS \ + "cr2", "cr3", "cr4" + +static int +slp_switch(void) +{ + int err; + long *stackref, stsizediff; + void * toc; + void * r30; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("std 2, %0" : "=m" (toc)); + __asm__ volatile ("std 30, %0" : "=m" (r30)); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("ld 30, %0" : : "m" (r30)); + __asm__ volatile ("ld 2, %0" : : "m" (toc)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_aix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_aix.h new file mode 100644 index 000000000..6d93c1326 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_aix.h @@ -0,0 +1,87 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Mar-11 Floris Bruynooghe + * Do not add stsizediff to general purpose + * register (GPR) 30 as this is a non-volatile and + * unused by the PowerOpen Environment, therefore + * this was modifying a user register instead of the + * frame pointer (which does not seem to exist). + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_linux.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_linux.h new file mode 100644 index 000000000..e83ad70a5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_linux.h @@ -0,0 +1,84 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + * 31-Jul-12 Trevor Bowen + * Changed memory constraints to register only. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + "add 30, 30, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_macosx.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_macosx.h new file mode 100644 index 000000000..bd414c68e --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_macosx.h @@ -0,0 +1,82 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" + +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("; asm block 2\n\tmr %0, r1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "; asm block 3\n" + "\tmr r11, %0\n" + "\tadd r1, r1, r11\n" + "\tadd r30, r30, r11\n" + : /* no outputs */ + : "r" (stsizediff) + : "r11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_unix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_unix.h new file mode 100644 index 000000000..bb188080a --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_ppc_unix.h @@ -0,0 +1,82 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + "add 30, 30, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_riscv_unix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_riscv_unix.h new file mode 100644 index 000000000..e74f37afa --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_riscv_unix.h @@ -0,0 +1,36 @@ +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "s1", "s2", "s3", "s4", "s5", \ + "s6", "s7", "s8", "s9", "s10", "s11", "fs0", "fs1", \ + "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", \ + "fs10", "fs11" + +static int +slp_switch(void) +{ + long fp; + int ret; + long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("mv %0, fp" : "=r" (fp) : ); + __asm__ volatile ("mv %0, sp" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add sp, sp, %0\n\t" + "add fp, fp, %0\n\t" + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("ld fp, %0" : : "m" (fp)); + __asm__ volatile ("mv %0, zero" : "=r" (ret) : ); + return ret; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_s390_unix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_s390_unix.h new file mode 100644 index 000000000..9199367f8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_s390_unix.h @@ -0,0 +1,87 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 25-Jan-12 Alexey Borzenkov + * Fixed Linux/S390 port to work correctly with + * different optimization options both on 31-bit + * and 64-bit. Thanks to Stefan Raabe for lots + * of testing. + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 06-Oct-02 Gustavo Niemeyer + * Ported to Linux/S390. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#ifdef __s390x__ +#define STACK_MAGIC 20 /* 20 * 8 = 160 bytes of function call area */ +#else +#define STACK_MAGIC 24 /* 24 * 4 = 96 bytes of function call area */ +#endif + +/* Technically, r11-r13 also need saving, but function prolog starts + with stm(g) and since there are so many saved registers already + it won't be optimized, resulting in all r6-r15 being saved */ +#define REGS_TO_SAVE "r6", "r7", "r8", "r9", "r10", "r14", \ + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", \ + "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15" + +static int +slp_switch(void) +{ + int ret; + long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); +#ifdef __s390x__ + __asm__ volatile ("lgr %0, 15" : "=r" (stackref) : ); +#else + __asm__ volatile ("lr %0, 15" : "=r" (stackref) : ); +#endif + { + SLP_SAVE_STATE(stackref, stsizediff); +/* N.B. + r11 may be used as the frame pointer, and in that case it cannot be + clobbered and needs offsetting just like the stack pointer (but in cases + where frame pointer isn't used we might clobber it accidentally). What's + scary is that r11 is 2nd (and even 1st when GOT is used) callee saved + register that gcc would chose for surviving function calls. However, + since r6-r10 are clobbered above, their cost for reuse is reduced, so + gcc IRA will chose them over r11 (not seeing r11 is implicitly saved), + making it relatively safe to offset in all cases. :) */ + __asm__ volatile ( +#ifdef __s390x__ + "agr 15, %0\n\t" + "agr 11, %0" +#else + "ar 15, %0\n\t" + "ar 11, %0" +#endif + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("lhi %0, 0" : "=r" (ret) : ); + return ret; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_sh_gcc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_sh_gcc.h new file mode 100644 index 000000000..5ecc3b394 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_sh_gcc.h @@ -0,0 +1,36 @@ +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 +#define REGS_TO_SAVE "r8", "r9", "r10", "r11", "r13", \ + "fr12", "fr13", "fr14", "fr15" + +// r12 Global context pointer, GP +// r14 Frame pointer, FP +// r15 Stack pointer, SP + +static int +slp_switch(void) +{ + int err; + void* fp; + int *stackref, stsizediff; + __asm__ volatile("" : : : REGS_TO_SAVE); + __asm__ volatile("mov.l r14, %0" : "=m"(fp) : :); + __asm__("mov r15, %0" : "=r"(stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile( + "add %0, r15\n" + "add %0, r14\n" + : /* no outputs */ + : "r"(stsizediff)); + SLP_RESTORE_STATE(); + __asm__ volatile("mov r0, %0" : "=r"(err) : :); + } + __asm__ volatile("mov.l %0, r14" : : "m"(fp) :); + __asm__ volatile("" : : : REGS_TO_SAVE); + return err; +} + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_sparc_sun_gcc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_sparc_sun_gcc.h new file mode 100644 index 000000000..96990c391 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_sparc_sun_gcc.h @@ -0,0 +1,92 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 16-May-15 Alexey Borzenkov + * Move stack spilling code inside save/restore functions + * 30-Aug-13 Floris Bruynooghe + Clean the register windows again before returning. + This does not clobber the PIC register as it leaves + the current window intact and is required for multi- + threaded code to work correctly. + * 08-Mar-11 Floris Bruynooghe + * No need to set return value register explicitly + * before the stack and framepointer are adjusted + * as none of the other registers are influenced by + * this. Also don't needlessly clean the windows + * ('ta %0" :: "i" (ST_CLEAN_WINDOWS)') as that + * clobbers the gcc PIC register (%l7). + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * added support for SunOS sparc with gcc + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + + +#define STACK_MAGIC 0 + + +#if defined(__sparcv9) +#define SLP_FLUSHW __asm__ volatile ("flushw") +#else +#define SLP_FLUSHW __asm__ volatile ("ta 3") /* ST_FLUSH_WINDOWS */ +#endif + +/* On sparc we need to spill register windows inside save/restore functions */ +#define SLP_BEFORE_SAVE_STATE() SLP_FLUSHW +#define SLP_BEFORE_RESTORE_STATE() SLP_FLUSHW + + +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + + /* Put current stack pointer into stackref. + * Register spilling is done in save/restore. + */ + __asm__ volatile ("mov %%sp, %0" : "=r" (stackref)); + + { + /* Thou shalt put SLP_SAVE_STATE into a local block */ + /* Copy the current stack onto the heap */ + SLP_SAVE_STATE(stackref, stsizediff); + + /* Increment stack and frame pointer by stsizediff */ + __asm__ volatile ( + "add %0, %%sp, %%sp\n\t" + "add %0, %%fp, %%fp" + : : "r" (stsizediff)); + + /* Copy new stack from it's save store on the heap */ + SLP_RESTORE_STATE(); + + __asm__ volatile ("mov %1, %0" : "=r" (err) : "i" (0)); + return err; + } +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_x32_unix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x32_unix.h new file mode 100644 index 000000000..893369c7a --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x32_unix.h @@ -0,0 +1,63 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 17-Aug-12 Fantix King + * Ported from amd64. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "r12", "r13", "r14", "r15" + + +static int +slp_switch(void) +{ + void* ebp; + void* ebx; + unsigned int csr; + unsigned short cw; + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("fstcw %0" : "=m" (cw)); + __asm__ volatile ("stmxcsr %0" : "=m" (csr)); + __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp)); + __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx)); + __asm__ ("movl %%esp, %0" : "=g" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addl %0, %%esp\n" + "addl %0, %%ebp\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx)); + __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp)); + __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); + __asm__ volatile ("fldcw %0" : : "m" (cw)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_masm.asm b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_masm.asm new file mode 100644 index 000000000..f5c72a27d --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_masm.asm @@ -0,0 +1,111 @@ +; +; stack switching code for MASM on x641 +; Kristjan Valur Jonsson, sept 2005 +; + + +;prototypes for our calls +slp_save_state_asm PROTO +slp_restore_state_asm PROTO + + +pushxmm MACRO reg + sub rsp, 16 + .allocstack 16 + movaps [rsp], reg ; faster than movups, but we must be aligned + ; .savexmm128 reg, offset (don't know what offset is, no documentation) +ENDM +popxmm MACRO reg + movaps reg, [rsp] ; faster than movups, but we must be aligned + add rsp, 16 +ENDM + +pushreg MACRO reg + push reg + .pushreg reg +ENDM +popreg MACRO reg + pop reg +ENDM + + +.code +slp_switch PROC FRAME + ;realign stack to 16 bytes after return address push, makes the following faster + sub rsp,8 + .allocstack 8 + + pushxmm xmm15 + pushxmm xmm14 + pushxmm xmm13 + pushxmm xmm12 + pushxmm xmm11 + pushxmm xmm10 + pushxmm xmm9 + pushxmm xmm8 + pushxmm xmm7 + pushxmm xmm6 + + pushreg r15 + pushreg r14 + pushreg r13 + pushreg r12 + + pushreg rbp + pushreg rbx + pushreg rdi + pushreg rsi + + sub rsp, 10h ;allocate the singlefunction argument (must be multiple of 16) + .allocstack 10h +.endprolog + + lea rcx, [rsp+10h] ;load stack base that we are saving + call slp_save_state_asm ;pass stackpointer, return offset in eax + cmp rax, 1 + je EXIT1 + cmp rax, -1 + je EXIT2 + ;actual stack switch: + add rsp, rax + call slp_restore_state_asm + xor rax, rax ;return 0 + +EXIT: + + add rsp, 10h + popreg rsi + popreg rdi + popreg rbx + popreg rbp + + popreg r12 + popreg r13 + popreg r14 + popreg r15 + + popxmm xmm6 + popxmm xmm7 + popxmm xmm8 + popxmm xmm9 + popxmm xmm10 + popxmm xmm11 + popxmm xmm12 + popxmm xmm13 + popxmm xmm14 + popxmm xmm15 + + add rsp, 8 + ret + +EXIT1: + mov rax, 1 + jmp EXIT + +EXIT2: + sar rax, 1 + jmp EXIT + +slp_switch ENDP + +END \ No newline at end of file diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_masm.obj b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_masm.obj new file mode 100644 index 0000000000000000000000000000000000000000..64e3e6b898ec765d4e37075f7b1635ad24c9efa2 GIT binary patch literal 1078 zcmZ{j&ubG=5XWb`DJB@*%~BA=L%=;Gk}d_~52VO$4J4q2U~MY6&1RFl{E&?scGnt@ zn(9GNy!ihFEO@PV4?T&H9`x2*oO!!jlNJZwd!P4xlX&_;U$Bg3z>p zje>}2kp+MsxE|w5hOUr>YC~(=fz6fwPdZd5+Hlb^P3{;ZO@Yuv96GG&+Gx?QfclNd zhy2KN&~>fNnlHQRR;U1cMyQ?hlQ$~k<0KBbB<0uD2#PTjVo+na7Q;#m=@=3m;xJOa zs2V#)&Db`cY;WzTF9)11;SjkVQWE!?bPTC%x3h3^F2;aBns5!i%m4&-*h69;~AUpZR%rDpm!zuXY+kc zFCz-n*^4&c)5~}y3e?r-Evy*n*(lp9r%ti58Y#l5&)rDjx5EbRd}nC+_8znRzz&#& XZ_Fi+`GM=5Rl{n4%KxAK>jC@)Nz=zi literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_msvc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_msvc.h new file mode 100644 index 000000000..601ea5605 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x64_msvc.h @@ -0,0 +1,60 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 26-Sep-02 Christian Tismer + * again as a result of virtualized stack access, + * the compiler used less registers. Needed to + * explicit mention registers in order to get them saved. + * Thanks to Jeff Senn for pointing this out and help. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 01-Mar-02 Christian Tismer + * Initial final version after lots of iterations for i386. + */ + +/* Avoid alloca redefined warning on mingw64 */ +#ifndef alloca +#define alloca _alloca +#endif + +#define STACK_REFPLUS 1 +#define STACK_MAGIC 0 + +/* Use the generic support for an external assembly language slp_switch function. */ +#define EXTERNAL_ASM + +#ifdef SLP_EVAL +/* This always uses the external masm assembly file. */ +#endif + +/* + * further self-processing support + */ + +/* we have IsBadReadPtr available, so we can peek at objects */ +/* +#define STACKLESS_SPY + +#ifdef IMPLEMENT_STACKLESSMODULE +#include "Windows.h" +#define CANNOT_READ_MEM(p, bytes) IsBadReadPtr(p, bytes) + +static int IS_ON_STACK(void*p) +{ + int stackref; + intptr_t stackbase = ((intptr_t)&stackref) & 0xfffff000; + return (intptr_t)p >= stackbase && (intptr_t)p < stackbase + 0x00100000; +} + +#endif +*/ \ No newline at end of file diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_msvc.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_msvc.h new file mode 100644 index 000000000..0f3a59f52 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_msvc.h @@ -0,0 +1,326 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 26-Sep-02 Christian Tismer + * again as a result of virtualized stack access, + * the compiler used less registers. Needed to + * explicit mention registers in order to get them saved. + * Thanks to Jeff Senn for pointing this out and help. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 01-Mar-02 Christian Tismer + * Initial final version after lots of iterations for i386. + */ + +#define alloca _alloca + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +/* Some magic to quell warnings and keep slp_switch() from crashing when built + with VC90. Disable global optimizations, and the warning: frame pointer + register 'ebp' modified by inline assembly code. + + We used to just disable global optimizations ("g") but upstream stackless + Python, as well as stackman, turn off all optimizations. + +References: +https://github.com/stackless-dev/stackman/blob/dbc72fe5207a2055e658c819fdeab9731dee78b9/stackman/platforms/switch_x86_msvc.h +https://github.com/stackless-dev/stackless/blob/main-slp/Stackless/platf/switch_x86_msvc.h +*/ +#define WIN32_LEAN_AND_MEAN +#include + +#pragma optimize("", off) /* so that autos are stored on the stack */ +#pragma warning(disable:4731) +#pragma warning(disable:4733) /* disable warning about modifying FS[0] */ + +/** + * Most modern compilers and environments handle C++ exceptions without any + * special help from us. MSVC on 32-bit windows is an exception. There, C++ + * exceptions are dealt with using Windows' Structured Exception Handling + * (SEH). + * + * SEH is implemented as a singly linked list of nodes. The + * head of this list is stored in the Thread Information Block, which itself + * is pointed to from the FS register. It's the first field in the structure, + * or offset 0, so we can access it using assembly FS:[0], or the compiler + * intrinsics and field offset information from the headers (as we do below). + * Somewhat unusually, the tail of the list doesn't have prev == NULL, it has + * prev == 0xFFFFFFFF. + * + * SEH was designed for C, and traditionally uses the MSVC compiler + * intrinsincs __try{}/__except{}. It is also utilized for C++ exceptions by + * MSVC; there, every throw of a C++ exception raises a SEH error with the + * ExceptionCode 0xE06D7363; the SEH handler list is then traversed to + * deal with the exception. + * + * If the SEH list is corrupt, then when a C++ exception is thrown the program + * will abruptly exit with exit code 1. This does not use std::terminate(), so + * std::set_terminate() is useless to debug this. + * + * The SEH list is closely tied to the call stack; entering a function that + * uses __try{} or most C++ functions will push a new handler onto the front + * of the list. Returning from the function will remove the handler. Saving + * and restoring the head node of the SEH list (FS:[0]) per-greenlet is NOT + * ENOUGH to make SEH or exceptions work. + * + * Stack switching breaks SEH because the call stack no longer necessarily + * matches the SEH list. For example, given greenlet A that switches to + * greenlet B, at the moment of entering greenlet B, we will have any SEH + * handlers from greenlet A on the SEH list; greenlet B can then add its own + * handlers to the SEH list. When greenlet B switches back to greenlet A, + * greenlet B's handlers would still be on the SEH stack, but when switch() + * returns control to greenlet A, we have replaced the contents of the stack + * in memory, so all the address that greenlet B added to the SEH list are now + * invalid: part of the call stack has been unwound, but the SEH list was out + * of sync with the call stack. The net effect is that exception handling + * stops working. + * + * Thus, when switching greenlets, we need to be sure that the SEH list + * matches the effective call stack, "cutting out" any handlers that were + * pushed by the greenlet that switched out and which are no longer valid. + * + * The easiest way to do this is to capture the SEH list at the time the main + * greenlet for a thread is created, and, when initially starting a greenlet, + * start a new SEH list for it, which contains nothing but the handler + * established for the new greenlet itself, with the tail being the handlers + * for the main greenlet. If we then save and restore the SEH per-greenlet, + * they won't interfere with each others SEH lists. (No greenlet can unwind + * the call stack past the handlers established by the main greenlet). + * + * By observation, a new thread starts with three SEH handlers on the list. By + * the time we get around to creating the main greenlet, though, there can be + * many more, established by transient calls that lead to the creation of the + * main greenlet. Therefore, 3 is a magic constant telling us when to perform + * the initial slice. + * + * All of this can be debugged using a vectored exception handler, which + * operates independently of the SEH handler list, and is called first. + * Walking the SEH list at key points can also be helpful. + * + * References: + * https://en.wikipedia.org/wiki/Win32_Thread_Information_Block + * https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 + * https://docs.microsoft.com/en-us/cpp/cpp/try-except-statement?view=msvc-160 + * https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-160 + * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * https://docs.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler + * https://bytepointer.com/resources/pietrek_crash_course_depths_of_win32_seh.htm + */ +#define GREENLET_NEEDS_EXCEPTION_STATE_SAVED + + +typedef struct _GExceptionRegistration { + struct _GExceptionRegistration* prev; + void* handler_f; +} GExceptionRegistration; + +static void +slp_set_exception_state(const void *const seh_state) +{ + // Because the stack from from which we do this is ALSO a handler, and + // that one we want to keep, we need to relink the current SEH handler + // frame to point to this one, cutting out the middle men, as it were. + // + // Entering a try block doesn't change the SEH frame, but entering a + // function containing a try block does. + GExceptionRegistration* current_seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + current_seh_state->prev = (GExceptionRegistration*)seh_state; +} + + +static GExceptionRegistration* +x86_slp_get_third_oldest_handler() +{ + GExceptionRegistration* a = NULL; /* Closest to the top */ + GExceptionRegistration* b = NULL; /* second */ + GExceptionRegistration* c = NULL; + GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + a = b = c = seh_state; + + while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) { + if ((void*)seh_state->prev < (void*)100) { + fprintf(stderr, "\tERROR: Broken SEH chain.\n"); + return NULL; + } + a = b; + b = c; + c = seh_state; + + seh_state = seh_state->prev; + } + return a ? a : (b ? b : c); +} + + +static void* +slp_get_exception_state() +{ + // XXX: There appear to be three SEH handlers on the stack already at the + // start of the thread. Is that a guarantee? Almost certainly not. Yet in + // all observed cases it has been three. This is consistent with + // faulthandler off or on, and optimizations off or on. It may not be + // consistent with other operating system versions, though: we only have + // CI on one or two versions (don't ask what there are). + // In theory we could capture the number of handlers on the chain when + // PyInit__greenlet is called: there are probably only the default + // handlers at that point (unless we're embedded and people have used + // __try/__except or a C++ handler)? + return x86_slp_get_third_oldest_handler(); +} + +static int +slp_switch(void) +{ + /* MASM syntax is typically reversed from other assemblers. + It is usually + */ + int *stackref, stsizediff; + /* store the structured exception state for this stack */ + DWORD seh_state = __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + __asm mov stackref, esp; + /* modify EBX, ESI and EDI in order to get them preserved */ + __asm mov ebx, ebx; + __asm xchg esi, edi; + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm { + mov eax, stsizediff + add esp, eax + add ebp, eax + } + SLP_RESTORE_STATE(); + } + __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), seh_state); + return 0; +} + +/* re-enable ebp warning and global optimizations. */ +#pragma optimize("", on) +#pragma warning(default:4731) +#pragma warning(default:4733) /* disable warning about modifying FS[0] */ + + +#endif + +/* + * further self-processing support + */ + +/* we have IsBadReadPtr available, so we can peek at objects */ +#define STACKLESS_SPY + +#ifdef GREENLET_DEBUG + +#define CANNOT_READ_MEM(p, bytes) IsBadReadPtr(p, bytes) + +static int IS_ON_STACK(void*p) +{ + int stackref; + int stackbase = ((int)&stackref) & 0xfffff000; + return (int)p >= stackbase && (int)p < stackbase + 0x00100000; +} + +static void +x86_slp_show_seh_chain() +{ + GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + fprintf(stderr, "====== SEH Chain ======\n"); + while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) { + fprintf(stderr, "\tSEH_chain addr: %p handler: %p prev: %p\n", + seh_state, + seh_state->handler_f, seh_state->prev); + if ((void*)seh_state->prev < (void*)100) { + fprintf(stderr, "\tERROR: Broken chain.\n"); + break; + } + seh_state = seh_state->prev; + } + fprintf(stderr, "====== End SEH Chain ======\n"); + fflush(NULL); + return; +} + +//addVectoredExceptionHandler constants: +//CALL_FIRST means call this exception handler first; +//CALL_LAST means call this exception handler last +#define CALL_FIRST 1 +#define CALL_LAST 0 + +LONG WINAPI +GreenletVectorHandler(PEXCEPTION_POINTERS ExceptionInfo) +{ + // We get one of these for every C++ exception, with code + // E06D7363 + // This is a special value that means "C++ exception from MSVC" + // https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 + // + // Install in the module init function with: + // AddVectoredExceptionHandler(CALL_FIRST, GreenletVectorHandler); + PEXCEPTION_RECORD ExceptionRecord = ExceptionInfo->ExceptionRecord; + + fprintf(stderr, + "GOT VECTORED EXCEPTION:\n" + "\tExceptionCode : %p\n" + "\tExceptionFlags : %p\n" + "\tExceptionAddr : %p\n" + "\tNumberparams : %ld\n", + ExceptionRecord->ExceptionCode, + ExceptionRecord->ExceptionFlags, + ExceptionRecord->ExceptionAddress, + ExceptionRecord->NumberParameters + ); + if (ExceptionRecord->ExceptionFlags & 1) { + fprintf(stderr, "\t\tEH_NONCONTINUABLE\n" ); + } + if (ExceptionRecord->ExceptionFlags & 2) { + fprintf(stderr, "\t\tEH_UNWINDING\n" ); + } + if (ExceptionRecord->ExceptionFlags & 4) { + fprintf(stderr, "\t\tEH_EXIT_UNWIND\n" ); + } + if (ExceptionRecord->ExceptionFlags & 8) { + fprintf(stderr, "\t\tEH_STACK_INVALID\n" ); + } + if (ExceptionRecord->ExceptionFlags & 0x10) { + fprintf(stderr, "\t\tEH_NESTED_CALL\n" ); + } + if (ExceptionRecord->ExceptionFlags & 0x20) { + fprintf(stderr, "\t\tEH_TARGET_UNWIND\n" ); + } + if (ExceptionRecord->ExceptionFlags & 0x40) { + fprintf(stderr, "\t\tEH_COLLIDED_UNWIND\n" ); + } + fprintf(stderr, "\n"); + fflush(NULL); + for(DWORD i = 0; i < ExceptionRecord->NumberParameters; i++) { + fprintf(stderr, "\t\t\tParam %ld: %lX\n", i, ExceptionRecord->ExceptionInformation[i]); + } + + if (ExceptionRecord->NumberParameters == 3) { + fprintf(stderr, "\tAbout to traverse SEH chain\n"); + // C++ Exception records have 3 params. + x86_slp_show_seh_chain(); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + + + + +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_unix.h b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_unix.h new file mode 100644 index 000000000..493fa6baa --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/platform/switch_x86_unix.h @@ -0,0 +1,105 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 3-May-13 Ralf Schmitt + * Add support for strange GCC caller-save decisions + * (ported from switch_aarch64_gcc.h) + * 19-Aug-11 Alexey Borzenkov + * Correctly save ebp, ebx and cw + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'ebx' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for spark + * 31-Avr-02 Armin Rigo + * Added ebx, esi and edi register-saves. + * 01-Mar-02 Samual M. Rushing + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +/* #define STACK_MAGIC 3 */ +/* the above works fine with gcc 2.96, but 2.95.3 wants this */ +#define STACK_MAGIC 0 + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +# define ATTR_NOCLONE __attribute__((noclone)) +#else +# define ATTR_NOCLONE +#endif + +static int +slp_switch(void) +{ + int err; +#ifdef _WIN32 + void *seh; +#endif + void *ebp, *ebx; + unsigned short cw; + int *stackref, stsizediff; + __asm__ volatile ("" : : : "esi", "edi"); + __asm__ volatile ("fstcw %0" : "=m" (cw)); + __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp)); + __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx)); +#ifdef _WIN32 + __asm__ volatile ( + "movl %%fs:0x0, %%eax\n" + "movl %%eax, %0\n" + : "=m" (seh) + : + : "eax"); +#endif + __asm__ ("movl %%esp, %0" : "=g" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addl %0, %%esp\n" + "addl %0, %%ebp\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err)); + } +#ifdef _WIN32 + __asm__ volatile ( + "movl %0, %%eax\n" + "movl %%eax, %%fs:0x0\n" + : + : "m" (seh) + : "eax"); +#endif + __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx)); + __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp)); + __asm__ volatile ("fldcw %0" : : "m" (cw)); + __asm__ volatile ("" : : : "esi", "edi"); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.8/site-packages/greenlet/slp_platformselect.h b/venv/lib/python3.8/site-packages/greenlet/slp_platformselect.h new file mode 100644 index 000000000..494564826 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/slp_platformselect.h @@ -0,0 +1,75 @@ +/* + * Platform Selection for Stackless Python + */ +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MS_WIN32) && !defined(MS_WIN64) && defined(_M_IX86) && defined(_MSC_VER) +# include "platform/switch_x86_msvc.h" /* MS Visual Studio on X86 */ +#elif defined(MS_WIN64) && defined(_M_X64) && defined(_MSC_VER) || defined(__MINGW64__) +# include "platform/switch_x64_msvc.h" /* MS Visual Studio on X64 */ +#elif defined(MS_WIN64) && defined(_M_ARM64) +# include "platform/switch_arm64_msvc.h" /* MS Visual Studio on ARM64 */ +#elif defined(__GNUC__) && defined(__amd64__) && defined(__ILP32__) +# include "platform/switch_x32_unix.h" /* gcc on amd64 with x32 ABI */ +#elif defined(__GNUC__) && defined(__amd64__) +# include "platform/switch_amd64_unix.h" /* gcc on amd64 */ +#elif defined(__GNUC__) && defined(__i386__) +# include "platform/switch_x86_unix.h" /* gcc on X86 */ +#elif defined(__GNUC__) && defined(__powerpc64__) && (defined(__linux__) || defined(__FreeBSD__)) +# include "platform/switch_ppc64_linux.h" /* gcc on PowerPC 64-bit */ +#elif defined(__GNUC__) && defined(__PPC__) && (defined(__linux__) || defined(__FreeBSD__)) +# include "platform/switch_ppc_linux.h" /* gcc on PowerPC */ +#elif defined(__GNUC__) && defined(__POWERPC__) && defined(__APPLE__) +# include "platform/switch_ppc_macosx.h" /* Apple MacOS X on 32-bit PowerPC */ +#elif defined(__GNUC__) && defined(__powerpc64__) && defined(_AIX) +# include "platform/switch_ppc64_aix.h" /* gcc on AIX/PowerPC 64-bit */ +#elif defined(__GNUC__) && defined(_ARCH_PPC) && defined(_AIX) +# include "platform/switch_ppc_aix.h" /* gcc on AIX/PowerPC */ +#elif defined(__GNUC__) && defined(__powerpc__) && defined(__NetBSD__) +#include "platform/switch_ppc_unix.h" /* gcc on NetBSD/powerpc */ +#elif defined(__GNUC__) && defined(sparc) +# include "platform/switch_sparc_sun_gcc.h" /* SunOS sparc with gcc */ +#elif defined(__SUNPRO_C) && defined(sparc) && defined(sun) +# iiclude "platform/switch_sparc_sun_gcc.h" /* SunStudio on amd64 */ +#elif defined(__SUNPRO_C) && defined(__amd64__) && defined(sun) +# include "platform/switch_amd64_unix.h" /* SunStudio on amd64 */ +#elif defined(__SUNPRO_C) && defined(__i386__) && defined(sun) +# include "platform/switch_x86_unix.h" /* SunStudio on x86 */ +#elif defined(__GNUC__) && defined(__s390__) && defined(__linux__) +# include "platform/switch_s390_unix.h" /* Linux/S390 */ +#elif defined(__GNUC__) && defined(__s390x__) && defined(__linux__) +# include "platform/switch_s390_unix.h" /* Linux/S390 zSeries (64-bit) */ +#elif defined(__GNUC__) && defined(__arm__) +# ifdef __APPLE__ +# include +# endif +# if TARGET_OS_IPHONE +# include "platform/switch_arm32_ios.h" /* iPhone OS on arm32 */ +# else +# include "platform/switch_arm32_gcc.h" /* gcc using arm32 */ +# endif +#elif defined(__GNUC__) && defined(__mips__) && defined(__linux__) +# include "platform/switch_mips_unix.h" /* Linux/MIPS */ +#elif defined(__GNUC__) && defined(__aarch64__) +# include "platform/switch_aarch64_gcc.h" /* Aarch64 ABI */ +#elif defined(__GNUC__) && defined(__mc68000__) +# include "platform/switch_m68k_gcc.h" /* gcc on m68k */ +#elif defined(__GNUC__) && defined(__csky__) +#include "platform/switch_csky_gcc.h" /* gcc on csky */ +# elif defined(__GNUC__) && defined(__riscv) +# include "platform/switch_riscv_unix.h" /* gcc on RISC-V */ +#elif defined(__GNUC__) && defined(__alpha__) +# include "platform/switch_alpha_unix.h" /* gcc on DEC Alpha */ +#elif defined(MS_WIN32) && defined(__llvm__) && defined(__aarch64__) +# include "platform/switch_aarch64_gcc.h" /* LLVM Aarch64 ABI for Windows */ +#elif defined(__GNUC__) && defined(__loongarch64) && defined(__linux__) +# include "platform/switch_loongarch64_linux.h" /* LoongArch64 */ +#elif defined(__GNUC__) && defined(__sh__) +# include "platform/switch_sh_gcc.h" /* SuperH */ +#endif + +#ifdef __cplusplus +}; +#endif diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__init__.py b/venv/lib/python3.8/site-packages/greenlet/tests/__init__.py new file mode 100644 index 000000000..e69392e46 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/__init__.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +""" +Tests for greenlet. + +""" +import os +import sys +import unittest + +from gc import collect +from gc import get_objects +from threading import active_count as active_thread_count +from time import sleep +from time import time + +import psutil + +from greenlet import greenlet as RawGreenlet +from greenlet import getcurrent + +from greenlet._greenlet import get_pending_cleanup_count +from greenlet._greenlet import get_total_main_greenlets + +from . import leakcheck + +PY312 = sys.version_info[:2] >= (3, 12) +PY313 = sys.version_info[:2] >= (3, 13) + +WIN = sys.platform.startswith("win") +RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') +RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS +RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') +RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR +RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX') + +class TestCaseMetaClass(type): + # wrap each test method with + # a) leak checks + def __new__(cls, classname, bases, classDict): + # pylint and pep8 fight over what this should be called (mcs or cls). + # pylint gets it right, but we can't scope disable pep8, so we go with + # its convention. + # pylint: disable=bad-mcs-classmethod-argument + check_totalrefcount = True + + # Python 3: must copy, we mutate the classDict. Interestingly enough, + # it doesn't actually error out, but under 3.6 we wind up wrapping + # and re-wrapping the same items over and over and over. + for key, value in list(classDict.items()): + if key.startswith('test') and callable(value): + classDict.pop(key) + if check_totalrefcount: + value = leakcheck.wrap_refcount(value) + classDict[key] = value + return type.__new__(cls, classname, bases, classDict) + + +class TestCase(TestCaseMetaClass( + "NewBase", + (unittest.TestCase,), + {})): + + cleanup_attempt_sleep_duration = 0.001 + cleanup_max_sleep_seconds = 1 + + def wait_for_pending_cleanups(self, + initial_active_threads=None, + initial_main_greenlets=None): + initial_active_threads = initial_active_threads or self.threads_before_test + initial_main_greenlets = initial_main_greenlets or self.main_greenlets_before_test + sleep_time = self.cleanup_attempt_sleep_duration + # NOTE: This is racy! A Python-level thread object may be dead + # and gone, but the C thread may not yet have fired its + # destructors and added to the queue. There's no particular + # way to know that's about to happen. We try to watch the + # Python threads to make sure they, at least, have gone away. + # Counting the main greenlets, which we can easily do deterministically, + # also helps. + + # Always sleep at least once to let other threads run + sleep(sleep_time) + quit_after = time() + self.cleanup_max_sleep_seconds + # TODO: We could add an API that calls us back when a particular main greenlet is deleted? + # It would have to drop the GIL + while ( + get_pending_cleanup_count() + or active_thread_count() > initial_active_threads + or (not self.expect_greenlet_leak + and get_total_main_greenlets() > initial_main_greenlets)): + sleep(sleep_time) + if time() > quit_after: + print("Time limit exceeded.") + print("Threads: Waiting for only", initial_active_threads, + "-->", active_thread_count()) + print("MGlets : Waiting for only", initial_main_greenlets, + "-->", get_total_main_greenlets()) + break + collect() + + def count_objects(self, kind=list, exact_kind=True): + # pylint:disable=unidiomatic-typecheck + # Collect the garbage. + for _ in range(3): + collect() + if exact_kind: + return sum( + 1 + for x in get_objects() + if type(x) is kind + ) + # instances + return sum( + 1 + for x in get_objects() + if isinstance(x, kind) + ) + + greenlets_before_test = 0 + threads_before_test = 0 + main_greenlets_before_test = 0 + expect_greenlet_leak = False + + def count_greenlets(self): + """ + Find all the greenlets and subclasses tracked by the GC. + """ + return self.count_objects(RawGreenlet, False) + + def setUp(self): + # Ensure the main greenlet exists, otherwise the first test + # gets a false positive leak + super().setUp() + getcurrent() + self.threads_before_test = active_thread_count() + self.main_greenlets_before_test = get_total_main_greenlets() + self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test) + self.greenlets_before_test = self.count_greenlets() + + def tearDown(self): + if getattr(self, 'skipTearDown', False): + return + + self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test) + super().tearDown() + + def get_expected_returncodes_for_aborted_process(self): + import signal + # The child should be aborted in an unusual way. On POSIX + # platforms, this is done with abort() and signal.SIGABRT, + # which is reflected in a negative return value; however, on + # Windows, even though we observe the child print "Fatal + # Python error: Aborted" and in older versions of the C + # runtime "This application has requested the Runtime to + # terminate it in an unusual way," it always has an exit code + # of 3. This is interesting because 3 is the error code for + # ERROR_PATH_NOT_FOUND; BUT: the C runtime abort() function + # also uses this code. + # + # If we link to the static C library on Windows, the error + # code changes to '0xc0000409' (hex(3221226505)), which + # apparently is STATUS_STACK_BUFFER_OVERRUN; but "What this + # means is that nowadays when you get a + # STATUS_STACK_BUFFER_OVERRUN, it doesn’t actually mean that + # there is a stack buffer overrun. It just means that the + # application decided to terminate itself with great haste." + # + # + # On windows, we've also seen '0xc0000005' (hex(3221225477)). + # That's "Access Violation" + # + # See + # https://devblogs.microsoft.com/oldnewthing/20110519-00/?p=10623 + # and + # https://docs.microsoft.com/en-us/previous-versions/k089yyh0(v=vs.140)?redirectedfrom=MSDN + # and + # https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655 + expected_exit = ( + -signal.SIGABRT, + # But beginning on Python 3.11, the faulthandler + # that prints the C backtraces sometimes segfaults after + # reporting the exception but before printing the stack. + # This has only been seen on linux/gcc. + -signal.SIGSEGV, + ) if not WIN else ( + 3, + 0xc0000409, + 0xc0000005, + ) + return expected_exit + + def get_process_uss(self): + """ + Return the current process's USS in bytes. + + uss is available on Linux, macOS, Windows. Also known as + "Unique Set Size", this is the memory which is unique to a + process and which would be freed if the process was terminated + right now. + + If this is not supported by ``psutil``, this raises the + :exc:`unittest.SkipTest` exception. + """ + try: + return psutil.Process().memory_full_info().uss + except AttributeError as e: + raise unittest.SkipTest("uss not supported") from e + + def run_script(self, script_name, show_output=True): + import subprocess + script = os.path.join( + os.path.dirname(__file__), + script_name, + ) + + try: + return subprocess.check_output([sys.executable, script], + encoding='utf-8', + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as ex: + if show_output: + print('-----') + print('Failed to run script', script) + print('~~~~~') + print(ex.output) + print('------') + raise + + + def assertScriptRaises(self, script_name, exitcodes=None): + import subprocess + with self.assertRaises(subprocess.CalledProcessError) as exc: + output = self.run_script(script_name, show_output=False) + __traceback_info__ = output + # We're going to fail the assertion if we get here, at least + # preserve the output in the traceback. + + if exitcodes is None: + exitcodes = self.get_expected_returncodes_for_aborted_process() + self.assertIn(exc.exception.returncode, exitcodes) + return exc.exception diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67594ad9f35c93b05d82e591f445f6c4ca6694a6 GIT binary patch literal 5765 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..525eca6f258167dd58d01ffeed3cbe7fbfa8a2ef GIT binary patch literal 2077 zcmaJ>OK;mo5Z>iWH1)6|yG_xiDG{J8bg&5|m!b$-TW*p|;6v?Pga8D^UCERwQr%rS zwcuXj9NYgOAN@ai>ba*q^wLxRLJw_cma?P7Mk&p3clI%yZ|0j9&1OL0S^qW4E^CDR ziIdgGhsh3zY*0cN=@UkomD4`xz6D}~IGirr3)1&Sq+eq;bKa1?&s^rgd+ixvHRit| z%;$7W&+UEymNgcDC0JVOV5zePSQ<-9g9qF{r+tgpxy72l(7w$Ze2v@Z=*?L)!CQ;h z7VE6VTSM!DQc_vrjn;K;joYlvZP0hPGj0z(vj?p5H+APV#TEIx8NsUfqZ+N;+j)C0 z?EJ^9ci0B6om-SJ>wt7O=Z}L!E>%{H!mP+tmgG{+2BA6<jBO zmDFA`o$`Wpsdf@El9xodaQ^R~qoZ=dBR=QH8PB8d_zBO;>4X<5dY%kqG(A;EWwAAV zv^5l*4>G=`M3NqJ5uNbjB+9eFa?7LbuOgW#zBPsAWW;4Of&)d)Ripp|84Z&xk5^!b zm!ZeY(6^_j+D&3HD=;u=fg+ZGiX-npfMy$H4@5oyRY8EsQ`Q2C&5RHh8%G8b=Ngi| zx%X6*lMvo}Au9{sbp(16c%`NT?a5b}N{LId~-WXFv!#6<0Y z$Ob4*fD8o4Krua`vB^(Iy6aO z5B#+k_b`*)ruJD0M2s_~{i|}-0bHEUga8g`n(EfBlw2Ssp9oQkm2z>^>6D8mz$35; ziZxK$D+gnqD&2nn10nN{nY-=~U%+J9A!yYh%&ejalI1pv^yVK`@Pri=uu%c<2;P!By?03C;EIX8Yl%<5 zqqd>JydNKOdJAO9oE9)=?n5jl5&K=Qg0TfV#5$@CREUMZlPkBqWkMSk?t_>sed{i71*b=I~7@>ZVOKPkE^cp4Z#{P-N(&u^wy=SvnL!f;8=f1y*4c8nMWdcd7@k9Ws+$GhwIBZBhc7pr;>A-}D2EFL2omxl$NIr_cnext`I?-mZ? zUo6f&2-4_q%AHSS7_Ba&tJr`xaS62q6;B%#Yk*1=@k5y*oJk2nqXQz=wM_LxW%Ba% zFq&pnt;f+8d6V1KE9<7u0db{cwGaXeeIC-bGp!f8%x&zkxaeG?>I*c6tSP4(N{MmS z?uOiZ>yGVihdcklU5j&qy?D1O3)9(WrT3P99glBfWaFO&jd zT3=@S@4mC2M4e#r&iwhx%4QA>RxqoeX76ALbtC7P#y(~f&E%sg8!?jSqbZi2R4_6s z%V(h262y|#)#NGUeEf##%D_nCaaMq42QJJ`U$cM4HP3jZ#z;|!YJPc*wCb#9xh8>-idvi*`$UKPvdB5q3jjy;7V&y zrfS5EQjJQ^a2s+sdesyx{yd=lU(e=-Z|4nP)No61`o%wcV=bJfMS@9XJweMl_t;X07y8OP-#-8I= zDGI@QOmJb1c@JgJ!d(-!*cP^MKC+%8Tv5l<6`p9|SqDD%z*%t4P~+e(c+WCg9km)* z^M>$^*B8xeLXo9K7Gb?%?UmWwKT)89IM6cgLok@8eVt@!ptC?-Cb~Zgh7uqhgGP`J zrYXG3p|3#%I+=jl^!?We9>HJlH-by30t{p}F_8XLN=Wsuh?FEwGkh)mDhoawC788L z4$8klR#^9-Pe-*lDn#2W+-d`bX2O2YzF40bXPV20!iu@J=CuXgnQ$tfU|6)+ z*+koa>D(jmV-!dkr)jyP@ywn(3-*CLHS&~%Ut5SFN}cYkfpak2isUpso~<8&o=P)B zrD@hYpdrX(>VMmnHYSk{io&ZtUid@M%Lysm5-IBlY4cu_Mkrd^h-$;Pq`C^X%y1v| zWuYDxx=Mwvq3w@C@y@8lrqFw+Xg22ycD9@pQws04ZP`MfTp>XOns1aT|4F`06+c3d zqQK0O>nLw%KxeHN*u5Z0gGrpEH)gb&MLmi{){i2S0*7f!x`;Jgn8uh-n;?a!K$}LE p`3+}x`Hf~6@SaXGF&)D*%BmiquwCN7wrtCzW&4)TeaA;megYiu6?Oms literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c670606da23a4be600e7009e7f9d711737bd9ab4 GIT binary patch literal 1541 zcmaJ=&2HQ_5GJXAt#Rssg+_RNGWhQoEeexeKWisk6i>~@2?;=421rXn}j|0}RHw$TXl7EATCiHwrVgw&!;QntEyf5m%&aY* z5vAFnX^b>3bwL|3qw(m7jg~~kj;Y5+ZwdS*BgR9- z;TD8i_vjDuJHkk4{7b6Gn$R1A8j=<8+P#+|$lkeV4Z-6DDEjRh%o#&jNJ^$&d7Em#$67O@ zdCHDuYB!7rw=BO6vUI)+d2;eJs8Yd>OK`^ttAf4^fnb%Wg8!*gcv9ZA&VYFWiB&Q? zgb%StbgW|+b7@^jw)#Opku{OS7oROc`Lcs8hwj?HX4&^Tl@yADE%IHQPhBH zm>0E~och2^?fjVUTBIJSN_72FPr$GCWz2z`+JZeTayGTN>nyCw-okQYPCE;Uv3gjdE^fS;a_0aJI+jt)&KLXpQiTDb<9}>}M zK!9vUc%pAGJbd$0B!r5D9r93S@DiZ!nsR_1ehhsNuI2ku97rKk<~E2a;fI6uBaG^X zFTmenBon+3* BZ>|6U literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..035085997867cb7f2037d794f8d62346fa6e9146 GIT binary patch literal 1185 zcmah|&2G~`5Z+z?#Bo#F13v;G%tkGuy@;VUOzp(kdX7z!$4rJdRJ%+AdB&G>kA)n~ZYzJfUpf3_L7zZN-kfmVI$pq63 z-W4z~AOn+3KzDurH9C*sulF0l1=InCDxXv?gQ-%GnO_kpNu1^QTKRPrd^k=p>moU5 z{|7R{diw)X_2PIS%3k5t8z>Bu_Ivh4d`6&#tH)Br+=#hFz|rQ^HpvjQ+bf-;!=2r| z{p(iqU~lK`UQk`zTnUVYiek6z(kWDunXfeveWLIc|9a2O|PRF`#ub=5uU7MEqSY9)2@4pybB$>S|ybDMiyt>Cvm z$izD%+`G7S7X=c=X;OZZcxum`C6yLgi0;aR=yZCsCeFZUGg8y+XtsU`W~!g^V$womw90rg{)JyNSt87fD92BKjgXsGsFw0pf-6E!$Q4R7VmTw3`R=SHU_wl@h z>LJuS1eM_b8|q!O8aC$@FtLP8fJAS0@3xY++7@#L+;W_;+`Vk7- TC2DM83y+rV3!nSK!vcQ-zcd=e literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e37689fdbabcc2edfff11d33305f2b34fe07cf4 GIT binary patch literal 7336 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 OS@9~~ox5Qv4F3bweZpY? literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abd9fefc14b8724999c4116fc58b74640c6d3d4e GIT binary patch literal 5261 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 O#9!cUyfNNh6Z{vpz~YGj literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3991af5ba2c62344b1c012213d57fb20390d62be GIT binary patch literal 46477 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 TD*Qi>v$3J)hqesO7qtHe3s6bu literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e494dc11324d7d2e35cbf8ec1f9470a26036d3e GIT binary patch literal 4911 zcmb7ITXWmS72X9v5~65XRW5Cubc&|a#>`lx)6}^*8BcU!9Ctj5M^@s5(+&&)OA;ao z(7T{zX(VSRa!2%Ae?TWAJ?2;RrPGK01ibc1?|tf1zq0@(F;UwD8Z39Q*t2KP`ObF^ z^x36LRfg;G-N(KV&#ocN! zk~B?PCYxKrr#-zWN_;O3q47{AzEH|hIE&vC>bx8;hF-Lp5`Gc~)rk>ESPTtD3h$HP zvt#2JU6DGILwH>;j2q4;IMFp{TR1_Y1y&ymx#Nf@!zlE_G}^i5Y~oyD>?DvTA(;eU zjl#Zww8s!ucf!>1lTj3ql{~o_VoMoDJI)}HqUt9D66K{LxT)PGo_KhIJw5E|^`+=Q zXcTRFe%~2{-5!hx1)!|wKN^J+A{|(y59Pe{4OR6bvi6SC6SU!uV=8v-Vig(*pT;`<{B7~B`a>80sV-E20yz|3f~0hOEi*(N`sQX`?7Z@qv0eF6^5 zo&tUZl#cYeAwWR>kRl3U9C$7esTW4*cC-%)2v$Qn_y9J`{hM2HkU&!Lq9m!h579$d z#OsRs&1%)bwdgz$x%0G4?N>q??Hq`Xkc7-B{CFL}Rj+lNhY#I*I~x=N?q_Qc?yRl4 zcUzyg?mm2|jjtMxJ{6#(QZG)gk;IU&AI61%;tX%#P#UTYA7BO~MU0S(;O!gNNeKDK z+4K}VP7ix9|G5NkJIIZ%}Y}o8(Qs@EW}p=dnSP z0I^S;5~@=L?&J;?{m%`u=qoZrAEIh?{YA)W^ERk z`^KKJWd&g7KN+upzxyS1D=mG^_t;T6C>>+vmn=5EU}+^NAMjQ7$J)M$Ih)x0(dPjp zK3|ih$hSQE1z_N&03eR9;q3+jye`}r%7Z9R=)`s7#Kh0ZCDQS&|FdF3<=$i9gFM?f zp@dbR$rdJ*&~PU`S;15`dq*3-C1oNrQ|+j%21X#)=*1*M?;47vZnc+L*X z9UfEy`iLPD< z?X?{3lI(TzTQuRnU@#w@sjF0s)F?eE5EqsJ(*`6hPhbSLRyQ&pW|fuo*3w36wQfkVU;FZRtWf7!r9ieOVfixN%4`?J z9;QGrMvC$vE0_81J3lH?8WSPkBSy+M+MEACXOC^MG00CYqsP_q*gP_hiIg~c=GZvq z7Tf0-TYLPdbWHSyj1IEAm6pHePkH@J+GS>d(k;0uyU0yC5f`Hliye?CTaA zn_D(g_Fp*LH#t&zInqL%2TBEEQSQ=&3bMpVLXJ(6;S)H~^ zYbS5wL?>6#Y{*y8)@$+#ezMB_drJ@2TB}(V5rQIH!o`_A$x2ye`L=ueZfhee=V2`= z`DA50U0+(uYPXB8xz>{~&8&21D6*N=*8Pq3Prro7+G*EazFoJppRu6}#CB^pVk($tVs^GopN*-nIt#7&-@vO2KL&CY6JR@p)dwZSz{i z;xF?uH;^kAtSW!en&ZfpH-0Q$G;7vtgumDu2-nT3t~*GAQAGWk>pmKJQ87d6B);oP zQbqon8p_-9RcgosvQEvK_NL; zCWu}6xMt7ImJu#}Q*v?wr{l<^g<*#A$R`S6(!1bKxs?#peG3c4Rh^ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb49d3dc4d227eaf68e9bb860a01363a86b1271a GIT binary patch literal 11396 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 YXOjm%s7g=&R>DZ9)1PbfKjrBE2UgRjP5=M^ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd72b1bf85d4258712720ed787cdefba8af1cff8 GIT binary patch literal 890 zcmZuvy>in)5Z*hWk%#Iti=YtK?!Sl4IfFwhNOLk0Wm3W6)XiI zpcEUil%-tz8(4aTt(AAkYA<(nTZE|C%0F2Jtfr?~$bbLAN}jNqBXZZ&i&RiIK_jCo z(Q8XrH|-;6^xqoMeT5K$XABa5NcXhzq>PFb`kj9P`O^jD-!~ zCVdl5uk2`2bf(9hfky0S*s+?(P=|9Y=3$=o?;ywBm!ZilcBXWk4AF!`DkyTap`~$} z%I_=bPOqFlm=);({|4U+h{bareU}UVf}Z4)G|A0rm+|eo3wGR5V~94LX-$INL1GMQ z`{vUu$#tFhbd6lp+NRGnCKff{ufcqvxvdjJCq7q`&;b|l+jK|&_UB2N$K*f+;!rld z27R02ko@A=y^4F(CWzxA8Dku~MjVePYL@f98ON+|d!io^=e9lf*zQ<^=GVsRtHA`a eirc-`33xYCmV literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_throw.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_throw.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7565736a5a8befc0967496765b536b159fb14797 GIT binary patch literal 3984 zcmbtXTW=Fb6rPz~+iS-$w{UM^1(ae+ViH6P2qDU)v{DtRkgCNmtBrT;Y_i^UXU2h8 zj#SA=Z6DhHh2$~+qrYNb`@~;BYUw$%>nlzR)Ycx)&h^aqotwX%oh=af9QdQ+|5zgA zPkb5unDBBJ9(fytM?4ykD*m#7Hffbo;u)S9u%=Nppq=ruFG$sVM7*45y&#^&jh<1> zc=QP==U>7OWVuXZt06cK1Fl|D=)sN^F4cWk@)}O*e{hh&^9UY!5QHK-beF8Nb)!pu zBd{f1E}H_U#DxY|wYCsERB^WDCcpBTuWDj)#vj{xU~%~Dw{mqtOWjQI_28Jjf(UYpKrmi z+u*X&zgI=UeWaF-S`*Q;h1QnH!QuEwRG3ixK6YR1#<9Eb$Oj-esZx(rnMXYauo?(8 z!kfaJNkFbP}7_73a9>AQ>)=rfeLOUHXXWW!v3<2n}X`UJPX0ish>a6}Jm<~tIe zF^QrZ1#y`Y{Pstlf^9t^-{U*OnfSJ6EXOR`h|Rj|2W6&#i;Iujq4Jyjp%9VK#Eu9w z>6q4M@#G>1LK!X661Cvlv4?OhAWu#$aHU+h9Yi%Zkarg90@v+jv|%NO&A!f!R4eLh2bHnORA8SLuHDzKs!gLDzraUo3P8XWBa?Pz zymOzkQ8yK6-Frz~8^J`QgTQFym!Kgr=l~|Pb9NGe4L^7T?y@!XL~$55IszgtBtE$! z+B`1!(hsF_!y2y^AEn9V;d5WbrpH}xDouG!6CSt-+ezFQbTSflEP7a5L}zZAhGQgU zVIAHPty5kFk4UoAFXSgur-*xmP>)lV% z#+DxsDuAO7D#)_D45Cx|ne2e=Cy<24(y($N2`i8>7-kI5&?)HEq57gNfb=a}MRv`F z0wB)T+;(G4b!NgyG~K}FU>W^9Qur$L`14^F*@J4K>H6V_L$mtg=co%BF$Rp1&Lo|i zlcu3C%{q;UOGiacB>aXSx`C5aBO^g%@r^Wzwxh3$acy{6jSgFWm8*-WY1fy;Sr|?f z`*_gOF^AQczJ#8%w0M8jmdaG1L5mA0K0<*El=F!TVlaw}iQ_$XeF@3#BF;hGg^l7I z3aoOprN4#v(jR;yCu`(4cRkFL6$L}6i{&m z4v+IITdlPCLCpkGlLW^6!{;^LQhpRB(eeX59a3Y`xyLjou>n0JVOleALNgFXp))uZ z&DU+#VUILKBF2oGl>Jr86e)Y%IX{W%l=bg}WRIw9k04rOaWJPWKr%mx!wgn0HI{2`fwrNA5}uA$Hdtc8Z} zw1%&*h!fSFXCew4jsmJ$Fh4Ch5J8igf2a6eoWyVX2Kz&J3$`8@Ojk&yHN5bx_1#bu{R#?{l5b8vYrnf&_hf`})$$unw zHQ*gY34h8JDrR5+AFUYF2PNycHEUghny#S7Aj)B>r;R8k0;`)O1mdD; WJI2=SM8i8+KufHkEx0E{Eb|}r)Nwxm literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8e7a743e9754f84dcda037783fb8067b67f916a GIT binary patch literal 9549 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 RyCx4zPPz8vPmNbx^Z!C%e0%@^ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_version.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_version.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..faba9c7413eca27ff55b2149ff21559e87848f19 GIT binary patch literal 1559 zcmZ`(%Wm676rCA9#K`iahG8{banTmgLSlfVXaNU70w-t}fzdWe;}R$cS~HSqQA1{C zXxIdmMV)2Q{({t9|D#_q+ph8pUG-kdE(|msaPB&m~s*`PC0u=fEJo^ZSM!6G$0{}9#gn@(>gH0FdRbHzkpb4E@<6rD0i;t zg1sj4flCaTb=5bNEF4Pe{e~vIYpKod&k2WI@|Kr!Nb9ZWrenGnVM7Ui;E1>WjjzH?M8=Z zN~UvRgCs%KB(a`KQOsPUyM;kThSQm9;&!|jr>z)cF zEX^#dN)-S>>rL?d52E_8`uid}E#@K;wU}ihkDiKikr(A$NE1B=K2dpTP766K?++(R zh?7hVjY`1>6`c!t9_87|^_BahuOgipF)ZOYoeCXIK|$uiLqv-Gi}9cR=EZz5eock&h7kU)YCvF-~*+X!IfMI?%#Y zCD^VZ@UOF8SwOn1t3=AJ2Y0j5ZlrDMp1rBk+y*z1vKz^bD@lTx z)FPj#y9l!8oZXX1!WiLsP|N2CI4!F0>V|6Lypzu%Lgs$-g3uwC@ zbP;J%nMwhw!V~47z}&ZCi-SsL2I(t&pyS=6LOvJi3@QZ!j!&ct@iM}H$gT|@u9-eU Pbln31MZ^LcpmhHM^TM6U literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-38.pyc b/venv/lib/python3.8/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bf6b155e4930de8c79f1299b0a2a9685e73f827 GIT binary patch literal 1870 zcmbVM%We}f6!rByI)t=^@)95rAcV-G89@c=0#%g&!2)%`L!fL%)3K9=P9E6P7NT_3 zO0<8GEc+jPg}1C&vtz}*o;(Qx!AQOykH^RN9-rek^YaCU&*GP`t{uj{(qQs&aj=d} zuVN5PK*}n44;8RuUg3fZCv_Tb#Rc|^30HWBOnA~ga4MdFSFGfJ!_{o#8(=Jf30_N` zJ&v_Z==0>maj=d}-^L&rZGrG0_#vw}!Vxa^2rqo>Ju!#SfhoR~@%BquB~YEv7Q+V% zVpPCmY`TmgN7{#s^`Hv}tPlG<<2|R(fOT2V?eYUGuFnA@VmljC2@*8Hy&||P7!YOv zi9iGZIneA<{28|xEF}!WKY@)CrhFWY!;g>QR;wXH*^%3InTAi~u1s6)hHPfxi+EFq z?N8ZOtGV1>S*|K6H|ugaQ*p8_Rk$mgyJ1@29Q?9UUJZ3Ulgn+Kj%!kfHC)h4Wfo?{ zl#S6Ia;Idq-2P;OM`_&H6!F?=%;-Bd)_FcjZJCxuLdIgW>QUNC;#9Ad^UuQ#%A@Z? z$PzcfaMt*Gubw4aClJ&Sgzr<#LOkF zG?htaPRCkHl|B8i6Q?D|xLT%FHBZ?<>(~6w&X915QVeX0fm@^`um3s8DT$KcM-sh@ z1Cl06lJ`lRkU2efAWn}2I~J$%@aK}R7O+!`6o@NzhQe72OBhOAoul{j6vjfcOkc2- z)SGdV)pzCaNNLq1;kRaM`E|)uJwwY;uU})Rv19nmK0`IPabV5Iy3mJXb~JE|^^rCA z5t=WzWS7$)g6(rM<`G^5H5eJwn>HqH!{`{OR(%IUXDI`s@cn?kx9kBsf*x*2o(J~M z`Y)%@Gk)A|%cigdRthzlB|A!?;f&vosaIqOOWz4sMSLfT$YxHc<f?=^i^JN0_>A5N-C-ut`sx<@Bj465Ae_5WY>H~U7!>$Qn*H8%!z%YBT{5CBFCOj0Kuuiv)|A{)-$$JvXEhl Z#A_#ihlKZJp2Zqn=+^lN=LAsXKLPK0sK)>R literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/_test_extension.c b/venv/lib/python3.8/site-packages/greenlet/tests/_test_extension.c new file mode 100644 index 000000000..05e81c03a --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/_test_extension.c @@ -0,0 +1,231 @@ +/* This is a set of functions used by test_extension_interface.py to test the + * Greenlet C API. + */ + +#include "../greenlet.h" + +#ifndef Py_RETURN_NONE +# define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#define TEST_MODULE_NAME "_test_extension" + +static PyObject* +test_switch(PyObject* self, PyObject* greenlet) +{ + PyObject* result = NULL; + + if (greenlet == NULL || !PyGreenlet_Check(greenlet)) { + PyErr_BadArgument(); + return NULL; + } + + result = PyGreenlet_Switch((PyGreenlet*)greenlet, NULL, NULL); + if (result == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_AssertionError, + "greenlet.switch() failed for some reason."); + } + return NULL; + } + Py_INCREF(result); + return result; +} + +static PyObject* +test_switch_kwargs(PyObject* self, PyObject* args, PyObject* kwargs) +{ + PyGreenlet* g = NULL; + PyObject* result = NULL; + + PyArg_ParseTuple(args, "O!", &PyGreenlet_Type, &g); + + if (g == NULL || !PyGreenlet_Check(g)) { + PyErr_BadArgument(); + return NULL; + } + + result = PyGreenlet_Switch(g, NULL, kwargs); + if (result == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_AssertionError, + "greenlet.switch() failed for some reason."); + } + return NULL; + } + Py_XINCREF(result); + return result; +} + +static PyObject* +test_getcurrent(PyObject* self) +{ + PyGreenlet* g = PyGreenlet_GetCurrent(); + if (g == NULL || !PyGreenlet_Check(g) || !PyGreenlet_ACTIVE(g)) { + PyErr_SetString(PyExc_AssertionError, + "getcurrent() returned an invalid greenlet"); + Py_XDECREF(g); + return NULL; + } + Py_DECREF(g); + Py_RETURN_NONE; +} + +static PyObject* +test_setparent(PyObject* self, PyObject* arg) +{ + PyGreenlet* current; + PyGreenlet* greenlet = NULL; + + if (arg == NULL || !PyGreenlet_Check(arg)) { + PyErr_BadArgument(); + return NULL; + } + if ((current = PyGreenlet_GetCurrent()) == NULL) { + return NULL; + } + greenlet = (PyGreenlet*)arg; + if (PyGreenlet_SetParent(greenlet, current)) { + Py_DECREF(current); + return NULL; + } + Py_DECREF(current); + if (PyGreenlet_Switch(greenlet, NULL, NULL) == NULL) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject* +test_new_greenlet(PyObject* self, PyObject* callable) +{ + PyObject* result = NULL; + PyGreenlet* greenlet = PyGreenlet_New(callable, NULL); + + if (!greenlet) { + return NULL; + } + + result = PyGreenlet_Switch(greenlet, NULL, NULL); + Py_CLEAR(greenlet); + if (result == NULL) { + return NULL; + } + + Py_INCREF(result); + return result; +} + +static PyObject* +test_raise_dead_greenlet(PyObject* self) +{ + PyErr_SetString(PyExc_GreenletExit, "test GreenletExit exception."); + return NULL; +} + +static PyObject* +test_raise_greenlet_error(PyObject* self) +{ + PyErr_SetString(PyExc_GreenletError, "test greenlet.error exception"); + return NULL; +} + +static PyObject* +test_throw(PyObject* self, PyGreenlet* g) +{ + const char msg[] = "take that sucka!"; + PyObject* msg_obj = Py_BuildValue("s", msg); + PyGreenlet_Throw(g, PyExc_ValueError, msg_obj, NULL); + Py_DECREF(msg_obj); + if (PyErr_Occurred()) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject* +test_throw_exact(PyObject* self, PyObject* args) +{ + PyGreenlet* g = NULL; + PyObject* typ = NULL; + PyObject* val = NULL; + PyObject* tb = NULL; + + if (!PyArg_ParseTuple(args, "OOOO:throw", &g, &typ, &val, &tb)) { + return NULL; + } + + PyGreenlet_Throw(g, typ, val, tb); + if (PyErr_Occurred()) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyMethodDef test_methods[] = { + {"test_switch", + (PyCFunction)test_switch, + METH_O, + "Switch to the provided greenlet sending provided arguments, and \n" + "return the results."}, + {"test_switch_kwargs", + (PyCFunction)test_switch_kwargs, + METH_VARARGS | METH_KEYWORDS, + "Switch to the provided greenlet sending the provided keyword args."}, + {"test_getcurrent", + (PyCFunction)test_getcurrent, + METH_NOARGS, + "Test PyGreenlet_GetCurrent()"}, + {"test_setparent", + (PyCFunction)test_setparent, + METH_O, + "Se the parent of the provided greenlet and switch to it."}, + {"test_new_greenlet", + (PyCFunction)test_new_greenlet, + METH_O, + "Test PyGreenlet_New()"}, + {"test_raise_dead_greenlet", + (PyCFunction)test_raise_dead_greenlet, + METH_NOARGS, + "Just raise greenlet.GreenletExit"}, + {"test_raise_greenlet_error", + (PyCFunction)test_raise_greenlet_error, + METH_NOARGS, + "Just raise greenlet.error"}, + {"test_throw", + (PyCFunction)test_throw, + METH_O, + "Throw a ValueError at the provided greenlet"}, + {"test_throw_exact", + (PyCFunction)test_throw_exact, + METH_VARARGS, + "Throw exactly the arguments given at the provided greenlet"}, + {NULL, NULL, 0, NULL} +}; + + +#define INITERROR return NULL + +static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, + TEST_MODULE_NAME, + NULL, + 0, + test_methods, + NULL, + NULL, + NULL, + NULL}; + +PyMODINIT_FUNC +PyInit__test_extension(void) +{ + PyObject* module = NULL; + module = PyModule_Create(&moduledef); + + if (module == NULL) { + return NULL; + } + + PyGreenlet_Import(); + return module; +} diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/_test_extension.cpython-38-x86_64-linux-gnu.so b/venv/lib/python3.8/site-packages/greenlet/tests/_test_extension.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..cdf1f8f63887183ae312c91fca175dcfe9dae945 GIT binary patch literal 35904 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 Qru_Alk|E|Y*aeFJ0s +#include + +struct exception_t { + int depth; + exception_t(int depth) : depth(depth) {} +}; + +/* Functions are called via pointers to prevent inlining */ +static void (*p_test_exception_throw_nonstd)(int depth); +static void (*p_test_exception_throw_std)(); +static PyObject* (*p_test_exception_switch_recurse)(int depth, int left); + +static void +test_exception_throw_nonstd(int depth) +{ + throw exception_t(depth); +} + +static void +test_exception_throw_std() +{ + throw std::runtime_error("Thrown from an extension."); +} + +static PyObject* +test_exception_switch_recurse(int depth, int left) +{ + if (left > 0) { + return p_test_exception_switch_recurse(depth, left - 1); + } + + PyObject* result = NULL; + PyGreenlet* self = PyGreenlet_GetCurrent(); + if (self == NULL) + return NULL; + + try { + if (PyGreenlet_Switch(PyGreenlet_GET_PARENT(self), NULL, NULL) == NULL) { + Py_DECREF(self); + return NULL; + } + p_test_exception_throw_nonstd(depth); + PyErr_SetString(PyExc_RuntimeError, + "throwing C++ exception didn't work"); + } + catch (const exception_t& e) { + if (e.depth != depth) + PyErr_SetString(PyExc_AssertionError, "depth mismatch"); + else + result = PyLong_FromLong(depth); + } + catch (...) { + PyErr_SetString(PyExc_RuntimeError, "unexpected C++ exception"); + } + + Py_DECREF(self); + return result; +} + +/* test_exception_switch(int depth) + * - recurses depth times + * - switches to parent inside try/catch block + * - throws an exception that (expected to be caught in the same function) + * - verifies depth matches (exceptions shouldn't be caught in other greenlets) + */ +static PyObject* +test_exception_switch(PyObject* UNUSED(self), PyObject* args) +{ + int depth; + if (!PyArg_ParseTuple(args, "i", &depth)) + return NULL; + return p_test_exception_switch_recurse(depth, depth); +} + + +static PyObject* +py_test_exception_throw_nonstd(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + p_test_exception_throw_nonstd(0); + PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw"); + return NULL; +} + +static PyObject* +py_test_exception_throw_std(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + p_test_exception_throw_std(); + PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw"); + return NULL; +} + +static PyObject* +py_test_call(PyObject* self, PyObject* arg) +{ + PyObject* noargs = PyTuple_New(0); + PyObject* ret = PyObject_Call(arg, noargs, nullptr); + Py_DECREF(noargs); + return ret; +} + + + +/* test_exception_switch_and_do_in_g2(g2func) + * - creates new greenlet g2 to run g2func + * - switches to g2 inside try/catch block + * - verifies that no exception has been caught + * + * it is used together with test_exception_throw to verify that unhandled + * exceptions thrown in one greenlet do not propagate to other greenlet nor + * segfault the process. + */ +static PyObject* +test_exception_switch_and_do_in_g2(PyObject* self, PyObject* args) +{ + PyObject* g2func = NULL; + PyObject* result = NULL; + + if (!PyArg_ParseTuple(args, "O", &g2func)) + return NULL; + PyGreenlet* g2 = PyGreenlet_New(g2func, NULL); + if (!g2) { + return NULL; + } + + try { + result = PyGreenlet_Switch(g2, NULL, NULL); + if (!result) { + return NULL; + } + } + catch (const exception_t& e) { + /* if we are here the memory can be already corrupted and the program + * might crash before below py-level exception might become printed. + * -> print something to stderr to make it clear that we had entered + * this catch block. + * See comments in inner_bootstrap() + */ +#if defined(WIN32) || defined(_WIN32) + fprintf(stderr, "C++ exception unexpectedly caught in g1\n"); + PyErr_SetString(PyExc_AssertionError, "C++ exception unexpectedly caught in g1"); + Py_XDECREF(result); + return NULL; +#else + throw; +#endif + } + + Py_XDECREF(result); + Py_RETURN_NONE; +} + +static PyMethodDef test_methods[] = { + {"test_exception_switch", + (PyCFunction)&test_exception_switch, + METH_VARARGS, + "Switches to parent twice, to test exception handling and greenlet " + "switching."}, + {"test_exception_switch_and_do_in_g2", + (PyCFunction)&test_exception_switch_and_do_in_g2, + METH_VARARGS, + "Creates new greenlet g2 to run g2func and switches to it inside try/catch " + "block. Used together with test_exception_throw to verify that unhandled " + "C++ exceptions thrown in a greenlet doe not corrupt memory."}, + {"test_exception_throw_nonstd", + (PyCFunction)&py_test_exception_throw_nonstd, + METH_VARARGS, + "Throws non-standard C++ exception. Calling this function directly should abort the process." + }, + {"test_exception_throw_std", + (PyCFunction)&py_test_exception_throw_std, + METH_VARARGS, + "Throws standard C++ exception. Calling this function directly should abort the process." + }, + {"test_call", + (PyCFunction)&py_test_call, + METH_O, + "Call the given callable. Unlike calling it directly, this creates a " + "new C-level stack frame, which may be helpful in testing." + }, + {NULL, NULL, 0, NULL} +}; + + +static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, + "greenlet.tests._test_extension_cpp", + NULL, + 0, + test_methods, + NULL, + NULL, + NULL, + NULL}; + +PyMODINIT_FUNC +PyInit__test_extension_cpp(void) +{ + PyObject* module = NULL; + + module = PyModule_Create(&moduledef); + + if (module == NULL) { + return NULL; + } + + PyGreenlet_Import(); + if (_PyGreenlet_API == NULL) { + return NULL; + } + + p_test_exception_throw_nonstd = test_exception_throw_nonstd; + p_test_exception_throw_std = test_exception_throw_std; + p_test_exception_switch_recurse = test_exception_switch_recurse; + + return module; +} diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/_test_extension_cpp.cpython-38-x86_64-linux-gnu.so b/venv/lib/python3.8/site-packages/greenlet/tests/_test_extension_cpp.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..d087dbe322acc05ffc920214c4af7d9eab1225ba GIT binary patch literal 57640 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 appearing to return, even though +# it has yet to run. +print('In main with', x, flush=True) +g.switch() +print('RESULTS', results) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/fail_cpp_exception.py b/venv/lib/python3.8/site-packages/greenlet/tests/fail_cpp_exception.py new file mode 100644 index 000000000..fa4dc2eb3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/fail_cpp_exception.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Helper for testing a C++ exception throw aborts the process. + +Takes one argument, the name of the function in :mod:`_test_extension_cpp` to call. +""" +import sys +import greenlet +from greenlet.tests import _test_extension_cpp +print('fail_cpp_exception is running') + +def run_unhandled_exception_in_greenlet_aborts(): + def _(): + _test_extension_cpp.test_exception_switch_and_do_in_g2( + _test_extension_cpp.test_exception_throw_nonstd + ) + g1 = greenlet.greenlet(_) + g1.switch() + + +func_name = sys.argv[1] +try: + func = getattr(_test_extension_cpp, func_name) +except AttributeError: + if func_name == run_unhandled_exception_in_greenlet_aborts.__name__: + func = run_unhandled_exception_in_greenlet_aborts + elif func_name == 'run_as_greenlet_target': + g = greenlet.greenlet(_test_extension_cpp.test_exception_throw_std) + func = g.switch + else: + raise +print('raising', func, flush=True) +func() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/fail_initialstub_already_started.py b/venv/lib/python3.8/site-packages/greenlet/tests/fail_initialstub_already_started.py new file mode 100644 index 000000000..c1a44efd2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/fail_initialstub_already_started.py @@ -0,0 +1,78 @@ +""" +Testing initialstub throwing an already started exception. +""" + +import greenlet + +a = None +b = None +c = None +main = greenlet.getcurrent() + +# If we switch into a dead greenlet, +# we go looking for its parents. +# if a parent is not yet started, we start it. + +results = [] + +def a_run(*args): + #results.append('A') + results.append(('Begin A', args)) + + +def c_run(): + results.append('Begin C') + b.switch('From C') + results.append('C done') + +class A(greenlet.greenlet): pass + +class B(greenlet.greenlet): + doing_it = False + def __getattribute__(self, name): + if name == 'run' and not self.doing_it: + assert greenlet.getcurrent() is c + self.doing_it = True + results.append('Switch to b from B.__getattribute__ in ' + + type(greenlet.getcurrent()).__name__) + b.switch() + results.append('B.__getattribute__ back from main in ' + + type(greenlet.getcurrent()).__name__) + if name == 'run': + name = '_B_run' + return object.__getattribute__(self, name) + + def _B_run(self, *arg): + results.append(('Begin B', arg)) + results.append('_B_run switching to main') + main.switch('From B') + +class C(greenlet.greenlet): + pass +a = A(a_run) +b = B(parent=a) +c = C(c_run, b) + +# Start a child; while running, it will start B, +# but starting B will ALSO start B. +result = c.switch() +results.append(('main from c', result)) + +# Switch back to C, which was in the middle of switching +# already. This will throw the ``GreenletStartedWhileInPython`` +# exception, which results in parent A getting started (B is finished) +c.switch() + +results.append(('A dead?', a.dead, 'B dead?', b.dead, 'C dead?', c.dead)) + +# A and B should both be dead now. +assert a.dead +assert b.dead +assert not c.dead + +result = c.switch() +results.append(('main from c.2', result)) +# Now C is dead +assert c.dead + +print("RESULTS:", results) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/fail_slp_switch.py b/venv/lib/python3.8/site-packages/greenlet/tests/fail_slp_switch.py new file mode 100644 index 000000000..09905269f --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/fail_slp_switch.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +A test helper for seeing what happens when slp_switch() +fails. +""" +# pragma: no cover + +import greenlet + + +print('fail_slp_switch is running', flush=True) + +runs = [] +def func(): + runs.append(1) + greenlet.getcurrent().parent.switch() + runs.append(2) + greenlet.getcurrent().parent.switch() + runs.append(3) + +g = greenlet._greenlet.UnswitchableGreenlet(func) +g.switch() +assert runs == [1] +g.switch() +assert runs == [1, 2] +g.force_slp_switch_error = True + +# This should crash. +g.switch() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets.py b/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets.py new file mode 100644 index 000000000..e151b19a6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets.py @@ -0,0 +1,44 @@ +""" +Uses a trace function to switch greenlets at unexpected times. + +In the trace function, we switch from the current greenlet to another +greenlet, which switches +""" +import greenlet + +g1 = None +g2 = None + +switch_to_g2 = False + +def tracefunc(*args): + print('TRACE', *args) + global switch_to_g2 + if switch_to_g2: + switch_to_g2 = False + g2.switch() + print('\tLEAVE TRACE', *args) + +def g1_run(): + print('In g1_run') + global switch_to_g2 + switch_to_g2 = True + from_parent = greenlet.getcurrent().parent.switch() + print('Return to g1_run') + print('From parent', from_parent) + +def g2_run(): + #g1.switch() + greenlet.getcurrent().parent.switch() + +greenlet.settrace(tracefunc) + +g1 = greenlet.greenlet(g1_run) +g2 = greenlet.greenlet(g2_run) + +# This switch didn't actually finish! +# And if it did, it would raise TypeError +# because g1_run() doesn't take any arguments. +g1.switch(1) +print('Back in main') +g1.switch(2) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets2.py b/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets2.py new file mode 100644 index 000000000..1f6b66bc6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_three_greenlets2.py @@ -0,0 +1,55 @@ +""" +Like fail_switch_three_greenlets, but the call into g1_run would actually be +valid. +""" +import greenlet + +g1 = None +g2 = None + +switch_to_g2 = True + +results = [] + +def tracefunc(*args): + results.append(('trace', args[0])) + print('TRACE', *args) + global switch_to_g2 + if switch_to_g2: + switch_to_g2 = False + g2.switch('g2 from tracefunc') + print('\tLEAVE TRACE', *args) + +def g1_run(arg): + results.append(('g1 arg', arg)) + print('In g1_run') + from_parent = greenlet.getcurrent().parent.switch('from g1_run') + results.append(('g1 from parent', from_parent)) + return 'g1 done' + +def g2_run(arg): + #g1.switch() + results.append(('g2 arg', arg)) + parent = greenlet.getcurrent().parent.switch('from g2_run') + global switch_to_g2 + switch_to_g2 = False + results.append(('g2 from parent', parent)) + return 'g2 done' + + +greenlet.settrace(tracefunc) + +g1 = greenlet.greenlet(g1_run) +g2 = greenlet.greenlet(g2_run) + +x = g1.switch('g1 from main') +results.append(('main g1', x)) +print('Back in main', x) +x = g1.switch('g2 from main') +results.append(('main g2', x)) +print('back in amain again', x) +x = g1.switch('g1 from main 2') +results.append(('main g1.2', x)) +x = g2.switch() +results.append(('main g2.2', x)) +print("RESULTS:", results) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_two_greenlets.py b/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_two_greenlets.py new file mode 100644 index 000000000..3e52345a6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/fail_switch_two_greenlets.py @@ -0,0 +1,41 @@ +""" +Uses a trace function to switch greenlets at unexpected times. + +In the trace function, we switch from the current greenlet to another +greenlet, which switches +""" +import greenlet + +g1 = None +g2 = None + +switch_to_g2 = False + +def tracefunc(*args): + print('TRACE', *args) + global switch_to_g2 + if switch_to_g2: + switch_to_g2 = False + g2.switch() + print('\tLEAVE TRACE', *args) + +def g1_run(): + print('In g1_run') + global switch_to_g2 + switch_to_g2 = True + greenlet.getcurrent().parent.switch() + print('Return to g1_run') + print('Falling off end of g1_run') + +def g2_run(): + g1.switch() + print('Falling off end of g2') + +greenlet.settrace(tracefunc) + +g1 = greenlet.greenlet(g1_run) +g2 = greenlet.greenlet(g2_run) + +g1.switch() +print('Falling off end of main') +g2.switch() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/leakcheck.py b/venv/lib/python3.8/site-packages/greenlet/tests/leakcheck.py new file mode 100644 index 000000000..a5152fb26 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/leakcheck.py @@ -0,0 +1,319 @@ +# Copyright (c) 2018 gevent community +# Copyright (c) 2021 greenlet community +# +# This was originally part of gevent's test suite. The main author +# (Jason Madden) vendored a copy of it into greenlet. +# +# 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. +from __future__ import print_function + +import os +import sys +import gc + +from functools import wraps +import unittest + + +import objgraph + +# graphviz 0.18 (Nov 7 2021), available only on Python 3.6 and newer, +# has added type hints (sigh). It wants to use ``typing.Literal`` for +# some stuff, but that's only available on Python 3.9+. If that's not +# found, it creates a ``unittest.mock.MagicMock`` object and annotates +# with that. These are GC'able objects, and doing almost *anything* +# with them results in an explosion of objects. For example, trying to +# compare them for equality creates new objects. This causes our +# leakchecks to fail, with reports like: +# +# greenlet.tests.leakcheck.LeakCheckError: refcount increased by [337, 1333, 343, 430, 530, 643, 769] +# _Call 1820 +546 +# dict 4094 +76 +# MagicProxy 585 +73 +# tuple 2693 +66 +# _CallList 24 +3 +# weakref 1441 +1 +# function 5996 +1 +# type 736 +1 +# cell 592 +1 +# MagicMock 8 +1 +# +# To avoid this, we *could* filter this type of object out early. In +# principle it could leak, but we don't use mocks in greenlet, so it +# doesn't leak from us. However, a further issue is that ``MagicMock`` +# objects have subobjects that are also GC'able, like ``_Call``, and +# those create new mocks of their own too. So we'd have to filter them +# as well, and they're not public. That's OK, we can workaround the +# problem by being very careful to never compare by equality or other +# user-defined operators, only using object identity or other builtin +# functions. + +RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') +RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS +RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') +RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR +RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX') +SKIP_LEAKCHECKS = RUNNING_ON_MANYLINUX or os.environ.get('GREENLET_SKIP_LEAKCHECKS') +SKIP_FAILING_LEAKCHECKS = os.environ.get('GREENLET_SKIP_FAILING_LEAKCHECKS') +ONLY_FAILING_LEAKCHECKS = os.environ.get('GREENLET_ONLY_FAILING_LEAKCHECKS') + +def ignores_leakcheck(func): + """ + Ignore the given object during leakchecks. + + Can be applied to a method, in which case the method will run, but + will not be subject to leak checks. + + If applied to a class, the entire class will be skipped during leakchecks. This + is intended to be used for classes that are very slow and cause problems such as + test timeouts; typically it will be used for classes that are subclasses of a base + class and specify variants of behaviour (such as pool sizes). + """ + func.ignore_leakcheck = True + return func + +def fails_leakcheck(func): + """ + Mark that the function is known to leak. + """ + func.fails_leakcheck = True + if SKIP_FAILING_LEAKCHECKS: + func = unittest.skip("Skipping known failures")(func) + return func + +class LeakCheckError(AssertionError): + pass + +if hasattr(sys, 'getobjects'): + # In a Python build with ``--with-trace-refs``, make objgraph + # trace *all* the objects, not just those that are tracked by the + # GC + class _MockGC(object): + def get_objects(self): + return sys.getobjects(0) # pylint:disable=no-member + def __getattr__(self, name): + return getattr(gc, name) + objgraph.gc = _MockGC() + fails_strict_leakcheck = fails_leakcheck +else: + def fails_strict_leakcheck(func): + """ + Decorator for a function that is known to fail when running + strict (``sys.getobjects()``) leakchecks. + + This type of leakcheck finds all objects, even those, such as + strings, which are not tracked by the garbage collector. + """ + return func + +class ignores_types_in_strict_leakcheck(object): + def __init__(self, types): + self.types = types + def __call__(self, func): + func.leakcheck_ignore_types = self.types + return func + +class _RefCountChecker(object): + + # Some builtin things that we ignore + # XXX: Those things were ignored by gevent, but they're important here, + # presumably. + IGNORED_TYPES = () #(tuple, dict, types.FrameType, types.TracebackType) + + def __init__(self, testcase, function): + self.testcase = testcase + self.function = function + self.deltas = [] + self.peak_stats = {} + self.ignored_types = () + + # The very first time we are called, we have already been + # self.setUp() by the test runner, so we don't need to do it again. + self.needs_setUp = False + + def _include_object_p(self, obj): + # pylint:disable=too-many-return-statements + # + # See the comment block at the top. We must be careful to + # avoid invoking user-defined operations. + if obj is self: + return False + kind = type(obj) + # ``self._include_object_p == obj`` returns NotImplemented + # for non-function objects, which causes the interpreter + # to try to reverse the order of arguments...which leads + # to the explosion of mock objects. We don't want that, so we implement + # the check manually. + if kind == type(self._include_object_p): + try: + # pylint:disable=not-callable + exact_method_equals = self._include_object_p.__eq__(obj) + except AttributeError: + # Python 2.7 methods may only have __cmp__, and that raises a + # TypeError for non-method arguments + # pylint:disable=no-member + exact_method_equals = self._include_object_p.__cmp__(obj) == 0 + + if exact_method_equals is not NotImplemented and exact_method_equals: + return False + + # Similarly, we need to check identity in our __dict__ to avoid mock explosions. + for x in self.__dict__.values(): + if obj is x: + return False + + + if kind in self.ignored_types or kind in self.IGNORED_TYPES: + return False + + return True + + def _growth(self): + return objgraph.growth(limit=None, peak_stats=self.peak_stats, + filter=self._include_object_p) + + def _report_diff(self, growth): + if not growth: + return "" + + lines = [] + width = max(len(name) for name, _, _ in growth) + for name, count, delta in growth: + lines.append('%-*s%9d %+9d' % (width, name, count, delta)) + + diff = '\n'.join(lines) + return diff + + + def _run_test(self, args, kwargs): + gc_enabled = gc.isenabled() + gc.disable() + + if self.needs_setUp: + self.testcase.setUp() + self.testcase.skipTearDown = False + try: + self.function(self.testcase, *args, **kwargs) + finally: + self.testcase.tearDown() + self.testcase.doCleanups() + self.testcase.skipTearDown = True + self.needs_setUp = True + if gc_enabled: + gc.enable() + + def _growth_after(self): + # Grab post snapshot + # pylint:disable=no-member + if 'urlparse' in sys.modules: + sys.modules['urlparse'].clear_cache() + if 'urllib.parse' in sys.modules: + sys.modules['urllib.parse'].clear_cache() + + return self._growth() + + def _check_deltas(self, growth): + # Return false when we have decided there is no leak, + # true if we should keep looping, raises an assertion + # if we have decided there is a leak. + + deltas = self.deltas + if not deltas: + # We haven't run yet, no data, keep looping + return True + + if gc.garbage: + raise LeakCheckError("Generated uncollectable garbage %r" % (gc.garbage,)) + + + # the following configurations are classified as "no leak" + # [0, 0] + # [x, 0, 0] + # [... a, b, c, d] where a+b+c+d = 0 + # + # the following configurations are classified as "leak" + # [... z, z, z] where z > 0 + + if deltas[-2:] == [0, 0] and len(deltas) in (2, 3): + return False + + if deltas[-3:] == [0, 0, 0]: + return False + + if len(deltas) >= 4 and sum(deltas[-4:]) == 0: + return False + + if len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]: + diff = self._report_diff(growth) + raise LeakCheckError('refcount increased by %r\n%s' % (deltas, diff)) + + # OK, we don't know for sure yet. Let's search for more + if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2: + # this is suspicious, so give a few more runs + limit = 11 + else: + limit = 7 + if len(deltas) >= limit: + raise LeakCheckError('refcount increased by %r\n%s' + % (deltas, + self._report_diff(growth))) + + # We couldn't decide yet, keep going + return True + + def __call__(self, args, kwargs): + for _ in range(3): + gc.collect() + + expect_failure = getattr(self.function, 'fails_leakcheck', False) + if expect_failure: + self.testcase.expect_greenlet_leak = True + self.ignored_types = getattr(self.function, "leakcheck_ignore_types", ()) + + # Capture state before; the incremental will be + # updated by each call to _growth_after + growth = self._growth() + + try: + while self._check_deltas(growth): + self._run_test(args, kwargs) + + growth = self._growth_after() + + self.deltas.append(sum((stat[2] for stat in growth))) + except LeakCheckError: + if not expect_failure: + raise + else: + if expect_failure: + raise LeakCheckError("Expected %s to leak but it did not." % (self.function,)) + +def wrap_refcount(method): + if getattr(method, 'ignore_leakcheck', False) or SKIP_LEAKCHECKS: + return method + + @wraps(method) + def wrapper(self, *args, **kwargs): # pylint:disable=too-many-branches + if getattr(self, 'ignore_leakcheck', False): + raise unittest.SkipTest("This class ignored during leakchecks") + if ONLY_FAILING_LEAKCHECKS and not getattr(method, 'fails_leakcheck', False): + raise unittest.SkipTest("Only running tests that fail leakchecks.") + return _RefCountChecker(self, method)(args, kwargs) + + return wrapper diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_contextvars.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_contextvars.py new file mode 100644 index 000000000..9a16f6710 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_contextvars.py @@ -0,0 +1,310 @@ +from __future__ import print_function + +import gc +import sys +import unittest + +from functools import partial +from unittest import skipUnless +from unittest import skipIf + +from greenlet import greenlet +from greenlet import getcurrent +from . import TestCase + + +try: + from contextvars import Context + from contextvars import ContextVar + from contextvars import copy_context + # From the documentation: + # + # Important: Context Variables should be created at the top module + # level and never in closures. Context objects hold strong + # references to context variables which prevents context variables + # from being properly garbage collected. + ID_VAR = ContextVar("id", default=None) + VAR_VAR = ContextVar("var", default=None) + ContextVar = None +except ImportError: + Context = ContextVar = copy_context = None + +# We don't support testing if greenlet's built-in context var support is disabled. +@skipUnless(Context is not None, "ContextVar not supported") +class ContextVarsTests(TestCase): + def _new_ctx_run(self, *args, **kwargs): + return copy_context().run(*args, **kwargs) + + def _increment(self, greenlet_id, callback, counts, expect): + ctx_var = ID_VAR + if expect is None: + self.assertIsNone(ctx_var.get()) + else: + self.assertEqual(ctx_var.get(), expect) + ctx_var.set(greenlet_id) + for _ in range(2): + counts[ctx_var.get()] += 1 + callback() + + def _test_context(self, propagate_by): + # pylint:disable=too-many-branches + ID_VAR.set(0) + + callback = getcurrent().switch + counts = dict((i, 0) for i in range(5)) + + lets = [ + greenlet(partial( + partial( + copy_context().run, + self._increment + ) if propagate_by == "run" else self._increment, + greenlet_id=i, + callback=callback, + counts=counts, + expect=( + i - 1 if propagate_by == "share" else + 0 if propagate_by in ("set", "run") else None + ) + )) + for i in range(1, 5) + ] + + for let in lets: + if propagate_by == "set": + let.gr_context = copy_context() + elif propagate_by == "share": + let.gr_context = getcurrent().gr_context + + for i in range(2): + counts[ID_VAR.get()] += 1 + for let in lets: + let.switch() + + if propagate_by == "run": + # Must leave each context.run() in reverse order of entry + for let in reversed(lets): + let.switch() + else: + # No context.run(), so fine to exit in any order. + for let in lets: + let.switch() + + for let in lets: + self.assertTrue(let.dead) + # When using run(), we leave the run() as the greenlet dies, + # and there's no context "underneath". When not using run(), + # gr_context still reflects the context the greenlet was + # running in. + if propagate_by == 'run': + self.assertIsNone(let.gr_context) + else: + self.assertIsNotNone(let.gr_context) + + + if propagate_by == "share": + self.assertEqual(counts, {0: 1, 1: 1, 2: 1, 3: 1, 4: 6}) + else: + self.assertEqual(set(counts.values()), set([2])) + + def test_context_propagated_by_context_run(self): + self._new_ctx_run(self._test_context, "run") + + def test_context_propagated_by_setting_attribute(self): + self._new_ctx_run(self._test_context, "set") + + def test_context_not_propagated(self): + self._new_ctx_run(self._test_context, None) + + def test_context_shared(self): + self._new_ctx_run(self._test_context, "share") + + def test_break_ctxvars(self): + let1 = greenlet(copy_context().run) + let2 = greenlet(copy_context().run) + let1.switch(getcurrent().switch) + let2.switch(getcurrent().switch) + # Since let2 entered the current context and let1 exits its own, the + # interpreter emits: + # RuntimeError: cannot exit context: thread state references a different context object + let1.switch() + + def test_not_broken_if_using_attribute_instead_of_context_run(self): + let1 = greenlet(getcurrent().switch) + let2 = greenlet(getcurrent().switch) + let1.gr_context = copy_context() + let2.gr_context = copy_context() + let1.switch() + let2.switch() + let1.switch() + let2.switch() + + def test_context_assignment_while_running(self): + # pylint:disable=too-many-statements + ID_VAR.set(None) + + def target(): + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + + # Context is created on first use + ID_VAR.set(1) + self.assertIsInstance(gr.gr_context, Context) + self.assertEqual(ID_VAR.get(), 1) + self.assertEqual(gr.gr_context[ID_VAR], 1) + + # Clearing the context makes it get re-created as another + # empty context when next used + old_context = gr.gr_context + gr.gr_context = None # assign None while running + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + ID_VAR.set(2) + self.assertIsInstance(gr.gr_context, Context) + self.assertEqual(ID_VAR.get(), 2) + self.assertEqual(gr.gr_context[ID_VAR], 2) + + new_context = gr.gr_context + getcurrent().parent.switch((old_context, new_context)) + # parent switches us back to old_context + + self.assertEqual(ID_VAR.get(), 1) + gr.gr_context = new_context # assign non-None while running + self.assertEqual(ID_VAR.get(), 2) + + getcurrent().parent.switch() + # parent switches us back to no context + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + gr.gr_context = old_context + self.assertEqual(ID_VAR.get(), 1) + + getcurrent().parent.switch() + # parent switches us back to no context + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + + gr = greenlet(target) + + with self.assertRaisesRegex(AttributeError, "can't delete context attribute"): + del gr.gr_context + + self.assertIsNone(gr.gr_context) + old_context, new_context = gr.switch() + self.assertIs(new_context, gr.gr_context) + self.assertEqual(old_context[ID_VAR], 1) + self.assertEqual(new_context[ID_VAR], 2) + self.assertEqual(new_context.run(ID_VAR.get), 2) + gr.gr_context = old_context # assign non-None while suspended + gr.switch() + self.assertIs(gr.gr_context, new_context) + gr.gr_context = None # assign None while suspended + gr.switch() + self.assertIs(gr.gr_context, old_context) + gr.gr_context = None + gr.switch() + self.assertIsNone(gr.gr_context) + + # Make sure there are no reference leaks + gr = None + gc.collect() + self.assertEqual(sys.getrefcount(old_context), 2) + self.assertEqual(sys.getrefcount(new_context), 2) + + def test_context_assignment_different_thread(self): + import threading + VAR_VAR.set(None) + ctx = Context() + + is_running = threading.Event() + should_suspend = threading.Event() + did_suspend = threading.Event() + should_exit = threading.Event() + holder = [] + + def greenlet_in_thread_fn(): + VAR_VAR.set(1) + is_running.set() + should_suspend.wait(10) + VAR_VAR.set(2) + getcurrent().parent.switch() + holder.append(VAR_VAR.get()) + + def thread_fn(): + gr = greenlet(greenlet_in_thread_fn) + gr.gr_context = ctx + holder.append(gr) + gr.switch() + did_suspend.set() + should_exit.wait(10) + gr.switch() + del gr + greenlet() # trigger cleanup + + thread = threading.Thread(target=thread_fn, daemon=True) + thread.start() + is_running.wait(10) + gr = holder[0] + + # Can't access or modify context if the greenlet is running + # in a different thread + with self.assertRaisesRegex(ValueError, "running in a different"): + getattr(gr, 'gr_context') + with self.assertRaisesRegex(ValueError, "running in a different"): + gr.gr_context = None + + should_suspend.set() + did_suspend.wait(10) + + # OK to access and modify context if greenlet is suspended + self.assertIs(gr.gr_context, ctx) + self.assertEqual(gr.gr_context[VAR_VAR], 2) + gr.gr_context = None + + should_exit.set() + thread.join(10) + + self.assertEqual(holder, [gr, None]) + + # Context can still be accessed/modified when greenlet is dead: + self.assertIsNone(gr.gr_context) + gr.gr_context = ctx + self.assertIs(gr.gr_context, ctx) + + # Otherwise we leak greenlets on some platforms. + # XXX: Should be able to do this automatically + del holder[:] + gr = None + thread = None + + def test_context_assignment_wrong_type(self): + g = greenlet() + with self.assertRaisesRegex(TypeError, + "greenlet context must be a contextvars.Context or None"): + g.gr_context = self + + +@skipIf(Context is not None, "ContextVar supported") +class NoContextVarsTests(TestCase): + def test_contextvars_errors(self): + let1 = greenlet(getcurrent().switch) + self.assertFalse(hasattr(let1, 'gr_context')) + with self.assertRaises(AttributeError): + getattr(let1, 'gr_context') + + with self.assertRaises(AttributeError): + let1.gr_context = None + + let1.switch() + + with self.assertRaises(AttributeError): + getattr(let1, 'gr_context') + + with self.assertRaises(AttributeError): + let1.gr_context = None + + del let1 + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_cpp.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_cpp.py new file mode 100644 index 000000000..2d0cc9c97 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_cpp.py @@ -0,0 +1,73 @@ +from __future__ import print_function +from __future__ import absolute_import + +import subprocess +import unittest + +import greenlet +from . import _test_extension_cpp +from . import TestCase +from . import WIN + +class CPPTests(TestCase): + def test_exception_switch(self): + greenlets = [] + for i in range(4): + g = greenlet.greenlet(_test_extension_cpp.test_exception_switch) + g.switch(i) + greenlets.append(g) + for i, g in enumerate(greenlets): + self.assertEqual(g.switch(), i) + + def _do_test_unhandled_exception(self, target): + import os + import sys + script = os.path.join( + os.path.dirname(__file__), + 'fail_cpp_exception.py', + ) + args = [sys.executable, script, target.__name__ if not isinstance(target, str) else target] + __traceback_info__ = args + with self.assertRaises(subprocess.CalledProcessError) as exc: + subprocess.check_output( + args, + encoding='utf-8', + stderr=subprocess.STDOUT + ) + + ex = exc.exception + expected_exit = self.get_expected_returncodes_for_aborted_process() + self.assertIn(ex.returncode, expected_exit) + self.assertIn('fail_cpp_exception is running', ex.output) + return ex.output + + + def test_unhandled_nonstd_exception_aborts(self): + # verify that plain unhandled throw aborts + self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_nonstd) + + def test_unhandled_std_exception_aborts(self): + # verify that plain unhandled throw aborts + self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_std) + + @unittest.skipIf(WIN, "XXX: This does not crash on Windows") + # Meaning the exception is getting lost somewhere... + def test_unhandled_std_exception_as_greenlet_function_aborts(self): + # verify that plain unhandled throw aborts + output = self._do_test_unhandled_exception('run_as_greenlet_target') + self.assertIn( + # We really expect this to be prefixed with "greenlet: Unhandled C++ exception:" + # as added by our handler for std::exception (see TUserGreenlet.cpp), but + # that's not correct everywhere --- our handler never runs before std::terminate + # gets called (for example, on arm32). + 'Thrown from an extension.', + output + ) + + def test_unhandled_exception_in_greenlet_aborts(self): + # verify that unhandled throw called in greenlet aborts too + self._do_test_unhandled_exception('run_unhandled_exception_in_greenlet_aborts') + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_extension_interface.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_extension_interface.py new file mode 100644 index 000000000..34b66567f --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_extension_interface.py @@ -0,0 +1,115 @@ +from __future__ import print_function +from __future__ import absolute_import + +import sys + +import greenlet +from . import _test_extension +from . import TestCase + +# pylint:disable=c-extension-no-member + +class CAPITests(TestCase): + def test_switch(self): + self.assertEqual( + 50, _test_extension.test_switch(greenlet.greenlet(lambda: 50))) + + def test_switch_kwargs(self): + def adder(x, y): + return x * y + g = greenlet.greenlet(adder) + self.assertEqual(6, _test_extension.test_switch_kwargs(g, x=3, y=2)) + + def test_setparent(self): + # pylint:disable=disallowed-name + def foo(): + def bar(): + greenlet.getcurrent().parent.switch() + + # This final switch should go back to the main greenlet, since + # the test_setparent() function in the C extension should have + # reparented this greenlet. + greenlet.getcurrent().parent.switch() + raise AssertionError("Should never have reached this code") + child = greenlet.greenlet(bar) + child.switch() + greenlet.getcurrent().parent.switch(child) + greenlet.getcurrent().parent.throw( + AssertionError("Should never reach this code")) + foo_child = greenlet.greenlet(foo).switch() + self.assertEqual(None, _test_extension.test_setparent(foo_child)) + + def test_getcurrent(self): + _test_extension.test_getcurrent() + + def test_new_greenlet(self): + self.assertEqual(-15, _test_extension.test_new_greenlet(lambda: -15)) + + def test_raise_greenlet_dead(self): + self.assertRaises( + greenlet.GreenletExit, _test_extension.test_raise_dead_greenlet) + + def test_raise_greenlet_error(self): + self.assertRaises( + greenlet.error, _test_extension.test_raise_greenlet_error) + + def test_throw(self): + seen = [] + + def foo(): # pylint:disable=disallowed-name + try: + greenlet.getcurrent().parent.switch() + except ValueError: + seen.append(sys.exc_info()[1]) + except greenlet.GreenletExit: + raise AssertionError + g = greenlet.greenlet(foo) + g.switch() + _test_extension.test_throw(g) + self.assertEqual(len(seen), 1) + self.assertTrue( + isinstance(seen[0], ValueError), + "ValueError was not raised in foo()") + self.assertEqual( + str(seen[0]), + 'take that sucka!', + "message doesn't match") + + def test_non_traceback_param(self): + with self.assertRaises(TypeError) as exc: + _test_extension.test_throw_exact( + greenlet.getcurrent(), + Exception, + Exception(), + self + ) + self.assertEqual(str(exc.exception), + "throw() third argument must be a traceback object") + + def test_instance_of_wrong_type(self): + with self.assertRaises(TypeError) as exc: + _test_extension.test_throw_exact( + greenlet.getcurrent(), + Exception(), + BaseException(), + None, + ) + + self.assertEqual(str(exc.exception), + "instance exception may not have a separate value") + + def test_not_throwable(self): + with self.assertRaises(TypeError) as exc: + _test_extension.test_throw_exact( + greenlet.getcurrent(), + "abc", + None, + None, + ) + self.assertEqual(str(exc.exception), + "exceptions must be classes, or instances, not str") + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_gc.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_gc.py new file mode 100644 index 000000000..994addb9b --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_gc.py @@ -0,0 +1,86 @@ +import gc + +import weakref + +import greenlet + + +from . import TestCase +from .leakcheck import fails_leakcheck +# These only work with greenlet gc support +# which is no longer optional. +assert greenlet.GREENLET_USE_GC + +class GCTests(TestCase): + def test_dead_circular_ref(self): + o = weakref.ref(greenlet.greenlet(greenlet.getcurrent).switch()) + gc.collect() + if o() is not None: + import sys + print("O IS NOT NONE.", sys.getrefcount(o())) + self.assertIsNone(o()) + self.assertFalse(gc.garbage, gc.garbage) + + def test_circular_greenlet(self): + class circular_greenlet(greenlet.greenlet): + self = None + o = circular_greenlet() + o.self = o + o = weakref.ref(o) + gc.collect() + self.assertIsNone(o()) + self.assertFalse(gc.garbage, gc.garbage) + + def test_inactive_ref(self): + class inactive_greenlet(greenlet.greenlet): + def __init__(self): + greenlet.greenlet.__init__(self, run=self.run) + + def run(self): + pass + o = inactive_greenlet() + o = weakref.ref(o) + gc.collect() + self.assertIsNone(o()) + self.assertFalse(gc.garbage, gc.garbage) + + @fails_leakcheck + def test_finalizer_crash(self): + # This test is designed to crash when active greenlets + # are made garbage collectable, until the underlying + # problem is resolved. How does it work: + # - order of object creation is important + # - array is created first, so it is moved to unreachable first + # - we create a cycle between a greenlet and this array + # - we create an object that participates in gc, is only + # referenced by a greenlet, and would corrupt gc lists + # on destruction, the easiest is to use an object with + # a finalizer + # - because array is the first object in unreachable it is + # cleared first, which causes all references to greenlet + # to disappear and causes greenlet to be destroyed, but since + # it is still live it causes a switch during gc, which causes + # an object with finalizer to be destroyed, which causes stack + # corruption and then a crash + + class object_with_finalizer(object): + def __del__(self): + pass + array = [] + parent = greenlet.getcurrent() + def greenlet_body(): + greenlet.getcurrent().object = object_with_finalizer() + try: + parent.switch() + except greenlet.GreenletExit: + print("Got greenlet exit!") + finally: + del greenlet.getcurrent().object + g = greenlet.greenlet(greenlet_body) + g.array = array + array.append(g) + g.switch() + del array + del g + greenlet.getcurrent() + gc.collect() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_generator.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_generator.py new file mode 100644 index 000000000..ca4a644b6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_generator.py @@ -0,0 +1,59 @@ + +from greenlet import greenlet + +from . import TestCase + +class genlet(greenlet): + parent = None + def __init__(self, *args, **kwds): + self.args = args + self.kwds = kwds + + def run(self): + fn, = self.fn + fn(*self.args, **self.kwds) + + def __iter__(self): + return self + + def __next__(self): + self.parent = greenlet.getcurrent() + result = self.switch() + if self: + return result + + raise StopIteration + + next = __next__ + + +def Yield(value): + g = greenlet.getcurrent() + while not isinstance(g, genlet): + if g is None: + raise RuntimeError('yield outside a genlet') + g = g.parent + g.parent.switch(value) + + +def generator(func): + class Generator(genlet): + fn = (func,) + return Generator + +# ____________________________________________________________ + + +class GeneratorTests(TestCase): + def test_generator(self): + seen = [] + + def g(n): + for i in range(n): + seen.append(i) + Yield(i) + g = generator(g) + for _ in range(3): + for j in g(5): + seen.append(j) + self.assertEqual(seen, 3 * [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_generator_nested.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_generator_nested.py new file mode 100644 index 000000000..8d752a63d --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_generator_nested.py @@ -0,0 +1,168 @@ + +from greenlet import greenlet +from . import TestCase +from .leakcheck import fails_leakcheck + +class genlet(greenlet): + parent = None + def __init__(self, *args, **kwds): + self.args = args + self.kwds = kwds + self.child = None + + def run(self): + # Note the function is packed in a tuple + # to avoid creating a bound method for it. + fn, = self.fn + fn(*self.args, **self.kwds) + + def __iter__(self): + return self + + def set_child(self, child): + self.child = child + + def __next__(self): + if self.child: + child = self.child + while child.child: + tmp = child + child = child.child + tmp.child = None + + result = child.switch() + else: + self.parent = greenlet.getcurrent() + result = self.switch() + + if self: + return result + + raise StopIteration + + next = __next__ + +def Yield(value, level=1): + g = greenlet.getcurrent() + + while level != 0: + if not isinstance(g, genlet): + raise RuntimeError('yield outside a genlet') + if level > 1: + g.parent.set_child(g) + g = g.parent + level -= 1 + + g.switch(value) + + +def Genlet(func): + class TheGenlet(genlet): + fn = (func,) + return TheGenlet + +# ____________________________________________________________ + + +def g1(n, seen): + for i in range(n): + seen.append(i + 1) + yield i + + +def g2(n, seen): + for i in range(n): + seen.append(i + 1) + Yield(i) + +g2 = Genlet(g2) + + +def nested(i): + Yield(i) + + +def g3(n, seen): + for i in range(n): + seen.append(i + 1) + nested(i) +g3 = Genlet(g3) + + +def a(n): + if n == 0: + return + for ii in ax(n - 1): + Yield(ii) + Yield(n) +ax = Genlet(a) + + +def perms(l): + if len(l) > 1: + for e in l: + # No syntactical sugar for generator expressions + x = [Yield([e] + p) for p in perms([x for x in l if x != e])] + assert x + else: + Yield(l) +perms = Genlet(perms) + + +def gr1(n): + for ii in range(1, n): + Yield(ii) + Yield(ii * ii, 2) + +gr1 = Genlet(gr1) + + +def gr2(n, seen): + for ii in gr1(n): + seen.append(ii) + +gr2 = Genlet(gr2) + + +class NestedGeneratorTests(TestCase): + def test_layered_genlets(self): + seen = [] + for ii in gr2(5, seen): + seen.append(ii) + self.assertEqual(seen, [1, 1, 2, 4, 3, 9, 4, 16]) + + @fails_leakcheck + def test_permutations(self): + gen_perms = perms(list(range(4))) + permutations = list(gen_perms) + self.assertEqual(len(permutations), 4 * 3 * 2 * 1) + self.assertIn([0, 1, 2, 3], permutations) + self.assertIn([3, 2, 1, 0], permutations) + res = [] + for ii in zip(perms(list(range(4))), perms(list(range(3)))): + res.append(ii) + self.assertEqual( + res, + [([0, 1, 2, 3], [0, 1, 2]), ([0, 1, 3, 2], [0, 2, 1]), + ([0, 2, 1, 3], [1, 0, 2]), ([0, 2, 3, 1], [1, 2, 0]), + ([0, 3, 1, 2], [2, 0, 1]), ([0, 3, 2, 1], [2, 1, 0])]) + # XXX Test to make sure we are working as a generator expression + + def test_genlet_simple(self): + for g in g1, g2, g3: + seen = [] + for _ in range(3): + for j in g(5, seen): + seen.append(j) + self.assertEqual(seen, 3 * [1, 0, 2, 1, 3, 2, 4, 3, 5, 4]) + + def test_genlet_bad(self): + try: + Yield(10) + except RuntimeError: + pass + + def test_nested_genlets(self): + seen = [] + for ii in ax(5): + seen.append(ii) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet.py new file mode 100644 index 000000000..c4aabea7e --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet.py @@ -0,0 +1,1324 @@ +import gc +import sys +import time +import threading +import unittest + +from abc import ABCMeta +from abc import abstractmethod + +import greenlet +from greenlet import greenlet as RawGreenlet +from . import TestCase +from . import RUNNING_ON_MANYLINUX +from . import PY313 +from .leakcheck import fails_leakcheck + + +# We manually manage locks in many tests +# pylint:disable=consider-using-with +# pylint:disable=too-many-public-methods +# This module is quite large. +# TODO: Refactor into separate test files. For example, +# put all the regression tests that used to produce +# crashes in test_greenlet_no_crash; put tests that DO deliberately crash +# the interpreter into test_greenlet_crash. +# pylint:disable=too-many-lines + +class SomeError(Exception): + pass + + +def fmain(seen): + try: + greenlet.getcurrent().parent.switch() + except: + seen.append(sys.exc_info()[0]) + raise + raise SomeError + + +def send_exception(g, exc): + # note: send_exception(g, exc) can be now done with g.throw(exc). + # the purpose of this test is to explicitly check the propagation rules. + def crasher(exc): + raise exc + g1 = RawGreenlet(crasher, parent=g) + g1.switch(exc) + + +class TestGreenlet(TestCase): + + def _do_simple_test(self): + lst = [] + + def f(): + lst.append(1) + greenlet.getcurrent().parent.switch() + lst.append(3) + g = RawGreenlet(f) + lst.append(0) + g.switch() + lst.append(2) + g.switch() + lst.append(4) + self.assertEqual(lst, list(range(5))) + + def test_simple(self): + self._do_simple_test() + + def test_switch_no_run_raises_AttributeError(self): + g = RawGreenlet() + with self.assertRaises(AttributeError) as exc: + g.switch() + + self.assertIn("run", str(exc.exception)) + + def test_throw_no_run_raises_AttributeError(self): + g = RawGreenlet() + with self.assertRaises(AttributeError) as exc: + g.throw(SomeError) + + self.assertIn("run", str(exc.exception)) + + def test_parent_equals_None(self): + g = RawGreenlet(parent=None) + self.assertIsNotNone(g) + self.assertIs(g.parent, greenlet.getcurrent()) + + def test_run_equals_None(self): + g = RawGreenlet(run=None) + self.assertIsNotNone(g) + self.assertIsNone(g.run) + + def test_two_children(self): + lst = [] + + def f(): + lst.append(1) + greenlet.getcurrent().parent.switch() + lst.extend([1, 1]) + g = RawGreenlet(f) + h = RawGreenlet(f) + g.switch() + self.assertEqual(len(lst), 1) + h.switch() + self.assertEqual(len(lst), 2) + h.switch() + self.assertEqual(len(lst), 4) + self.assertEqual(h.dead, True) + g.switch() + self.assertEqual(len(lst), 6) + self.assertEqual(g.dead, True) + + def test_two_recursive_children(self): + lst = [] + + def f(): + lst.append('b') + greenlet.getcurrent().parent.switch() + + def g(): + lst.append('a') + g = RawGreenlet(f) + g.switch() + lst.append('c') + + g = RawGreenlet(g) + self.assertEqual(sys.getrefcount(g), 2) + g.switch() + self.assertEqual(lst, ['a', 'b', 'c']) + # Just the one in this frame, plus the one on the stack we pass to the function + self.assertEqual(sys.getrefcount(g), 2) + + def test_threads(self): + success = [] + + def f(): + self._do_simple_test() + success.append(True) + ths = [threading.Thread(target=f) for i in range(10)] + for th in ths: + th.start() + for th in ths: + th.join(10) + self.assertEqual(len(success), len(ths)) + + def test_exception(self): + seen = [] + g1 = RawGreenlet(fmain) + g2 = RawGreenlet(fmain) + g1.switch(seen) + g2.switch(seen) + g2.parent = g1 + + self.assertEqual(seen, []) + #with self.assertRaises(SomeError): + # p("***Switching back") + # g2.switch() + # Creating this as a bound method can reveal bugs that + # are hidden on newer versions of Python that avoid creating + # bound methods for direct expressions; IOW, don't use the `with` + # form! + self.assertRaises(SomeError, g2.switch) + self.assertEqual(seen, [SomeError]) + + value = g2.switch() + self.assertEqual(value, ()) + self.assertEqual(seen, [SomeError]) + + value = g2.switch(25) + self.assertEqual(value, 25) + self.assertEqual(seen, [SomeError]) + + + def test_send_exception(self): + seen = [] + g1 = RawGreenlet(fmain) + g1.switch(seen) + self.assertRaises(KeyError, send_exception, g1, KeyError) + self.assertEqual(seen, [KeyError]) + + def test_dealloc(self): + seen = [] + g1 = RawGreenlet(fmain) + g2 = RawGreenlet(fmain) + g1.switch(seen) + g2.switch(seen) + self.assertEqual(seen, []) + del g1 + gc.collect() + self.assertEqual(seen, [greenlet.GreenletExit]) + del g2 + gc.collect() + self.assertEqual(seen, [greenlet.GreenletExit, greenlet.GreenletExit]) + + def test_dealloc_catches_GreenletExit_throws_other(self): + def run(): + try: + greenlet.getcurrent().parent.switch() + except greenlet.GreenletExit: + raise SomeError from None + + g = RawGreenlet(run) + g.switch() + # Destroying the only reference to the greenlet causes it + # to get GreenletExit; when it in turn raises, even though we're the parent + # we don't get the exception, it just gets printed. + # When we run on 3.8 only, we can use sys.unraisablehook + oldstderr = sys.stderr + from io import StringIO + stderr = sys.stderr = StringIO() + try: + del g + finally: + sys.stderr = oldstderr + + v = stderr.getvalue() + self.assertIn("Exception", v) + self.assertIn('ignored', v) + self.assertIn("SomeError", v) + + + @unittest.skipIf( + PY313 and RUNNING_ON_MANYLINUX, + "Sometimes flaky (getting one GreenletExit in the second list)" + # Probably due to funky timing interactions? + # TODO: FIXME Make that work. + ) + + def test_dealloc_other_thread(self): + seen = [] + someref = [] + + bg_glet_created_running_and_no_longer_ref_in_bg = threading.Event() + fg_ref_released = threading.Event() + bg_should_be_clear = threading.Event() + ok_to_exit_bg_thread = threading.Event() + + def f(): + g1 = RawGreenlet(fmain) + g1.switch(seen) + someref.append(g1) + del g1 + gc.collect() + + bg_glet_created_running_and_no_longer_ref_in_bg.set() + fg_ref_released.wait(3) + + RawGreenlet() # trigger release + bg_should_be_clear.set() + ok_to_exit_bg_thread.wait(3) + RawGreenlet() # One more time + + t = threading.Thread(target=f) + t.start() + bg_glet_created_running_and_no_longer_ref_in_bg.wait(10) + + self.assertEqual(seen, []) + self.assertEqual(len(someref), 1) + del someref[:] + gc.collect() + # g1 is not released immediately because it's from another thread + self.assertEqual(seen, []) + fg_ref_released.set() + bg_should_be_clear.wait(3) + try: + self.assertEqual(seen, [greenlet.GreenletExit]) + finally: + ok_to_exit_bg_thread.set() + t.join(10) + del seen[:] + del someref[:] + + def test_frame(self): + def f1(): + f = sys._getframe(0) # pylint:disable=protected-access + self.assertEqual(f.f_back, None) + greenlet.getcurrent().parent.switch(f) + return "meaning of life" + g = RawGreenlet(f1) + frame = g.switch() + self.assertTrue(frame is g.gr_frame) + self.assertTrue(g) + + from_g = g.switch() + self.assertFalse(g) + self.assertEqual(from_g, 'meaning of life') + self.assertEqual(g.gr_frame, None) + + def test_thread_bug(self): + def runner(x): + g = RawGreenlet(lambda: time.sleep(x)) + g.switch() + t1 = threading.Thread(target=runner, args=(0.2,)) + t2 = threading.Thread(target=runner, args=(0.3,)) + t1.start() + t2.start() + t1.join(10) + t2.join(10) + + def test_switch_kwargs(self): + def run(a, b): + self.assertEqual(a, 4) + self.assertEqual(b, 2) + return 42 + x = RawGreenlet(run).switch(a=4, b=2) + self.assertEqual(x, 42) + + def test_switch_kwargs_to_parent(self): + def run(x): + greenlet.getcurrent().parent.switch(x=x) + greenlet.getcurrent().parent.switch(2, x=3) + return x, x ** 2 + g = RawGreenlet(run) + self.assertEqual({'x': 3}, g.switch(3)) + self.assertEqual(((2,), {'x': 3}), g.switch()) + self.assertEqual((3, 9), g.switch()) + + def test_switch_to_another_thread(self): + data = {} + created_event = threading.Event() + done_event = threading.Event() + + def run(): + data['g'] = RawGreenlet(lambda: None) + created_event.set() + done_event.wait(10) + thread = threading.Thread(target=run) + thread.start() + created_event.wait(10) + with self.assertRaises(greenlet.error): + data['g'].switch() + done_event.set() + thread.join(10) + # XXX: Should handle this automatically + data.clear() + + def test_exc_state(self): + def f(): + try: + raise ValueError('fun') + except: # pylint:disable=bare-except + exc_info = sys.exc_info() + RawGreenlet(h).switch() + self.assertEqual(exc_info, sys.exc_info()) + + def h(): + self.assertEqual(sys.exc_info(), (None, None, None)) + + RawGreenlet(f).switch() + + def test_instance_dict(self): + def f(): + greenlet.getcurrent().test = 42 + def deldict(g): + del g.__dict__ + def setdict(g, value): + g.__dict__ = value + g = RawGreenlet(f) + self.assertEqual(g.__dict__, {}) + g.switch() + self.assertEqual(g.test, 42) + self.assertEqual(g.__dict__, {'test': 42}) + g.__dict__ = g.__dict__ + self.assertEqual(g.__dict__, {'test': 42}) + self.assertRaises(TypeError, deldict, g) + self.assertRaises(TypeError, setdict, g, 42) + + def test_running_greenlet_has_no_run(self): + has_run = [] + def func(): + has_run.append( + hasattr(greenlet.getcurrent(), 'run') + ) + + g = RawGreenlet(func) + g.switch() + self.assertEqual(has_run, [False]) + + def test_deepcopy(self): + import copy + self.assertRaises(TypeError, copy.copy, RawGreenlet()) + self.assertRaises(TypeError, copy.deepcopy, RawGreenlet()) + + def test_parent_restored_on_kill(self): + hub = RawGreenlet(lambda: None) + main = greenlet.getcurrent() + result = [] + def worker(): + try: + # Wait to be killed by going back to the test. + main.switch() + except greenlet.GreenletExit: + # Resurrect and switch to parent + result.append(greenlet.getcurrent().parent) + result.append(greenlet.getcurrent()) + hub.switch() + g = RawGreenlet(worker, parent=hub) + g.switch() + # delete the only reference, thereby raising GreenletExit + del g + self.assertTrue(result) + self.assertIs(result[0], main) + self.assertIs(result[1].parent, hub) + # Delete them, thereby breaking the cycle between the greenlet + # and the frame, which otherwise would never be collectable + # XXX: We should be able to automatically fix this. + del result[:] + hub = None + main = None + + def test_parent_return_failure(self): + # No run causes AttributeError on switch + g1 = RawGreenlet() + # Greenlet that implicitly switches to parent + g2 = RawGreenlet(lambda: None, parent=g1) + # AttributeError should propagate to us, no fatal errors + with self.assertRaises(AttributeError): + g2.switch() + + def test_throw_exception_not_lost(self): + class mygreenlet(RawGreenlet): + def __getattribute__(self, name): + try: + raise Exception # pylint:disable=broad-exception-raised + except: # pylint:disable=bare-except + pass + return RawGreenlet.__getattribute__(self, name) + g = mygreenlet(lambda: None) + self.assertRaises(SomeError, g.throw, SomeError()) + + @fails_leakcheck + def _do_test_throw_to_dead_thread_doesnt_crash(self, wait_for_cleanup=False): + result = [] + def worker(): + greenlet.getcurrent().parent.switch() + + def creator(): + g = RawGreenlet(worker) + g.switch() + result.append(g) + if wait_for_cleanup: + # Let this greenlet eventually be cleaned up. + g.switch() + greenlet.getcurrent() + t = threading.Thread(target=creator) + t.start() + t.join(10) + del t + # But, depending on the operating system, the thread + # deallocator may not actually have run yet! So we can't be + # sure about the error message unless we wait. + if wait_for_cleanup: + self.wait_for_pending_cleanups() + with self.assertRaises(greenlet.error) as exc: + result[0].throw(SomeError) + + if not wait_for_cleanup: + s = str(exc.exception) + self.assertTrue( + s == "cannot switch to a different thread (which happens to have exited)" + or 'Cannot switch' in s + ) + else: + self.assertEqual( + str(exc.exception), + "cannot switch to a different thread (which happens to have exited)", + ) + + if hasattr(result[0].gr_frame, 'clear'): + # The frame is actually executing (it thinks), we can't clear it. + with self.assertRaises(RuntimeError): + result[0].gr_frame.clear() + # Unfortunately, this doesn't actually clear the references, they're in the + # fast local array. + if not wait_for_cleanup: + # f_locals has no clear method in Python 3.13 + if hasattr(result[0].gr_frame.f_locals, 'clear'): + result[0].gr_frame.f_locals.clear() + else: + self.assertIsNone(result[0].gr_frame) + + del creator + worker = None + del result[:] + # XXX: we ought to be able to automatically fix this. + # See issue 252 + self.expect_greenlet_leak = True # direct us not to wait for it to go away + + @fails_leakcheck + def test_throw_to_dead_thread_doesnt_crash(self): + self._do_test_throw_to_dead_thread_doesnt_crash() + + def test_throw_to_dead_thread_doesnt_crash_wait(self): + self._do_test_throw_to_dead_thread_doesnt_crash(True) + + @fails_leakcheck + def test_recursive_startup(self): + class convoluted(RawGreenlet): + def __init__(self): + RawGreenlet.__init__(self) + self.count = 0 + def __getattribute__(self, name): + if name == 'run' and self.count == 0: + self.count = 1 + self.switch(43) + return RawGreenlet.__getattribute__(self, name) + def run(self, value): + while True: + self.parent.switch(value) + g = convoluted() + self.assertEqual(g.switch(42), 43) + # Exits the running greenlet, otherwise it leaks + # XXX: We should be able to automatically fix this + #g.throw(greenlet.GreenletExit) + #del g + self.expect_greenlet_leak = True + + def test_threaded_updatecurrent(self): + # released when main thread should execute + lock1 = threading.Lock() + lock1.acquire() + # released when another thread should execute + lock2 = threading.Lock() + lock2.acquire() + class finalized(object): + def __del__(self): + # happens while in green_updatecurrent() in main greenlet + # should be very careful not to accidentally call it again + # at the same time we must make sure another thread executes + lock2.release() + lock1.acquire() + # now ts_current belongs to another thread + def deallocator(): + greenlet.getcurrent().parent.switch() + def fthread(): + lock2.acquire() + greenlet.getcurrent() + del g[0] + lock1.release() + lock2.acquire() + greenlet.getcurrent() + lock1.release() + main = greenlet.getcurrent() + g = [RawGreenlet(deallocator)] + g[0].bomb = finalized() + g[0].switch() + t = threading.Thread(target=fthread) + t.start() + # let another thread grab ts_current and deallocate g[0] + lock2.release() + lock1.acquire() + # this is the corner stone + # getcurrent() will notice that ts_current belongs to another thread + # and start the update process, which would notice that g[0] should + # be deallocated, and that will execute an object's finalizer. Now, + # that object will let another thread run so it can grab ts_current + # again, which would likely crash the interpreter if there's no + # check for this case at the end of green_updatecurrent(). This test + # passes if getcurrent() returns correct result, but it's likely + # to randomly crash if it's not anyway. + self.assertEqual(greenlet.getcurrent(), main) + # wait for another thread to complete, just in case + t.join(10) + + def test_dealloc_switch_args_not_lost(self): + seen = [] + def worker(): + # wait for the value + value = greenlet.getcurrent().parent.switch() + # delete all references to ourself + del worker[0] + initiator.parent = greenlet.getcurrent().parent + # switch to main with the value, but because + # ts_current is the last reference to us we + # return here immediately, where we resurrect ourself. + try: + greenlet.getcurrent().parent.switch(value) + finally: + seen.append(greenlet.getcurrent()) + def initiator(): + return 42 # implicitly falls thru to parent + + worker = [RawGreenlet(worker)] + + worker[0].switch() # prime worker + initiator = RawGreenlet(initiator, worker[0]) + value = initiator.switch() + self.assertTrue(seen) + self.assertEqual(value, 42) + + def test_tuple_subclass(self): + # The point of this test is to see what happens when a custom + # tuple subclass is used as an object passed directly to the C + # function ``green_switch``; part of ``green_switch`` checks + # the ``len()`` of the ``args`` tuple, and that can call back + # into Python. Here, when it calls back into Python, we + # recursively enter ``green_switch`` again. + + # This test is really only relevant on Python 2. The builtin + # `apply` function directly passes the given args tuple object + # to the underlying function, whereas the Python 3 version + # unpacks and repacks into an actual tuple. This could still + # happen using the C API on Python 3 though. We should write a + # builtin version of apply() ourself. + def _apply(func, a, k): + func(*a, **k) + + class mytuple(tuple): + def __len__(self): + greenlet.getcurrent().switch() + return tuple.__len__(self) + args = mytuple() + kwargs = dict(a=42) + def switchapply(): + _apply(greenlet.getcurrent().parent.switch, args, kwargs) + g = RawGreenlet(switchapply) + self.assertEqual(g.switch(), kwargs) + + def test_abstract_subclasses(self): + AbstractSubclass = ABCMeta( + 'AbstractSubclass', + (RawGreenlet,), + {'run': abstractmethod(lambda self: None)}) + + class BadSubclass(AbstractSubclass): + pass + + class GoodSubclass(AbstractSubclass): + def run(self): + pass + + GoodSubclass() # should not raise + self.assertRaises(TypeError, BadSubclass) + + def test_implicit_parent_with_threads(self): + if not gc.isenabled(): + return # cannot test with disabled gc + N = gc.get_threshold()[0] + if N < 50: + return # cannot test with such a small N + def attempt(): + lock1 = threading.Lock() + lock1.acquire() + lock2 = threading.Lock() + lock2.acquire() + recycled = [False] + def another_thread(): + lock1.acquire() # wait for gc + greenlet.getcurrent() # update ts_current + lock2.release() # release gc + t = threading.Thread(target=another_thread) + t.start() + class gc_callback(object): + def __del__(self): + lock1.release() + lock2.acquire() + recycled[0] = True + class garbage(object): + def __init__(self): + self.cycle = self + self.callback = gc_callback() + l = [] + x = range(N*2) + current = greenlet.getcurrent() + g = garbage() + for _ in x: + g = None # lose reference to garbage + if recycled[0]: + # gc callback called prematurely + t.join(10) + return False + last = RawGreenlet() + if recycled[0]: + break # yes! gc called in green_new + l.append(last) # increase allocation counter + else: + # gc callback not called when expected + gc.collect() + if recycled[0]: + t.join(10) + return False + self.assertEqual(last.parent, current) + for g in l: + self.assertEqual(g.parent, current) + return True + for _ in range(5): + if attempt(): + break + + def test_issue_245_reference_counting_subclass_no_threads(self): + # https://github.com/python-greenlet/greenlet/issues/245 + # Before the fix, this crashed pretty reliably on + # Python 3.10, at least on macOS; but much less reliably on other + # interpreters (memory layout must have changed). + # The threaded test crashed more reliably on more interpreters. + from greenlet import getcurrent + from greenlet import GreenletExit + + class Greenlet(RawGreenlet): + pass + + initial_refs = sys.getrefcount(Greenlet) + # This has to be an instance variable because + # Python 2 raises a SyntaxError if we delete a local + # variable referenced in an inner scope. + self.glets = [] # pylint:disable=attribute-defined-outside-init + + def greenlet_main(): + try: + getcurrent().parent.switch() + except GreenletExit: + self.glets.append(getcurrent()) + + # Before the + for _ in range(10): + Greenlet(greenlet_main).switch() + + del self.glets + self.assertEqual(sys.getrefcount(Greenlet), initial_refs) + + @unittest.skipIf( + PY313 and RUNNING_ON_MANYLINUX, + "The manylinux images appear to hang on this test on 3.13rc2" + # Or perhaps I just got tired of waiting for the 450s timeout. + # Still, it shouldn't take anywhere near that long. Does not reproduce in + # Ubuntu images, on macOS or Windows. + ) + def test_issue_245_reference_counting_subclass_threads(self): + # https://github.com/python-greenlet/greenlet/issues/245 + from threading import Thread + from threading import Event + + from greenlet import getcurrent + + class MyGreenlet(RawGreenlet): + pass + + glets = [] + ref_cleared = Event() + + def greenlet_main(): + getcurrent().parent.switch() + + def thread_main(greenlet_running_event): + mine = MyGreenlet(greenlet_main) + glets.append(mine) + # The greenlets being deleted must be active + mine.switch() + # Don't keep any reference to it in this thread + del mine + # Let main know we published our greenlet. + greenlet_running_event.set() + # Wait for main to let us know the references are + # gone and the greenlet objects no longer reachable + ref_cleared.wait(10) + # The creating thread must call getcurrent() (or a few other + # greenlet APIs) because that's when the thread-local list of dead + # greenlets gets cleared. + getcurrent() + + # We start with 3 references to the subclass: + # - This module + # - Its __mro__ + # - The __subclassess__ attribute of greenlet + # - (If we call gc.get_referents(), we find four entries, including + # some other tuple ``(greenlet)`` that I'm not sure about but must be part + # of the machinery.) + # + # On Python 3.10 it's often enough to just run 3 threads; on Python 2.7, + # more threads are needed, and the results are still + # non-deterministic. Presumably the memory layouts are different + initial_refs = sys.getrefcount(MyGreenlet) + thread_ready_events = [] + for _ in range( + initial_refs + 45 + ): + event = Event() + thread = Thread(target=thread_main, args=(event,)) + thread_ready_events.append(event) + thread.start() + + + for done_event in thread_ready_events: + done_event.wait(10) + + + del glets[:] + ref_cleared.set() + # Let any other thread run; it will crash the interpreter + # if not fixed (or silently corrupt memory and we possibly crash + # later). + self.wait_for_pending_cleanups() + self.assertEqual(sys.getrefcount(MyGreenlet), initial_refs) + + def test_falling_off_end_switches_to_unstarted_parent_raises_error(self): + def no_args(): + return 13 + + parent_never_started = RawGreenlet(no_args) + + def leaf(): + return 42 + + child = RawGreenlet(leaf, parent_never_started) + + # Because the run function takes to arguments + with self.assertRaises(TypeError): + child.switch() + + def test_falling_off_end_switches_to_unstarted_parent_works(self): + def one_arg(x): + return (x, 24) + + parent_never_started = RawGreenlet(one_arg) + + def leaf(): + return 42 + + child = RawGreenlet(leaf, parent_never_started) + + result = child.switch() + self.assertEqual(result, (42, 24)) + + def test_switch_to_dead_greenlet_with_unstarted_perverse_parent(self): + class Parent(RawGreenlet): + def __getattribute__(self, name): + if name == 'run': + raise SomeError + + + parent_never_started = Parent() + seen = [] + child = RawGreenlet(lambda: seen.append(42), parent_never_started) + # Because we automatically start the parent when the child is + # finished + with self.assertRaises(SomeError): + child.switch() + + self.assertEqual(seen, [42]) + + with self.assertRaises(SomeError): + child.switch() + self.assertEqual(seen, [42]) + + def test_switch_to_dead_greenlet_reparent(self): + seen = [] + parent_never_started = RawGreenlet(lambda: seen.append(24)) + child = RawGreenlet(lambda: seen.append(42)) + + child.switch() + self.assertEqual(seen, [42]) + + child.parent = parent_never_started + # This actually is the same as switching to the parent. + result = child.switch() + self.assertIsNone(result) + self.assertEqual(seen, [42, 24]) + + def test_can_access_f_back_of_suspended_greenlet(self): + # This tests our frame rewriting to work around Python 3.12+ having + # some interpreter frames on the C stack. It will crash in the absence + # of that logic. + main = greenlet.getcurrent() + + def outer(): + inner() + + def inner(): + main.switch(sys._getframe(0)) + + hub = RawGreenlet(outer) + # start it + hub.switch() + + # start another greenlet to make sure we aren't relying on + # anything in `hub` still being on the C stack + unrelated = RawGreenlet(lambda: None) + unrelated.switch() + + # now it is suspended + self.assertIsNotNone(hub.gr_frame) + self.assertEqual(hub.gr_frame.f_code.co_name, "inner") + self.assertIsNotNone(hub.gr_frame.f_back) + self.assertEqual(hub.gr_frame.f_back.f_code.co_name, "outer") + # The next line is what would crash + self.assertIsNone(hub.gr_frame.f_back.f_back) + + def test_get_stack_with_nested_c_calls(self): + from functools import partial + from . import _test_extension_cpp + + def recurse(v): + if v > 0: + return v * _test_extension_cpp.test_call(partial(recurse, v - 1)) + return greenlet.getcurrent().parent.switch() + + gr = RawGreenlet(recurse) + gr.switch(5) + frame = gr.gr_frame + for i in range(5): + self.assertEqual(frame.f_locals["v"], i) + frame = frame.f_back + self.assertEqual(frame.f_locals["v"], 5) + self.assertIsNone(frame.f_back) + self.assertEqual(gr.switch(10), 1200) # 1200 = 5! * 10 + + def test_frames_always_exposed(self): + # On Python 3.12 this will crash if we don't set the + # gr_frames_always_exposed attribute. More background: + # https://github.com/python-greenlet/greenlet/issues/388 + main = greenlet.getcurrent() + + def outer(): + inner(sys._getframe(0)) + + def inner(frame): + main.switch(frame) + + gr = RawGreenlet(outer) + frame = gr.switch() + + # Do something else to clobber the part of the C stack used by `gr`, + # so we can't skate by on "it just happened to still be there" + unrelated = RawGreenlet(lambda: None) + unrelated.switch() + + self.assertEqual(frame.f_code.co_name, "outer") + # The next line crashes on 3.12 if we haven't exposed the frames. + self.assertIsNone(frame.f_back) + + +class TestGreenletSetParentErrors(TestCase): + def test_threaded_reparent(self): + data = {} + created_event = threading.Event() + done_event = threading.Event() + + def run(): + data['g'] = RawGreenlet(lambda: None) + created_event.set() + done_event.wait(10) + + def blank(): + greenlet.getcurrent().parent.switch() + + thread = threading.Thread(target=run) + thread.start() + created_event.wait(10) + g = RawGreenlet(blank) + g.switch() + with self.assertRaises(ValueError) as exc: + g.parent = data['g'] + done_event.set() + thread.join(10) + + self.assertEqual(str(exc.exception), "parent cannot be on a different thread") + + def test_unexpected_reparenting(self): + another = [] + def worker(): + g = RawGreenlet(lambda: None) + another.append(g) + g.switch() + t = threading.Thread(target=worker) + t.start() + t.join(10) + # The first time we switch (running g_initialstub(), which is + # when we look up the run attribute) we attempt to change the + # parent to one from another thread (which also happens to be + # dead). ``g_initialstub()`` should detect this and raise a + # greenlet error. + # + # EXCEPT: With the fix for #252, this is actually detected + # sooner, when setting the parent itself. Prior to that fix, + # the main greenlet from the background thread kept a valid + # value for ``run_info``, and appeared to be a valid parent + # until we actually started the greenlet. But now that it's + # cleared, this test is catching whether ``green_setparent`` + # can detect the dead thread. + # + # Further refactoring once again changes this back to a greenlet.error + # + # We need to wait for the cleanup to happen, but we're + # deliberately leaking a main greenlet here. + self.wait_for_pending_cleanups(initial_main_greenlets=self.main_greenlets_before_test + 1) + + class convoluted(RawGreenlet): + def __getattribute__(self, name): + if name == 'run': + self.parent = another[0] # pylint:disable=attribute-defined-outside-init + return RawGreenlet.__getattribute__(self, name) + g = convoluted(lambda: None) + with self.assertRaises(greenlet.error) as exc: + g.switch() + self.assertEqual(str(exc.exception), + "cannot switch to a different thread (which happens to have exited)") + del another[:] + + def test_unexpected_reparenting_thread_running(self): + # Like ``test_unexpected_reparenting``, except the background thread is + # actually still alive. + another = [] + switched_to_greenlet = threading.Event() + keep_main_alive = threading.Event() + def worker(): + g = RawGreenlet(lambda: None) + another.append(g) + g.switch() + switched_to_greenlet.set() + keep_main_alive.wait(10) + class convoluted(RawGreenlet): + def __getattribute__(self, name): + if name == 'run': + self.parent = another[0] # pylint:disable=attribute-defined-outside-init + return RawGreenlet.__getattribute__(self, name) + + t = threading.Thread(target=worker) + t.start() + + switched_to_greenlet.wait(10) + try: + g = convoluted(lambda: None) + + with self.assertRaises(greenlet.error) as exc: + g.switch() + self.assertIn("Cannot switch to a different thread", str(exc.exception)) + self.assertIn("Expected", str(exc.exception)) + self.assertIn("Current", str(exc.exception)) + finally: + keep_main_alive.set() + t.join(10) + # XXX: Should handle this automatically. + del another[:] + + def test_cannot_delete_parent(self): + worker = RawGreenlet(lambda: None) + self.assertIs(worker.parent, greenlet.getcurrent()) + + with self.assertRaises(AttributeError) as exc: + del worker.parent + self.assertEqual(str(exc.exception), "can't delete attribute") + + def test_cannot_delete_parent_of_main(self): + with self.assertRaises(AttributeError) as exc: + del greenlet.getcurrent().parent + self.assertEqual(str(exc.exception), "can't delete attribute") + + + def test_main_greenlet_parent_is_none(self): + # assuming we're in a main greenlet here. + self.assertIsNone(greenlet.getcurrent().parent) + + def test_set_parent_wrong_types(self): + def bg(): + # Go back to main. + greenlet.getcurrent().parent.switch() + + def check(glet): + for p in None, 1, self, "42": + with self.assertRaises(TypeError) as exc: + glet.parent = p + + self.assertEqual( + str(exc.exception), + "GreenletChecker: Expected any type of greenlet, not " + type(p).__name__) + + # First, not running + g = RawGreenlet(bg) + self.assertFalse(g) + check(g) + + # Then when running. + g.switch() + self.assertTrue(g) + check(g) + + # Let it finish + g.switch() + + + def test_trivial_cycle(self): + glet = RawGreenlet(lambda: None) + with self.assertRaises(ValueError) as exc: + glet.parent = glet + self.assertEqual(str(exc.exception), "cyclic parent chain") + + def test_trivial_cycle_main(self): + # This used to produce a ValueError, but we catch it earlier than that now. + with self.assertRaises(AttributeError) as exc: + greenlet.getcurrent().parent = greenlet.getcurrent() + self.assertEqual(str(exc.exception), "cannot set the parent of a main greenlet") + + def test_deeper_cycle(self): + g1 = RawGreenlet(lambda: None) + g2 = RawGreenlet(lambda: None) + g3 = RawGreenlet(lambda: None) + + g1.parent = g2 + g2.parent = g3 + with self.assertRaises(ValueError) as exc: + g3.parent = g1 + self.assertEqual(str(exc.exception), "cyclic parent chain") + + +class TestRepr(TestCase): + + def assertEndsWith(self, got, suffix): + self.assertTrue(got.endswith(suffix), (got, suffix)) + + def test_main_while_running(self): + r = repr(greenlet.getcurrent()) + self.assertEndsWith(r, " current active started main>") + + def test_main_in_background(self): + main = greenlet.getcurrent() + def run(): + return repr(main) + + g = RawGreenlet(run) + r = g.switch() + self.assertEndsWith(r, ' suspended active started main>') + + def test_initial(self): + r = repr(RawGreenlet()) + self.assertEndsWith(r, ' pending>') + + def test_main_from_other_thread(self): + main = greenlet.getcurrent() + + class T(threading.Thread): + original_main = thread_main = None + main_glet = None + def run(self): + self.original_main = repr(main) + self.main_glet = greenlet.getcurrent() + self.thread_main = repr(self.main_glet) + + t = T() + t.start() + t.join(10) + + self.assertEndsWith(t.original_main, ' suspended active started main>') + self.assertEndsWith(t.thread_main, ' current active started main>') + # give the machinery time to notice the death of the thread, + # and clean it up. Note that we don't use + # ``expect_greenlet_leak`` or wait_for_pending_cleanups, + # because at this point we know we have an extra greenlet + # still reachable. + for _ in range(3): + time.sleep(0.001) + + # In the past, main greenlets, even from dead threads, never + # really appear dead. We have fixed that, and we also report + # that the thread is dead in the repr. (Do this multiple times + # to make sure that we don't self-modify and forget our state + # in the C++ code). + for _ in range(3): + self.assertTrue(t.main_glet.dead) + r = repr(t.main_glet) + self.assertEndsWith(r, ' (thread exited) dead>') + + def test_dead(self): + g = RawGreenlet(lambda: None) + g.switch() + self.assertEndsWith(repr(g), ' dead>') + self.assertNotIn('suspended', repr(g)) + self.assertNotIn('started', repr(g)) + self.assertNotIn('active', repr(g)) + + def test_formatting_produces_native_str(self): + # https://github.com/python-greenlet/greenlet/issues/218 + # %s formatting on Python 2 was producing unicode, not str. + + g_dead = RawGreenlet(lambda: None) + g_not_started = RawGreenlet(lambda: None) + g_cur = greenlet.getcurrent() + + for g in g_dead, g_not_started, g_cur: + + self.assertIsInstance( + '%s' % (g,), + str + ) + self.assertIsInstance( + '%r' % (g,), + str, + ) + + +class TestMainGreenlet(TestCase): + # Tests some implementation details, and relies on some + # implementation details. + + def _check_current_is_main(self): + # implementation detail + assert 'main' in repr(greenlet.getcurrent()) + + t = type(greenlet.getcurrent()) + assert 'main' not in repr(t) + return t + + def test_main_greenlet_type_can_be_subclassed(self): + main_type = self._check_current_is_main() + subclass = type('subclass', (main_type,), {}) + self.assertIsNotNone(subclass) + + def test_main_greenlet_is_greenlet(self): + self._check_current_is_main() + self.assertIsInstance(greenlet.getcurrent(), RawGreenlet) + + + +class TestBrokenGreenlets(TestCase): + # Tests for things that used to, or still do, terminate the interpreter. + # This often means doing unsavory things. + + def test_failed_to_initialstub(self): + def func(): + raise AssertionError("Never get here") + + + g = greenlet._greenlet.UnswitchableGreenlet(func) + g.force_switch_error = True + + with self.assertRaisesRegex(SystemError, + "Failed to switch stacks into a greenlet for the first time."): + g.switch() + + def test_failed_to_switch_into_running(self): + runs = [] + def func(): + runs.append(1) + greenlet.getcurrent().parent.switch() + runs.append(2) + greenlet.getcurrent().parent.switch() + runs.append(3) # pragma: no cover + + g = greenlet._greenlet.UnswitchableGreenlet(func) + g.switch() + self.assertEqual(runs, [1]) + g.switch() + self.assertEqual(runs, [1, 2]) + g.force_switch_error = True + + with self.assertRaisesRegex(SystemError, + "Failed to switch stacks into a running greenlet."): + g.switch() + + # If we stopped here, we would fail the leakcheck, because we've left + # the ``inner_bootstrap()`` C frame and its descendents hanging around, + # which have a bunch of Python references. They'll never get cleaned up + # if we don't let the greenlet finish. + g.force_switch_error = False + g.switch() + self.assertEqual(runs, [1, 2, 3]) + + def test_failed_to_slp_switch_into_running(self): + ex = self.assertScriptRaises('fail_slp_switch.py') + + self.assertIn('fail_slp_switch is running', ex.output) + self.assertIn(ex.returncode, self.get_expected_returncodes_for_aborted_process()) + + def test_reentrant_switch_two_greenlets(self): + # Before we started capturing the arguments in g_switch_finish, this could crash. + output = self.run_script('fail_switch_two_greenlets.py') + self.assertIn('In g1_run', output) + self.assertIn('TRACE', output) + self.assertIn('LEAVE TRACE', output) + self.assertIn('Falling off end of main', output) + self.assertIn('Falling off end of g1_run', output) + self.assertIn('Falling off end of g2', output) + + def test_reentrant_switch_three_greenlets(self): + # On debug builds of greenlet, this used to crash with an assertion error; + # on non-debug versions, it ran fine (which it should not do!). + # Now it always crashes correctly with a TypeError + ex = self.assertScriptRaises('fail_switch_three_greenlets.py', exitcodes=(1,)) + + self.assertIn('TypeError', ex.output) + self.assertIn('positional arguments', ex.output) + + def test_reentrant_switch_three_greenlets2(self): + # This actually passed on debug and non-debug builds. It + # should probably have been triggering some debug assertions + # but it didn't. + # + # I think the fixes for the above test also kicked in here. + output = self.run_script('fail_switch_three_greenlets2.py') + self.assertIn( + "RESULTS: [('trace', 'switch'), " + "('trace', 'switch'), ('g2 arg', 'g2 from tracefunc'), " + "('trace', 'switch'), ('main g1', 'from g2_run'), ('trace', 'switch'), " + "('g1 arg', 'g1 from main'), ('trace', 'switch'), ('main g2', 'from g1_run'), " + "('trace', 'switch'), ('g1 from parent', 'g1 from main 2'), ('trace', 'switch'), " + "('main g1.2', 'g1 done'), ('trace', 'switch'), ('g2 from parent', ()), " + "('trace', 'switch'), ('main g2.2', 'g2 done')]", + output + ) + + def test_reentrant_switch_GreenletAlreadyStartedInPython(self): + output = self.run_script('fail_initialstub_already_started.py') + + self.assertIn( + "RESULTS: ['Begin C', 'Switch to b from B.__getattribute__ in C', " + "('Begin B', ()), '_B_run switching to main', ('main from c', 'From B'), " + "'B.__getattribute__ back from main in C', ('Begin A', (None,)), " + "('A dead?', True, 'B dead?', True, 'C dead?', False), " + "'C done', ('main from c.2', None)]", + output + ) + + def test_reentrant_switch_run_callable_has_del(self): + output = self.run_script('fail_clearing_run_switches.py') + self.assertIn( + "RESULTS [" + "('G.__getattribute__', 'run'), ('RunCallable', '__del__'), " + "('main: g.switch()', 'from RunCallable'), ('run_func', 'enter')" + "]", + output + ) + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet_trash.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet_trash.py new file mode 100644 index 000000000..c1fc1374c --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_greenlet_trash.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +""" +Tests for greenlets interacting with the CPython trash can API. + +The CPython trash can API is not designed to be re-entered from a +single thread. But this can happen using greenlets, if something +during the object deallocation process switches greenlets, and this second +greenlet then causes the trash can to get entered again. Here, we do this +very explicitly, but in other cases (like gevent) it could be arbitrarily more +complicated: for example, a weakref callback might try to acquire a lock that's +already held by another greenlet; that would allow a greenlet switch to occur. + +See https://github.com/gevent/gevent/issues/1909 + +This test is fragile and relies on details of the CPython +implementation (like most of the rest of this package): + + - We enter the trashcan and deferred deallocation after + ``_PyTrash_UNWIND_LEVEL`` calls. This constant, defined in + CPython's object.c, is generally 50. That's basically how many objects are required to + get us into the deferred deallocation situation. + + - The test fails by hitting an ``assert()`` in object.c; if the + build didn't enable assert, then we don't catch this. + + - If the test fails in that way, the interpreter crashes. +""" +from __future__ import print_function, absolute_import, division + +import unittest + + +class TestTrashCanReEnter(unittest.TestCase): + + def test_it(self): + try: + # pylint:disable-next=no-name-in-module + from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=unused-import + except ImportError: + import sys + # Python 3.13 has not "trash delete nesting" anymore (but "delete later") + assert sys.version_info[:2] >= (3, 13) + self.skipTest("get_tstate_trash_delete_nesting is not available.") + + # Try several times to trigger it, because it isn't 100% + # reliable. + for _ in range(10): + self.check_it() + + def check_it(self): # pylint:disable=too-many-statements + import greenlet + from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module + main = greenlet.getcurrent() + + assert get_tstate_trash_delete_nesting() == 0 + + # We expect to be in deferred deallocation after this many + # deallocations have occurred. TODO: I wish we had a better way to do + # this --- that was before get_tstate_trash_delete_nesting; perhaps + # we can use that API to do better? + TRASH_UNWIND_LEVEL = 50 + # How many objects to put in a container; it's the container that + # queues objects for deferred deallocation. + OBJECTS_PER_CONTAINER = 500 + + class Dealloc: # define the class here because we alter class variables each time we run. + """ + An object with a ``__del__`` method. When it starts getting deallocated + from a deferred trash can run, it switches greenlets, allocates more objects + which then also go in the trash can. If we don't save state appropriately, + nesting gets out of order and we can crash the interpreter. + """ + + #: Has our deallocation actually run and switched greenlets? + #: When it does, this will be set to the current greenlet. This should + #: be happening in the main greenlet, so we check that down below. + SPAWNED = False + + #: Has the background greenlet run? + BG_RAN = False + + BG_GLET = None + + #: How many of these things have ever been allocated. + CREATED = 0 + + #: How many of these things have ever been deallocated. + DESTROYED = 0 + + #: How many were destroyed not in the main greenlet. There should always + #: be some. + #: If the test is broken or things change in the trashcan implementation, + #: this may not be correct. + DESTROYED_BG = 0 + + def __init__(self, sequence_number): + """ + :param sequence_number: The ordinal of this object during + one particular creation run. This is used to detect (guess, really) + when we have entered the trash can's deferred deallocation. + """ + self.i = sequence_number + Dealloc.CREATED += 1 + + def __del__(self): + if self.i == TRASH_UNWIND_LEVEL and not self.SPAWNED: + Dealloc.SPAWNED = greenlet.getcurrent() + other = Dealloc.BG_GLET = greenlet.greenlet(background_greenlet) + x = other.switch() + assert x == 42 + # It's important that we don't switch back to the greenlet, + # we leave it hanging there in an incomplete state. But we don't let it + # get collected, either. If we complete it now, while we're still + # in the scope of the initial trash can, things work out and we + # don't see the problem. We need this greenlet to complete + # at some point in the future, after we've exited this trash can invocation. + del other + elif self.i == 40 and greenlet.getcurrent() is not main: + Dealloc.BG_RAN = True + try: + main.switch(42) + except greenlet.GreenletExit as ex: + # We expect this; all references to us go away + # while we're still running, and we need to finish deleting + # ourself. + Dealloc.BG_RAN = type(ex) + del ex + + # Record the fact that we're dead last of all. This ensures that + # we actually get returned too. + Dealloc.DESTROYED += 1 + if greenlet.getcurrent() is not main: + Dealloc.DESTROYED_BG += 1 + + + def background_greenlet(): + # We direct through a second function, instead of + # directly calling ``make_some()``, so that we have complete + # control over when these objects are destroyed: we need them + # to be destroyed in the context of the background greenlet + t = make_some() + del t # Triggere deletion. + + def make_some(): + t = () + i = OBJECTS_PER_CONTAINER + while i: + # Nest the tuples; it's the recursion that gets us + # into trash. + t = (Dealloc(i), t) + i -= 1 + return t + + + some = make_some() + self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER) + self.assertEqual(Dealloc.DESTROYED, 0) + + # If we're going to crash, it should be on the following line. + # We only crash if ``assert()`` is enabled, of course. + del some + + # For non-debug builds of CPython, we won't crash. The best we can do is check + # the nesting level explicitly. + self.assertEqual(0, get_tstate_trash_delete_nesting()) + + # Discard this, raising GreenletExit into where it is waiting. + Dealloc.BG_GLET = None + # The same nesting level maintains. + self.assertEqual(0, get_tstate_trash_delete_nesting()) + + # We definitely cleaned some up in the background + self.assertGreater(Dealloc.DESTROYED_BG, 0) + + # Make sure all the cleanups happened. + self.assertIs(Dealloc.SPAWNED, main) + self.assertTrue(Dealloc.BG_RAN) + self.assertEqual(Dealloc.BG_RAN, greenlet.GreenletExit) + self.assertEqual(Dealloc.CREATED, Dealloc.DESTROYED ) + self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER * 2) + + import gc + gc.collect() + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_leaks.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_leaks.py new file mode 100644 index 000000000..ed1fa7175 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_leaks.py @@ -0,0 +1,443 @@ +# -*- coding: utf-8 -*- +""" +Testing scenarios that may have leaked. +""" +from __future__ import print_function, absolute_import, division + +import sys +import gc + +import time +import weakref +import threading + + +import greenlet +from . import TestCase +from .leakcheck import fails_leakcheck +from .leakcheck import ignores_leakcheck +from .leakcheck import RUNNING_ON_MANYLINUX + +# pylint:disable=protected-access + +assert greenlet.GREENLET_USE_GC # Option to disable this was removed in 1.0 + +class HasFinalizerTracksInstances(object): + EXTANT_INSTANCES = set() + def __init__(self, msg): + self.msg = sys.intern(msg) + self.EXTANT_INSTANCES.add(id(self)) + def __del__(self): + self.EXTANT_INSTANCES.remove(id(self)) + def __repr__(self): + return "" % ( + id(self), self.msg + ) + @classmethod + def reset(cls): + cls.EXTANT_INSTANCES.clear() + + +class TestLeaks(TestCase): + + def test_arg_refs(self): + args = ('a', 'b', 'c') + refcount_before = sys.getrefcount(args) + # pylint:disable=unnecessary-lambda + g = greenlet.greenlet( + lambda *args: greenlet.getcurrent().parent.switch(*args)) + for _ in range(100): + g.switch(*args) + self.assertEqual(sys.getrefcount(args), refcount_before) + + def test_kwarg_refs(self): + kwargs = {} + # pylint:disable=unnecessary-lambda + g = greenlet.greenlet( + lambda **kwargs: greenlet.getcurrent().parent.switch(**kwargs)) + for _ in range(100): + g.switch(**kwargs) + self.assertEqual(sys.getrefcount(kwargs), 2) + + + @staticmethod + def __recycle_threads(): + # By introducing a thread that does sleep we allow other threads, + # that have triggered their __block condition, but did not have a + # chance to deallocate their thread state yet, to finally do so. + # The way it works is by requiring a GIL switch (different thread), + # which does a GIL release (sleep), which might do a GIL switch + # to finished threads and allow them to clean up. + def worker(): + time.sleep(0.001) + t = threading.Thread(target=worker) + t.start() + time.sleep(0.001) + t.join(10) + + def test_threaded_leak(self): + gg = [] + def worker(): + # only main greenlet present + gg.append(weakref.ref(greenlet.getcurrent())) + for _ in range(2): + t = threading.Thread(target=worker) + t.start() + t.join(10) + del t + greenlet.getcurrent() # update ts_current + self.__recycle_threads() + greenlet.getcurrent() # update ts_current + gc.collect() + greenlet.getcurrent() # update ts_current + for g in gg: + self.assertIsNone(g()) + + def test_threaded_adv_leak(self): + gg = [] + def worker(): + # main and additional *finished* greenlets + ll = greenlet.getcurrent().ll = [] + def additional(): + ll.append(greenlet.getcurrent()) + for _ in range(2): + greenlet.greenlet(additional).switch() + gg.append(weakref.ref(greenlet.getcurrent())) + for _ in range(2): + t = threading.Thread(target=worker) + t.start() + t.join(10) + del t + greenlet.getcurrent() # update ts_current + self.__recycle_threads() + greenlet.getcurrent() # update ts_current + gc.collect() + greenlet.getcurrent() # update ts_current + for g in gg: + self.assertIsNone(g()) + + def assertClocksUsed(self): + used = greenlet._greenlet.get_clocks_used_doing_optional_cleanup() + self.assertGreaterEqual(used, 0) + # we don't lose the value + greenlet._greenlet.enable_optional_cleanup(True) + used2 = greenlet._greenlet.get_clocks_used_doing_optional_cleanup() + self.assertEqual(used, used2) + self.assertGreater(greenlet._greenlet.CLOCKS_PER_SEC, 1) + + def _check_issue251(self, + manually_collect_background=True, + explicit_reference_to_switch=False): + # See https://github.com/python-greenlet/greenlet/issues/251 + # Killing a greenlet (probably not the main one) + # in one thread from another thread would + # result in leaking a list (the ts_delkey list). + # We no longer use lists to hold that stuff, though. + + # For the test to be valid, even empty lists have to be tracked by the + # GC + + assert gc.is_tracked([]) + HasFinalizerTracksInstances.reset() + greenlet.getcurrent() + greenlets_before = self.count_objects(greenlet.greenlet, exact_kind=False) + + background_glet_running = threading.Event() + background_glet_killed = threading.Event() + background_greenlets = [] + + # XXX: Switching this to a greenlet subclass that overrides + # run results in all callers failing the leaktest; that + # greenlet instance is leaked. There's a bound method for + # run() living on the stack of the greenlet in g_initialstub, + # and since we don't manually switch back to the background + # greenlet to let it "fall off the end" and exit the + # g_initialstub function, it never gets cleaned up. Making the + # garbage collector aware of this bound method (making it an + # attribute of the greenlet structure and traversing into it) + # doesn't help, for some reason. + def background_greenlet(): + # Throw control back to the main greenlet. + jd = HasFinalizerTracksInstances("DELETING STACK OBJECT") + greenlet._greenlet.set_thread_local( + 'test_leaks_key', + HasFinalizerTracksInstances("DELETING THREAD STATE")) + # Explicitly keeping 'switch' in a local variable + # breaks this test in all versions + if explicit_reference_to_switch: + s = greenlet.getcurrent().parent.switch + s([jd]) + else: + greenlet.getcurrent().parent.switch([jd]) + + bg_main_wrefs = [] + + def background_thread(): + glet = greenlet.greenlet(background_greenlet) + bg_main_wrefs.append(weakref.ref(glet.parent)) + + background_greenlets.append(glet) + glet.switch() # Be sure it's active. + # Control is ours again. + del glet # Delete one reference from the thread it runs in. + background_glet_running.set() + background_glet_killed.wait(10) + + # To trigger the background collection of the dead + # greenlet, thus clearing out the contents of the list, we + # need to run some APIs. See issue 252. + if manually_collect_background: + greenlet.getcurrent() + + + t = threading.Thread(target=background_thread) + t.start() + background_glet_running.wait(10) + greenlet.getcurrent() + lists_before = self.count_objects(list, exact_kind=True) + + assert len(background_greenlets) == 1 + self.assertFalse(background_greenlets[0].dead) + # Delete the last reference to the background greenlet + # from a different thread. This puts it in the background thread's + # ts_delkey list. + del background_greenlets[:] + background_glet_killed.set() + + # Now wait for the background thread to die. + t.join(10) + del t + # As part of the fix for 252, we need to cycle the ceval.c + # interpreter loop to be sure it has had a chance to process + # the pending call. + self.wait_for_pending_cleanups() + + lists_after = self.count_objects(list, exact_kind=True) + greenlets_after = self.count_objects(greenlet.greenlet, exact_kind=False) + + # On 2.7, we observe that lists_after is smaller than + # lists_before. No idea what lists got cleaned up. All the + # Python 3 versions match exactly. + self.assertLessEqual(lists_after, lists_before) + # On versions after 3.6, we've successfully cleaned up the + # greenlet references thanks to the internal "vectorcall" + # protocol; prior to that, there is a reference path through + # the ``greenlet.switch`` method still on the stack that we + # can't reach to clean up. The C code goes through terrific + # lengths to clean that up. + if not explicit_reference_to_switch \ + and greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None: + # If cleanup was disabled, though, we may not find it. + self.assertEqual(greenlets_after, greenlets_before) + if manually_collect_background: + # TODO: Figure out how to make this work! + # The one on the stack is still leaking somehow + # in the non-manually-collect state. + self.assertEqual(HasFinalizerTracksInstances.EXTANT_INSTANCES, set()) + else: + # The explicit reference prevents us from collecting it + # and it isn't always found by the GC either for some + # reason. The entire frame is leaked somehow, on some + # platforms (e.g., MacPorts builds of Python (all + # versions!)), but not on other platforms (the linux and + # windows builds on GitHub actions and Appveyor). So we'd + # like to write a test that proves that the main greenlet + # sticks around, and we can on my machine (macOS 11.6, + # MacPorts builds of everything) but we can't write that + # same test on other platforms. However, hopefully iteration + # done by leakcheck will find it. + pass + + if greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None: + self.assertClocksUsed() + + def test_issue251_killing_cross_thread_leaks_list(self): + self._check_issue251() + + def test_issue251_with_cleanup_disabled(self): + greenlet._greenlet.enable_optional_cleanup(False) + try: + self._check_issue251() + finally: + greenlet._greenlet.enable_optional_cleanup(True) + + @fails_leakcheck + def test_issue251_issue252_need_to_collect_in_background(self): + # Between greenlet 1.1.2 and the next version, this was still + # failing because the leak of the list still exists when we + # don't call a greenlet API before exiting the thread. The + # proximate cause is that neither of the two greenlets from + # the background thread are actually being destroyed, even + # though the GC is in fact visiting both objects. It's not + # clear where that leak is? For some reason the thread-local + # dict holding it isn't being cleaned up. + # + # The leak, I think, is in the CPYthon internal function that + # calls into green_switch(). The argument tuple is still on + # the C stack somewhere and can't be reached? That doesn't + # make sense, because the tuple should be collectable when + # this object goes away. + # + # Note that this test sometimes spuriously passes on Linux, + # for some reason, but I've never seen it pass on macOS. + self._check_issue251(manually_collect_background=False) + + @fails_leakcheck + def test_issue251_issue252_need_to_collect_in_background_cleanup_disabled(self): + self.expect_greenlet_leak = True + greenlet._greenlet.enable_optional_cleanup(False) + try: + self._check_issue251(manually_collect_background=False) + finally: + greenlet._greenlet.enable_optional_cleanup(True) + + @fails_leakcheck + def test_issue251_issue252_explicit_reference_not_collectable(self): + self._check_issue251( + manually_collect_background=False, + explicit_reference_to_switch=True) + + UNTRACK_ATTEMPTS = 100 + + def _only_test_some_versions(self): + # We're only looking for this problem specifically on 3.11, + # and this set of tests is relatively fragile, depending on + # OS and memory management details. So we want to run it on 3.11+ + # (obviously) but not every older 3.x version in order to reduce + # false negatives. At the moment, those false results seem to have + # resolved, so we are actually running this on 3.8+ + assert sys.version_info[0] >= 3 + if sys.version_info[:2] < (3, 8): + self.skipTest('Only observed on 3.11') + if RUNNING_ON_MANYLINUX: + self.skipTest("Slow and not worth repeating here") + + @ignores_leakcheck + # Because we're just trying to track raw memory, not objects, and running + # the leakcheck makes an already slow test slower. + def test_untracked_memory_doesnt_increase(self): + # See https://github.com/gevent/gevent/issues/1924 + # and https://github.com/python-greenlet/greenlet/issues/328 + self._only_test_some_versions() + def f(): + return 1 + + ITER = 10000 + def run_it(): + for _ in range(ITER): + greenlet.greenlet(f).switch() + + # Establish baseline + for _ in range(3): + run_it() + + # uss: (Linux, macOS, Windows): aka "Unique Set Size", this is + # the memory which is unique to a process and which would be + # freed if the process was terminated right now. + uss_before = self.get_process_uss() + + for count in range(self.UNTRACK_ATTEMPTS): + uss_before = max(uss_before, self.get_process_uss()) + run_it() + + uss_after = self.get_process_uss() + if uss_after <= uss_before and count > 1: + break + + self.assertLessEqual(uss_after, uss_before) + + def _check_untracked_memory_thread(self, deallocate_in_thread=True): + self._only_test_some_versions() + # Like the above test, but what if there are a bunch of + # unfinished greenlets in a thread that dies? + # Does it matter if we deallocate in the thread or not? + EXIT_COUNT = [0] + + def f(): + try: + greenlet.getcurrent().parent.switch() + except greenlet.GreenletExit: + EXIT_COUNT[0] += 1 + raise + return 1 + + ITER = 10000 + def run_it(): + glets = [] + for _ in range(ITER): + # Greenlet starts, switches back to us. + # We keep a strong reference to the greenlet though so it doesn't + # get a GreenletExit exception. + g = greenlet.greenlet(f) + glets.append(g) + g.switch() + + return glets + + test = self + + class ThreadFunc: + uss_before = uss_after = 0 + glets = () + ITER = 2 + def __call__(self): + self.uss_before = test.get_process_uss() + + for _ in range(self.ITER): + self.glets += tuple(run_it()) + + for g in self.glets: + test.assertIn('suspended active', str(g)) + # Drop them. + if deallocate_in_thread: + self.glets = () + self.uss_after = test.get_process_uss() + + # Establish baseline + uss_before = uss_after = None + for count in range(self.UNTRACK_ATTEMPTS): + EXIT_COUNT[0] = 0 + thread_func = ThreadFunc() + t = threading.Thread(target=thread_func) + t.start() + t.join(30) + self.assertFalse(t.is_alive()) + + if uss_before is None: + uss_before = thread_func.uss_before + + uss_before = max(uss_before, thread_func.uss_before) + if deallocate_in_thread: + self.assertEqual(thread_func.glets, ()) + self.assertEqual(EXIT_COUNT[0], ITER * thread_func.ITER) + + del thread_func # Deallocate the greenlets; but this won't raise into them + del t + if not deallocate_in_thread: + self.assertEqual(EXIT_COUNT[0], 0) + if deallocate_in_thread: + self.wait_for_pending_cleanups() + + uss_after = self.get_process_uss() + # See if we achieve a non-growth state at some point. Break when we do. + if uss_after <= uss_before and count > 1: + break + + self.wait_for_pending_cleanups() + uss_after = self.get_process_uss() + self.assertLessEqual(uss_after, uss_before, "after attempts %d" % (count,)) + + @ignores_leakcheck + # Because we're just trying to track raw memory, not objects, and running + # the leakcheck makes an already slow test slower. + def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_thread(self): + self._check_untracked_memory_thread(deallocate_in_thread=True) + + @ignores_leakcheck + # Because the main greenlets from the background threads do not exit in a timely fashion, + # we fail the object-based leakchecks. + def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main(self): + self._check_untracked_memory_thread(deallocate_in_thread=False) + +if __name__ == '__main__': + __import__('unittest').main() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_stack_saved.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_stack_saved.py new file mode 100644 index 000000000..b362bf95a --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_stack_saved.py @@ -0,0 +1,19 @@ +import greenlet +from . import TestCase + + +class Test(TestCase): + + def test_stack_saved(self): + main = greenlet.getcurrent() + self.assertEqual(main._stack_saved, 0) + + def func(): + main.switch(main._stack_saved) + + g = greenlet.greenlet(func) + x = g.switch() + self.assertGreater(x, 0) + self.assertGreater(g._stack_saved, 0) + g.switch() + self.assertEqual(g._stack_saved, 0) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_throw.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_throw.py new file mode 100644 index 000000000..f4f9a1402 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_throw.py @@ -0,0 +1,128 @@ +import sys + + +from greenlet import greenlet +from . import TestCase + +def switch(*args): + return greenlet.getcurrent().parent.switch(*args) + + +class ThrowTests(TestCase): + def test_class(self): + def f(): + try: + switch("ok") + except RuntimeError: + switch("ok") + return + switch("fail") + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw(RuntimeError) + self.assertEqual(res, "ok") + + def test_val(self): + def f(): + try: + switch("ok") + except RuntimeError: + val = sys.exc_info()[1] + if str(val) == "ciao": + switch("ok") + return + switch("fail") + + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw(RuntimeError("ciao")) + self.assertEqual(res, "ok") + + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw(RuntimeError, "ciao") + self.assertEqual(res, "ok") + + def test_kill(self): + def f(): + switch("ok") + switch("fail") + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw() + self.assertTrue(isinstance(res, greenlet.GreenletExit)) + self.assertTrue(g.dead) + res = g.throw() # immediately eaten by the already-dead greenlet + self.assertTrue(isinstance(res, greenlet.GreenletExit)) + + def test_throw_goes_to_original_parent(self): + main = greenlet.getcurrent() + + def f1(): + try: + main.switch("f1 ready to catch") + except IndexError: + return "caught" + return "normal exit" + + def f2(): + main.switch("from f2") + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + with self.assertRaises(IndexError): + g2.throw(IndexError) + self.assertTrue(g2.dead) + self.assertTrue(g1.dead) + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + res = g1.switch() + self.assertEqual(res, "f1 ready to catch") + res = g2.throw(IndexError) + self.assertEqual(res, "caught") + self.assertTrue(g2.dead) + self.assertTrue(g1.dead) + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + res = g1.switch() + self.assertEqual(res, "f1 ready to catch") + res = g2.switch() + self.assertEqual(res, "from f2") + res = g2.throw(IndexError) + self.assertEqual(res, "caught") + self.assertTrue(g2.dead) + self.assertTrue(g1.dead) + + def test_non_traceback_param(self): + with self.assertRaises(TypeError) as exc: + greenlet.getcurrent().throw( + Exception, + Exception(), + self + ) + self.assertEqual(str(exc.exception), + "throw() third argument must be a traceback object") + + def test_instance_of_wrong_type(self): + with self.assertRaises(TypeError) as exc: + greenlet.getcurrent().throw( + Exception(), + BaseException() + ) + + self.assertEqual(str(exc.exception), + "instance exception may not have a separate value") + + def test_not_throwable(self): + with self.assertRaises(TypeError) as exc: + greenlet.getcurrent().throw( + "abc" + ) + self.assertEqual(str(exc.exception), + "exceptions must be classes, or instances, not str") diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_tracing.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_tracing.py new file mode 100644 index 000000000..c044d4b64 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_tracing.py @@ -0,0 +1,291 @@ +from __future__ import print_function +import sys +import greenlet +import unittest + +from . import TestCase +from . import PY312 + +# https://discuss.python.org/t/cpython-3-12-greenlet-and-tracing-profiling-how-to-not-crash-and-get-correct-results/33144/2 +DEBUG_BUILD_PY312 = ( + PY312 and hasattr(sys, 'gettotalrefcount'), + "Broken on debug builds of Python 3.12" +) + +class SomeError(Exception): + pass + +class GreenletTracer(object): + oldtrace = None + + def __init__(self, error_on_trace=False): + self.actions = [] + self.error_on_trace = error_on_trace + + def __call__(self, *args): + self.actions.append(args) + if self.error_on_trace: + raise SomeError + + def __enter__(self): + self.oldtrace = greenlet.settrace(self) + return self.actions + + def __exit__(self, *args): + greenlet.settrace(self.oldtrace) + + +class TestGreenletTracing(TestCase): + """ + Tests of ``greenlet.settrace()`` + """ + + def test_a_greenlet_tracing(self): + main = greenlet.getcurrent() + def dummy(): + pass + def dummyexc(): + raise SomeError() + + with GreenletTracer() as actions: + g1 = greenlet.greenlet(dummy) + g1.switch() + g2 = greenlet.greenlet(dummyexc) + self.assertRaises(SomeError, g2.switch) + + self.assertEqual(actions, [ + ('switch', (main, g1)), + ('switch', (g1, main)), + ('switch', (main, g2)), + ('throw', (g2, main)), + ]) + + def test_b_exception_disables_tracing(self): + main = greenlet.getcurrent() + def dummy(): + main.switch() + g = greenlet.greenlet(dummy) + g.switch() + with GreenletTracer(error_on_trace=True) as actions: + self.assertRaises(SomeError, g.switch) + self.assertEqual(greenlet.gettrace(), None) + + self.assertEqual(actions, [ + ('switch', (main, g)), + ]) + + def test_set_same_tracer_twice(self): + # https://github.com/python-greenlet/greenlet/issues/332 + # Our logic in asserting that the tracefunction should + # gain a reference was incorrect if the same tracefunction was set + # twice. + tracer = GreenletTracer() + with tracer: + greenlet.settrace(tracer) + + +class PythonTracer(object): + oldtrace = None + + def __init__(self): + self.actions = [] + + def __call__(self, frame, event, arg): + # Record the co_name so we have an idea what function we're in. + self.actions.append((event, frame.f_code.co_name)) + + def __enter__(self): + self.oldtrace = sys.setprofile(self) + return self.actions + + def __exit__(self, *args): + sys.setprofile(self.oldtrace) + +def tpt_callback(): + return 42 + +class TestPythonTracing(TestCase): + """ + Tests of the interaction of ``sys.settrace()`` + with greenlet facilities. + + NOTE: Most of this is probably CPython specific. + """ + + maxDiff = None + + def test_trace_events_trivial(self): + with PythonTracer() as actions: + tpt_callback() + # If we use the sys.settrace instead of setprofile, we get + # this: + + # self.assertEqual(actions, [ + # ('call', 'tpt_callback'), + # ('call', '__exit__'), + # ]) + + self.assertEqual(actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + def _trace_switch(self, glet): + with PythonTracer() as actions: + glet.switch() + return actions + + def _check_trace_events_func_already_set(self, glet): + actions = self._trace_switch(glet) + self.assertEqual(actions, [ + ('return', '__enter__'), + ('c_call', '_trace_switch'), + ('call', 'run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('return', 'run'), + ('c_return', '_trace_switch'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + def test_trace_events_into_greenlet_func_already_set(self): + def run(): + return tpt_callback() + + self._check_trace_events_func_already_set(greenlet.greenlet(run)) + + def test_trace_events_into_greenlet_subclass_already_set(self): + class X(greenlet.greenlet): + def run(self): + return tpt_callback() + self._check_trace_events_func_already_set(X()) + + def _check_trace_events_from_greenlet_sets_profiler(self, g, tracer): + g.switch() + tpt_callback() + tracer.__exit__() + self.assertEqual(tracer.actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('return', 'run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + + def test_trace_events_from_greenlet_func_sets_profiler(self): + tracer = PythonTracer() + def run(): + tracer.__enter__() + return tpt_callback() + + self._check_trace_events_from_greenlet_sets_profiler(greenlet.greenlet(run), + tracer) + + def test_trace_events_from_greenlet_subclass_sets_profiler(self): + tracer = PythonTracer() + class X(greenlet.greenlet): + def run(self): + tracer.__enter__() + return tpt_callback() + + self._check_trace_events_from_greenlet_sets_profiler(X(), tracer) + + @unittest.skipIf(*DEBUG_BUILD_PY312) + def test_trace_events_multiple_greenlets_switching(self): + tracer = PythonTracer() + + g1 = None + g2 = None + + def g1_run(): + tracer.__enter__() + tpt_callback() + g2.switch() + tpt_callback() + return 42 + + def g2_run(): + tpt_callback() + tracer.__exit__() + tpt_callback() + g1.switch() + + g1 = greenlet.greenlet(g1_run) + g2 = greenlet.greenlet(g2_run) + + x = g1.switch() + self.assertEqual(x, 42) + tpt_callback() # ensure not in the trace + self.assertEqual(tracer.actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('c_call', 'g1_run'), + ('call', 'g2_run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + @unittest.skipIf(*DEBUG_BUILD_PY312) + def test_trace_events_multiple_greenlets_switching_siblings(self): + # Like the first version, but get both greenlets running first + # as "siblings" and then establish the tracing. + tracer = PythonTracer() + + g1 = None + g2 = None + + def g1_run(): + greenlet.getcurrent().parent.switch() + tracer.__enter__() + tpt_callback() + g2.switch() + tpt_callback() + return 42 + + def g2_run(): + greenlet.getcurrent().parent.switch() + + tpt_callback() + tracer.__exit__() + tpt_callback() + g1.switch() + + g1 = greenlet.greenlet(g1_run) + g2 = greenlet.greenlet(g2_run) + + # Start g1 + g1.switch() + # And it immediately returns control to us. + # Start g2 + g2.switch() + # Which also returns. Now kick of the real part of the + # test. + x = g1.switch() + self.assertEqual(x, 42) + + tpt_callback() # ensure not in the trace + self.assertEqual(tracer.actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('c_call', 'g1_run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_version.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_version.py new file mode 100644 index 000000000..96c17cf17 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_version.py @@ -0,0 +1,41 @@ +#! /usr/bin/env python +from __future__ import absolute_import +from __future__ import print_function + +import sys +import os +from unittest import TestCase as NonLeakingTestCase + +import greenlet + +# No reason to run this multiple times under leakchecks, +# it doesn't do anything. +class VersionTests(NonLeakingTestCase): + def test_version(self): + def find_dominating_file(name): + if os.path.exists(name): + return name + + tried = [] + here = os.path.abspath(os.path.dirname(__file__)) + for i in range(10): + up = ['..'] * i + path = [here] + up + [name] + fname = os.path.join(*path) + fname = os.path.abspath(fname) + tried.append(fname) + if os.path.exists(fname): + return fname + raise AssertionError("Could not find file " + name + "; checked " + str(tried)) + + try: + setup_py = find_dominating_file('setup.py') + except AssertionError as e: + self.skipTest("Unable to find setup.py; must be out of tree. " + str(e)) + + + invoke_setup = "%s %s --version" % (sys.executable, setup_py) + with os.popen(invoke_setup) as f: + sversion = f.read().strip() + + self.assertEqual(sversion, greenlet.__version__) diff --git a/venv/lib/python3.8/site-packages/greenlet/tests/test_weakref.py b/venv/lib/python3.8/site-packages/greenlet/tests/test_weakref.py new file mode 100644 index 000000000..05a38a7f1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/greenlet/tests/test_weakref.py @@ -0,0 +1,35 @@ +import gc +import weakref + + +import greenlet +from . import TestCase + +class WeakRefTests(TestCase): + def test_dead_weakref(self): + def _dead_greenlet(): + g = greenlet.greenlet(lambda: None) + g.switch() + return g + o = weakref.ref(_dead_greenlet()) + gc.collect() + self.assertEqual(o(), None) + + def test_inactive_weakref(self): + o = weakref.ref(greenlet.greenlet()) + gc.collect() + self.assertEqual(o(), None) + + def test_dealloc_weakref(self): + seen = [] + def worker(): + try: + greenlet.getcurrent().parent.switch() + finally: + seen.append(g()) + g = greenlet.greenlet(worker) + g.switch() + g2 = greenlet.greenlet(lambda: None, g) + g = weakref.ref(g2) + g2 = None + self.assertEqual(seen, [None]) diff --git a/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/INSTALLER b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/LICENSE b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/METADATA b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/METADATA new file mode 100644 index 000000000..6b35667b8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/METADATA @@ -0,0 +1,133 @@ +Metadata-Version: 2.1 +Name: importlib_metadata +Version: 8.5.0 +Summary: Read metadata from Python packages +Author-email: "Jason R. Coombs" +Project-URL: Source, https://github.com/python/importlib_metadata +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: zipp >=3.20 +Requires-Dist: typing-extensions >=3.6.4 ; python_version < "3.8" +Provides-Extra: check +Requires-Dist: pytest-checkdocs >=2.4 ; extra == 'check' +Requires-Dist: pytest-ruff >=0.2.1 ; (sys_platform != "cygwin") and extra == 'check' +Provides-Extra: cover +Requires-Dist: pytest-cov ; extra == 'cover' +Provides-Extra: doc +Requires-Dist: sphinx >=3.5 ; extra == 'doc' +Requires-Dist: jaraco.packaging >=9.3 ; extra == 'doc' +Requires-Dist: rst.linker >=1.9 ; extra == 'doc' +Requires-Dist: furo ; extra == 'doc' +Requires-Dist: sphinx-lint ; extra == 'doc' +Requires-Dist: jaraco.tidelift >=1.4 ; extra == 'doc' +Provides-Extra: enabler +Requires-Dist: pytest-enabler >=2.2 ; extra == 'enabler' +Provides-Extra: perf +Requires-Dist: ipython ; extra == 'perf' +Provides-Extra: test +Requires-Dist: pytest !=8.1.*,>=6 ; extra == 'test' +Requires-Dist: packaging ; extra == 'test' +Requires-Dist: pyfakefs ; extra == 'test' +Requires-Dist: flufl.flake8 ; extra == 'test' +Requires-Dist: pytest-perf >=0.9.2 ; extra == 'test' +Requires-Dist: jaraco.test >=5.4 ; extra == 'test' +Requires-Dist: importlib-resources >=1.3 ; (python_version < "3.9") and extra == 'test' +Provides-Extra: type +Requires-Dist: pytest-mypy ; extra == 'type' + +.. image:: https://img.shields.io/pypi/v/importlib_metadata.svg + :target: https://pypi.org/project/importlib_metadata + +.. image:: https://img.shields.io/pypi/pyversions/importlib_metadata.svg + +.. image:: https://github.com/python/importlib_metadata/actions/workflows/main.yml/badge.svg + :target: https://github.com/python/importlib_metadata/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff + +.. image:: https://readthedocs.org/projects/importlib-metadata/badge/?version=latest + :target: https://importlib-metadata.readthedocs.io/en/latest/?badge=latest + +.. image:: https://img.shields.io/badge/skeleton-2024-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://tidelift.com/badges/package/pypi/importlib-metadata + :target: https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=readme + +Library to access the metadata for a Python package. + +This package supplies third-party access to the functionality of +`importlib.metadata `_ +including improvements added to subsequent Python versions. + + +Compatibility +============= + +New features are introduced in this third-party library and later merged +into CPython. The following table indicates which versions of this library +were contributed to different versions in the standard library: + +.. list-table:: + :header-rows: 1 + + * - importlib_metadata + - stdlib + * - 7.0 + - 3.13 + * - 6.5 + - 3.12 + * - 4.13 + - 3.11 + * - 4.6 + - 3.10 + * - 1.4 + - 3.8 + + +Usage +===== + +See the `online documentation `_ +for usage details. + +`Finder authors +`_ can +also add support for custom package installers. See the above documentation +for details. + + +Caveats +======= + +This project primarily supports third-party packages installed by PyPA +tools (or other conforming packages). It does not support: + +- Packages in the stdlib. +- Packages installed without metadata. + +Project details +=============== + + * Project home: https://github.com/python/importlib_metadata + * Report bugs at: https://github.com/python/importlib_metadata/issues + * Code hosting: https://github.com/python/importlib_metadata + * Documentation: https://importlib-metadata.readthedocs.io/ + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. diff --git a/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/RECORD b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/RECORD new file mode 100644 index 000000000..832f9623f --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/RECORD @@ -0,0 +1,31 @@ +importlib_metadata-8.5.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +importlib_metadata-8.5.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +importlib_metadata-8.5.0.dist-info/METADATA,sha256=HXikDpZut4rY2D4ZuhAxXnLUDb-f_XP_Cyp9iYmF4G0,4775 +importlib_metadata-8.5.0.dist-info/RECORD,, +importlib_metadata-8.5.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91 +importlib_metadata-8.5.0.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19 +importlib_metadata/__init__.py,sha256=-Sk7aVqfmzLecdjSOpLKo1P_PegQanR__HsMMyEq0PI,35853 +importlib_metadata/__pycache__/__init__.cpython-38.pyc,, +importlib_metadata/__pycache__/_adapters.cpython-38.pyc,, +importlib_metadata/__pycache__/_collections.cpython-38.pyc,, +importlib_metadata/__pycache__/_compat.cpython-38.pyc,, +importlib_metadata/__pycache__/_functools.cpython-38.pyc,, +importlib_metadata/__pycache__/_itertools.cpython-38.pyc,, +importlib_metadata/__pycache__/_meta.cpython-38.pyc,, +importlib_metadata/__pycache__/_text.cpython-38.pyc,, +importlib_metadata/__pycache__/diagnose.cpython-38.pyc,, +importlib_metadata/_adapters.py,sha256=wdyNWoVblu1r4z8v4t6iQEyjnqAujEyqWAp9wTCVluI,2317 +importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743 +importlib_metadata/_compat.py,sha256=VC5ZDLlT-BcshauCShdFJvMNLntJJfZzNK1meGa-enw,1313 +importlib_metadata/_functools.py,sha256=bSbAqC9-2niWM9364FYBx9GWtetnJEfo4mdLv8uMl7c,2895 +importlib_metadata/_itertools.py,sha256=nMvp9SfHAQ_JYwK4L2i64lr3GRXGlYlikGTVzWbys_E,5351 +importlib_metadata/_meta.py,sha256=JzuqMG4za5MoaBPCPv61c26fUBdQPZ4by3pbaQA_E_o,1823 +importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166 +importlib_metadata/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +importlib_metadata/compat/__pycache__/__init__.cpython-38.pyc,, +importlib_metadata/compat/__pycache__/py311.cpython-38.pyc,, +importlib_metadata/compat/__pycache__/py39.cpython-38.pyc,, +importlib_metadata/compat/py311.py,sha256=uqm-K-uohyj1042TH4a9Er_I5o7667DvulcD-gC_fSA,608 +importlib_metadata/compat/py39.py,sha256=cPkMv6-0ilK-0Jw_Tkn0xYbOKJZc4WJKQHow0c2T44w,1102 +importlib_metadata/diagnose.py,sha256=nkSRMiowlmkhLYhKhvCg9glmt_11Cox-EmLzEbqYTa8,379 +importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/WHEEL b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/WHEEL new file mode 100644 index 000000000..0fde4dd96 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (74.1.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/top_level.txt b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/top_level.txt new file mode 100644 index 000000000..bbb07547a --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata-8.5.0.dist-info/top_level.txt @@ -0,0 +1 @@ +importlib_metadata diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/__init__.py b/venv/lib/python3.8/site-packages/importlib_metadata/__init__.py new file mode 100644 index 000000000..46a14e640 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata/__init__.py @@ -0,0 +1,1132 @@ +""" +APIs exposing metadata from third-party Python packages. + +This codebase is shared between importlib.metadata in the stdlib +and importlib_metadata in PyPI. See +https://github.com/python/importlib_metadata/wiki/Development-Methodology +for more detail. +""" + +from __future__ import annotations + +import abc +import collections +import email +import functools +import itertools +import operator +import os +import pathlib +import posixpath +import re +import sys +import textwrap +import types +from contextlib import suppress +from importlib import import_module +from importlib.abc import MetaPathFinder +from itertools import starmap +from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast + +from . import _meta +from ._collections import FreezableDefaultDict, Pair +from ._compat import ( + NullFinder, + install, +) +from ._functools import method_cache, pass_none +from ._itertools import always_iterable, bucket, unique_everseen +from ._meta import PackageMetadata, SimplePath +from .compat import py39, py311 + +__all__ = [ + 'Distribution', + 'DistributionFinder', + 'PackageMetadata', + 'PackageNotFoundError', + 'SimplePath', + 'distribution', + 'distributions', + 'entry_points', + 'files', + 'metadata', + 'packages_distributions', + 'requires', + 'version', +] + + +class PackageNotFoundError(ModuleNotFoundError): + """The package was not found.""" + + def __str__(self) -> str: + return f"No package metadata was found for {self.name}" + + @property + def name(self) -> str: # type: ignore[override] # make readonly + (name,) = self.args + return name + + +class Sectioned: + """ + A simple entry point config parser for performance + + >>> for item in Sectioned.read(Sectioned._sample): + ... print(item) + Pair(name='sec1', value='# comments ignored') + Pair(name='sec1', value='a = 1') + Pair(name='sec1', value='b = 2') + Pair(name='sec2', value='a = 2') + + >>> res = Sectioned.section_pairs(Sectioned._sample) + >>> item = next(res) + >>> item.name + 'sec1' + >>> item.value + Pair(name='a', value='1') + >>> item = next(res) + >>> item.value + Pair(name='b', value='2') + >>> item = next(res) + >>> item.name + 'sec2' + >>> item.value + Pair(name='a', value='2') + >>> list(res) + [] + """ + + _sample = textwrap.dedent( + """ + [sec1] + # comments ignored + a = 1 + b = 2 + + [sec2] + a = 2 + """ + ).lstrip() + + @classmethod + def section_pairs(cls, text): + return ( + section._replace(value=Pair.parse(section.value)) + for section in cls.read(text, filter_=cls.valid) + if section.name is not None + ) + + @staticmethod + def read(text, filter_=None): + lines = filter(filter_, map(str.strip, text.splitlines())) + name = None + for value in lines: + section_match = value.startswith('[') and value.endswith(']') + if section_match: + name = value.strip('[]') + continue + yield Pair(name, value) + + @staticmethod + def valid(line: str): + return line and not line.startswith('#') + + +class EntryPoint: + """An entry point as defined by Python packaging conventions. + + See `the packaging docs on entry points + `_ + for more information. + + >>> ep = EntryPoint( + ... name=None, group=None, value='package.module:attr [extra1, extra2]') + >>> ep.module + 'package.module' + >>> ep.attr + 'attr' + >>> ep.extras + ['extra1', 'extra2'] + """ + + pattern = re.compile( + r'(?P[\w.]+)\s*' + r'(:\s*(?P[\w.]+)\s*)?' + r'((?P\[.*\])\s*)?$' + ) + """ + A regular expression describing the syntax for an entry point, + which might look like: + + - module + - package.module + - package.module:attribute + - package.module:object.attribute + - package.module:attr [extra1, extra2] + + Other combinations are possible as well. + + The expression is lenient about whitespace around the ':', + following the attr, and following any extras. + """ + + name: str + value: str + group: str + + dist: Optional[Distribution] = None + + def __init__(self, name: str, value: str, group: str) -> None: + vars(self).update(name=name, value=value, group=group) + + def load(self) -> Any: + """Load the entry point from its definition. If only a module + is indicated by the value, return that module. Otherwise, + return the named object. + """ + match = cast(Match, self.pattern.match(self.value)) + module = import_module(match.group('module')) + attrs = filter(None, (match.group('attr') or '').split('.')) + return functools.reduce(getattr, attrs, module) + + @property + def module(self) -> str: + match = self.pattern.match(self.value) + assert match is not None + return match.group('module') + + @property + def attr(self) -> str: + match = self.pattern.match(self.value) + assert match is not None + return match.group('attr') + + @property + def extras(self) -> List[str]: + match = self.pattern.match(self.value) + assert match is not None + return re.findall(r'\w+', match.group('extras') or '') + + def _for(self, dist): + vars(self).update(dist=dist) + return self + + def matches(self, **params): + """ + EntryPoint matches the given parameters. + + >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]') + >>> ep.matches(group='foo') + True + >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]') + True + >>> ep.matches(group='foo', name='other') + False + >>> ep.matches() + True + >>> ep.matches(extras=['extra1', 'extra2']) + True + >>> ep.matches(module='bing') + True + >>> ep.matches(attr='bong') + True + """ + self._disallow_dist(params) + attrs = (getattr(self, param) for param in params) + return all(map(operator.eq, params.values(), attrs)) + + @staticmethod + def _disallow_dist(params): + """ + Querying by dist is not allowed (dist objects are not comparable). + >>> EntryPoint(name='fan', value='fav', group='fag').matches(dist='foo') + Traceback (most recent call last): + ... + ValueError: "dist" is not suitable for matching... + """ + if "dist" in params: + raise ValueError( + '"dist" is not suitable for matching. ' + "Instead, use Distribution.entry_points.select() on a " + "located distribution." + ) + + def _key(self): + return self.name, self.value, self.group + + def __lt__(self, other): + return self._key() < other._key() + + def __eq__(self, other): + return self._key() == other._key() + + def __setattr__(self, name, value): + raise AttributeError("EntryPoint objects are immutable.") + + def __repr__(self): + return ( + f'EntryPoint(name={self.name!r}, value={self.value!r}, ' + f'group={self.group!r})' + ) + + def __hash__(self) -> int: + return hash(self._key()) + + +class EntryPoints(tuple): + """ + An immutable collection of selectable EntryPoint objects. + """ + + __slots__ = () + + def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] # Work with str instead of int + """ + Get the EntryPoint in self matching name. + """ + try: + return next(iter(self.select(name=name))) + except StopIteration: + raise KeyError(name) + + def __repr__(self): + """ + Repr with classname and tuple constructor to + signal that we deviate from regular tuple behavior. + """ + return '%s(%r)' % (self.__class__.__name__, tuple(self)) + + def select(self, **params) -> EntryPoints: + """ + Select entry points from self that match the + given parameters (typically group and/or name). + """ + return EntryPoints(ep for ep in self if py39.ep_matches(ep, **params)) + + @property + def names(self) -> Set[str]: + """ + Return the set of all names of all entry points. + """ + return {ep.name for ep in self} + + @property + def groups(self) -> Set[str]: + """ + Return the set of all groups of all entry points. + """ + return {ep.group for ep in self} + + @classmethod + def _from_text_for(cls, text, dist): + return cls(ep._for(dist) for ep in cls._from_text(text)) + + @staticmethod + def _from_text(text): + return ( + EntryPoint(name=item.value.name, value=item.value.value, group=item.name) + for item in Sectioned.section_pairs(text or '') + ) + + +class PackagePath(pathlib.PurePosixPath): + """A reference to a path in a package""" + + hash: Optional[FileHash] + size: int + dist: Distribution + + def read_text(self, encoding: str = 'utf-8') -> str: + return self.locate().read_text(encoding=encoding) + + def read_binary(self) -> bytes: + return self.locate().read_bytes() + + def locate(self) -> SimplePath: + """Return a path-like object for this path""" + return self.dist.locate_file(self) + + +class FileHash: + def __init__(self, spec: str) -> None: + self.mode, _, self.value = spec.partition('=') + + def __repr__(self) -> str: + return f'' + + +class Distribution(metaclass=abc.ABCMeta): + """ + An abstract Python distribution package. + + Custom providers may derive from this class and define + the abstract methods to provide a concrete implementation + for their environment. Some providers may opt to override + the default implementation of some properties to bypass + the file-reading mechanism. + """ + + @abc.abstractmethod + def read_text(self, filename) -> Optional[str]: + """Attempt to load metadata file given by the name. + + Python distribution metadata is organized by blobs of text + typically represented as "files" in the metadata directory + (e.g. package-1.0.dist-info). These files include things + like: + + - METADATA: The distribution metadata including fields + like Name and Version and Description. + - entry_points.txt: A series of entry points as defined in + `the entry points spec `_. + - RECORD: A record of files according to + `this recording spec `_. + + A package may provide any set of files, including those + not listed here or none at all. + + :param filename: The name of the file in the distribution info. + :return: The text if found, otherwise None. + """ + + @abc.abstractmethod + def locate_file(self, path: str | os.PathLike[str]) -> SimplePath: + """ + Given a path to a file in this distribution, return a SimplePath + to it. + + This method is used by callers of ``Distribution.files()`` to + locate files within the distribution. If it's possible for a + Distribution to represent files in the distribution as + ``SimplePath`` objects, it should implement this method + to resolve such objects. + + Some Distribution providers may elect not to resolve SimplePath + objects within the distribution by raising a + NotImplementedError, but consumers of such a Distribution would + be unable to invoke ``Distribution.files()``. + """ + + @classmethod + def from_name(cls, name: str) -> Distribution: + """Return the Distribution for the given package name. + + :param name: The name of the distribution package to search for. + :return: The Distribution instance (or subclass thereof) for the named + package, if found. + :raises PackageNotFoundError: When the named package's distribution + metadata cannot be found. + :raises ValueError: When an invalid value is supplied for name. + """ + if not name: + raise ValueError("A distribution name is required.") + try: + return next(iter(cls._prefer_valid(cls.discover(name=name)))) + except StopIteration: + raise PackageNotFoundError(name) + + @classmethod + def discover( + cls, *, context: Optional[DistributionFinder.Context] = None, **kwargs + ) -> Iterable[Distribution]: + """Return an iterable of Distribution objects for all packages. + + Pass a ``context`` or pass keyword arguments for constructing + a context. + + :context: A ``DistributionFinder.Context`` object. + :return: Iterable of Distribution objects for packages matching + the context. + """ + if context and kwargs: + raise ValueError("cannot accept context and kwargs") + context = context or DistributionFinder.Context(**kwargs) + return itertools.chain.from_iterable( + resolver(context) for resolver in cls._discover_resolvers() + ) + + @staticmethod + def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]: + """ + Prefer (move to the front) distributions that have metadata. + + Ref python/importlib_resources#489. + """ + buckets = bucket(dists, lambda dist: bool(dist.metadata)) + return itertools.chain(buckets[True], buckets[False]) + + @staticmethod + def at(path: str | os.PathLike[str]) -> Distribution: + """Return a Distribution for the indicated metadata path. + + :param path: a string or path-like object + :return: a concrete Distribution instance for the path + """ + return PathDistribution(pathlib.Path(path)) + + @staticmethod + def _discover_resolvers(): + """Search the meta_path for resolvers (MetadataPathFinders).""" + declared = ( + getattr(finder, 'find_distributions', None) for finder in sys.meta_path + ) + return filter(None, declared) + + @property + def metadata(self) -> _meta.PackageMetadata: + """Return the parsed metadata for this Distribution. + + The returned object will have keys that name the various bits of + metadata per the + `Core metadata specifications `_. + + Custom providers may provide the METADATA file or override this + property. + """ + # deferred for performance (python/cpython#109829) + from . import _adapters + + opt_text = ( + self.read_text('METADATA') + or self.read_text('PKG-INFO') + # This last clause is here to support old egg-info files. Its + # effect is to just end up using the PathDistribution's self._path + # (which points to the egg-info file) attribute unchanged. + or self.read_text('') + ) + text = cast(str, opt_text) + return _adapters.Message(email.message_from_string(text)) + + @property + def name(self) -> str: + """Return the 'Name' metadata for the distribution package.""" + return self.metadata['Name'] + + @property + def _normalized_name(self): + """Return a normalized version of the name.""" + return Prepared.normalize(self.name) + + @property + def version(self) -> str: + """Return the 'Version' metadata for the distribution package.""" + return self.metadata['Version'] + + @property + def entry_points(self) -> EntryPoints: + """ + Return EntryPoints for this distribution. + + Custom providers may provide the ``entry_points.txt`` file + or override this property. + """ + return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self) + + @property + def files(self) -> Optional[List[PackagePath]]: + """Files in this distribution. + + :return: List of PackagePath for this distribution or None + + Result is `None` if the metadata file that enumerates files + (i.e. RECORD for dist-info, or installed-files.txt or + SOURCES.txt for egg-info) is missing. + Result may be empty if the metadata exists but is empty. + + Custom providers are recommended to provide a "RECORD" file (in + ``read_text``) or override this property to allow for callers to be + able to resolve filenames provided by the package. + """ + + def make_file(name, hash=None, size_str=None): + result = PackagePath(name) + result.hash = FileHash(hash) if hash else None + result.size = int(size_str) if size_str else None + result.dist = self + return result + + @pass_none + def make_files(lines): + # Delay csv import, since Distribution.files is not as widely used + # as other parts of importlib.metadata + import csv + + return starmap(make_file, csv.reader(lines)) + + @pass_none + def skip_missing_files(package_paths): + return list(filter(lambda path: path.locate().exists(), package_paths)) + + return skip_missing_files( + make_files( + self._read_files_distinfo() + or self._read_files_egginfo_installed() + or self._read_files_egginfo_sources() + ) + ) + + def _read_files_distinfo(self): + """ + Read the lines of RECORD. + """ + text = self.read_text('RECORD') + return text and text.splitlines() + + def _read_files_egginfo_installed(self): + """ + Read installed-files.txt and return lines in a similar + CSV-parsable format as RECORD: each file must be placed + relative to the site-packages directory and must also be + quoted (since file names can contain literal commas). + + This file is written when the package is installed by pip, + but it might not be written for other installation methods. + Assume the file is accurate if it exists. + """ + text = self.read_text('installed-files.txt') + # Prepend the .egg-info/ subdir to the lines in this file. + # But this subdir is only available from PathDistribution's + # self._path. + subdir = getattr(self, '_path', None) + if not text or not subdir: + return + + paths = ( + py311.relative_fix((subdir / name).resolve()) + .relative_to(self.locate_file('').resolve(), walk_up=True) + .as_posix() + for name in text.splitlines() + ) + return map('"{}"'.format, paths) + + def _read_files_egginfo_sources(self): + """ + Read SOURCES.txt and return lines in a similar CSV-parsable + format as RECORD: each file name must be quoted (since it + might contain literal commas). + + Note that SOURCES.txt is not a reliable source for what + files are installed by a package. This file is generated + for a source archive, and the files that are present + there (e.g. setup.py) may not correctly reflect the files + that are present after the package has been installed. + """ + text = self.read_text('SOURCES.txt') + return text and map('"{}"'.format, text.splitlines()) + + @property + def requires(self) -> Optional[List[str]]: + """Generated requirements specified for this Distribution""" + reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() + return reqs and list(reqs) + + def _read_dist_info_reqs(self): + return self.metadata.get_all('Requires-Dist') + + def _read_egg_info_reqs(self): + source = self.read_text('requires.txt') + return pass_none(self._deps_from_requires_text)(source) + + @classmethod + def _deps_from_requires_text(cls, source): + return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source)) + + @staticmethod + def _convert_egg_info_reqs_to_simple_reqs(sections): + """ + Historically, setuptools would solicit and store 'extra' + requirements, including those with environment markers, + in separate sections. More modern tools expect each + dependency to be defined separately, with any relevant + extras and environment markers attached directly to that + requirement. This method converts the former to the + latter. See _test_deps_from_requires_text for an example. + """ + + def make_condition(name): + return name and f'extra == "{name}"' + + def quoted_marker(section): + section = section or '' + extra, sep, markers = section.partition(':') + if extra and markers: + markers = f'({markers})' + conditions = list(filter(None, [markers, make_condition(extra)])) + return '; ' + ' and '.join(conditions) if conditions else '' + + def url_req_space(req): + """ + PEP 508 requires a space between the url_spec and the quoted_marker. + Ref python/importlib_metadata#357. + """ + # '@' is uniquely indicative of a url_req. + return ' ' * ('@' in req) + + for section in sections: + space = url_req_space(section.value) + yield section.value + space + quoted_marker(section.name) + + @property + def origin(self): + return self._load_json('direct_url.json') + + def _load_json(self, filename): + # Deferred for performance (python/importlib_metadata#503) + import json + + return pass_none(json.loads)( + self.read_text(filename), + object_hook=lambda data: types.SimpleNamespace(**data), + ) + + +class DistributionFinder(MetaPathFinder): + """ + A MetaPathFinder capable of discovering installed distributions. + + Custom providers should implement this interface in order to + supply metadata. + """ + + class Context: + """ + Keyword arguments presented by the caller to + ``distributions()`` or ``Distribution.discover()`` + to narrow the scope of a search for distributions + in all DistributionFinders. + + Each DistributionFinder may expect any parameters + and should attempt to honor the canonical + parameters defined below when appropriate. + + This mechanism gives a custom provider a means to + solicit additional details from the caller beyond + "name" and "path" when searching distributions. + For example, imagine a provider that exposes suites + of packages in either a "public" or "private" ``realm``. + A caller may wish to query only for distributions in + a particular realm and could call + ``distributions(realm="private")`` to signal to the + custom provider to only include distributions from that + realm. + """ + + name = None + """ + Specific name for which a distribution finder should match. + A name of ``None`` matches all distributions. + """ + + def __init__(self, **kwargs): + vars(self).update(kwargs) + + @property + def path(self) -> List[str]: + """ + The sequence of directory path that a distribution finder + should search. + + Typically refers to Python installed package paths such as + "site-packages" directories and defaults to ``sys.path``. + """ + return vars(self).get('path', sys.path) + + @abc.abstractmethod + def find_distributions(self, context=Context()) -> Iterable[Distribution]: + """ + Find distributions. + + Return an iterable of all Distribution instances capable of + loading the metadata for packages matching the ``context``, + a DistributionFinder.Context instance. + """ + + +class FastPath: + """ + Micro-optimized class for searching a root for children. + + Root is a path on the file system that may contain metadata + directories either as natural directories or within a zip file. + + >>> FastPath('').children() + ['...'] + + FastPath objects are cached and recycled for any given root. + + >>> FastPath('foobar') is FastPath('foobar') + True + """ + + @functools.lru_cache() # type: ignore[misc] + def __new__(cls, root): + return super().__new__(cls) + + def __init__(self, root): + self.root = root + + def joinpath(self, child): + return pathlib.Path(self.root, child) + + def children(self): + with suppress(Exception): + return os.listdir(self.root or '.') + with suppress(Exception): + return self.zip_children() + return [] + + def zip_children(self): + # deferred for performance (python/importlib_metadata#502) + from zipp.compat.overlay import zipfile + + zip_path = zipfile.Path(self.root) + names = zip_path.root.namelist() + self.joinpath = zip_path.joinpath + + return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names) + + def search(self, name): + return self.lookup(self.mtime).search(name) + + @property + def mtime(self): + with suppress(OSError): + return os.stat(self.root).st_mtime + self.lookup.cache_clear() + + @method_cache + def lookup(self, mtime): + return Lookup(self) + + +class Lookup: + """ + A micro-optimized class for searching a (fast) path for metadata. + """ + + def __init__(self, path: FastPath): + """ + Calculate all of the children representing metadata. + + From the children in the path, calculate early all of the + children that appear to represent metadata (infos) or legacy + metadata (eggs). + """ + + base = os.path.basename(path.root).lower() + base_is_egg = base.endswith(".egg") + self.infos = FreezableDefaultDict(list) + self.eggs = FreezableDefaultDict(list) + + for child in path.children(): + low = child.lower() + if low.endswith((".dist-info", ".egg-info")): + # rpartition is faster than splitext and suitable for this purpose. + name = low.rpartition(".")[0].partition("-")[0] + normalized = Prepared.normalize(name) + self.infos[normalized].append(path.joinpath(child)) + elif base_is_egg and low == "egg-info": + name = base.rpartition(".")[0].partition("-")[0] + legacy_normalized = Prepared.legacy_normalize(name) + self.eggs[legacy_normalized].append(path.joinpath(child)) + + self.infos.freeze() + self.eggs.freeze() + + def search(self, prepared: Prepared): + """ + Yield all infos and eggs matching the Prepared query. + """ + infos = ( + self.infos[prepared.normalized] + if prepared + else itertools.chain.from_iterable(self.infos.values()) + ) + eggs = ( + self.eggs[prepared.legacy_normalized] + if prepared + else itertools.chain.from_iterable(self.eggs.values()) + ) + return itertools.chain(infos, eggs) + + +class Prepared: + """ + A prepared search query for metadata on a possibly-named package. + + Pre-calculates the normalization to prevent repeated operations. + + >>> none = Prepared(None) + >>> none.normalized + >>> none.legacy_normalized + >>> bool(none) + False + >>> sample = Prepared('Sample__Pkg-name.foo') + >>> sample.normalized + 'sample_pkg_name_foo' + >>> sample.legacy_normalized + 'sample__pkg_name.foo' + >>> bool(sample) + True + """ + + normalized = None + legacy_normalized = None + + def __init__(self, name: Optional[str]): + self.name = name + if name is None: + return + self.normalized = self.normalize(name) + self.legacy_normalized = self.legacy_normalize(name) + + @staticmethod + def normalize(name): + """ + PEP 503 normalization plus dashes as underscores. + """ + return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') + + @staticmethod + def legacy_normalize(name): + """ + Normalize the package name as found in the convention in + older packaging tools versions and specs. + """ + return name.lower().replace('-', '_') + + def __bool__(self): + return bool(self.name) + + +@install +class MetadataPathFinder(NullFinder, DistributionFinder): + """A degenerate finder for distribution packages on the file system. + + This finder supplies only a find_distributions() method for versions + of Python that do not have a PathFinder find_distributions(). + """ + + @classmethod + def find_distributions( + cls, context=DistributionFinder.Context() + ) -> Iterable[PathDistribution]: + """ + Find distributions. + + Return an iterable of all Distribution instances capable of + loading the metadata for packages matching ``context.name`` + (or all names if ``None`` indicated) along the paths in the list + of directories ``context.path``. + """ + found = cls._search_paths(context.name, context.path) + return map(PathDistribution, found) + + @classmethod + def _search_paths(cls, name, paths): + """Find metadata directories in paths heuristically.""" + prepared = Prepared(name) + return itertools.chain.from_iterable( + path.search(prepared) for path in map(FastPath, paths) + ) + + @classmethod + def invalidate_caches(cls) -> None: + FastPath.__new__.cache_clear() + + +class PathDistribution(Distribution): + def __init__(self, path: SimplePath) -> None: + """Construct a distribution. + + :param path: SimplePath indicating the metadata directory. + """ + self._path = path + + def read_text(self, filename: str | os.PathLike[str]) -> Optional[str]: + with suppress( + FileNotFoundError, + IsADirectoryError, + KeyError, + NotADirectoryError, + PermissionError, + ): + return self._path.joinpath(filename).read_text(encoding='utf-8') + + return None + + read_text.__doc__ = Distribution.read_text.__doc__ + + def locate_file(self, path: str | os.PathLike[str]) -> SimplePath: + return self._path.parent / path + + @property + def _normalized_name(self): + """ + Performance optimization: where possible, resolve the + normalized name from the file system path. + """ + stem = os.path.basename(str(self._path)) + return ( + pass_none(Prepared.normalize)(self._name_from_stem(stem)) + or super()._normalized_name + ) + + @staticmethod + def _name_from_stem(stem): + """ + >>> PathDistribution._name_from_stem('foo-3.0.egg-info') + 'foo' + >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info') + 'CherryPy' + >>> PathDistribution._name_from_stem('face.egg-info') + 'face' + >>> PathDistribution._name_from_stem('foo.bar') + """ + filename, ext = os.path.splitext(stem) + if ext not in ('.dist-info', '.egg-info'): + return + name, sep, rest = filename.partition('-') + return name + + +def distribution(distribution_name: str) -> Distribution: + """Get the ``Distribution`` instance for the named package. + + :param distribution_name: The name of the distribution package as a string. + :return: A ``Distribution`` instance (or subclass thereof). + """ + return Distribution.from_name(distribution_name) + + +def distributions(**kwargs) -> Iterable[Distribution]: + """Get all ``Distribution`` instances in the current environment. + + :return: An iterable of ``Distribution`` instances. + """ + return Distribution.discover(**kwargs) + + +def metadata(distribution_name: str) -> _meta.PackageMetadata: + """Get the metadata for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: A PackageMetadata containing the parsed metadata. + """ + return Distribution.from_name(distribution_name).metadata + + +def version(distribution_name: str) -> str: + """Get the version string for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: The version string for the package as defined in the package's + "Version" metadata key. + """ + return distribution(distribution_name).version + + +_unique = functools.partial( + unique_everseen, + key=py39.normalized_name, +) +""" +Wrapper for ``distributions`` to return unique distributions by name. +""" + + +def entry_points(**params) -> EntryPoints: + """Return EntryPoint objects for all installed packages. + + Pass selection parameters (group or name) to filter the + result to entry points matching those properties (see + EntryPoints.select()). + + :return: EntryPoints for all installed packages. + """ + eps = itertools.chain.from_iterable( + dist.entry_points for dist in _unique(distributions()) + ) + return EntryPoints(eps).select(**params) + + +def files(distribution_name: str) -> Optional[List[PackagePath]]: + """Return a list of files for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: List of files composing the distribution. + """ + return distribution(distribution_name).files + + +def requires(distribution_name: str) -> Optional[List[str]]: + """ + Return a list of requirements for the named package. + + :return: An iterable of requirements, suitable for + packaging.requirement.Requirement. + """ + return distribution(distribution_name).requires + + +def packages_distributions() -> Mapping[str, List[str]]: + """ + Return a mapping of top-level packages to their + distributions. + + >>> import collections.abc + >>> pkgs = packages_distributions() + >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values()) + True + """ + pkg_to_dist = collections.defaultdict(list) + for dist in distributions(): + for pkg in _top_level_declared(dist) or _top_level_inferred(dist): + pkg_to_dist[pkg].append(dist.metadata['Name']) + return dict(pkg_to_dist) + + +def _top_level_declared(dist): + return (dist.read_text('top_level.txt') or '').split() + + +def _topmost(name: PackagePath) -> Optional[str]: + """ + Return the top-most parent as long as there is a parent. + """ + top, *rest = name.parts + return top if rest else None + + +def _get_toplevel_name(name: PackagePath) -> str: + """ + Infer a possibly importable module name from a name presumed on + sys.path. + + >>> _get_toplevel_name(PackagePath('foo.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pyc')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo/__init__.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pth')) + 'foo.pth' + >>> _get_toplevel_name(PackagePath('foo.dist-info')) + 'foo.dist-info' + """ + # Defer import of inspect for performance (python/cpython#118761) + import inspect + + return _topmost(name) or inspect.getmodulename(name) or str(name) + + +def _top_level_inferred(dist): + opt_names = set(map(_get_toplevel_name, always_iterable(dist.files))) + + def importable_name(name): + return '.' not in name + + return filter(importable_name, opt_names) diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa1aef669d9873935cad07d8e57794c586c4cc56 GIT binary patch literal 41898 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_adapters.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_adapters.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..354cf02f454ef0f4649a949b6f7dc85dcb3ae301 GIT binary patch literal 2929 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_functools.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_functools.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcb69572ea4fd7ec960e83e6868299ffc5f034c1 GIT binary patch literal 3161 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_meta.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_metadata/__pycache__/_meta.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c132eec6464ce5b75a1e186e90c70f29cc4d9d3 GIT binary patch literal 3259 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 dd = FreezableDefaultDict(list) + >>> dd[0].append('1') + >>> dd.freeze() + >>> dd[1] + [] + >>> len(dd) + 1 + """ + + def __missing__(self, key): + return getattr(self, '_frozen', super().__missing__)(key) + + def freeze(self): + self._frozen = lambda key: self.default_factory() + + +class Pair(collections.namedtuple('Pair', 'name value')): + @classmethod + def parse(cls, text): + return cls(*map(str.strip, text.split("=", 1))) diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/_compat.py b/venv/lib/python3.8/site-packages/importlib_metadata/_compat.py new file mode 100644 index 000000000..01356d69b --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata/_compat.py @@ -0,0 +1,56 @@ +import platform +import sys + +__all__ = ['install', 'NullFinder'] + + +def install(cls): + """ + Class decorator for installation on sys.meta_path. + + Adds the backport DistributionFinder to sys.meta_path and + attempts to disable the finder functionality of the stdlib + DistributionFinder. + """ + sys.meta_path.append(cls()) + disable_stdlib_finder() + return cls + + +def disable_stdlib_finder(): + """ + Give the backport primacy for discovering path-based distributions + by monkey-patching the stdlib O_O. + + See #91 for more background for rationale on this sketchy + behavior. + """ + + def matches(finder): + return getattr( + finder, '__module__', None + ) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions') + + for finder in filter(matches, sys.meta_path): # pragma: nocover + del finder.find_distributions + + +class NullFinder: + """ + A "Finder" (aka "MetaPathFinder") that never finds any modules, + but may find distributions. + """ + + @staticmethod + def find_spec(*args, **kwargs): + return None + + +def pypy_partial(val): + """ + Adjust for variable stacklevel on partial under PyPy. + + Workaround for #327. + """ + is_pypy = platform.python_implementation() == 'PyPy' + return val + is_pypy diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/_functools.py b/venv/lib/python3.8/site-packages/importlib_metadata/_functools.py new file mode 100644 index 000000000..5dda6a219 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata/_functools.py @@ -0,0 +1,104 @@ +import functools +import types + + +# from jaraco.functools 3.3 +def method_cache(method, cache_wrapper=None): + """ + Wrap lru_cache to support storing the cache data in the object instances. + + Abstracts the common paradigm where the method explicitly saves an + underscore-prefixed protected property on first call and returns that + subsequently. + + >>> class MyClass: + ... calls = 0 + ... + ... @method_cache + ... def method(self, value): + ... self.calls += 1 + ... return value + + >>> a = MyClass() + >>> a.method(3) + 3 + >>> for x in range(75): + ... res = a.method(x) + >>> a.calls + 75 + + Note that the apparent behavior will be exactly like that of lru_cache + except that the cache is stored on each instance, so values in one + instance will not flush values from another, and when an instance is + deleted, so are the cached values for that instance. + + >>> b = MyClass() + >>> for x in range(35): + ... res = b.method(x) + >>> b.calls + 35 + >>> a.method(0) + 0 + >>> a.calls + 75 + + Note that if method had been decorated with ``functools.lru_cache()``, + a.calls would have been 76 (due to the cached value of 0 having been + flushed by the 'b' instance). + + Clear the cache with ``.cache_clear()`` + + >>> a.method.cache_clear() + + Same for a method that hasn't yet been called. + + >>> c = MyClass() + >>> c.method.cache_clear() + + Another cache wrapper may be supplied: + + >>> cache = functools.lru_cache(maxsize=2) + >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache) + >>> a = MyClass() + >>> a.method2() + 3 + + Caution - do not subsequently wrap the method with another decorator, such + as ``@property``, which changes the semantics of the function. + + See also + http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/ + for another implementation and additional justification. + """ + cache_wrapper = cache_wrapper or functools.lru_cache() + + def wrapper(self, *args, **kwargs): + # it's the first call, replace the method with a cached, bound method + bound_method = types.MethodType(method, self) + cached_method = cache_wrapper(bound_method) + setattr(self, method.__name__, cached_method) + return cached_method(*args, **kwargs) + + # Support cache clear even before cache has been created. + wrapper.cache_clear = lambda: None + + return wrapper + + +# From jaraco.functools 3.3 +def pass_none(func): + """ + Wrap func so it's not called if its first param is None + + >>> print_text = pass_none(print) + >>> print_text('text') + text + >>> print_text(None) + """ + + @functools.wraps(func) + def wrapper(param, *args, **kwargs): + if param is not None: + return func(param, *args, **kwargs) + + return wrapper diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/_itertools.py b/venv/lib/python3.8/site-packages/importlib_metadata/_itertools.py new file mode 100644 index 000000000..79d37198c --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata/_itertools.py @@ -0,0 +1,171 @@ +from collections import defaultdict, deque +from itertools import filterfalse + + +def unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element + + +# copied from more_itertools 8.8 +def always_iterable(obj, base_type=(str, bytes)): + """If *obj* is iterable, return an iterator over its items:: + + >>> obj = (1, 2, 3) + >>> list(always_iterable(obj)) + [1, 2, 3] + + If *obj* is not iterable, return a one-item iterable containing *obj*:: + + >>> obj = 1 + >>> list(always_iterable(obj)) + [1] + + If *obj* is ``None``, return an empty iterable: + + >>> obj = None + >>> list(always_iterable(None)) + [] + + By default, binary and text strings are not considered iterable:: + + >>> obj = 'foo' + >>> list(always_iterable(obj)) + ['foo'] + + If *base_type* is set, objects for which ``isinstance(obj, base_type)`` + returns ``True`` won't be considered iterable. + + >>> obj = {'a': 1} + >>> list(always_iterable(obj)) # Iterate over the dict's keys + ['a'] + >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit + [{'a': 1}] + + Set *base_type* to ``None`` to avoid any special handling and treat objects + Python considers iterable as iterable: + + >>> obj = 'foo' + >>> list(always_iterable(obj, base_type=None)) + ['f', 'o', 'o'] + """ + if obj is None: + return iter(()) + + if (base_type is not None) and isinstance(obj, base_type): + return iter((obj,)) + + try: + return iter(obj) + except TypeError: + return iter((obj,)) + + +# Copied from more_itertools 10.3 +class bucket: + """Wrap *iterable* and return an object that buckets the iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/_meta.py b/venv/lib/python3.8/site-packages/importlib_metadata/_meta.py new file mode 100644 index 000000000..0942bbd96 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata/_meta.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import os +from typing import ( + Any, + Dict, + Iterator, + List, + Optional, + Protocol, + TypeVar, + Union, + overload, +) + +_T = TypeVar("_T") + + +class PackageMetadata(Protocol): + def __len__(self) -> int: ... # pragma: no cover + + def __contains__(self, item: str) -> bool: ... # pragma: no cover + + def __getitem__(self, key: str) -> str: ... # pragma: no cover + + def __iter__(self) -> Iterator[str]: ... # pragma: no cover + + @overload + def get( + self, name: str, failobj: None = None + ) -> Optional[str]: ... # pragma: no cover + + @overload + def get(self, name: str, failobj: _T) -> Union[str, _T]: ... # pragma: no cover + + # overload per python/importlib_metadata#435 + @overload + def get_all( + self, name: str, failobj: None = None + ) -> Optional[List[Any]]: ... # pragma: no cover + + @overload + def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]: + """ + Return all values associated with a possibly multi-valued key. + """ + + @property + def json(self) -> Dict[str, Union[str, List[str]]]: + """ + A JSON-compatible form of the metadata. + """ + + +class SimplePath(Protocol): + """ + A minimal subset of pathlib.Path required by Distribution. + """ + + def joinpath( + self, other: Union[str, os.PathLike[str]] + ) -> SimplePath: ... # pragma: no cover + + def __truediv__( + self, other: Union[str, os.PathLike[str]] + ) -> SimplePath: ... # pragma: no cover + + @property + def parent(self) -> SimplePath: ... # pragma: no cover + + def read_text(self, encoding=None) -> str: ... # pragma: no cover + + def read_bytes(self) -> bytes: ... # pragma: no cover + + def exists(self) -> bool: ... # pragma: no cover diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/_text.py b/venv/lib/python3.8/site-packages/importlib_metadata/_text.py new file mode 100644 index 000000000..c88cfbb23 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata/_text.py @@ -0,0 +1,99 @@ +import re + +from ._functools import method_cache + + +# from jaraco.text 3.5 +class FoldedCase(str): + """ + A case insensitive string class; behaves just like str + except compares equal when the only variation is case. + + >>> s = FoldedCase('hello world') + + >>> s == 'Hello World' + True + + >>> 'Hello World' == s + True + + >>> s != 'Hello World' + False + + >>> s.index('O') + 4 + + >>> s.split('O') + ['hell', ' w', 'rld'] + + >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])) + ['alpha', 'Beta', 'GAMMA'] + + Sequence membership is straightforward. + + >>> "Hello World" in [s] + True + >>> s in ["Hello World"] + True + + You may test for set inclusion, but candidate and elements + must both be folded. + + >>> FoldedCase("Hello World") in {s} + True + >>> s in {FoldedCase("Hello World")} + True + + String inclusion works as long as the FoldedCase object + is on the right. + + >>> "hello" in FoldedCase("Hello World") + True + + But not if the FoldedCase object is on the left: + + >>> FoldedCase('hello') in 'Hello World' + False + + In that case, use in_: + + >>> FoldedCase('hello').in_('Hello World') + True + + >>> FoldedCase('hello') > FoldedCase('Hello') + False + """ + + def __lt__(self, other): + return self.lower() < other.lower() + + def __gt__(self, other): + return self.lower() > other.lower() + + def __eq__(self, other): + return self.lower() == other.lower() + + def __ne__(self, other): + return self.lower() != other.lower() + + def __hash__(self): + return hash(self.lower()) + + def __contains__(self, other): + return super().lower().__contains__(other.lower()) + + def in_(self, other): + "Does self appear in other?" + return self in FoldedCase(other) + + # cache lower since it's likely to be called frequently. + @method_cache + def lower(self): + return super().lower() + + def index(self, sub): + return self.lower().index(sub.lower()) + + def split(self, splitter=' ', maxsplit=0): + pattern = re.compile(re.escape(splitter), re.I) + return pattern.split(self, maxsplit) diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/compat/__init__.py b/venv/lib/python3.8/site-packages/importlib_metadata/compat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/compat/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_metadata/compat/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0936e0c94df33c410fe137f9a719d3b41f98776 GIT binary patch literal 218 zcmYk0Jqp4=5QR5jAws}wSZt<9V>s;hAU2-Idw2?4k6>rz1_XWZ zzQ@eGV)l7HVM)_Fs3D&N{*|R+l^jP24coCy_{$)jq&QP!as3-$+VDD5A6Vuhfwdom0{H;W3*09)_KDTdDJhY&w*KAuBD| z8emjnI$+w1AekRCOhe|@ai!S1#h>qapN;s0+kugVW2sbNnJ%4`0%{PQGAkV{LBK9@ zdmGcLwM_ZC%f8#b%T3hVsP+ynj+|2$jKcHiJDHP<-~xMlNe|*i)xr)8mF!p12i#PF zwpHY>s+IxeH*ru!Mr&t0=p{8RZ^++yGSnF)a0+7yDtQAFP`bz=Zo!#WW+7g)++Boz$%D{2K9ZwdM!iespN1b9;ZD4XZxd|LO zz2l4Y$q{;be`kkS#Z=u zHOJbX;#ptt{k!P)2bCBEomK4Qti!M8KC*2AiVjXf>fnr#Z1k2XJ4bTl;1#B{HzQjxU>{R%tnQE}2XG)R;05Bc8vJ^TB;y@b(t_)o4ToUwo1;lmNo z;Y*s`Pqau@FzHFZ@@l{E>!1i~UT}|rk7F4~UJdH72Xz4&_al?|RZYa738jna*%-uceRx5H!A8C0{S6WipZ zF}Oa{sxhSFl5{piFiULap1XECobwLNBbwbev~1#QcFA6|SBx{`T_t$zG56_9Lgo0? z^VqfbCpUv@=F0IlXrGZsC zD~pPU_dV_Cxw&}_P7pO(hORU0&fN<(x&R}{$i_8oq%b1$F(#wl@^6NZX#d|Y`9jx_ z!v?MtRQWNipwev(jmf_ivo3FYkO%GI!8roV6dV{7^DDr71 Optional[str]: + """ + Honor name normalization for distributions that don't provide ``_normalized_name``. + """ + try: + return dist._normalized_name + except AttributeError: + from .. import Prepared # -> delay to prevent circular imports. + + return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name']) + + +def ep_matches(ep: EntryPoint, **params) -> bool: + """ + Workaround for ``EntryPoint`` objects without the ``matches`` method. + """ + try: + return ep.matches(**params) + except AttributeError: + from .. import EntryPoint # -> delay to prevent circular imports. + + # Reconstruct the EntryPoint object to make sure it is compatible. + return EntryPoint(ep.name, ep.value, ep.group).matches(**params) diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/diagnose.py b/venv/lib/python3.8/site-packages/importlib_metadata/diagnose.py new file mode 100644 index 000000000..e405471ac --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_metadata/diagnose.py @@ -0,0 +1,21 @@ +import sys + +from . import Distribution + + +def inspect(path): + print("Inspecting", path) + dists = list(Distribution.discover(path=[path])) + if not dists: + return + print("Found", len(dists), "packages:", end=' ') + print(', '.join(dist.name for dist in dists)) + + +def run(): + for path in sys.path: + inspect(path) + + +if __name__ == '__main__': + run() diff --git a/venv/lib/python3.8/site-packages/importlib_metadata/py.typed b/venv/lib/python3.8/site-packages/importlib_metadata/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/INSTALLER b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/LICENSE b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/METADATA b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/METADATA new file mode 100644 index 000000000..1ee4166c1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/METADATA @@ -0,0 +1,103 @@ +Metadata-Version: 2.1 +Name: importlib_resources +Version: 6.4.5 +Summary: Read resources from Python packages +Author-email: Barry Warsaw +Maintainer-email: "Jason R. Coombs" +Project-URL: Source, https://github.com/python/importlib_resources +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: zipp >=3.1.0 ; python_version < "3.10" +Provides-Extra: check +Requires-Dist: pytest-checkdocs >=2.4 ; extra == 'check' +Requires-Dist: pytest-ruff >=0.2.1 ; (sys_platform != "cygwin") and extra == 'check' +Provides-Extra: cover +Requires-Dist: pytest-cov ; extra == 'cover' +Provides-Extra: doc +Requires-Dist: sphinx >=3.5 ; extra == 'doc' +Requires-Dist: jaraco.packaging >=9.3 ; extra == 'doc' +Requires-Dist: rst.linker >=1.9 ; extra == 'doc' +Requires-Dist: furo ; extra == 'doc' +Requires-Dist: sphinx-lint ; extra == 'doc' +Requires-Dist: jaraco.tidelift >=1.4 ; extra == 'doc' +Provides-Extra: enabler +Requires-Dist: pytest-enabler >=2.2 ; extra == 'enabler' +Provides-Extra: test +Requires-Dist: pytest !=8.1.*,>=6 ; extra == 'test' +Requires-Dist: zipp >=3.17 ; extra == 'test' +Requires-Dist: jaraco.test >=5.4 ; extra == 'test' +Provides-Extra: type +Requires-Dist: pytest-mypy ; extra == 'type' + +.. image:: https://img.shields.io/pypi/v/importlib_resources.svg + :target: https://pypi.org/project/importlib_resources + +.. image:: https://img.shields.io/pypi/pyversions/importlib_resources.svg + +.. image:: https://github.com/python/importlib_resources/actions/workflows/main.yml/badge.svg + :target: https://github.com/python/importlib_resources/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff + +.. image:: https://readthedocs.org/projects/importlib-resources/badge/?version=latest + :target: https://importlib-resources.readthedocs.io/en/latest/?badge=latest + +.. image:: https://img.shields.io/badge/skeleton-2024-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://tidelift.com/badges/package/pypi/importlib-resources + :target: https://tidelift.com/subscription/pkg/pypi-importlib-resources?utm_source=pypi-importlib-resources&utm_medium=readme + +``importlib_resources`` is a backport of Python standard library +`importlib.resources +`_ +module for older Pythons. + +The key goal of this module is to replace parts of `pkg_resources +`_ with a +solution in Python's stdlib that relies on well-defined APIs. This makes +reading resources included in packages easier, with more stable and consistent +semantics. + +Compatibility +============= + +New features are introduced in this third-party library and later merged +into CPython. The following table indicates which versions of this library +were contributed to different versions in the standard library: + +.. list-table:: + :header-rows: 1 + + * - importlib_resources + - stdlib + * - 6.0 + - 3.13 + * - 5.12 + - 3.12 + * - 5.7 + - 3.11 + * - 5.0 + - 3.10 + * - 1.3 + - 3.9 + * - 0.5 (?) + - 3.7 + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. diff --git a/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/RECORD b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/RECORD new file mode 100644 index 000000000..dbcc62347 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/RECORD @@ -0,0 +1,67 @@ +importlib_resources-6.4.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +importlib_resources-6.4.5.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +importlib_resources-6.4.5.dist-info/METADATA,sha256=iwGfOzcc9jUBSuQa6zQeXla0el4YCZlAoj5uRIm7fyg,3975 +importlib_resources-6.4.5.dist-info/RECORD,, +importlib_resources-6.4.5.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91 +importlib_resources-6.4.5.dist-info/top_level.txt,sha256=fHIjHU1GZwAjvcydpmUnUrTnbvdiWjG4OEVZK8by0TQ,20 +importlib_resources/__init__.py,sha256=3J-261Qqzg-1kBenMVsOsvJo19EbUmYqjHkrZWiFHOM,703 +importlib_resources/__pycache__/__init__.cpython-38.pyc,, +importlib_resources/__pycache__/_adapters.cpython-38.pyc,, +importlib_resources/__pycache__/_common.cpython-38.pyc,, +importlib_resources/__pycache__/_functional.cpython-38.pyc,, +importlib_resources/__pycache__/_itertools.cpython-38.pyc,, +importlib_resources/__pycache__/abc.cpython-38.pyc,, +importlib_resources/__pycache__/readers.cpython-38.pyc,, +importlib_resources/__pycache__/simple.cpython-38.pyc,, +importlib_resources/_adapters.py,sha256=vprJGbUeHbajX6XCuMP6J3lMrqCi-P_MTlziJUR7jfk,4482 +importlib_resources/_common.py,sha256=5PVT4ezn_Ptj7LIAebtLYquK7A6X4EYoQJM37yTBdbQ,5624 +importlib_resources/_functional.py,sha256=mLU4DwSlh8_2IXWqwKOfPVxyRqAEpB3B4XTfRxr3X3M,2651 +importlib_resources/_itertools.py,sha256=eDisV6RqiNZOogLSXf6LOGHOYc79FGgPrKNLzFLmCrU,1277 +importlib_resources/abc.py,sha256=UKNU9ncEDkZRB3txcGb3WLxsL2iju9JbaLTI-dfLE_4,5162 +importlib_resources/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +importlib_resources/compat/__pycache__/__init__.cpython-38.pyc,, +importlib_resources/compat/__pycache__/py38.cpython-38.pyc,, +importlib_resources/compat/__pycache__/py39.cpython-38.pyc,, +importlib_resources/compat/py38.py,sha256=MWhut3XsAJwBYUaa5Qb2AoCrZNqcQjVThP-P1uBoE_4,230 +importlib_resources/compat/py39.py,sha256=KlP7QiD3NkoytWFjJgrJI33uRTw667vEDtkZWUtBDZM,152 +importlib_resources/future/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +importlib_resources/future/__pycache__/__init__.cpython-38.pyc,, +importlib_resources/future/__pycache__/adapters.cpython-38.pyc,, +importlib_resources/future/adapters.py,sha256=1-MF2VRcCButhcC1OMfZILU9o3kwZ4nXB2lurXpaIAw,2940 +importlib_resources/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +importlib_resources/readers.py,sha256=n9Rn8B5UHapkXGSfFhQNbdk_pfDCISPBLIXZnpoOKs8,6251 +importlib_resources/simple.py,sha256=wJm2qGZ9EMPFhRLiJBa9Em5tVKbD7Q8ibWtt4ZNgWBU,2590 +importlib_resources/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +importlib_resources/tests/__pycache__/__init__.cpython-38.pyc,, +importlib_resources/tests/__pycache__/_path.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_compatibilty_files.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_contents.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_custom.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_files.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_functional.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_open.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_path.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_read.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_reader.cpython-38.pyc,, +importlib_resources/tests/__pycache__/test_resource.cpython-38.pyc,, +importlib_resources/tests/__pycache__/util.cpython-38.pyc,, +importlib_resources/tests/__pycache__/zip.cpython-38.pyc,, +importlib_resources/tests/_path.py,sha256=eqMTKkYA9Hc8_mzrVBN14HPuoc2HFNxuyTWqAo_2ZEM,2288 +importlib_resources/tests/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +importlib_resources/tests/compat/__pycache__/__init__.cpython-38.pyc,, +importlib_resources/tests/compat/__pycache__/py312.cpython-38.pyc,, +importlib_resources/tests/compat/__pycache__/py39.cpython-38.pyc,, +importlib_resources/tests/compat/py312.py,sha256=qcWjpZhQo2oEsdwIlRRQHrsMGDltkFTnETeG7fLdUS8,364 +importlib_resources/tests/compat/py39.py,sha256=nOjut4CZDtRgZEblX9hWhupo9vli_CA1270_JwdQzRo,441 +importlib_resources/tests/test_compatibilty_files.py,sha256=95N_R7aik8cvnE6sBJpsxmP0K5plOWRIJDgbalD-Hpw,3314 +importlib_resources/tests/test_contents.py,sha256=EagRx9Mz7MOe1kRaOc9XNX_YlBYy90Qzhv2rqWMhMlw,837 +importlib_resources/tests/test_custom.py,sha256=QrHZqIWl0e-fsQRfm0ych8stOlKJOsAIU3rK6QOcyN0,1221 +importlib_resources/tests/test_files.py,sha256=4G4Wo4_2UJtpg9HrEKjRaXimM68lXKjkAmFY-9OLCyE,5796 +importlib_resources/tests/test_functional.py,sha256=DV8sdnwtpacNjHQ3ExifIoeQTCr4C-M-NVbaWewOcAo,8863 +importlib_resources/tests/test_open.py,sha256=eCDLP6SszzBK6vs5j6LDkuR0Y2rHXrTYNRVZsAFKnCQ,2681 +importlib_resources/tests/test_path.py,sha256=rz_BOqNzEu5ZRyrhAHOi9tvx3K_0AqHiRNkYT4QFhQw,1985 +importlib_resources/tests/test_read.py,sha256=oh0ZkCZ04N0DxPXgBLdadENwia4J0_sYt73uaH_I11g,3045 +importlib_resources/tests/test_reader.py,sha256=G62D2vkFjYSeFT6BFE-z_shUX8n7h80f089ojXCufBY,4655 +importlib_resources/tests/test_resource.py,sha256=ZxW6cIXJRwgvU7-wCc2jIRWV9M4pX4uznijN0vNfplM,7686 +importlib_resources/tests/util.py,sha256=CtY1uL-Xl71sw553ow2st6M5X710jf2jrhYAv6nhZ_Y,6065 +importlib_resources/tests/zip.py,sha256=nG9D6u_4nu67NrEUFfMt7vzba0qO_-QPyAAWc6W4gP4,577 diff --git a/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/WHEEL b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/WHEEL new file mode 100644 index 000000000..0fde4dd96 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (74.1.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/top_level.txt b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/top_level.txt new file mode 100644 index 000000000..58ad1bd33 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources-6.4.5.dist-info/top_level.txt @@ -0,0 +1 @@ +importlib_resources diff --git a/venv/lib/python3.8/site-packages/importlib_resources/__init__.py b/venv/lib/python3.8/site-packages/importlib_resources/__init__.py new file mode 100644 index 000000000..723c9f9eb --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/__init__.py @@ -0,0 +1,43 @@ +""" +Read resources contained within a package. + +This codebase is shared between importlib.resources in the stdlib +and importlib_resources in PyPI. See +https://github.com/python/importlib_metadata/wiki/Development-Methodology +for more detail. +""" + +from ._common import ( + as_file, + files, + Package, + Anchor, +) + +from ._functional import ( + contents, + is_resource, + open_binary, + open_text, + path, + read_binary, + read_text, +) + +from .abc import ResourceReader + + +__all__ = [ + 'Package', + 'Anchor', + 'ResourceReader', + 'as_file', + 'files', + 'contents', + 'is_resource', + 'open_binary', + 'open_text', + 'path', + 'read_binary', + 'read_text', +] diff --git a/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e9ffc6578c65941186b6f56f571f885e3ca0bcd GIT binary patch literal 888 zcma)4KabNe98KG_X_NNu%EXv~r96AKd)cBy(rV!&3O{Ij2)e|df%XR|yBGR3JJ;$biD55!1jIFn;MmJ>XYInHH)3pvG8Im5F(ClzP%9G{2vkd0Wz z#%#iJRm3Q2=T$Qx!Xmdk1 zQ04iDmPZDInp!Bs+P2gLY7OoVK$To}+Bm`M>On9_xE4z5SlG|0Vh=3x7i+a!y{XD; zfV_3Cv(FIPgbucK)o6*jools1#|wxAM;Ub#-SHcaUc)U2-APbx`3?e=X`wf}e639> zwSkfaIu}*`y%#)Qq&}mTthoR`jA(tf`ty^1^h!0YHjB)UqZL8W`hweItG>`3C{lAp z&CXBug@aGdryX^zFHErWc)Bm*>7wV)t_~hY!@-y|G-8GkW)VU#5;KS}ijYMZN0>y2 z0Ww8|7?$^oxz7k;x*-H-B={q>I#D=EZ!vK27_*f6kx30r718`2YX_ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_adapters.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_adapters.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1d862d9c3052f47da808352837481383684b107 GIT binary patch literal 7453 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 oCc`cDuJaqE*$TblCoYp*yh3MqM{ZtaeCG^2En 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_functional.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/_functional.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38c5c9a5a2ca06fade04ae9038012fe51ea65719 GIT binary patch literal 2778 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/simple.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/__pycache__/simple.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f70793a967a63f2afe4664358d96b4cb6bb68485 GIT binary patch literal 4538 zcmb7HTXWmS6~+P}2~rnRif>A%3DPzfHWAs@y`&jeBU?^d&3NcWcG3=KS`LU^NsvGQ zUO;rjkzY#BG*A5nnU4MF|B+vT*F5 zuQs%L7SsNy@hY!9(|Ap=llsKyH85iFI!5ZEhW-lr4Zec@it4YTzslFpUlW#SOw8UI z*B@%F_3_7z{U8zY&~b(Ba3?}Lwhu#T`{9#qUyOurOW|;gwi~Y?w$^Rwsdd*29C^O~ zAT{rMaq@~`9K$P*q%*=wXW)zUYN>M|;_ytmB6ewq@-u~Gj8UNh3XNds8Nw8mld7n3 z{X2lc&1dF>O<2!Tqjj!rX=j2_Py86%?M!q;ummPoEu<<-P7WU8!yy4mKlM*1_6fr3tpLtSn`%s2M+W}PBMb+Dp z3`ZW2YpY(1rBx}CGa0x9SMgAr${tE=qT&qV=;J@DmkJ>QXDICO&`uyw>@OOVpx)X4$Q&Zcd;FSVB}HDlo)$~w+b)qkFJj>Dnoh_N{Jgx|SG3J;^9 z2$IfyXApOy^W-=Twxe6yhf;`vC$Y-CJ~T9hGK`ua;HN&KJB9THzF}2ZO}%0^12}2qVc73Aggb3OHxv zcs|WeUv!T>pWAL2(EH>BABpx++vZTOn}pI6ajOJo1|hlo3{1I-ZfdEGqzK+dFT0sO zzE&2XU95PctKFYUSX`|T-6pHxGhM5TDO(tMGC6bn!c^W!?)p?q@0A3@PfDZ2R76X3ox~D_s`6rOOrUEt^zLjX1-7C5%MSA1Gq_ z5c37}H|K6pTC{+^fwz}uTcF{J5+hjGwWNp z)(N|(P1sYG=!rr7=jID-qCeG>%JVAl*I!U}Vsz)WBqh<7w`fbU)C=uzO>Cik3zbdu zufaKwbyAxo21C4EFGT&56L4REwDk5bGB21RtCU#3eIx?$O(b{j(B^cC2^t%JQtU9b z$2Nr(;NQOOhpywtJMHPKt(vs)qVl@b7&?)Bhq~m6X`Nn6!qAV?ihGO*(=rs5r4>Rh zPEBkXmlUC-s^w?xVu!ejN@LeqlZ}_DAd_kiFM^(o>j6qk7f!T^_77xOGlVww<2(2A z2zUOul%Xu-G97sor`6LZ^ncmrO`_4yXUqdaiS&NRzqIL0(pZfQkmSMiugwU zgsPuW^#N5Ksz|k^!)6RcNRk!B1PmC;TsJKGRTRc92In5S`YuX*7u7r~i3)g0&r(Y8 zQN~NPoR=(qMJ0(0jHL4AUPGmaO-UhT{CmYknKju-XkU(3Hf0`mfkl=|=G>#F)yP3s zkSP0eNx2Qzn>sf4t4fU;l%`9V2%%$X#aBNcU35|Of9`92!2KZi`@E0p4#YnLvR zQC-UBS)D#C2v}wOZ8RvLpRnfu^~89=o|>Fpu+y6Sd19VaF3dx4&cF2w<6oG$Few8^ zxF58t8PYc1pM}s=_ri4Tk@zOrfAAOSL@j(x|To5??+?12*_%?^XPuZZ^4&F$hlFIJljopT#D;Ej;d(nq06$X(xwV`7Ya zt@@B#c9_~?_I)`xx8t)Y^3T2CNWD`|BT2j2mwU!B3!XM56*98~9xx$qdk-C`OX*~p zUsKxZ5sdUYxc$bQoRmJbRm|$b=$DZ@y}wkQJD3=tC~g9WS0#0Eju-}ZfA-04)IRBiOMuBIGn|rdehi^ Jzj>p%^*?9R<4yno literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/_adapters.py b/venv/lib/python3.8/site-packages/importlib_resources/_adapters.py new file mode 100644 index 000000000..50688fbb6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/_adapters.py @@ -0,0 +1,168 @@ +from contextlib import suppress +from io import TextIOWrapper + +from . import abc + + +class SpecLoaderAdapter: + """ + Adapt a package spec to adapt the underlying loader. + """ + + def __init__(self, spec, adapter=lambda spec: spec.loader): + self.spec = spec + self.loader = adapter(spec) + + def __getattr__(self, name): + return getattr(self.spec, name) + + +class TraversableResourcesLoader: + """ + Adapt a loader to provide TraversableResources. + """ + + def __init__(self, spec): + self.spec = spec + + def get_resource_reader(self, name): + return CompatibilityFiles(self.spec)._native() + + +def _io_wrapper(file, mode='r', *args, **kwargs): + if mode == 'r': + return TextIOWrapper(file, *args, **kwargs) + elif mode == 'rb': + return file + raise ValueError(f"Invalid mode value '{mode}', only 'r' and 'rb' are supported") + + +class CompatibilityFiles: + """ + Adapter for an existing or non-existent resource reader + to provide a compatibility .files(). + """ + + class SpecPath(abc.Traversable): + """ + Path tied to a module spec. + Can be read and exposes the resource reader children. + """ + + def __init__(self, spec, reader): + self._spec = spec + self._reader = reader + + def iterdir(self): + if not self._reader: + return iter(()) + return iter( + CompatibilityFiles.ChildPath(self._reader, path) + for path in self._reader.contents() + ) + + def is_file(self): + return False + + is_dir = is_file + + def joinpath(self, other): + if not self._reader: + return CompatibilityFiles.OrphanPath(other) + return CompatibilityFiles.ChildPath(self._reader, other) + + @property + def name(self): + return self._spec.name + + def open(self, mode='r', *args, **kwargs): + return _io_wrapper(self._reader.open_resource(None), mode, *args, **kwargs) + + class ChildPath(abc.Traversable): + """ + Path tied to a resource reader child. + Can be read but doesn't expose any meaningful children. + """ + + def __init__(self, reader, name): + self._reader = reader + self._name = name + + def iterdir(self): + return iter(()) + + def is_file(self): + return self._reader.is_resource(self.name) + + def is_dir(self): + return not self.is_file() + + def joinpath(self, other): + return CompatibilityFiles.OrphanPath(self.name, other) + + @property + def name(self): + return self._name + + def open(self, mode='r', *args, **kwargs): + return _io_wrapper( + self._reader.open_resource(self.name), mode, *args, **kwargs + ) + + class OrphanPath(abc.Traversable): + """ + Orphan path, not tied to a module spec or resource reader. + Can't be read and doesn't expose any meaningful children. + """ + + def __init__(self, *path_parts): + if len(path_parts) < 1: + raise ValueError('Need at least one path part to construct a path') + self._path = path_parts + + def iterdir(self): + return iter(()) + + def is_file(self): + return False + + is_dir = is_file + + def joinpath(self, other): + return CompatibilityFiles.OrphanPath(*self._path, other) + + @property + def name(self): + return self._path[-1] + + def open(self, mode='r', *args, **kwargs): + raise FileNotFoundError("Can't open orphan path") + + def __init__(self, spec): + self.spec = spec + + @property + def _reader(self): + with suppress(AttributeError): + return self.spec.loader.get_resource_reader(self.spec.name) + + def _native(self): + """ + Return the native reader if it supports files(). + """ + reader = self._reader + return reader if hasattr(reader, 'files') else self + + def __getattr__(self, attr): + return getattr(self._reader, attr) + + def files(self): + return CompatibilityFiles.SpecPath(self.spec, self._reader) + + +def wrap_spec(package): + """ + Construct a package spec with traversable compatibility + on the spec/loader/reader. + """ + return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/_common.py b/venv/lib/python3.8/site-packages/importlib_resources/_common.py new file mode 100644 index 000000000..f065d493f --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/_common.py @@ -0,0 +1,211 @@ +import os +import pathlib +import tempfile +import functools +import contextlib +import types +import importlib +import inspect +import warnings +import itertools + +from typing import Union, Optional, cast +from .abc import ResourceReader, Traversable + +Package = Union[types.ModuleType, str] +Anchor = Package + + +def package_to_anchor(func): + """ + Replace 'package' parameter as 'anchor' and warn about the change. + + Other errors should fall through. + + >>> files('a', 'b') + Traceback (most recent call last): + TypeError: files() takes from 0 to 1 positional arguments but 2 were given + + Remove this compatibility in Python 3.14. + """ + undefined = object() + + @functools.wraps(func) + def wrapper(anchor=undefined, package=undefined): + if package is not undefined: + if anchor is not undefined: + return func(anchor, package) + warnings.warn( + "First parameter to files is renamed to 'anchor'", + DeprecationWarning, + stacklevel=2, + ) + return func(package) + elif anchor is undefined: + return func() + return func(anchor) + + return wrapper + + +@package_to_anchor +def files(anchor: Optional[Anchor] = None) -> Traversable: + """ + Get a Traversable resource for an anchor. + """ + return from_package(resolve(anchor)) + + +def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]: + """ + Return the package's loader if it's a ResourceReader. + """ + # We can't use + # a issubclass() check here because apparently abc.'s __subclasscheck__() + # hook wants to create a weak reference to the object, but + # zipimport.zipimporter does not support weak references, resulting in a + # TypeError. That seems terrible. + spec = package.__spec__ + reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore[union-attr] + if reader is None: + return None + return reader(spec.name) # type: ignore[union-attr] + + +@functools.singledispatch +def resolve(cand: Optional[Anchor]) -> types.ModuleType: + return cast(types.ModuleType, cand) + + +@resolve.register +def _(cand: str) -> types.ModuleType: + return importlib.import_module(cand) + + +@resolve.register +def _(cand: None) -> types.ModuleType: + return resolve(_infer_caller().f_globals['__name__']) + + +def _infer_caller(): + """ + Walk the stack and find the frame of the first caller not in this module. + """ + + def is_this_file(frame_info): + return frame_info.filename == stack[0].filename + + def is_wrapper(frame_info): + return frame_info.function == 'wrapper' + + stack = inspect.stack() + not_this_file = itertools.filterfalse(is_this_file, stack) + # also exclude 'wrapper' due to singledispatch in the call stack + callers = itertools.filterfalse(is_wrapper, not_this_file) + return next(callers).frame + + +def from_package(package: types.ModuleType): + """ + Return a Traversable object for the given package. + + """ + # deferred for performance (python/cpython#109829) + from .future.adapters import wrap_spec + + spec = wrap_spec(package) + reader = spec.loader.get_resource_reader(spec.name) + return reader.files() + + +@contextlib.contextmanager +def _tempfile( + reader, + suffix='', + # gh-93353: Keep a reference to call os.remove() in late Python + # finalization. + *, + _os_remove=os.remove, +): + # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try' + # blocks due to the need to close the temporary file to work on Windows + # properly. + fd, raw_path = tempfile.mkstemp(suffix=suffix) + try: + try: + os.write(fd, reader()) + finally: + os.close(fd) + del reader + yield pathlib.Path(raw_path) + finally: + try: + _os_remove(raw_path) + except FileNotFoundError: + pass + + +def _temp_file(path): + return _tempfile(path.read_bytes, suffix=path.name) + + +def _is_present_dir(path: Traversable) -> bool: + """ + Some Traversables implement ``is_dir()`` to raise an + exception (i.e. ``FileNotFoundError``) when the + directory doesn't exist. This function wraps that call + to always return a boolean and only return True + if there's a dir and it exists. + """ + with contextlib.suppress(FileNotFoundError): + return path.is_dir() + return False + + +@functools.singledispatch +def as_file(path): + """ + Given a Traversable object, return that object as a + path on the local file system in a context manager. + """ + return _temp_dir(path) if _is_present_dir(path) else _temp_file(path) + + +@as_file.register(pathlib.Path) +@contextlib.contextmanager +def _(path): + """ + Degenerate behavior for pathlib.Path objects. + """ + yield path + + +@contextlib.contextmanager +def _temp_path(dir: tempfile.TemporaryDirectory): + """ + Wrap tempfile.TemporaryDirectory to return a pathlib object. + """ + with dir as result: + yield pathlib.Path(result) + + +@contextlib.contextmanager +def _temp_dir(path): + """ + Given a traversable dir, recursively replicate the whole tree + to the file system in a context manager. + """ + assert path.is_dir() + with _temp_path(tempfile.TemporaryDirectory()) as temp_dir: + yield _write_contents(temp_dir, path) + + +def _write_contents(target, source): + child = target.joinpath(source.name) + if source.is_dir(): + child.mkdir() + for item in source.iterdir(): + _write_contents(child, item) + else: + child.write_bytes(source.read_bytes()) + return child diff --git a/venv/lib/python3.8/site-packages/importlib_resources/_functional.py b/venv/lib/python3.8/site-packages/importlib_resources/_functional.py new file mode 100644 index 000000000..f59416f2d --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/_functional.py @@ -0,0 +1,81 @@ +"""Simplified function-based API for importlib.resources""" + +import warnings + +from ._common import files, as_file + + +_MISSING = object() + + +def open_binary(anchor, *path_names): + """Open for binary reading the *resource* within *package*.""" + return _get_resource(anchor, path_names).open('rb') + + +def open_text(anchor, *path_names, encoding=_MISSING, errors='strict'): + """Open for text reading the *resource* within *package*.""" + encoding = _get_encoding_arg(path_names, encoding) + resource = _get_resource(anchor, path_names) + return resource.open('r', encoding=encoding, errors=errors) + + +def read_binary(anchor, *path_names): + """Read and return contents of *resource* within *package* as bytes.""" + return _get_resource(anchor, path_names).read_bytes() + + +def read_text(anchor, *path_names, encoding=_MISSING, errors='strict'): + """Read and return contents of *resource* within *package* as str.""" + encoding = _get_encoding_arg(path_names, encoding) + resource = _get_resource(anchor, path_names) + return resource.read_text(encoding=encoding, errors=errors) + + +def path(anchor, *path_names): + """Return the path to the *resource* as an actual file system path.""" + return as_file(_get_resource(anchor, path_names)) + + +def is_resource(anchor, *path_names): + """Return ``True`` if there is a resource named *name* in the package, + + Otherwise returns ``False``. + """ + return _get_resource(anchor, path_names).is_file() + + +def contents(anchor, *path_names): + """Return an iterable over the named resources within the package. + + The iterable returns :class:`str` resources (e.g. files). + The iterable does not recurse into subdirectories. + """ + warnings.warn( + "importlib.resources.contents is deprecated. " + "Use files(anchor).iterdir() instead.", + DeprecationWarning, + stacklevel=1, + ) + return (resource.name for resource in _get_resource(anchor, path_names).iterdir()) + + +def _get_encoding_arg(path_names, encoding): + # For compatibility with versions where *encoding* was a positional + # argument, it needs to be given explicitly when there are multiple + # *path_names*. + # This limitation can be removed in Python 3.15. + if encoding is _MISSING: + if len(path_names) > 1: + raise TypeError( + "'encoding' argument required with multiple path names", + ) + else: + return 'utf-8' + return encoding + + +def _get_resource(anchor, path_names): + if anchor is None: + raise TypeError("anchor must be module or string, got None") + return files(anchor).joinpath(*path_names) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/_itertools.py b/venv/lib/python3.8/site-packages/importlib_resources/_itertools.py new file mode 100644 index 000000000..7b775ef5a --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/_itertools.py @@ -0,0 +1,38 @@ +# from more_itertools 9.0 +def only(iterable, default=None, too_long=None): + """If *iterable* has only one item, return it. + If it has zero items, return *default*. + If it has more than one item, raise the exception given by *too_long*, + which is ``ValueError`` by default. + >>> only([], default='missing') + 'missing' + >>> only([1]) + 1 + >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 1, 2, + and perhaps more.' + >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + TypeError + Note that :func:`only` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check + iterable contents less destructively. + """ + it = iter(iterable) + first_value = next(it, default) + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value diff --git a/venv/lib/python3.8/site-packages/importlib_resources/abc.py b/venv/lib/python3.8/site-packages/importlib_resources/abc.py new file mode 100644 index 000000000..7a58dd2f9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/abc.py @@ -0,0 +1,171 @@ +import abc +import io +import itertools +import pathlib +from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text, Optional +from typing import runtime_checkable, Protocol + +from .compat.py38 import StrPath + + +__all__ = ["ResourceReader", "Traversable", "TraversableResources"] + + +class ResourceReader(metaclass=abc.ABCMeta): + """Abstract base class for loaders to provide resource reading support.""" + + @abc.abstractmethod + def open_resource(self, resource: Text) -> BinaryIO: + """Return an opened, file-like object for binary reading. + + The 'resource' argument is expected to represent only a file name. + If the resource cannot be found, FileNotFoundError is raised. + """ + # This deliberately raises FileNotFoundError instead of + # NotImplementedError so that if this method is accidentally called, + # it'll still do the right thing. + raise FileNotFoundError + + @abc.abstractmethod + def resource_path(self, resource: Text) -> Text: + """Return the file system path to the specified resource. + + The 'resource' argument is expected to represent only a file name. + If the resource does not exist on the file system, raise + FileNotFoundError. + """ + # This deliberately raises FileNotFoundError instead of + # NotImplementedError so that if this method is accidentally called, + # it'll still do the right thing. + raise FileNotFoundError + + @abc.abstractmethod + def is_resource(self, path: Text) -> bool: + """Return True if the named 'path' is a resource. + + Files are resources, directories are not. + """ + raise FileNotFoundError + + @abc.abstractmethod + def contents(self) -> Iterable[str]: + """Return an iterable of entries in `package`.""" + raise FileNotFoundError + + +class TraversalError(Exception): + pass + + +@runtime_checkable +class Traversable(Protocol): + """ + An object with a subset of pathlib.Path methods suitable for + traversing directories and opening files. + + Any exceptions that occur when accessing the backing resource + may propagate unaltered. + """ + + @abc.abstractmethod + def iterdir(self) -> Iterator["Traversable"]: + """ + Yield Traversable objects in self + """ + + def read_bytes(self) -> bytes: + """ + Read contents of self as bytes + """ + with self.open('rb') as strm: + return strm.read() + + def read_text(self, encoding: Optional[str] = None) -> str: + """ + Read contents of self as text + """ + with self.open(encoding=encoding) as strm: + return strm.read() + + @abc.abstractmethod + def is_dir(self) -> bool: + """ + Return True if self is a directory + """ + + @abc.abstractmethod + def is_file(self) -> bool: + """ + Return True if self is a file + """ + + def joinpath(self, *descendants: StrPath) -> "Traversable": + """ + Return Traversable resolved with any descendants applied. + + Each descendant should be a path segment relative to self + and each may contain multiple levels separated by + ``posixpath.sep`` (``/``). + """ + if not descendants: + return self + names = itertools.chain.from_iterable( + path.parts for path in map(pathlib.PurePosixPath, descendants) + ) + target = next(names) + matches = ( + traversable for traversable in self.iterdir() if traversable.name == target + ) + try: + match = next(matches) + except StopIteration: + raise TraversalError( + "Target not found during traversal.", target, list(names) + ) + return match.joinpath(*names) + + def __truediv__(self, child: StrPath) -> "Traversable": + """ + Return Traversable child in self + """ + return self.joinpath(child) + + @abc.abstractmethod + def open(self, mode='r', *args, **kwargs): + """ + mode may be 'r' or 'rb' to open as text or binary. Return a handle + suitable for reading (same as pathlib.Path.open). + + When opening as text, accepts encoding parameters such as those + accepted by io.TextIOWrapper. + """ + + @property + @abc.abstractmethod + def name(self) -> str: + """ + The base name of this object without any parent references. + """ + + +class TraversableResources(ResourceReader): + """ + The required interface for providing traversable + resources. + """ + + @abc.abstractmethod + def files(self) -> "Traversable": + """Return a Traversable object for the loaded package.""" + + def open_resource(self, resource: StrPath) -> io.BufferedReader: + return self.files().joinpath(resource).open('rb') + + def resource_path(self, resource: Any) -> NoReturn: + raise FileNotFoundError(resource) + + def is_resource(self, path: StrPath) -> bool: + return self.files().joinpath(path).is_file() + + def contents(self) -> Iterator[str]: + return (item.name for item in self.files().iterdir()) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/compat/__init__.py b/venv/lib/python3.8/site-packages/importlib_resources/compat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/importlib_resources/compat/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/compat/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b1c65e0cbad4ed637f83de9864e9a2db255a83f GIT binary patch literal 219 zcmYk0Jqp4=5QR5jAws}wSZt<9VR>8ohJziSwf+|Eq)K4%gZK)*hfk5K zr@n$Gr`3Z4`G^0@{K>qmRtti4{>jBV_OH?WuK>**9(##Wl9Ec2kuo^Yi~@NLN>)ea z0(mG|6J`-5?Q(LnUsuv+l0*Y$$)k@mp`Qv@CBeWD4CBuU_2I-~&unjfoacpwCR zm1qsL2dT#E;sBP5Ml0iRA~#^w(d>}zRHF-r ck= (3, 9): + StrPath = Union[str, os.PathLike[str]] +else: + # PathLike is only subscriptable at runtime in 3.9+ + StrPath = Union[str, "os.PathLike[str]"] diff --git a/venv/lib/python3.8/site-packages/importlib_resources/compat/py39.py b/venv/lib/python3.8/site-packages/importlib_resources/compat/py39.py new file mode 100644 index 000000000..ed5abd5e5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/compat/py39.py @@ -0,0 +1,10 @@ +import sys + + +__all__ = ['ZipPath'] + + +if sys.version_info >= (3, 10): + from zipfile import Path as ZipPath +else: + from zipp import Path as ZipPath diff --git a/venv/lib/python3.8/site-packages/importlib_resources/future/__init__.py b/venv/lib/python3.8/site-packages/importlib_resources/future/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/importlib_resources/future/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/future/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e53425b304f7659ad46986d0f1455bf614e76d2a GIT binary patch literal 219 zcmYk0Jqp4=5QR5jAws}wSR_-Vu@JGa6dSQ#^7EogesLDaT@dua z`-XY*iaEydjCsxPB*J_K{44Uy#@kFhDt2ItXn$im#6P^Rbe0_m7c>GbZ84ygszYyS zmluZAXwR}wRF>2lCjgRT7la*_+4xhce*$1M!J(mCg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/future/__pycache__/adapters.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/future/__pycache__/adapters.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65cd7641e2a83faaaed512931b3e34255b8b1242 GIT binary patch literal 3479 zcmb7G-HzMF6`mm}iPCD<>#gCYZi=$8>qLz$6-fi6Xkf&3?6!crb-nr34MB;cT}l+m z%uu@)Ar}SKKyuUO5xPRJe9^b)Q_O9y@(Kl7_d7#s|FSV!67$31@NmvK-}%n@XnnoK zaBcpx7cJHp`|S1u03PnnsCQ!hjxE0Y=`T3(+>F#>uh|3f3fWj*RGmn znG{N4erYF~X0g}~rb1=GP<&%yQ^(Q`DS}W)rJH^b23d~jkduJwqHUywji#=l%h?hC zk}dfcY{WwzTF=}={**81&vtRp@^F13gUmY?*Zoii`w$xpV)0n0bS8&FJxuAZ-CdFU zIG|a>gw~9NAPK!F!C{6X%e^3(dn(6TAVV*X1~QOyuR3?v>>uJ_#}^&fMf)+D`V+ch z<4d++3;tLB)cS-aR~;xZX~`R$(ft|Rvco=Q3v0~B)>C_7AK9U`yDeWAA6l8SNmsHYWSw0Zx$BJQZJU~kYt+Qjl6tMREPH><&9)IHB09e zyoF8qOeWrTng}mXy&#U$(!M0fJi!E={9$jF0< z@BoRS2t_g!rSVAIcG1G;sm!stD{C;CZWd8iuIXz&j#-GrKi=buC-6=Z#GRJj+D`Mk zA4HIvr*eK@%2WbP{xA-d@_pU%{b?G`VnK6gDRXrY<$Kx{Nm$NWk%|)1e<);~e!X=s zoo3Kt5XDhGe-On&X-8#ZsOzx-PRG$sAa|9nPY%klgoAeRD?b6qpL4xluK55N&ib7F z>(9NtbSiqH5R*v6y${5`h|`QL)_WKXR4<$7duehzd;9iC3NeVp?OX=KiIBa0k?i+i znrh42-FJH`%EfI&(j?dws&`_T6`WOXG=u*{ZxvSEY_1yz2#rk0O9V|19jjY>gKt=_ zQ1*3g&1y*W(jPc#!A;{Rdp=+ zeaMy+uksb@u2S~{bo$acflVwbvO24~eB%}QBh0?V02KZA(9D^01XB*^A6n_wf{pDq zqxe^^F4&F*ym z6(2yCD|pjikCcBRr_{=*X0+P$)g;PJZFrMTYt*W3y|8RaWN?Nh@N7tpM)f{A!HAOk zR>=FdAvY_upSgX9=osW$t7y1h(Quv!51Yd4o?R~w`JtrPZTf)Nyns8QOX5?^a5#ueHk_67R1rniB%3sW;H&@PBR{!uN;p{2}h_=7JqLOP1ZF^&@-1m!QAre7jRKrZ=$C zH6$b<_4h8IeGLy*6o2e^#Jq?{+9~5%QmQTePA-?%(cM8)CIwqpcpZ_``giev=9@EE zr_4v%-Xpn$E@;J|)m4nV8m%R8E`NfT9b2#YV9Dyh`m$jjlU`R#ZGQyzs-B*6`BQv$ z4!iOO*6yPzV{=}-c@Y+>gn8ab-#R@R!<_yXn2?_i`B$%TmLpLiR-JLp^z|hYp~c=| ze`Jr@-=PnUpP~v`Y1WW*7`<^f&t){2<-*XqNeumFKgu4PTrD@TNnWPzCUq2a=d~le z{1#17T4ub(i<>Xg@5G$nt=i8asDhyN)vHhrc^Ycf#`PutU(}MfAV9Z14&vEqy)GRW zN2j#Y*VJrK%4x{$lV1>wZE8oH#gjml@_%+@QvAeG^;0FHvSQ-S+*@XF*KHLsil6{V*Nk-F3V(;Uo!l zl3XF__j2k+yH(c9wTu0KIGj1W4AT%4dP}*8K zCAXJU@bThrhF$*vc9lf#C$%u8XP+t=;{KVsnD@(AHzG`V7!1JEUGIeJgWM~N3vVFy zg8e8(@uyG{9t3j?`#}^_vF)Xam&U7-`jIJaRGDv)ZYekRFYn+ep0y)FdbUs+g z!w%pC_%KDzDXW>3+dS6gGi)r$t1f> abc.Traversable | None: + r""" + Given an item from a namespace path, resolve it to a Traversable. + + path_str might be a directory on the filesystem or a path to a + zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or + ``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``. + + path_str might also be a sentinel used by editable packages to + trigger other behaviors (see python/importlib_resources#311). + In that case, return None. + """ + dirs = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir()) + return next(dirs, None) + + @classmethod + def _candidate_paths(cls, path_str: str) -> Iterator[abc.Traversable]: + yield pathlib.Path(path_str) + yield from cls._resolve_zip_path(path_str) + + @staticmethod + def _resolve_zip_path(path_str: str): + for match in reversed(list(re.finditer(r'[\\/]', path_str))): + with contextlib.suppress( + FileNotFoundError, + IsADirectoryError, + NotADirectoryError, + PermissionError, + ): + inner = path_str[match.end() :].replace('\\', '/') + '/' + yield ZipPath(path_str[: match.start()], inner.lstrip('/')) + + def resource_path(self, resource): + """ + Return the file system path to prevent + `resources.path()` from creating a temporary + copy. + """ + return str(self.path.joinpath(resource)) + + def files(self): + return self.path + + +def _ensure_traversable(path): + """ + Convert deprecated string arguments to traversables (pathlib.Path). + + Remove with Python 3.15. + """ + if not isinstance(path, str): + return path + + warnings.warn( + "String arguments are deprecated. Pass a Traversable instead.", + DeprecationWarning, + stacklevel=3, + ) + + return pathlib.Path(path) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/simple.py b/venv/lib/python3.8/site-packages/importlib_resources/simple.py new file mode 100644 index 000000000..2e75299b1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/simple.py @@ -0,0 +1,106 @@ +""" +Interface adapters for low-level readers. +""" + +import abc +import io +import itertools +from typing import BinaryIO, List + +from .abc import Traversable, TraversableResources + + +class SimpleReader(abc.ABC): + """ + The minimum, low-level interface required from a resource + provider. + """ + + @property + @abc.abstractmethod + def package(self) -> str: + """ + The name of the package for which this reader loads resources. + """ + + @abc.abstractmethod + def children(self) -> List['SimpleReader']: + """ + Obtain an iterable of SimpleReader for available + child containers (e.g. directories). + """ + + @abc.abstractmethod + def resources(self) -> List[str]: + """ + Obtain available named resources for this virtual package. + """ + + @abc.abstractmethod + def open_binary(self, resource: str) -> BinaryIO: + """ + Obtain a File-like for a named resource. + """ + + @property + def name(self): + return self.package.split('.')[-1] + + +class ResourceContainer(Traversable): + """ + Traversable container for a package's resources via its reader. + """ + + def __init__(self, reader: SimpleReader): + self.reader = reader + + def is_dir(self): + return True + + def is_file(self): + return False + + def iterdir(self): + files = (ResourceHandle(self, name) for name in self.reader.resources) + dirs = map(ResourceContainer, self.reader.children()) + return itertools.chain(files, dirs) + + def open(self, *args, **kwargs): + raise IsADirectoryError() + + +class ResourceHandle(Traversable): + """ + Handle to a named resource in a ResourceReader. + """ + + def __init__(self, parent: ResourceContainer, name: str): + self.parent = parent + self.name = name # type: ignore[misc] + + def is_file(self): + return True + + def is_dir(self): + return False + + def open(self, mode='r', *args, **kwargs): + stream = self.parent.reader.open_binary(self.name) + if 'b' not in mode: + stream = io.TextIOWrapper(stream, *args, **kwargs) + return stream + + def joinpath(self, name): + raise RuntimeError("Cannot traverse into a resource") + + +class TraversableReader(TraversableResources, SimpleReader): + """ + A TraversableResources based on SimpleReader. Resource providers + may derive from this class to provide the TraversableResources + interface by supplying the SimpleReader interface. + """ + + def files(self): + return ResourceContainer(self) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__init__.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1c2f41c8fc4e011382f459dbf88ceb99a66f334 GIT binary patch literal 218 zcmYk0y$ZrG6opf85TW2}IHY&z;vnMSC@$h0`g63QO%sx|P#2%a_wXrPeFQfrQxNpP z`7Za|Gu&ew&sfm>PI8>j5&w$9vI%yRfRY{9BHG`W9pfL~SE{rE;euMCp)Gne+9K4C^ZE&u=k literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/_path.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/_path.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd73847343df475c2b3055749c640f1709f9a77c GIT binary patch literal 3245 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 DO@#JV literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_contents.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_contents.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc6a95a3ed6108f49ae01de1fa9b4acbc8b301bc GIT binary patch literal 1528 zcmb7E%}yIJ5VkkoWb?;QQCkXa4?Q(flLaAF91u!HP%kt+P%bM~t4(a0g-v#`9aIp# z6rP|@07u@VPvL7%yaK&+W|QToM3w5u6VH!5-_AGoPPJMhFlK(b;bxAIU#Q$2c~F`# z)gcH@IE{!m`lp`7EuM>VedaMr-V@HaeMY!_YI!zjd0qgmAZ)?*@`CIYPHoQtyTglM zFQ)e5*j@sAiI>4%z87ESbeq&FSCE^uuPDeGHEgKEsEek%!v>`ZQ@sKqh(|dAfD|Dj zu%7MZMvy$l9XPaTsvGe@i-A_3gwpD`?EB#$)V|+Hj;_m{a1h9&#%>r1Q&GbX52fhp zSb}S)ck9bS;lHC8I}aPCyNlA zit97C7x#rLj>Uc`BDWRJZfeIeaLF*tOi zurm%>X{@>`)S{k%JNP1$8}^e}YKZWqQ1MW9!KSgKsr0+!DnnUKaZL%`jr+;^EPe}C zC{HYM+_*_ zuK51JFo;GTIR_`orzmdt8Lcq8#G({nswohTWzq5PH*-?ogU-jkYvP6qh}+JGKC;Yg z2DcTe{j|^9mpM5Pf$|xO1r(D|EP~@POog57P>K3~WuD10dtow4ET6*}6O+9F2g1pc zq5dEl)}@I-c+Bl*!nMw>IJ`qV51(ByPqO4~;6g#CiWGu3jSQqZHp|ko#8*S&8z-^k z(g6W1{#788|M0Q3v$Z)QZc?>g!af9@Qs7X=GR`D=Y8B5J!qb9n<;_jFE^->843T)r zQ;xkN^0h7WF!3COz7aqRBL|)X^-LWS6Q<`+s;P0-M%}lL?J>sXSe4w=)I6JKe*kG` BUETly literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_custom.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_custom.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9be75b526bd07b245b3827e5534492d1f6ed49e6 GIT binary patch literal 2357 zcmah~OK%%D5GHq5+SSvF?Idmbh>A8vb+84*mlh}r8*bW5Y7}vD*+YocE^V#7uacBu z3(6(-p+BMr>FEE_{)xTz0us=OxWzwpsg@1b(Ep}u1x8>-mAL9wDFNP!sR?ZGqejS|GPLIckGuC7}DH*S-R4$ljF! zv`2H(8~8mVUw0dN31M9+aE9lt;ajSt1mN|KIKa8`8zfAGA@lL%9W2Y;% zvKwh_R}DSYO_S)XtJN`2ll9^TYvFKJ#aWFOs~59D#~!jy>NgiZ(pX!gk^ z-4m#RkXVvO2ciMOB@u1-`|o{!n8v)%$NV_tQU7Ot!lN`p3;Hj;f$V2zYM3S)+1AE^ z;CvAB4JADPn2Y`iPfq$#I4HMlZGPXEq2e1EEPIDs_Q97_DA>W~j>x{^QWcc>BdOAO zGds&2#=<02j6DF0r1pxAA1=pjbH3w_&j_FPxeCmL9D%>032f=HXaS$wrA^x9f~=r^ z?U1W$7NkC7qJpM% zkgOs>2$l#MC2By>1Bo6v)Uw*9*7+db7$!*DFy(hZZsy}rWho=8Wk%X%Mz(+R4nu!l z26Fsd=ap7^5#*rYY9tZ~S;?~s zBKHUgrj!~&Zs*qTz!Z>vhO3KfG^Zhb3=3$=HKZL=pzaQ&7`|=X_&JaTq=5kq#ncUk zKwGUqW2xp(cXa{X*W_MfmHD@$g!!^N|F|MkI)j2?#)M2wC>-O+no;P*^8)Hs^17cU zil1T$=d~B7q1sna+qrWPoDIj<*p0ZCjIzAqN2%oE30$xggCf+Dt2fy*yc*vR zLhE$=>?781>N7Wdz}mr@&_28c27N%wr>3%|l^L1Q8GT1pRoN4I4!?JXa;CM30a{bl zr;UjToTZu*OSRzjFyt?4k#^gHUXeI?a=Y_kAZaeLwjJ#t$O(yzA845+*cc4!ooK&b!s@&u>@**9oHGbn}8RH zr+5}&aM$j6Qv^Q8Ingl_m|sPc)$womX$*x5kIdE&8ma|Au7g#w>p@iU!uxE?oga&S Z{u7px#FqgKq>kZ0h&nKvX1m#G{twduC3pY; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_files.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_files.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac0e103124481b2c58d53d10a6e9f950e5a0fa7b GIT binary patch literal 7656 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_functional.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_functional.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0de3bb165d2dfb97d9a942e5437d3e6d4e852bf6 GIT binary patch literal 6724 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_open.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_open.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..276b95e1f4232ae73d33aee40e8b7a3ebf1546a7 GIT binary patch literal 3751 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 Cm&G># literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_read.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_read.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9a2493e29d140cbbceb99e204d7bde845f42011 GIT binary patch literal 4159 zcmb_fOOG2x5bmCr$6oJy6Os_Xfk0%GwOJxU2nemn10=vm$?jz?)~vgCGaJ9?87Hi@ zNJyJ&{zG!~|KJyJ;LumZVgEu7gs*!1UT*+}wz@oB(_LLv{Z)0%_WXQ~p)LJdj{clu z><=1@HxGkN^!RrW!33|fApPfoB`jgr?S>OLoOn*%ZFqsl+1E_C!h6DmC!PI_Wc&V+ z6J){72p?R3jGF^DD{|oGj;vq~qdAesC@<$QD{t1i!94i$VgdXG!(Zt07r|c?OW-d} z%wGb3S*(D+GBJNyu87r#mikSyXRFT{wC49&aqSqkvC1(AEpqL2lBkaV`KZxusifLy ziB4VCXwG=EFxW(o*FYo-xM1)I7uFLN*usWfM=#%MH5#qvS5Y%m2U{{u;+l=wgGSHP zZ=uKMKoa(hAM)q?nRRG^+D1hNQM>|aSta9EN7ZDk-QB2;KYza!HQQnGKklomZ4A* z8wzG9O6>!kkw3{=Cy`k)o7~8F_xuS@mHM+PJxBZ1YBOxeYE|c|{qw+czFPgU6V}r= zY6)_Tg=1uqcrJ*KZnpBd8P_nLX#eIk7gHU#zoaIHxo;=Hao1pQOQ=s5H?aB+c zlkBeF=yC1KX00Wn=3dcJ6mBX*gfylT$EhaRP*IppTSg|+nGT%z#*~6X7pmpS)wD<% z=bXXQ{6GYpL*vt(a}{q-LR~n3ypx7};HE(ycp?isb2@tuCZz77_!>0wHyLZ7U&4cN z7uz?m7Zd`{bM`YoWY63~o_I+{SV#PbKet8>oZB&BRiaQhb|9kYkNl?{5hkJWq9UB> zxiF5UO70M1v}*!IyDDt%$)2T<`IzS~AS>SePUTJDx zZQ5K~W6b>!57XxEJCsbH@`1_18Jg-=x;cXtW2rie)#?q9?%Fr9UTiRv_afxDq1Er4 zV)d0%t)5&zVY3Zkl!*)iHt{e*fZ%T$sPEf}bIA8yg9Mwi{xc%$sr9pkQ1A{d;Tc?H z5LW5dJx^-hj^c+~bYoWFj46F)?OlSL>m-o!DbhEMPwYzXHqm3cmPWe{DwRR{h8CXA zlNLeLHcFfTFx^ZBz_e$A>gfmxE1`0QGPW%!dsE&fu)2qq$=kMYj*z8yuzwo1AA zqm5GB*%6VFwWOsE)SD2gNW)|DW6xKVFUN3X(io-kG>uVkssc!_kD4r`rKQ?T;9ML5 zXEExWrhe%`ONbReNpbf=N|352RoiOj-i z882?qwsfF~4_6WA1WJdoK~>ENrKqJXAk)?>q-h0JV2pZG8hq+Ss{PD3MHH&!Dc!X9 z{SUXdzq>P2vm4myYxF_%(D;O2!{{mC?WA79HiZuoH2(3$13feP%!Hl;5UI~dHrY66 zHXepxdA)eLIOT?ufpts?=h&3UALB_~BQiQHQwq{4R?x=-;p3Ucyb>o9M!s%Dd_j7W zb7mplK<++iY`$Tw97+?9%95M^UF#9Ly(t7K!mfHX#3_G#ONvkq@wsCGtK0AD(!fK!BSqm?mGJEYhc1`HYq2-^Q5&7)ruT+MeWq85c;-ahM|br&w5 LbI#49FZ=v&Z{nZu literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_reader.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_reader.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a06a2a46e0db6d1ab1efdc7f88a8643dc260c48 GIT binary patch literal 5808 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 YPoM;yfH=UF16<7+MRT#fc-rCr1*#K8i~s-t literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_resource.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/test_resource.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1511a64d0d65c76cca0064b10e203afb1add90fb GIT binary patch literal 10318 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/util.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/util.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36b43874d2dc00fc09dea803f7aa39d3770dcedc GIT binary patch literal 7566 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/zip.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/__pycache__/zip.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf940bd8d36d20f2e009efd6b3aced3331e942d8 GIT binary patch literal 870 zcmY*Y&2AGh5VpNevJFis0(ykK1}Ryj3aLT}0WF0Cs`wEPxzuOI>$8Pg1sUAh3W0?GblhM27;-q0t4CvA~8uYk{Re> z_cs8LG>T}Vy8Ww|@b zdRW!6>B#aT$-(+*Sr{mc{z23dUiWMo?vy!%Fom&%EZl&7$jU0ml<-Bet;6cT?3Bfz zS{;nA+_r>)QAs)m74Ac^A7*mxK0sT5<5xDJu` zusX24JQ+iblh{3h>k8ep24AAfbcwD|wTv-K{!@%&sEwYw^p^-VnUOg`jh>)JsFI TreeMaker: + return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore[return-value] + + +def build( + spec: FilesSpec, + prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore[assignment] +): + """ + Build a set of files/directories, as described by the spec. + + Each key represents a pathname, and the value represents + the content. Content may be a nested directory. + + >>> spec = { + ... 'README.txt': "A README file", + ... "foo": { + ... "__init__.py": "", + ... "bar": { + ... "__init__.py": "", + ... }, + ... "baz.py": "# Some code", + ... "bar.py": Symlink("baz.py"), + ... }, + ... "bing": Symlink("foo"), + ... } + >>> target = getfixture('tmp_path') + >>> build(spec, target) + >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8') + '# Some code' + >>> target.joinpath('bing/bar.py').read_text(encoding='utf-8') + '# Some code' + """ + for name, contents in spec.items(): + create(contents, _ensure_tree_maker(prefix) / name) + + +@functools.singledispatch +def create(content: Union[str, bytes, FilesSpec], path): + path.mkdir(exist_ok=True) + build(content, prefix=path) # type: ignore[arg-type] + + +@create.register +def _(content: bytes, path): + path.write_bytes(content) + + +@create.register +def _(content: str, path): + path.write_text(content, encoding='utf-8') + + +@create.register +def _(content: Symlink, path): + path.symlink_to(content) + + +# end from jaraco.path +#### diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__init__.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3529522b3cbcf6e7e1ee7a32f92c3acd26424b2 GIT binary patch literal 225 zcmYk0JqiLb5QQUHh_K)_EY>Nuu@JGa6dSP)`}1_eCYz9q3wi+W;ypZttw*r4G7Ew} zc;7H@UNQS58H=dNom4-cJ^nR_%PKkyBPy{I)4}#8d>{YtzEW)s2p2R8n%3x`tuttu zHhB@qow>Gc>az6UfkGi=pO+0hb!a=%s`ys2*_?u6q%NNGBLXSotmoK}9>Jb`88b%U dK&3Sh4WjQ4;kxbWo5MWs|_lHw&;6z z(K8;<=|T6Dl7}01fUoQ)>=Ma70fW!*vPXj-a9|9+936t)izKDE5G|&amW@J}>N>^R z+ifapnHCB~-IxKqF8yMlpJ$;B$t9nSnw3M}nEc6IIjS<2$BstJ-(dTN{M+-5u8`vn zuS!(>3|my%RAk3Lh_&Np>o>Zdo8|n{BCboEdn<}7w7kW-<*Hndl;z?zccsU9i6aY?$W5@IEU0PTSTT2@+d@x^lE*~T=9E!!>19_iEU zBkY~0$dwbXz-5_iTEtlXJl|)JJs$gOnkEEo{*$WvfRJB3`QKtB@3Ff}1W6c?fefpl z4p~@7ED8ujGCBn26@)UDBYAd+Sd7{jwb!Uk4gnh>pCbQ+JdsZhAv=>#ugN04{g8YV z#rC_fa#`rw2rt&EQohYxscM&bow=Pc+WPFO^`&mIReqj{MrI&vrNHLNZ%m6U!tVKv z)iw9vJU@nXiMK6RbsyfP!^lgh3|N-dTJ9=v+=1U2HapRx0@0w5UMTCnG}o=W629!F z+8raiiHF*7scbjHv>VlIPE%~AHjTExPda;6lu&H{@yo@y8*?so!8v>JO;}Oryw5l< z%;W8Bhf{qTagMq8_+_c@~ c?NG6I_+^NDi4e#0FbP6}?IDil!K4517lVAdjQ{`u literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py312.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py312.py new file mode 100644 index 000000000..ea9a58ba2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py312.py @@ -0,0 +1,18 @@ +import contextlib + +from .py39 import import_helper + + +@contextlib.contextmanager +def isolated_modules(): + """ + Save modules on entry and cleanup on exit. + """ + (saved,) = import_helper.modules_setup() + try: + yield + finally: + import_helper.modules_cleanup(saved) + + +vars(import_helper).setdefault('isolated_modules', isolated_modules) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py39.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py39.py new file mode 100644 index 000000000..e01d276bd --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/compat/py39.py @@ -0,0 +1,13 @@ +""" +Backward-compatability shims to support Python 3.9 and earlier. +""" + +from jaraco.test.cpython import from_test_support, try_import + +import_helper = try_import('import_helper') or from_test_support( + 'modules_setup', 'modules_cleanup', 'DirsOnSysPath' +) +os_helper = try_import('os_helper') or from_test_support('temp_dir') +warnings_helper = try_import('warnings_helper') or from_test_support( + 'ignore_warnings', 'check_warnings' +) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_compatibilty_files.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_compatibilty_files.py new file mode 100644 index 000000000..13ad0dfb2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_compatibilty_files.py @@ -0,0 +1,104 @@ +import io +import unittest + +import importlib_resources as resources + +from importlib_resources._adapters import ( + CompatibilityFiles, + wrap_spec, +) + +from . import util + + +class CompatibilityFilesTests(unittest.TestCase): + @property + def package(self): + bytes_data = io.BytesIO(b'Hello, world!') + return util.create_package( + file=bytes_data, + path='some_path', + contents=('a', 'b', 'c'), + ) + + @property + def files(self): + return resources.files(self.package) + + def test_spec_path_iter(self): + self.assertEqual( + sorted(path.name for path in self.files.iterdir()), + ['a', 'b', 'c'], + ) + + def test_child_path_iter(self): + self.assertEqual(list((self.files / 'a').iterdir()), []) + + def test_orphan_path_iter(self): + self.assertEqual(list((self.files / 'a' / 'a').iterdir()), []) + self.assertEqual(list((self.files / 'a' / 'a' / 'a').iterdir()), []) + + def test_spec_path_is(self): + self.assertFalse(self.files.is_file()) + self.assertFalse(self.files.is_dir()) + + def test_child_path_is(self): + self.assertTrue((self.files / 'a').is_file()) + self.assertFalse((self.files / 'a').is_dir()) + + def test_orphan_path_is(self): + self.assertFalse((self.files / 'a' / 'a').is_file()) + self.assertFalse((self.files / 'a' / 'a').is_dir()) + self.assertFalse((self.files / 'a' / 'a' / 'a').is_file()) + self.assertFalse((self.files / 'a' / 'a' / 'a').is_dir()) + + def test_spec_path_name(self): + self.assertEqual(self.files.name, 'testingpackage') + + def test_child_path_name(self): + self.assertEqual((self.files / 'a').name, 'a') + + def test_orphan_path_name(self): + self.assertEqual((self.files / 'a' / 'b').name, 'b') + self.assertEqual((self.files / 'a' / 'b' / 'c').name, 'c') + + def test_spec_path_open(self): + self.assertEqual(self.files.read_bytes(), b'Hello, world!') + self.assertEqual(self.files.read_text(encoding='utf-8'), 'Hello, world!') + + def test_child_path_open(self): + self.assertEqual((self.files / 'a').read_bytes(), b'Hello, world!') + self.assertEqual( + (self.files / 'a').read_text(encoding='utf-8'), 'Hello, world!' + ) + + def test_orphan_path_open(self): + with self.assertRaises(FileNotFoundError): + (self.files / 'a' / 'b').read_bytes() + with self.assertRaises(FileNotFoundError): + (self.files / 'a' / 'b' / 'c').read_bytes() + + def test_open_invalid_mode(self): + with self.assertRaises(ValueError): + self.files.open('0') + + def test_orphan_path_invalid(self): + with self.assertRaises(ValueError): + CompatibilityFiles.OrphanPath() + + def test_wrap_spec(self): + spec = wrap_spec(self.package) + self.assertIsInstance(spec.loader.get_resource_reader(None), CompatibilityFiles) + + +class CompatibilityFilesNoReaderTests(unittest.TestCase): + @property + def package(self): + return util.create_package_from_loader(None) + + @property + def files(self): + return resources.files(self.package) + + def test_spec_path_joinpath(self): + self.assertIsInstance(self.files / 'a', CompatibilityFiles.OrphanPath) diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_contents.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_contents.py new file mode 100644 index 000000000..741a74076 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_contents.py @@ -0,0 +1,38 @@ +import unittest +import importlib_resources as resources + +from . import util + + +class ContentsTests: + expected = { + '__init__.py', + 'binary.file', + 'subdirectory', + 'utf-16.file', + 'utf-8.file', + } + + def test_contents(self): + contents = {path.name for path in resources.files(self.data).iterdir()} + assert self.expected <= contents + + +class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase): + pass + + +class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase): + pass + + +class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + expected = { + # no __init__ because of namespace design + 'binary.file', + 'subdirectory', + 'utf-16.file', + 'utf-8.file', + } diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_custom.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_custom.py new file mode 100644 index 000000000..86c65676f --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_custom.py @@ -0,0 +1,47 @@ +import unittest +import contextlib +import pathlib + +import importlib_resources as resources +from .. import abc +from ..abc import TraversableResources, ResourceReader +from . import util +from .compat.py39 import os_helper + + +class SimpleLoader: + """ + A simple loader that only implements a resource reader. + """ + + def __init__(self, reader: ResourceReader): + self.reader = reader + + def get_resource_reader(self, package): + return self.reader + + +class MagicResources(TraversableResources): + """ + Magically returns the resources at path. + """ + + def __init__(self, path: pathlib.Path): + self.path = path + + def files(self): + return self.path + + +class CustomTraversableResourcesTests(unittest.TestCase): + def setUp(self): + self.fixtures = contextlib.ExitStack() + self.addCleanup(self.fixtures.close) + + def test_custom_loader(self): + temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir())) + loader = SimpleLoader(MagicResources(temp_dir)) + pkg = util.create_package_from_loader(loader) + files = resources.files(pkg) + assert isinstance(files, abc.Traversable) + assert list(files.iterdir()) == [] diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_files.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_files.py new file mode 100644 index 000000000..f1fe2337a --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_files.py @@ -0,0 +1,194 @@ +import os +import pathlib +import py_compile +import shutil +import textwrap +import unittest +import warnings +import importlib +import contextlib + +import importlib_resources as resources +from ..abc import Traversable +from . import util +from .compat.py39 import os_helper, import_helper + + +@contextlib.contextmanager +def suppress_known_deprecation(): + with warnings.catch_warnings(record=True) as ctx: + warnings.simplefilter('default', category=DeprecationWarning) + yield ctx + + +class FilesTests: + def test_read_bytes(self): + files = resources.files(self.data) + actual = files.joinpath('utf-8.file').read_bytes() + assert actual == b'Hello, UTF-8 world!\n' + + def test_read_text(self): + files = resources.files(self.data) + actual = files.joinpath('utf-8.file').read_text(encoding='utf-8') + assert actual == 'Hello, UTF-8 world!\n' + + def test_traversable(self): + assert isinstance(resources.files(self.data), Traversable) + + def test_joinpath_with_multiple_args(self): + files = resources.files(self.data) + binfile = files.joinpath('subdirectory', 'binary.file') + self.assertTrue(binfile.is_file()) + + def test_old_parameter(self): + """ + Files used to take a 'package' parameter. Make sure anyone + passing by name is still supported. + """ + with suppress_known_deprecation(): + resources.files(package=self.data) + + +class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase): + pass + + +class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase): + pass + + +class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + def test_non_paths_in_dunder_path(self): + """ + Non-path items in a namespace package's ``__path__`` are ignored. + + As reported in python/importlib_resources#311, some tools + like Setuptools, when creating editable packages, will inject + non-paths into a namespace package's ``__path__``, a + sentinel like + ``__editable__.sample_namespace-1.0.finder.__path_hook__`` + to cause the ``PathEntryFinder`` to be called when searching + for packages. In that case, resources should still be loadable. + """ + import namespacedata01 + + namespacedata01.__path__.append( + '__editable__.sample_namespace-1.0.finder.__path_hook__' + ) + + resources.files(namespacedata01) + + +class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase): + ZIP_MODULE = 'namespacedata01' + + +class DirectSpec: + """ + Override behavior of ModuleSetup to write a full spec directly. + """ + + MODULE = 'unused' + + def load_fixture(self, name): + self.tree_on_path(self.spec) + + +class ModulesFiles: + spec = { + 'mod.py': '', + 'res.txt': 'resources are the best', + } + + def test_module_resources(self): + """ + A module can have resources found adjacent to the module. + """ + import mod # type: ignore[import-not-found] + + actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8') + assert actual == self.spec['res.txt'] + + +class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase): + pass + + +class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase): + pass + + +class ImplicitContextFiles: + set_val = textwrap.dedent( + f""" + import {resources.__name__} as res + val = res.files().joinpath('res.txt').read_text(encoding='utf-8') + """ + ) + spec = { + 'somepkg': { + '__init__.py': set_val, + 'submod.py': set_val, + 'res.txt': 'resources are the best', + }, + 'frozenpkg': { + '__init__.py': set_val.replace(resources.__name__, 'c_resources'), + 'res.txt': 'resources are the best', + }, + } + + def test_implicit_files_package(self): + """ + Without any parameter, files() will infer the location as the caller. + """ + assert importlib.import_module('somepkg').val == 'resources are the best' + + def test_implicit_files_submodule(self): + """ + Without any parameter, files() will infer the location as the caller. + """ + assert importlib.import_module('somepkg.submod').val == 'resources are the best' + + def _compile_importlib(self): + """ + Make a compiled-only copy of the importlib resources package. + """ + bin_site = self.fixtures.enter_context(os_helper.temp_dir()) + c_resources = pathlib.Path(bin_site, 'c_resources') + sources = pathlib.Path(resources.__file__).parent + shutil.copytree(sources, c_resources, ignore=lambda *_: ['__pycache__']) + + for dirpath, _, filenames in os.walk(c_resources): + for filename in filenames: + source_path = pathlib.Path(dirpath) / filename + cfile = source_path.with_suffix('.pyc') + py_compile.compile(source_path, cfile) + pathlib.Path.unlink(source_path) + self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site)) + + def test_implicit_files_with_compiled_importlib(self): + """ + Caller detection works for compiled-only resources module. + + python/cpython#123085 + """ + self._compile_importlib() + assert importlib.import_module('frozenpkg').val == 'resources are the best' + + +class ImplicitContextFilesDiskTests( + DirectSpec, util.DiskSetup, ImplicitContextFiles, unittest.TestCase +): + pass + + +class ImplicitContextFilesZipTests( + DirectSpec, util.ZipSetup, ImplicitContextFiles, unittest.TestCase +): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_functional.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_functional.py new file mode 100644 index 000000000..1851edfba --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_functional.py @@ -0,0 +1,255 @@ +import unittest +import os +import importlib + +from .compat.py39 import warnings_helper + +import importlib_resources as resources + +from . import util + +# Since the functional API forwards to Traversable, we only test +# filesystem resources here -- not zip files, namespace packages etc. +# We do test for two kinds of Anchor, though. + + +class StringAnchorMixin: + anchor01 = 'data01' + anchor02 = 'data02' + + +class ModuleAnchorMixin: + @property + def anchor01(self): + return importlib.import_module('data01') + + @property + def anchor02(self): + return importlib.import_module('data02') + + +class FunctionalAPIBase(util.DiskSetup): + def setUp(self): + super().setUp() + self.load_fixture('data02') + + def _gen_resourcetxt_path_parts(self): + """Yield various names of a text file in anchor02, each in a subTest""" + for path_parts in ( + ('subdirectory', 'subsubdir', 'resource.txt'), + ('subdirectory/subsubdir/resource.txt',), + ('subdirectory/subsubdir', 'resource.txt'), + ): + with self.subTest(path_parts=path_parts): + yield path_parts + + def assertEndsWith(self, string, suffix): + """Assert that `string` ends with `suffix`. + + Used to ignore an architecture-specific UTF-16 byte-order mark.""" + self.assertEqual(string[-len(suffix) :], suffix) + + def test_read_text(self): + self.assertEqual( + resources.read_text(self.anchor01, 'utf-8.file'), + 'Hello, UTF-8 world!\n', + ) + self.assertEqual( + resources.read_text( + self.anchor02, + 'subdirectory', + 'subsubdir', + 'resource.txt', + encoding='utf-8', + ), + 'a resource', + ) + for path_parts in self._gen_resourcetxt_path_parts(): + self.assertEqual( + resources.read_text( + self.anchor02, + *path_parts, + encoding='utf-8', + ), + 'a resource', + ) + # Use generic OSError, since e.g. attempting to read a directory can + # fail with PermissionError rather than IsADirectoryError + with self.assertRaises(OSError): + resources.read_text(self.anchor01) + with self.assertRaises(OSError): + resources.read_text(self.anchor01, 'no-such-file') + with self.assertRaises(UnicodeDecodeError): + resources.read_text(self.anchor01, 'utf-16.file') + self.assertEqual( + resources.read_text( + self.anchor01, + 'binary.file', + encoding='latin1', + ), + '\x00\x01\x02\x03', + ) + self.assertEndsWith( # ignore the BOM + resources.read_text( + self.anchor01, + 'utf-16.file', + errors='backslashreplace', + ), + 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode( + errors='backslashreplace', + ), + ) + + def test_read_binary(self): + self.assertEqual( + resources.read_binary(self.anchor01, 'utf-8.file'), + b'Hello, UTF-8 world!\n', + ) + for path_parts in self._gen_resourcetxt_path_parts(): + self.assertEqual( + resources.read_binary(self.anchor02, *path_parts), + b'a resource', + ) + + def test_open_text(self): + with resources.open_text(self.anchor01, 'utf-8.file') as f: + self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') + for path_parts in self._gen_resourcetxt_path_parts(): + with resources.open_text( + self.anchor02, + *path_parts, + encoding='utf-8', + ) as f: + self.assertEqual(f.read(), 'a resource') + # Use generic OSError, since e.g. attempting to read a directory can + # fail with PermissionError rather than IsADirectoryError + with self.assertRaises(OSError): + resources.open_text(self.anchor01) + with self.assertRaises(OSError): + resources.open_text(self.anchor01, 'no-such-file') + with resources.open_text(self.anchor01, 'utf-16.file') as f: + with self.assertRaises(UnicodeDecodeError): + f.read() + with resources.open_text( + self.anchor01, + 'binary.file', + encoding='latin1', + ) as f: + self.assertEqual(f.read(), '\x00\x01\x02\x03') + with resources.open_text( + self.anchor01, + 'utf-16.file', + errors='backslashreplace', + ) as f: + self.assertEndsWith( # ignore the BOM + f.read(), + 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode( + errors='backslashreplace', + ), + ) + + def test_open_binary(self): + with resources.open_binary(self.anchor01, 'utf-8.file') as f: + self.assertEqual(f.read(), b'Hello, UTF-8 world!\n') + for path_parts in self._gen_resourcetxt_path_parts(): + with resources.open_binary( + self.anchor02, + *path_parts, + ) as f: + self.assertEqual(f.read(), b'a resource') + + def test_path(self): + with resources.path(self.anchor01, 'utf-8.file') as path: + with open(str(path), encoding='utf-8') as f: + self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') + with resources.path(self.anchor01) as path: + with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f: + self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') + + def test_is_resource(self): + is_resource = resources.is_resource + self.assertTrue(is_resource(self.anchor01, 'utf-8.file')) + self.assertFalse(is_resource(self.anchor01, 'no_such_file')) + self.assertFalse(is_resource(self.anchor01)) + self.assertFalse(is_resource(self.anchor01, 'subdirectory')) + for path_parts in self._gen_resourcetxt_path_parts(): + self.assertTrue(is_resource(self.anchor02, *path_parts)) + + def test_contents(self): + with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)): + c = resources.contents(self.anchor01) + self.assertGreaterEqual( + set(c), + {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}, + ) + with self.assertRaises(OSError), warnings_helper.check_warnings(( + ".*contents.*", + DeprecationWarning, + )): + list(resources.contents(self.anchor01, 'utf-8.file')) + + for path_parts in self._gen_resourcetxt_path_parts(): + with self.assertRaises(OSError), warnings_helper.check_warnings(( + ".*contents.*", + DeprecationWarning, + )): + list(resources.contents(self.anchor01, *path_parts)) + with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)): + c = resources.contents(self.anchor01, 'subdirectory') + self.assertGreaterEqual( + set(c), + {'binary.file'}, + ) + + @warnings_helper.ignore_warnings(category=DeprecationWarning) + def test_common_errors(self): + for func in ( + resources.read_text, + resources.read_binary, + resources.open_text, + resources.open_binary, + resources.path, + resources.is_resource, + resources.contents, + ): + with self.subTest(func=func): + # Rejecting None anchor + with self.assertRaises(TypeError): + func(None) + # Rejecting invalid anchor type + with self.assertRaises((TypeError, AttributeError)): + func(1234) + # Unknown module + with self.assertRaises(ModuleNotFoundError): + func('$missing module$') + + def test_text_errors(self): + for func in ( + resources.read_text, + resources.open_text, + ): + with self.subTest(func=func): + # Multiple path arguments need explicit encoding argument. + with self.assertRaises(TypeError): + func( + self.anchor02, + 'subdirectory', + 'subsubdir', + 'resource.txt', + ) + + +class FunctionalAPITest_StringAnchor( + StringAnchorMixin, + FunctionalAPIBase, + unittest.TestCase, +): + pass + + +class FunctionalAPITest_ModuleAnchor( + ModuleAnchorMixin, + FunctionalAPIBase, + unittest.TestCase, +): + pass diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_open.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_open.py new file mode 100644 index 000000000..c40bb8c6f --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_open.py @@ -0,0 +1,84 @@ +import unittest + +import importlib_resources as resources +from . import util + + +class CommonBinaryTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + target = resources.files(package).joinpath(path) + with target.open('rb'): + pass + + +class CommonTextTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + target = resources.files(package).joinpath(path) + with target.open(encoding='utf-8'): + pass + + +class OpenTests: + def test_open_binary(self): + target = resources.files(self.data) / 'binary.file' + with target.open('rb') as fp: + result = fp.read() + self.assertEqual(result, bytes(range(4))) + + def test_open_text_default_encoding(self): + target = resources.files(self.data) / 'utf-8.file' + with target.open(encoding='utf-8') as fp: + result = fp.read() + self.assertEqual(result, 'Hello, UTF-8 world!\n') + + def test_open_text_given_encoding(self): + target = resources.files(self.data) / 'utf-16.file' + with target.open(encoding='utf-16', errors='strict') as fp: + result = fp.read() + self.assertEqual(result, 'Hello, UTF-16 world!\n') + + def test_open_text_with_errors(self): + """ + Raises UnicodeError without the 'errors' argument. + """ + target = resources.files(self.data) / 'utf-16.file' + with target.open(encoding='utf-8', errors='strict') as fp: + self.assertRaises(UnicodeError, fp.read) + with target.open(encoding='utf-8', errors='ignore') as fp: + result = fp.read() + self.assertEqual( + result, + 'H\x00e\x00l\x00l\x00o\x00,\x00 ' + '\x00U\x00T\x00F\x00-\x001\x006\x00 ' + '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', + ) + + def test_open_binary_FileNotFoundError(self): + target = resources.files(self.data) / 'does-not-exist' + with self.assertRaises(FileNotFoundError): + target.open('rb') + + def test_open_text_FileNotFoundError(self): + target = resources.files(self.data) / 'does-not-exist' + with self.assertRaises(FileNotFoundError): + target.open(encoding='utf-8') + + +class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase): + pass + + +class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + +class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase): + pass + + +class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_path.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_path.py new file mode 100644 index 000000000..1e30f2bc5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_path.py @@ -0,0 +1,62 @@ +import io +import pathlib +import unittest + +import importlib_resources as resources +from . import util + + +class CommonTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + with resources.as_file(resources.files(package).joinpath(path)): + pass + + +class PathTests: + def test_reading(self): + """ + Path should be readable and a pathlib.Path instance. + """ + target = resources.files(self.data) / 'utf-8.file' + with resources.as_file(target) as path: + self.assertIsInstance(path, pathlib.Path) + self.assertTrue(path.name.endswith("utf-8.file"), repr(path)) + self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8')) + + +class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase): + def test_natural_path(self): + """ + Guarantee the internal implementation detail that + file-system-backed resources do not get the tempdir + treatment. + """ + target = resources.files(self.data) / 'utf-8.file' + with resources.as_file(target) as path: + assert 'data' in str(path) + + +class PathMemoryTests(PathTests, unittest.TestCase): + def setUp(self): + file = io.BytesIO(b'Hello, UTF-8 world!\n') + self.addCleanup(file.close) + self.data = util.create_package( + file=file, path=FileNotFoundError("package exists only in memory") + ) + self.data.__spec__.origin = None + self.data.__spec__.has_location = False + + +class PathZipTests(PathTests, util.ZipSetup, unittest.TestCase): + def test_remove_in_context_manager(self): + """ + It is not an error if the file that was temporarily stashed on the + file system is removed inside the `with` stanza. + """ + target = resources.files(self.data) / 'utf-8.file' + with resources.as_file(target) as path: + path.unlink() + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_read.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_read.py new file mode 100644 index 000000000..6780a2d16 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_read.py @@ -0,0 +1,93 @@ +import unittest +import importlib_resources as resources + +from . import util +from importlib import import_module + + +class CommonBinaryTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + resources.files(package).joinpath(path).read_bytes() + + +class CommonTextTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + resources.files(package).joinpath(path).read_text(encoding='utf-8') + + +class ReadTests: + def test_read_bytes(self): + result = resources.files(self.data).joinpath('binary.file').read_bytes() + self.assertEqual(result, bytes(range(4))) + + def test_read_text_default_encoding(self): + result = ( + resources.files(self.data) + .joinpath('utf-8.file') + .read_text(encoding='utf-8') + ) + self.assertEqual(result, 'Hello, UTF-8 world!\n') + + def test_read_text_given_encoding(self): + result = ( + resources.files(self.data) + .joinpath('utf-16.file') + .read_text(encoding='utf-16') + ) + self.assertEqual(result, 'Hello, UTF-16 world!\n') + + def test_read_text_with_errors(self): + """ + Raises UnicodeError without the 'errors' argument. + """ + target = resources.files(self.data) / 'utf-16.file' + self.assertRaises(UnicodeError, target.read_text, encoding='utf-8') + result = target.read_text(encoding='utf-8', errors='ignore') + self.assertEqual( + result, + 'H\x00e\x00l\x00l\x00o\x00,\x00 ' + '\x00U\x00T\x00F\x00-\x001\x006\x00 ' + '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', + ) + + +class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase): + pass + + +class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase): + def test_read_submodule_resource(self): + submodule = import_module('data01.subdirectory') + result = resources.files(submodule).joinpath('binary.file').read_bytes() + self.assertEqual(result, bytes(range(4, 8))) + + def test_read_submodule_resource_by_name(self): + result = ( + resources.files('data01.subdirectory').joinpath('binary.file').read_bytes() + ) + self.assertEqual(result, bytes(range(4, 8))) + + +class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + +class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + def test_read_submodule_resource(self): + submodule = import_module('namespacedata01.subdirectory') + result = resources.files(submodule).joinpath('binary.file').read_bytes() + self.assertEqual(result, bytes(range(12, 16))) + + def test_read_submodule_resource_by_name(self): + result = ( + resources.files('namespacedata01.subdirectory') + .joinpath('binary.file') + .read_bytes() + ) + self.assertEqual(result, bytes(range(12, 16))) + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_reader.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_reader.py new file mode 100644 index 000000000..0a77eb404 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_reader.py @@ -0,0 +1,137 @@ +import os.path +import pathlib +import unittest + +from importlib import import_module +from importlib_resources.readers import MultiplexedPath, NamespaceReader + +from . import util + + +class MultiplexedPathTest(util.DiskSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + def setUp(self): + super().setUp() + self.folder = pathlib.Path(self.data.__path__[0]) + self.data01 = pathlib.Path(self.load_fixture('data01').__file__).parent + self.data02 = pathlib.Path(self.load_fixture('data02').__file__).parent + + def test_init_no_paths(self): + with self.assertRaises(FileNotFoundError): + MultiplexedPath() + + def test_init_file(self): + with self.assertRaises(NotADirectoryError): + MultiplexedPath(self.folder / 'binary.file') + + def test_iterdir(self): + contents = {path.name for path in MultiplexedPath(self.folder).iterdir()} + try: + contents.remove('__pycache__') + except (KeyError, ValueError): + pass + self.assertEqual( + contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'} + ) + + def test_iterdir_duplicate(self): + contents = { + path.name for path in MultiplexedPath(self.folder, self.data01).iterdir() + } + for remove in ('__pycache__', '__init__.pyc'): + try: + contents.remove(remove) + except (KeyError, ValueError): + pass + self.assertEqual( + contents, + {'__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file'}, + ) + + def test_is_dir(self): + self.assertEqual(MultiplexedPath(self.folder).is_dir(), True) + + def test_is_file(self): + self.assertEqual(MultiplexedPath(self.folder).is_file(), False) + + def test_open_file(self): + path = MultiplexedPath(self.folder) + with self.assertRaises(FileNotFoundError): + path.read_bytes() + with self.assertRaises(FileNotFoundError): + path.read_text() + with self.assertRaises(FileNotFoundError): + path.open() + + def test_join_path(self): + prefix = str(self.folder.parent) + path = MultiplexedPath(self.folder, self.data01) + self.assertEqual( + str(path.joinpath('binary.file'))[len(prefix) + 1 :], + os.path.join('namespacedata01', 'binary.file'), + ) + sub = path.joinpath('subdirectory') + assert isinstance(sub, MultiplexedPath) + assert 'namespacedata01' in str(sub) + assert 'data01' in str(sub) + self.assertEqual( + str(path.joinpath('imaginary'))[len(prefix) + 1 :], + os.path.join('namespacedata01', 'imaginary'), + ) + self.assertEqual(path.joinpath(), path) + + def test_join_path_compound(self): + path = MultiplexedPath(self.folder) + assert not path.joinpath('imaginary/foo.py').exists() + + def test_join_path_common_subdir(self): + prefix = str(self.data02.parent) + path = MultiplexedPath(self.data01, self.data02) + self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath) + self.assertEqual( + str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :], + os.path.join('data02', 'subdirectory', 'subsubdir'), + ) + + def test_repr(self): + self.assertEqual( + repr(MultiplexedPath(self.folder)), + f"MultiplexedPath('{self.folder}')", + ) + + def test_name(self): + self.assertEqual( + MultiplexedPath(self.folder).name, + os.path.basename(self.folder), + ) + + +class NamespaceReaderTest(util.DiskSetup, unittest.TestCase): + MODULE = 'namespacedata01' + + def test_init_error(self): + with self.assertRaises(ValueError): + NamespaceReader(['path1', 'path2']) + + def test_resource_path(self): + namespacedata01 = import_module('namespacedata01') + reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) + + root = self.data.__path__[0] + self.assertEqual( + reader.resource_path('binary.file'), os.path.join(root, 'binary.file') + ) + self.assertEqual( + reader.resource_path('imaginary'), os.path.join(root, 'imaginary') + ) + + def test_files(self): + reader = NamespaceReader(self.data.__spec__.submodule_search_locations) + root = self.data.__path__[0] + self.assertIsInstance(reader.files(), MultiplexedPath) + self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')") + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/test_resource.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_resource.py new file mode 100644 index 000000000..a0da6a356 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/test_resource.py @@ -0,0 +1,237 @@ +import unittest +import importlib_resources as resources + +from . import util +from importlib import import_module + + +class ResourceTests: + # Subclasses are expected to set the `data` attribute. + + def test_is_file_exists(self): + target = resources.files(self.data) / 'binary.file' + self.assertTrue(target.is_file()) + + def test_is_file_missing(self): + target = resources.files(self.data) / 'not-a-file' + self.assertFalse(target.is_file()) + + def test_is_dir(self): + target = resources.files(self.data) / 'subdirectory' + self.assertFalse(target.is_file()) + self.assertTrue(target.is_dir()) + + +class ResourceDiskTests(ResourceTests, util.DiskSetup, unittest.TestCase): + pass + + +class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase): + pass + + +def names(traversable): + return {item.name for item in traversable.iterdir()} + + +class ResourceLoaderTests(util.DiskSetup, unittest.TestCase): + def test_resource_contents(self): + package = util.create_package( + file=self.data, path=self.data.__file__, contents=['A', 'B', 'C'] + ) + self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'}) + + def test_is_file(self): + package = util.create_package( + file=self.data, + path=self.data.__file__, + contents=['A', 'B', 'C', 'D/E', 'D/F'], + ) + self.assertTrue(resources.files(package).joinpath('B').is_file()) + + def test_is_dir(self): + package = util.create_package( + file=self.data, + path=self.data.__file__, + contents=['A', 'B', 'C', 'D/E', 'D/F'], + ) + self.assertTrue(resources.files(package).joinpath('D').is_dir()) + + def test_resource_missing(self): + package = util.create_package( + file=self.data, + path=self.data.__file__, + contents=['A', 'B', 'C', 'D/E', 'D/F'], + ) + self.assertFalse(resources.files(package).joinpath('Z').is_file()) + + +class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase): + def test_package_has_no_reader_fallback(self): + """ + Test odd ball packages which: + # 1. Do not have a ResourceReader as a loader + # 2. Are not on the file system + # 3. Are not in a zip file + """ + module = util.create_package( + file=self.data, path=self.data.__file__, contents=['A', 'B', 'C'] + ) + # Give the module a dummy loader. + module.__loader__ = object() + # Give the module a dummy origin. + module.__file__ = '/path/which/shall/not/be/named' + module.__spec__.loader = module.__loader__ + module.__spec__.origin = module.__file__ + self.assertFalse(resources.files(module).joinpath('A').is_file()) + + +class ResourceFromZipsTest01(util.ZipSetup, unittest.TestCase): + def test_is_submodule_resource(self): + submodule = import_module('data01.subdirectory') + self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file()) + + def test_read_submodule_resource_by_name(self): + self.assertTrue( + resources.files('data01.subdirectory').joinpath('binary.file').is_file() + ) + + def test_submodule_contents(self): + submodule = import_module('data01.subdirectory') + self.assertEqual( + names(resources.files(submodule)), {'__init__.py', 'binary.file'} + ) + + def test_submodule_contents_by_name(self): + self.assertEqual( + names(resources.files('data01.subdirectory')), + {'__init__.py', 'binary.file'}, + ) + + def test_as_file_directory(self): + with resources.as_file(resources.files('data01')) as data: + assert data.name == 'data01' + assert data.is_dir() + assert data.joinpath('subdirectory').is_dir() + assert len(list(data.iterdir())) + assert not data.parent.exists() + + +class ResourceFromZipsTest02(util.ZipSetup, unittest.TestCase): + MODULE = 'data02' + + def test_unrelated_contents(self): + """ + Test thata zip with two unrelated subpackages return + distinct resources. Ref python/importlib_resources#44. + """ + self.assertEqual( + names(resources.files('data02.one')), + {'__init__.py', 'resource1.txt'}, + ) + self.assertEqual( + names(resources.files('data02.two')), + {'__init__.py', 'resource2.txt'}, + ) + + +class DeletingZipsTest(util.ZipSetup, unittest.TestCase): + """Having accessed resources in a zip file should not keep an open + reference to the zip. + """ + + def test_iterdir_does_not_keep_open(self): + [item.name for item in resources.files('data01').iterdir()] + + def test_is_file_does_not_keep_open(self): + resources.files('data01').joinpath('binary.file').is_file() + + def test_is_file_failure_does_not_keep_open(self): + resources.files('data01').joinpath('not-present').is_file() + + @unittest.skip("Desired but not supported.") + def test_as_file_does_not_keep_open(self): # pragma: no cover + resources.as_file(resources.files('data01') / 'binary.file') + + def test_entered_path_does_not_keep_open(self): + """ + Mimic what certifi does on import to make its bundle + available for the process duration. + """ + resources.as_file(resources.files('data01') / 'binary.file').__enter__() + + def test_read_binary_does_not_keep_open(self): + resources.files('data01').joinpath('binary.file').read_bytes() + + def test_read_text_does_not_keep_open(self): + resources.files('data01').joinpath('utf-8.file').read_text(encoding='utf-8') + + +class ResourceFromNamespaceTests: + def test_is_submodule_resource(self): + self.assertTrue( + resources.files(import_module('namespacedata01')) + .joinpath('binary.file') + .is_file() + ) + + def test_read_submodule_resource_by_name(self): + self.assertTrue( + resources.files('namespacedata01').joinpath('binary.file').is_file() + ) + + def test_submodule_contents(self): + contents = names(resources.files(import_module('namespacedata01'))) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual( + contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'} + ) + + def test_submodule_contents_by_name(self): + contents = names(resources.files('namespacedata01')) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual( + contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'} + ) + + def test_submodule_sub_contents(self): + contents = names(resources.files(import_module('namespacedata01.subdirectory'))) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual(contents, {'binary.file'}) + + def test_submodule_sub_contents_by_name(self): + contents = names(resources.files('namespacedata01.subdirectory')) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual(contents, {'binary.file'}) + + +class ResourceFromNamespaceDiskTests( + util.DiskSetup, + ResourceFromNamespaceTests, + unittest.TestCase, +): + MODULE = 'namespacedata01' + + +class ResourceFromNamespaceZipTests( + util.ZipSetup, + ResourceFromNamespaceTests, + unittest.TestCase, +): + MODULE = 'namespacedata01' + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/util.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/util.py new file mode 100644 index 000000000..a4eafac3a --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/util.py @@ -0,0 +1,206 @@ +import abc +import importlib +import io +import sys +import types +import pathlib +import contextlib + +from ..abc import ResourceReader +from .compat.py39 import import_helper, os_helper +from . import zip as zip_ +from . import _path + + +from importlib.machinery import ModuleSpec + + +class Reader(ResourceReader): + def __init__(self, **kwargs): + vars(self).update(kwargs) + + def get_resource_reader(self, package): + return self + + def open_resource(self, path): + self._path = path + if isinstance(self.file, Exception): + raise self.file + return self.file + + def resource_path(self, path_): + self._path = path_ + if isinstance(self.path, Exception): + raise self.path + return self.path + + def is_resource(self, path_): + self._path = path_ + if isinstance(self.path, Exception): + raise self.path + + def part(entry): + return entry.split('/') + + return any( + len(parts) == 1 and parts[0] == path_ for parts in map(part, self._contents) + ) + + def contents(self): + if isinstance(self.path, Exception): + raise self.path + yield from self._contents + + +def create_package_from_loader(loader, is_package=True): + name = 'testingpackage' + module = types.ModuleType(name) + spec = ModuleSpec(name, loader, origin='does-not-exist', is_package=is_package) + module.__spec__ = spec + module.__loader__ = loader + return module + + +def create_package(file=None, path=None, is_package=True, contents=()): + return create_package_from_loader( + Reader(file=file, path=path, _contents=contents), + is_package, + ) + + +class CommonTestsBase(metaclass=abc.ABCMeta): + """ + Tests shared by test_open, test_path, and test_read. + """ + + @abc.abstractmethod + def execute(self, package, path): + """ + Call the pertinent legacy API function (e.g. open_text, path) + on package and path. + """ + + def test_package_name(self): + """ + Passing in the package name should succeed. + """ + self.execute(self.data.__name__, 'utf-8.file') + + def test_package_object(self): + """ + Passing in the package itself should succeed. + """ + self.execute(self.data, 'utf-8.file') + + def test_string_path(self): + """ + Passing in a string for the path should succeed. + """ + path = 'utf-8.file' + self.execute(self.data, path) + + def test_pathlib_path(self): + """ + Passing in a pathlib.PurePath object for the path should succeed. + """ + path = pathlib.PurePath('utf-8.file') + self.execute(self.data, path) + + def test_importing_module_as_side_effect(self): + """ + The anchor package can already be imported. + """ + del sys.modules[self.data.__name__] + self.execute(self.data.__name__, 'utf-8.file') + + def test_missing_path(self): + """ + Attempting to open or read or request the path for a + non-existent path should succeed if open_resource + can return a viable data stream. + """ + bytes_data = io.BytesIO(b'Hello, world!') + package = create_package(file=bytes_data, path=FileNotFoundError()) + self.execute(package, 'utf-8.file') + self.assertEqual(package.__loader__._path, 'utf-8.file') + + def test_extant_path(self): + # Attempting to open or read or request the path when the + # path does exist should still succeed. Does not assert + # anything about the result. + bytes_data = io.BytesIO(b'Hello, world!') + # any path that exists + path = __file__ + package = create_package(file=bytes_data, path=path) + self.execute(package, 'utf-8.file') + self.assertEqual(package.__loader__._path, 'utf-8.file') + + def test_useless_loader(self): + package = create_package(file=FileNotFoundError(), path=FileNotFoundError()) + with self.assertRaises(FileNotFoundError): + self.execute(package, 'utf-8.file') + + +fixtures = dict( + data01={ + '__init__.py': '', + 'binary.file': bytes(range(4)), + 'utf-16.file': 'Hello, UTF-16 world!\n'.encode('utf-16'), + 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), + 'subdirectory': { + '__init__.py': '', + 'binary.file': bytes(range(4, 8)), + }, + }, + data02={ + '__init__.py': '', + 'one': {'__init__.py': '', 'resource1.txt': 'one resource'}, + 'two': {'__init__.py': '', 'resource2.txt': 'two resource'}, + 'subdirectory': {'subsubdir': {'resource.txt': 'a resource'}}, + }, + namespacedata01={ + 'binary.file': bytes(range(4)), + 'utf-16.file': 'Hello, UTF-16 world!\n'.encode('utf-16'), + 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), + 'subdirectory': { + 'binary.file': bytes(range(12, 16)), + }, + }, +) + + +class ModuleSetup: + def setUp(self): + self.fixtures = contextlib.ExitStack() + self.addCleanup(self.fixtures.close) + + self.fixtures.enter_context(import_helper.isolated_modules()) + self.data = self.load_fixture(self.MODULE) + + def load_fixture(self, module): + self.tree_on_path({module: fixtures[module]}) + return importlib.import_module(module) + + +class ZipSetup(ModuleSetup): + MODULE = 'data01' + + def tree_on_path(self, spec): + temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) + modules = pathlib.Path(temp_dir) / 'zipped modules.zip' + self.fixtures.enter_context( + import_helper.DirsOnSysPath(str(zip_.make_zip_file(spec, modules))) + ) + + +class DiskSetup(ModuleSetup): + MODULE = 'data01' + + def tree_on_path(self, spec): + temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) + _path.build(spec, pathlib.Path(temp_dir)) + self.fixtures.enter_context(import_helper.DirsOnSysPath(temp_dir)) + + +class CommonTests(DiskSetup, CommonTestsBase): + pass diff --git a/venv/lib/python3.8/site-packages/importlib_resources/tests/zip.py b/venv/lib/python3.8/site-packages/importlib_resources/tests/zip.py new file mode 100644 index 000000000..51ee56487 --- /dev/null +++ b/venv/lib/python3.8/site-packages/importlib_resources/tests/zip.py @@ -0,0 +1,26 @@ +""" +Generate zip test data files. +""" + +import zipfile + +import zipp + + +def make_zip_file(tree, dst): + """ + Zip the files in tree into a new zipfile at dst. + """ + with zipfile.ZipFile(dst, 'w') as zf: + for name, contents in walk(tree): + zf.writestr(name, contents) + zipp.CompleteDirs.inject(zf) + return dst + + +def walk(tree, prefix=''): + for name, contents in tree.items(): + if isinstance(contents, dict): + yield from walk(contents, prefix=f'{prefix}{name}/') + else: + yield f'{prefix}{name}', contents diff --git a/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/INSTALLER b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/METADATA b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/METADATA new file mode 100644 index 000000000..067e8fa64 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/METADATA @@ -0,0 +1,88 @@ +Metadata-Version: 2.4 +Name: Mako +Version: 1.3.10 +Summary: A super-fast templating language that borrows the best ideas from the existing templating languages. +Home-page: https://www.makotemplates.org/ +Author: Mike Bayer +Author-email: mike@zzzcomputing.com +License: MIT +Project-URL: Documentation, https://docs.makotemplates.org +Project-URL: Issue Tracker, https://github.com/sqlalchemy/mako +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: MarkupSafe>=0.9.2 +Provides-Extra: testing +Requires-Dist: pytest; extra == "testing" +Provides-Extra: babel +Requires-Dist: Babel; extra == "babel" +Provides-Extra: lingua +Requires-Dist: lingua; extra == "lingua" +Dynamic: license-file + +========================= +Mako Templates for Python +========================= + +Mako is a template library written in Python. It provides a familiar, non-XML +syntax which compiles into Python modules for maximum performance. Mako's +syntax and API borrows from the best ideas of many others, including Django +templates, Cheetah, Myghty, and Genshi. Conceptually, Mako is an embedded +Python (i.e. Python Server Page) language, which refines the familiar ideas +of componentized layout and inheritance to produce one of the most +straightforward and flexible models available, while also maintaining close +ties to Python calling and scoping semantics. + +Nutshell +======== + +:: + + <%inherit file="base.html"/> + <% + rows = [[v for v in range(0,10)] for row in range(0,10)] + %> + + % for row in rows: + ${makerow(row)} + % endfor +
    + + <%def name="makerow(row)"> + + % for name in row: + ${name}\ + % endfor + + + +Philosophy +=========== + +Python is a great scripting language. Don't reinvent the wheel...your templates can handle it ! + +Documentation +============== + +See documentation for Mako at https://docs.makotemplates.org/en/latest/ + +License +======== + +Mako is licensed under an MIT-style license (see LICENSE). +Other incorporated projects may be licensed under different licenses. +All licenses allow for non-commercial and commercial use. diff --git a/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/RECORD b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/RECORD new file mode 100644 index 000000000..267cb5ea5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/RECORD @@ -0,0 +1,74 @@ +../../../bin/mako-render,sha256=mQkhS2yllVZCTWHvs6CTWj5ESxy7Kv_JN644Kc7JV4k,270 +mako-1.3.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +mako-1.3.10.dist-info/METADATA,sha256=VAbwPOfALVKbDJYv5l848ZceyGwynnE9s_TXP6_dr5o,2919 +mako-1.3.10.dist-info/RECORD,, +mako-1.3.10.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91 +mako-1.3.10.dist-info/entry_points.txt,sha256=LsKkUsOsJQYbJ2M72hZCm968wi5K8Ywb5uFxCuN8Obk,512 +mako-1.3.10.dist-info/licenses/LICENSE,sha256=aNcGTlPj_6jp9CCp5gS9LiBZ2cMwSS-m69TrPUgRFok,1098 +mako-1.3.10.dist-info/top_level.txt,sha256=LItdH8cDPetpUu8rUyBG3DObS6h9Gcpr9j_WLj2S-R0,5 +mako/__init__.py,sha256=n-XoSW8ChrO6NkJ2xUHElzvSnU6XUu5ruMZe86QldTI,243 +mako/__pycache__/__init__.cpython-38.pyc,, +mako/__pycache__/_ast_util.cpython-38.pyc,, +mako/__pycache__/ast.cpython-38.pyc,, +mako/__pycache__/cache.cpython-38.pyc,, +mako/__pycache__/cmd.cpython-38.pyc,, +mako/__pycache__/codegen.cpython-38.pyc,, +mako/__pycache__/compat.cpython-38.pyc,, +mako/__pycache__/exceptions.cpython-38.pyc,, +mako/__pycache__/filters.cpython-38.pyc,, +mako/__pycache__/lexer.cpython-38.pyc,, +mako/__pycache__/lookup.cpython-38.pyc,, +mako/__pycache__/parsetree.cpython-38.pyc,, +mako/__pycache__/pygen.cpython-38.pyc,, +mako/__pycache__/pyparser.cpython-38.pyc,, +mako/__pycache__/runtime.cpython-38.pyc,, +mako/__pycache__/template.cpython-38.pyc,, +mako/__pycache__/util.cpython-38.pyc,, +mako/_ast_util.py,sha256=hCbfnnizWEa3xRCA-uVyShC2HohSpmVvexz5as_lHc8,20247 +mako/ast.py,sha256=xYrdSiJFbf1CxJ9tU9pcPEWK0BYfwF2aDNDNLQG9PqQ,6642 +mako/cache.py,sha256=kA6FKGl5NeTBnSTcnhoPkSaeJ0JeYpF6GM8qzEZGSts,7680 +mako/cmd.py,sha256=Y2Y6VxNCYwO2Y8EXOdLTf5tpYgfdoondV6Ehlbh8a84,2813 +mako/codegen.py,sha256=N49EH57CcTUfKGzI8V7GFBrAEoFhRo9lkdXBzpFYzBY,47736 +mako/compat.py,sha256=owGbObdoF0C-6rCCs6Vnk3YGHv0bf0PpTja55Woxqb4,1820 +mako/exceptions.py,sha256=87Djuoi1IS5zyVFml1Z5zpCP1IoI7UMOH3h4ejt3x3g,12530 +mako/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mako/ext/__pycache__/__init__.cpython-38.pyc,, +mako/ext/__pycache__/autohandler.cpython-38.pyc,, +mako/ext/__pycache__/babelplugin.cpython-38.pyc,, +mako/ext/__pycache__/beaker_cache.cpython-38.pyc,, +mako/ext/__pycache__/extract.cpython-38.pyc,, +mako/ext/__pycache__/linguaplugin.cpython-38.pyc,, +mako/ext/__pycache__/preprocessors.cpython-38.pyc,, +mako/ext/__pycache__/pygmentplugin.cpython-38.pyc,, +mako/ext/__pycache__/turbogears.cpython-38.pyc,, +mako/ext/autohandler.py,sha256=QuhNiPSF1LZ53awQ1Qfpwu1Zi9RGOmrtCDnwCLgQzeE,1885 +mako/ext/babelplugin.py,sha256=mmt5fG3pcYII1QsrLEV3wH1ltj-C7uKl8vBIpAjxsKY,2091 +mako/ext/beaker_cache.py,sha256=4quuJQuXRKKUyF6kM43LQhJT1J2z1KSglRZVDW-7c1I,2578 +mako/ext/extract.py,sha256=ZUeaRL2jWcUlrpnhXFXJB0CUJfvQVGBF9tJr5nKJAWI,4659 +mako/ext/linguaplugin.py,sha256=sUZalJSI_XeON9aRBb2hds-ilVQaxHKlfCg_nAl7EuU,1935 +mako/ext/preprocessors.py,sha256=HYG45idRJUwJkDpswEELL8lPFLisQzgDhW5EHpTDGkI,576 +mako/ext/pygmentplugin.py,sha256=TnpJDyQeWTTGHZraMDpw8FB3PNzbmXhaZcjyIBudyi0,4753 +mako/ext/turbogears.py,sha256=egv8hradAnnSJwxtmW8uXAsqPUzX8cZZCXclmO_8hMA,2141 +mako/filters.py,sha256=IBXyGOby4eFE3UGvNLhJlzbe1FfwJ2dcEr1-gKO_Ljc,4658 +mako/lexer.py,sha256=GMq8yf0dEn04-xw2EVDEzaOLXMSVVQz9HyUbfwKnZKg,16321 +mako/lookup.py,sha256=4ALORJiL0wIdDvK1okW8rbjq2jL5F_TNASekDFQSULY,12428 +mako/parsetree.py,sha256=-wmyX_mklAoKhc-7Psx0U15EKpNSd8oGRXFmvk2JQWo,19021 +mako/pygen.py,sha256=689_jR0GG_8Am62Dmlm5h59VY6eyZAU3GroodqEDnZ0,10416 +mako/pyparser.py,sha256=8ZqUbCDIlgEup4si2zP7-xJns14JvVzX_QMO9sCCGJ0,7558 +mako/runtime.py,sha256=ZsUEN22nX3d3dECQujF69mBKDQS6yVv2nvz_0eTvFGg,27804 +mako/template.py,sha256=Vn5nLoBY-YzSQJKvRPFGb4fiPyZryA5Q-8mWutv0b9A,23563 +mako/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mako/testing/__pycache__/__init__.cpython-38.pyc,, +mako/testing/__pycache__/_config.cpython-38.pyc,, +mako/testing/__pycache__/assertions.cpython-38.pyc,, +mako/testing/__pycache__/config.cpython-38.pyc,, +mako/testing/__pycache__/exclusions.cpython-38.pyc,, +mako/testing/__pycache__/fixtures.cpython-38.pyc,, +mako/testing/__pycache__/helpers.cpython-38.pyc,, +mako/testing/_config.py,sha256=k-qpnsnbXUoN-ykMN5BRpg84i1x0p6UsAddKQnrIytU,3566 +mako/testing/assertions.py,sha256=pfbGl84QlW7QWGg3_lo3wP8XnBAVo9AjzNp2ajmn7FA,5161 +mako/testing/config.py,sha256=wmYVZfzGvOK3mJUZpzmgO8-iIgvaCH41Woi4yDpxq6E,323 +mako/testing/exclusions.py,sha256=_t6ADKdatk3f18tOfHV_ZY6u_ZwQsKphZ2MXJVSAOcI,1553 +mako/testing/fixtures.py,sha256=nEp7wTusf7E0n3Q-BHJW2s_t1vx0KB9poadQ1BmIJzE,3044 +mako/testing/helpers.py,sha256=z4HAactwlht4ut1cbvxKt1QLb3yLPk1U7cnh5BwVUlc,1623 +mako/util.py,sha256=SNYeX2_PmajQJIR3-S1Yqxxylz8lwS65rC8YbCdTkUU,10638 diff --git a/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/WHEEL b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/WHEEL new file mode 100644 index 000000000..1eb3c49d9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (78.1.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/entry_points.txt b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/entry_points.txt new file mode 100644 index 000000000..30f31b2bf --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/entry_points.txt @@ -0,0 +1,18 @@ +[babel.extractors] +mako = mako.ext.babelplugin:extract [babel] + +[console_scripts] +mako-render = mako.cmd:cmdline + +[lingua.extractors] +mako = mako.ext.linguaplugin:LinguaMakoExtractor [lingua] + +[pygments.lexers] +css+mako = mako.ext.pygmentplugin:MakoCssLexer +html+mako = mako.ext.pygmentplugin:MakoHtmlLexer +js+mako = mako.ext.pygmentplugin:MakoJavascriptLexer +mako = mako.ext.pygmentplugin:MakoLexer +xml+mako = mako.ext.pygmentplugin:MakoXmlLexer + +[python.templating.engines] +mako = mako.ext.turbogears:TGPlugin diff --git a/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/licenses/LICENSE b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/licenses/LICENSE new file mode 100644 index 000000000..4c18adee3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/licenses/LICENSE @@ -0,0 +1,19 @@ +Copyright 2006-2025 the Mako authors and contributors . + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/top_level.txt b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/top_level.txt new file mode 100644 index 000000000..2951cdd49 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako-1.3.10.dist-info/top_level.txt @@ -0,0 +1 @@ +mako diff --git a/venv/lib/python3.8/site-packages/mako/__init__.py b/venv/lib/python3.8/site-packages/mako/__init__.py new file mode 100644 index 000000000..71f8f6036 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/__init__.py @@ -0,0 +1,8 @@ +# mako/__init__.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +__version__ = "1.3.10" diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..265377eafde0f173158e37f62ec2a4422a1501c3 GIT binary patch literal 219 zcmYjMu?oU46ihovQ40P>hxRF597G%(#YJ3PLTaBjwoO733+*5IAAUtwKf%q(R2)2b z_c-o`yF(a`iNo{({N?_05dZL;Si3WUqnsQ`?(IqBccXcdCiB@Y^7;v-4Qj)sp!80( z?}A@?U!jmS0(Mw(tYCu;R#Md{ENoe3K()4zB35a9)`%I$*lKnvQA2~Gfr@9pNSZ9b iaEq~WHoKsKnw1hL<$_yE6V>*?vX=R+!qw1oSVBJaz&z3b literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/_ast_util.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/_ast_util.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0184199be1789879a390880f6fa6643cec17cd2c GIT binary patch literal 21569 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 JuKh~u{RaSd%Ygs@ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/cmd.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/cmd.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12ea5865687926259060cf8144752a3db996be02 GIT binary patch literal 2407 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 A*Z=?k literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/compat.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/compat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2651e0648416078d681bb597956dc6bbe79915d GIT binary patch literal 1931 zcmZWqOK%)S5T5RN?CitN+95=dAX*+mSqXck7ziLhF-hP6a!|}e>}52b?zLxkc4pK) zYiF(53kGgnxFRI)F%mz4Bfp}robngAz*n=owj)NG?&_*i?^Zak?@4c1JNJb%XZ^Zt zNKd$Dyx)}8c}sYrCVWvBjYDV7`)i^pT0dI-b+IPaF<%qcL>qHkY=}+F8{)cn3G=34 z`&ReHbHrlpb(!(UvIs?3gpi#{5hunziBq$YPxEPTER~M4G>FqdW@@|Ya6gxkaYA*d zP3<^TmEe!$^dwV48(+vlI7tc}G1`9l_?TV15AfpO*0 zTEB6@e&=WG2VjQojKS)a4A#$JWw7$4l?`h%Ub3M-voY3ZPSGfBVVC?Te#<&zGq>cW zbI+P_Vb8F`uvu8c)_qI;O5c>gK+A=5bAhNW5o!n+IWeu@hJ9CgZ_w>_4 zWDFTVDmn+DmZFoToxvnYrk!J%icCQR2x(TZfy&06ucpNjkUQHu+o!iWvF@Z<(Fs@T z!6c0e|_T+85MiaDW%=Esl_RIE{q>*lvjNNKBukU&h22SF4Sz5Uk`R?|Ja`;@ zyZdDK$>;lKeIbAG==<*;KY6&XJVa?)L9k*%5OjUxP*8?PnQ>D}2~FgymX1znt>}s< z;)!t)$mrymrC!78pQGMUHkLhkDo3$QdJpBXOtO3|)1vofc%XZEmC~(zXKSFOJc#91 zp~7e+RS%Uo?j`ZT60)=Xeox1R+{$4N52fyn!%^0YvT+_3+xgUNF4G!Z7WW1s(04Fc zc7u7W!Pfa|^ssJopE;}r{c`5&4PYZqUo0O7Grbk{--589KIqJ695OHYoPoZ4XrDW! zJ!i}+?S1PR+v~c@2HxDHVh6;ZU=SxVNU|s-nwa)D6B8&Z6M>AEWTBACH04u#vM_g@ zetijyg>i7$BjYU43w4#}#qc`$YtkmmkMBaEsVY`=%obB`Lykys^`Qe-2W&y01J2Ro zZdtf1d`~r@>vCi3DcXa48da_2ic>1Hsyw$~l{uX)2=xb0>nf8>^&r2{Q0OMmTRElp za5=3Z+M0S33wW!8?@6dN2oLHL*FZyNY6LIFV}q679mp^ErFbr?xsAo* zuxL?eLxP=gbsLQXtxWJ=5KYNYQ?&Lu-$#o_I-EV@X_?^iA{h5jWs|%Lr|W`#<2+LZ z2pg>WalZwct7(vDaa!Q69ECb83Z>qGV`GD+U9R3GnajmCSE~5(|FKU$FV6Y3B8HU=L|Y~jNB92bu} Rj9ZO=9oK)=ulbJe{09nf{YC%) literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/exceptions.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/exceptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ee7d9d29843d24fda72608b4addd0cf0a0b9d45 GIT binary patch literal 11620 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 ATL1t6 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/lexer.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/lexer.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e2f13b67303abfca491a359064d4e7bf06319e1 GIT binary patch literal 10588 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 IN=xtlH)bI#*Z=?k literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/parsetree.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/parsetree.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eff7d24c2be2c94db8307851041c8676dc24f097 GIT binary patch literal 19664 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/pyparser.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/pyparser.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..937cdf6c020415dcde08f66a6d34efc687390cf5 GIT binary patch literal 7261 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 W__K;;!6=x8y^BBA@{0!+pZE_79a3We literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/runtime.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/runtime.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..beaaebe6e4b6c2bb0a921c749af26fb084c15318 GIT binary patch literal 27742 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 oPfVYmp2nREvoFt{DbJM;;g~GXBQ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/__pycache__/template.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/__pycache__/template.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aac187eeaaa34245cd47ca0e0d7e71ad6c47d67f GIT binary patch literal 19716 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/_ast_util.py b/venv/lib/python3.8/site-packages/mako/_ast_util.py new file mode 100644 index 000000000..385d7a008 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/_ast_util.py @@ -0,0 +1,713 @@ +# mako/_ast_util.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +""" + ast + ~~~ + + This is a stripped down version of Armin Ronacher's ast module. + + :copyright: Copyright 2008 by Armin Ronacher. + :license: Python License. +""" + + +from _ast import Add +from _ast import And +from _ast import AST +from _ast import BitAnd +from _ast import BitOr +from _ast import BitXor +from _ast import Div +from _ast import Eq +from _ast import FloorDiv +from _ast import Gt +from _ast import GtE +from _ast import If +from _ast import In +from _ast import Invert +from _ast import Is +from _ast import IsNot +from _ast import LShift +from _ast import Lt +from _ast import LtE +from _ast import Mod +from _ast import Mult +from _ast import Name +from _ast import Not +from _ast import NotEq +from _ast import NotIn +from _ast import Or +from _ast import PyCF_ONLY_AST +from _ast import RShift +from _ast import Sub +from _ast import UAdd +from _ast import USub + + +BOOLOP_SYMBOLS = {And: "and", Or: "or"} + +BINOP_SYMBOLS = { + Add: "+", + Sub: "-", + Mult: "*", + Div: "/", + FloorDiv: "//", + Mod: "%", + LShift: "<<", + RShift: ">>", + BitOr: "|", + BitAnd: "&", + BitXor: "^", +} + +CMPOP_SYMBOLS = { + Eq: "==", + Gt: ">", + GtE: ">=", + In: "in", + Is: "is", + IsNot: "is not", + Lt: "<", + LtE: "<=", + NotEq: "!=", + NotIn: "not in", +} + +UNARYOP_SYMBOLS = {Invert: "~", Not: "not", UAdd: "+", USub: "-"} + +ALL_SYMBOLS = {} +ALL_SYMBOLS.update(BOOLOP_SYMBOLS) +ALL_SYMBOLS.update(BINOP_SYMBOLS) +ALL_SYMBOLS.update(CMPOP_SYMBOLS) +ALL_SYMBOLS.update(UNARYOP_SYMBOLS) + + +def parse(expr, filename="", mode="exec"): + """Parse an expression into an AST node.""" + return compile(expr, filename, mode, PyCF_ONLY_AST) + + +def iter_fields(node): + """Iterate over all fields of a node, only yielding existing fields.""" + + for field in node._fields: + try: + yield field, getattr(node, field) + except AttributeError: + pass + + +class NodeVisitor: + + """ + Walks the abstract syntax tree and call visitor functions for every node + found. The visitor functions may return values which will be forwarded + by the `visit` method. + + Per default the visitor functions for the nodes are ``'visit_'`` + + class name of the node. So a `TryFinally` node visit function would + be `visit_TryFinally`. This behavior can be changed by overriding + the `get_visitor` function. If no visitor function exists for a node + (return value `None`) the `generic_visit` visitor is used instead. + + Don't use the `NodeVisitor` if you want to apply changes to nodes during + traversing. For this a special visitor exists (`NodeTransformer`) that + allows modifications. + """ + + def get_visitor(self, node): + """ + Return the visitor function for this node or `None` if no visitor + exists for this node. In that case the generic visit function is + used instead. + """ + method = "visit_" + node.__class__.__name__ + return getattr(self, method, None) + + def visit(self, node): + """Visit a node.""" + f = self.get_visitor(node) + if f is not None: + return f(node) + return self.generic_visit(node) + + def generic_visit(self, node): + """Called if no explicit visitor function exists for a node.""" + for field, value in iter_fields(node): + if isinstance(value, list): + for item in value: + if isinstance(item, AST): + self.visit(item) + elif isinstance(value, AST): + self.visit(value) + + +class NodeTransformer(NodeVisitor): + + """ + Walks the abstract syntax tree and allows modifications of nodes. + + The `NodeTransformer` will walk the AST and use the return value of the + visitor functions to replace or remove the old node. If the return + value of the visitor function is `None` the node will be removed + from the previous location otherwise it's replaced with the return + value. The return value may be the original node in which case no + replacement takes place. + + Here an example transformer that rewrites all `foo` to `data['foo']`:: + + class RewriteName(NodeTransformer): + + def visit_Name(self, node): + return copy_location(Subscript( + value=Name(id='data', ctx=Load()), + slice=Index(value=Str(s=node.id)), + ctx=node.ctx + ), node) + + Keep in mind that if the node you're operating on has child nodes + you must either transform the child nodes yourself or call the generic + visit function for the node first. + + Nodes that were part of a collection of statements (that applies to + all statement nodes) may also return a list of nodes rather than just + a single node. + + Usually you use the transformer like this:: + + node = YourTransformer().visit(node) + """ + + def generic_visit(self, node): + for field, old_value in iter_fields(node): + old_value = getattr(node, field, None) + if isinstance(old_value, list): + new_values = [] + for value in old_value: + if isinstance(value, AST): + value = self.visit(value) + if value is None: + continue + elif not isinstance(value, AST): + new_values.extend(value) + continue + new_values.append(value) + old_value[:] = new_values + elif isinstance(old_value, AST): + new_node = self.visit(old_value) + if new_node is None: + delattr(node, field) + else: + setattr(node, field, new_node) + return node + + +class SourceGenerator(NodeVisitor): + + """ + This visitor is able to transform a well formed syntax tree into python + sourcecode. For more details have a look at the docstring of the + `node_to_source` function. + """ + + def __init__(self, indent_with): + self.result = [] + self.indent_with = indent_with + self.indentation = 0 + self.new_lines = 0 + + def write(self, x): + if self.new_lines: + if self.result: + self.result.append("\n" * self.new_lines) + self.result.append(self.indent_with * self.indentation) + self.new_lines = 0 + self.result.append(x) + + def newline(self, n=1): + self.new_lines = max(self.new_lines, n) + + def body(self, statements): + self.new_line = True + self.indentation += 1 + for stmt in statements: + self.visit(stmt) + self.indentation -= 1 + + def body_or_else(self, node): + self.body(node.body) + if node.orelse: + self.newline() + self.write("else:") + self.body(node.orelse) + + def signature(self, node): + want_comma = [] + + def write_comma(): + if want_comma: + self.write(", ") + else: + want_comma.append(True) + + padding = [None] * (len(node.args) - len(node.defaults)) + for arg, default in zip(node.args, padding + node.defaults): + write_comma() + self.visit(arg) + if default is not None: + self.write("=") + self.visit(default) + if node.vararg is not None: + write_comma() + self.write("*" + node.vararg.arg) + if node.kwarg is not None: + write_comma() + self.write("**" + node.kwarg.arg) + + def decorators(self, node): + for decorator in node.decorator_list: + self.newline() + self.write("@") + self.visit(decorator) + + # Statements + + def visit_Assign(self, node): + self.newline() + for idx, target in enumerate(node.targets): + if idx: + self.write(", ") + self.visit(target) + self.write(" = ") + self.visit(node.value) + + def visit_AugAssign(self, node): + self.newline() + self.visit(node.target) + self.write(BINOP_SYMBOLS[type(node.op)] + "=") + self.visit(node.value) + + def visit_ImportFrom(self, node): + self.newline() + self.write("from %s%s import " % ("." * node.level, node.module)) + for idx, item in enumerate(node.names): + if idx: + self.write(", ") + self.write(item) + + def visit_Import(self, node): + self.newline() + for item in node.names: + self.write("import ") + self.visit(item) + + def visit_Expr(self, node): + self.newline() + self.generic_visit(node) + + def visit_FunctionDef(self, node): + self.newline(n=2) + self.decorators(node) + self.newline() + self.write("def %s(" % node.name) + self.signature(node.args) + self.write("):") + self.body(node.body) + + def visit_ClassDef(self, node): + have_args = [] + + def paren_or_comma(): + if have_args: + self.write(", ") + else: + have_args.append(True) + self.write("(") + + self.newline(n=3) + self.decorators(node) + self.newline() + self.write("class %s" % node.name) + for base in node.bases: + paren_or_comma() + self.visit(base) + # XXX: the if here is used to keep this module compatible + # with python 2.6. + if hasattr(node, "keywords"): + for keyword in node.keywords: + paren_or_comma() + self.write(keyword.arg + "=") + self.visit(keyword.value) + if getattr(node, "starargs", None): + paren_or_comma() + self.write("*") + self.visit(node.starargs) + if getattr(node, "kwargs", None): + paren_or_comma() + self.write("**") + self.visit(node.kwargs) + self.write(have_args and "):" or ":") + self.body(node.body) + + def visit_If(self, node): + self.newline() + self.write("if ") + self.visit(node.test) + self.write(":") + self.body(node.body) + while True: + else_ = node.orelse + if len(else_) == 1 and isinstance(else_[0], If): + node = else_[0] + self.newline() + self.write("elif ") + self.visit(node.test) + self.write(":") + self.body(node.body) + else: + self.newline() + self.write("else:") + self.body(else_) + break + + def visit_For(self, node): + self.newline() + self.write("for ") + self.visit(node.target) + self.write(" in ") + self.visit(node.iter) + self.write(":") + self.body_or_else(node) + + def visit_While(self, node): + self.newline() + self.write("while ") + self.visit(node.test) + self.write(":") + self.body_or_else(node) + + def visit_With(self, node): + self.newline() + self.write("with ") + self.visit(node.context_expr) + if node.optional_vars is not None: + self.write(" as ") + self.visit(node.optional_vars) + self.write(":") + self.body(node.body) + + def visit_Pass(self, node): + self.newline() + self.write("pass") + + def visit_Print(self, node): + # XXX: python 2.6 only + self.newline() + self.write("print ") + want_comma = False + if node.dest is not None: + self.write(" >> ") + self.visit(node.dest) + want_comma = True + for value in node.values: + if want_comma: + self.write(", ") + self.visit(value) + want_comma = True + if not node.nl: + self.write(",") + + def visit_Delete(self, node): + self.newline() + self.write("del ") + for idx, target in enumerate(node): + if idx: + self.write(", ") + self.visit(target) + + def visit_TryExcept(self, node): + self.newline() + self.write("try:") + self.body(node.body) + for handler in node.handlers: + self.visit(handler) + + def visit_TryFinally(self, node): + self.newline() + self.write("try:") + self.body(node.body) + self.newline() + self.write("finally:") + self.body(node.finalbody) + + def visit_Global(self, node): + self.newline() + self.write("global " + ", ".join(node.names)) + + def visit_Nonlocal(self, node): + self.newline() + self.write("nonlocal " + ", ".join(node.names)) + + def visit_Return(self, node): + self.newline() + self.write("return ") + self.visit(node.value) + + def visit_Break(self, node): + self.newline() + self.write("break") + + def visit_Continue(self, node): + self.newline() + self.write("continue") + + def visit_Raise(self, node): + # XXX: Python 2.6 / 3.0 compatibility + self.newline() + self.write("raise") + if hasattr(node, "exc") and node.exc is not None: + self.write(" ") + self.visit(node.exc) + if node.cause is not None: + self.write(" from ") + self.visit(node.cause) + elif hasattr(node, "type") and node.type is not None: + self.visit(node.type) + if node.inst is not None: + self.write(", ") + self.visit(node.inst) + if node.tback is not None: + self.write(", ") + self.visit(node.tback) + + # Expressions + + def visit_Attribute(self, node): + self.visit(node.value) + self.write("." + node.attr) + + def visit_Call(self, node): + want_comma = [] + + def write_comma(): + if want_comma: + self.write(", ") + else: + want_comma.append(True) + + self.visit(node.func) + self.write("(") + for arg in node.args: + write_comma() + self.visit(arg) + for keyword in node.keywords: + write_comma() + self.write(keyword.arg + "=") + self.visit(keyword.value) + if getattr(node, "starargs", None): + write_comma() + self.write("*") + self.visit(node.starargs) + if getattr(node, "kwargs", None): + write_comma() + self.write("**") + self.visit(node.kwargs) + self.write(")") + + def visit_Name(self, node): + self.write(node.id) + + def visit_NameConstant(self, node): + self.write(str(node.value)) + + def visit_arg(self, node): + self.write(node.arg) + + def visit_Str(self, node): + self.write(repr(node.s)) + + def visit_Bytes(self, node): + self.write(repr(node.s)) + + def visit_Num(self, node): + self.write(repr(node.n)) + + # newly needed in Python 3.8 + def visit_Constant(self, node): + self.write(repr(node.value)) + + def visit_Tuple(self, node): + self.write("(") + idx = -1 + for idx, item in enumerate(node.elts): + if idx: + self.write(", ") + self.visit(item) + self.write(idx and ")" or ",)") + + def sequence_visit(left, right): + def visit(self, node): + self.write(left) + for idx, item in enumerate(node.elts): + if idx: + self.write(", ") + self.visit(item) + self.write(right) + + return visit + + visit_List = sequence_visit("[", "]") + visit_Set = sequence_visit("{", "}") + del sequence_visit + + def visit_Dict(self, node): + self.write("{") + for idx, (key, value) in enumerate(zip(node.keys, node.values)): + if idx: + self.write(", ") + self.visit(key) + self.write(": ") + self.visit(value) + self.write("}") + + def visit_BinOp(self, node): + self.write("(") + self.visit(node.left) + self.write(" %s " % BINOP_SYMBOLS[type(node.op)]) + self.visit(node.right) + self.write(")") + + def visit_BoolOp(self, node): + self.write("(") + for idx, value in enumerate(node.values): + if idx: + self.write(" %s " % BOOLOP_SYMBOLS[type(node.op)]) + self.visit(value) + self.write(")") + + def visit_Compare(self, node): + self.write("(") + self.visit(node.left) + for op, right in zip(node.ops, node.comparators): + self.write(" %s " % CMPOP_SYMBOLS[type(op)]) + self.visit(right) + self.write(")") + + def visit_UnaryOp(self, node): + self.write("(") + op = UNARYOP_SYMBOLS[type(node.op)] + self.write(op) + if op == "not": + self.write(" ") + self.visit(node.operand) + self.write(")") + + def visit_Subscript(self, node): + self.visit(node.value) + self.write("[") + self.visit(node.slice) + self.write("]") + + def visit_Slice(self, node): + if node.lower is not None: + self.visit(node.lower) + self.write(":") + if node.upper is not None: + self.visit(node.upper) + if node.step is not None: + self.write(":") + if not (isinstance(node.step, Name) and node.step.id == "None"): + self.visit(node.step) + + def visit_ExtSlice(self, node): + for idx, item in node.dims: + if idx: + self.write(", ") + self.visit(item) + + def visit_Yield(self, node): + self.write("yield ") + self.visit(node.value) + + def visit_Lambda(self, node): + self.write("lambda ") + self.signature(node.args) + self.write(": ") + self.visit(node.body) + + def visit_Ellipsis(self, node): + self.write("Ellipsis") + + def generator_visit(left, right): + def visit(self, node): + self.write(left) + self.visit(node.elt) + for comprehension in node.generators: + self.visit(comprehension) + self.write(right) + + return visit + + visit_ListComp = generator_visit("[", "]") + visit_GeneratorExp = generator_visit("(", ")") + visit_SetComp = generator_visit("{", "}") + del generator_visit + + def visit_DictComp(self, node): + self.write("{") + self.visit(node.key) + self.write(": ") + self.visit(node.value) + for comprehension in node.generators: + self.visit(comprehension) + self.write("}") + + def visit_IfExp(self, node): + self.visit(node.body) + self.write(" if ") + self.visit(node.test) + self.write(" else ") + self.visit(node.orelse) + + def visit_Starred(self, node): + self.write("*") + self.visit(node.value) + + def visit_Repr(self, node): + # XXX: python 2.6 only + self.write("`") + self.visit(node.value) + self.write("`") + + # Helper Nodes + + def visit_alias(self, node): + self.write(node.name) + if node.asname is not None: + self.write(" as " + node.asname) + + def visit_comprehension(self, node): + self.write(" for ") + self.visit(node.target) + self.write(" in ") + self.visit(node.iter) + if node.ifs: + for if_ in node.ifs: + self.write(" if ") + self.visit(if_) + + def visit_excepthandler(self, node): + self.newline() + self.write("except") + if node.type is not None: + self.write(" ") + self.visit(node.type) + if node.name is not None: + self.write(" as ") + self.visit(node.name) + self.write(":") + self.body(node.body) diff --git a/venv/lib/python3.8/site-packages/mako/ast.py b/venv/lib/python3.8/site-packages/mako/ast.py new file mode 100644 index 000000000..82cfa1166 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ast.py @@ -0,0 +1,202 @@ +# mako/ast.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""utilities for analyzing expressions and blocks of Python +code, as well as generating Python from AST nodes""" + +import re + +from mako import exceptions +from mako import pyparser + + +class PythonCode: + + """represents information about a string containing Python code""" + + def __init__(self, code, **exception_kwargs): + self.code = code + + # represents all identifiers which are assigned to at some point in + # the code + self.declared_identifiers = set() + + # represents all identifiers which are referenced before their + # assignment, if any + self.undeclared_identifiers = set() + + # note that an identifier can be in both the undeclared and declared + # lists. + + # using AST to parse instead of using code.co_varnames, + # code.co_names has several advantages: + # - we can locate an identifier as "undeclared" even if + # its declared later in the same block of code + # - AST is less likely to break with version changes + # (for example, the behavior of co_names changed a little bit + # in python version 2.5) + if isinstance(code, str): + expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs) + else: + expr = code + + f = pyparser.FindIdentifiers(self, **exception_kwargs) + f.visit(expr) + + +class ArgumentList: + + """parses a fragment of code as a comma-separated list of expressions""" + + def __init__(self, code, **exception_kwargs): + self.codeargs = [] + self.args = [] + self.declared_identifiers = set() + self.undeclared_identifiers = set() + if isinstance(code, str): + if re.match(r"\S", code) and not re.match(r",\s*$", code): + # if theres text and no trailing comma, insure its parsed + # as a tuple by adding a trailing comma + code += "," + expr = pyparser.parse(code, "exec", **exception_kwargs) + else: + expr = code + + f = pyparser.FindTuple(self, PythonCode, **exception_kwargs) + f.visit(expr) + + +class PythonFragment(PythonCode): + + """extends PythonCode to provide identifier lookups in partial control + statements + + e.g.:: + + for x in 5: + elif y==9: + except (MyException, e): + + """ + + def __init__(self, code, **exception_kwargs): + m = re.match(r"^(\w+)(?:\s+(.*?))?:\s*(#|$)", code.strip(), re.S) + if not m: + raise exceptions.CompileException( + "Fragment '%s' is not a partial control statement" % code, + **exception_kwargs, + ) + if m.group(3): + code = code[: m.start(3)] + (keyword, expr) = m.group(1, 2) + if keyword in ["for", "if", "while"]: + code = code + "pass" + elif keyword == "try": + code = code + "pass\nexcept:pass" + elif keyword in ["elif", "else"]: + code = "if False:pass\n" + code + "pass" + elif keyword == "except": + code = "try:pass\n" + code + "pass" + elif keyword == "with": + code = code + "pass" + else: + raise exceptions.CompileException( + "Unsupported control keyword: '%s'" % keyword, + **exception_kwargs, + ) + super().__init__(code, **exception_kwargs) + + +class FunctionDecl: + + """function declaration""" + + def __init__(self, code, allow_kwargs=True, **exception_kwargs): + self.code = code + expr = pyparser.parse(code, "exec", **exception_kwargs) + + f = pyparser.ParseFunc(self, **exception_kwargs) + f.visit(expr) + if not hasattr(self, "funcname"): + raise exceptions.CompileException( + "Code '%s' is not a function declaration" % code, + **exception_kwargs, + ) + if not allow_kwargs and self.kwargs: + raise exceptions.CompileException( + "'**%s' keyword argument not allowed here" + % self.kwargnames[-1], + **exception_kwargs, + ) + + def get_argument_expressions(self, as_call=False): + """Return the argument declarations of this FunctionDecl as a printable + list. + + By default the return value is appropriate for writing in a ``def``; + set `as_call` to true to build arguments to be passed to the function + instead (assuming locals with the same names as the arguments exist). + """ + + namedecls = [] + + # Build in reverse order, since defaults and slurpy args come last + argnames = self.argnames[::-1] + kwargnames = self.kwargnames[::-1] + defaults = self.defaults[::-1] + kwdefaults = self.kwdefaults[::-1] + + # Named arguments + if self.kwargs: + namedecls.append("**" + kwargnames.pop(0)) + + for name in kwargnames: + # Keyword-only arguments must always be used by name, so even if + # this is a call, print out `foo=foo` + if as_call: + namedecls.append("%s=%s" % (name, name)) + elif kwdefaults: + default = kwdefaults.pop(0) + if default is None: + # The AST always gives kwargs a default, since you can do + # `def foo(*, a=1, b, c=3)` + namedecls.append(name) + else: + namedecls.append( + "%s=%s" + % (name, pyparser.ExpressionGenerator(default).value()) + ) + else: + namedecls.append(name) + + # Positional arguments + if self.varargs: + namedecls.append("*" + argnames.pop(0)) + + for name in argnames: + if as_call or not defaults: + namedecls.append(name) + else: + default = defaults.pop(0) + namedecls.append( + "%s=%s" + % (name, pyparser.ExpressionGenerator(default).value()) + ) + + namedecls.reverse() + return namedecls + + @property + def allargnames(self): + return tuple(self.argnames) + tuple(self.kwargnames) + + +class FunctionArgs(FunctionDecl): + + """the argument portion of a function declaration""" + + def __init__(self, code, **kwargs): + super().__init__("def ANON(%s):pass" % code, **kwargs) diff --git a/venv/lib/python3.8/site-packages/mako/cache.py b/venv/lib/python3.8/site-packages/mako/cache.py new file mode 100644 index 000000000..267411c33 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/cache.py @@ -0,0 +1,239 @@ +# mako/cache.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from mako import util + +_cache_plugins = util.PluginLoader("mako.cache") + +register_plugin = _cache_plugins.register +register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl") + + +class Cache: + + """Represents a data content cache made available to the module + space of a specific :class:`.Template` object. + + .. versionadded:: 0.6 + :class:`.Cache` by itself is mostly a + container for a :class:`.CacheImpl` object, which implements + a fixed API to provide caching services; specific subclasses exist to + implement different + caching strategies. Mako includes a backend that works with + the Beaker caching system. Beaker itself then supports + a number of backends (i.e. file, memory, memcached, etc.) + + The construction of a :class:`.Cache` is part of the mechanics + of a :class:`.Template`, and programmatic access to this + cache is typically via the :attr:`.Template.cache` attribute. + + """ + + impl = None + """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`. + + This accessor allows a :class:`.CacheImpl` with additional + methods beyond that of :class:`.Cache` to be used programmatically. + + """ + + id = None + """Return the 'id' that identifies this cache. + + This is a value that should be globally unique to the + :class:`.Template` associated with this cache, and can + be used by a caching system to name a local container + for data specific to this template. + + """ + + starttime = None + """Epochal time value for when the owning :class:`.Template` was + first compiled. + + A cache implementation may wish to invalidate data earlier than + this timestamp; this has the effect of the cache for a specific + :class:`.Template` starting clean any time the :class:`.Template` + is recompiled, such as when the original template file changed on + the filesystem. + + """ + + def __init__(self, template, *args): + # check for a stale template calling the + # constructor + if isinstance(template, str) and args: + return + self.template = template + self.id = template.module.__name__ + self.starttime = template.module._modified_time + self._def_regions = {} + self.impl = self._load_impl(self.template.cache_impl) + + def _load_impl(self, name): + return _cache_plugins.load(name)(self) + + def get_or_create(self, key, creation_function, **kw): + """Retrieve a value from the cache, using the given creation function + to generate a new value.""" + + return self._ctx_get_or_create(key, creation_function, None, **kw) + + def _ctx_get_or_create(self, key, creation_function, context, **kw): + """Retrieve a value from the cache, using the given creation function + to generate a new value.""" + + if not self.template.cache_enabled: + return creation_function() + + return self.impl.get_or_create( + key, creation_function, **self._get_cache_kw(kw, context) + ) + + def set(self, key, value, **kw): + r"""Place a value in the cache. + + :param key: the value's key. + :param value: the value. + :param \**kw: cache configuration arguments. + + """ + + self.impl.set(key, value, **self._get_cache_kw(kw, None)) + + put = set + """A synonym for :meth:`.Cache.set`. + + This is here for backwards compatibility. + + """ + + def get(self, key, **kw): + r"""Retrieve a value from the cache. + + :param key: the value's key. + :param \**kw: cache configuration arguments. The + backend is configured using these arguments upon first request. + Subsequent requests that use the same series of configuration + values will use that same backend. + + """ + return self.impl.get(key, **self._get_cache_kw(kw, None)) + + def invalidate(self, key, **kw): + r"""Invalidate a value in the cache. + + :param key: the value's key. + :param \**kw: cache configuration arguments. The + backend is configured using these arguments upon first request. + Subsequent requests that use the same series of configuration + values will use that same backend. + + """ + self.impl.invalidate(key, **self._get_cache_kw(kw, None)) + + def invalidate_body(self): + """Invalidate the cached content of the "body" method for this + template. + + """ + self.invalidate("render_body", __M_defname="render_body") + + def invalidate_def(self, name): + """Invalidate the cached content of a particular ``<%def>`` within this + template. + + """ + + self.invalidate("render_%s" % name, __M_defname="render_%s" % name) + + def invalidate_closure(self, name): + """Invalidate a nested ``<%def>`` within this template. + + Caching of nested defs is a blunt tool as there is no + management of scope -- nested defs that use cache tags + need to have names unique of all other nested defs in the + template, else their content will be overwritten by + each other. + + """ + + self.invalidate(name, __M_defname=name) + + def _get_cache_kw(self, kw, context): + defname = kw.pop("__M_defname", None) + if not defname: + tmpl_kw = self.template.cache_args.copy() + tmpl_kw.update(kw) + elif defname in self._def_regions: + tmpl_kw = self._def_regions[defname] + else: + tmpl_kw = self.template.cache_args.copy() + tmpl_kw.update(kw) + self._def_regions[defname] = tmpl_kw + if context and self.impl.pass_context: + tmpl_kw = tmpl_kw.copy() + tmpl_kw.setdefault("context", context) + return tmpl_kw + + +class CacheImpl: + + """Provide a cache implementation for use by :class:`.Cache`.""" + + def __init__(self, cache): + self.cache = cache + + pass_context = False + """If ``True``, the :class:`.Context` will be passed to + :meth:`get_or_create <.CacheImpl.get_or_create>` as the name ``'context'``. + """ + + def get_or_create(self, key, creation_function, **kw): + r"""Retrieve a value from the cache, using the given creation function + to generate a new value. + + This function *must* return a value, either from + the cache, or via the given creation function. + If the creation function is called, the newly + created value should be populated into the cache + under the given key before being returned. + + :param key: the value's key. + :param creation_function: function that when called generates + a new value. + :param \**kw: cache configuration arguments. + + """ + raise NotImplementedError() + + def set(self, key, value, **kw): + r"""Place a value in the cache. + + :param key: the value's key. + :param value: the value. + :param \**kw: cache configuration arguments. + + """ + raise NotImplementedError() + + def get(self, key, **kw): + r"""Retrieve a value from the cache. + + :param key: the value's key. + :param \**kw: cache configuration arguments. + + """ + raise NotImplementedError() + + def invalidate(self, key, **kw): + r"""Invalidate a value in the cache. + + :param key: the value's key. + :param \**kw: cache configuration arguments. + + """ + raise NotImplementedError() diff --git a/venv/lib/python3.8/site-packages/mako/cmd.py b/venv/lib/python3.8/site-packages/mako/cmd.py new file mode 100644 index 000000000..3fff197ba --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/cmd.py @@ -0,0 +1,99 @@ +# mako/cmd.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php +from argparse import ArgumentParser +from os.path import dirname +from os.path import isfile +import sys + +from mako import exceptions +from mako.lookup import TemplateLookup +from mako.template import Template + + +def varsplit(var): + if "=" not in var: + return (var, "") + return var.split("=", 1) + + +def _exit(): + sys.stderr.write(exceptions.text_error_template().render()) + sys.exit(1) + + +def cmdline(argv=None): + parser = ArgumentParser() + parser.add_argument( + "--var", + default=[], + action="append", + help="variable (can be used multiple times, use name=value)", + ) + parser.add_argument( + "--template-dir", + default=[], + action="append", + help="Directory to use for template lookup (multiple " + "directories may be provided). If not given then if the " + "template is read from stdin, the value defaults to be " + "the current directory, otherwise it defaults to be the " + "parent directory of the file provided.", + ) + parser.add_argument( + "--output-encoding", default=None, help="force output encoding" + ) + parser.add_argument( + "--output-file", + default=None, + help="Write to file upon successful render instead of stdout", + ) + parser.add_argument("input", nargs="?", default="-") + + options = parser.parse_args(argv) + + output_encoding = options.output_encoding + output_file = options.output_file + + if options.input == "-": + lookup_dirs = options.template_dir or ["."] + lookup = TemplateLookup(lookup_dirs) + try: + template = Template( + sys.stdin.read(), + lookup=lookup, + output_encoding=output_encoding, + ) + except: + _exit() + else: + filename = options.input + if not isfile(filename): + raise SystemExit("error: can't find %s" % filename) + lookup_dirs = options.template_dir or [dirname(filename)] + lookup = TemplateLookup(lookup_dirs) + try: + template = Template( + filename=filename, + lookup=lookup, + output_encoding=output_encoding, + ) + except: + _exit() + + kw = dict(varsplit(var) for var in options.var) + try: + rendered = template.render(**kw) + except: + _exit() + else: + if output_file: + open(output_file, "wt", encoding=output_encoding).write(rendered) + else: + sys.stdout.write(rendered) + + +if __name__ == "__main__": + cmdline() diff --git a/venv/lib/python3.8/site-packages/mako/codegen.py b/venv/lib/python3.8/site-packages/mako/codegen.py new file mode 100644 index 000000000..b5293f475 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/codegen.py @@ -0,0 +1,1319 @@ +# mako/codegen.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""provides functionality for rendering a parsetree constructing into module +source code.""" + +import json +import re +import time + +from mako import ast +from mako import exceptions +from mako import filters +from mako import parsetree +from mako import util +from mako.pygen import PythonPrinter + + +MAGIC_NUMBER = 10 + +# names which are hardwired into the +# template and are not accessed via the +# context itself +TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"} +RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED) + + +def compile( # noqa + node, + uri, + filename=None, + default_filters=None, + buffer_filters=None, + imports=None, + future_imports=None, + source_encoding=None, + generate_magic_comment=True, + strict_undefined=False, + enable_loop=True, + reserved_names=frozenset(), +): + """Generate module source code given a parsetree node, + uri, and optional source filename""" + + buf = util.FastEncodingBuffer() + + printer = PythonPrinter(buf) + _GenerateRenderMethod( + printer, + _CompileContext( + uri, + filename, + default_filters, + buffer_filters, + imports, + future_imports, + source_encoding, + generate_magic_comment, + strict_undefined, + enable_loop, + reserved_names, + ), + node, + ) + return buf.getvalue() + + +class _CompileContext: + def __init__( + self, + uri, + filename, + default_filters, + buffer_filters, + imports, + future_imports, + source_encoding, + generate_magic_comment, + strict_undefined, + enable_loop, + reserved_names, + ): + self.uri = uri + self.filename = filename + self.default_filters = default_filters + self.buffer_filters = buffer_filters + self.imports = imports + self.future_imports = future_imports + self.source_encoding = source_encoding + self.generate_magic_comment = generate_magic_comment + self.strict_undefined = strict_undefined + self.enable_loop = enable_loop + self.reserved_names = reserved_names + + +class _GenerateRenderMethod: + + """A template visitor object which generates the + full module source for a template. + + """ + + def __init__(self, printer, compiler, node): + self.printer = printer + self.compiler = compiler + self.node = node + self.identifier_stack = [None] + self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag)) + + if self.in_def: + name = "render_%s" % node.funcname + args = node.get_argument_expressions() + filtered = len(node.filter_args.args) > 0 + buffered = eval(node.attributes.get("buffered", "False")) + cached = eval(node.attributes.get("cached", "False")) + defs = None + pagetag = None + if node.is_block and not node.is_anonymous: + args += ["**pageargs"] + else: + defs = self.write_toplevel() + pagetag = self.compiler.pagetag + name = "render_body" + if pagetag is not None: + args = pagetag.body_decl.get_argument_expressions() + if not pagetag.body_decl.kwargs: + args += ["**pageargs"] + cached = eval(pagetag.attributes.get("cached", "False")) + self.compiler.enable_loop = self.compiler.enable_loop or eval( + pagetag.attributes.get("enable_loop", "False") + ) + else: + args = ["**pageargs"] + cached = False + buffered = filtered = False + if args is None: + args = ["context"] + else: + args = [a for a in ["context"] + args] + + self.write_render_callable( + pagetag or node, name, args, buffered, filtered, cached + ) + + if defs is not None: + for node in defs: + _GenerateRenderMethod(printer, compiler, node) + + if not self.in_def: + self.write_metadata_struct() + + def write_metadata_struct(self): + self.printer.source_map[self.printer.lineno] = max( + self.printer.source_map + ) + struct = { + "filename": self.compiler.filename, + "uri": self.compiler.uri, + "source_encoding": self.compiler.source_encoding, + "line_map": self.printer.source_map, + } + self.printer.writelines( + '"""', + "__M_BEGIN_METADATA", + json.dumps(struct), + "__M_END_METADATA\n" '"""', + ) + + @property + def identifiers(self): + return self.identifier_stack[-1] + + def write_toplevel(self): + """Traverse a template structure for module-level directives and + generate the start of module-level code. + + """ + inherit = [] + namespaces = {} + module_code = [] + + self.compiler.pagetag = None + + class FindTopLevel: + def visitInheritTag(s, node): + inherit.append(node) + + def visitNamespaceTag(s, node): + namespaces[node.name] = node + + def visitPageTag(s, node): + self.compiler.pagetag = node + + def visitCode(s, node): + if node.ismodule: + module_code.append(node) + + f = FindTopLevel() + for n in self.node.nodes: + n.accept_visitor(f) + + self.compiler.namespaces = namespaces + + module_ident = set() + for n in module_code: + module_ident = module_ident.union(n.declared_identifiers()) + + module_identifiers = _Identifiers(self.compiler) + module_identifiers.declared = module_ident + + # module-level names, python code + if ( + self.compiler.generate_magic_comment + and self.compiler.source_encoding + ): + self.printer.writeline( + "# -*- coding:%s -*-" % self.compiler.source_encoding + ) + + if self.compiler.future_imports: + self.printer.writeline( + "from __future__ import %s" + % (", ".join(self.compiler.future_imports),) + ) + self.printer.writeline("from mako import runtime, filters, cache") + self.printer.writeline("UNDEFINED = runtime.UNDEFINED") + self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING") + self.printer.writeline("__M_dict_builtin = dict") + self.printer.writeline("__M_locals_builtin = locals") + self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER) + self.printer.writeline("_modified_time = %r" % time.time()) + self.printer.writeline("_enable_loop = %r" % self.compiler.enable_loop) + self.printer.writeline( + "_template_filename = %r" % self.compiler.filename + ) + self.printer.writeline("_template_uri = %r" % self.compiler.uri) + self.printer.writeline( + "_source_encoding = %r" % self.compiler.source_encoding + ) + if self.compiler.imports: + buf = "" + for imp in self.compiler.imports: + buf += imp + "\n" + self.printer.writeline(imp) + impcode = ast.PythonCode( + buf, + source="", + lineno=0, + pos=0, + filename="template defined imports", + ) + else: + impcode = None + + main_identifiers = module_identifiers.branch(self.node) + mit = module_identifiers.topleveldefs + module_identifiers.topleveldefs = mit.union( + main_identifiers.topleveldefs + ) + module_identifiers.declared.update(TOPLEVEL_DECLARED) + if impcode: + module_identifiers.declared.update(impcode.declared_identifiers) + + self.compiler.identifiers = module_identifiers + self.printer.writeline( + "_exports = %r" + % [n.name for n in main_identifiers.topleveldefs.values()] + ) + self.printer.write_blanks(2) + + if len(module_code): + self.write_module_code(module_code) + + if len(inherit): + self.write_namespaces(namespaces) + self.write_inherit(inherit[-1]) + elif len(namespaces): + self.write_namespaces(namespaces) + + return list(main_identifiers.topleveldefs.values()) + + def write_render_callable( + self, node, name, args, buffered, filtered, cached + ): + """write a top-level render callable. + + this could be the main render() method or that of a top-level def.""" + + if self.in_def: + decorator = node.decorator + if decorator: + self.printer.writeline( + "@runtime._decorate_toplevel(%s)" % decorator + ) + + self.printer.start_source(node.lineno) + self.printer.writelines( + "def %s(%s):" % (name, ",".join(args)), + # push new frame, assign current frame to __M_caller + "__M_caller = context.caller_stack._push_frame()", + "try:", + ) + if buffered or filtered or cached: + self.printer.writeline("context._push_buffer()") + + self.identifier_stack.append( + self.compiler.identifiers.branch(self.node) + ) + if (not self.in_def or self.node.is_block) and "**pageargs" in args: + self.identifier_stack[-1].argument_declared.add("pageargs") + + if not self.in_def and ( + len(self.identifiers.locally_assigned) > 0 + or len(self.identifiers.argument_declared) > 0 + ): + self.printer.writeline( + "__M_locals = __M_dict_builtin(%s)" + % ",".join( + [ + "%s=%s" % (x, x) + for x in self.identifiers.argument_declared + ] + ) + ) + + self.write_variable_declares(self.identifiers, toplevel=True) + + for n in self.node.nodes: + n.accept_visitor(self) + + self.write_def_finish(self.node, buffered, filtered, cached) + self.printer.writeline(None) + self.printer.write_blanks(2) + if cached: + self.write_cache_decorator( + node, name, args, buffered, self.identifiers, toplevel=True + ) + + def write_module_code(self, module_code): + """write module-level template code, i.e. that which + is enclosed in <%! %> tags in the template.""" + for n in module_code: + self.printer.write_indented_block(n.text, starting_lineno=n.lineno) + + def write_inherit(self, node): + """write the module-level inheritance-determination callable.""" + + self.printer.writelines( + "def _mako_inherit(template, context):", + "_mako_generate_namespaces(context)", + "return runtime._inherit_from(context, %s, _template_uri)" + % (node.parsed_attributes["file"]), + None, + ) + + def write_namespaces(self, namespaces): + """write the module-level namespace-generating callable.""" + self.printer.writelines( + "def _mako_get_namespace(context, name):", + "try:", + "return context.namespaces[(__name__, name)]", + "except KeyError:", + "_mako_generate_namespaces(context)", + "return context.namespaces[(__name__, name)]", + None, + None, + ) + self.printer.writeline("def _mako_generate_namespaces(context):") + + for node in namespaces.values(): + if "import" in node.attributes: + self.compiler.has_ns_imports = True + self.printer.start_source(node.lineno) + if len(node.nodes): + self.printer.writeline("def make_namespace():") + export = [] + identifiers = self.compiler.identifiers.branch(node) + self.in_def = True + + class NSDefVisitor: + def visitDefTag(s, node): + s.visitDefOrBase(node) + + def visitBlockTag(s, node): + s.visitDefOrBase(node) + + def visitDefOrBase(s, node): + if node.is_anonymous: + raise exceptions.CompileException( + "Can't put anonymous blocks inside " + "<%namespace>", + **node.exception_kwargs, + ) + self.write_inline_def(node, identifiers, nested=False) + export.append(node.funcname) + + vis = NSDefVisitor() + for n in node.nodes: + n.accept_visitor(vis) + self.printer.writeline("return [%s]" % (",".join(export))) + self.printer.writeline(None) + self.in_def = False + callable_name = "make_namespace()" + else: + callable_name = "None" + + if "file" in node.parsed_attributes: + self.printer.writeline( + "ns = runtime.TemplateNamespace(%r," + " context._clean_inheritance_tokens()," + " templateuri=%s, callables=%s, " + " calling_uri=_template_uri)" + % ( + node.name, + node.parsed_attributes.get("file", "None"), + callable_name, + ) + ) + elif "module" in node.parsed_attributes: + self.printer.writeline( + "ns = runtime.ModuleNamespace(%r," + " context._clean_inheritance_tokens()," + " callables=%s, calling_uri=_template_uri," + " module=%s)" + % ( + node.name, + callable_name, + node.parsed_attributes.get("module", "None"), + ) + ) + else: + self.printer.writeline( + "ns = runtime.Namespace(%r," + " context._clean_inheritance_tokens()," + " callables=%s, calling_uri=_template_uri)" + % (node.name, callable_name) + ) + if eval(node.attributes.get("inheritable", "False")): + self.printer.writeline("context['self'].%s = ns" % (node.name)) + + self.printer.writeline( + "context.namespaces[(__name__, %s)] = ns" % repr(node.name) + ) + self.printer.write_blanks(1) + if not len(namespaces): + self.printer.writeline("pass") + self.printer.writeline(None) + + def write_variable_declares(self, identifiers, toplevel=False, limit=None): + """write variable declarations at the top of a function. + + the variable declarations are in the form of callable + definitions for defs and/or name lookup within the + function's context argument. the names declared are based + on the names that are referenced in the function body, + which don't otherwise have any explicit assignment + operation. names that are assigned within the body are + assumed to be locally-scoped variables and are not + separately declared. + + for def callable definitions, if the def is a top-level + callable then a 'stub' callable is generated which wraps + the current Context into a closure. if the def is not + top-level, it is fully rendered as a local closure. + + """ + + # collection of all defs available to us in this scope + comp_idents = {c.funcname: c for c in identifiers.defs} + to_write = set() + + # write "context.get()" for all variables we are going to + # need that arent in the namespace yet + to_write = to_write.union(identifiers.undeclared) + + # write closure functions for closures that we define + # right here + to_write = to_write.union( + [c.funcname for c in identifiers.closuredefs.values()] + ) + + # remove identifiers that are declared in the argument + # signature of the callable + to_write = to_write.difference(identifiers.argument_declared) + + # remove identifiers that we are going to assign to. + # in this way we mimic Python's behavior, + # i.e. assignment to a variable within a block + # means that variable is now a "locally declared" var, + # which cannot be referenced beforehand. + to_write = to_write.difference(identifiers.locally_declared) + + if self.compiler.enable_loop: + has_loop = "loop" in to_write + to_write.discard("loop") + else: + has_loop = False + + # if a limiting set was sent, constraint to those items in that list + # (this is used for the caching decorator) + if limit is not None: + to_write = to_write.intersection(limit) + + if toplevel and getattr(self.compiler, "has_ns_imports", False): + self.printer.writeline("_import_ns = {}") + self.compiler.has_imports = True + for ident, ns in self.compiler.namespaces.items(): + if "import" in ns.attributes: + self.printer.writeline( + "_mako_get_namespace(context, %r)." + "_populate(_import_ns, %r)" + % ( + ident, + re.split(r"\s*,\s*", ns.attributes["import"]), + ) + ) + + if has_loop: + self.printer.writeline("loop = __M_loop = runtime.LoopStack()") + + for ident in to_write: + if ident in comp_idents: + comp = comp_idents[ident] + if comp.is_block: + if not comp.is_anonymous: + self.write_def_decl(comp, identifiers) + else: + self.write_inline_def(comp, identifiers, nested=True) + else: + if comp.is_root(): + self.write_def_decl(comp, identifiers) + else: + self.write_inline_def(comp, identifiers, nested=True) + + elif ident in self.compiler.namespaces: + self.printer.writeline( + "%s = _mako_get_namespace(context, %r)" % (ident, ident) + ) + else: + if getattr(self.compiler, "has_ns_imports", False): + if self.compiler.strict_undefined: + self.printer.writelines( + "%s = _import_ns.get(%r, UNDEFINED)" + % (ident, ident), + "if %s is UNDEFINED:" % ident, + "try:", + "%s = context[%r]" % (ident, ident), + "except KeyError:", + "raise NameError(\"'%s' is not defined\")" % ident, + None, + None, + ) + else: + self.printer.writeline( + "%s = _import_ns.get" + "(%r, context.get(%r, UNDEFINED))" + % (ident, ident, ident) + ) + else: + if self.compiler.strict_undefined: + self.printer.writelines( + "try:", + "%s = context[%r]" % (ident, ident), + "except KeyError:", + "raise NameError(\"'%s' is not defined\")" % ident, + None, + ) + else: + self.printer.writeline( + "%s = context.get(%r, UNDEFINED)" % (ident, ident) + ) + + self.printer.writeline("__M_writer = context.writer()") + + def write_def_decl(self, node, identifiers): + """write a locally-available callable referencing a top-level def""" + funcname = node.funcname + namedecls = node.get_argument_expressions() + nameargs = node.get_argument_expressions(as_call=True) + + if not self.in_def and ( + len(self.identifiers.locally_assigned) > 0 + or len(self.identifiers.argument_declared) > 0 + ): + nameargs.insert(0, "context._locals(__M_locals)") + else: + nameargs.insert(0, "context") + self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls))) + self.printer.writeline( + "return render_%s(%s)" % (funcname, ",".join(nameargs)) + ) + self.printer.writeline(None) + + def write_inline_def(self, node, identifiers, nested): + """write a locally-available def callable inside an enclosing def.""" + + namedecls = node.get_argument_expressions() + + decorator = node.decorator + if decorator: + self.printer.writeline( + "@runtime._decorate_inline(context, %s)" % decorator + ) + self.printer.writeline( + "def %s(%s):" % (node.funcname, ",".join(namedecls)) + ) + filtered = len(node.filter_args.args) > 0 + buffered = eval(node.attributes.get("buffered", "False")) + cached = eval(node.attributes.get("cached", "False")) + self.printer.writelines( + # push new frame, assign current frame to __M_caller + "__M_caller = context.caller_stack._push_frame()", + "try:", + ) + if buffered or filtered or cached: + self.printer.writelines("context._push_buffer()") + + identifiers = identifiers.branch(node, nested=nested) + + self.write_variable_declares(identifiers) + + self.identifier_stack.append(identifiers) + for n in node.nodes: + n.accept_visitor(self) + self.identifier_stack.pop() + + self.write_def_finish(node, buffered, filtered, cached) + self.printer.writeline(None) + if cached: + self.write_cache_decorator( + node, + node.funcname, + namedecls, + False, + identifiers, + inline=True, + toplevel=False, + ) + + def write_def_finish( + self, node, buffered, filtered, cached, callstack=True + ): + """write the end section of a rendering function, either outermost or + inline. + + this takes into account if the rendering function was filtered, + buffered, etc. and closes the corresponding try: block if any, and + writes code to retrieve captured content, apply filters, send proper + return value.""" + + if not buffered and not cached and not filtered: + self.printer.writeline("return ''") + if callstack: + self.printer.writelines( + "finally:", "context.caller_stack._pop_frame()", None + ) + + if buffered or filtered or cached: + if buffered or cached: + # in a caching scenario, don't try to get a writer + # from the context after popping; assume the caching + # implemenation might be using a context with no + # extra buffers + self.printer.writelines( + "finally:", "__M_buf = context._pop_buffer()" + ) + else: + self.printer.writelines( + "finally:", + "__M_buf, __M_writer = context._pop_buffer_and_writer()", + ) + + if callstack: + self.printer.writeline("context.caller_stack._pop_frame()") + + s = "__M_buf.getvalue()" + if filtered: + s = self.create_filter_callable( + node.filter_args.args, s, False + ) + self.printer.writeline(None) + if buffered and not cached: + s = self.create_filter_callable( + self.compiler.buffer_filters, s, False + ) + if buffered or cached: + self.printer.writeline("return %s" % s) + else: + self.printer.writelines("__M_writer(%s)" % s, "return ''") + + def write_cache_decorator( + self, + node_or_pagetag, + name, + args, + buffered, + identifiers, + inline=False, + toplevel=False, + ): + """write a post-function decorator to replace a rendering + callable with a cached version of itself.""" + + self.printer.writeline("__M_%s = %s" % (name, name)) + cachekey = node_or_pagetag.parsed_attributes.get( + "cache_key", repr(name) + ) + + cache_args = {} + if self.compiler.pagetag is not None: + cache_args.update( + (pa[6:], self.compiler.pagetag.parsed_attributes[pa]) + for pa in self.compiler.pagetag.parsed_attributes + if pa.startswith("cache_") and pa != "cache_key" + ) + cache_args.update( + (pa[6:], node_or_pagetag.parsed_attributes[pa]) + for pa in node_or_pagetag.parsed_attributes + if pa.startswith("cache_") and pa != "cache_key" + ) + if "timeout" in cache_args: + cache_args["timeout"] = int(eval(cache_args["timeout"])) + + self.printer.writeline("def %s(%s):" % (name, ",".join(args))) + + # form "arg1, arg2, arg3=arg3, arg4=arg4", etc. + pass_args = [ + "%s=%s" % ((a.split("=")[0],) * 2) if "=" in a else a for a in args + ] + + self.write_variable_declares( + identifiers, + toplevel=toplevel, + limit=node_or_pagetag.undeclared_identifiers(), + ) + if buffered: + s = ( + "context.get('local')." + "cache._ctx_get_or_create(" + "%s, lambda:__M_%s(%s), context, %s__M_defname=%r)" + % ( + cachekey, + name, + ",".join(pass_args), + "".join( + ["%s=%s, " % (k, v) for k, v in cache_args.items()] + ), + name, + ) + ) + # apply buffer_filters + s = self.create_filter_callable( + self.compiler.buffer_filters, s, False + ) + self.printer.writelines("return " + s, None) + else: + self.printer.writelines( + "__M_writer(context.get('local')." + "cache._ctx_get_or_create(" + "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))" + % ( + cachekey, + name, + ",".join(pass_args), + "".join( + ["%s=%s, " % (k, v) for k, v in cache_args.items()] + ), + name, + ), + "return ''", + None, + ) + + def create_filter_callable(self, args, target, is_expression): + """write a filter-applying expression based on the filters + present in the given filter names, adjusting for the global + 'default' filter aliases as needed.""" + + def locate_encode(name): + if re.match(r"decode\..+", name): + return "filters." + name + else: + return filters.DEFAULT_ESCAPES.get(name, name) + + if "n" not in args: + if is_expression: + if self.compiler.pagetag: + args = self.compiler.pagetag.filter_args.args + args + if self.compiler.default_filters and "n" not in args: + args = self.compiler.default_filters + args + for e in args: + # if filter given as a function, get just the identifier portion + if e == "n": + continue + m = re.match(r"(.+?)(\(.*\))", e) + if m: + ident, fargs = m.group(1, 2) + f = locate_encode(ident) + e = f + fargs + else: + e = locate_encode(e) + assert e is not None + target = "%s(%s)" % (e, target) + return target + + def visitExpression(self, node): + self.printer.start_source(node.lineno) + if ( + len(node.escapes) + or ( + self.compiler.pagetag is not None + and len(self.compiler.pagetag.filter_args.args) + ) + or len(self.compiler.default_filters) + ): + s = self.create_filter_callable( + node.escapes_code.args, "%s" % node.text, True + ) + self.printer.writeline("__M_writer(%s)" % s) + else: + self.printer.writeline("__M_writer(%s)" % node.text) + + def visitControlLine(self, node): + if node.isend: + self.printer.writeline(None) + if node.has_loop_context: + self.printer.writeline("finally:") + self.printer.writeline("loop = __M_loop._exit()") + self.printer.writeline(None) + else: + self.printer.start_source(node.lineno) + if self.compiler.enable_loop and node.keyword == "for": + text = mangle_mako_loop(node, self.printer) + else: + text = node.text + self.printer.writeline(text) + children = node.get_children() + + # this covers the four situations where we want to insert a pass: + # 1) a ternary control line with no children, + # 2) a primary control line with nothing but its own ternary + # and end control lines, and + # 3) any control line with no content other than comments + # 4) the first control block with no content other than comments + def _search_for_control_line(): + for c in children: + if isinstance(c, parsetree.Comment): + continue + elif isinstance(c, parsetree.ControlLine): + return True + return False + + if ( + not children + or all( + isinstance(c, (parsetree.Comment, parsetree.ControlLine)) + for c in children + ) + and all( + (node.is_ternary(c.keyword) or c.isend) + for c in children + if isinstance(c, parsetree.ControlLine) + ) + or _search_for_control_line() + ): + self.printer.writeline("pass") + + def visitText(self, node): + self.printer.start_source(node.lineno) + self.printer.writeline("__M_writer(%s)" % repr(node.content)) + + def visitTextTag(self, node): + filtered = len(node.filter_args.args) > 0 + if filtered: + self.printer.writelines( + "__M_writer = context._push_writer()", "try:" + ) + for n in node.nodes: + n.accept_visitor(self) + if filtered: + self.printer.writelines( + "finally:", + "__M_buf, __M_writer = context._pop_buffer_and_writer()", + "__M_writer(%s)" + % self.create_filter_callable( + node.filter_args.args, "__M_buf.getvalue()", False + ), + None, + ) + + def visitCode(self, node): + if not node.ismodule: + self.printer.write_indented_block( + node.text, starting_lineno=node.lineno + ) + + if not self.in_def and len(self.identifiers.locally_assigned) > 0: + # if we are the "template" def, fudge locally + # declared/modified variables into the "__M_locals" dictionary, + # which is used for def calls within the same template, + # to simulate "enclosing scope" + self.printer.writeline( + "__M_locals_builtin_stored = __M_locals_builtin()" + ) + self.printer.writeline( + "__M_locals.update(__M_dict_builtin([(__M_key," + " __M_locals_builtin_stored[__M_key]) for __M_key in" + " [%s] if __M_key in __M_locals_builtin_stored]))" + % ",".join([repr(x) for x in node.declared_identifiers()]) + ) + + def visitIncludeTag(self, node): + self.printer.start_source(node.lineno) + args = node.attributes.get("args") + if args: + self.printer.writeline( + "runtime._include_file(context, %s, _template_uri, %s)" + % (node.parsed_attributes["file"], args) + ) + else: + self.printer.writeline( + "runtime._include_file(context, %s, _template_uri)" + % (node.parsed_attributes["file"]) + ) + + def visitNamespaceTag(self, node): + pass + + def visitDefTag(self, node): + pass + + def visitBlockTag(self, node): + if node.is_anonymous: + self.printer.writeline("%s()" % node.funcname) + else: + nameargs = node.get_argument_expressions(as_call=True) + nameargs += ["**pageargs"] + self.printer.writeline( + "if 'parent' not in context._data or " + "not hasattr(context._data['parent'], '%s'):" % node.funcname + ) + self.printer.writeline( + "context['self'].%s(%s)" % (node.funcname, ",".join(nameargs)) + ) + self.printer.writeline("\n") + + def visitCallNamespaceTag(self, node): + # TODO: we can put namespace-specific checks here, such + # as ensure the given namespace will be imported, + # pre-import the namespace, etc. + self.visitCallTag(node) + + def visitCallTag(self, node): + self.printer.writeline("def ccall(caller):") + export = ["body"] + callable_identifiers = self.identifiers.branch(node, nested=True) + body_identifiers = callable_identifiers.branch(node, nested=False) + # we want the 'caller' passed to ccall to be used + # for the body() function, but for other non-body() + # <%def>s within <%call> we want the current caller + # off the call stack (if any) + body_identifiers.add_declared("caller") + + self.identifier_stack.append(body_identifiers) + + class DefVisitor: + def visitDefTag(s, node): + s.visitDefOrBase(node) + + def visitBlockTag(s, node): + s.visitDefOrBase(node) + + def visitDefOrBase(s, node): + self.write_inline_def(node, callable_identifiers, nested=False) + if not node.is_anonymous: + export.append(node.funcname) + # remove defs that are within the <%call> from the + # "closuredefs" defined in the body, so they dont render twice + if node.funcname in body_identifiers.closuredefs: + del body_identifiers.closuredefs[node.funcname] + + vis = DefVisitor() + for n in node.nodes: + n.accept_visitor(vis) + self.identifier_stack.pop() + + bodyargs = node.body_decl.get_argument_expressions() + self.printer.writeline("def body(%s):" % ",".join(bodyargs)) + + # TODO: figure out best way to specify + # buffering/nonbuffering (at call time would be better) + buffered = False + if buffered: + self.printer.writelines("context._push_buffer()", "try:") + self.write_variable_declares(body_identifiers) + self.identifier_stack.append(body_identifiers) + + for n in node.nodes: + n.accept_visitor(self) + self.identifier_stack.pop() + + self.write_def_finish(node, buffered, False, False, callstack=False) + self.printer.writelines(None, "return [%s]" % (",".join(export)), None) + + self.printer.writelines( + # push on caller for nested call + "context.caller_stack.nextcaller = " + "runtime.Namespace('caller', context, " + "callables=ccall(__M_caller))", + "try:", + ) + self.printer.start_source(node.lineno) + self.printer.writelines( + "__M_writer(%s)" + % self.create_filter_callable([], node.expression, True), + "finally:", + "context.caller_stack.nextcaller = None", + None, + ) + + +class _Identifiers: + + """tracks the status of identifier names as template code is rendered.""" + + def __init__(self, compiler, node=None, parent=None, nested=False): + if parent is not None: + # if we are the branch created in write_namespaces(), + # we don't share any context from the main body(). + if isinstance(node, parsetree.NamespaceTag): + self.declared = set() + self.topleveldefs = util.SetLikeDict() + else: + # things that have already been declared + # in an enclosing namespace (i.e. names we can just use) + self.declared = ( + set(parent.declared) + .union([c.name for c in parent.closuredefs.values()]) + .union(parent.locally_declared) + .union(parent.argument_declared) + ) + + # if these identifiers correspond to a "nested" + # scope, it means whatever the parent identifiers + # had as undeclared will have been declared by that parent, + # and therefore we have them in our scope. + if nested: + self.declared = self.declared.union(parent.undeclared) + + # top level defs that are available + self.topleveldefs = util.SetLikeDict(**parent.topleveldefs) + else: + self.declared = set() + self.topleveldefs = util.SetLikeDict() + + self.compiler = compiler + + # things within this level that are referenced before they + # are declared (e.g. assigned to) + self.undeclared = set() + + # things that are declared locally. some of these things + # could be in the "undeclared" list as well if they are + # referenced before declared + self.locally_declared = set() + + # assignments made in explicit python blocks. + # these will be propagated to + # the context of local def calls. + self.locally_assigned = set() + + # things that are declared in the argument + # signature of the def callable + self.argument_declared = set() + + # closure defs that are defined in this level + self.closuredefs = util.SetLikeDict() + + self.node = node + + if node is not None: + node.accept_visitor(self) + + illegal_names = self.compiler.reserved_names.intersection( + self.locally_declared + ) + if illegal_names: + raise exceptions.NameConflictError( + "Reserved words declared in template: %s" + % ", ".join(illegal_names) + ) + + def branch(self, node, **kwargs): + """create a new Identifiers for a new Node, with + this Identifiers as the parent.""" + + return _Identifiers(self.compiler, node, self, **kwargs) + + @property + def defs(self): + return set(self.topleveldefs.union(self.closuredefs).values()) + + def __repr__(self): + return ( + "Identifiers(declared=%r, locally_declared=%r, " + "undeclared=%r, topleveldefs=%r, closuredefs=%r, " + "argumentdeclared=%r)" + % ( + list(self.declared), + list(self.locally_declared), + list(self.undeclared), + [c.name for c in self.topleveldefs.values()], + [c.name for c in self.closuredefs.values()], + self.argument_declared, + ) + ) + + def check_declared(self, node): + """update the state of this Identifiers with the undeclared + and declared identifiers of the given node.""" + + for ident in node.undeclared_identifiers(): + if ident != "context" and ident not in self.declared.union( + self.locally_declared + ): + self.undeclared.add(ident) + for ident in node.declared_identifiers(): + self.locally_declared.add(ident) + + def add_declared(self, ident): + self.declared.add(ident) + if ident in self.undeclared: + self.undeclared.remove(ident) + + def visitExpression(self, node): + self.check_declared(node) + + def visitControlLine(self, node): + self.check_declared(node) + + def visitCode(self, node): + if not node.ismodule: + self.check_declared(node) + self.locally_assigned = self.locally_assigned.union( + node.declared_identifiers() + ) + + def visitNamespaceTag(self, node): + # only traverse into the sub-elements of a + # <%namespace> tag if we are the branch created in + # write_namespaces() + if self.node is node: + for n in node.nodes: + n.accept_visitor(self) + + def _check_name_exists(self, collection, node): + existing = collection.get(node.funcname) + collection[node.funcname] = node + if ( + existing is not None + and existing is not node + and (node.is_block or existing.is_block) + ): + raise exceptions.CompileException( + "%%def or %%block named '%s' already " + "exists in this template." % node.funcname, + **node.exception_kwargs, + ) + + def visitDefTag(self, node): + if node.is_root() and not node.is_anonymous: + self._check_name_exists(self.topleveldefs, node) + elif node is not self.node: + self._check_name_exists(self.closuredefs, node) + + for ident in node.undeclared_identifiers(): + if ident != "context" and ident not in self.declared.union( + self.locally_declared + ): + self.undeclared.add(ident) + + # visit defs only one level deep + if node is self.node: + for ident in node.declared_identifiers(): + self.argument_declared.add(ident) + + for n in node.nodes: + n.accept_visitor(self) + + def visitBlockTag(self, node): + if node is not self.node and not node.is_anonymous: + if isinstance(self.node, parsetree.DefTag): + raise exceptions.CompileException( + "Named block '%s' not allowed inside of def '%s'" + % (node.name, self.node.name), + **node.exception_kwargs, + ) + elif isinstance( + self.node, (parsetree.CallTag, parsetree.CallNamespaceTag) + ): + raise exceptions.CompileException( + "Named block '%s' not allowed inside of <%%call> tag" + % (node.name,), + **node.exception_kwargs, + ) + + for ident in node.undeclared_identifiers(): + if ident != "context" and ident not in self.declared.union( + self.locally_declared + ): + self.undeclared.add(ident) + + if not node.is_anonymous: + self._check_name_exists(self.topleveldefs, node) + self.undeclared.add(node.funcname) + elif node is not self.node: + self._check_name_exists(self.closuredefs, node) + for ident in node.declared_identifiers(): + self.argument_declared.add(ident) + for n in node.nodes: + n.accept_visitor(self) + + def visitTextTag(self, node): + for ident in node.undeclared_identifiers(): + if ident != "context" and ident not in self.declared.union( + self.locally_declared + ): + self.undeclared.add(ident) + + def visitIncludeTag(self, node): + self.check_declared(node) + + def visitPageTag(self, node): + for ident in node.declared_identifiers(): + self.argument_declared.add(ident) + self.check_declared(node) + + def visitCallNamespaceTag(self, node): + self.visitCallTag(node) + + def visitCallTag(self, node): + if node is self.node: + for ident in node.undeclared_identifiers(): + if ident != "context" and ident not in self.declared.union( + self.locally_declared + ): + self.undeclared.add(ident) + for ident in node.declared_identifiers(): + self.argument_declared.add(ident) + for n in node.nodes: + n.accept_visitor(self) + else: + for ident in node.undeclared_identifiers(): + if ident != "context" and ident not in self.declared.union( + self.locally_declared + ): + self.undeclared.add(ident) + + +_FOR_LOOP = re.compile( + r"^for\s+((?:\(?)\s*" + r"(?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*" + r"(?:\s*,\s*(?:[A-Za-z_][A-Za-z_0-9]*),??)*\s*(?:\)?)" + r"(?:\s*,\s*(?:" + r"(?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*" + r"(?:\s*,\s*(?:[A-Za-z_][A-Za-z_0-9]*),??)*\s*(?:\)?)" + r"),??)*\s*(?:\)?))\s+in\s+(.*):" +) + + +def mangle_mako_loop(node, printer): + """converts a for loop into a context manager wrapped around a for loop + when access to the `loop` variable has been detected in the for loop body + """ + loop_variable = LoopVariable() + node.accept_visitor(loop_variable) + if loop_variable.detected: + node.nodes[-1].has_loop_context = True + match = _FOR_LOOP.match(node.text) + if match: + printer.writelines( + "loop = __M_loop._enter(%s)" % match.group(2), + "try:" + # 'with __M_loop(%s) as loop:' % match.group(2) + ) + text = "for %s in loop:" % match.group(1) + else: + raise SyntaxError("Couldn't apply loop context: %s" % node.text) + else: + text = node.text + return text + + +class LoopVariable: + + """A node visitor which looks for the name 'loop' within undeclared + identifiers.""" + + def __init__(self): + self.detected = False + + def _loop_reference_detected(self, node): + if "loop" in node.undeclared_identifiers(): + self.detected = True + else: + for n in node.get_children(): + n.accept_visitor(self) + + def visitControlLine(self, node): + self._loop_reference_detected(node) + + def visitCode(self, node): + self._loop_reference_detected(node) + + def visitExpression(self, node): + self._loop_reference_detected(node) diff --git a/venv/lib/python3.8/site-packages/mako/compat.py b/venv/lib/python3.8/site-packages/mako/compat.py new file mode 100644 index 000000000..1c1760124 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/compat.py @@ -0,0 +1,70 @@ +# mako/compat.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +import collections +from importlib import metadata as importlib_metadata +from importlib import util +import inspect +import sys + +win32 = sys.platform.startswith("win") +pypy = hasattr(sys, "pypy_version_info") + +ArgSpec = collections.namedtuple( + "ArgSpec", ["args", "varargs", "keywords", "defaults"] +) + + +def inspect_getargspec(func): + """getargspec based on fully vendored getfullargspec from Python 3.3.""" + + if inspect.ismethod(func): + func = func.__func__ + if not inspect.isfunction(func): + raise TypeError(f"{func!r} is not a Python function") + + co = func.__code__ + if not inspect.iscode(co): + raise TypeError(f"{co!r} is not a code object") + + nargs = co.co_argcount + names = co.co_varnames + nkwargs = co.co_kwonlyargcount + args = list(names[:nargs]) + + nargs += nkwargs + varargs = None + if co.co_flags & inspect.CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & inspect.CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + + return ArgSpec(args, varargs, varkw, func.__defaults__) + + +def load_module(module_id, path): + spec = util.spec_from_file_location(module_id, path) + module = util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def exception_as(): + return sys.exc_info()[1] + + +def exception_name(exc): + return exc.__class__.__name__ + + +def importlib_metadata_get(group): + ep = importlib_metadata.entry_points() + if hasattr(ep, "select"): + return ep.select(group=group) + else: + return ep.get(group, ()) diff --git a/venv/lib/python3.8/site-packages/mako/exceptions.py b/venv/lib/python3.8/site-packages/mako/exceptions.py new file mode 100644 index 000000000..6aeb2d274 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/exceptions.py @@ -0,0 +1,417 @@ +# mako/exceptions.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""exception classes""" + +import sys +import traceback + +from mako import compat +from mako import util + + +class MakoException(Exception): + pass + + +class RuntimeException(MakoException): + pass + + +def _format_filepos(lineno, pos, filename): + if filename is None: + return " at line: %d char: %d" % (lineno, pos) + else: + return " in file '%s' at line: %d char: %d" % (filename, lineno, pos) + + +class CompileException(MakoException): + def __init__(self, message, source, lineno, pos, filename): + MakoException.__init__( + self, message + _format_filepos(lineno, pos, filename) + ) + self.lineno = lineno + self.pos = pos + self.filename = filename + self.source = source + + +class SyntaxException(MakoException): + def __init__(self, message, source, lineno, pos, filename): + MakoException.__init__( + self, message + _format_filepos(lineno, pos, filename) + ) + self.lineno = lineno + self.pos = pos + self.filename = filename + self.source = source + + +class UnsupportedError(MakoException): + + """raised when a retired feature is used.""" + + +class NameConflictError(MakoException): + + """raised when a reserved word is used inappropriately""" + + +class TemplateLookupException(MakoException): + pass + + +class TopLevelLookupException(TemplateLookupException): + pass + + +class RichTraceback: + + """Pull the current exception from the ``sys`` traceback and extracts + Mako-specific template information. + + See the usage examples in :ref:`handling_exceptions`. + + """ + + def __init__(self, error=None, traceback=None): + self.source, self.lineno = "", 0 + + if error is None or traceback is None: + t, value, tback = sys.exc_info() + + if error is None: + error = value or t + + if traceback is None: + traceback = tback + + self.error = error + self.records = self._init(traceback) + + if isinstance(self.error, (CompileException, SyntaxException)): + self.source = self.error.source + self.lineno = self.error.lineno + self._has_source = True + + self._init_message() + + @property + def errorname(self): + return compat.exception_name(self.error) + + def _init_message(self): + """Find a unicode representation of self.error""" + try: + self.message = str(self.error) + except UnicodeError: + try: + self.message = str(self.error) + except UnicodeEncodeError: + # Fallback to args as neither unicode nor + # str(Exception(u'\xe6')) work in Python < 2.6 + self.message = self.error.args[0] + if not isinstance(self.message, str): + self.message = str(self.message, "ascii", "replace") + + def _get_reformatted_records(self, records): + for rec in records: + if rec[6] is not None: + yield (rec[4], rec[5], rec[2], rec[6]) + else: + yield tuple(rec[0:4]) + + @property + def traceback(self): + """Return a list of 4-tuple traceback records (i.e. normal python + format) with template-corresponding lines remapped to the originating + template. + + """ + return list(self._get_reformatted_records(self.records)) + + @property + def reverse_records(self): + return reversed(self.records) + + @property + def reverse_traceback(self): + """Return the same data as traceback, except in reverse order.""" + + return list(self._get_reformatted_records(self.reverse_records)) + + def _init(self, trcback): + """format a traceback from sys.exc_info() into 7-item tuples, + containing the regular four traceback tuple items, plus the original + template filename, the line number adjusted relative to the template + source, and code line from that line number of the template.""" + + import mako.template + + mods = {} + rawrecords = traceback.extract_tb(trcback) + new_trcback = [] + for filename, lineno, function, line in rawrecords: + if not line: + line = "" + try: + (line_map, template_lines, template_filename) = mods[filename] + except KeyError: + try: + info = mako.template._get_module_info(filename) + module_source = info.code + template_source = info.source + template_filename = ( + info.template_filename or info.template_uri or filename + ) + except KeyError: + # A normal .py file (not a Template) + new_trcback.append( + ( + filename, + lineno, + function, + line, + None, + None, + None, + None, + ) + ) + continue + + template_ln = 1 + + mtm = mako.template.ModuleInfo + source_map = mtm.get_module_source_metadata( + module_source, full_line_map=True + ) + line_map = source_map["full_line_map"] + + template_lines = [ + line_ for line_ in template_source.split("\n") + ] + mods[filename] = (line_map, template_lines, template_filename) + + template_ln = line_map[lineno - 1] + + if template_ln <= len(template_lines): + template_line = template_lines[template_ln - 1] + else: + template_line = None + new_trcback.append( + ( + filename, + lineno, + function, + line, + template_filename, + template_ln, + template_line, + template_source, + ) + ) + if not self.source: + for l in range(len(new_trcback) - 1, 0, -1): + if new_trcback[l][5]: + self.source = new_trcback[l][7] + self.lineno = new_trcback[l][5] + break + else: + if new_trcback: + try: + # A normal .py file (not a Template) + with open(new_trcback[-1][0], "rb") as fp: + encoding = util.parse_encoding(fp) + if not encoding: + encoding = "utf-8" + fp.seek(0) + self.source = fp.read() + if encoding: + self.source = self.source.decode(encoding) + except IOError: + self.source = "" + self.lineno = new_trcback[-1][1] + return new_trcback + + +def text_error_template(lookup=None): + """Provides a template that renders a stack trace in a similar format to + the Python interpreter, substituting source template filenames, line + numbers and code for that of the originating source template, as + applicable. + + """ + import mako.template + + return mako.template.Template( + r""" +<%page args="error=None, traceback=None"/> +<%! + from mako.exceptions import RichTraceback +%>\ +<% + tback = RichTraceback(error=error, traceback=traceback) +%>\ +Traceback (most recent call last): +% for (filename, lineno, function, line) in tback.traceback: + File "${filename}", line ${lineno}, in ${function or '?'} + ${line | trim} +% endfor +${tback.errorname}: ${tback.message} +""" + ) + + +def _install_pygments(): + global syntax_highlight, pygments_html_formatter + from mako.ext.pygmentplugin import syntax_highlight # noqa + from mako.ext.pygmentplugin import pygments_html_formatter # noqa + + +def _install_fallback(): + global syntax_highlight, pygments_html_formatter + from mako.filters import html_escape + + pygments_html_formatter = None + + def syntax_highlight(filename="", language=None): + return html_escape + + +def _install_highlighting(): + try: + _install_pygments() + except ImportError: + _install_fallback() + + +_install_highlighting() + + +def html_error_template(): + """Provides a template that renders a stack trace in an HTML format, + providing an excerpt of code as well as substituting source template + filenames, line numbers and code for that of the originating source + template, as applicable. + + The template's default ``encoding_errors`` value is + ``'htmlentityreplace'``. The template has two options. With the + ``full`` option disabled, only a section of an HTML document is + returned. With the ``css`` option disabled, the default stylesheet + won't be included. + + """ + import mako.template + + return mako.template.Template( + r""" +<%! + from mako.exceptions import RichTraceback, syntax_highlight,\ + pygments_html_formatter +%> +<%page args="full=True, css=True, error=None, traceback=None"/> +% if full: + + + Mako Runtime Error +% endif +% if css: + +% endif +% if full: + + +% endif + +

    Error !

    +<% + tback = RichTraceback(error=error, traceback=traceback) + src = tback.source + line = tback.lineno + if src: + lines = src.split('\n') + else: + lines = None +%> +

    ${tback.errorname}: ${tback.message|h}

    + +% if lines: +
    +
    +% for index in range(max(0, line-4),min(len(lines), line+5)): + <% + if pygments_html_formatter: + pygments_html_formatter.linenostart = index + 1 + %> + % if index + 1 == line: + <% + if pygments_html_formatter: + old_cssclass = pygments_html_formatter.cssclass + pygments_html_formatter.cssclass = 'error ' + old_cssclass + %> + ${lines[index] | syntax_highlight(language='mako')} + <% + if pygments_html_formatter: + pygments_html_formatter.cssclass = old_cssclass + %> + % else: + ${lines[index] | syntax_highlight(language='mako')} + % endif +% endfor +
    +
    +% endif + +
    +% for (filename, lineno, function, line) in tback.reverse_traceback: +
    ${filename}, line ${lineno}:
    +
    + <% + if pygments_html_formatter: + pygments_html_formatter.linenostart = lineno + %> +
    ${line | syntax_highlight(filename)}
    +
    +% endfor +
    + +% if full: + + +% endif +""", + output_encoding=sys.getdefaultencoding(), + encoding_errors="htmlentityreplace", + ) diff --git a/venv/lib/python3.8/site-packages/mako/ext/__init__.py b/venv/lib/python3.8/site-packages/mako/ext/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/mako/ext/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/ext/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f16a41217228833b417af5db7b175bd63e5bfc8 GIT binary patch literal 201 zcmWIL<>g`k0)Zp?nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10RKO;XkRX?>V zH9IpkN8cs2EHx*;AU8FyMBgVdsaU_DvLquvPq)BWH?1f&H7PSyx1=aBIXksTzbrMc zOg|?x397_c&qBXAvm{lw04SZ9o?5J*o0y%ipIT9(A0MBYmst`YuUAlci^C>2KczG$ M)ehv2&p^xo06WMv*8l(j literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/ext/__pycache__/autohandler.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/ext/__pycache__/autohandler.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a63c479d5a70a663feaa75ced7680cb4895c935a GIT binary patch literal 1521 zcmc&!&2Aev5GJ`lQr16p1E)@bqTK`y5}~mcNO}ki#|Y50m!`hhMOzilV!fmyN~`6H z)t!|rAl6~y}cj^VeH?zRnK-K6=d{_vn zSM#im?i?(9@Vv()*I}1u?D6eSe_pY@w@m$hn2KD3=hozQbPxoAkn`NqQfUahVpIFc zli~lF<^9km@c6D0LuP-=?(Qy9zl4sXL$kJ$a%$nQ8~_*81o{F<8DJIeqlr_YKJJr= zTRDA4?O$An+0|a-x z@$_2dy~OejC6r9pD{p3{e&tv0z>%-@M&(rgOCn#_(SEhDrkl%Xl|KUrR{jy1d!f#N z$lpe9tnbPLxT0UkJ{ld@wtu~AOOdsukgcVbcX-jb_v7>E(wxvq=XLAyo%lcctW%i+Sp1FAv!2o@Sm>$j@H zGiO2ooa8Aw`FP@J3l*<&sI!AAvV&$=Zev8VrAvsfq2qizTDu3RJmOHPqm37?0p2xH<@0{HPx!eNw1W47-FZ^SGw{ery z@)oX(((+9sj;ToFSbk(})-AK=Yju5e#jWk1w7yMqe-6b1dqe90?u{lCvgrh${0pum BoBjX* literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/ext/__pycache__/babelplugin.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/ext/__pycache__/babelplugin.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9c4629830056c3367742e9b29b5fcc8037070c4 GIT binary patch literal 2147 zcmZ`)PjlQf6qjtT*V%4DD9}I9q3VUn4$1n^ODEG|7-)wcXf6yKJTQva(r%o1EmzV8 zlG&WnnSKcl$+6#qPvI-4eucsS@5y$%fubF2C0Tm!>HU81=kM3o2L#&YZ*l&4M9A+r zS#1GKzJ$j-fKHHv2r9gS&b`E=IQ9#F9wY%J-w+Xq-g6>)GC22>o(M(%IY~kheMd%X zr;lgSTKS7*b7@RElMKc>&1|kpcATf|t8^lZU3Oq?Wp?AZI;rxp(zCeI>QH7jcJJd? z5Cj>~dZRtfs}p;mN?6%^+Sq;Em{mH%t*gy%CuSQ4k|Y#v51T14|W8$cTO(CO9B}s;IFs$nzCdgFRO*+vdomo%UKo|FUO$Tu)Lh2$R37EBh#$#gS*QwwiN8%J29QHUcB=b3 zQ!V8rmph2_k<{_AERW+NpR`-k0K(Hjs3 zKIS@fgoZTqA{tPS4F2~1io(-R{)^7IyI$tunYQ5=A^I8~gS=mo3xMj9LelBph$cgz zTLwhp)4Y%)PrnCy>OM}?Nph?8{augSkcEGCceQ zx+OV;g1Sx40!xuThu$)P-aii>k)J;MF*xg;g=hV4#LepLh3a? zf8hSZKTq$y1Kg`NkoufsNSxOL&gV)j3LFnPf3`@A=8a>Kz5$*zI<41RQfCq&TnY1i z+=2jWgliohgE&QWcrEhX+7%po@D4XR96yDD#o~uDKljf9MDUzG23((DaU;*X^e%wW z%1Eb%hE27aGZYwR<-CIYOEVq}7_{9g)#;qIB-~~8K)T6JksnD0yaai4iya)y9H>Ph z*yN4KO5yCUn(MS;m!+)8jb&=ex;3_ykL8#l&`zH*fo23gQv24nGlub9Xg<3`VJR&I%gd}-2>I?PFr>V6Nzu|Q z3VX$AD~{+-1 zlKAmddWE-6lltu&{Kgq4H^U*-_h7aMWzy$dsEqUEhEv#a$Cz<@%E?5si8_o+Z#?l-(C7JqEue0UdfnfAj7?`CLOE literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/ext/__pycache__/beaker_cache.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/ext/__pycache__/beaker_cache.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e206c91c44b79e8aa336f5265d7f44376c028954 GIT binary patch literal 2591 zcmah~&5zqe6rUN76UUouw~N#dP{BZ<3StwaUaGP}(3USzRuvQh7Lwz5Cc94j#n@Sz z$a|rcdO^K%-97FyPwg|D3W7vKQzjkDQpw_6-}@@D4E%=`V`Z=S#DbZi38+8_OJ z9jrgGbMk3G=RyjhV|R+D2r=+~t(a*PzEEGj6y|O1>q@8YTD>rfYpq zlE!yLG+awG{nn>MSfcfah?e9J4c8XUuSwUQZ{AbsZYZSVJ2!*K&-0su-fchFk)Ov| zG;l_#a*7@4+>-u8Dkp%MFxhtUSzgGv_ZLJ*y0o(8gFt3Qm?k;&TFdp&F>#6f$TcRyG@ZWk& zIB{)2v|633V_gO@|L3FULd$C4;(svOwgb=ooiKN@Xu2IH4p`G%3THTTgdF+PsCd^I z`Z$s%nykgBUbyQ=Q|Y85XE3-n)FfUT9Md`&XnMR5$sRbFul!gRKr}1>+Vu|0wA`2A zlduT=D4Z`fQ?vH| zk%WM~Bt^fx@S{kIUe%2K#NU>xYghJ;pL;b;3Zr$`zM3XQ7|V~31Xgtx^PXm|vOtHL zmCCNE^woA=wd*}<>_jMekHtm3(hMvB9Mhag|i(;RxtA>IsDkaoLjU_SD8iUmu_ht zX;64edq+qDiVGGGmJmer1-$w1(3Er^O9we%dkj+y>AkAQ*a0n}q->JpQc1>i%*KY= zJhJK;!L|RE_UD*(u;6MQP|=t$B}5ba%uOgU+V2TbXrt~2qA&OFMJOyg-|PA z%rfb=Vi{{4hFgJhslo*!V1Vuj~m0+S!(C2TDTO8BLSmkf~Q;t$GETu36(VCUWMU z*FM0nhi>>KP*>1V!+i#62O~_CS8sa<+;Vgy0CaxkSvXJmo6m|t!|A0@;xmU7?cCcD^u6%Gviiof)jqLj-!3)*=S+Qrz$SuTE>XXEn)<2fpudK zBCA<1HQNf4u3m%YkZW>vU}r~!X}AmT3nBvk&W;F=dr?F+XF93{Yi?V~INg|ex3(6!CTxtuWEbVk5fNd%rreuq=Dz@5?L$p zb&LQrqcy2e+3<;|D|jHI2%L}QcMkpVwkf<07vRnDidu|Y?T&TP;`q~Z-4*o?%v2n! zbp0u^(d_BsqA;g!W2ozJt??BYQB7>HLg+nC9IQjo*WB8yv3-EPy25kTrftSR{S9g} H&baX(HD-S` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/ext/__pycache__/extract.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/ext/__pycache__/extract.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0619fafafcf78f692724053ea57180ad8f1d0a3a GIT binary patch literal 2944 zcmai0&2J<}6|buPn09++*4cG-?T~CHAB31-4`RcL!)k?gy+F|7Aa+5h@oLhZD%)-M zbdRgc%X-wq#g^a@B=aB49K)Z$ffM3S;Lul2E9C??B;x#D&G;*^phs0x{obqhUcHZB z^)DNZkl~s8TRXXPow0w?%f-jR%MUS%pJI|s@|5*>#5t`6)*=#i?W9iRfaa!d&x<@u z^RV_KpR*q`>C4&?leI$;)ui);Ed?*3k1g@4_Ta#%;-inT44xRBWLqD7yoMJqRWFpr zVx}MKLK&@8m$qDd&Yb%WsES2gvPeh{M}%~aSma7qdieL0KXKrY!+ZeqGu7PBR8hoR z>d_0M4MoXDUMWWYn2q@( zMCK=q&JQ!rS{10Qn@b!EM`24>f)GAwYDz{MSW>wovQk8G)L=B^Q{WLbpihsuR9Q6OE*WS*1s;vfU zH&JPORXtZ}-tVc*v>(SCMZ14sw)1Sce`k3UQQk<@GKFMU>GpG#J#VMU##H3a$`9H_ zV$^aUY{af;_u}2W4Hs!p7+3lSRlTqCE`r-3uTWF0uqDP_?z7+|bOvvHHS{YNcU($C z6Wap}n}$!2pe=-3@}YwyAOVZ4KQEo3Yq$}T8E1ryTaLHL#^O*M@}XDqRdy_%y*>0L z8`erkuN$xQjlY9p)a#|c6O3lYtXSW>JEe!9{{{3}q7MV?_!P;lBgqX=n`JN-V>H0t z-O^iS&)yr(l)>m)IWy+^So-knD%s;R?Q_un59oPi=lU?j|BYcS`}@@LJb3>C?bQps zKTml#55+OpbKqXT!2NW}y=A%YpYF}fl)hl;tDrBQS^M>r_nPHxgZB++{)D26GvVCB zF#B*S^SYHevJszJ&K9P;H{dPFTYoZvz2xCvOCf7$j$o02*OW60Y?l$;W-gSoN30a9 z44IjQp3vO37CuMT-h|ZWWjJb;uB?y6Foab^9dx06^OkIs;Vs%1W_9znY@WU$FYlBT zNo%W|9XrU%+^|tLenoBQ59`Nb@4XVUesESjHF(ZO@0PXmhTXesTlU)8k8}+cS9x*K zO_G7LUVJd9ck>?g%d&~Lil;A6@MsR9mo#;0I(-O3sHvY|6!Vz2fEUl|Ltuwwi|kKg z&jl`U{VG_tJC{TyR=!)+rWP!1GQ9$kD!i8_g#ojF|2L$Pbo*Se!S}w_M3<0a&PxrW$2CY8ym*|AzW(?eE%nq(@=s}LHsDSn%n z?Z{tMn@{7dsIj@9bxo3I9jUr$2RPL(NzH8J^1w<&wTE$<60P=gU=l6n zh_V*zzslRhH&F@IX!t1-AuC16?9D~7oFBw$DqzC zmku4xTDYI!f8{-o(|w{%!c21d+qCog1)Y?NY^#VC;Jv)cSXD!4=<@qYwb>&qKwUAy z#DLf?Z*swJp15wvT_H|{2)__c@L%BzQRkrHS3hwb!Gp6m-yvRr-(+subrbw1_pzpT z7g}lMf(kuCxZ=bW^Iy2GD^T7izB|wB!u25|@Q;EIm^cX>><1rKK^xru-z{+IB023# z%3Sdprr)zoF1Zv(fnm7U`54ebutc}^VDJlN_I1`W+p5*uPfbGg+L{Kv6`R(iep|Mx zTS;c}Rt#EUTKQ(!nm(uEc5k-d+fce_#hJ8P<1X0!I6FY5&J>g6B{U&#spD^u*RNx; zXfnSphI}ag0KabmOgF^@RnC*SIF4*rat5PebCOKMN^`GhcUqv;hzld zd@Z1t`}QhO0e;OWoK9(ccA%jNEy!N6Bqj!Lomu@ptq9fhw=fMb3Lg`5LsZq^&HrP3 zZ7HaNPA5ZKcRE$r>GX2BpVGSC>Fn*t>4c+Kpi$FjhppzS4u~;Hx2N!pBdy`{I9&(^ zIdTodeygB9Mc@Y;f@`9~ut@Ur&eyj=tX%r`u^(_25Uu1@s;l7M#MAq9tyuka>J0Qq G-T4ywMh$WR literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/ext/__pycache__/linguaplugin.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/ext/__pycache__/linguaplugin.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e398f905d02fe9200f4cf6ff29f10ddf3817a53 GIT binary patch literal 1686 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/ext/__pycache__/turbogears.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/ext/__pycache__/turbogears.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a9e91d3027c4624efe7b195a4a2d926398134b6 GIT binary patch literal 1667 zcmZuxOK%%D5GJ|Yhu4zr1g=x3K&w*_9jrY#2yzID0yW}`Arwe@Su`NlOG;X6Um+>S z7S^Yv*PMHlj{P6~6}NX^4u`{ee8XS+{Sd*}`zy-c1_=Fa7x#w) zi=SYs=Ri2(I7bDJF}6I(N#Vp!%R4zO+}OqF6miPkkBGY;NbGU%H5&S#;Vc?r@Kc`$#5 zsr~@c;07_&I6!ej)P9C;2u#|L6Plup#SbP~F4VQsqG0Jvq;nPT6%}9Pf^}-w65E5@ zW>;KknXpU97Mm=})NFx3lq#!A<7Qw%nNaCOYV|(TGii%tyu7WZ>1ik~t7;9(N)p(L zA7-Vh!4WnUaA<0IX;W+h#pw_d!#LVrWQV<`daS7wRv26z|~>Kg0ph z9`T%g;Fb?M434^BQ=6}DSl*Vah1Ur%4ZwT8B@Z-k6M(}7{)AsczcqO7%NO>)BHZC* zO&%fcJhC7FdSf-|%54B}00p{ydR8S|4Uz#=t8KQyL{hq%DB#1BrW3JJ#76I?{$8_c}qD6e8a3R%~Orti+NR5vVdQPgJc88DDBs__RR_Zze#4P9es zO303U3ONIA<*d;fUay8;xX^5EUWa%*^h}s3fT&K&R2T=Gmyf~R28-UJ=I~S*Pl}QY zIdt2|4b93)WxPoxi$t4XB9o~FoC&%d{3m$%16VwUsoE#k332Fn5cYESF5ue>obg!N z_r`kW$nSuAH6Hr5Rcw+OGa+NQealRbv3H9kzuoZ}<5kL7i_c9A_S$={*nW|=7}93X zW?$KY2ZWOFAhaw(U)w! +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""adds autohandler functionality to Mako templates. + +requires that the TemplateLookup class is used with templates. + +usage:: + + <%! + from mako.ext.autohandler import autohandler + %> + <%inherit file="${autohandler(template, context)}"/> + + +or with custom autohandler filename:: + + <%! + from mako.ext.autohandler import autohandler + %> + <%inherit file="${autohandler(template, context, name='somefilename')}"/> + +""" + +import os +import posixpath +import re + + +def autohandler(template, context, name="autohandler"): + lookup = context.lookup + _template_uri = template.module._template_uri + if not lookup.filesystem_checks: + try: + return lookup._uri_cache[(autohandler, _template_uri, name)] + except KeyError: + pass + + tokens = re.findall(r"([^/]+)", posixpath.dirname(_template_uri)) + [name] + while len(tokens): + path = "/" + "/".join(tokens) + if path != _template_uri and _file_exists(lookup, path): + if not lookup.filesystem_checks: + return lookup._uri_cache.setdefault( + (autohandler, _template_uri, name), path + ) + else: + return path + if len(tokens) == 1: + break + tokens[-2:] = [name] + + if not lookup.filesystem_checks: + return lookup._uri_cache.setdefault( + (autohandler, _template_uri, name), None + ) + else: + return None + + +def _file_exists(lookup, path): + psub = re.sub(r"^/", "", path) + for d in lookup.directories: + if os.path.exists(d + "/" + psub): + return True + else: + return False diff --git a/venv/lib/python3.8/site-packages/mako/ext/babelplugin.py b/venv/lib/python3.8/site-packages/mako/ext/babelplugin.py new file mode 100644 index 000000000..541ea5409 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ext/babelplugin.py @@ -0,0 +1,57 @@ +# ext/babelplugin.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""gettext message extraction via Babel: https://pypi.org/project/Babel/""" +from babel.messages.extract import extract_python + +from mako.ext.extract import MessageExtractor + + +class BabelMakoExtractor(MessageExtractor): + def __init__(self, keywords, comment_tags, options): + self.keywords = keywords + self.options = options + self.config = { + "comment-tags": " ".join(comment_tags), + "encoding": options.get( + "input_encoding", options.get("encoding", None) + ), + } + super().__init__() + + def __call__(self, fileobj): + return self.process_file(fileobj) + + def process_python(self, code, code_lineno, translator_strings): + comment_tags = self.config["comment-tags"] + for ( + lineno, + funcname, + messages, + python_translator_comments, + ) in extract_python(code, self.keywords, comment_tags, self.options): + yield ( + code_lineno + (lineno - 1), + funcname, + messages, + translator_strings + python_translator_comments, + ) + + +def extract(fileobj, keywords, comment_tags, options): + """Extract messages from Mako templates. + + :param fileobj: the file-like object the messages should be extracted from + :param keywords: a list of keywords (i.e. function names) that should be + recognized as translation functions + :param comment_tags: a list of translator tags to search for and include + in the results + :param options: a dictionary of additional options (optional) + :return: an iterator over ``(lineno, funcname, message, comments)`` tuples + :rtype: ``iterator`` + """ + extractor = BabelMakoExtractor(keywords, comment_tags, options) + yield from extractor(fileobj) diff --git a/venv/lib/python3.8/site-packages/mako/ext/beaker_cache.py b/venv/lib/python3.8/site-packages/mako/ext/beaker_cache.py new file mode 100644 index 000000000..b1392f30f --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ext/beaker_cache.py @@ -0,0 +1,82 @@ +# ext/beaker_cache.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""Provide a :class:`.CacheImpl` for the Beaker caching system.""" + +from mako import exceptions +from mako.cache import CacheImpl + +try: + from beaker import cache as beaker_cache +except: + has_beaker = False +else: + has_beaker = True + +_beaker_cache = None + + +class BeakerCacheImpl(CacheImpl): + + """A :class:`.CacheImpl` provided for the Beaker caching system. + + This plugin is used by default, based on the default + value of ``'beaker'`` for the ``cache_impl`` parameter of the + :class:`.Template` or :class:`.TemplateLookup` classes. + + """ + + def __init__(self, cache): + if not has_beaker: + raise exceptions.RuntimeException( + "Can't initialize Beaker plugin; Beaker is not installed." + ) + global _beaker_cache + if _beaker_cache is None: + if "manager" in cache.template.cache_args: + _beaker_cache = cache.template.cache_args["manager"] + else: + _beaker_cache = beaker_cache.CacheManager() + super().__init__(cache) + + def _get_cache(self, **kw): + expiretime = kw.pop("timeout", None) + if "dir" in kw: + kw["data_dir"] = kw.pop("dir") + elif self.cache.template.module_directory: + kw["data_dir"] = self.cache.template.module_directory + + if "manager" in kw: + kw.pop("manager") + + if kw.get("type") == "memcached": + kw["type"] = "ext:memcached" + + if "region" in kw: + region = kw.pop("region") + cache = _beaker_cache.get_cache_region(self.cache.id, region, **kw) + else: + cache = _beaker_cache.get_cache(self.cache.id, **kw) + cache_args = {"starttime": self.cache.starttime} + if expiretime: + cache_args["expiretime"] = expiretime + return cache, cache_args + + def get_or_create(self, key, creation_function, **kw): + cache, kw = self._get_cache(**kw) + return cache.get(key, createfunc=creation_function, **kw) + + def put(self, key, value, **kw): + cache, kw = self._get_cache(**kw) + cache.put(key, value, **kw) + + def get(self, key, **kw): + cache, kw = self._get_cache(**kw) + return cache.get(key, **kw) + + def invalidate(self, key, **kw): + cache, kw = self._get_cache(**kw) + cache.remove_value(key, **kw) diff --git a/venv/lib/python3.8/site-packages/mako/ext/extract.py b/venv/lib/python3.8/site-packages/mako/ext/extract.py new file mode 100644 index 000000000..609c5c298 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ext/extract.py @@ -0,0 +1,129 @@ +# ext/extract.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from io import BytesIO +from io import StringIO +import re + +from mako import lexer +from mako import parsetree + + +class MessageExtractor: + use_bytes = True + + def process_file(self, fileobj): + template_node = lexer.Lexer( + fileobj.read(), input_encoding=self.config["encoding"] + ).parse() + yield from self.extract_nodes(template_node.get_children()) + + def extract_nodes(self, nodes): + translator_comments = [] + in_translator_comments = False + input_encoding = self.config["encoding"] or "ascii" + comment_tags = list( + filter(None, re.split(r"\s+", self.config["comment-tags"])) + ) + + for node in nodes: + child_nodes = None + if ( + in_translator_comments + and isinstance(node, parsetree.Text) + and not node.content.strip() + ): + # Ignore whitespace within translator comments + continue + + if isinstance(node, parsetree.Comment): + value = node.text.strip() + if in_translator_comments: + translator_comments.extend( + self._split_comment(node.lineno, value) + ) + continue + for comment_tag in comment_tags: + if value.startswith(comment_tag): + in_translator_comments = True + translator_comments.extend( + self._split_comment(node.lineno, value) + ) + continue + + if isinstance(node, parsetree.DefTag): + code = node.function_decl.code + child_nodes = node.nodes + elif isinstance(node, parsetree.BlockTag): + code = node.body_decl.code + child_nodes = node.nodes + elif isinstance(node, parsetree.CallTag): + code = node.code.code + child_nodes = node.nodes + elif isinstance(node, parsetree.PageTag): + code = node.body_decl.code + elif isinstance(node, parsetree.CallNamespaceTag): + code = node.expression + child_nodes = node.nodes + elif isinstance(node, parsetree.ControlLine): + if node.isend: + in_translator_comments = False + continue + code = node.text + elif isinstance(node, parsetree.Code): + in_translator_comments = False + code = node.code.code + elif isinstance(node, parsetree.Expression): + code = node.code.code + else: + continue + + # Comments don't apply unless they immediately precede the message + if ( + translator_comments + and translator_comments[-1][0] < node.lineno - 1 + ): + translator_comments = [] + + translator_strings = [ + comment[1] for comment in translator_comments + ] + + if isinstance(code, str) and self.use_bytes: + code = code.encode(input_encoding, "backslashreplace") + + used_translator_comments = False + # We add extra newline to work around a pybabel bug + # (see python-babel/babel#274, parse_encoding dies if the first + # input string of the input is non-ascii) + # Also, because we added it, we have to subtract one from + # node.lineno + if self.use_bytes: + code = BytesIO(b"\n" + code) + else: + code = StringIO("\n" + code) + + for message in self.process_python( + code, node.lineno - 1, translator_strings + ): + yield message + used_translator_comments = True + + if used_translator_comments: + translator_comments = [] + in_translator_comments = False + + if child_nodes: + yield from self.extract_nodes(child_nodes) + + @staticmethod + def _split_comment(lineno, comment): + """Return the multiline comment at lineno split into a list of + comment line numbers and the accompanying comment line""" + return [ + (lineno + index, line) + for index, line in enumerate(comment.splitlines()) + ] diff --git a/venv/lib/python3.8/site-packages/mako/ext/linguaplugin.py b/venv/lib/python3.8/site-packages/mako/ext/linguaplugin.py new file mode 100644 index 000000000..118668de2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ext/linguaplugin.py @@ -0,0 +1,57 @@ +# ext/linguaplugin.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +import contextlib +import io + +from lingua.extractors import Extractor +from lingua.extractors import get_extractor +from lingua.extractors import Message + +from mako.ext.extract import MessageExtractor + + +class LinguaMakoExtractor(Extractor, MessageExtractor): + """Mako templates""" + + use_bytes = False + extensions = [".mako"] + default_config = {"encoding": "utf-8", "comment-tags": ""} + + def __call__(self, filename, options, fileobj=None): + self.options = options + self.filename = filename + self.python_extractor = get_extractor("x.py") + if fileobj is None: + ctx = open(filename, "r") + else: + ctx = contextlib.nullcontext(fileobj) + with ctx as file_: + yield from self.process_file(file_) + + def process_python(self, code, code_lineno, translator_strings): + source = code.getvalue().strip() + if source.endswith(":"): + if source in ("try:", "else:") or source.startswith("except"): + source = "" # Ignore try/except and else + elif source.startswith("elif"): + source = source[2:] # Replace "elif" with "if" + source += "pass" + code = io.StringIO(source) + for msg in self.python_extractor( + self.filename, self.options, code, code_lineno - 1 + ): + if translator_strings: + msg = Message( + msg.msgctxt, + msg.msgid, + msg.msgid_plural, + msg.flags, + " ".join(translator_strings + [msg.comment]), + msg.tcomment, + msg.location, + ) + yield msg diff --git a/venv/lib/python3.8/site-packages/mako/ext/preprocessors.py b/venv/lib/python3.8/site-packages/mako/ext/preprocessors.py new file mode 100644 index 000000000..21f2db54d --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ext/preprocessors.py @@ -0,0 +1,20 @@ +# ext/preprocessors.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""preprocessing functions, used with the 'preprocessor' +argument on Template, TemplateLookup""" + +import re + + +def convert_comments(text): + """preprocess old style comments. + + example: + + from mako.ext.preprocessors import convert_comments + t = Template(..., preprocessor=convert_comments)""" + return re.sub(r"(?<=\n)\s*#[^#]", "##", text) diff --git a/venv/lib/python3.8/site-packages/mako/ext/pygmentplugin.py b/venv/lib/python3.8/site-packages/mako/ext/pygmentplugin.py new file mode 100644 index 000000000..74a8fb9db --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ext/pygmentplugin.py @@ -0,0 +1,150 @@ +# ext/pygmentplugin.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from pygments import highlight +from pygments.formatters.html import HtmlFormatter +from pygments.lexer import bygroups +from pygments.lexer import DelegatingLexer +from pygments.lexer import include +from pygments.lexer import RegexLexer +from pygments.lexer import using +from pygments.lexers.agile import Python3Lexer +from pygments.lexers.agile import PythonLexer +from pygments.lexers.web import CssLexer +from pygments.lexers.web import HtmlLexer +from pygments.lexers.web import JavascriptLexer +from pygments.lexers.web import XmlLexer +from pygments.token import Comment +from pygments.token import Keyword +from pygments.token import Name +from pygments.token import Operator +from pygments.token import Other +from pygments.token import String +from pygments.token import Text + + +class MakoLexer(RegexLexer): + name = "Mako" + aliases = ["mako"] + filenames = ["*.mao"] + + tokens = { + "root": [ + ( + r"(\s*)(\%)(\s*end(?:\w+))(\n|\Z)", + bygroups(Text, Comment.Preproc, Keyword, Other), + ), + ( + r"(\s*)(\%(?!%))([^\n]*)(\n|\Z)", + bygroups(Text, Comment.Preproc, using(PythonLexer), Other), + ), + ( + r"(\s*)(##[^\n]*)(\n|\Z)", + bygroups(Text, Comment.Preproc, Other), + ), + (r"""(?s)<%doc>.*?""", Comment.Preproc), + ( + r"(<%)([\w\.\:]+)", + bygroups(Comment.Preproc, Name.Builtin), + "tag", + ), + ( + r"()", + bygroups(Comment.Preproc, Name.Builtin, Comment.Preproc), + ), + (r"<%(?=([\w\.\:]+))", Comment.Preproc, "ondeftags"), + ( + r"(?s)(<%(?:!?))(.*?)(%>)", + bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc), + ), + ( + r"(\$\{)(.*?)(\})", + bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc), + ), + ( + r"""(?sx) + (.+?) # anything, followed by: + (?: + (?<=\n)(?=%(?!%)|\#\#) | # an eval or comment line + (?=\#\*) | # multiline comment + (?=", Comment.Preproc, "#pop"), + (r"\s+", Text), + ], + "attr": [ + ('".*?"', String, "#pop"), + ("'.*?'", String, "#pop"), + (r"[^\s>]+", String, "#pop"), + ], + } + + +class MakoHtmlLexer(DelegatingLexer): + name = "HTML+Mako" + aliases = ["html+mako"] + + def __init__(self, **options): + super().__init__(HtmlLexer, MakoLexer, **options) + + +class MakoXmlLexer(DelegatingLexer): + name = "XML+Mako" + aliases = ["xml+mako"] + + def __init__(self, **options): + super().__init__(XmlLexer, MakoLexer, **options) + + +class MakoJavascriptLexer(DelegatingLexer): + name = "JavaScript+Mako" + aliases = ["js+mako", "javascript+mako"] + + def __init__(self, **options): + super().__init__(JavascriptLexer, MakoLexer, **options) + + +class MakoCssLexer(DelegatingLexer): + name = "CSS+Mako" + aliases = ["css+mako"] + + def __init__(self, **options): + super().__init__(CssLexer, MakoLexer, **options) + + +pygments_html_formatter = HtmlFormatter( + cssclass="syntax-highlighted", linenos=True +) + + +def syntax_highlight(filename="", language=None): + mako_lexer = MakoLexer() + python_lexer = Python3Lexer() + if filename.startswith("memory:") or language == "mako": + return lambda string: highlight( + string, mako_lexer, pygments_html_formatter + ) + return lambda string: highlight( + string, python_lexer, pygments_html_formatter + ) diff --git a/venv/lib/python3.8/site-packages/mako/ext/turbogears.py b/venv/lib/python3.8/site-packages/mako/ext/turbogears.py new file mode 100644 index 000000000..25ad35e77 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/ext/turbogears.py @@ -0,0 +1,61 @@ +# ext/turbogears.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from mako import compat +from mako.lookup import TemplateLookup +from mako.template import Template + + +class TGPlugin: + + """TurboGears compatible Template Plugin.""" + + def __init__(self, extra_vars_func=None, options=None, extension="mak"): + self.extra_vars_func = extra_vars_func + self.extension = extension + if not options: + options = {} + + # Pull the options out and initialize the lookup + lookup_options = {} + for k, v in options.items(): + if k.startswith("mako."): + lookup_options[k[5:]] = v + elif k in ["directories", "filesystem_checks", "module_directory"]: + lookup_options[k] = v + self.lookup = TemplateLookup(**lookup_options) + + self.tmpl_options = {} + # transfer lookup args to template args, based on those available + # in getargspec + for kw in compat.inspect_getargspec(Template.__init__)[0]: + if kw in lookup_options: + self.tmpl_options[kw] = lookup_options[kw] + + def load_template(self, templatename, template_string=None): + """Loads a template from a file or a string""" + if template_string is not None: + return Template(template_string, **self.tmpl_options) + # Translate TG dot notation to normal / template path + if "/" not in templatename: + templatename = ( + "/" + templatename.replace(".", "/") + "." + self.extension + ) + + # Lookup template + return self.lookup.get_template(templatename) + + def render( + self, info, format="html", fragment=False, template=None # noqa + ): + if isinstance(template, str): + template = self.load_template(template) + + # Load extra vars func if provided + if self.extra_vars_func: + info.update(self.extra_vars_func()) + + return template.render(**info) diff --git a/venv/lib/python3.8/site-packages/mako/filters.py b/venv/lib/python3.8/site-packages/mako/filters.py new file mode 100644 index 000000000..d5338bd9c --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/filters.py @@ -0,0 +1,163 @@ +# mako/filters.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +import codecs +from html.entities import codepoint2name +from html.entities import name2codepoint +import re +from urllib.parse import quote_plus + +import markupsafe + +html_escape = markupsafe.escape + +xml_escapes = { + "&": "&", + ">": ">", + "<": "<", + '"': """, # also " in html-only + "'": "'", # also ' in html-only +} + + +def xml_escape(string): + return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string) + + +def url_escape(string): + # convert into a list of octets + string = string.encode("utf8") + return quote_plus(string) + + +def trim(string): + return string.strip() + + +class Decode: + def __getattr__(self, key): + def decode(x): + if isinstance(x, str): + return x + elif not isinstance(x, bytes): + return decode(str(x)) + else: + return str(x, encoding=key) + + return decode + + +decode = Decode() + + +class XMLEntityEscaper: + def __init__(self, codepoint2name, name2codepoint): + self.codepoint2entity = { + c: str("&%s;" % n) for c, n in codepoint2name.items() + } + self.name2codepoint = name2codepoint + + def escape_entities(self, text): + """Replace characters with their character entity references. + + Only characters corresponding to a named entity are replaced. + """ + return str(text).translate(self.codepoint2entity) + + def __escape(self, m): + codepoint = ord(m.group()) + try: + return self.codepoint2entity[codepoint] + except (KeyError, IndexError): + return "&#x%X;" % codepoint + + __escapable = re.compile(r'["&<>]|[^\x00-\x7f]') + + def escape(self, text): + """Replace characters with their character references. + + Replace characters by their named entity references. + Non-ASCII characters, if they do not have a named entity reference, + are replaced by numerical character references. + + The return value is guaranteed to be ASCII. + """ + return self.__escapable.sub(self.__escape, str(text)).encode("ascii") + + # XXX: This regexp will not match all valid XML entity names__. + # (It punts on details involving involving CombiningChars and Extenders.) + # + # .. __: http://www.w3.org/TR/2000/REC-xml-20001006#NT-EntityRef + __characterrefs = re.compile( + r"""& (?: + \#(\d+) + | \#x([\da-f]+) + | ( (?!\d) [:\w] [-.:\w]+ ) + ) ;""", + re.X | re.UNICODE, + ) + + def __unescape(self, m): + dval, hval, name = m.groups() + if dval: + codepoint = int(dval) + elif hval: + codepoint = int(hval, 16) + else: + codepoint = self.name2codepoint.get(name, 0xFFFD) + # U+FFFD = "REPLACEMENT CHARACTER" + if codepoint < 128: + return chr(codepoint) + return chr(codepoint) + + def unescape(self, text): + """Unescape character references. + + All character references (both entity references and numerical + character references) are unescaped. + """ + return self.__characterrefs.sub(self.__unescape, text) + + +_html_entities_escaper = XMLEntityEscaper(codepoint2name, name2codepoint) + +html_entities_escape = _html_entities_escaper.escape_entities +html_entities_unescape = _html_entities_escaper.unescape + + +def htmlentityreplace_errors(ex): + """An encoding error handler. + + This python codecs error handler replaces unencodable + characters with HTML entities, or, if no HTML entity exists for + the character, XML character references:: + + >>> 'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace') + 'The cost was €12.' + """ + if isinstance(ex, UnicodeEncodeError): + # Handle encoding errors + bad_text = ex.object[ex.start : ex.end] + text = _html_entities_escaper.escape(bad_text) + return (str(text), ex.end) + raise ex + + +codecs.register_error("htmlentityreplace", htmlentityreplace_errors) + + +DEFAULT_ESCAPES = { + "x": "filters.xml_escape", + "h": "filters.html_escape", + "u": "filters.url_escape", + "trim": "filters.trim", + "entity": "filters.html_entities_escape", + "unicode": "str", + "decode": "decode", + "str": "str", + "n": "n", +} diff --git a/venv/lib/python3.8/site-packages/mako/lexer.py b/venv/lib/python3.8/site-packages/mako/lexer.py new file mode 100644 index 000000000..37afb4de2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/lexer.py @@ -0,0 +1,481 @@ +# mako/lexer.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""provides the Lexer class for parsing template strings into parse trees.""" + +import codecs +import re + +from mako import exceptions +from mako import parsetree +from mako.pygen import adjust_whitespace + +_regexp_cache = {} + + +class Lexer: + def __init__( + self, text, filename=None, input_encoding=None, preprocessor=None + ): + self.text = text + self.filename = filename + self.template = parsetree.TemplateNode(self.filename) + self.matched_lineno = 1 + self.matched_charpos = 0 + self.lineno = 1 + self.match_position = 0 + self.tag = [] + self.control_line = [] + self.ternary_stack = [] + self.encoding = input_encoding + + if preprocessor is None: + self.preprocessor = [] + elif not hasattr(preprocessor, "__iter__"): + self.preprocessor = [preprocessor] + else: + self.preprocessor = preprocessor + + @property + def exception_kwargs(self): + return { + "source": self.text, + "lineno": self.matched_lineno, + "pos": self.matched_charpos, + "filename": self.filename, + } + + def match(self, regexp, flags=None): + """compile the given regexp, cache the reg, and call match_reg().""" + + try: + reg = _regexp_cache[(regexp, flags)] + except KeyError: + reg = re.compile(regexp, flags) if flags else re.compile(regexp) + _regexp_cache[(regexp, flags)] = reg + + return self.match_reg(reg) + + def match_reg(self, reg): + """match the given regular expression object to the current text + position. + + if a match occurs, update the current text and line position. + + """ + + mp = self.match_position + + match = reg.match(self.text, self.match_position) + if match: + (start, end) = match.span() + self.match_position = end + 1 if end == start else end + self.matched_lineno = self.lineno + cp = mp - 1 + if cp >= 0 and cp < self.textlength: + cp = self.text[: cp + 1].rfind("\n") + self.matched_charpos = mp - cp + self.lineno += self.text[mp : self.match_position].count("\n") + return match + + def parse_until_text(self, watch_nesting, *text): + startpos = self.match_position + text_re = r"|".join(text) + brace_level = 0 + paren_level = 0 + bracket_level = 0 + while True: + match = self.match(r"#.*\n") + if match: + continue + match = self.match( + r"(\"\"\"|\'\'\'|\"|\')[^\\]*?(\\.[^\\]*?)*\1", re.S + ) + if match: + continue + match = self.match(r"(%s)" % text_re) + if match and not ( + watch_nesting + and (brace_level > 0 or paren_level > 0 or bracket_level > 0) + ): + return ( + self.text[ + startpos : self.match_position - len(match.group(1)) + ], + match.group(1), + ) + elif not match: + match = self.match(r"(.*?)(?=\"|\'|#|%s)" % text_re, re.S) + if match: + brace_level += match.group(1).count("{") + brace_level -= match.group(1).count("}") + paren_level += match.group(1).count("(") + paren_level -= match.group(1).count(")") + bracket_level += match.group(1).count("[") + bracket_level -= match.group(1).count("]") + continue + raise exceptions.SyntaxException( + "Expected: %s" % ",".join(text), **self.exception_kwargs + ) + + def append_node(self, nodecls, *args, **kwargs): + kwargs.setdefault("source", self.text) + kwargs.setdefault("lineno", self.matched_lineno) + kwargs.setdefault("pos", self.matched_charpos) + kwargs["filename"] = self.filename + node = nodecls(*args, **kwargs) + if len(self.tag): + self.tag[-1].nodes.append(node) + else: + self.template.nodes.append(node) + # build a set of child nodes for the control line + # (used for loop variable detection) + # also build a set of child nodes on ternary control lines + # (used for determining if a pass needs to be auto-inserted + if self.control_line: + control_frame = self.control_line[-1] + control_frame.nodes.append(node) + if ( + not ( + isinstance(node, parsetree.ControlLine) + and control_frame.is_ternary(node.keyword) + ) + and self.ternary_stack + and self.ternary_stack[-1] + ): + self.ternary_stack[-1][-1].nodes.append(node) + if isinstance(node, parsetree.Tag): + if len(self.tag): + node.parent = self.tag[-1] + self.tag.append(node) + elif isinstance(node, parsetree.ControlLine): + if node.isend: + self.control_line.pop() + self.ternary_stack.pop() + elif node.is_primary: + self.control_line.append(node) + self.ternary_stack.append([]) + elif self.control_line and self.control_line[-1].is_ternary( + node.keyword + ): + self.ternary_stack[-1].append(node) + elif self.control_line and not self.control_line[-1].is_ternary( + node.keyword + ): + raise exceptions.SyntaxException( + "Keyword '%s' not a legal ternary for keyword '%s'" + % (node.keyword, self.control_line[-1].keyword), + **self.exception_kwargs, + ) + + _coding_re = re.compile(r"#.*coding[:=]\s*([-\w.]+).*\r?\n") + + def decode_raw_stream(self, text, decode_raw, known_encoding, filename): + """given string/unicode or bytes/string, determine encoding + from magic encoding comment, return body as unicode + or raw if decode_raw=False + + """ + if isinstance(text, str): + m = self._coding_re.match(text) + encoding = m and m.group(1) or known_encoding or "utf-8" + return encoding, text + + if text.startswith(codecs.BOM_UTF8): + text = text[len(codecs.BOM_UTF8) :] + parsed_encoding = "utf-8" + m = self._coding_re.match(text.decode("utf-8", "ignore")) + if m is not None and m.group(1) != "utf-8": + raise exceptions.CompileException( + "Found utf-8 BOM in file, with conflicting " + "magic encoding comment of '%s'" % m.group(1), + text.decode("utf-8", "ignore"), + 0, + 0, + filename, + ) + else: + m = self._coding_re.match(text.decode("utf-8", "ignore")) + parsed_encoding = m.group(1) if m else known_encoding or "utf-8" + if decode_raw: + try: + text = text.decode(parsed_encoding) + except UnicodeDecodeError: + raise exceptions.CompileException( + "Unicode decode operation of encoding '%s' failed" + % parsed_encoding, + text.decode("utf-8", "ignore"), + 0, + 0, + filename, + ) + + return parsed_encoding, text + + def parse(self): + self.encoding, self.text = self.decode_raw_stream( + self.text, True, self.encoding, self.filename + ) + + for preproc in self.preprocessor: + self.text = preproc(self.text) + + # push the match marker past the + # encoding comment. + self.match_reg(self._coding_re) + + self.textlength = len(self.text) + + while True: + if self.match_position > self.textlength: + break + + if self.match_end(): + break + if self.match_expression(): + continue + if self.match_control_line(): + continue + if self.match_comment(): + continue + if self.match_tag_start(): + continue + if self.match_tag_end(): + continue + if self.match_python_block(): + continue + if self.match_percent(): + continue + if self.match_text(): + continue + + if self.match_position > self.textlength: + break + # TODO: no coverage here + raise exceptions.MakoException("assertion failed") + + if len(self.tag): + raise exceptions.SyntaxException( + "Unclosed tag: <%%%s>" % self.tag[-1].keyword, + **self.exception_kwargs, + ) + if len(self.control_line): + raise exceptions.SyntaxException( + "Unterminated control keyword: '%s'" + % self.control_line[-1].keyword, + self.text, + self.control_line[-1].lineno, + self.control_line[-1].pos, + self.filename, + ) + return self.template + + def match_tag_start(self): + reg = r""" + \<% # opening tag + + ([\w\.\:]+) # keyword + + ((?:\s+\w+|\s*=\s*|"[^"]*?"|'[^']*?'|\s*,\s*)*) # attrname, = \ + # sign, string expression + # comma is for backwards compat + # identified in #366 + + \s* # more whitespace + + (/)?> # closing + + """ + + match = self.match( + reg, + re.I | re.S | re.X, + ) + + if not match: + return False + + keyword, attr, isend = match.groups() + self.keyword = keyword + attributes = {} + if attr: + for att in re.findall( + r"\s*(\w+)\s*=\s*(?:'([^']*)'|\"([^\"]*)\")", attr + ): + key, val1, val2 = att + text = val1 or val2 + text = text.replace("\r\n", "\n") + attributes[key] = text + self.append_node(parsetree.Tag, keyword, attributes) + if isend: + self.tag.pop() + elif keyword == "text": + match = self.match(r"(.*?)(?=\)", re.S) + if not match: + raise exceptions.SyntaxException( + "Unclosed tag: <%%%s>" % self.tag[-1].keyword, + **self.exception_kwargs, + ) + self.append_node(parsetree.Text, match.group(1)) + return self.match_tag_end() + return True + + def match_tag_end(self): + match = self.match(r"\") + if match: + if not len(self.tag): + raise exceptions.SyntaxException( + "Closing tag without opening tag: " + % match.group(1), + **self.exception_kwargs, + ) + elif self.tag[-1].keyword != match.group(1): + raise exceptions.SyntaxException( + "Closing tag does not match tag: <%%%s>" + % (match.group(1), self.tag[-1].keyword), + **self.exception_kwargs, + ) + self.tag.pop() + return True + else: + return False + + def match_end(self): + match = self.match(r"\Z", re.S) + if not match: + return False + + string = match.group() + if string: + return string + else: + return True + + def match_percent(self): + match = self.match(r"(?<=^)(\s*)%%(%*)", re.M) + if match: + self.append_node( + parsetree.Text, match.group(1) + "%" + match.group(2) + ) + return True + else: + return False + + def match_text(self): + match = self.match( + r""" + (.*?) # anything, followed by: + ( + (?<=\n)(?=[ \t]*(?=%|\#\#)) # an eval or line-based + # comment, preceded by a + # consumed newline and whitespace + | + (?=\${) # an expression + | + (?=") + # the trailing newline helps + # compiler.parse() not complain about indentation + text = adjust_whitespace(text) + "\n" + self.append_node( + parsetree.Code, + text, + match.group(1) == "!", + lineno=line, + pos=pos, + ) + return True + else: + return False + + def match_expression(self): + match = self.match(r"\${") + if not match: + return False + + line, pos = self.matched_lineno, self.matched_charpos + text, end = self.parse_until_text(True, r"\|", r"}") + if end == "|": + escapes, end = self.parse_until_text(True, r"}") + else: + escapes = "" + text = text.replace("\r\n", "\n") + self.append_node( + parsetree.Expression, + text, + escapes.strip(), + lineno=line, + pos=pos, + ) + return True + + def match_control_line(self): + match = self.match( + r"(?<=^)[\t ]*(%(?!%)|##)[\t ]*((?:(?:\\\r?\n)|[^\r\n])*)" + r"(?:\r?\n|\Z)", + re.M, + ) + if not match: + return False + + operator = match.group(1) + text = match.group(2) + if operator == "%": + m2 = re.match(r"(end)?(\w+)\s*(.*)", text) + if not m2: + raise exceptions.SyntaxException( + "Invalid control line: '%s'" % text, + **self.exception_kwargs, + ) + isend, keyword = m2.group(1, 2) + isend = isend is not None + + if isend: + if not len(self.control_line): + raise exceptions.SyntaxException( + "No starting keyword '%s' for '%s'" % (keyword, text), + **self.exception_kwargs, + ) + elif self.control_line[-1].keyword != keyword: + raise exceptions.SyntaxException( + "Keyword '%s' doesn't match keyword '%s'" + % (text, self.control_line[-1].keyword), + **self.exception_kwargs, + ) + self.append_node(parsetree.ControlLine, keyword, isend, text) + else: + self.append_node(parsetree.Comment, text) + return True + + def match_comment(self): + """matches the multiline version of a comment""" + match = self.match(r"<%doc>(.*?)", re.S) + if match: + self.append_node(parsetree.Comment, match.group(1)) + return True + else: + return False diff --git a/venv/lib/python3.8/site-packages/mako/lookup.py b/venv/lib/python3.8/site-packages/mako/lookup.py new file mode 100644 index 000000000..1a431c805 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/lookup.py @@ -0,0 +1,361 @@ +# mako/lookup.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +import os +import posixpath +import re +import stat +import threading + +from mako import exceptions +from mako import util +from mako.template import Template + + +class TemplateCollection: + + """Represent a collection of :class:`.Template` objects, + identifiable via URI. + + A :class:`.TemplateCollection` is linked to the usage of + all template tags that address other templates, such + as ``<%include>``, ``<%namespace>``, and ``<%inherit>``. + The ``file`` attribute of each of those tags refers + to a string URI that is passed to that :class:`.Template` + object's :class:`.TemplateCollection` for resolution. + + :class:`.TemplateCollection` is an abstract class, + with the usual default implementation being :class:`.TemplateLookup`. + + """ + + def has_template(self, uri): + """Return ``True`` if this :class:`.TemplateLookup` is + capable of returning a :class:`.Template` object for the + given ``uri``. + + :param uri: String URI of the template to be resolved. + + """ + try: + self.get_template(uri) + return True + except exceptions.TemplateLookupException: + return False + + def get_template(self, uri, relativeto=None): + """Return a :class:`.Template` object corresponding to the given + ``uri``. + + The default implementation raises + :class:`.NotImplementedError`. Implementations should + raise :class:`.TemplateLookupException` if the given ``uri`` + cannot be resolved. + + :param uri: String URI of the template to be resolved. + :param relativeto: if present, the given ``uri`` is assumed to + be relative to this URI. + + """ + raise NotImplementedError() + + def filename_to_uri(self, uri, filename): + """Convert the given ``filename`` to a URI relative to + this :class:`.TemplateCollection`.""" + + return uri + + def adjust_uri(self, uri, filename): + """Adjust the given ``uri`` based on the calling ``filename``. + + When this method is called from the runtime, the + ``filename`` parameter is taken directly to the ``filename`` + attribute of the calling template. Therefore a custom + :class:`.TemplateCollection` subclass can place any string + identifier desired in the ``filename`` parameter of the + :class:`.Template` objects it constructs and have them come back + here. + + """ + return uri + + +class TemplateLookup(TemplateCollection): + + """Represent a collection of templates that locates template source files + from the local filesystem. + + The primary argument is the ``directories`` argument, the list of + directories to search: + + .. sourcecode:: python + + lookup = TemplateLookup(["/path/to/templates"]) + some_template = lookup.get_template("/index.html") + + The :class:`.TemplateLookup` can also be given :class:`.Template` objects + programatically using :meth:`.put_string` or :meth:`.put_template`: + + .. sourcecode:: python + + lookup = TemplateLookup() + lookup.put_string("base.html", ''' + ${self.next()} + ''') + lookup.put_string("hello.html", ''' + <%include file='base.html'/> + + Hello, world ! + ''') + + + :param directories: A list of directory names which will be + searched for a particular template URI. The URI is appended + to each directory and the filesystem checked. + + :param collection_size: Approximate size of the collection used + to store templates. If left at its default of ``-1``, the size + is unbounded, and a plain Python dictionary is used to + relate URI strings to :class:`.Template` instances. + Otherwise, a least-recently-used cache object is used which + will maintain the size of the collection approximately to + the number given. + + :param filesystem_checks: When at its default value of ``True``, + each call to :meth:`.TemplateLookup.get_template()` will + compare the filesystem last modified time to the time in + which an existing :class:`.Template` object was created. + This allows the :class:`.TemplateLookup` to regenerate a + new :class:`.Template` whenever the original source has + been updated. Set this to ``False`` for a very minor + performance increase. + + :param modulename_callable: A callable which, when present, + is passed the path of the source file as well as the + requested URI, and then returns the full path of the + generated Python module file. This is used to inject + alternate schemes for Python module location. If left at + its default of ``None``, the built in system of generation + based on ``module_directory`` plus ``uri`` is used. + + All other keyword parameters available for + :class:`.Template` are mirrored here. When new + :class:`.Template` objects are created, the keywords + established with this :class:`.TemplateLookup` are passed on + to each new :class:`.Template`. + + """ + + def __init__( + self, + directories=None, + module_directory=None, + filesystem_checks=True, + collection_size=-1, + format_exceptions=False, + error_handler=None, + output_encoding=None, + encoding_errors="strict", + cache_args=None, + cache_impl="beaker", + cache_enabled=True, + cache_type=None, + cache_dir=None, + cache_url=None, + modulename_callable=None, + module_writer=None, + default_filters=None, + buffer_filters=(), + strict_undefined=False, + imports=None, + future_imports=None, + enable_loop=True, + input_encoding=None, + preprocessor=None, + lexer_cls=None, + include_error_handler=None, + ): + self.directories = [ + posixpath.normpath(d) for d in util.to_list(directories, ()) + ] + self.module_directory = module_directory + self.modulename_callable = modulename_callable + self.filesystem_checks = filesystem_checks + self.collection_size = collection_size + + if cache_args is None: + cache_args = {} + # transfer deprecated cache_* args + if cache_dir: + cache_args.setdefault("dir", cache_dir) + if cache_url: + cache_args.setdefault("url", cache_url) + if cache_type: + cache_args.setdefault("type", cache_type) + + self.template_args = { + "format_exceptions": format_exceptions, + "error_handler": error_handler, + "include_error_handler": include_error_handler, + "output_encoding": output_encoding, + "cache_impl": cache_impl, + "encoding_errors": encoding_errors, + "input_encoding": input_encoding, + "module_directory": module_directory, + "module_writer": module_writer, + "cache_args": cache_args, + "cache_enabled": cache_enabled, + "default_filters": default_filters, + "buffer_filters": buffer_filters, + "strict_undefined": strict_undefined, + "imports": imports, + "future_imports": future_imports, + "enable_loop": enable_loop, + "preprocessor": preprocessor, + "lexer_cls": lexer_cls, + } + + if collection_size == -1: + self._collection = {} + self._uri_cache = {} + else: + self._collection = util.LRUCache(collection_size) + self._uri_cache = util.LRUCache(collection_size) + self._mutex = threading.Lock() + + def get_template(self, uri): + """Return a :class:`.Template` object corresponding to the given + ``uri``. + + .. note:: The ``relativeto`` argument is not supported here at + the moment. + + """ + + try: + if self.filesystem_checks: + return self._check(uri, self._collection[uri]) + else: + return self._collection[uri] + except KeyError as e: + u = re.sub(r"^\/+", "", uri) + for dir_ in self.directories: + # make sure the path seperators are posix - os.altsep is empty + # on POSIX and cannot be used. + dir_ = dir_.replace(os.path.sep, posixpath.sep) + srcfile = posixpath.normpath(posixpath.join(dir_, u)) + if os.path.isfile(srcfile): + return self._load(srcfile, uri) + else: + raise exceptions.TopLevelLookupException( + "Can't locate template for uri %r" % uri + ) from e + + def adjust_uri(self, uri, relativeto): + """Adjust the given ``uri`` based on the given relative URI.""" + + key = (uri, relativeto) + if key in self._uri_cache: + return self._uri_cache[key] + + if uri[0] == "/": + v = self._uri_cache[key] = uri + elif relativeto is not None: + v = self._uri_cache[key] = posixpath.join( + posixpath.dirname(relativeto), uri + ) + else: + v = self._uri_cache[key] = "/" + uri + return v + + def filename_to_uri(self, filename): + """Convert the given ``filename`` to a URI relative to + this :class:`.TemplateCollection`.""" + + try: + return self._uri_cache[filename] + except KeyError: + value = self._relativeize(filename) + self._uri_cache[filename] = value + return value + + def _relativeize(self, filename): + """Return the portion of a filename that is 'relative' + to the directories in this lookup. + + """ + + filename = posixpath.normpath(filename) + for dir_ in self.directories: + if filename[0 : len(dir_)] == dir_: + return filename[len(dir_) :] + else: + return None + + def _load(self, filename, uri): + self._mutex.acquire() + try: + try: + # try returning from collection one + # more time in case concurrent thread already loaded + return self._collection[uri] + except KeyError: + pass + try: + if self.modulename_callable is not None: + module_filename = self.modulename_callable(filename, uri) + else: + module_filename = None + self._collection[uri] = template = Template( + uri=uri, + filename=posixpath.normpath(filename), + lookup=self, + module_filename=module_filename, + **self.template_args, + ) + return template + except: + # if compilation fails etc, ensure + # template is removed from collection, + # re-raise + self._collection.pop(uri, None) + raise + finally: + self._mutex.release() + + def _check(self, uri, template): + if template.filename is None: + return template + + try: + template_stat = os.stat(template.filename) + if template.module._modified_time >= template_stat[stat.ST_MTIME]: + return template + self._collection.pop(uri, None) + return self._load(template.filename, uri) + except OSError as e: + self._collection.pop(uri, None) + raise exceptions.TemplateLookupException( + "Can't locate template for uri %r" % uri + ) from e + + def put_string(self, uri, text): + """Place a new :class:`.Template` object into this + :class:`.TemplateLookup`, based on the given string of + ``text``. + + """ + self._collection[uri] = Template( + text, lookup=self, uri=uri, **self.template_args + ) + + def put_template(self, uri, template): + """Place a new :class:`.Template` object into this + :class:`.TemplateLookup`, based on the given + :class:`.Template` object. + + """ + self._collection[uri] = template diff --git a/venv/lib/python3.8/site-packages/mako/parsetree.py b/venv/lib/python3.8/site-packages/mako/parsetree.py new file mode 100644 index 000000000..f7bdda8d6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/parsetree.py @@ -0,0 +1,656 @@ +# mako/parsetree.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""defines the parse tree components for Mako templates.""" + +import re + +from mako import ast +from mako import exceptions +from mako import filters +from mako import util + + +class Node: + + """base class for a Node in the parse tree.""" + + def __init__(self, source, lineno, pos, filename): + self.source = source + self.lineno = lineno + self.pos = pos + self.filename = filename + + @property + def exception_kwargs(self): + return { + "source": self.source, + "lineno": self.lineno, + "pos": self.pos, + "filename": self.filename, + } + + def get_children(self): + return [] + + def accept_visitor(self, visitor): + def traverse(node): + for n in node.get_children(): + n.accept_visitor(visitor) + + method = getattr(visitor, "visit" + self.__class__.__name__, traverse) + method(self) + + +class TemplateNode(Node): + + """a 'container' node that stores the overall collection of nodes.""" + + def __init__(self, filename): + super().__init__("", 0, 0, filename) + self.nodes = [] + self.page_attributes = {} + + def get_children(self): + return self.nodes + + def __repr__(self): + return "TemplateNode(%s, %r)" % ( + util.sorted_dict_repr(self.page_attributes), + self.nodes, + ) + + +class ControlLine(Node): + + """defines a control line, a line-oriented python line or end tag. + + e.g.:: + + % if foo: + (markup) + % endif + + """ + + has_loop_context = False + + def __init__(self, keyword, isend, text, **kwargs): + super().__init__(**kwargs) + self.text = text + self.keyword = keyword + self.isend = isend + self.is_primary = keyword in ["for", "if", "while", "try", "with"] + self.nodes = [] + if self.isend: + self._declared_identifiers = [] + self._undeclared_identifiers = [] + else: + code = ast.PythonFragment(text, **self.exception_kwargs) + self._declared_identifiers = code.declared_identifiers + self._undeclared_identifiers = code.undeclared_identifiers + + def get_children(self): + return self.nodes + + def declared_identifiers(self): + return self._declared_identifiers + + def undeclared_identifiers(self): + return self._undeclared_identifiers + + def is_ternary(self, keyword): + """return true if the given keyword is a ternary keyword + for this ControlLine""" + + cases = { + "if": {"else", "elif"}, + "try": {"except", "finally"}, + "for": {"else"}, + } + + return keyword in cases.get(self.keyword, set()) + + def __repr__(self): + return "ControlLine(%r, %r, %r, %r)" % ( + self.keyword, + self.text, + self.isend, + (self.lineno, self.pos), + ) + + +class Text(Node): + """defines plain text in the template.""" + + def __init__(self, content, **kwargs): + super().__init__(**kwargs) + self.content = content + + def __repr__(self): + return "Text(%r, %r)" % (self.content, (self.lineno, self.pos)) + + +class Code(Node): + """defines a Python code block, either inline or module level. + + e.g.:: + + inline: + <% + x = 12 + %> + + module level: + <%! + import logger + %> + + """ + + def __init__(self, text, ismodule, **kwargs): + super().__init__(**kwargs) + self.text = text + self.ismodule = ismodule + self.code = ast.PythonCode(text, **self.exception_kwargs) + + def declared_identifiers(self): + return self.code.declared_identifiers + + def undeclared_identifiers(self): + return self.code.undeclared_identifiers + + def __repr__(self): + return "Code(%r, %r, %r)" % ( + self.text, + self.ismodule, + (self.lineno, self.pos), + ) + + +class Comment(Node): + """defines a comment line. + + # this is a comment + + """ + + def __init__(self, text, **kwargs): + super().__init__(**kwargs) + self.text = text + + def __repr__(self): + return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos)) + + +class Expression(Node): + """defines an inline expression. + + ${x+y} + + """ + + def __init__(self, text, escapes, **kwargs): + super().__init__(**kwargs) + self.text = text + self.escapes = escapes + self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs) + self.code = ast.PythonCode(text, **self.exception_kwargs) + + def declared_identifiers(self): + return [] + + def undeclared_identifiers(self): + # TODO: make the "filter" shortcut list configurable at parse/gen time + return self.code.undeclared_identifiers.union( + self.escapes_code.undeclared_identifiers.difference( + filters.DEFAULT_ESCAPES + ) + ).difference(self.code.declared_identifiers) + + def __repr__(self): + return "Expression(%r, %r, %r)" % ( + self.text, + self.escapes_code.args, + (self.lineno, self.pos), + ) + + +class _TagMeta(type): + """metaclass to allow Tag to produce a subclass according to + its keyword""" + + _classmap = {} + + def __init__(cls, clsname, bases, dict_): + if getattr(cls, "__keyword__", None) is not None: + cls._classmap[cls.__keyword__] = cls + super().__init__(clsname, bases, dict_) + + def __call__(cls, keyword, attributes, **kwargs): + if ":" in keyword: + ns, defname = keyword.split(":") + return type.__call__( + CallNamespaceTag, ns, defname, attributes, **kwargs + ) + + try: + cls = _TagMeta._classmap[keyword] + except KeyError: + raise exceptions.CompileException( + "No such tag: '%s'" % keyword, + source=kwargs["source"], + lineno=kwargs["lineno"], + pos=kwargs["pos"], + filename=kwargs["filename"], + ) + return type.__call__(cls, keyword, attributes, **kwargs) + + +class Tag(Node, metaclass=_TagMeta): + """abstract base class for tags. + + e.g.:: + + <%sometag/> + + <%someothertag> + stuff + + + """ + + __keyword__ = None + + def __init__( + self, + keyword, + attributes, + expressions, + nonexpressions, + required, + **kwargs, + ): + r"""construct a new Tag instance. + + this constructor not called directly, and is only called + by subclasses. + + :param keyword: the tag keyword + + :param attributes: raw dictionary of attribute key/value pairs + + :param expressions: a set of identifiers that are legal attributes, + which can also contain embedded expressions + + :param nonexpressions: a set of identifiers that are legal + attributes, which cannot contain embedded expressions + + :param \**kwargs: + other arguments passed to the Node superclass (lineno, pos) + + """ + super().__init__(**kwargs) + self.keyword = keyword + self.attributes = attributes + self._parse_attributes(expressions, nonexpressions) + missing = [r for r in required if r not in self.parsed_attributes] + if len(missing): + raise exceptions.CompileException( + ( + "Missing attribute(s): %s" + % ",".join(repr(m) for m in missing) + ), + **self.exception_kwargs, + ) + + self.parent = None + self.nodes = [] + + def is_root(self): + return self.parent is None + + def get_children(self): + return self.nodes + + def _parse_attributes(self, expressions, nonexpressions): + undeclared_identifiers = set() + self.parsed_attributes = {} + for key in self.attributes: + if key in expressions: + expr = [] + for x in re.compile(r"(\${(?:[^$]*?{.+|.+?)})", re.S).split( + self.attributes[key] + ): + m = re.compile(r"^\${(.+?)}$", re.S).match(x) + if m: + code = ast.PythonCode( + m.group(1).rstrip(), **self.exception_kwargs + ) + # we aren't discarding "declared_identifiers" here, + # which we do so that list comprehension-declared + # variables aren't counted. As yet can't find a + # condition that requires it here. + undeclared_identifiers = undeclared_identifiers.union( + code.undeclared_identifiers + ) + expr.append("(%s)" % m.group(1)) + elif x: + expr.append(repr(x)) + self.parsed_attributes[key] = " + ".join(expr) or repr("") + elif key in nonexpressions: + if re.search(r"\${.+?}", self.attributes[key]): + raise exceptions.CompileException( + "Attribute '%s' in tag '%s' does not allow embedded " + "expressions" % (key, self.keyword), + **self.exception_kwargs, + ) + self.parsed_attributes[key] = repr(self.attributes[key]) + else: + raise exceptions.CompileException( + "Invalid attribute for tag '%s': '%s'" + % (self.keyword, key), + **self.exception_kwargs, + ) + self.expression_undeclared_identifiers = undeclared_identifiers + + def declared_identifiers(self): + return [] + + def undeclared_identifiers(self): + return self.expression_undeclared_identifiers + + def __repr__(self): + return "%s(%r, %s, %r, %r)" % ( + self.__class__.__name__, + self.keyword, + util.sorted_dict_repr(self.attributes), + (self.lineno, self.pos), + self.nodes, + ) + + +class IncludeTag(Tag): + __keyword__ = "include" + + def __init__(self, keyword, attributes, **kwargs): + super().__init__( + keyword, + attributes, + ("file", "import", "args"), + (), + ("file",), + **kwargs, + ) + self.page_args = ast.PythonCode( + "__DUMMY(%s)" % attributes.get("args", ""), **self.exception_kwargs + ) + + def declared_identifiers(self): + return [] + + def undeclared_identifiers(self): + identifiers = self.page_args.undeclared_identifiers.difference( + {"__DUMMY"} + ).difference(self.page_args.declared_identifiers) + return identifiers.union(super().undeclared_identifiers()) + + +class NamespaceTag(Tag): + __keyword__ = "namespace" + + def __init__(self, keyword, attributes, **kwargs): + super().__init__( + keyword, + attributes, + ("file",), + ("name", "inheritable", "import", "module"), + (), + **kwargs, + ) + + self.name = attributes.get("name", "__anon_%s" % hex(abs(id(self)))) + if "name" not in attributes and "import" not in attributes: + raise exceptions.CompileException( + "'name' and/or 'import' attributes are required " + "for <%namespace>", + **self.exception_kwargs, + ) + if "file" in attributes and "module" in attributes: + raise exceptions.CompileException( + "<%namespace> may only have one of 'file' or 'module'", + **self.exception_kwargs, + ) + + def declared_identifiers(self): + return [] + + +class TextTag(Tag): + __keyword__ = "text" + + def __init__(self, keyword, attributes, **kwargs): + super().__init__(keyword, attributes, (), ("filter"), (), **kwargs) + self.filter_args = ast.ArgumentList( + attributes.get("filter", ""), **self.exception_kwargs + ) + + def undeclared_identifiers(self): + return self.filter_args.undeclared_identifiers.difference( + filters.DEFAULT_ESCAPES.keys() + ).union(self.expression_undeclared_identifiers) + + +class DefTag(Tag): + __keyword__ = "def" + + def __init__(self, keyword, attributes, **kwargs): + expressions = ["buffered", "cached"] + [ + c for c in attributes if c.startswith("cache_") + ] + + super().__init__( + keyword, + attributes, + expressions, + ("name", "filter", "decorator"), + ("name",), + **kwargs, + ) + name = attributes["name"] + if re.match(r"^[\w_]+$", name): + raise exceptions.CompileException( + "Missing parenthesis in %def", **self.exception_kwargs + ) + self.function_decl = ast.FunctionDecl( + "def " + name + ":pass", **self.exception_kwargs + ) + self.name = self.function_decl.funcname + self.decorator = attributes.get("decorator", "") + self.filter_args = ast.ArgumentList( + attributes.get("filter", ""), **self.exception_kwargs + ) + + is_anonymous = False + is_block = False + + @property + def funcname(self): + return self.function_decl.funcname + + def get_argument_expressions(self, **kw): + return self.function_decl.get_argument_expressions(**kw) + + def declared_identifiers(self): + return self.function_decl.allargnames + + def undeclared_identifiers(self): + res = [] + for c in self.function_decl.defaults: + res += list( + ast.PythonCode( + c, **self.exception_kwargs + ).undeclared_identifiers + ) + return ( + set(res) + .union( + self.filter_args.undeclared_identifiers.difference( + filters.DEFAULT_ESCAPES.keys() + ) + ) + .union(self.expression_undeclared_identifiers) + .difference(self.function_decl.allargnames) + ) + + +class BlockTag(Tag): + __keyword__ = "block" + + def __init__(self, keyword, attributes, **kwargs): + expressions = ["buffered", "cached", "args"] + [ + c for c in attributes if c.startswith("cache_") + ] + + super().__init__( + keyword, + attributes, + expressions, + ("name", "filter", "decorator"), + (), + **kwargs, + ) + name = attributes.get("name") + if name and not re.match(r"^[\w_]+$", name): + raise exceptions.CompileException( + "%block may not specify an argument signature", + **self.exception_kwargs, + ) + if not name and attributes.get("args", None): + raise exceptions.CompileException( + "Only named %blocks may specify args", **self.exception_kwargs + ) + self.body_decl = ast.FunctionArgs( + attributes.get("args", ""), **self.exception_kwargs + ) + + self.name = name + self.decorator = attributes.get("decorator", "") + self.filter_args = ast.ArgumentList( + attributes.get("filter", ""), **self.exception_kwargs + ) + + is_block = True + + @property + def is_anonymous(self): + return self.name is None + + @property + def funcname(self): + return self.name or "__M_anon_%d" % (self.lineno,) + + def get_argument_expressions(self, **kw): + return self.body_decl.get_argument_expressions(**kw) + + def declared_identifiers(self): + return self.body_decl.allargnames + + def undeclared_identifiers(self): + return ( + self.filter_args.undeclared_identifiers.difference( + filters.DEFAULT_ESCAPES.keys() + ) + ).union(self.expression_undeclared_identifiers) + + +class CallTag(Tag): + __keyword__ = "call" + + def __init__(self, keyword, attributes, **kwargs): + super().__init__( + keyword, attributes, ("args"), ("expr",), ("expr",), **kwargs + ) + self.expression = attributes["expr"] + self.code = ast.PythonCode(self.expression, **self.exception_kwargs) + self.body_decl = ast.FunctionArgs( + attributes.get("args", ""), **self.exception_kwargs + ) + + def declared_identifiers(self): + return self.code.declared_identifiers.union(self.body_decl.allargnames) + + def undeclared_identifiers(self): + return self.code.undeclared_identifiers.difference( + self.code.declared_identifiers + ) + + +class CallNamespaceTag(Tag): + def __init__(self, namespace, defname, attributes, **kwargs): + super().__init__( + namespace + ":" + defname, + attributes, + tuple(attributes.keys()) + ("args",), + (), + (), + **kwargs, + ) + + self.expression = "%s.%s(%s)" % ( + namespace, + defname, + ",".join( + "%s=%s" % (k, v) + for k, v in self.parsed_attributes.items() + if k != "args" + ), + ) + + self.code = ast.PythonCode(self.expression, **self.exception_kwargs) + self.body_decl = ast.FunctionArgs( + attributes.get("args", ""), **self.exception_kwargs + ) + + def declared_identifiers(self): + return self.code.declared_identifiers.union(self.body_decl.allargnames) + + def undeclared_identifiers(self): + return self.code.undeclared_identifiers.difference( + self.code.declared_identifiers + ) + + +class InheritTag(Tag): + __keyword__ = "inherit" + + def __init__(self, keyword, attributes, **kwargs): + super().__init__( + keyword, attributes, ("file",), (), ("file",), **kwargs + ) + + +class PageTag(Tag): + __keyword__ = "page" + + def __init__(self, keyword, attributes, **kwargs): + expressions = [ + "cached", + "args", + "expression_filter", + "enable_loop", + ] + [c for c in attributes if c.startswith("cache_")] + + super().__init__(keyword, attributes, expressions, (), (), **kwargs) + self.body_decl = ast.FunctionArgs( + attributes.get("args", ""), **self.exception_kwargs + ) + self.filter_args = ast.ArgumentList( + attributes.get("expression_filter", ""), **self.exception_kwargs + ) + + def declared_identifiers(self): + return self.body_decl.allargnames diff --git a/venv/lib/python3.8/site-packages/mako/pygen.py b/venv/lib/python3.8/site-packages/mako/pygen.py new file mode 100644 index 000000000..41f9afdaa --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/pygen.py @@ -0,0 +1,309 @@ +# mako/pygen.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""utilities for generating and formatting literal Python code.""" + +import re + +from mako import exceptions + + +class PythonPrinter: + def __init__(self, stream): + # indentation counter + self.indent = 0 + + # a stack storing information about why we incremented + # the indentation counter, to help us determine if we + # should decrement it + self.indent_detail = [] + + # the string of whitespace multiplied by the indent + # counter to produce a line + self.indentstring = " " + + # the stream we are writing to + self.stream = stream + + # current line number + self.lineno = 1 + + # a list of lines that represents a buffered "block" of code, + # which can be later printed relative to an indent level + self.line_buffer = [] + + self.in_indent_lines = False + + self._reset_multi_line_flags() + + # mapping of generated python lines to template + # source lines + self.source_map = {} + + self._re_space_comment = re.compile(r"^\s*#") + self._re_space = re.compile(r"^\s*$") + self._re_indent = re.compile(r":[ \t]*(?:#.*)?$") + self._re_compound = re.compile(r"^\s*(if|try|elif|while|for|with)") + self._re_indent_keyword = re.compile( + r"^\s*(def|class|else|elif|except|finally)" + ) + self._re_unindentor = re.compile(r"^\s*(else|elif|except|finally).*\:") + + def _update_lineno(self, num): + self.lineno += num + + def start_source(self, lineno): + if self.lineno not in self.source_map: + self.source_map[self.lineno] = lineno + + def write_blanks(self, num): + self.stream.write("\n" * num) + self._update_lineno(num) + + def write_indented_block(self, block, starting_lineno=None): + """print a line or lines of python which already contain indentation. + + The indentation of the total block of lines will be adjusted to that of + the current indent level.""" + self.in_indent_lines = False + for i, l in enumerate(re.split(r"\r?\n", block)): + self.line_buffer.append(l) + if starting_lineno is not None: + self.start_source(starting_lineno + i) + self._update_lineno(1) + + def writelines(self, *lines): + """print a series of lines of python.""" + for line in lines: + self.writeline(line) + + def writeline(self, line): + """print a line of python, indenting it according to the current + indent level. + + this also adjusts the indentation counter according to the + content of the line. + + """ + + if not self.in_indent_lines: + self._flush_adjusted_lines() + self.in_indent_lines = True + + if ( + line is None + or self._re_space_comment.match(line) + or self._re_space.match(line) + ): + hastext = False + else: + hastext = True + + is_comment = line and len(line) and line[0] == "#" + + # see if this line should decrease the indentation level + if ( + not is_comment + and (not hastext or self._is_unindentor(line)) + and self.indent > 0 + ): + self.indent -= 1 + # if the indent_detail stack is empty, the user + # probably put extra closures - the resulting + # module wont compile. + if len(self.indent_detail) == 0: + # TODO: no coverage here + raise exceptions.MakoException("Too many whitespace closures") + self.indent_detail.pop() + + if line is None: + return + + # write the line + self.stream.write(self._indent_line(line) + "\n") + self._update_lineno(len(line.split("\n"))) + + # see if this line should increase the indentation level. + # note that a line can both decrase (before printing) and + # then increase (after printing) the indentation level. + + if self._re_indent.search(line): + # increment indentation count, and also + # keep track of what the keyword was that indented us, + # if it is a python compound statement keyword + # where we might have to look for an "unindent" keyword + match = self._re_compound.match(line) + if match: + # its a "compound" keyword, so we will check for "unindentors" + indentor = match.group(1) + self.indent += 1 + self.indent_detail.append(indentor) + else: + indentor = None + # its not a "compound" keyword. but lets also + # test for valid Python keywords that might be indenting us, + # else assume its a non-indenting line + m2 = self._re_indent_keyword.match(line) + if m2: + self.indent += 1 + self.indent_detail.append(indentor) + + def close(self): + """close this printer, flushing any remaining lines.""" + self._flush_adjusted_lines() + + def _is_unindentor(self, line): + """return true if the given line is an 'unindentor', + relative to the last 'indent' event received. + + """ + + # no indentation detail has been pushed on; return False + if len(self.indent_detail) == 0: + return False + + indentor = self.indent_detail[-1] + + # the last indent keyword we grabbed is not a + # compound statement keyword; return False + if indentor is None: + return False + + # if the current line doesnt have one of the "unindentor" keywords, + # return False + match = self._re_unindentor.match(line) + # if True, whitespace matches up, we have a compound indentor, + # and this line has an unindentor, this + # is probably good enough + return bool(match) + + # should we decide that its not good enough, heres + # more stuff to check. + # keyword = match.group(1) + + # match the original indent keyword + # for crit in [ + # (r'if|elif', r'else|elif'), + # (r'try', r'except|finally|else'), + # (r'while|for', r'else'), + # ]: + # if re.match(crit[0], indentor) and re.match(crit[1], keyword): + # return True + + # return False + + def _indent_line(self, line, stripspace=""): + """indent the given line according to the current indent level. + + stripspace is a string of space that will be truncated from the + start of the line before indenting.""" + if stripspace == "": + # Fast path optimization. + return self.indentstring * self.indent + line + + return re.sub( + r"^%s" % stripspace, self.indentstring * self.indent, line + ) + + def _reset_multi_line_flags(self): + """reset the flags which would indicate we are in a backslashed + or triple-quoted section.""" + + self.backslashed, self.triplequoted = False, False + + def _in_multi_line(self, line): + """return true if the given line is part of a multi-line block, + via backslash or triple-quote.""" + + # we are only looking for explicitly joined lines here, not + # implicit ones (i.e. brackets, braces etc.). this is just to + # guard against the possibility of modifying the space inside of + # a literal multiline string with unfortunately placed + # whitespace + + current_state = self.backslashed or self.triplequoted + + self.backslashed = bool(re.search(r"\\$", line)) + triples = len(re.findall(r"\"\"\"|\'\'\'", line)) + if triples == 1 or triples % 2 != 0: + self.triplequoted = not self.triplequoted + + return current_state + + def _flush_adjusted_lines(self): + stripspace = None + self._reset_multi_line_flags() + + for entry in self.line_buffer: + if self._in_multi_line(entry): + self.stream.write(entry + "\n") + else: + entry = entry.expandtabs() + if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry): + stripspace = re.match(r"^([ \t]*)", entry).group(1) + self.stream.write(self._indent_line(entry, stripspace) + "\n") + + self.line_buffer = [] + self._reset_multi_line_flags() + + +def adjust_whitespace(text): + """remove the left-whitespace margin of a block of Python code.""" + + state = [False, False] + (backslashed, triplequoted) = (0, 1) + + def in_multi_line(line): + start_state = state[backslashed] or state[triplequoted] + + if re.search(r"\\$", line): + state[backslashed] = True + else: + state[backslashed] = False + + def match(reg, t): + m = re.match(reg, t) + if m: + return m, t[len(m.group(0)) :] + else: + return None, t + + while line: + if state[triplequoted]: + m, line = match(r"%s" % state[triplequoted], line) + if m: + state[triplequoted] = False + else: + m, line = match(r".*?(?=%s|$)" % state[triplequoted], line) + else: + m, line = match(r"#", line) + if m: + return start_state + + m, line = match(r"\"\"\"|\'\'\'", line) + if m: + state[triplequoted] = m.group(0) + continue + + m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line) + + return start_state + + def _indent_line(line, stripspace=""): + return re.sub(r"^%s" % stripspace, "", line) + + lines = [] + stripspace = None + + for line in re.split(r"\r?\n", text): + if in_multi_line(line): + lines.append(line) + else: + line = line.expandtabs() + if stripspace is None and re.search(r"^[ \t]*[^# \t]", line): + stripspace = re.match(r"^([ \t]*)", line).group(1) + lines.append(_indent_line(line, stripspace)) + return "\n".join(lines) diff --git a/venv/lib/python3.8/site-packages/mako/pyparser.py b/venv/lib/python3.8/site-packages/mako/pyparser.py new file mode 100644 index 000000000..1da9ddb55 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/pyparser.py @@ -0,0 +1,235 @@ +# mako/pyparser.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""Handles parsing of Python code. + +Parsing to AST is done via _ast on Python > 2.5, otherwise the compiler +module is used. +""" + +import operator + +import _ast + +from mako import _ast_util +from mako import compat +from mako import exceptions +from mako import util + +# words that cannot be assigned to (notably +# smaller than the total keys in __builtins__) +reserved = {"True", "False", "None", "print"} + +# the "id" attribute on a function node +arg_id = operator.attrgetter("arg") + +util.restore__ast(_ast) + + +def parse(code, mode="exec", **exception_kwargs): + """Parse an expression into AST""" + + try: + return _ast_util.parse(code, "", mode) + except Exception as e: + raise exceptions.SyntaxException( + "(%s) %s (%r)" + % ( + compat.exception_as().__class__.__name__, + compat.exception_as(), + code[0:50], + ), + **exception_kwargs, + ) from e + + +class FindIdentifiers(_ast_util.NodeVisitor): + def __init__(self, listener, **exception_kwargs): + self.in_function = False + self.in_assign_targets = False + self.local_ident_stack = set() + self.listener = listener + self.exception_kwargs = exception_kwargs + + def _add_declared(self, name): + if not self.in_function: + self.listener.declared_identifiers.add(name) + else: + self.local_ident_stack.add(name) + + def visit_ClassDef(self, node): + self._add_declared(node.name) + + def visit_Assign(self, node): + # flip around the visiting of Assign so the expression gets + # evaluated first, in the case of a clause like "x=x+5" (x + # is undeclared) + + self.visit(node.value) + in_a = self.in_assign_targets + self.in_assign_targets = True + for n in node.targets: + self.visit(n) + self.in_assign_targets = in_a + + def visit_ExceptHandler(self, node): + if node.name is not None: + self._add_declared(node.name) + if node.type is not None: + self.visit(node.type) + for statement in node.body: + self.visit(statement) + + def visit_Lambda(self, node, *args): + self._visit_function(node, True) + + def visit_FunctionDef(self, node): + self._add_declared(node.name) + self._visit_function(node, False) + + def visit_ListComp(self, node): + if self.in_function: + for comp in node.generators: + self.visit(comp.target) + self.visit(comp.iter) + else: + self.generic_visit(node) + + visit_SetComp = visit_GeneratorExp = visit_ListComp + + def visit_DictComp(self, node): + if self.in_function: + for comp in node.generators: + self.visit(comp.target) + self.visit(comp.iter) + else: + self.generic_visit(node) + + def _expand_tuples(self, args): + for arg in args: + if isinstance(arg, _ast.Tuple): + yield from arg.elts + else: + yield arg + + def _visit_function(self, node, islambda): + # push function state onto stack. dont log any more + # identifiers as "declared" until outside of the function, + # but keep logging identifiers as "undeclared". track + # argument names in each function header so they arent + # counted as "undeclared" + + inf = self.in_function + self.in_function = True + + local_ident_stack = self.local_ident_stack + self.local_ident_stack = local_ident_stack.union( + [arg_id(arg) for arg in self._expand_tuples(node.args.args)] + ) + if islambda: + self.visit(node.body) + else: + for n in node.body: + self.visit(n) + self.in_function = inf + self.local_ident_stack = local_ident_stack + + def visit_For(self, node): + # flip around visit + + self.visit(node.iter) + self.visit(node.target) + for statement in node.body: + self.visit(statement) + for statement in node.orelse: + self.visit(statement) + + def visit_Name(self, node): + if isinstance(node.ctx, _ast.Store): + # this is eqiuvalent to visit_AssName in + # compiler + self._add_declared(node.id) + elif ( + node.id not in reserved + and node.id not in self.listener.declared_identifiers + and node.id not in self.local_ident_stack + ): + self.listener.undeclared_identifiers.add(node.id) + + def visit_Import(self, node): + for name in node.names: + if name.asname is not None: + self._add_declared(name.asname) + else: + self._add_declared(name.name.split(".")[0]) + + def visit_ImportFrom(self, node): + for name in node.names: + if name.asname is not None: + self._add_declared(name.asname) + elif name.name == "*": + raise exceptions.CompileException( + "'import *' is not supported, since all identifier " + "names must be explicitly declared. Please use the " + "form 'from import , , " + "...' instead.", + **self.exception_kwargs, + ) + else: + self._add_declared(name.name) + + +class FindTuple(_ast_util.NodeVisitor): + def __init__(self, listener, code_factory, **exception_kwargs): + self.listener = listener + self.exception_kwargs = exception_kwargs + self.code_factory = code_factory + + def visit_Tuple(self, node): + for n in node.elts: + p = self.code_factory(n, **self.exception_kwargs) + self.listener.codeargs.append(p) + self.listener.args.append(ExpressionGenerator(n).value()) + ldi = self.listener.declared_identifiers + self.listener.declared_identifiers = ldi.union( + p.declared_identifiers + ) + lui = self.listener.undeclared_identifiers + self.listener.undeclared_identifiers = lui.union( + p.undeclared_identifiers + ) + + +class ParseFunc(_ast_util.NodeVisitor): + def __init__(self, listener, **exception_kwargs): + self.listener = listener + self.exception_kwargs = exception_kwargs + + def visit_FunctionDef(self, node): + self.listener.funcname = node.name + + argnames = [arg_id(arg) for arg in node.args.args] + if node.args.vararg: + argnames.append(node.args.vararg.arg) + + kwargnames = [arg_id(arg) for arg in node.args.kwonlyargs] + if node.args.kwarg: + kwargnames.append(node.args.kwarg.arg) + self.listener.argnames = argnames + self.listener.defaults = node.args.defaults # ast + self.listener.kwargnames = kwargnames + self.listener.kwdefaults = node.args.kw_defaults + self.listener.varargs = node.args.vararg + self.listener.kwargs = node.args.kwarg + + +class ExpressionGenerator: + def __init__(self, astnode): + self.generator = _ast_util.SourceGenerator(" " * 4) + self.generator.visit(astnode) + + def value(self): + return "".join(self.generator.result) diff --git a/venv/lib/python3.8/site-packages/mako/runtime.py b/venv/lib/python3.8/site-packages/mako/runtime.py new file mode 100644 index 000000000..23401b70c --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/runtime.py @@ -0,0 +1,968 @@ +# mako/runtime.py +# Copyright 2006-2020 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""provides runtime services for templates, including Context, +Namespace, and various helper functions.""" + +import builtins +import functools +import sys + +from mako import compat +from mako import exceptions +from mako import util + + +class Context: + + """Provides runtime namespace, output buffer, and various + callstacks for templates. + + See :ref:`runtime_toplevel` for detail on the usage of + :class:`.Context`. + + """ + + def __init__(self, buffer, **data): + self._buffer_stack = [buffer] + + self._data = data + + self._kwargs = data.copy() + self._with_template = None + self._outputting_as_unicode = None + self.namespaces = {} + + # "capture" function which proxies to the + # generic "capture" function + self._data["capture"] = functools.partial(capture, self) + + # "caller" stack used by def calls with content + self.caller_stack = self._data["caller"] = CallerStack() + + def _set_with_template(self, t): + self._with_template = t + illegal_names = t.reserved_names.intersection(self._data) + if illegal_names: + raise exceptions.NameConflictError( + "Reserved words passed to render(): %s" + % ", ".join(illegal_names) + ) + + @property + def lookup(self): + """Return the :class:`.TemplateLookup` associated + with this :class:`.Context`. + + """ + return self._with_template.lookup + + @property + def kwargs(self): + """Return the dictionary of top level keyword arguments associated + with this :class:`.Context`. + + This dictionary only includes the top-level arguments passed to + :meth:`.Template.render`. It does not include names produced within + the template execution such as local variable names or special names + such as ``self``, ``next``, etc. + + The purpose of this dictionary is primarily for the case that + a :class:`.Template` accepts arguments via its ``<%page>`` tag, + which are normally expected to be passed via :meth:`.Template.render`, + except the template is being called in an inheritance context, + using the ``body()`` method. :attr:`.Context.kwargs` can then be + used to propagate these arguments to the inheriting template:: + + ${next.body(**context.kwargs)} + + """ + return self._kwargs.copy() + + def push_caller(self, caller): + """Push a ``caller`` callable onto the callstack for + this :class:`.Context`.""" + + self.caller_stack.append(caller) + + def pop_caller(self): + """Pop a ``caller`` callable onto the callstack for this + :class:`.Context`.""" + + del self.caller_stack[-1] + + def keys(self): + """Return a list of all names established in this :class:`.Context`.""" + + return list(self._data.keys()) + + def __getitem__(self, key): + if key in self._data: + return self._data[key] + else: + return builtins.__dict__[key] + + def _push_writer(self): + """push a capturing buffer onto this Context and return + the new writer function.""" + + buf = util.FastEncodingBuffer() + self._buffer_stack.append(buf) + return buf.write + + def _pop_buffer_and_writer(self): + """pop the most recent capturing buffer from this Context + and return the current writer after the pop. + + """ + + buf = self._buffer_stack.pop() + return buf, self._buffer_stack[-1].write + + def _push_buffer(self): + """push a capturing buffer onto this Context.""" + + self._push_writer() + + def _pop_buffer(self): + """pop the most recent capturing buffer from this Context.""" + + return self._buffer_stack.pop() + + def get(self, key, default=None): + """Return a value from this :class:`.Context`.""" + + return self._data.get(key, builtins.__dict__.get(key, default)) + + def write(self, string): + """Write a string to this :class:`.Context` object's + underlying output buffer.""" + + self._buffer_stack[-1].write(string) + + def writer(self): + """Return the current writer function.""" + + return self._buffer_stack[-1].write + + def _copy(self): + c = Context.__new__(Context) + c._buffer_stack = self._buffer_stack + c._data = self._data.copy() + c._kwargs = self._kwargs + c._with_template = self._with_template + c._outputting_as_unicode = self._outputting_as_unicode + c.namespaces = self.namespaces + c.caller_stack = self.caller_stack + return c + + def _locals(self, d): + """Create a new :class:`.Context` with a copy of this + :class:`.Context`'s current state, + updated with the given dictionary. + + The :attr:`.Context.kwargs` collection remains + unaffected. + + + """ + + if not d: + return self + c = self._copy() + c._data.update(d) + return c + + def _clean_inheritance_tokens(self): + """create a new copy of this :class:`.Context`. with + tokens related to inheritance state removed.""" + + c = self._copy() + x = c._data + x.pop("self", None) + x.pop("parent", None) + x.pop("next", None) + return c + + +class CallerStack(list): + def __init__(self): + self.nextcaller = None + + def __nonzero__(self): + return self.__bool__() + + def __bool__(self): + return len(self) and self._get_caller() and True or False + + def _get_caller(self): + # this method can be removed once + # codegen MAGIC_NUMBER moves past 7 + return self[-1] + + def __getattr__(self, key): + return getattr(self._get_caller(), key) + + def _push_frame(self): + frame = self.nextcaller or None + self.append(frame) + self.nextcaller = None + return frame + + def _pop_frame(self): + self.nextcaller = self.pop() + + +class Undefined: + + """Represents an undefined value in a template. + + All template modules have a constant value + ``UNDEFINED`` present which is an instance of this + object. + + """ + + def __str__(self): + raise NameError("Undefined") + + def __nonzero__(self): + return self.__bool__() + + def __bool__(self): + return False + + +UNDEFINED = Undefined() +STOP_RENDERING = "" + + +class LoopStack: + + """a stack for LoopContexts that implements the context manager protocol + to automatically pop off the top of the stack on context exit + """ + + def __init__(self): + self.stack = [] + + def _enter(self, iterable): + self._push(iterable) + return self._top + + def _exit(self): + self._pop() + return self._top + + @property + def _top(self): + if self.stack: + return self.stack[-1] + else: + return self + + def _pop(self): + return self.stack.pop() + + def _push(self, iterable): + new = LoopContext(iterable) + if self.stack: + new.parent = self.stack[-1] + return self.stack.append(new) + + def __getattr__(self, key): + raise exceptions.RuntimeException("No loop context is established") + + def __iter__(self): + return iter(self._top) + + +class LoopContext: + + """A magic loop variable. + Automatically accessible in any ``% for`` block. + + See the section :ref:`loop_context` for usage + notes. + + :attr:`parent` -> :class:`.LoopContext` or ``None`` + The parent loop, if one exists. + :attr:`index` -> `int` + The 0-based iteration count. + :attr:`reverse_index` -> `int` + The number of iterations remaining. + :attr:`first` -> `bool` + ``True`` on the first iteration, ``False`` otherwise. + :attr:`last` -> `bool` + ``True`` on the last iteration, ``False`` otherwise. + :attr:`even` -> `bool` + ``True`` when ``index`` is even. + :attr:`odd` -> `bool` + ``True`` when ``index`` is odd. + """ + + def __init__(self, iterable): + self._iterable = iterable + self.index = 0 + self.parent = None + + def __iter__(self): + for i in self._iterable: + yield i + self.index += 1 + + @util.memoized_instancemethod + def __len__(self): + return len(self._iterable) + + @property + def reverse_index(self): + return len(self) - self.index - 1 + + @property + def first(self): + return self.index == 0 + + @property + def last(self): + return self.index == len(self) - 1 + + @property + def even(self): + return not self.odd + + @property + def odd(self): + return bool(self.index % 2) + + def cycle(self, *values): + """Cycle through values as the loop progresses.""" + if not values: + raise ValueError("You must provide values to cycle through") + return values[self.index % len(values)] + + +class _NSAttr: + def __init__(self, parent): + self.__parent = parent + + def __getattr__(self, key): + ns = self.__parent + while ns: + if hasattr(ns.module, key): + return getattr(ns.module, key) + else: + ns = ns.inherits + raise AttributeError(key) + + +class Namespace: + + """Provides access to collections of rendering methods, which + can be local, from other templates, or from imported modules. + + To access a particular rendering method referenced by a + :class:`.Namespace`, use plain attribute access: + + .. sourcecode:: mako + + ${some_namespace.foo(x, y, z)} + + :class:`.Namespace` also contains several built-in attributes + described here. + + """ + + def __init__( + self, + name, + context, + callables=None, + inherits=None, + populate_self=True, + calling_uri=None, + ): + self.name = name + self.context = context + self.inherits = inherits + if callables is not None: + self.callables = {c.__name__: c for c in callables} + + callables = () + + module = None + """The Python module referenced by this :class:`.Namespace`. + + If the namespace references a :class:`.Template`, then + this module is the equivalent of ``template.module``, + i.e. the generated module for the template. + + """ + + template = None + """The :class:`.Template` object referenced by this + :class:`.Namespace`, if any. + + """ + + context = None + """The :class:`.Context` object for this :class:`.Namespace`. + + Namespaces are often created with copies of contexts that + contain slightly different data, particularly in inheritance + scenarios. Using the :class:`.Context` off of a :class:`.Namespace` one + can traverse an entire chain of templates that inherit from + one-another. + + """ + + filename = None + """The path of the filesystem file used for this + :class:`.Namespace`'s module or template. + + If this is a pure module-based + :class:`.Namespace`, this evaluates to ``module.__file__``. If a + template-based namespace, it evaluates to the original + template file location. + + """ + + uri = None + """The URI for this :class:`.Namespace`'s template. + + I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`. + + This is the equivalent of :attr:`.Template.uri`. + + """ + + _templateuri = None + + @util.memoized_property + def attr(self): + """Access module level attributes by name. + + This accessor allows templates to supply "scalar" + attributes which are particularly handy in inheritance + relationships. + + .. seealso:: + + :ref:`inheritance_attr` + + :ref:`namespace_attr_for_includes` + + """ + return _NSAttr(self) + + def get_namespace(self, uri): + """Return a :class:`.Namespace` corresponding to the given ``uri``. + + If the given ``uri`` is a relative URI (i.e. it does not + contain a leading slash ``/``), the ``uri`` is adjusted to + be relative to the ``uri`` of the namespace itself. This + method is therefore mostly useful off of the built-in + ``local`` namespace, described in :ref:`namespace_local`. + + In + most cases, a template wouldn't need this function, and + should instead use the ``<%namespace>`` tag to load + namespaces. However, since all ``<%namespace>`` tags are + evaluated before the body of a template ever runs, + this method can be used to locate namespaces using + expressions that were generated within the body code of + the template, or to conditionally use a particular + namespace. + + """ + key = (self, uri) + if key in self.context.namespaces: + return self.context.namespaces[key] + ns = TemplateNamespace( + uri, + self.context._copy(), + templateuri=uri, + calling_uri=self._templateuri, + ) + self.context.namespaces[key] = ns + return ns + + def get_template(self, uri): + """Return a :class:`.Template` from the given ``uri``. + + The ``uri`` resolution is relative to the ``uri`` of this + :class:`.Namespace` object's :class:`.Template`. + + """ + return _lookup_template(self.context, uri, self._templateuri) + + def get_cached(self, key, **kwargs): + """Return a value from the :class:`.Cache` referenced by this + :class:`.Namespace` object's :class:`.Template`. + + The advantage to this method versus direct access to the + :class:`.Cache` is that the configuration parameters + declared in ``<%page>`` take effect here, thereby calling + up the same configured backend as that configured + by ``<%page>``. + + """ + + return self.cache.get(key, **kwargs) + + @property + def cache(self): + """Return the :class:`.Cache` object referenced + by this :class:`.Namespace` object's + :class:`.Template`. + + """ + return self.template.cache + + def include_file(self, uri, **kwargs): + """Include a file at the given ``uri``.""" + + _include_file(self.context, uri, self._templateuri, **kwargs) + + def _populate(self, d, l): + for ident in l: + if ident == "*": + for k, v in self._get_star(): + d[k] = v + else: + d[ident] = getattr(self, ident) + + def _get_star(self): + if self.callables: + for key in self.callables: + yield (key, self.callables[key]) + + def __getattr__(self, key): + if key in self.callables: + val = self.callables[key] + elif self.inherits: + val = getattr(self.inherits, key) + else: + raise AttributeError( + "Namespace '%s' has no member '%s'" % (self.name, key) + ) + setattr(self, key, val) + return val + + +class TemplateNamespace(Namespace): + + """A :class:`.Namespace` specific to a :class:`.Template` instance.""" + + def __init__( + self, + name, + context, + template=None, + templateuri=None, + callables=None, + inherits=None, + populate_self=True, + calling_uri=None, + ): + self.name = name + self.context = context + self.inherits = inherits + if callables is not None: + self.callables = {c.__name__: c for c in callables} + + if templateuri is not None: + self.template = _lookup_template(context, templateuri, calling_uri) + self._templateuri = self.template.module._template_uri + elif template is not None: + self.template = template + self._templateuri = template.module._template_uri + else: + raise TypeError("'template' argument is required.") + + if populate_self: + lclcallable, lclcontext = _populate_self_namespace( + context, self.template, self_ns=self + ) + + @property + def module(self): + """The Python module referenced by this :class:`.Namespace`. + + If the namespace references a :class:`.Template`, then + this module is the equivalent of ``template.module``, + i.e. the generated module for the template. + + """ + return self.template.module + + @property + def filename(self): + """The path of the filesystem file used for this + :class:`.Namespace`'s module or template. + """ + return self.template.filename + + @property + def uri(self): + """The URI for this :class:`.Namespace`'s template. + + I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`. + + This is the equivalent of :attr:`.Template.uri`. + + """ + return self.template.uri + + def _get_star(self): + if self.callables: + for key in self.callables: + yield (key, self.callables[key]) + + def get(key): + callable_ = self.template._get_def_callable(key) + return functools.partial(callable_, self.context) + + for k in self.template.module._exports: + yield (k, get(k)) + + def __getattr__(self, key): + if key in self.callables: + val = self.callables[key] + elif self.template.has_def(key): + callable_ = self.template._get_def_callable(key) + val = functools.partial(callable_, self.context) + elif self.inherits: + val = getattr(self.inherits, key) + + else: + raise AttributeError( + "Namespace '%s' has no member '%s'" % (self.name, key) + ) + setattr(self, key, val) + return val + + +class ModuleNamespace(Namespace): + + """A :class:`.Namespace` specific to a Python module instance.""" + + def __init__( + self, + name, + context, + module, + callables=None, + inherits=None, + populate_self=True, + calling_uri=None, + ): + self.name = name + self.context = context + self.inherits = inherits + if callables is not None: + self.callables = {c.__name__: c for c in callables} + + mod = __import__(module) + for token in module.split(".")[1:]: + mod = getattr(mod, token) + self.module = mod + + @property + def filename(self): + """The path of the filesystem file used for this + :class:`.Namespace`'s module or template. + """ + return self.module.__file__ + + def _get_star(self): + if self.callables: + for key in self.callables: + yield (key, self.callables[key]) + for key in dir(self.module): + if key[0] != "_": + callable_ = getattr(self.module, key) + if callable(callable_): + yield key, functools.partial(callable_, self.context) + + def __getattr__(self, key): + if key in self.callables: + val = self.callables[key] + elif hasattr(self.module, key): + callable_ = getattr(self.module, key) + val = functools.partial(callable_, self.context) + elif self.inherits: + val = getattr(self.inherits, key) + else: + raise AttributeError( + "Namespace '%s' has no member '%s'" % (self.name, key) + ) + setattr(self, key, val) + return val + + +def supports_caller(func): + """Apply a caller_stack compatibility decorator to a plain + Python function. + + See the example in :ref:`namespaces_python_modules`. + + """ + + def wrap_stackframe(context, *args, **kwargs): + context.caller_stack._push_frame() + try: + return func(context, *args, **kwargs) + finally: + context.caller_stack._pop_frame() + + return wrap_stackframe + + +def capture(context, callable_, *args, **kwargs): + """Execute the given template def, capturing the output into + a buffer. + + See the example in :ref:`namespaces_python_modules`. + + """ + + if not callable(callable_): + raise exceptions.RuntimeException( + "capture() function expects a callable as " + "its argument (i.e. capture(func, *args, **kwargs))" + ) + context._push_buffer() + try: + callable_(*args, **kwargs) + finally: + buf = context._pop_buffer() + return buf.getvalue() + + +def _decorate_toplevel(fn): + def decorate_render(render_fn): + def go(context, *args, **kw): + def y(*args, **kw): + return render_fn(context, *args, **kw) + + try: + y.__name__ = render_fn.__name__[7:] + except TypeError: + # < Python 2.4 + pass + return fn(y)(context, *args, **kw) + + return go + + return decorate_render + + +def _decorate_inline(context, fn): + def decorate_render(render_fn): + dec = fn(render_fn) + + def go(*args, **kw): + return dec(context, *args, **kw) + + return go + + return decorate_render + + +def _include_file(context, uri, calling_uri, **kwargs): + """locate the template from the given uri and include it in + the current output.""" + + template = _lookup_template(context, uri, calling_uri) + (callable_, ctx) = _populate_self_namespace( + context._clean_inheritance_tokens(), template + ) + kwargs = _kwargs_for_include(callable_, context._data, **kwargs) + if template.include_error_handler: + try: + callable_(ctx, **kwargs) + except Exception: + result = template.include_error_handler(ctx, compat.exception_as()) + if not result: + raise + else: + callable_(ctx, **kwargs) + + +def _inherit_from(context, uri, calling_uri): + """called by the _inherit method in template modules to set + up the inheritance chain at the start of a template's + execution.""" + + if uri is None: + return None + template = _lookup_template(context, uri, calling_uri) + self_ns = context["self"] + ih = self_ns + while ih.inherits is not None: + ih = ih.inherits + lclcontext = context._locals({"next": ih}) + ih.inherits = TemplateNamespace( + "self:%s" % template.uri, + lclcontext, + template=template, + populate_self=False, + ) + context._data["parent"] = lclcontext._data["local"] = ih.inherits + callable_ = getattr(template.module, "_mako_inherit", None) + if callable_ is not None: + ret = callable_(template, lclcontext) + if ret: + return ret + + gen_ns = getattr(template.module, "_mako_generate_namespaces", None) + if gen_ns is not None: + gen_ns(context) + return (template.callable_, lclcontext) + + +def _lookup_template(context, uri, relativeto): + lookup = context._with_template.lookup + if lookup is None: + raise exceptions.TemplateLookupException( + "Template '%s' has no TemplateLookup associated" + % context._with_template.uri + ) + uri = lookup.adjust_uri(uri, relativeto) + try: + return lookup.get_template(uri) + except exceptions.TopLevelLookupException as e: + raise exceptions.TemplateLookupException( + str(compat.exception_as()) + ) from e + + +def _populate_self_namespace(context, template, self_ns=None): + if self_ns is None: + self_ns = TemplateNamespace( + "self:%s" % template.uri, + context, + template=template, + populate_self=False, + ) + context._data["self"] = context._data["local"] = self_ns + if hasattr(template.module, "_mako_inherit"): + ret = template.module._mako_inherit(template, context) + if ret: + return ret + return (template.callable_, context) + + +def _render(template, callable_, args, data, as_unicode=False): + """create a Context and return the string + output of the given template and template callable.""" + + if as_unicode: + buf = util.FastEncodingBuffer() + else: + buf = util.FastEncodingBuffer( + encoding=template.output_encoding, errors=template.encoding_errors + ) + context = Context(buf, **data) + context._outputting_as_unicode = as_unicode + context._set_with_template(template) + + _render_context( + template, + callable_, + context, + *args, + **_kwargs_for_callable(callable_, data), + ) + return context._pop_buffer().getvalue() + + +def _kwargs_for_callable(callable_, data): + argspec = compat.inspect_getargspec(callable_) + # for normal pages, **pageargs is usually present + if argspec[2]: + return data + + # for rendering defs from the top level, figure out the args + namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] + kwargs = {} + for arg in namedargs: + if arg != "context" and arg in data and arg not in kwargs: + kwargs[arg] = data[arg] + return kwargs + + +def _kwargs_for_include(callable_, data, **kwargs): + argspec = compat.inspect_getargspec(callable_) + namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] + for arg in namedargs: + if arg != "context" and arg in data and arg not in kwargs: + kwargs[arg] = data[arg] + return kwargs + + +def _render_context(tmpl, callable_, context, *args, **kwargs): + import mako.template as template + + # create polymorphic 'self' namespace for this + # template with possibly updated context + if not isinstance(tmpl, template.DefTemplate): + # if main render method, call from the base of the inheritance stack + (inherit, lclcontext) = _populate_self_namespace(context, tmpl) + _exec_template(inherit, lclcontext, args=args, kwargs=kwargs) + else: + # otherwise, call the actual rendering method specified + (inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent) + _exec_template(callable_, context, args=args, kwargs=kwargs) + + +def _exec_template(callable_, context, args=None, kwargs=None): + """execute a rendering callable given the callable, a + Context, and optional explicit arguments + + the contextual Template will be located if it exists, and + the error handling options specified on that Template will + be interpreted here. + """ + template = context._with_template + if template is not None and ( + template.format_exceptions or template.error_handler + ): + try: + callable_(context, *args, **kwargs) + except Exception: + _render_error(template, context, compat.exception_as()) + except: + e = sys.exc_info()[0] + _render_error(template, context, e) + else: + callable_(context, *args, **kwargs) + + +def _render_error(template, context, error): + if template.error_handler: + result = template.error_handler(context, error) + if not result: + tp, value, tb = sys.exc_info() + if value and tb: + raise value.with_traceback(tb) + else: + raise error + else: + error_template = exceptions.html_error_template() + if context._outputting_as_unicode: + context._buffer_stack[:] = [util.FastEncodingBuffer()] + else: + context._buffer_stack[:] = [ + util.FastEncodingBuffer( + error_template.output_encoding, + error_template.encoding_errors, + ) + ] + + context._set_with_template(error_template) + error_template.render_context(context, error=error) diff --git a/venv/lib/python3.8/site-packages/mako/template.py b/venv/lib/python3.8/site-packages/mako/template.py new file mode 100644 index 000000000..82c7cba8b --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/template.py @@ -0,0 +1,711 @@ +# mako/template.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""Provides the Template class, a facade for parsing, generating and executing +template strings, as well as template runtime operations.""" + +import json +import os +import re +import shutil +import stat +import tempfile +import types +import weakref + +from mako import cache +from mako import codegen +from mako import compat +from mako import exceptions +from mako import runtime +from mako import util +from mako.lexer import Lexer + + +class Template: + r"""Represents a compiled template. + + :class:`.Template` includes a reference to the original + template source (via the :attr:`.source` attribute) + as well as the source code of the + generated Python module (i.e. the :attr:`.code` attribute), + as well as a reference to an actual Python module. + + :class:`.Template` is constructed using either a literal string + representing the template text, or a filename representing a filesystem + path to a source file. + + :param text: textual template source. This argument is mutually + exclusive versus the ``filename`` parameter. + + :param filename: filename of the source template. This argument is + mutually exclusive versus the ``text`` parameter. + + :param buffer_filters: string list of filters to be applied + to the output of ``%def``\ s which are buffered, cached, or otherwise + filtered, after all filters + defined with the ``%def`` itself have been applied. Allows the + creation of default expression filters that let the output + of return-valued ``%def``\ s "opt out" of that filtering via + passing special attributes or objects. + + :param cache_args: Dictionary of cache configuration arguments that + will be passed to the :class:`.CacheImpl`. See :ref:`caching_toplevel`. + + :param cache_dir: + + .. deprecated:: 0.6 + Use the ``'dir'`` argument in the ``cache_args`` dictionary. + See :ref:`caching_toplevel`. + + :param cache_enabled: Boolean flag which enables caching of this + template. See :ref:`caching_toplevel`. + + :param cache_impl: String name of a :class:`.CacheImpl` caching + implementation to use. Defaults to ``'beaker'``. + + :param cache_type: + + .. deprecated:: 0.6 + Use the ``'type'`` argument in the ``cache_args`` dictionary. + See :ref:`caching_toplevel`. + + :param cache_url: + + .. deprecated:: 0.6 + Use the ``'url'`` argument in the ``cache_args`` dictionary. + See :ref:`caching_toplevel`. + + :param default_filters: List of string filter names that will + be applied to all expressions. See :ref:`filtering_default_filters`. + + :param enable_loop: When ``True``, enable the ``loop`` context variable. + This can be set to ``False`` to support templates that may + be making usage of the name "``loop``". Individual templates can + re-enable the "loop" context by placing the directive + ``enable_loop="True"`` inside the ``<%page>`` tag -- see + :ref:`migrating_loop`. + + :param encoding_errors: Error parameter passed to ``encode()`` when + string encoding is performed. See :ref:`usage_unicode`. + + :param error_handler: Python callable which is called whenever + compile or runtime exceptions occur. The callable is passed + the current context as well as the exception. If the + callable returns ``True``, the exception is considered to + be handled, else it is re-raised after the function + completes. Is used to provide custom error-rendering + functions. + + .. seealso:: + + :paramref:`.Template.include_error_handler` - include-specific + error handler function + + :param format_exceptions: if ``True``, exceptions which occur during + the render phase of this template will be caught and + formatted into an HTML error page, which then becomes the + rendered result of the :meth:`.render` call. Otherwise, + runtime exceptions are propagated outwards. + + :param imports: String list of Python statements, typically individual + "import" lines, which will be placed into the module level + preamble of all generated Python modules. See the example + in :ref:`filtering_default_filters`. + + :param future_imports: String list of names to import from `__future__`. + These will be concatenated into a comma-separated string and inserted + into the beginning of the template, e.g. ``futures_imports=['FOO', + 'BAR']`` results in ``from __future__ import FOO, BAR``. + + :param include_error_handler: An error handler that runs when this template + is included within another one via the ``<%include>`` tag, and raises an + error. Compare to the :paramref:`.Template.error_handler` option. + + .. versionadded:: 1.0.6 + + .. seealso:: + + :paramref:`.Template.error_handler` - top-level error handler function + + :param input_encoding: Encoding of the template's source code. Can + be used in lieu of the coding comment. See + :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for + details on source encoding. + + :param lookup: a :class:`.TemplateLookup` instance that will be used + for all file lookups via the ``<%namespace>``, + ``<%include>``, and ``<%inherit>`` tags. See + :ref:`usage_templatelookup`. + + :param module_directory: Filesystem location where generated + Python module files will be placed. + + :param module_filename: Overrides the filename of the generated + Python module file. For advanced usage only. + + :param module_writer: A callable which overrides how the Python + module is written entirely. The callable is passed the + encoded source content of the module and the destination + path to be written to. The default behavior of module writing + uses a tempfile in conjunction with a file move in order + to make the operation atomic. So a user-defined module + writing function that mimics the default behavior would be: + + .. sourcecode:: python + + import tempfile + import os + import shutil + + def module_writer(source, outputpath): + (dest, name) = \\ + tempfile.mkstemp( + dir=os.path.dirname(outputpath) + ) + + os.write(dest, source) + os.close(dest) + shutil.move(name, outputpath) + + from mako.template import Template + mytemplate = Template( + filename="index.html", + module_directory="/path/to/modules", + module_writer=module_writer + ) + + The function is provided for unusual configurations where + certain platform-specific permissions or other special + steps are needed. + + :param output_encoding: The encoding to use when :meth:`.render` + is called. + See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`. + + :param preprocessor: Python callable which will be passed + the full template source before it is parsed. The return + result of the callable will be used as the template source + code. + + :param lexer_cls: A :class:`.Lexer` class used to parse + the template. The :class:`.Lexer` class is used by + default. + + .. versionadded:: 0.7.4 + + :param strict_undefined: Replaces the automatic usage of + ``UNDEFINED`` for any undeclared variables not located in + the :class:`.Context` with an immediate raise of + ``NameError``. The advantage is immediate reporting of + missing variables which include the name. + + .. versionadded:: 0.3.6 + + :param uri: string URI or other identifier for this template. + If not provided, the ``uri`` is generated from the filesystem + path, or from the in-memory identity of a non-file-based + template. The primary usage of the ``uri`` is to provide a key + within :class:`.TemplateLookup`, as well as to generate the + file path of the generated Python module file, if + ``module_directory`` is specified. + + """ + + lexer_cls = Lexer + + def __init__( + self, + text=None, + filename=None, + uri=None, + format_exceptions=False, + error_handler=None, + lookup=None, + output_encoding=None, + encoding_errors="strict", + module_directory=None, + cache_args=None, + cache_impl="beaker", + cache_enabled=True, + cache_type=None, + cache_dir=None, + cache_url=None, + module_filename=None, + input_encoding=None, + module_writer=None, + default_filters=None, + buffer_filters=(), + strict_undefined=False, + imports=None, + future_imports=None, + enable_loop=True, + preprocessor=None, + lexer_cls=None, + include_error_handler=None, + ): + if uri: + self.module_id = re.sub(r"\W", "_", uri) + self.uri = uri + elif filename: + self.module_id = re.sub(r"\W", "_", filename) + drive, path = os.path.splitdrive(filename) + path = os.path.normpath(path).replace(os.path.sep, "/") + self.uri = path + else: + self.module_id = "memory:" + hex(id(self)) + self.uri = self.module_id + + u_norm = self.uri + if u_norm.startswith("/"): + u_norm = u_norm[1:] + u_norm = os.path.normpath(u_norm) + if u_norm.startswith(".."): + raise exceptions.TemplateLookupException( + 'Template uri "%s" is invalid - ' + "it cannot be relative outside " + "of the root path." % self.uri + ) + + self.input_encoding = input_encoding + self.output_encoding = output_encoding + self.encoding_errors = encoding_errors + self.enable_loop = enable_loop + self.strict_undefined = strict_undefined + self.module_writer = module_writer + + if default_filters is None: + self.default_filters = ["str"] + else: + self.default_filters = default_filters + self.buffer_filters = buffer_filters + + self.imports = imports + self.future_imports = future_imports + self.preprocessor = preprocessor + + if lexer_cls is not None: + self.lexer_cls = lexer_cls + + # if plain text, compile code in memory only + if text is not None: + (code, module) = _compile_text(self, text, filename) + self._code = code + self._source = text + ModuleInfo(module, None, self, filename, code, text, uri) + elif filename is not None: + # if template filename and a module directory, load + # a filesystem-based module file, generating if needed + if module_filename is not None: + path = module_filename + elif module_directory is not None: + path = os.path.abspath( + os.path.join( + os.path.normpath(module_directory), u_norm + ".py" + ) + ) + else: + path = None + module = self._compile_from_file(path, filename) + else: + raise exceptions.RuntimeException( + "Template requires text or filename" + ) + + self.module = module + self.filename = filename + self.callable_ = self.module.render_body + self.format_exceptions = format_exceptions + self.error_handler = error_handler + self.include_error_handler = include_error_handler + self.lookup = lookup + + self.module_directory = module_directory + + self._setup_cache_args( + cache_impl, + cache_enabled, + cache_args, + cache_type, + cache_dir, + cache_url, + ) + + @util.memoized_property + def reserved_names(self): + if self.enable_loop: + return codegen.RESERVED_NAMES + else: + return codegen.RESERVED_NAMES.difference(["loop"]) + + def _setup_cache_args( + self, + cache_impl, + cache_enabled, + cache_args, + cache_type, + cache_dir, + cache_url, + ): + self.cache_impl = cache_impl + self.cache_enabled = cache_enabled + self.cache_args = cache_args or {} + # transfer deprecated cache_* args + if cache_type: + self.cache_args["type"] = cache_type + if cache_dir: + self.cache_args["dir"] = cache_dir + if cache_url: + self.cache_args["url"] = cache_url + + def _compile_from_file(self, path, filename): + if path is not None: + util.verify_directory(os.path.dirname(path)) + filemtime = os.stat(filename)[stat.ST_MTIME] + if ( + not os.path.exists(path) + or os.stat(path)[stat.ST_MTIME] < filemtime + ): + data = util.read_file(filename) + _compile_module_file( + self, data, filename, path, self.module_writer + ) + module = compat.load_module(self.module_id, path) + if module._magic_number != codegen.MAGIC_NUMBER: + data = util.read_file(filename) + _compile_module_file( + self, data, filename, path, self.module_writer + ) + module = compat.load_module(self.module_id, path) + ModuleInfo(module, path, self, filename, None, None, None) + else: + # template filename and no module directory, compile code + # in memory + data = util.read_file(filename) + code, module = _compile_text(self, data, filename) + self._source = None + self._code = code + ModuleInfo(module, None, self, filename, code, None, None) + return module + + @property + def source(self): + """Return the template source code for this :class:`.Template`.""" + + return _get_module_info_from_callable(self.callable_).source + + @property + def code(self): + """Return the module source code for this :class:`.Template`.""" + + return _get_module_info_from_callable(self.callable_).code + + @util.memoized_property + def cache(self): + return cache.Cache(self) + + @property + def cache_dir(self): + return self.cache_args["dir"] + + @property + def cache_url(self): + return self.cache_args["url"] + + @property + def cache_type(self): + return self.cache_args["type"] + + def render(self, *args, **data): + """Render the output of this template as a string. + + If the template specifies an output encoding, the string + will be encoded accordingly, else the output is raw (raw + output uses `StringIO` and can't handle multibyte + characters). A :class:`.Context` object is created corresponding + to the given data. Arguments that are explicitly declared + by this template's internal rendering method are also + pulled from the given ``*args``, ``**data`` members. + + """ + return runtime._render(self, self.callable_, args, data) + + def render_unicode(self, *args, **data): + """Render the output of this template as a unicode object.""" + + return runtime._render( + self, self.callable_, args, data, as_unicode=True + ) + + def render_context(self, context, *args, **kwargs): + """Render this :class:`.Template` with the given context. + + The data is written to the context's buffer. + + """ + if getattr(context, "_with_template", None) is None: + context._set_with_template(self) + runtime._render_context(self, self.callable_, context, *args, **kwargs) + + def has_def(self, name): + return hasattr(self.module, "render_%s" % name) + + def get_def(self, name): + """Return a def of this template as a :class:`.DefTemplate`.""" + + return DefTemplate(self, getattr(self.module, "render_%s" % name)) + + def list_defs(self): + """return a list of defs in the template. + + .. versionadded:: 1.0.4 + + """ + return [i[7:] for i in dir(self.module) if i[:7] == "render_"] + + def _get_def_callable(self, name): + return getattr(self.module, "render_%s" % name) + + @property + def last_modified(self): + return self.module._modified_time + + +class ModuleTemplate(Template): + + """A Template which is constructed given an existing Python module. + + e.g.:: + + t = Template("this is a template") + f = file("mymodule.py", "w") + f.write(t.code) + f.close() + + import mymodule + + t = ModuleTemplate(mymodule) + print(t.render()) + + """ + + def __init__( + self, + module, + module_filename=None, + template=None, + template_filename=None, + module_source=None, + template_source=None, + output_encoding=None, + encoding_errors="strict", + format_exceptions=False, + error_handler=None, + lookup=None, + cache_args=None, + cache_impl="beaker", + cache_enabled=True, + cache_type=None, + cache_dir=None, + cache_url=None, + include_error_handler=None, + ): + self.module_id = re.sub(r"\W", "_", module._template_uri) + self.uri = module._template_uri + self.input_encoding = module._source_encoding + self.output_encoding = output_encoding + self.encoding_errors = encoding_errors + self.enable_loop = module._enable_loop + + self.module = module + self.filename = template_filename + ModuleInfo( + module, + module_filename, + self, + template_filename, + module_source, + template_source, + module._template_uri, + ) + + self.callable_ = self.module.render_body + self.format_exceptions = format_exceptions + self.error_handler = error_handler + self.include_error_handler = include_error_handler + self.lookup = lookup + self._setup_cache_args( + cache_impl, + cache_enabled, + cache_args, + cache_type, + cache_dir, + cache_url, + ) + + +class DefTemplate(Template): + + """A :class:`.Template` which represents a callable def in a parent + template.""" + + def __init__(self, parent, callable_): + self.parent = parent + self.callable_ = callable_ + self.output_encoding = parent.output_encoding + self.module = parent.module + self.encoding_errors = parent.encoding_errors + self.format_exceptions = parent.format_exceptions + self.error_handler = parent.error_handler + self.include_error_handler = parent.include_error_handler + self.enable_loop = parent.enable_loop + self.lookup = parent.lookup + + def get_def(self, name): + return self.parent.get_def(name) + + +class ModuleInfo: + + """Stores information about a module currently loaded into + memory, provides reverse lookups of template source, module + source code based on a module's identifier. + + """ + + _modules = weakref.WeakValueDictionary() + + def __init__( + self, + module, + module_filename, + template, + template_filename, + module_source, + template_source, + template_uri, + ): + self.module = module + self.module_filename = module_filename + self.template_filename = template_filename + self.module_source = module_source + self.template_source = template_source + self.template_uri = template_uri + self._modules[module.__name__] = template._mmarker = self + if module_filename: + self._modules[module_filename] = self + + @classmethod + def get_module_source_metadata(cls, module_source, full_line_map=False): + source_map = re.search( + r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S + ).group(1) + source_map = json.loads(source_map) + source_map["line_map"] = { + int(k): int(v) for k, v in source_map["line_map"].items() + } + if full_line_map: + f_line_map = source_map["full_line_map"] = [] + line_map = source_map["line_map"] + + curr_templ_line = 1 + for mod_line in range(1, max(line_map)): + if mod_line in line_map: + curr_templ_line = line_map[mod_line] + f_line_map.append(curr_templ_line) + return source_map + + @property + def code(self): + if self.module_source is not None: + return self.module_source + else: + return util.read_python_file(self.module_filename) + + @property + def source(self): + if self.template_source is None: + data = util.read_file(self.template_filename) + if self.module._source_encoding: + return data.decode(self.module._source_encoding) + else: + return data + + elif self.module._source_encoding and not isinstance( + self.template_source, str + ): + return self.template_source.decode(self.module._source_encoding) + else: + return self.template_source + + +def _compile(template, text, filename, generate_magic_comment): + lexer = template.lexer_cls( + text, + filename, + input_encoding=template.input_encoding, + preprocessor=template.preprocessor, + ) + node = lexer.parse() + source = codegen.compile( + node, + template.uri, + filename, + default_filters=template.default_filters, + buffer_filters=template.buffer_filters, + imports=template.imports, + future_imports=template.future_imports, + source_encoding=lexer.encoding, + generate_magic_comment=generate_magic_comment, + strict_undefined=template.strict_undefined, + enable_loop=template.enable_loop, + reserved_names=template.reserved_names, + ) + return source, lexer + + +def _compile_text(template, text, filename): + identifier = template.module_id + source, lexer = _compile( + template, text, filename, generate_magic_comment=False + ) + + cid = identifier + module = types.ModuleType(cid) + code = compile(source, cid, "exec") + + # this exec() works for 2.4->3.3. + exec(code, module.__dict__, module.__dict__) + return (source, module) + + +def _compile_module_file(template, text, filename, outputpath, module_writer): + source, lexer = _compile( + template, text, filename, generate_magic_comment=True + ) + + if isinstance(source, str): + source = source.encode(lexer.encoding or "ascii") + + if module_writer: + module_writer(source, outputpath) + else: + # make tempfiles in the same location as the ultimate + # location. this ensures they're on the same filesystem, + # avoiding synchronization issues. + (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath)) + + os.write(dest, source) + os.close(dest) + shutil.move(name, outputpath) + + +def _get_module_info_from_callable(callable_): + return _get_module_info(callable_.__globals__["__name__"]) + + +def _get_module_info(filename): + return ModuleInfo._modules[filename] diff --git a/venv/lib/python3.8/site-packages/mako/testing/__init__.py b/venv/lib/python3.8/site-packages/mako/testing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/mako/testing/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/testing/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adad78de29c02eef823fe1da2fc10a95d8b198b1 GIT binary patch literal 205 zcmYk0Jqp4=5QR5jAws}wSZt<9VRfC z!@POL9P@m}lIC}yaXv@-MB#?V Pbfs;5QMev|c_p(CC{8zs literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/testing/__pycache__/_config.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/testing/__pycache__/_config.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6967326b2e48b4551fa3b46aaa47a77893e856b GIT binary patch literal 4403 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 SG5iWQLouVX2sw`EyW&5-H@hDI literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/testing/__pycache__/exclusions.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/testing/__pycache__/exclusions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09a12c82d81ae92ba6bf22ec0263f252d28923d2 GIT binary patch literal 1684 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 K&hqx^t^Wd`)i8Mg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/testing/__pycache__/helpers.cpython-38.pyc b/venv/lib/python3.8/site-packages/mako/testing/__pycache__/helpers.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1456e248f0dc3f22fb8cd844afc9d493654579f2 GIT binary patch literal 2441 zcmaJ?OLH4V5T2P`$!kSY;AguUZYWCcv`=A!uwUm z{vhG-gE{sY-uyQ-l1UyhZ`yN@LlzN_g(vuQ&ho5M$M$UWR#b_rUbS4aqnX(89E>Zn zy34#-IU^nPH90G5=ugP=s;Y#luZr_bHSiO%@rx~+^5kyCYsw3W*fASk9OAa`NEHbMCgSLTFW3`EaGEA8%Jd1G^88_^pT?@AMrshMsB=qgs3^^1m6*;~{z~4-woE@w7PD)Mt6Hg*P%Rqm z2Lq)$8!Fl8MB&Q5%C)8Iojf#ZF~hRIrt(hg57LfNxe1fCPG3ct()m)hRWw&4-x!tj zrj}4>bAg6&-9Y;q>{(2fuw4uY^6nJ81s4cu%L=(*B5tuA@@lnv3yM(z|;FA5WtFWrdJz>o5q7#wVPURl6Mf8JVdf<>K!InosBN@7`|P zg|iw)D$i6LGSNp7lL8EO@n zRBySyaoe^gsunYVl|~z?sP3CKcN^;6)Z9*oiTqOQR73SVRP_aFF487DL;X1B|6;ZT zOe>_A6LDF^;Wn@1*W~(5$U&KRw1S7gzRb0&5U9jV*tP)Rvy^Mbns7+sNt44Bd-jBJ zcAtI3zG2_8?~zIX%e(DLQ47+D`+!hTrD42QhD9%8{2GRrLAi`oVxil1*N3@rSHZ)rbXaSP!cH@l6qQYlB981M ziWPkIIFNI%034ju3sR}BLwy<$0I|9FyIIl1p=VTyjvzXqSwK5z8=`_rOsMAB0z+&# z$~sUznJ4Ov#X9TZ!v5}cvRU1*g&H)Pqnl=ltXNaYk_lslSlQHTO5&n|&;)%C6_k>^ zx8X-ag}brRPlr19ka3%oTV8$tgkHJxXf-QNLn}-qu*MmMkaRvL1ZDU{3#c8gKfz2} zpz?71^)-^_^;_ul6>5l()3Wv4@-=FnHt>(-)HJkqffw&&v literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/mako/testing/_config.py b/venv/lib/python3.8/site-packages/mako/testing/_config.py new file mode 100644 index 000000000..4ee3d0a6e --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/testing/_config.py @@ -0,0 +1,128 @@ +import configparser +import dataclasses +from dataclasses import dataclass +from pathlib import Path +from typing import Callable +from typing import ClassVar +from typing import Optional +from typing import Union + +from .helpers import make_path + + +class ConfigError(BaseException): + pass + + +class MissingConfig(ConfigError): + pass + + +class MissingConfigSection(ConfigError): + pass + + +class MissingConfigItem(ConfigError): + pass + + +class ConfigValueTypeError(ConfigError): + pass + + +class _GetterDispatch: + def __init__(self, initialdata, default_getter: Callable): + self.default_getter = default_getter + self.data = initialdata + + def get_fn_for_type(self, type_): + return self.data.get(type_, self.default_getter) + + def get_typed_value(self, type_, name): + get_fn = self.get_fn_for_type(type_) + return get_fn(name) + + +def _parse_cfg_file(filespec: Union[Path, str]): + cfg = configparser.ConfigParser() + try: + filepath = make_path(filespec, check_exists=True) + except FileNotFoundError as e: + raise MissingConfig(f"No config file found at {filespec}") from e + else: + with open(filepath, encoding="utf-8") as f: + cfg.read_file(f) + return cfg + + +def _build_getter(cfg_obj, cfg_section, method, converter=None): + def caller(option, **kwargs): + try: + rv = getattr(cfg_obj, method)(cfg_section, option, **kwargs) + except configparser.NoSectionError as nse: + raise MissingConfigSection( + f"No config section named {cfg_section}" + ) from nse + except configparser.NoOptionError as noe: + raise MissingConfigItem(f"No config item for {option}") from noe + except ValueError as ve: + # ConfigParser.getboolean, .getint, .getfloat raise ValueError + # on bad types + raise ConfigValueTypeError( + f"Wrong value type for {option}" + ) from ve + else: + if converter: + try: + rv = converter(rv) + except Exception as e: + raise ConfigValueTypeError( + f"Wrong value type for {option}" + ) from e + return rv + + return caller + + +def _build_getter_dispatch(cfg_obj, cfg_section, converters=None): + converters = converters or {} + + default_getter = _build_getter(cfg_obj, cfg_section, "get") + + # support ConfigParser builtins + getters = { + int: _build_getter(cfg_obj, cfg_section, "getint"), + bool: _build_getter(cfg_obj, cfg_section, "getboolean"), + float: _build_getter(cfg_obj, cfg_section, "getfloat"), + str: default_getter, + } + + # use ConfigParser.get and convert value + getters.update( + { + type_: _build_getter( + cfg_obj, cfg_section, "get", converter=converter_fn + ) + for type_, converter_fn in converters.items() + } + ) + + return _GetterDispatch(getters, default_getter) + + +@dataclass +class ReadsCfg: + section_header: ClassVar[str] + converters: ClassVar[Optional[dict]] = None + + @classmethod + def from_cfg_file(cls, filespec: Union[Path, str]): + cfg = _parse_cfg_file(filespec) + dispatch = _build_getter_dispatch( + cfg, cls.section_header, converters=cls.converters + ) + kwargs = { + field.name: dispatch.get_typed_value(field.type, field.name) + for field in dataclasses.fields(cls) + } + return cls(**kwargs) diff --git a/venv/lib/python3.8/site-packages/mako/testing/assertions.py b/venv/lib/python3.8/site-packages/mako/testing/assertions.py new file mode 100644 index 000000000..22221cd20 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/testing/assertions.py @@ -0,0 +1,166 @@ +import contextlib +import re +import sys + + +def eq_(a, b, msg=None): + """Assert a == b, with repr messaging on failure.""" + assert a == b, msg or "%r != %r" % (a, b) + + +def ne_(a, b, msg=None): + """Assert a != b, with repr messaging on failure.""" + assert a != b, msg or "%r == %r" % (a, b) + + +def in_(a, b, msg=None): + """Assert a in b, with repr messaging on failure.""" + assert a in b, msg or "%r not in %r" % (a, b) + + +def not_in(a, b, msg=None): + """Assert a in not b, with repr messaging on failure.""" + assert a not in b, msg or "%r is in %r" % (a, b) + + +def _assert_proper_exception_context(exception): + """assert that any exception we're catching does not have a __context__ + without a __cause__, and that __suppress_context__ is never set. + + Python 3 will report nested as exceptions as "during the handling of + error X, error Y occurred". That's not what we want to do. We want + these exceptions in a cause chain. + + """ + + if ( + exception.__context__ is not exception.__cause__ + and not exception.__suppress_context__ + ): + assert False, ( + "Exception %r was correctly raised but did not set a cause, " + "within context %r as its cause." + % (exception, exception.__context__) + ) + + +def _assert_proper_cause_cls(exception, cause_cls): + """assert that any exception we're catching does not have a __context__ + without a __cause__, and that __suppress_context__ is never set. + + Python 3 will report nested as exceptions as "during the handling of + error X, error Y occurred". That's not what we want to do. We want + these exceptions in a cause chain. + + """ + assert isinstance(exception.__cause__, cause_cls), ( + "Exception %r was correctly raised but has cause %r, which does not " + "have the expected cause type %r." + % (exception, exception.__cause__, cause_cls) + ) + + +def assert_raises(except_cls, callable_, *args, **kw): + return _assert_raises(except_cls, callable_, args, kw) + + +def assert_raises_with_proper_context(except_cls, callable_, *args, **kw): + return _assert_raises(except_cls, callable_, args, kw, check_context=True) + + +def assert_raises_with_given_cause( + except_cls, cause_cls, callable_, *args, **kw +): + return _assert_raises(except_cls, callable_, args, kw, cause_cls=cause_cls) + + +def assert_raises_message(except_cls, msg, callable_, *args, **kwargs): + return _assert_raises(except_cls, callable_, args, kwargs, msg=msg) + + +def assert_raises_message_with_proper_context( + except_cls, msg, callable_, *args, **kwargs +): + return _assert_raises( + except_cls, callable_, args, kwargs, msg=msg, check_context=True + ) + + +def assert_raises_message_with_given_cause( + except_cls, msg, cause_cls, callable_, *args, **kwargs +): + return _assert_raises( + except_cls, callable_, args, kwargs, msg=msg, cause_cls=cause_cls + ) + + +def _assert_raises( + except_cls, + callable_, + args, + kwargs, + msg=None, + check_context=False, + cause_cls=None, +): + with _expect_raises(except_cls, msg, check_context, cause_cls) as ec: + callable_(*args, **kwargs) + return ec.error + + +class _ErrorContainer: + error = None + + +@contextlib.contextmanager +def _expect_raises(except_cls, msg=None, check_context=False, cause_cls=None): + ec = _ErrorContainer() + if check_context: + are_we_already_in_a_traceback = sys.exc_info()[0] + try: + yield ec + success = False + except except_cls as err: + ec.error = err + success = True + if msg is not None: + # I'm often pdbing here, and "err" above isn't + # in scope, so assign the string explicitly + error_as_string = str(err) + assert re.search(msg, error_as_string, re.UNICODE), "%r !~ %s" % ( + msg, + error_as_string, + ) + if cause_cls is not None: + _assert_proper_cause_cls(err, cause_cls) + if check_context and not are_we_already_in_a_traceback: + _assert_proper_exception_context(err) + print(str(err).encode("utf-8")) + + # it's generally a good idea to not carry traceback objects outside + # of the except: block, but in this case especially we seem to have + # hit some bug in either python 3.10.0b2 or greenlet or both which + # this seems to fix: + # https://github.com/python-greenlet/greenlet/issues/242 + del ec + + # assert outside the block so it works for AssertionError too ! + assert success, "Callable did not raise an exception" + + +def expect_raises(except_cls, check_context=False): + return _expect_raises(except_cls, check_context=check_context) + + +def expect_raises_message(except_cls, msg, check_context=False): + return _expect_raises(except_cls, msg=msg, check_context=check_context) + + +def expect_raises_with_proper_context(except_cls, check_context=True): + return _expect_raises(except_cls, check_context=check_context) + + +def expect_raises_message_with_proper_context( + except_cls, msg, check_context=True +): + return _expect_raises(except_cls, msg=msg, check_context=check_context) diff --git a/venv/lib/python3.8/site-packages/mako/testing/config.py b/venv/lib/python3.8/site-packages/mako/testing/config.py new file mode 100644 index 000000000..b77d0c080 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/testing/config.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from pathlib import Path + +from ._config import ReadsCfg +from .helpers import make_path + + +@dataclass +class Config(ReadsCfg): + module_base: Path + template_base: Path + + section_header = "mako_testing" + converters = {Path: make_path} + + +config = Config.from_cfg_file("./setup.cfg") diff --git a/venv/lib/python3.8/site-packages/mako/testing/exclusions.py b/venv/lib/python3.8/site-packages/mako/testing/exclusions.py new file mode 100644 index 000000000..37b2d14a3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/testing/exclusions.py @@ -0,0 +1,80 @@ +import pytest + +from mako.ext.beaker_cache import has_beaker +from mako.util import update_wrapper + + +try: + import babel.messages.extract as babel +except ImportError: + babel = None + + +try: + import lingua +except ImportError: + lingua = None + + +try: + import dogpile.cache # noqa +except ImportError: + has_dogpile_cache = False +else: + has_dogpile_cache = True + + +requires_beaker = pytest.mark.skipif( + not has_beaker, reason="Beaker is required for these tests." +) + + +requires_babel = pytest.mark.skipif( + babel is None, reason="babel not installed: skipping babelplugin test" +) + + +requires_lingua = pytest.mark.skipif( + lingua is None, reason="lingua not installed: skipping linguaplugin test" +) + + +requires_dogpile_cache = pytest.mark.skipif( + not has_dogpile_cache, + reason="dogpile.cache is required to run these tests", +) + + +def _pygments_version(): + try: + import pygments + + version = pygments.__version__ + except: + version = "0" + return version + + +requires_pygments_14 = pytest.mark.skipif( + _pygments_version() < "1.4", reason="Requires pygments 1.4 or greater" +) + + +# def requires_pygments_14(fn): + +# return skip_if( +# lambda: version < "1.4", "Requires pygments 1.4 or greater" +# )(fn) + + +def requires_no_pygments_exceptions(fn): + def go(*arg, **kw): + from mako import exceptions + + exceptions._install_fallback() + try: + return fn(*arg, **kw) + finally: + exceptions._install_highlighting() + + return update_wrapper(go, fn) diff --git a/venv/lib/python3.8/site-packages/mako/testing/fixtures.py b/venv/lib/python3.8/site-packages/mako/testing/fixtures.py new file mode 100644 index 000000000..01e996171 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/testing/fixtures.py @@ -0,0 +1,119 @@ +import os + +from mako.cache import CacheImpl +from mako.cache import register_plugin +from mako.template import Template +from .assertions import eq_ +from .config import config + + +class TemplateTest: + def _file_template(self, filename, **kw): + filepath = self._file_path(filename) + return Template( + uri=filename, + filename=filepath, + module_directory=config.module_base, + **kw, + ) + + def _file_path(self, filename): + name, ext = os.path.splitext(filename) + py3k_path = os.path.join(config.template_base, name + "_py3k" + ext) + if os.path.exists(py3k_path): + return py3k_path + + return os.path.join(config.template_base, filename) + + def _do_file_test( + self, + filename, + expected, + filters=None, + unicode_=True, + template_args=None, + **kw, + ): + t1 = self._file_template(filename, **kw) + self._do_test( + t1, + expected, + filters=filters, + unicode_=unicode_, + template_args=template_args, + ) + + def _do_memory_test( + self, + source, + expected, + filters=None, + unicode_=True, + template_args=None, + **kw, + ): + t1 = Template(text=source, **kw) + self._do_test( + t1, + expected, + filters=filters, + unicode_=unicode_, + template_args=template_args, + ) + + def _do_test( + self, + template, + expected, + filters=None, + template_args=None, + unicode_=True, + ): + if template_args is None: + template_args = {} + if unicode_: + output = template.render_unicode(**template_args) + else: + output = template.render(**template_args) + + if filters: + output = filters(output) + eq_(output, expected) + + def indicates_unbound_local_error(self, rendered_output, unbound_var): + var = f"'{unbound_var}'" + error_msgs = ( + # < 3.11 + f"local variable {var} referenced before assignment", + # >= 3.11 + f"cannot access local variable {var} where it is not associated", + ) + return any((msg in rendered_output) for msg in error_msgs) + + +class PlainCacheImpl(CacheImpl): + """Simple memory cache impl so that tests which + use caching can run without beaker.""" + + def __init__(self, cache): + self.cache = cache + self.data = {} + + def get_or_create(self, key, creation_function, **kw): + if key in self.data: + return self.data[key] + else: + self.data[key] = data = creation_function(**kw) + return data + + def put(self, key, value, **kw): + self.data[key] = value + + def get(self, key, **kw): + return self.data[key] + + def invalidate(self, key, **kw): + del self.data[key] + + +register_plugin("plain", __name__, "PlainCacheImpl") diff --git a/venv/lib/python3.8/site-packages/mako/testing/helpers.py b/venv/lib/python3.8/site-packages/mako/testing/helpers.py new file mode 100644 index 000000000..5ae9d38d3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/testing/helpers.py @@ -0,0 +1,71 @@ +import contextlib +import pathlib +from pathlib import Path +import re +import time +from typing import Union +from unittest import mock + + +def flatten_result(result): + return re.sub(r"[\s\r\n]+", " ", result).strip() + + +def result_lines(result): + return [ + x.strip() + for x in re.split(r"\r?\n", re.sub(r" +", " ", result)) + if x.strip() != "" + ] + + +def result_raw_lines(result): + return [x for x in re.split(r"\r?\n", result) if x.strip() != ""] + + +def make_path( + filespec: Union[Path, str], + make_absolute: bool = True, + check_exists: bool = False, +) -> Path: + path = Path(filespec) + if make_absolute: + path = path.resolve(strict=check_exists) + if check_exists and (not path.exists()): + raise FileNotFoundError(f"No file or directory at {filespec}") + return path + + +def _unlink_path(path, missing_ok=False): + # Replicate 3.8+ functionality in 3.7 + cm = contextlib.nullcontext() + if missing_ok: + cm = contextlib.suppress(FileNotFoundError) + + with cm: + path.unlink() + + +def replace_file_with_dir(pathspec): + path = pathlib.Path(pathspec) + _unlink_path(path, missing_ok=True) + path.mkdir(exist_ok=True) + return path + + +def file_with_template_code(filespec): + with open(filespec, "w") as f: + f.write( + """ +i am an artificial template just for you +""" + ) + return filespec + + +@contextlib.contextmanager +def rewind_compile_time(hours=1): + rewound = time.time() - (hours * 3_600) + with mock.patch("mako.codegen.time") as codegen_time: + codegen_time.time.return_value = rewound + yield diff --git a/venv/lib/python3.8/site-packages/mako/util.py b/venv/lib/python3.8/site-packages/mako/util.py new file mode 100644 index 000000000..470713a51 --- /dev/null +++ b/venv/lib/python3.8/site-packages/mako/util.py @@ -0,0 +1,388 @@ +# mako/util.py +# Copyright 2006-2025 the Mako authors and contributors +# +# This module is part of Mako and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php +from ast import parse +import codecs +import collections +import operator +import os +import re +import timeit + +from .compat import importlib_metadata_get + + +def update_wrapper(decorated, fn): + decorated.__wrapped__ = fn + decorated.__name__ = fn.__name__ + return decorated + + +class PluginLoader: + def __init__(self, group): + self.group = group + self.impls = {} + + def load(self, name): + if name in self.impls: + return self.impls[name]() + + for impl in importlib_metadata_get(self.group): + if impl.name == name: + self.impls[name] = impl.load + return impl.load() + + from mako import exceptions + + raise exceptions.RuntimeException( + "Can't load plugin %s %s" % (self.group, name) + ) + + def register(self, name, modulepath, objname): + def load(): + mod = __import__(modulepath) + for token in modulepath.split(".")[1:]: + mod = getattr(mod, token) + return getattr(mod, objname) + + self.impls[name] = load + + +def verify_directory(dir_): + """create and/or verify a filesystem directory.""" + + tries = 0 + + while not os.path.exists(dir_): + try: + tries += 1 + os.makedirs(dir_, 0o755) + except: + if tries > 5: + raise + + +def to_list(x, default=None): + if x is None: + return default + if not isinstance(x, (list, tuple)): + return [x] + else: + return x + + +class memoized_property: + + """A read-only @property that is only evaluated once.""" + + def __init__(self, fget, doc=None): + self.fget = fget + self.__doc__ = doc or fget.__doc__ + self.__name__ = fget.__name__ + + def __get__(self, obj, cls): + if obj is None: + return self + obj.__dict__[self.__name__] = result = self.fget(obj) + return result + + +class memoized_instancemethod: + + """Decorate a method memoize its return value. + + Best applied to no-arg methods: memoization is not sensitive to + argument values, and will always return the same value even when + called with different arguments. + + """ + + def __init__(self, fget, doc=None): + self.fget = fget + self.__doc__ = doc or fget.__doc__ + self.__name__ = fget.__name__ + + def __get__(self, obj, cls): + if obj is None: + return self + + def oneshot(*args, **kw): + result = self.fget(obj, *args, **kw) + + def memo(*a, **kw): + return result + + memo.__name__ = self.__name__ + memo.__doc__ = self.__doc__ + obj.__dict__[self.__name__] = memo + return result + + oneshot.__name__ = self.__name__ + oneshot.__doc__ = self.__doc__ + return oneshot + + +class SetLikeDict(dict): + + """a dictionary that has some setlike methods on it""" + + def union(self, other): + """produce a 'union' of this dict and another (at the key level). + + values in the second dict take precedence over that of the first""" + x = SetLikeDict(**self) + x.update(other) + return x + + +class FastEncodingBuffer: + + """a very rudimentary buffer that is faster than StringIO, + and supports unicode data.""" + + def __init__(self, encoding=None, errors="strict"): + self.data = collections.deque() + self.encoding = encoding + self.delim = "" + self.errors = errors + self.write = self.data.append + + def truncate(self): + self.data = collections.deque() + self.write = self.data.append + + def getvalue(self): + if self.encoding: + return self.delim.join(self.data).encode( + self.encoding, self.errors + ) + else: + return self.delim.join(self.data) + + +class LRUCache(dict): + + """A dictionary-like object that stores a limited number of items, + discarding lesser used items periodically. + + this is a rewrite of LRUCache from Myghty to use a periodic timestamp-based + paradigm so that synchronization is not really needed. the size management + is inexact. + """ + + class _Item: + def __init__(self, key, value): + self.key = key + self.value = value + self.timestamp = timeit.default_timer() + + def __repr__(self): + return repr(self.value) + + def __init__(self, capacity, threshold=0.5): + self.capacity = capacity + self.threshold = threshold + + def __getitem__(self, key): + item = dict.__getitem__(self, key) + item.timestamp = timeit.default_timer() + return item.value + + def values(self): + return [i.value for i in dict.values(self)] + + def setdefault(self, key, value): + if key in self: + return self[key] + self[key] = value + return value + + def __setitem__(self, key, value): + item = dict.get(self, key) + if item is None: + item = self._Item(key, value) + dict.__setitem__(self, key, item) + else: + item.value = value + self._manage_size() + + def _manage_size(self): + while len(self) > self.capacity + self.capacity * self.threshold: + bytime = sorted( + dict.values(self), + key=operator.attrgetter("timestamp"), + reverse=True, + ) + for item in bytime[self.capacity :]: + try: + del self[item.key] + except KeyError: + # if we couldn't find a key, most likely some other thread + # broke in on us. loop around and try again + break + + +# Regexp to match python magic encoding line +_PYTHON_MAGIC_COMMENT_re = re.compile( + r"[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)", re.VERBOSE +) + + +def parse_encoding(fp): + """Deduce the encoding of a Python source file (binary mode) from magic + comment. + + It does this in the same way as the `Python interpreter`__ + + .. __: http://docs.python.org/ref/encodings.html + + The ``fp`` argument should be a seekable file object in binary mode. + """ + pos = fp.tell() + fp.seek(0) + try: + line1 = fp.readline() + has_bom = line1.startswith(codecs.BOM_UTF8) + if has_bom: + line1 = line1[len(codecs.BOM_UTF8) :] + + m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode("ascii", "ignore")) + if not m: + try: + parse(line1.decode("ascii", "ignore")) + except (ImportError, SyntaxError): + # Either it's a real syntax error, in which case the source + # is not valid python source, or line2 is a continuation of + # line1, in which case we don't want to scan line2 for a magic + # comment. + pass + else: + line2 = fp.readline() + m = _PYTHON_MAGIC_COMMENT_re.match( + line2.decode("ascii", "ignore") + ) + + if has_bom: + if m: + raise SyntaxError( + "python refuses to compile code with both a UTF8" + " byte-order-mark and a magic encoding comment" + ) + return "utf_8" + elif m: + return m.group(1) + else: + return None + finally: + fp.seek(pos) + + +def sorted_dict_repr(d): + """repr() a dictionary with the keys in order. + + Used by the lexer unit test to compare parse trees based on strings. + + """ + keys = list(d.keys()) + keys.sort() + return "{" + ", ".join("%r: %r" % (k, d[k]) for k in keys) + "}" + + +def restore__ast(_ast): + """Attempt to restore the required classes to the _ast module if it + appears to be missing them + """ + if hasattr(_ast, "AST"): + return + _ast.PyCF_ONLY_AST = 2 << 9 + m = compile( + """\ +def foo(): pass +class Bar: pass +if False: pass +baz = 'mako' +1 + 2 - 3 * 4 / 5 +6 // 7 % 8 << 9 >> 10 +11 & 12 ^ 13 | 14 +15 and 16 or 17 +-baz + (not +18) - ~17 +baz and 'foo' or 'bar' +(mako is baz == baz) is not baz != mako +mako > baz < mako >= baz <= mako +mako in baz not in mako""", + "", + "exec", + _ast.PyCF_ONLY_AST, + ) + _ast.Module = type(m) + + for cls in _ast.Module.__mro__: + if cls.__name__ == "mod": + _ast.mod = cls + elif cls.__name__ == "AST": + _ast.AST = cls + + _ast.FunctionDef = type(m.body[0]) + _ast.ClassDef = type(m.body[1]) + _ast.If = type(m.body[2]) + + _ast.Name = type(m.body[3].targets[0]) + _ast.Store = type(m.body[3].targets[0].ctx) + _ast.Str = type(m.body[3].value) + + _ast.Sub = type(m.body[4].value.op) + _ast.Add = type(m.body[4].value.left.op) + _ast.Div = type(m.body[4].value.right.op) + _ast.Mult = type(m.body[4].value.right.left.op) + + _ast.RShift = type(m.body[5].value.op) + _ast.LShift = type(m.body[5].value.left.op) + _ast.Mod = type(m.body[5].value.left.left.op) + _ast.FloorDiv = type(m.body[5].value.left.left.left.op) + + _ast.BitOr = type(m.body[6].value.op) + _ast.BitXor = type(m.body[6].value.left.op) + _ast.BitAnd = type(m.body[6].value.left.left.op) + + _ast.Or = type(m.body[7].value.op) + _ast.And = type(m.body[7].value.values[0].op) + + _ast.Invert = type(m.body[8].value.right.op) + _ast.Not = type(m.body[8].value.left.right.op) + _ast.UAdd = type(m.body[8].value.left.right.operand.op) + _ast.USub = type(m.body[8].value.left.left.op) + + _ast.Or = type(m.body[9].value.op) + _ast.And = type(m.body[9].value.values[0].op) + + _ast.IsNot = type(m.body[10].value.ops[0]) + _ast.NotEq = type(m.body[10].value.ops[1]) + _ast.Is = type(m.body[10].value.left.ops[0]) + _ast.Eq = type(m.body[10].value.left.ops[1]) + + _ast.Gt = type(m.body[11].value.ops[0]) + _ast.Lt = type(m.body[11].value.ops[1]) + _ast.GtE = type(m.body[11].value.ops[2]) + _ast.LtE = type(m.body[11].value.ops[3]) + + _ast.In = type(m.body[12].value.ops[0]) + _ast.NotIn = type(m.body[12].value.ops[1]) + + +def read_file(path, mode="rb"): + with open(path, mode) as fp: + return fp.read() + + +def read_python_file(path): + fp = open(path, "rb") + try: + encoding = parse_encoding(fp) + data = fp.read() + if encoding: + data = data.decode(encoding) + return data + finally: + fp.close() diff --git a/venv/lib/python3.8/site-packages/markupsafe/__init__.py b/venv/lib/python3.8/site-packages/markupsafe/__init__.py new file mode 100644 index 000000000..b40f24c66 --- /dev/null +++ b/venv/lib/python3.8/site-packages/markupsafe/__init__.py @@ -0,0 +1,332 @@ +import functools +import string +import sys +import typing as t + +if t.TYPE_CHECKING: + import typing_extensions as te + + class HasHTML(te.Protocol): + def __html__(self) -> str: + pass + + _P = te.ParamSpec("_P") + + +__version__ = "2.1.5" + + +def _simple_escaping_wrapper(func: "t.Callable[_P, str]") -> "t.Callable[_P, Markup]": + @functools.wraps(func) + def wrapped(self: "Markup", *args: "_P.args", **kwargs: "_P.kwargs") -> "Markup": + arg_list = _escape_argspec(list(args), enumerate(args), self.escape) + _escape_argspec(kwargs, kwargs.items(), self.escape) + return self.__class__(func(self, *arg_list, **kwargs)) # type: ignore[arg-type] + + return wrapped # type: ignore[return-value] + + +class Markup(str): + """A string that is ready to be safely inserted into an HTML or XML + document, either because it was escaped or because it was marked + safe. + + Passing an object to the constructor converts it to text and wraps + it to mark it safe without escaping. To escape the text, use the + :meth:`escape` class method instead. + + >>> Markup("Hello, World!") + Markup('Hello, World!') + >>> Markup(42) + Markup('42') + >>> Markup.escape("Hello, World!") + Markup('Hello <em>World</em>!') + + This implements the ``__html__()`` interface that some frameworks + use. Passing an object that implements ``__html__()`` will wrap the + output of that method, marking it safe. + + >>> class Foo: + ... def __html__(self): + ... return '
    foo' + ... + >>> Markup(Foo()) + Markup('foo') + + This is a subclass of :class:`str`. It has the same methods, but + escapes their arguments and returns a ``Markup`` instance. + + >>> Markup("%s") % ("foo & bar",) + Markup('foo & bar') + >>> Markup("Hello ") + "" + Markup('Hello <foo>') + """ + + __slots__ = () + + def __new__( + cls, base: t.Any = "", encoding: t.Optional[str] = None, errors: str = "strict" + ) -> "te.Self": + if hasattr(base, "__html__"): + base = base.__html__() + + if encoding is None: + return super().__new__(cls, base) + + return super().__new__(cls, base, encoding, errors) + + def __html__(self) -> "te.Self": + return self + + def __add__(self, other: t.Union[str, "HasHTML"]) -> "te.Self": + if isinstance(other, str) or hasattr(other, "__html__"): + return self.__class__(super().__add__(self.escape(other))) + + return NotImplemented + + def __radd__(self, other: t.Union[str, "HasHTML"]) -> "te.Self": + if isinstance(other, str) or hasattr(other, "__html__"): + return self.escape(other).__add__(self) + + return NotImplemented + + def __mul__(self, num: "te.SupportsIndex") -> "te.Self": + if isinstance(num, int): + return self.__class__(super().__mul__(num)) + + return NotImplemented + + __rmul__ = __mul__ + + def __mod__(self, arg: t.Any) -> "te.Self": + if isinstance(arg, tuple): + # a tuple of arguments, each wrapped + arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg) + elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str): + # a mapping of arguments, wrapped + arg = _MarkupEscapeHelper(arg, self.escape) + else: + # a single argument, wrapped with the helper and a tuple + arg = (_MarkupEscapeHelper(arg, self.escape),) + + return self.__class__(super().__mod__(arg)) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({super().__repr__()})" + + def join(self, seq: t.Iterable[t.Union[str, "HasHTML"]]) -> "te.Self": + return self.__class__(super().join(map(self.escape, seq))) + + join.__doc__ = str.join.__doc__ + + def split( # type: ignore[override] + self, sep: t.Optional[str] = None, maxsplit: int = -1 + ) -> t.List["te.Self"]: + return [self.__class__(v) for v in super().split(sep, maxsplit)] + + split.__doc__ = str.split.__doc__ + + def rsplit( # type: ignore[override] + self, sep: t.Optional[str] = None, maxsplit: int = -1 + ) -> t.List["te.Self"]: + return [self.__class__(v) for v in super().rsplit(sep, maxsplit)] + + rsplit.__doc__ = str.rsplit.__doc__ + + def splitlines( # type: ignore[override] + self, keepends: bool = False + ) -> t.List["te.Self"]: + return [self.__class__(v) for v in super().splitlines(keepends)] + + splitlines.__doc__ = str.splitlines.__doc__ + + def unescape(self) -> str: + """Convert escaped markup back into a text string. This replaces + HTML entities with the characters they represent. + + >>> Markup("Main » About").unescape() + 'Main » About' + """ + from html import unescape + + return unescape(str(self)) + + def striptags(self) -> str: + """:meth:`unescape` the markup, remove tags, and normalize + whitespace to single spaces. + + >>> Markup("Main »\tAbout").striptags() + 'Main » About' + """ + value = str(self) + + # Look for comments then tags separately. Otherwise, a comment that + # contains a tag would end early, leaving some of the comment behind. + + while True: + # keep finding comment start marks + start = value.find("", start) + + if end == -1: + break + + value = f"{value[:start]}{value[end + 3:]}" + + # remove tags using the same method + while True: + start = value.find("<") + + if start == -1: + break + + end = value.find(">", start) + + if end == -1: + break + + value = f"{value[:start]}{value[end + 1:]}" + + # collapse spaces + value = " ".join(value.split()) + return self.__class__(value).unescape() + + @classmethod + def escape(cls, s: t.Any) -> "te.Self": + """Escape a string. Calls :func:`escape` and ensures that for + subclasses the correct type is returned. + """ + rv = escape(s) + + if rv.__class__ is not cls: + return cls(rv) + + return rv # type: ignore[return-value] + + __getitem__ = _simple_escaping_wrapper(str.__getitem__) + capitalize = _simple_escaping_wrapper(str.capitalize) + title = _simple_escaping_wrapper(str.title) + lower = _simple_escaping_wrapper(str.lower) + upper = _simple_escaping_wrapper(str.upper) + replace = _simple_escaping_wrapper(str.replace) + ljust = _simple_escaping_wrapper(str.ljust) + rjust = _simple_escaping_wrapper(str.rjust) + lstrip = _simple_escaping_wrapper(str.lstrip) + rstrip = _simple_escaping_wrapper(str.rstrip) + center = _simple_escaping_wrapper(str.center) + strip = _simple_escaping_wrapper(str.strip) + translate = _simple_escaping_wrapper(str.translate) + expandtabs = _simple_escaping_wrapper(str.expandtabs) + swapcase = _simple_escaping_wrapper(str.swapcase) + zfill = _simple_escaping_wrapper(str.zfill) + casefold = _simple_escaping_wrapper(str.casefold) + + if sys.version_info >= (3, 9): + removeprefix = _simple_escaping_wrapper(str.removeprefix) + removesuffix = _simple_escaping_wrapper(str.removesuffix) + + def partition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]: + l, s, r = super().partition(self.escape(sep)) + cls = self.__class__ + return cls(l), cls(s), cls(r) + + def rpartition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]: + l, s, r = super().rpartition(self.escape(sep)) + cls = self.__class__ + return cls(l), cls(s), cls(r) + + def format(self, *args: t.Any, **kwargs: t.Any) -> "te.Self": + formatter = EscapeFormatter(self.escape) + return self.__class__(formatter.vformat(self, args, kwargs)) + + def format_map( # type: ignore[override] + self, map: t.Mapping[str, t.Any] + ) -> "te.Self": + formatter = EscapeFormatter(self.escape) + return self.__class__(formatter.vformat(self, (), map)) + + def __html_format__(self, format_spec: str) -> "te.Self": + if format_spec: + raise ValueError("Unsupported format specification for Markup.") + + return self + + +class EscapeFormatter(string.Formatter): + __slots__ = ("escape",) + + def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None: + self.escape = escape + super().__init__() + + def format_field(self, value: t.Any, format_spec: str) -> str: + if hasattr(value, "__html_format__"): + rv = value.__html_format__(format_spec) + elif hasattr(value, "__html__"): + if format_spec: + raise ValueError( + f"Format specifier {format_spec} given, but {type(value)} does not" + " define __html_format__. A class that defines __html__ must define" + " __html_format__ to work with format specifiers." + ) + rv = value.__html__() + else: + # We need to make sure the format spec is str here as + # otherwise the wrong callback methods are invoked. + rv = string.Formatter.format_field(self, value, str(format_spec)) + return str(self.escape(rv)) + + +_ListOrDict = t.TypeVar("_ListOrDict", list, dict) + + +def _escape_argspec( + obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup] +) -> _ListOrDict: + """Helper for various string-wrapped functions.""" + for key, value in iterable: + if isinstance(value, str) or hasattr(value, "__html__"): + obj[key] = escape(value) + + return obj + + +class _MarkupEscapeHelper: + """Helper for :meth:`Markup.__mod__`.""" + + __slots__ = ("obj", "escape") + + def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None: + self.obj = obj + self.escape = escape + + def __getitem__(self, item: t.Any) -> "te.Self": + return self.__class__(self.obj[item], self.escape) + + def __str__(self) -> str: + return str(self.escape(self.obj)) + + def __repr__(self) -> str: + return str(self.escape(repr(self.obj))) + + def __int__(self) -> int: + return int(self.obj) + + def __float__(self) -> float: + return float(self.obj) + + +# circular import +try: + from ._speedups import escape as escape + from ._speedups import escape_silent as escape_silent + from ._speedups import soft_str as soft_str +except ImportError: + from ._native import escape as escape + from ._native import escape_silent as escape_silent # noqa: F401 + from ._native import soft_str as soft_str # noqa: F401 diff --git a/venv/lib/python3.8/site-packages/markupsafe/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/markupsafe/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c77359b4208af92d7957d22b409575ba48deea22 GIT binary patch literal 11480 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/markupsafe/__pycache__/_native.cpython-38.pyc b/venv/lib/python3.8/site-packages/markupsafe/__pycache__/_native.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53c7860c71629c9d025b7c6903b6d0f38fa60c5e GIT binary patch literal 2043 zcmZ`)&2HO95GE-~re!-xT{K89#li(_3$P^w?x9s&p+S0R3&#ao=i-aBB3Bk;{&jaH zSB7uyWAqT;nqyy}r#=O*J>?bLpY)quDsm8q0B4uGGqbZZ-#2`=w$^m;-2Jl?|K4z% zzv$!UQ^Utsc-0X^;kS(`tJmvCqQnb9%Rb@j8J&{>Rd-I3pb-YjT zs_!7QbAh-nJ$YZd7tRas%DIGJ?dsYEW-ok-_R7QPM|Cqcq9rp1(Xttv(aM!aHmz0u z2i*T4iiC%P>9Jtpm`fgNAr%`8HV1(&eviplDtSoraK3Gs5-?ANzQhQz1@)%Vi=2E4K$q!*_n!L z+DYPJrLx!lyrW_*b_;Ct6QMe(p#i10)6ck$r=nfV%7(o!1*WzkoRy}#>UnP9dXD$n z_aOiCYmoo?KIA`s9rADQwOohpJ2djJXlQgF6C%-RAn$1B(joc~c|Zw|yP(8Dd@5LX zG|9s5z=kkj!wG;1AZVh-VV((~3P3YeNeexzYGtbesE;Oz-8#NY?DpPIIsxXwqESgLa(pl z1Xa65eaosIwmgdoN~F9?f)HTE2bh&>HZPT@Pq3t@OinX!8=m|Svv=}OAbXR4YGWF0 zOs&sze}Ou*T>cyK|HKl9SiB${QF*JBL}aT++^#CMDC#x>-8NROw;^D9!orq1#O?O^ zNcZ3CWZ__8jNeu>R&k~@M~jzhDDbUd2~_2}4GoKvJ54w6e4Z| wD{GIlnPG7md|&a$fXbdhW;Xz7plMJU?5BA&Nkor+n<{_^yw%$Jg9rEi2cJqYe*gdg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/markupsafe/_native.py b/venv/lib/python3.8/site-packages/markupsafe/_native.py new file mode 100644 index 000000000..8117b2716 --- /dev/null +++ b/venv/lib/python3.8/site-packages/markupsafe/_native.py @@ -0,0 +1,63 @@ +import typing as t + +from . import Markup + + +def escape(s: t.Any) -> Markup: + """Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in + the string with HTML-safe sequences. Use this if you need to display + text that might contain such characters in HTML. + + If the object has an ``__html__`` method, it is called and the + return value is assumed to already be safe for HTML. + + :param s: An object to be converted to a string and escaped. + :return: A :class:`Markup` string with the escaped text. + """ + if hasattr(s, "__html__"): + return Markup(s.__html__()) + + return Markup( + str(s) + .replace("&", "&") + .replace(">", ">") + .replace("<", "<") + .replace("'", "'") + .replace('"', """) + ) + + +def escape_silent(s: t.Optional[t.Any]) -> Markup: + """Like :func:`escape` but treats ``None`` as the empty string. + Useful with optional values, as otherwise you get the string + ``'None'`` when the value is ``None``. + + >>> escape(None) + Markup('None') + >>> escape_silent(None) + Markup('') + """ + if s is None: + return Markup() + + return escape(s) + + +def soft_str(s: t.Any) -> str: + """Convert an object to a string if it isn't already. This preserves + a :class:`Markup` string rather than converting it back to a basic + string, so it will still be marked as safe and won't be escaped + again. + + >>> value = escape("") + >>> value + Markup('<User 1>') + >>> escape(str(value)) + Markup('&lt;User 1&gt;') + >>> escape(soft_str(value)) + Markup('<User 1>') + """ + if not isinstance(s, str): + return str(s) + + return s diff --git a/venv/lib/python3.8/site-packages/markupsafe/_speedups.c b/venv/lib/python3.8/site-packages/markupsafe/_speedups.c new file mode 100644 index 000000000..3c463fb82 --- /dev/null +++ b/venv/lib/python3.8/site-packages/markupsafe/_speedups.c @@ -0,0 +1,320 @@ +#include + +static PyObject* markup; + +static int +init_constants(void) +{ + PyObject *module; + + /* import markup type so that we can mark the return value */ + module = PyImport_ImportModule("markupsafe"); + if (!module) + return 0; + markup = PyObject_GetAttrString(module, "Markup"); + Py_DECREF(module); + + return 1; +} + +#define GET_DELTA(inp, inp_end, delta) \ + while (inp < inp_end) { \ + switch (*inp++) { \ + case '"': \ + case '\'': \ + case '&': \ + delta += 4; \ + break; \ + case '<': \ + case '>': \ + delta += 3; \ + break; \ + } \ + } + +#define DO_ESCAPE(inp, inp_end, outp) \ + { \ + Py_ssize_t ncopy = 0; \ + while (inp < inp_end) { \ + switch (*inp) { \ + case '"': \ + memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ + outp += ncopy; ncopy = 0; \ + *outp++ = '&'; \ + *outp++ = '#'; \ + *outp++ = '3'; \ + *outp++ = '4'; \ + *outp++ = ';'; \ + break; \ + case '\'': \ + memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ + outp += ncopy; ncopy = 0; \ + *outp++ = '&'; \ + *outp++ = '#'; \ + *outp++ = '3'; \ + *outp++ = '9'; \ + *outp++ = ';'; \ + break; \ + case '&': \ + memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ + outp += ncopy; ncopy = 0; \ + *outp++ = '&'; \ + *outp++ = 'a'; \ + *outp++ = 'm'; \ + *outp++ = 'p'; \ + *outp++ = ';'; \ + break; \ + case '<': \ + memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ + outp += ncopy; ncopy = 0; \ + *outp++ = '&'; \ + *outp++ = 'l'; \ + *outp++ = 't'; \ + *outp++ = ';'; \ + break; \ + case '>': \ + memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ + outp += ncopy; ncopy = 0; \ + *outp++ = '&'; \ + *outp++ = 'g'; \ + *outp++ = 't'; \ + *outp++ = ';'; \ + break; \ + default: \ + ncopy++; \ + } \ + inp++; \ + } \ + memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ + } + +static PyObject* +escape_unicode_kind1(PyUnicodeObject *in) +{ + Py_UCS1 *inp = PyUnicode_1BYTE_DATA(in); + Py_UCS1 *inp_end = inp + PyUnicode_GET_LENGTH(in); + Py_UCS1 *outp; + PyObject *out; + Py_ssize_t delta = 0; + + GET_DELTA(inp, inp_end, delta); + if (!delta) { + Py_INCREF(in); + return (PyObject*)in; + } + + out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, + PyUnicode_IS_ASCII(in) ? 127 : 255); + if (!out) + return NULL; + + inp = PyUnicode_1BYTE_DATA(in); + outp = PyUnicode_1BYTE_DATA(out); + DO_ESCAPE(inp, inp_end, outp); + return out; +} + +static PyObject* +escape_unicode_kind2(PyUnicodeObject *in) +{ + Py_UCS2 *inp = PyUnicode_2BYTE_DATA(in); + Py_UCS2 *inp_end = inp + PyUnicode_GET_LENGTH(in); + Py_UCS2 *outp; + PyObject *out; + Py_ssize_t delta = 0; + + GET_DELTA(inp, inp_end, delta); + if (!delta) { + Py_INCREF(in); + return (PyObject*)in; + } + + out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 65535); + if (!out) + return NULL; + + inp = PyUnicode_2BYTE_DATA(in); + outp = PyUnicode_2BYTE_DATA(out); + DO_ESCAPE(inp, inp_end, outp); + return out; +} + + +static PyObject* +escape_unicode_kind4(PyUnicodeObject *in) +{ + Py_UCS4 *inp = PyUnicode_4BYTE_DATA(in); + Py_UCS4 *inp_end = inp + PyUnicode_GET_LENGTH(in); + Py_UCS4 *outp; + PyObject *out; + Py_ssize_t delta = 0; + + GET_DELTA(inp, inp_end, delta); + if (!delta) { + Py_INCREF(in); + return (PyObject*)in; + } + + out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 1114111); + if (!out) + return NULL; + + inp = PyUnicode_4BYTE_DATA(in); + outp = PyUnicode_4BYTE_DATA(out); + DO_ESCAPE(inp, inp_end, outp); + return out; +} + +static PyObject* +escape_unicode(PyUnicodeObject *in) +{ + if (PyUnicode_READY(in)) + return NULL; + + switch (PyUnicode_KIND(in)) { + case PyUnicode_1BYTE_KIND: + return escape_unicode_kind1(in); + case PyUnicode_2BYTE_KIND: + return escape_unicode_kind2(in); + case PyUnicode_4BYTE_KIND: + return escape_unicode_kind4(in); + } + assert(0); /* shouldn't happen */ + return NULL; +} + +static PyObject* +escape(PyObject *self, PyObject *text) +{ + static PyObject *id_html; + PyObject *s = NULL, *rv = NULL, *html; + + if (id_html == NULL) { + id_html = PyUnicode_InternFromString("__html__"); + if (id_html == NULL) { + return NULL; + } + } + + /* we don't have to escape integers, bools or floats */ + if (PyLong_CheckExact(text) || + PyFloat_CheckExact(text) || PyBool_Check(text) || + text == Py_None) + return PyObject_CallFunctionObjArgs(markup, text, NULL); + + /* if the object has an __html__ method that performs the escaping */ + html = PyObject_GetAttr(text ,id_html); + if (html) { + s = PyObject_CallObject(html, NULL); + Py_DECREF(html); + if (s == NULL) { + return NULL; + } + /* Convert to Markup object */ + rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL); + Py_DECREF(s); + return rv; + } + + /* otherwise make the object unicode if it isn't, then escape */ + PyErr_Clear(); + if (!PyUnicode_Check(text)) { + PyObject *unicode = PyObject_Str(text); + if (!unicode) + return NULL; + s = escape_unicode((PyUnicodeObject*)unicode); + Py_DECREF(unicode); + } + else + s = escape_unicode((PyUnicodeObject*)text); + + /* convert the unicode string into a markup object. */ + rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL); + Py_DECREF(s); + return rv; +} + + +static PyObject* +escape_silent(PyObject *self, PyObject *text) +{ + if (text != Py_None) + return escape(self, text); + return PyObject_CallFunctionObjArgs(markup, NULL); +} + + +static PyObject* +soft_str(PyObject *self, PyObject *s) +{ + if (!PyUnicode_Check(s)) + return PyObject_Str(s); + Py_INCREF(s); + return s; +} + + +static PyMethodDef module_methods[] = { + { + "escape", + (PyCFunction)escape, + METH_O, + "Replace the characters ``&``, ``<``, ``>``, ``'``, and ``\"`` in" + " the string with HTML-safe sequences. Use this if you need to display" + " text that might contain such characters in HTML.\n\n" + "If the object has an ``__html__`` method, it is called and the" + " return value is assumed to already be safe for HTML.\n\n" + ":param s: An object to be converted to a string and escaped.\n" + ":return: A :class:`Markup` string with the escaped text.\n" + }, + { + "escape_silent", + (PyCFunction)escape_silent, + METH_O, + "Like :func:`escape` but treats ``None`` as the empty string." + " Useful with optional values, as otherwise you get the string" + " ``'None'`` when the value is ``None``.\n\n" + ">>> escape(None)\n" + "Markup('None')\n" + ">>> escape_silent(None)\n" + "Markup('')\n" + }, + { + "soft_str", + (PyCFunction)soft_str, + METH_O, + "Convert an object to a string if it isn't already. This preserves" + " a :class:`Markup` string rather than converting it back to a basic" + " string, so it will still be marked as safe and won't be escaped" + " again.\n\n" + ">>> value = escape(\"\")\n" + ">>> value\n" + "Markup('<User 1>')\n" + ">>> escape(str(value))\n" + "Markup('&lt;User 1&gt;')\n" + ">>> escape(soft_str(value))\n" + "Markup('<User 1>')\n" + }, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +static struct PyModuleDef module_definition = { + PyModuleDef_HEAD_INIT, + "markupsafe._speedups", + NULL, + -1, + module_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit__speedups(void) +{ + if (!init_constants()) + return NULL; + + return PyModule_Create(&module_definition); +} diff --git a/venv/lib/python3.8/site-packages/markupsafe/_speedups.cpython-38-x86_64-linux-gnu.so b/venv/lib/python3.8/site-packages/markupsafe/_speedups.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..1404eeb57ae594958df8956c1e6fc96baa56a3ce GIT binary patch literal 45024 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�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 Markup: ... +def escape_silent(s: Optional[Any]) -> Markup: ... +def soft_str(s: Any) -> str: ... +def soft_unicode(s: Any) -> str: ... diff --git a/venv/lib/python3.8/site-packages/markupsafe/py.typed b/venv/lib/python3.8/site-packages/markupsafe/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/AUTHORS.txt b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/AUTHORS.txt new file mode 100644 index 000000000..f42daec02 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/AUTHORS.txt @@ -0,0 +1,806 @@ +@Switch01 +A_Rog +Aakanksha Agrawal +Abhinav Sagar +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Wentz +admin +Adolfo Ochagavía +Adrien Morison +Agus +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Alberto Sottile +Aleks Bunin +Ales Erjavec +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Hedges +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Aleš Erjavec +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andre Aguiar +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrew Shymanel +Andrey Bienkowski +Andrey Bulgakov +Andrés Delfino +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Anton Zelenov +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anudit Nagar +Anuj Godase +AQNOUCH Mohammed +AraHaan +arena +arenasys +Arindam Choudhury +Armin Ronacher +Arnon Yaari +Artem +Arun Babu Neelicattu +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avinash Karhana +Avner Cohen +Awit (Ah-Wit) Ghirmai +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Bodenmiller +Ben Darnell +Ben Hoyt +Ben Mares +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernard +Bernard Tyers +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bhavam Vidyarthi +Blazej Michalik +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Branch Vincent +Brandon L. Reiss +Brandt Bucher +Brannon Dorsey +Brett Randall +Brett Rosen +Brian Cristante +Brian Rosner +briantracy +BrownTruck +Bruno Oliveira +Bruno Renié +Bruno S +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +bwoodsend +c22 +Caleb Brown +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Charlie Marsh +charwick +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris Kuehl +Chris Markiewicz +Chris McDonough +Chris Pawley +Chris Pryer +Chris Wolfe +Christian Clauss +Christian Heimes +Christian Oudard +Christoph Reiter +Christopher Hunt +Christopher Snyder +chrysle +cjc7373 +Clark Boylan +Claudio Jolowicz +Clay McClure +Cody +Cody Soyland +Colin Watson +Collin Anderson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Cristina +Cristina Muñoz +ctg123 +Curtis Doty +cytolentino +Daan De Meyer +Dale +Damian +Damian Quiroga +Damian Shaw +Dan Black +Dan Savilonis +Dan Sully +Dane Hillard +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Katz +Daniel Shaulov +Daniele Esposti +Daniele Nicolodi +Daniele Procida +Daniil Konovalenko +Danny Hermes +Danny McClanahan +Darren Kavanagh +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Caro +David D Lowe +David Evans +David Hewitt +David Linke +David Poggi +David Poznik +David Pursehouse +David Runge +David Tucker +David Wales +Davidovich +ddelange +Deepak Sharma +Deepyaman Datta +Denise Yu +dependabot[bot] +derwolfe +Desetude +Devesh Kumar Singh +devsagul +Diego Caraballo +Diego Ramirez +DiegoCaraballo +Dimitri Merejkowsky +Dimitri Papadopoulos +Dimitri Papadopoulos Orfanos +Dirk Stolle +Dmitry Gladkov +Dmitry Volodin +Domen Kožar +Dominic Davis-Foster +Donald Stufft +Dongweiming +doron zarhi +Dos Moonen +Douglas Thor +DrFeathers +Dustin Ingram +Dustin Rodrigues +Dwayne Bailey +Ed Morley +Edgar Ramírez +Edgar Ramírez Mondragón +Ee Durbin +Efflam Lemaillet +efflamlemaillet +Eitan Adler +ekristina +elainechan +Eli Schwartz +Elisha Hollander +Ellen Marie Dash +Emil Burzo +Emil Styrke +Emmanuel Arias +Endoh Takanao +enoch +Erdinc Mutlu +Eric Cousineau +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Erwin Janssen +Eugene Vereshchagin +everdimension +Federico +Felipe Peter +Felix Yan +fiber-space +Filip Kokosiński +Filipe Laíns +Finn Womack +finnagin +Flavio Amurrio +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Fredrik Orderud +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gavin +gdanielson +Geoffrey Sneddon +George Song +Georgi Valkov +Georgy Pchelkin +ghost +Giftlin Rajaiah +gizmoguy1 +gkdoc +Godefroid Chapelle +Gopinath M +GOTO Hayato +gousaiyang +gpiks +Greg Roodt +Greg Ward +Guilherme Espada +Guillaume Seguin +gutsytechster +Guy Rozendorn +Guy Tuval +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +harupy +Harutaka Kawamura +hauntsaninja +Henrich Hartzer +Henry Schreiner +Herbert Pfennig +Holly Stotelmyer +Honnix +Hsiaoming Yang +Hugo Lopes Tavares +Hugo van Kemenade +Hugues Bruant +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ikko Ashimine +Ilan Schnell +Illia Volochii +Ilya Baryshev +Inada Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Itamar Turner-Trauring +Ivan Pozdeev +J. Nick Koston +Jacob Kim +Jacob Walls +Jaime Sanz +jakirkham +Jakub Kuczys +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Curtin +James Firth +James Gerity +James Polley +Jan Pokorný +Jannis Leidel +Jarek Potiuk +jarondl +Jason Curtis +Jason R. Coombs +JasonMo +JasonMo1 +Jay Graves +Jean Abou Samra +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jeff Widman +Jelmer Vernooij +jenix21 +Jeremy Fleischman +Jeremy Stanley +Jeremy Zafran +Jesse Rittner +Jiashuo Li +Jim Fisher +Jim Garrison +Jinzhe Zeng +Jiun Bae +Jivan Amara +Joe Bylund +Joe Michelini +John Paton +John Sirois +John T. Wodder II +John-Scott Atlakson +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joonatan Partanen +Joost Molenaar +Jorge Niedbalski +Joseph Bylund +Joseph Long +Josh Bronson +Josh Cannon +Josh Hansen +Josh Schneier +Joshua +JoshuaPerdue +Juan Luis Cano Rodríguez +Juanjo Bazán +Judah Rand +Julian Berman +Julian Gethmann +Julien Demoor +July Tikhonov +Jussi Kukkonen +Justin van Heek +jwg4 +Jyrki Pulliainen +Kai Chen +Kai Mueller +Kamal Bin Mustafa +Karolina Surma +kasium +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +Klaas van Schelven +KOLANICH +konstin +kpinc +Krishna Oza +Kumar McMillan +Kuntal Majumder +Kurt McKee +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurent LAPORTE +Laurie O +Laurie Opperman +layday +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +lorddavidiii +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luis Medel +Lukas Geiger +Lukas Juhrich +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +M00nL1ght +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark McLoughlin +Mark Williams +Markus Hametner +Martey Dodoo +Martin Fischer +Martin Häcker +Martin Pavlasek +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Mathieu Kniewallner +Matt Bacchi +Matt Good +Matt Maker +Matt Robenolt +Matt Wozniski +matthew +Matthew Einhorn +Matthew Feickert +Matthew Gilliard +Matthew Hughes +Matthew Iversen +Matthew Treinish +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maurits van Rees +Max W Chase +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna +mdebi +memoselyk +meowmeowcat +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Mintz +Michael Williamson +michaelpacer +Michał Górny +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +morotti +mrKazzila +Muha Ajjan +Nadav Wexler +Nahuel Ambrosini +Nate Coraor +Nate Prewitt +Nathan Houghton +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nguyễn Gia Phong +Nicholas Serra +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nicole Harris +Nikhil Benesch +Nikhil Ladha +Nikita Chepanov +Nikolay Korolev +Nipunn Koorapati +Nitesh Sharma +Niyas Sait +Noah +Noah Gorny +Nowell Strite +NtaleGrey +nvdv +OBITORASU +Ofek Lev +ofrinevo +Oliver Freund +Oliver Jeeves +Oliver Mannion +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +onlinejudge95 +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Ganssle +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavel Safronov +Pavithra Eswaramoorthy +Pawel Jasinski +Paweł Szramowski +Pekka Klärck +Peter Gessler +Peter Lisák +Peter Shen +Peter Waller +Petr Viktorin +petr-tik +Phaneendra Chiruvella +Phil Elson +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +Pieter Degroote +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Prashant Sharma +Pratik Mallya +pre-commit-ci[bot] +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal +q0w +Qiangning Hong +Qiming Xu +Quentin Lee +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Ran Benita +Randy Döring +Razzi Abuissa +rdb +Reece Dunham +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Riccardo Schirone +Richard Jones +Richard Si +Ricky Ng-Adam +Rishi +rmorotti +RobberPhex +Robert Collins +Robert McGibbon +Robert Pollak +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Roman Bogorodskiy +Roman Donchenko +Romuald Brunet +ronaudinho +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Ruairidh MacLeod +Russell Keith-Magee +Ryan Shepherd +Ryan Wooden +ryneeverett +S. Guliaev +Sachi King +Salvatore Rinchiera +sandeepkiran-js +Sander Van Balen +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Michael Larson +Seth Woodworth +Shahar Epstein +Shantanu +shenxianpeng +shireenrao +Shivansh-007 +Shixian Sheng +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +sinscary +snook92 +socketubs +Sorin Sbarnea +Srinivas Nyayapati +Srishti Hegde +Stavros Korokithakis +Stefan Scherfke +Stefano Rivera +Stephan Erb +Stephen Rosen +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +Steven Silvester +stonebig +studioj +Stéphane Bidoul +Stéphane Bidoul (ACSONE) +Stéphane Klein +Sumana Harihareswara +Surbhi Sharma +Sviatoslav Sydorenko +Sviatoslav Sydorenko (Святослав Сидоренко) +Swat009 +Sylvain +Takayuki SHIMIZUKAWA +Taneli Hukkinen +tbeswick +Thiago +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Thomas VINCENT +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tobias Hermann +Tom Forbes +Tom Freudenheim +Tom V +Tomas Hrnciar +Tomas Orsava +Tomer Chachamu +Tommi Enenkel | AnB +Tomáš Hrnčiar +Tony Beswick +Tony Narlock +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +toxinu +Travis Swicegood +Tushar Sadhwani +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Vikram - Google +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo +Vipul Kumar +Vitaly Babiy +Vladimir Fokow +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William Edwards +William ML Leslie +William T Olson +William Woodruff +Wilson Mo +wim glenn +Winson Luk +Wolfgang Maier +Wu Zhenyu +XAMES3 +Xavier Fernandez +Xianpeng Shen +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Yusuke Hayashi +Zearin +Zhiping Deng +ziebam +Zvezdan Petkovic +Łukasz Langa +Роман Донченко +Семён Марьясин diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/INSTALLER b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/LICENSE.txt b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/LICENSE.txt new file mode 100644 index 000000000..8e7b65eaf --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-present The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/METADATA b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/METADATA new file mode 100644 index 000000000..3315c0635 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/METADATA @@ -0,0 +1,90 @@ +Metadata-Version: 2.2 +Name: pip +Version: 25.0.1 +Summary: The PyPA recommended tool for installing Python packages. +Author-email: The pip developers +License: MIT +Project-URL: Homepage, https://pip.pypa.io/ +Project-URL: Documentation, https://pip.pypa.io +Project-URL: Source, https://github.com/pypa/pip +Project-URL: Changelog, https://pip.pypa.io/en/stable/news/ +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Topic :: Software Development :: Build Tools +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE.txt +License-File: AUTHORS.txt + +pip - The Python Package Installer +================================== + +.. |pypi-version| image:: https://img.shields.io/pypi/v/pip.svg + :target: https://pypi.org/project/pip/ + :alt: PyPI + +.. |python-versions| image:: https://img.shields.io/pypi/pyversions/pip + :target: https://pypi.org/project/pip + :alt: PyPI - Python Version + +.. |docs-badge| image:: https://readthedocs.org/projects/pip/badge/?version=latest + :target: https://pip.pypa.io/en/latest + :alt: Documentation + +|pypi-version| |python-versions| |docs-badge| + +pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes. + +Please take a look at our documentation for how to install and use pip: + +* `Installation`_ +* `Usage`_ + +We release updates regularly, with a new version every 3 months. Find more details in our documentation: + +* `Release notes`_ +* `Release process`_ + +If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms: + +* `Issue tracking`_ +* `Discourse channel`_ +* `User IRC`_ + +If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms: + +* `GitHub page`_ +* `Development documentation`_ +* `Development IRC`_ + +Code of Conduct +--------------- + +Everyone interacting in the pip project's codebases, issue trackers, chat +rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_. + +.. _package installer: https://packaging.python.org/guides/tool-recommendations/ +.. _Python Package Index: https://pypi.org +.. _Installation: https://pip.pypa.io/en/stable/installation/ +.. _Usage: https://pip.pypa.io/en/stable/ +.. _Release notes: https://pip.pypa.io/en/stable/news.html +.. _Release process: https://pip.pypa.io/en/latest/development/release-process/ +.. _GitHub page: https://github.com/pypa/pip +.. _Development documentation: https://pip.pypa.io/en/latest/development +.. _Issue tracking: https://github.com/pypa/pip/issues +.. _Discourse channel: https://discuss.python.org/c/packaging +.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa +.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev +.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/RECORD b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/RECORD new file mode 100644 index 000000000..1c940a295 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/RECORD @@ -0,0 +1,854 @@ +../../../bin/pip,sha256=Ywb6pmWJWusrHmsaGLh7Tge26m3eqxzA_ElzkeW8Qss,278 +../../../bin/pip3,sha256=Ywb6pmWJWusrHmsaGLh7Tge26m3eqxzA_ElzkeW8Qss,278 +../../../bin/pip3.8,sha256=Ywb6pmWJWusrHmsaGLh7Tge26m3eqxzA_ElzkeW8Qss,278 +pip-25.0.1.dist-info/AUTHORS.txt,sha256=HqzpBVLfT1lBthqQfiDlVeFkg65hJ7ZQvvWhoq-BAsA,11018 +pip-25.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pip-25.0.1.dist-info/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093 +pip-25.0.1.dist-info/METADATA,sha256=T6cxjPMPl523zsRcEsu8K0-IoV8S7vv1mmKR0KA6-SY,3677 +pip-25.0.1.dist-info/RECORD,, +pip-25.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip-25.0.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91 +pip-25.0.1.dist-info/entry_points.txt,sha256=eeIjuzfnfR2PrhbjnbzFU6MnSS70kZLxwaHHq6M-bD0,87 +pip-25.0.1.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pip/__init__.py,sha256=aKiv_sTe7UbE7qmtCinJutFjqN0MndZQZ1fKLNwFFLE,357 +pip/__main__.py,sha256=WzbhHXTbSE6gBY19mNN9m4s5o_365LOvTYSgqgbdBhE,854 +pip/__pip-runner__.py,sha256=cPPWuJ6NK_k-GzfvlejLFgwzmYUROmpAR6QC3Q-vkXQ,1450 +pip/__pycache__/__init__.cpython-38.pyc,, +pip/__pycache__/__main__.cpython-38.pyc,, +pip/__pycache__/__pip-runner__.cpython-38.pyc,, +pip/_internal/__init__.py,sha256=MfcoOluDZ8QMCFYal04IqOJ9q6m2V7a0aOsnI-WOxUo,513 +pip/_internal/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/__pycache__/build_env.cpython-38.pyc,, +pip/_internal/__pycache__/cache.cpython-38.pyc,, +pip/_internal/__pycache__/configuration.cpython-38.pyc,, +pip/_internal/__pycache__/exceptions.cpython-38.pyc,, +pip/_internal/__pycache__/main.cpython-38.pyc,, +pip/_internal/__pycache__/pyproject.cpython-38.pyc,, +pip/_internal/__pycache__/self_outdated_check.cpython-38.pyc,, +pip/_internal/__pycache__/wheel_builder.cpython-38.pyc,, +pip/_internal/build_env.py,sha256=Dv4UCClSg4uNaal_hL-trg5-zl3Is9CuIDxkChCkXF4,10700 +pip/_internal/cache.py,sha256=Jb698p5PNigRtpW5o26wQNkkUv4MnQ94mc471wL63A0,10369 +pip/_internal/cli/__init__.py,sha256=FkHBgpxxb-_gd6r1FjnNhfMOzAUYyXoXKJ6abijfcFU,132 +pip/_internal/cli/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/cli/__pycache__/autocompletion.cpython-38.pyc,, +pip/_internal/cli/__pycache__/base_command.cpython-38.pyc,, +pip/_internal/cli/__pycache__/cmdoptions.cpython-38.pyc,, +pip/_internal/cli/__pycache__/command_context.cpython-38.pyc,, +pip/_internal/cli/__pycache__/index_command.cpython-38.pyc,, +pip/_internal/cli/__pycache__/main.cpython-38.pyc,, +pip/_internal/cli/__pycache__/main_parser.cpython-38.pyc,, +pip/_internal/cli/__pycache__/parser.cpython-38.pyc,, +pip/_internal/cli/__pycache__/progress_bars.cpython-38.pyc,, +pip/_internal/cli/__pycache__/req_command.cpython-38.pyc,, +pip/_internal/cli/__pycache__/spinners.cpython-38.pyc,, +pip/_internal/cli/__pycache__/status_codes.cpython-38.pyc,, +pip/_internal/cli/autocompletion.py,sha256=Lli3Mr6aDNu7ZkJJFFvwD2-hFxNI6Avz8OwMyS5TVrs,6865 +pip/_internal/cli/base_command.py,sha256=NZin6KMzW9NSYzKk4Tc8isb_TQYKR4CKd5j9mSm46PI,8625 +pip/_internal/cli/cmdoptions.py,sha256=V3BB22F4_v_RkHaZ5onWnszhbBtjYZvNhbn9M0NO0HI,30116 +pip/_internal/cli/command_context.py,sha256=RHgIPwtObh5KhMrd3YZTkl8zbVG-6Okml7YbFX4Ehg0,774 +pip/_internal/cli/index_command.py,sha256=i_sgNlPmXC5iHUaY-dmmrHKKTgc5O4hWzisr5Al1rr0,5677 +pip/_internal/cli/main.py,sha256=BDZef-bWe9g9Jpr4OVs4dDf-845HJsKw835T7AqEnAc,2817 +pip/_internal/cli/main_parser.py,sha256=laDpsuBDl6kyfywp9eMMA9s84jfH2TJJn-vmL0GG90w,4338 +pip/_internal/cli/parser.py,sha256=VCMtduzECUV87KaHNu-xJ-wLNL82yT3x16V4XBxOAqI,10825 +pip/_internal/cli/progress_bars.py,sha256=9GcgusWtwfqou2zhAQp1XNbQHIDslqyyz9UwLzw7Jgc,2717 +pip/_internal/cli/req_command.py,sha256=DqeFhmUMs6o6Ev8qawAcOoYNdAZsfyKS0MZI5jsJYwQ,12250 +pip/_internal/cli/spinners.py,sha256=hIJ83GerdFgFCdobIA23Jggetegl_uC4Sp586nzFbPE,5118 +pip/_internal/cli/status_codes.py,sha256=sEFHUaUJbqv8iArL3HAtcztWZmGOFX01hTesSytDEh0,116 +pip/_internal/commands/__init__.py,sha256=5oRO9O3dM2vGuh0bFw4HOVletryrz5HHMmmPWwJrH9U,3882 +pip/_internal/commands/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/commands/__pycache__/cache.cpython-38.pyc,, +pip/_internal/commands/__pycache__/check.cpython-38.pyc,, +pip/_internal/commands/__pycache__/completion.cpython-38.pyc,, +pip/_internal/commands/__pycache__/configuration.cpython-38.pyc,, +pip/_internal/commands/__pycache__/debug.cpython-38.pyc,, +pip/_internal/commands/__pycache__/download.cpython-38.pyc,, +pip/_internal/commands/__pycache__/freeze.cpython-38.pyc,, +pip/_internal/commands/__pycache__/hash.cpython-38.pyc,, +pip/_internal/commands/__pycache__/help.cpython-38.pyc,, +pip/_internal/commands/__pycache__/index.cpython-38.pyc,, +pip/_internal/commands/__pycache__/inspect.cpython-38.pyc,, +pip/_internal/commands/__pycache__/install.cpython-38.pyc,, +pip/_internal/commands/__pycache__/list.cpython-38.pyc,, +pip/_internal/commands/__pycache__/search.cpython-38.pyc,, +pip/_internal/commands/__pycache__/show.cpython-38.pyc,, +pip/_internal/commands/__pycache__/uninstall.cpython-38.pyc,, +pip/_internal/commands/__pycache__/wheel.cpython-38.pyc,, +pip/_internal/commands/cache.py,sha256=IOezTicHjGE5sWdBx2nwPVgbjuJHM3s-BZEkpZLemuY,8107 +pip/_internal/commands/check.py,sha256=Hr_4eiMd9cgVDgEvjtIdw915NmL7ROIWW8enkr8slPQ,2268 +pip/_internal/commands/completion.py,sha256=HT4lD0bgsflHq2IDgYfiEdp7IGGtE7s6MgI3xn0VQEw,4287 +pip/_internal/commands/configuration.py,sha256=n98enwp6y0b5G6fiRQjaZo43FlJKYve_daMhN-4BRNc,9766 +pip/_internal/commands/debug.py,sha256=DNDRgE9YsKrbYzU0s3VKi8rHtKF4X13CJ_br_8PUXO0,6797 +pip/_internal/commands/download.py,sha256=0qB0nys6ZEPsog451lDsjL5Bx7Z97t-B80oFZKhpzKM,5273 +pip/_internal/commands/freeze.py,sha256=2Vt72BYTSm9rzue6d8dNzt8idxWK4Db6Hd-anq7GQ80,3203 +pip/_internal/commands/hash.py,sha256=EVVOuvGtoPEdFi8SNnmdqlCQrhCxV-kJsdwtdcCnXGQ,1703 +pip/_internal/commands/help.py,sha256=gcc6QDkcgHMOuAn5UxaZwAStsRBrnGSn_yxjS57JIoM,1132 +pip/_internal/commands/index.py,sha256=RAXxmJwFhVb5S1BYzb5ifX3sn9Na8v2CCVYwSMP8pao,4731 +pip/_internal/commands/inspect.py,sha256=PGrY9TRTRCM3y5Ml8Bdk8DEOXquWRfscr4DRo1LOTPc,3189 +pip/_internal/commands/install.py,sha256=r3yHQUxvxt7gD5j9n6zRDslAvtx9CT_whLuQJcktp6M,29390 +pip/_internal/commands/list.py,sha256=oiIzSjLP6__d7dIS3q0Xb5ywsaOThBWRqMyjjKzkPdM,12769 +pip/_internal/commands/search.py,sha256=fWkUQVx_gm8ebbFAlCgqtxKXT9rNahpJ-BI__3HNZpg,5626 +pip/_internal/commands/show.py,sha256=0YBhCga3PAd81vT3l7UWflktSpB5-aYqQcJxBVPazVM,7857 +pip/_internal/commands/uninstall.py,sha256=7pOR7enK76gimyxQbzxcG1OsyLXL3DvX939xmM8Fvtg,3892 +pip/_internal/commands/wheel.py,sha256=eJRhr_qoNNxWAkkdJCNiQM7CXd4E1_YyQhsqJnBPGGg,6414 +pip/_internal/configuration.py,sha256=-KOok6jh3hFzXMPQFPJ1_EFjBpAsge-RSreQuLHLmzo,14005 +pip/_internal/distributions/__init__.py,sha256=Hq6kt6gXBgjNit5hTTWLAzeCNOKoB-N0pGYSqehrli8,858 +pip/_internal/distributions/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/distributions/__pycache__/base.cpython-38.pyc,, +pip/_internal/distributions/__pycache__/installed.cpython-38.pyc,, +pip/_internal/distributions/__pycache__/sdist.cpython-38.pyc,, +pip/_internal/distributions/__pycache__/wheel.cpython-38.pyc,, +pip/_internal/distributions/base.py,sha256=QeB9qvKXDIjLdPBDE5fMgpfGqMMCr-govnuoQnGuiF8,1783 +pip/_internal/distributions/installed.py,sha256=QinHFbWAQ8oE0pbD8MFZWkwlnfU1QYTccA1vnhrlYOU,842 +pip/_internal/distributions/sdist.py,sha256=PlcP4a6-R6c98XnOM-b6Lkb3rsvh9iG4ok8shaanrzs,6751 +pip/_internal/distributions/wheel.py,sha256=THBYfnv7VVt8mYhMYUtH13S1E7FDwtDyDfmUcl8ai0E,1317 +pip/_internal/exceptions.py,sha256=2_byISIv3kSnI_9T-Esfxrt0LnTRgcUHyxu0twsHjQY,26481 +pip/_internal/index/__init__.py,sha256=vpt-JeTZefh8a-FC22ZeBSXFVbuBcXSGiILhQZJaNpQ,30 +pip/_internal/index/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/index/__pycache__/collector.cpython-38.pyc,, +pip/_internal/index/__pycache__/package_finder.cpython-38.pyc,, +pip/_internal/index/__pycache__/sources.cpython-38.pyc,, +pip/_internal/index/collector.py,sha256=RdPO0JLAlmyBWPAWYHPyRoGjz3GNAeTngCNkbGey_mE,16265 +pip/_internal/index/package_finder.py,sha256=mJHAljlHeHuclyuxtjvBZO6DtovKjsZjF_tCh_wux5E,38076 +pip/_internal/index/sources.py,sha256=lPBLK5Xiy8Q6IQMio26Wl7ocfZOKkgGklIBNyUJ23fI,8632 +pip/_internal/locations/__init__.py,sha256=UaAxeZ_f93FyouuFf4p7SXYF-4WstXuEvd3LbmPCAno,14925 +pip/_internal/locations/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/locations/__pycache__/_distutils.cpython-38.pyc,, +pip/_internal/locations/__pycache__/_sysconfig.cpython-38.pyc,, +pip/_internal/locations/__pycache__/base.cpython-38.pyc,, +pip/_internal/locations/_distutils.py,sha256=x6nyVLj7X11Y4khIdf-mFlxMl2FWadtVEgeb8upc_WI,6013 +pip/_internal/locations/_sysconfig.py,sha256=IGzds60qsFneRogC-oeBaY7bEh3lPt_v47kMJChQXsU,7724 +pip/_internal/locations/base.py,sha256=RQiPi1d4FVM2Bxk04dQhXZ2PqkeljEL2fZZ9SYqIQ78,2556 +pip/_internal/main.py,sha256=r-UnUe8HLo5XFJz8inTcOOTiu_sxNhgHb6VwlGUllOI,340 +pip/_internal/metadata/__init__.py,sha256=CU8jK1TZso7jOLdr0sX9xDjrcs5iy8d7IRK-hvaIO5Y,4337 +pip/_internal/metadata/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/metadata/__pycache__/_json.cpython-38.pyc,, +pip/_internal/metadata/__pycache__/base.cpython-38.pyc,, +pip/_internal/metadata/__pycache__/pkg_resources.cpython-38.pyc,, +pip/_internal/metadata/_json.py,sha256=ezrIYazHCINM2QUk1eA9wEAMj3aeGWeDVgGalgUzKpc,2707 +pip/_internal/metadata/base.py,sha256=ft0K5XNgI4ETqZnRv2-CtvgYiMOMAeGMAzxT-f6VLJA,25298 +pip/_internal/metadata/importlib/__init__.py,sha256=jUUidoxnHcfITHHaAWG1G2i5fdBYklv_uJcjo2x7VYE,135 +pip/_internal/metadata/importlib/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/metadata/importlib/__pycache__/_compat.cpython-38.pyc,, +pip/_internal/metadata/importlib/__pycache__/_dists.cpython-38.pyc,, +pip/_internal/metadata/importlib/__pycache__/_envs.cpython-38.pyc,, +pip/_internal/metadata/importlib/_compat.py,sha256=c6av8sP8BBjAZuFSJow1iWfygUXNM3xRTCn5nqw6B9M,2796 +pip/_internal/metadata/importlib/_dists.py,sha256=ftmYiyfUGUIjnVwt6W-Ijsimy5c28KgmXly5Q5IQ2P4,8279 +pip/_internal/metadata/importlib/_envs.py,sha256=UUB980XSrDWrMpQ1_G45i0r8Hqlg_tg3IPQ63mEqbNc,7431 +pip/_internal/metadata/pkg_resources.py,sha256=U07ETAINSGeSRBfWUG93E4tZZbaW_f7PGzEqZN0hulc,10542 +pip/_internal/models/__init__.py,sha256=3DHUd_qxpPozfzouoqa9g9ts1Czr5qaHfFxbnxriepM,63 +pip/_internal/models/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/models/__pycache__/candidate.cpython-38.pyc,, +pip/_internal/models/__pycache__/direct_url.cpython-38.pyc,, +pip/_internal/models/__pycache__/format_control.cpython-38.pyc,, +pip/_internal/models/__pycache__/index.cpython-38.pyc,, +pip/_internal/models/__pycache__/installation_report.cpython-38.pyc,, +pip/_internal/models/__pycache__/link.cpython-38.pyc,, +pip/_internal/models/__pycache__/scheme.cpython-38.pyc,, +pip/_internal/models/__pycache__/search_scope.cpython-38.pyc,, +pip/_internal/models/__pycache__/selection_prefs.cpython-38.pyc,, +pip/_internal/models/__pycache__/target_python.cpython-38.pyc,, +pip/_internal/models/__pycache__/wheel.cpython-38.pyc,, +pip/_internal/models/candidate.py,sha256=zzgFRuw_kWPjKpGw7LC0ZUMD2CQ2EberUIYs8izjdCA,753 +pip/_internal/models/direct_url.py,sha256=uBtY2HHd3TO9cKQJWh0ThvE5FRr-MWRYChRU4IG9HZE,6578 +pip/_internal/models/format_control.py,sha256=wtsQqSK9HaUiNxQEuB-C62eVimw6G4_VQFxV9-_KDBE,2486 +pip/_internal/models/index.py,sha256=tYnL8oxGi4aSNWur0mG8DAP7rC6yuha_MwJO8xw0crI,1030 +pip/_internal/models/installation_report.py,sha256=zRVZoaz-2vsrezj_H3hLOhMZCK9c7TbzWgC-jOalD00,2818 +pip/_internal/models/link.py,sha256=GQ8hq7x-FDFPv25Nbn2veIM-MlBrGZDGLd7aZeF4Xrg,21448 +pip/_internal/models/scheme.py,sha256=PakmHJM3e8OOWSZFtfz1Az7f1meONJnkGuQxFlt3wBE,575 +pip/_internal/models/search_scope.py,sha256=67NEnsYY84784S-MM7ekQuo9KXLH-7MzFntXjapvAo0,4531 +pip/_internal/models/selection_prefs.py,sha256=qaFfDs3ciqoXPg6xx45N1jPLqccLJw4N0s4P0PyHTQ8,2015 +pip/_internal/models/target_python.py,sha256=2XaH2rZ5ZF-K5wcJbEMGEl7SqrTToDDNkrtQ2v_v_-Q,4271 +pip/_internal/models/wheel.py,sha256=G7dND_s4ebPkEL7RJ1qCY0QhUUWIIK6AnjWgRATF5no,4539 +pip/_internal/network/__init__.py,sha256=jf6Tt5nV_7zkARBrKojIXItgejvoegVJVKUbhAa5Ioc,50 +pip/_internal/network/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/network/__pycache__/auth.cpython-38.pyc,, +pip/_internal/network/__pycache__/cache.cpython-38.pyc,, +pip/_internal/network/__pycache__/download.cpython-38.pyc,, +pip/_internal/network/__pycache__/lazy_wheel.cpython-38.pyc,, +pip/_internal/network/__pycache__/session.cpython-38.pyc,, +pip/_internal/network/__pycache__/utils.cpython-38.pyc,, +pip/_internal/network/__pycache__/xmlrpc.cpython-38.pyc,, +pip/_internal/network/auth.py,sha256=D4gASjUrqoDFlSt6gQ767KAAjv6PUyJU0puDlhXNVRE,20809 +pip/_internal/network/cache.py,sha256=0yGMA3Eet59xBSLtbPAenvI53dl29oUOeqZ2c0QL2Ss,4614 +pip/_internal/network/download.py,sha256=FLOP29dPYECBiAi7eEjvAbNkyzaKNqbyjOT2m8HPW8U,6048 +pip/_internal/network/lazy_wheel.py,sha256=PBdoMoNQQIA84Fhgne38jWF52W4x_KtsHjxgv4dkRKA,7622 +pip/_internal/network/session.py,sha256=msM4es16LmmNEYNkrYyg8fTc7gAHbKFltawfKP27LOI,18771 +pip/_internal/network/utils.py,sha256=Inaxel-NxBu4PQWkjyErdnfewsFCcgHph7dzR1-FboY,4088 +pip/_internal/network/xmlrpc.py,sha256=sAxzOacJ-N1NXGPvap9jC3zuYWSnnv3GXtgR2-E2APA,1838 +pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/operations/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/operations/__pycache__/check.cpython-38.pyc,, +pip/_internal/operations/__pycache__/freeze.cpython-38.pyc,, +pip/_internal/operations/__pycache__/prepare.cpython-38.pyc,, +pip/_internal/operations/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/operations/build/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/operations/build/__pycache__/build_tracker.cpython-38.pyc,, +pip/_internal/operations/build/__pycache__/metadata.cpython-38.pyc,, +pip/_internal/operations/build/__pycache__/metadata_editable.cpython-38.pyc,, +pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-38.pyc,, +pip/_internal/operations/build/__pycache__/wheel.cpython-38.pyc,, +pip/_internal/operations/build/__pycache__/wheel_editable.cpython-38.pyc,, +pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-38.pyc,, +pip/_internal/operations/build/build_tracker.py,sha256=-ARW_TcjHCOX7D2NUOGntB4Fgc6b4aolsXkAK6BWL7w,4774 +pip/_internal/operations/build/metadata.py,sha256=9S0CUD8U3QqZeXp-Zyt8HxwU90lE4QrnYDgrqZDzBnc,1422 +pip/_internal/operations/build/metadata_editable.py,sha256=xlAwcP9q_8_fmv_3I39w9EZ7SQV9hnJZr9VuTsq2Y68,1510 +pip/_internal/operations/build/metadata_legacy.py,sha256=8i6i1QZX9m_lKPStEFsHKM0MT4a-CD408JOw99daLmo,2190 +pip/_internal/operations/build/wheel.py,sha256=sT12FBLAxDC6wyrDorh8kvcZ1jG5qInCRWzzP-UkJiQ,1075 +pip/_internal/operations/build/wheel_editable.py,sha256=yOtoH6zpAkoKYEUtr8FhzrYnkNHQaQBjWQ2HYae1MQg,1417 +pip/_internal/operations/build/wheel_legacy.py,sha256=K-6kNhmj-1xDF45ny1yheMerF0ui4EoQCLzEoHh6-tc,3045 +pip/_internal/operations/check.py,sha256=L24vRL8VWbyywdoeAhM89WCd8zLTnjIbULlKelUgIec,5912 +pip/_internal/operations/freeze.py,sha256=1_M79jAQKnCxWr-KCCmHuVXOVFGaUJHmoWLfFzgh7K4,9843 +pip/_internal/operations/install/__init__.py,sha256=mX7hyD2GNBO2mFGokDQ30r_GXv7Y_PLdtxcUv144e-s,51 +pip/_internal/operations/install/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/operations/install/__pycache__/editable_legacy.cpython-38.pyc,, +pip/_internal/operations/install/__pycache__/wheel.cpython-38.pyc,, +pip/_internal/operations/install/editable_legacy.py,sha256=PoEsNEPGbIZ2yQphPsmYTKLOCMs4gv5OcCdzW124NcA,1283 +pip/_internal/operations/install/wheel.py,sha256=X5Iz9yUg5LlK5VNQ9g2ikc6dcRu8EPi_SUi5iuEDRgo,27615 +pip/_internal/operations/prepare.py,sha256=joWJwPkuqGscQgVNImLK71e9hRapwKvRCM8HclysmvU,28118 +pip/_internal/pyproject.py,sha256=GLJ6rWRS5_2noKdajohoLyDty57Z7QXhcUAYghmTnWc,7286 +pip/_internal/req/__init__.py,sha256=HxBFtZy_BbCclLgr26waMtpzYdO5T3vxePvpGAXSt5s,2653 +pip/_internal/req/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/req/__pycache__/constructors.cpython-38.pyc,, +pip/_internal/req/__pycache__/req_file.cpython-38.pyc,, +pip/_internal/req/__pycache__/req_install.cpython-38.pyc,, +pip/_internal/req/__pycache__/req_set.cpython-38.pyc,, +pip/_internal/req/__pycache__/req_uninstall.cpython-38.pyc,, +pip/_internal/req/constructors.py,sha256=v1qzCN1mIldwx-nCrPc8JO4lxkm3Fv8M5RWvt8LISjc,18430 +pip/_internal/req/req_file.py,sha256=eys82McgaICOGic2UZRHjD720piKJPwmeSYdXlWwl6w,20234 +pip/_internal/req/req_install.py,sha256=BMptxHYg2uG_b-7HFEULPb3nuw0FMAbuea8zTq2rE7w,35786 +pip/_internal/req/req_set.py,sha256=j3esG0s6SzoVReX9rWn4rpYNtyET_fwxbwJPRimvRxo,2858 +pip/_internal/req/req_uninstall.py,sha256=qzDIxJo-OETWqGais7tSMCDcWbATYABT-Tid3ityF0s,23853 +pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/resolution/__pycache__/base.cpython-38.pyc,, +pip/_internal/resolution/base.py,sha256=qlmh325SBVfvG6Me9gc5Nsh5sdwHBwzHBq6aEXtKsLA,583 +pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/legacy/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/resolution/legacy/__pycache__/resolver.cpython-38.pyc,, +pip/_internal/resolution/legacy/resolver.py,sha256=3HZiJBRd1FTN6jQpI4qRO8-TbLYeIbUTS6PFvXnXs2w,24068 +pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/base.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-38.pyc,, +pip/_internal/resolution/resolvelib/base.py,sha256=DCf669FsqyQY5uqXeePDHQY1e4QO-pBzWH8O0s9-K94,5023 +pip/_internal/resolution/resolvelib/candidates.py,sha256=5UZ1upNnmqsP-nmEZaDYxaBgCoejw_e2WVGmmAvBxXc,20001 +pip/_internal/resolution/resolvelib/factory.py,sha256=MJOLSZJY8_28PPdcutoQ6gjJ_1eBDt6Z1edtfTJyR4E,32659 +pip/_internal/resolution/resolvelib/found_candidates.py,sha256=9hrTyQqFvl9I7Tji79F1AxHv39Qh1rkJ_7deSHSMfQc,6383 +pip/_internal/resolution/resolvelib/provider.py,sha256=bcsFnYvlmtB80cwVdW1fIwgol8ZNr1f1VHyRTkz47SM,9935 +pip/_internal/resolution/resolvelib/reporter.py,sha256=00JtoXEkTlw0-rl_sl54d71avwOsJHt9GGHcrj5Sza0,3168 +pip/_internal/resolution/resolvelib/requirements.py,sha256=7JG4Z72e5Yk4vU0S5ulGvbqTy4FMQGYhY5zQhX9zTtY,8065 +pip/_internal/resolution/resolvelib/resolver.py,sha256=nLJOsVMEVi2gQUVJoUFKMZAeu2f7GRMjGMvNSWyz0Bc,12592 +pip/_internal/self_outdated_check.py,sha256=1PFtttvLAeyCVR3tPoBq2sOlPD0IJ-KSqU6bc1HUk9c,8318 +pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/utils/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/utils/__pycache__/_jaraco_text.cpython-38.pyc,, +pip/_internal/utils/__pycache__/_log.cpython-38.pyc,, +pip/_internal/utils/__pycache__/appdirs.cpython-38.pyc,, +pip/_internal/utils/__pycache__/compat.cpython-38.pyc,, +pip/_internal/utils/__pycache__/compatibility_tags.cpython-38.pyc,, +pip/_internal/utils/__pycache__/datetime.cpython-38.pyc,, +pip/_internal/utils/__pycache__/deprecation.cpython-38.pyc,, +pip/_internal/utils/__pycache__/direct_url_helpers.cpython-38.pyc,, +pip/_internal/utils/__pycache__/egg_link.cpython-38.pyc,, +pip/_internal/utils/__pycache__/entrypoints.cpython-38.pyc,, +pip/_internal/utils/__pycache__/filesystem.cpython-38.pyc,, +pip/_internal/utils/__pycache__/filetypes.cpython-38.pyc,, +pip/_internal/utils/__pycache__/glibc.cpython-38.pyc,, +pip/_internal/utils/__pycache__/hashes.cpython-38.pyc,, +pip/_internal/utils/__pycache__/logging.cpython-38.pyc,, +pip/_internal/utils/__pycache__/misc.cpython-38.pyc,, +pip/_internal/utils/__pycache__/packaging.cpython-38.pyc,, +pip/_internal/utils/__pycache__/retry.cpython-38.pyc,, +pip/_internal/utils/__pycache__/setuptools_build.cpython-38.pyc,, +pip/_internal/utils/__pycache__/subprocess.cpython-38.pyc,, +pip/_internal/utils/__pycache__/temp_dir.cpython-38.pyc,, +pip/_internal/utils/__pycache__/unpacking.cpython-38.pyc,, +pip/_internal/utils/__pycache__/urls.cpython-38.pyc,, +pip/_internal/utils/__pycache__/virtualenv.cpython-38.pyc,, +pip/_internal/utils/__pycache__/wheel.cpython-38.pyc,, +pip/_internal/utils/_jaraco_text.py,sha256=M15uUPIh5NpP1tdUGBxRau6q1ZAEtI8-XyLEETscFfE,3350 +pip/_internal/utils/_log.py,sha256=-jHLOE_THaZz5BFcCnoSL9EYAtJ0nXem49s9of4jvKw,1015 +pip/_internal/utils/appdirs.py,sha256=swgcTKOm3daLeXTW6v5BUS2Ti2RvEnGRQYH_yDXklAo,1665 +pip/_internal/utils/compat.py,sha256=ckkFveBiYQjRWjkNsajt_oWPS57tJvE8XxoC4OIYgCY,2399 +pip/_internal/utils/compatibility_tags.py,sha256=OWq5axHpW-MEEPztGdvgADrgJPAcV9a88Rxm4Z8VBs8,6272 +pip/_internal/utils/datetime.py,sha256=m21Y3wAtQc-ji6Veb6k_M5g6A0ZyFI4egchTdnwh-pQ,242 +pip/_internal/utils/deprecation.py,sha256=k7Qg_UBAaaTdyq82YVARA6D7RmcGTXGv7fnfcgigj4Q,3707 +pip/_internal/utils/direct_url_helpers.py,sha256=r2MRtkVDACv9AGqYODBUC9CjwgtsUU1s68hmgfCJMtA,3196 +pip/_internal/utils/egg_link.py,sha256=0FePZoUYKv4RGQ2t6x7w5Z427wbA_Uo3WZnAkrgsuqo,2463 +pip/_internal/utils/entrypoints.py,sha256=YlhLTRl2oHBAuqhc-zmL7USS67TPWVHImjeAQHreZTQ,3064 +pip/_internal/utils/filesystem.py,sha256=ajvA-q4ocliW9kPp8Yquh-4vssXbu-UKbo5FV9V4X64,4950 +pip/_internal/utils/filetypes.py,sha256=i8XAQ0eFCog26Fw9yV0Yb1ygAqKYB1w9Cz9n0fj8gZU,716 +pip/_internal/utils/glibc.py,sha256=vUkWq_1pJuzcYNcGKLlQmABoUiisK8noYY1yc8Wq4w4,3734 +pip/_internal/utils/hashes.py,sha256=XGGLL0AG8-RhWnyz87xF6MFZ--BKadHU35D47eApCKI,4972 +pip/_internal/utils/logging.py,sha256=ONfbrhaD248akkosK79if97n20EABxwjOxp5dE5RCRY,11845 +pip/_internal/utils/misc.py,sha256=DWnYxBUItjRp7hhxEg4ih6P6YpKrykM86dbi_EcU8SQ,23450 +pip/_internal/utils/packaging.py,sha256=cm-X_0HVHV_jRwUVZh6AuEWqSitzf8EpaJ7Uv2UGu6A,2142 +pip/_internal/utils/retry.py,sha256=mhFbykXjhTnZfgzeuy-vl9c8nECnYn_CMtwNJX2tYzQ,1392 +pip/_internal/utils/setuptools_build.py,sha256=ouXpud-jeS8xPyTPsXJ-m34NPvK5os45otAzdSV_IJE,4435 +pip/_internal/utils/subprocess.py,sha256=EsvqSRiSMHF98T8Txmu6NLU3U--MpTTQjtNgKP0P--M,8988 +pip/_internal/utils/temp_dir.py,sha256=5qOXe8M4JeY6vaFQM867d5zkp1bSwMZ-KT5jymmP0Zg,9310 +pip/_internal/utils/unpacking.py,sha256=_gVdyzTRDMYktpnYljn4OoxrZTtMCf4xknSm4rK0WaA,11967 +pip/_internal/utils/urls.py,sha256=qceSOZb5lbNDrHNsv7_S4L4Ytszja5NwPKUMnZHbYnM,1599 +pip/_internal/utils/virtualenv.py,sha256=S6f7csYorRpiD6cvn3jISZYc3I8PJC43H5iMFpRAEDU,3456 +pip/_internal/utils/wheel.py,sha256=b442jkydFHjXzDy6cMR7MpzWBJ1Q82hR5F33cmcHV3g,4494 +pip/_internal/vcs/__init__.py,sha256=UAqvzpbi0VbZo3Ub6skEeZAw-ooIZR-zX_WpCbxyCoU,596 +pip/_internal/vcs/__pycache__/__init__.cpython-38.pyc,, +pip/_internal/vcs/__pycache__/bazaar.cpython-38.pyc,, +pip/_internal/vcs/__pycache__/git.cpython-38.pyc,, +pip/_internal/vcs/__pycache__/mercurial.cpython-38.pyc,, +pip/_internal/vcs/__pycache__/subversion.cpython-38.pyc,, +pip/_internal/vcs/__pycache__/versioncontrol.cpython-38.pyc,, +pip/_internal/vcs/bazaar.py,sha256=EKStcQaKpNu0NK4p5Q10Oc4xb3DUxFw024XrJy40bFQ,3528 +pip/_internal/vcs/git.py,sha256=3tpc9LQA_J4IVW5r5NvWaaSeDzcmJOrSFZN0J8vIKfU,18177 +pip/_internal/vcs/mercurial.py,sha256=oULOhzJ2Uie-06d1omkL-_Gc6meGaUkyogvqG9ZCyPs,5249 +pip/_internal/vcs/subversion.py,sha256=ddTugHBqHzV3ebKlU5QXHPN4gUqlyXbOx8q8NgXKvs8,11735 +pip/_internal/vcs/versioncontrol.py,sha256=cvf_-hnTAjQLXJ3d17FMNhQfcO1AcKWUF10tfrYyP-c,22440 +pip/_internal/wheel_builder.py,sha256=DL3A8LKeRj_ACp11WS5wSgASgPFqeyAeXJKdXfmaWXU,11799 +pip/_vendor/__init__.py,sha256=JYuAXvClhInxIrA2FTp5p-uuWVL7WV6-vEpTs46-Qh4,4873 +pip/_vendor/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/__pycache__/typing_extensions.cpython-38.pyc,, +pip/_vendor/cachecontrol/__init__.py,sha256=LMC5CBe94ZRL5xhlzwyPDmHXvBD0p7lT4R3Z73D6a_I,677 +pip/_vendor/cachecontrol/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/adapter.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/cache.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/controller.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/serialize.cpython-38.pyc,, +pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-38.pyc,, +pip/_vendor/cachecontrol/_cmd.py,sha256=iist2EpzJvDVIhMAxXq8iFnTBsiZAd6iplxfmNboNyk,1737 +pip/_vendor/cachecontrol/adapter.py,sha256=febjY4LV87iiCIK3jcl8iH58iaSA7b9WkovsByIDK0Y,6348 +pip/_vendor/cachecontrol/cache.py,sha256=OXwv7Fn2AwnKNiahJHnjtvaKLndvVLv_-zO-ltlV9qI,1953 +pip/_vendor/cachecontrol/caches/__init__.py,sha256=dtrrroK5BnADR1GWjCZ19aZ0tFsMfvFBtLQQU1sp_ag,303 +pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-38.pyc,, +pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-38.pyc,, +pip/_vendor/cachecontrol/caches/file_cache.py,sha256=b7oMgsRSqPmEsonVJw6uFEYUlFgD6GF8TyacOGG1x3M,5399 +pip/_vendor/cachecontrol/caches/redis_cache.py,sha256=9rmqwtYu_ljVkW6_oLqbC7EaX_a8YT_yLuna-eS0dgo,1386 +pip/_vendor/cachecontrol/controller.py,sha256=glbPj2iZlGqdBg8z09D2DtQOzoOGXnWvy7K2LEyBsEQ,18576 +pip/_vendor/cachecontrol/filewrapper.py,sha256=2ktXNPE0KqnyzF24aOsKCA58HQq1xeC6l2g6_zwjghc,4291 +pip/_vendor/cachecontrol/heuristics.py,sha256=gqMXU8w0gQuEQiSdu3Yg-0vd9kW7nrWKbLca75rheGE,4881 +pip/_vendor/cachecontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/cachecontrol/serialize.py,sha256=HQd2IllQ05HzPkVLMXTF2uX5mjEQjDBkxCqUJUODpZk,5163 +pip/_vendor/cachecontrol/wrapper.py,sha256=hsGc7g8QGQTT-4f8tgz3AM5qwScg6FO0BSdLSRdEvpU,1417 +pip/_vendor/certifi/__init__.py,sha256=p_GYZrjUwPBUhpLlCZoGb0miKBKSqDAyZC5DvIuqbHQ,94 +pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255 +pip/_vendor/certifi/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/certifi/__pycache__/__main__.cpython-38.pyc,, +pip/_vendor/certifi/__pycache__/core.cpython-38.pyc,, +pip/_vendor/certifi/cacert.pem,sha256=lO3rZukXdPyuk6BWUJFOKQliWaXH6HGh9l1GGrUgG0c,299427 +pip/_vendor/certifi/core.py,sha256=2SRT5rIcQChFDbe37BQa-kULxAgJ8qN6l1jfqTp4HIs,4486 +pip/_vendor/certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/distlib/__init__.py,sha256=dcwgYGYGQqAEawBXPDtIx80DO_3cOmFv8HTc8JMzknQ,625 +pip/_vendor/distlib/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/compat.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/database.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/index.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/locators.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/manifest.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/markers.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/metadata.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/resources.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/scripts.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/util.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/version.cpython-38.pyc,, +pip/_vendor/distlib/__pycache__/wheel.cpython-38.pyc,, +pip/_vendor/distlib/compat.py,sha256=2jRSjRI4o-vlXeTK2BCGIUhkc6e9ZGhSsacRM5oseTw,41467 +pip/_vendor/distlib/database.py,sha256=mHy_LxiXIsIVRb-T0-idBrVLw3Ffij5teHCpbjmJ9YU,51160 +pip/_vendor/distlib/index.py,sha256=lTbw268rRhj8dw1sib3VZ_0EhSGgoJO3FKJzSFMOaeA,20797 +pip/_vendor/distlib/locators.py,sha256=oBeAZpFuPQSY09MgNnLfQGGAXXvVO96BFpZyKMuK4tM,51026 +pip/_vendor/distlib/manifest.py,sha256=3qfmAmVwxRqU1o23AlfXrQGZzh6g_GGzTAP_Hb9C5zQ,14168 +pip/_vendor/distlib/markers.py,sha256=X6sDvkFGcYS8gUW8hfsWuKEKAqhQZAJ7iXOMLxRYjYk,5164 +pip/_vendor/distlib/metadata.py,sha256=zil3sg2EUfLXVigljY2d_03IJt-JSs7nX-73fECMX2s,38724 +pip/_vendor/distlib/resources.py,sha256=LwbPksc0A1JMbi6XnuPdMBUn83X7BPuFNWqPGEKI698,10820 +pip/_vendor/distlib/scripts.py,sha256=BJliaDAZaVB7WAkwokgC3HXwLD2iWiHaVI50H7C6eG8,18608 +pip/_vendor/distlib/t32.exe,sha256=a0GV5kCoWsMutvliiCKmIgV98eRZ33wXoS-XrqvJQVs,97792 +pip/_vendor/distlib/t64-arm.exe,sha256=68TAa32V504xVBnufojh0PcenpR3U4wAqTqf-MZqbPw,182784 +pip/_vendor/distlib/t64.exe,sha256=gaYY8hy4fbkHYTTnA4i26ct8IQZzkBG2pRdy0iyuBrc,108032 +pip/_vendor/distlib/util.py,sha256=vMPGvsS4j9hF6Y9k3Tyom1aaHLb0rFmZAEyzeAdel9w,66682 +pip/_vendor/distlib/version.py,sha256=s5VIs8wBn0fxzGxWM_aA2ZZyx525HcZbMvcTlTyZ3Rg,23727 +pip/_vendor/distlib/w32.exe,sha256=R4csx3-OGM9kL4aPIzQKRo5TfmRSHZo6QWyLhDhNBks,91648 +pip/_vendor/distlib/w64-arm.exe,sha256=xdyYhKj0WDcVUOCb05blQYvzdYIKMbmJn2SZvzkcey4,168448 +pip/_vendor/distlib/w64.exe,sha256=ejGf-rojoBfXseGLpya6bFTFPWRG21X5KvU8J5iU-K0,101888 +pip/_vendor/distlib/wheel.py,sha256=DFIVguEQHCdxnSdAO0dfFsgMcvVZitg7bCOuLwZ7A_s,43979 +pip/_vendor/distro/__init__.py,sha256=2fHjF-SfgPvjyNZ1iHh_wjqWdR_Yo5ODHwZC0jLBPhc,981 +pip/_vendor/distro/__main__.py,sha256=bu9d3TifoKciZFcqRBuygV3GSuThnVD_m2IK4cz96Vs,64 +pip/_vendor/distro/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/distro/__pycache__/__main__.cpython-38.pyc,, +pip/_vendor/distro/__pycache__/distro.cpython-38.pyc,, +pip/_vendor/distro/distro.py,sha256=XqbefacAhDT4zr_trnbA15eY8vdK4GTghgmvUGrEM_4,49430 +pip/_vendor/distro/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868 +pip/_vendor/idna/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/idna/__pycache__/codec.cpython-38.pyc,, +pip/_vendor/idna/__pycache__/compat.cpython-38.pyc,, +pip/_vendor/idna/__pycache__/core.cpython-38.pyc,, +pip/_vendor/idna/__pycache__/idnadata.cpython-38.pyc,, +pip/_vendor/idna/__pycache__/intranges.cpython-38.pyc,, +pip/_vendor/idna/__pycache__/package_data.cpython-38.pyc,, +pip/_vendor/idna/__pycache__/uts46data.cpython-38.pyc,, +pip/_vendor/idna/codec.py,sha256=PEew3ItwzjW4hymbasnty2N2OXvNcgHB-JjrBuxHPYY,3422 +pip/_vendor/idna/compat.py,sha256=RzLy6QQCdl9784aFhb2EX9EKGCJjg0P3PilGdeXXcx8,316 +pip/_vendor/idna/core.py,sha256=YJYyAMnwiQEPjVC4-Fqu_p4CJ6yKKuDGmppBNQNQpFs,13239 +pip/_vendor/idna/idnadata.py,sha256=W30GcIGvtOWYwAjZj4ZjuouUutC6ffgNuyjJy7fZ-lo,78306 +pip/_vendor/idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898 +pip/_vendor/idna/package_data.py,sha256=q59S3OXsc5VI8j6vSD0sGBMyk6zZ4vWFREE88yCJYKs,21 +pip/_vendor/idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/idna/uts46data.py,sha256=rt90K9J40gUSwppDPCrhjgi5AA6pWM65dEGRSf6rIhM,239289 +pip/_vendor/msgpack/__init__.py,sha256=reRaiOtEzSjPnr7TpxjgIvbfln5pV66FhricAs2eC-g,1109 +pip/_vendor/msgpack/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/msgpack/__pycache__/exceptions.cpython-38.pyc,, +pip/_vendor/msgpack/__pycache__/ext.cpython-38.pyc,, +pip/_vendor/msgpack/__pycache__/fallback.cpython-38.pyc,, +pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081 +pip/_vendor/msgpack/ext.py,sha256=kteJv03n9tYzd5oo3xYopVTo4vRaAxonBQQJhXohZZo,5726 +pip/_vendor/msgpack/fallback.py,sha256=0g1Pzp0vtmBEmJ5w9F3s_-JMVURP8RS4G1cc5TRaAsI,32390 +pip/_vendor/packaging/__init__.py,sha256=dk4Ta_vmdVJxYHDcfyhvQNw8V3PgSBomKNXqg-D2JDY,494 +pip/_vendor/packaging/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/_elffile.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/_manylinux.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/_musllinux.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/_parser.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/_structures.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/_tokenizer.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/markers.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/metadata.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/requirements.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/tags.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/utils.cpython-38.pyc,, +pip/_vendor/packaging/__pycache__/version.cpython-38.pyc,, +pip/_vendor/packaging/_elffile.py,sha256=cflAQAkE25tzhYmq_aCi72QfbT_tn891tPzfpbeHOwE,3306 +pip/_vendor/packaging/_manylinux.py,sha256=vl5OCoz4kx80H5rwXKeXWjl9WNISGmr4ZgTpTP9lU9c,9612 +pip/_vendor/packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694 +pip/_vendor/packaging/_parser.py,sha256=s_TvTvDNK0NrM2QB3VKThdWFM4Nc0P6JnkObkl3MjpM,10236 +pip/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431 +pip/_vendor/packaging/_tokenizer.py,sha256=J6v5H7Jzvb-g81xp_2QACKwO7LxHQA6ikryMU7zXwN8,5273 +pip/_vendor/packaging/licenses/__init__.py,sha256=A116-FU49_Dz4162M4y1uAiZN4Rgdc83FxNd8EjlfqI,5727 +pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-38.pyc,, +pip/_vendor/packaging/licenses/_spdx.py,sha256=oAm1ztPFwlsmCKe7lAAsv_OIOfS1cWDu9bNBkeu-2ns,48398 +pip/_vendor/packaging/markers.py,sha256=c89TNzB7ZdGYhkovm6PYmqGyHxXlYVaLW591PHUNKD8,10561 +pip/_vendor/packaging/metadata.py,sha256=YJibM7GYe4re8-0a3OlXmGS-XDgTEoO4tlBt2q25Bng,34762 +pip/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947 +pip/_vendor/packaging/specifiers.py,sha256=hGU6kuCd77bL-msIL6yLCp6MNT75RSMUKZDuju26c8U,40098 +pip/_vendor/packaging/tags.py,sha256=CFqrJzAzc2XNGexerH__T-Y5Iwq7WbsYXsiLERLWxY0,21014 +pip/_vendor/packaging/utils.py,sha256=0F3Hh9OFuRgrhTgGZUl5K22Fv1YP2tZl1z_2gO6kJiA,5050 +pip/_vendor/packaging/version.py,sha256=oiHqzTUv_p12hpjgsLDVcaF5hT7pDaSOViUNMD4GTW0,16688 +pip/_vendor/pkg_resources/__init__.py,sha256=jrhDRbOubP74QuPXxd7U7Po42PH2l-LZ2XfcO7llpZ4,124463 +pip/_vendor/pkg_resources/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/platformdirs/__init__.py,sha256=JueR2cRLkxY7iwik-qNWJCwKOrAlBgVgcZ_IHQzqGLE,22344 +pip/_vendor/platformdirs/__main__.py,sha256=jBJ8zb7Mpx5ebcqF83xrpO94MaeCpNGHVf9cvDN2JLg,1505 +pip/_vendor/platformdirs/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/platformdirs/__pycache__/__main__.cpython-38.pyc,, +pip/_vendor/platformdirs/__pycache__/android.cpython-38.pyc,, +pip/_vendor/platformdirs/__pycache__/api.cpython-38.pyc,, +pip/_vendor/platformdirs/__pycache__/macos.cpython-38.pyc,, +pip/_vendor/platformdirs/__pycache__/unix.cpython-38.pyc,, +pip/_vendor/platformdirs/__pycache__/version.cpython-38.pyc,, +pip/_vendor/platformdirs/__pycache__/windows.cpython-38.pyc,, +pip/_vendor/platformdirs/android.py,sha256=kV5oL3V3DZ6WZKu9yFiQupv18yp_jlSV2ChH1TmPcds,9007 +pip/_vendor/platformdirs/api.py,sha256=2dfUDNbEXeDhDKarqtR5NY7oUikUZ4RZhs3ozstmhBQ,9246 +pip/_vendor/platformdirs/macos.py,sha256=UlbyFZ8Rzu3xndCqQEHrfsYTeHwYdFap1Ioz-yxveT4,6154 +pip/_vendor/platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/platformdirs/unix.py,sha256=uRPJWRyQEtv7yOSvU94rUmsblo5XKDLA1SzFg55kbK0,10393 +pip/_vendor/platformdirs/version.py,sha256=oH4KgTfK4AklbTYVcV_yynvJ9JLI3pyvDVay0hRsLCs,411 +pip/_vendor/platformdirs/windows.py,sha256=IFpiohUBwxPtCzlyKwNtxyW4Jk8haa6W8o59mfrDXVo,10125 +pip/_vendor/pygments/__init__.py,sha256=7N1oiaWulw_nCsTY4EEixYLz15pWY5u4uPAFFi-ielU,2983 +pip/_vendor/pygments/__main__.py,sha256=isIhBxLg65nLlXukG4VkMuPfNdd7gFzTZ_R_z3Q8diY,353 +pip/_vendor/pygments/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/__main__.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/cmdline.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/console.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/filter.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/formatter.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/lexer.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/modeline.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/plugin.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/regexopt.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/scanner.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/sphinxext.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/style.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/token.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/unistring.cpython-38.pyc,, +pip/_vendor/pygments/__pycache__/util.cpython-38.pyc,, +pip/_vendor/pygments/cmdline.py,sha256=LIVzmAunlk9sRJJp54O4KRy9GDIN4Wu13v9p9QzfGPM,23656 +pip/_vendor/pygments/console.py,sha256=yhP9UsLAVmWKVQf2446JJewkA7AiXeeTf4Ieg3Oi2fU,1718 +pip/_vendor/pygments/filter.py,sha256=_ADNPCskD8_GmodHi6_LoVgPU3Zh336aBCT5cOeTMs0,1910 +pip/_vendor/pygments/filters/__init__.py,sha256=RdedK2KWKXlKwR7cvkfr3NUj9YiZQgMgilRMFUg2jPA,40392 +pip/_vendor/pygments/filters/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/pygments/formatter.py,sha256=jDWBTndlBH2Z5IYZFVDnP0qn1CaTQjTWt7iAGtCnJEg,4390 +pip/_vendor/pygments/formatters/__init__.py,sha256=8No-NUs8rBTSSBJIv4hSEQt2M0cFB4hwAT0snVc2QGE,5385 +pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/groff.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/html.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/img.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/irc.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/latex.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/other.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/svg.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-38.pyc,, +pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-38.pyc,, +pip/_vendor/pygments/formatters/_mapping.py,sha256=1Cw37FuQlNacnxRKmtlPX4nyLoX9_ttko5ZwscNUZZ4,4176 +pip/_vendor/pygments/formatters/bbcode.py,sha256=3JQLI45tcrQ_kRUMjuab6C7Hb0XUsbVWqqbSn9cMjkI,3320 +pip/_vendor/pygments/formatters/groff.py,sha256=M39k0PaSSZRnxWjqBSVPkF0mu1-Vr7bm6RsFvs-CNN4,5106 +pip/_vendor/pygments/formatters/html.py,sha256=SE2jc3YCqbMS3rZW9EAmDlAUhdVxJ52gA4dileEvCGU,35669 +pip/_vendor/pygments/formatters/img.py,sha256=MwA4xWPLOwh6j7Yc6oHzjuqSPt0M1fh5r-5BTIIUfsU,23287 +pip/_vendor/pygments/formatters/irc.py,sha256=dp1Z0l_ObJ5NFh9MhqLGg5ptG5hgJqedT2Vkutt9v0M,4981 +pip/_vendor/pygments/formatters/latex.py,sha256=XMmhOCqUKDBQtG5mGJNAFYxApqaC5puo5cMmPfK3944,19306 +pip/_vendor/pygments/formatters/other.py,sha256=56PMJOliin-rAUdnRM0i1wsV1GdUPd_dvQq0_UPfF9c,5034 +pip/_vendor/pygments/formatters/pangomarkup.py,sha256=y16U00aVYYEFpeCfGXlYBSMacG425CbfoG8oKbKegIg,2218 +pip/_vendor/pygments/formatters/rtf.py,sha256=ZT90dmcKyJboIB0mArhL7IhE467GXRN0G7QAUgG03To,11957 +pip/_vendor/pygments/formatters/svg.py,sha256=KKsiophPupHuxm0So-MsbQEWOT54IAiSF7hZPmxtKXE,7174 +pip/_vendor/pygments/formatters/terminal.py,sha256=AojNG4MlKq2L6IsC_VnXHu4AbHCBn9Otog6u45XvxeI,4674 +pip/_vendor/pygments/formatters/terminal256.py,sha256=kGkNUVo3FpwjytIDS0if79EuUoroAprcWt3igrcIqT0,11753 +pip/_vendor/pygments/lexer.py,sha256=TYHDt___gNW4axTl2zvPZff-VQi8fPaIh5OKRcVSjUM,35349 +pip/_vendor/pygments/lexers/__init__.py,sha256=pIlxyQJuu_syh9lE080cq8ceVbEVcKp0osAFU5fawJU,12115 +pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-38.pyc,, +pip/_vendor/pygments/lexers/__pycache__/python.cpython-38.pyc,, +pip/_vendor/pygments/lexers/_mapping.py,sha256=61-h3zr103m01OS5BUq_AfUiL9YI06Ves9ipQ7k4vr4,76097 +pip/_vendor/pygments/lexers/python.py,sha256=2J_YJrPTr_A6fJY_qKiKv0GpgPwHMrlMSeo59qN3fe4,53687 +pip/_vendor/pygments/modeline.py,sha256=gtRYZBS-CKOCDXHhGZqApboHBaZwGH8gznN3O6nuxj4,1005 +pip/_vendor/pygments/plugin.py,sha256=ioeJ3QeoJ-UQhZpY9JL7vbxsTVuwwM7BCu-Jb8nN0AU,1891 +pip/_vendor/pygments/regexopt.py,sha256=Hky4EB13rIXEHQUNkwmCrYqtIlnXDehNR3MztafZ43w,3072 +pip/_vendor/pygments/scanner.py,sha256=NDy3ofK_fHRFK4hIDvxpamG871aewqcsIb6sgTi7Fhk,3092 +pip/_vendor/pygments/sphinxext.py,sha256=iOptJBcqOGPwMEJ2p70PvwpZPIGdvdZ8dxvq6kzxDgA,7981 +pip/_vendor/pygments/style.py,sha256=rSCZWFpg1_DwFMXDU0nEVmAcBHpuQGf9RxvOPPQvKLQ,6420 +pip/_vendor/pygments/styles/__init__.py,sha256=qUk6_1z5KmT8EdJFZYgESmG6P_HJF_2vVrDD7HSCGYY,2042 +pip/_vendor/pygments/styles/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-38.pyc,, +pip/_vendor/pygments/styles/_mapping.py,sha256=6lovFUE29tz6EsV3XYY4hgozJ7q1JL7cfO3UOlgnS8w,3312 +pip/_vendor/pygments/token.py,sha256=qZwT7LSPy5YBY3JgDjut642CCy7JdQzAfmqD9NmT5j0,6226 +pip/_vendor/pygments/unistring.py,sha256=p5c1i-HhoIhWemy9CUsaN9o39oomYHNxXll0Xfw6tEA,63208 +pip/_vendor/pygments/util.py,sha256=2tj2nS1X9_OpcuSjf8dOET2bDVZhs8cEKd_uT6-Fgg8,10031 +pip/_vendor/pyproject_hooks/__init__.py,sha256=cPB_a9LXz5xvsRbX1o2qyAdjLatZJdQ_Lc5McNX-X7Y,691 +pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-38.pyc,, +pip/_vendor/pyproject_hooks/_impl.py,sha256=jY-raxnmyRyB57ruAitrJRUzEexuAhGTpgMygqx67Z4,14936 +pip/_vendor/pyproject_hooks/_in_process/__init__.py,sha256=MJNPpfIxcO-FghxpBbxkG1rFiQf6HOUbV4U5mq0HFns,557 +pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-38.pyc,, +pip/_vendor/pyproject_hooks/_in_process/_in_process.py,sha256=qcXMhmx__MIJq10gGHW3mA4Tl8dy8YzHMccwnNoKlw0,12216 +pip/_vendor/pyproject_hooks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/requests/__init__.py,sha256=HlB_HzhrzGtfD_aaYUwUh1zWXLZ75_YCLyit75d0Vz8,5057 +pip/_vendor/requests/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/__version__.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/_internal_utils.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/adapters.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/api.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/auth.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/certs.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/compat.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/cookies.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/exceptions.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/help.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/hooks.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/models.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/packages.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/sessions.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/status_codes.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/structures.cpython-38.pyc,, +pip/_vendor/requests/__pycache__/utils.cpython-38.pyc,, +pip/_vendor/requests/__version__.py,sha256=FVfglgZmNQnmYPXpOohDU58F5EUb_-VnSTaAesS187g,435 +pip/_vendor/requests/_internal_utils.py,sha256=nMQymr4hs32TqVo5AbCrmcJEhvPUh7xXlluyqwslLiQ,1495 +pip/_vendor/requests/adapters.py,sha256=J7VeVxKBvawbtlX2DERVo05J9BXTcWYLMHNd1Baa-bk,27607 +pip/_vendor/requests/api.py,sha256=_Zb9Oa7tzVIizTKwFrPjDEY9ejtm_OnSRERnADxGsQs,6449 +pip/_vendor/requests/auth.py,sha256=kF75tqnLctZ9Mf_hm9TZIj4cQWnN5uxRz8oWsx5wmR0,10186 +pip/_vendor/requests/certs.py,sha256=kHDlkK_beuHXeMPc5jta2wgl8gdKeUWt5f2nTDVrvt8,441 +pip/_vendor/requests/compat.py,sha256=Mo9f9xZpefod8Zm-n9_StJcVTmwSukXR2p3IQyyVXvU,1485 +pip/_vendor/requests/cookies.py,sha256=bNi-iqEj4NPZ00-ob-rHvzkvObzN3lEpgw3g6paS3Xw,18590 +pip/_vendor/requests/exceptions.py,sha256=D1wqzYWne1mS2rU43tP9CeN1G7QAy7eqL9o1god6Ejw,4272 +pip/_vendor/requests/help.py,sha256=hRKaf9u0G7fdwrqMHtF3oG16RKktRf6KiwtSq2Fo1_0,3813 +pip/_vendor/requests/hooks.py,sha256=CiuysiHA39V5UfcCBXFIx83IrDpuwfN9RcTUgv28ftQ,733 +pip/_vendor/requests/models.py,sha256=x4K4CmH-lC0l2Kb-iPfMN4dRXxHEcbOaEWBL_i09AwI,35483 +pip/_vendor/requests/packages.py,sha256=_ZQDCJTJ8SP3kVWunSqBsRZNPzj2c1WFVqbdr08pz3U,1057 +pip/_vendor/requests/sessions.py,sha256=ykTI8UWGSltOfH07HKollH7kTBGw4WhiBVaQGmckTw4,30495 +pip/_vendor/requests/status_codes.py,sha256=iJUAeA25baTdw-6PfD0eF4qhpINDJRJI-yaMqxs4LEI,4322 +pip/_vendor/requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912 +pip/_vendor/requests/utils.py,sha256=L79vnFbzJ3SFLKtJwpoWe41Tozi3RlZv94pY1TFIyow,33631 +pip/_vendor/resolvelib/__init__.py,sha256=h509TdEcpb5-44JonaU3ex2TM15GVBLjM9CNCPwnTTs,537 +pip/_vendor/resolvelib/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/resolvelib/__pycache__/providers.cpython-38.pyc,, +pip/_vendor/resolvelib/__pycache__/reporters.cpython-38.pyc,, +pip/_vendor/resolvelib/__pycache__/resolvers.cpython-38.pyc,, +pip/_vendor/resolvelib/__pycache__/structs.cpython-38.pyc,, +pip/_vendor/resolvelib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-38.pyc,, +pip/_vendor/resolvelib/compat/collections_abc.py,sha256=uy8xUZ-NDEw916tugUXm8HgwCGiMO0f-RcdnpkfXfOs,156 +pip/_vendor/resolvelib/providers.py,sha256=fuuvVrCetu5gsxPB43ERyjfO8aReS3rFQHpDgiItbs4,5871 +pip/_vendor/resolvelib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/resolvelib/reporters.py,sha256=TSbRmWzTc26w0ggsV1bxVpeWDB8QNIre6twYl7GIZBE,1601 +pip/_vendor/resolvelib/resolvers.py,sha256=G8rsLZSq64g5VmIq-lB7UcIJ1gjAxIQJmTF4REZleQ0,20511 +pip/_vendor/resolvelib/structs.py,sha256=0_1_XO8z_CLhegP3Vpf9VJ3zJcfLm0NOHRM-i0Ykz3o,4963 +pip/_vendor/rich/__init__.py,sha256=dRxjIL-SbFVY0q3IjSMrfgBTHrm1LZDgLOygVBwiYZc,6090 +pip/_vendor/rich/__main__.py,sha256=eO7Cq8JnrgG8zVoeImiAs92q3hXNMIfp0w5lMsO7Q2Y,8477 +pip/_vendor/rich/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/__main__.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_cell_widths.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_emoji_codes.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_emoji_replace.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_export_format.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_extension.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_fileno.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_inspect.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_log_render.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_loop.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_null_file.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_palettes.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_pick.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_ratio.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_spinners.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_stack.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_timer.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_win32_console.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_windows.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_windows_renderer.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/_wrap.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/abc.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/align.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/ansi.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/bar.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/box.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/cells.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/color.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/color_triplet.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/columns.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/console.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/constrain.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/containers.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/control.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/default_styles.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/diagnose.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/emoji.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/errors.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/file_proxy.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/filesize.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/highlighter.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/json.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/jupyter.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/layout.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/live.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/live_render.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/logging.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/markup.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/measure.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/padding.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/pager.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/palette.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/panel.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/pretty.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/progress.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/progress_bar.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/prompt.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/protocol.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/region.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/repr.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/rule.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/scope.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/screen.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/segment.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/spinner.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/status.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/style.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/styled.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/syntax.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/table.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/terminal_theme.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/text.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/theme.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/themes.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/traceback.cpython-38.pyc,, +pip/_vendor/rich/__pycache__/tree.cpython-38.pyc,, +pip/_vendor/rich/_cell_widths.py,sha256=fbmeyetEdHjzE_Vx2l1uK7tnPOhMs2X1lJfO3vsKDpA,10209 +pip/_vendor/rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235 +pip/_vendor/rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064 +pip/_vendor/rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128 +pip/_vendor/rich/_extension.py,sha256=Xt47QacCKwYruzjDi-gOBq724JReDj9Cm9xUi5fr-34,265 +pip/_vendor/rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799 +pip/_vendor/rich/_inspect.py,sha256=QM05lEFnFoTaFqpnbx-zBEI6k8oIKrD3cvjEOQNhKig,9655 +pip/_vendor/rich/_log_render.py,sha256=1ByI0PA1ZpxZY3CGJOK54hjlq4X-Bz_boIjIqCd8Kns,3225 +pip/_vendor/rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236 +pip/_vendor/rich/_null_file.py,sha256=ADGKp1yt-k70FMKV6tnqCqecB-rSJzp-WQsD7LPL-kg,1394 +pip/_vendor/rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063 +pip/_vendor/rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423 +pip/_vendor/rich/_ratio.py,sha256=Zt58apszI6hAAcXPpgdWKpu3c31UBWebOeR4mbyptvU,5471 +pip/_vendor/rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919 +pip/_vendor/rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351 +pip/_vendor/rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417 +pip/_vendor/rich/_win32_console.py,sha256=BSaDRIMwBLITn_m0mTRLPqME5q-quGdSMuYMpYeYJwc,22755 +pip/_vendor/rich/_windows.py,sha256=aBwaD_S56SbgopIvayVmpk0Y28uwY2C5Bab1wl3Bp-I,1925 +pip/_vendor/rich/_windows_renderer.py,sha256=t74ZL3xuDCP3nmTp9pH1L5LiI2cakJuQRQleHCJerlk,2783 +pip/_vendor/rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404 +pip/_vendor/rich/abc.py,sha256=ON-E-ZqSSheZ88VrKX2M3PXpFbGEUUZPMa_Af0l-4f0,890 +pip/_vendor/rich/align.py,sha256=Rh-3adnDaN1Ao07EjR2PhgE62PGLPgO8SMwJBku1urQ,10469 +pip/_vendor/rich/ansi.py,sha256=Avs1LHbSdcyOvDOdpELZUoULcBiYewY76eNBp6uFBhs,6921 +pip/_vendor/rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263 +pip/_vendor/rich/box.py,sha256=nr5fYIUghB_iUCEq6y0Z3LlCT8gFPDrzN9u2kn7tJl4,10831 +pip/_vendor/rich/cells.py,sha256=KrQkj5-LghCCpJLSNQIyAZjndc4bnEqOEmi5YuZ9UCY,5130 +pip/_vendor/rich/color.py,sha256=3HSULVDj7qQkXUdFWv78JOiSZzfy5y1nkcYhna296V0,18211 +pip/_vendor/rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054 +pip/_vendor/rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131 +pip/_vendor/rich/console.py,sha256=nKjrEx_7xy8KGmDVT-BgNII0R5hm1cexhAHDwdwNVqg,100156 +pip/_vendor/rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288 +pip/_vendor/rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502 +pip/_vendor/rich/control.py,sha256=DSkHTUQLorfSERAKE_oTAEUFefZnZp4bQb4q8rHbKws,6630 +pip/_vendor/rich/default_styles.py,sha256=dZxgaSD9VUy7SXQShO33aLYiAWspCr2sCQZFX_JK1j4,8159 +pip/_vendor/rich/diagnose.py,sha256=an6uouwhKPAlvQhYpNNpGq9EJysfMIOvvCbO3oSoR24,972 +pip/_vendor/rich/emoji.py,sha256=omTF9asaAnsM4yLY94eR_9dgRRSm1lHUszX20D1yYCQ,2501 +pip/_vendor/rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642 +pip/_vendor/rich/file_proxy.py,sha256=Tl9THMDZ-Pk5Wm8sI1gGg_U5DhusmxD-FZ0fUbcU0W0,1683 +pip/_vendor/rich/filesize.py,sha256=_iz9lIpRgvW7MNSeCZnLg-HwzbP4GETg543WqD8SFs0,2484 +pip/_vendor/rich/highlighter.py,sha256=G_sn-8DKjM1sEjLG_oc4ovkWmiUpWvj8bXi0yed2LnY,9586 +pip/_vendor/rich/json.py,sha256=vVEoKdawoJRjAFayPwXkMBPLy7RSTs-f44wSQDR2nJ0,5031 +pip/_vendor/rich/jupyter.py,sha256=QyoKoE_8IdCbrtiSHp9TsTSNyTHY0FO5whE7jOTd9UE,3252 +pip/_vendor/rich/layout.py,sha256=ajkSFAtEVv9EFTcFs-w4uZfft7nEXhNzL7ZVdgrT5rI,14004 +pip/_vendor/rich/live.py,sha256=DhzAPEnjTxQuq9_0Y2xh2MUwQcP_aGPkenLfKETslwM,14270 +pip/_vendor/rich/live_render.py,sha256=zJtB471jGziBtEwxc54x12wEQtH4BuQr1SA8v9kU82w,3666 +pip/_vendor/rich/logging.py,sha256=ZgpKMMBY_BuMAI_BYzo-UtXak6t5oH9VK8m9Q2Lm0f4,12458 +pip/_vendor/rich/markup.py,sha256=3euGKP5s41NCQwaSjTnJxus5iZMHjxpIM0W6fCxra38,8451 +pip/_vendor/rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305 +pip/_vendor/rich/padding.py,sha256=KVEI3tOwo9sgK1YNSuH__M1_jUWmLZwRVV_KmOtVzyM,4908 +pip/_vendor/rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828 +pip/_vendor/rich/palette.py,sha256=lInvR1ODDT2f3UZMfL1grq7dY_pDdKHw4bdUgOGaM4Y,3396 +pip/_vendor/rich/panel.py,sha256=fFRHcviXvWhk3V3zx5Zwmsb_RL9KJ3esD-sU0NYEVyw,11235 +pip/_vendor/rich/pretty.py,sha256=gy3S72u4FRg2ytoo7N1ZDWDIvB4unbzd5iUGdgm-8fc,36391 +pip/_vendor/rich/progress.py,sha256=MtmCjTk5zYU_XtRHxRHTAEHG6hF9PeF7EMWbEPleIC0,60357 +pip/_vendor/rich/progress_bar.py,sha256=mZTPpJUwcfcdgQCTTz3kyY-fc79ddLwtx6Ghhxfo064,8162 +pip/_vendor/rich/prompt.py,sha256=l0RhQU-0UVTV9e08xW1BbIj0Jq2IXyChX4lC0lFNzt4,12447 +pip/_vendor/rich/protocol.py,sha256=5hHHDDNHckdk8iWH5zEbi-zuIVSF5hbU2jIo47R7lTE,1391 +pip/_vendor/rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166 +pip/_vendor/rich/repr.py,sha256=5MZJZmONgC6kud-QW-_m1okXwL2aR6u6y-pUcUCJz28,4431 +pip/_vendor/rich/rule.py,sha256=0fNaS_aERa3UMRc3T5WMpN_sumtDxfaor2y3of1ftBk,4602 +pip/_vendor/rich/scope.py,sha256=TMUU8qo17thyqQCPqjDLYpg_UU1k5qVd-WwiJvnJVas,2843 +pip/_vendor/rich/screen.py,sha256=YoeReESUhx74grqb0mSSb9lghhysWmFHYhsbMVQjXO8,1591 +pip/_vendor/rich/segment.py,sha256=otnKeKGEV-WRlQVosfJVeFDcDxAKHpvJ_hLzSu5lumM,24743 +pip/_vendor/rich/spinner.py,sha256=PT5qgXPG3ZpqRj7n3EZQ6NW56mx3ldZqZCU7gEMyZk4,4364 +pip/_vendor/rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424 +pip/_vendor/rich/style.py,sha256=aSoUNbVgfP1PAnduAqgbbl4AMQy668qs2S1FEwr3Oqs,27067 +pip/_vendor/rich/styled.py,sha256=eZNnzGrI4ki_54pgY3Oj0T-x3lxdXTYh4_ryDB24wBU,1258 +pip/_vendor/rich/syntax.py,sha256=qqAnEUZ4K57Po81_5RBxnsuU4KRzSdvDPAhKw8ma_3E,35763 +pip/_vendor/rich/table.py,sha256=yXYUr0YsPpG466N50HCAw2bpb5ZUuuzdc-G66Zk-oTc,40103 +pip/_vendor/rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370 +pip/_vendor/rich/text.py,sha256=AO7JPCz6-gaN1thVLXMBntEmDPVYFgFNG1oM61_sanU,47552 +pip/_vendor/rich/theme.py,sha256=oNyhXhGagtDlbDye3tVu3esWOWk0vNkuxFw-_unlaK0,3771 +pip/_vendor/rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102 +pip/_vendor/rich/traceback.py,sha256=z8UoN7NbTQKW6YDDUVwOh7F8snZf6gYnUWtOrKsLE1w,31797 +pip/_vendor/rich/tree.py,sha256=yWnQ6rAvRGJ3qZGqBrxS2SW2TKBTNrP0SdY8QxOFPuw,9451 +pip/_vendor/tomli/__init__.py,sha256=PhNw_eyLgdn7McJ6nrAN8yIm3dXC75vr1sVGVVwDSpA,314 +pip/_vendor/tomli/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/tomli/__pycache__/_parser.cpython-38.pyc,, +pip/_vendor/tomli/__pycache__/_re.cpython-38.pyc,, +pip/_vendor/tomli/__pycache__/_types.cpython-38.pyc,, +pip/_vendor/tomli/_parser.py,sha256=9w8LG0jB7fwmZZWB0vVXbeejDHcl4ANIJxB2scEnDlA,25591 +pip/_vendor/tomli/_re.py,sha256=sh4sBDRgO94KJZwNIrgdcyV_qQast50YvzOAUGpRDKA,3171 +pip/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254 +pip/_vendor/tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 +pip/_vendor/truststore/__init__.py,sha256=WIDeyzWm7EVX44g354M25vpRXbeY1lsPH6EmUJUcq4o,1264 +pip/_vendor/truststore/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/truststore/__pycache__/_api.cpython-38.pyc,, +pip/_vendor/truststore/__pycache__/_macos.cpython-38.pyc,, +pip/_vendor/truststore/__pycache__/_openssl.cpython-38.pyc,, +pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-38.pyc,, +pip/_vendor/truststore/__pycache__/_windows.cpython-38.pyc,, +pip/_vendor/truststore/_api.py,sha256=GeXRNTlxPZ3kif4kNoh6JY0oE4QRzTGcgXr6l_X_Gk0,10555 +pip/_vendor/truststore/_macos.py,sha256=nZlLkOmszUE0g6ryRwBVGY5COzPyudcsiJtDWarM5LQ,20503 +pip/_vendor/truststore/_openssl.py,sha256=LLUZ7ZGaio-i5dpKKjKCSeSufmn6T8pi9lDcFnvSyq0,2324 +pip/_vendor/truststore/_ssl_constants.py,sha256=NUD4fVKdSD02ri7-db0tnO0VqLP9aHuzmStcW7tAl08,1130 +pip/_vendor/truststore/_windows.py,sha256=rAHyKYD8M7t-bXfG8VgOVa3TpfhVhbt4rZQlO45YuP8,17993 +pip/_vendor/truststore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/typing_extensions.py,sha256=78hFl0HpDY-ylHUVCnWdU5nTHxUP2-S-3wEZk6CQmLk,134499 +pip/_vendor/urllib3/__init__.py,sha256=iXLcYiJySn0GNbWOOZDDApgBL1JgP44EZ8i1760S8Mc,3333 +pip/_vendor/urllib3/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/_collections.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/_version.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/connection.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/connectionpool.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/exceptions.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/fields.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/filepost.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/poolmanager.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/request.cpython-38.pyc,, +pip/_vendor/urllib3/__pycache__/response.cpython-38.pyc,, +pip/_vendor/urllib3/_collections.py,sha256=pyASJJhW7wdOpqJj9QJA8FyGRfr8E8uUUhqUvhF0728,11372 +pip/_vendor/urllib3/_version.py,sha256=t9wGB6ooOTXXgiY66K1m6BZS1CJyXHAU8EoWDTe6Shk,64 +pip/_vendor/urllib3/connection.py,sha256=ttIA909BrbTUzwkqEe_TzZVh4JOOj7g61Ysei2mrwGg,20314 +pip/_vendor/urllib3/connectionpool.py,sha256=e2eiAwNbFNCKxj4bwDKNK-w7HIdSz3OmMxU_TIt-evQ,40408 +pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957 +pip/_vendor/urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-38.pyc,, +pip/_vendor/urllib3/contrib/_securetransport/bindings.py,sha256=4Xk64qIkPBt09A5q-RIFUuDhNc9mXilVapm7WnYnzRw,17632 +pip/_vendor/urllib3/contrib/_securetransport/low_level.py,sha256=B2JBB2_NRP02xK6DCa1Pa9IuxrPwxzDzZbixQkb7U9M,13922 +pip/_vendor/urllib3/contrib/appengine.py,sha256=VR68eAVE137lxTgjBDwCna5UiBZTOKa01Aj_-5BaCz4,11036 +pip/_vendor/urllib3/contrib/ntlmpool.py,sha256=NlfkW7WMdW8ziqudopjHoW299og1BTWi0IeIibquFwk,4528 +pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=hDJh4MhyY_p-oKlFcYcQaVQRDv6GMmBGuW9yjxyeejM,17081 +pip/_vendor/urllib3/contrib/securetransport.py,sha256=Fef1IIUUFHqpevzXiDPbIGkDKchY2FVKeVeLGR1Qq3g,34446 +pip/_vendor/urllib3/contrib/socks.py,sha256=aRi9eWXo9ZEb95XUxef4Z21CFlnnjbEiAo9HOseoMt4,7097 +pip/_vendor/urllib3/exceptions.py,sha256=0Mnno3KHTNfXRfY7638NufOPkUb6mXOm-Lqj-4x2w8A,8217 +pip/_vendor/urllib3/fields.py,sha256=kvLDCg_JmH1lLjUUEY_FLS8UhY7hBvDPuVETbY8mdrM,8579 +pip/_vendor/urllib3/filepost.py,sha256=5b_qqgRHVlL7uLtdAYBzBh-GHmU5AfJVt_2N0XS3PeY,2440 +pip/_vendor/urllib3/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/urllib3/packages/__pycache__/six.cpython-38.pyc,, +pip/_vendor/urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-38.pyc,, +pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-38.pyc,, +pip/_vendor/urllib3/packages/backports/makefile.py,sha256=nbzt3i0agPVP07jqqgjhaYjMmuAi_W5E0EywZivVO8E,1417 +pip/_vendor/urllib3/packages/backports/weakref_finalize.py,sha256=tRCal5OAhNSRyb0DhHp-38AtIlCsRP8BxF3NX-6rqIA,5343 +pip/_vendor/urllib3/packages/six.py,sha256=b9LM0wBXv7E7SrbCjAm4wwN-hrH-iNxv18LgWNMMKPo,34665 +pip/_vendor/urllib3/poolmanager.py,sha256=aWyhXRtNO4JUnCSVVqKTKQd8EXTvUm1VN9pgs2bcONo,19990 +pip/_vendor/urllib3/request.py,sha256=YTWFNr7QIwh7E1W9dde9LM77v2VWTJ5V78XuTTw7D1A,6691 +pip/_vendor/urllib3/response.py,sha256=fmDJAFkG71uFTn-sVSTh2Iw0WmcXQYqkbRjihvwBjU8,30641 +pip/_vendor/urllib3/util/__init__.py,sha256=JEmSmmqqLyaw8P51gUImZh8Gwg9i1zSe-DoqAitn2nc,1155 +pip/_vendor/urllib3/util/__pycache__/__init__.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/connection.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/proxy.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/queue.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/request.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/response.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/retry.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/timeout.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/url.cpython-38.pyc,, +pip/_vendor/urllib3/util/__pycache__/wait.cpython-38.pyc,, +pip/_vendor/urllib3/util/connection.py,sha256=5Lx2B1PW29KxBn2T0xkN1CBgRBa3gGVJBKoQoRogEVk,4901 +pip/_vendor/urllib3/util/proxy.py,sha256=zUvPPCJrp6dOF0N4GAVbOcl6o-4uXKSrGiTkkr5vUS4,1605 +pip/_vendor/urllib3/util/queue.py,sha256=nRgX8_eX-_VkvxoX096QWoz8Ps0QHUAExILCY_7PncM,498 +pip/_vendor/urllib3/util/request.py,sha256=C0OUt2tcU6LRiQJ7YYNP9GvPrSvl7ziIBekQ-5nlBZk,3997 +pip/_vendor/urllib3/util/response.py,sha256=GJpg3Egi9qaJXRwBh5wv-MNuRWan5BIu40oReoxWP28,3510 +pip/_vendor/urllib3/util/retry.py,sha256=6ENvOZ8PBDzh8kgixpql9lIrb2dxH-k7ZmBanJF2Ng4,22050 +pip/_vendor/urllib3/util/ssl_.py,sha256=QDuuTxPSCj1rYtZ4xpD7Ux-r20TD50aHyqKyhQ7Bq4A,17460 +pip/_vendor/urllib3/util/ssl_match_hostname.py,sha256=Ir4cZVEjmAk8gUAIHWSi7wtOO83UCYABY2xFD1Ql_WA,5758 +pip/_vendor/urllib3/util/ssltransport.py,sha256=NA-u5rMTrDFDFC8QzRKUEKMG0561hOD4qBTr3Z4pv6E,6895 +pip/_vendor/urllib3/util/timeout.py,sha256=cwq4dMk87mJHSBktK1miYJ-85G-3T3RmT20v7SFCpno,10168 +pip/_vendor/urllib3/util/url.py,sha256=lCAE7M5myA8EDdW0sJuyyZhVB9K_j38ljWhHAnFaWoE,14296 +pip/_vendor/urllib3/util/wait.py,sha256=fOX0_faozG2P7iVojQoE1mbydweNyTcm-hXEfFrTtLI,5403 +pip/_vendor/vendor.txt,sha256=EW-E3cE5XEAtVFzGInikArOMDxGP0DLUWzXpY4RZfFY,333 +pip/py.typed,sha256=EBVvvPRTn_eIpz5e5QztSCdrMX7Qwd7VP93RSoIlZ2I,286 diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/REQUESTED b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/WHEEL b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/WHEEL new file mode 100644 index 000000000..505164bc0 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.8.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/entry_points.txt b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/entry_points.txt new file mode 100644 index 000000000..25fcf7e2c --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +pip = pip._internal.cli.main:main +pip3 = pip._internal.cli.main:main diff --git a/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/top_level.txt b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/top_level.txt new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip-25.0.1.dist-info/top_level.txt @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.8/site-packages/pip/__init__.py b/venv/lib/python3.8/site-packages/pip/__init__.py new file mode 100644 index 000000000..d628f93ee --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/__init__.py @@ -0,0 +1,13 @@ +from typing import List, Optional + +__version__ = "25.0.1" + + +def main(args: Optional[List[str]] = None) -> int: + """This is an internal API only meant for use by pip's own console scripts. + + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pip._internal.utils.entrypoints import _wrapper + + return _wrapper(args) diff --git a/venv/lib/python3.8/site-packages/pip/__main__.py b/venv/lib/python3.8/site-packages/pip/__main__.py new file mode 100644 index 000000000..599132611 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/__main__.py @@ -0,0 +1,24 @@ +import os +import sys + +# Remove '' and current working directory from the first entry +# of sys.path, if present to avoid using current directory +# in pip commands check, freeze, install, list and show, +# when invoked as python -m pip +if sys.path[0] in ("", os.getcwd()): + sys.path.pop(0) + +# If we are running from a wheel, add the wheel to sys.path +# This allows the usage python pip-*.whl/pip install pip-*.whl +if __package__ == "": + # __file__ is pip-*.whl/pip/__main__.py + # first dirname call strips of '/__main__.py', second strips off '/pip' + # Resulting path is the name of the wheel itself + # Add that to sys.path so we can import pip + path = os.path.dirname(os.path.dirname(__file__)) + sys.path.insert(0, path) + +if __name__ == "__main__": + from pip._internal.cli.main import main as _main + + sys.exit(_main()) diff --git a/venv/lib/python3.8/site-packages/pip/__pip-runner__.py b/venv/lib/python3.8/site-packages/pip/__pip-runner__.py new file mode 100644 index 000000000..c633787fc --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/__pip-runner__.py @@ -0,0 +1,50 @@ +"""Execute exactly this copy of pip, within a different environment. + +This file is named as it is, to ensure that this module can't be imported via +an import statement. +""" + +# /!\ This version compatibility check section must be Python 2 compatible. /!\ + +import sys + +# Copied from pyproject.toml +PYTHON_REQUIRES = (3, 8) + + +def version_str(version): # type: ignore + return ".".join(str(v) for v in version) + + +if sys.version_info[:2] < PYTHON_REQUIRES: + raise SystemExit( + "This version of pip does not support python {} (requires >={}).".format( + version_str(sys.version_info[:2]), version_str(PYTHON_REQUIRES) + ) + ) + +# From here on, we can use Python 3 features, but the syntax must remain +# Python 2 compatible. + +import runpy # noqa: E402 +from importlib.machinery import PathFinder # noqa: E402 +from os.path import dirname # noqa: E402 + +PIP_SOURCES_ROOT = dirname(dirname(__file__)) + + +class PipImportRedirectingFinder: + @classmethod + def find_spec(self, fullname, path=None, target=None): # type: ignore + if fullname != "pip": + return None + + spec = PathFinder.find_spec(fullname, [PIP_SOURCES_ROOT], target) + assert spec, (PIP_SOURCES_ROOT, fullname) + return spec + + +sys.meta_path.insert(0, PipImportRedirectingFinder()) + +assert __name__ == "__main__", "Cannot run __pip-runner__.py as a non-main module" +runpy.run_module("pip", run_name="__main__", alter_sys=True) diff --git a/venv/lib/python3.8/site-packages/pip/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81dd48e1cc6f6248c667922b056d65e402e1beb7 GIT binary patch literal 652 zcmYk4&2H2%5P8tSK!2W`B6ui(Kw!;Z*0Hc-JLL!Z{MW>&e)H%xGV)No|57|8O=0r zSj*?U&E^?rI-BQu2QuJL?6ExtBVByS^hlSVM)OkVZ`pMGlk(U!8;T=?aTve$(KxG` z^K$=wb*H*}oVHY0;ZO#MU9d}{_%9iGNuQJA4KkYRED?O*oL%q>_K{PM{QUE~wLw9j zvclL1fjWq1Z(a#!n_je_Y!oLhhz>z4df|<~g~FX#v2+$)0|J-9_=weHB1m3RsM6Z} zK@}RJG7a7q2q4xmdVD11%EWcIsFtpkzV}K}O=%E2K>6U_!^3J@D5PPgd>GefL3s~> zroBm#RsD~$>LQh{z{b#fM}IMtDJKi*(sv92rM4@Ikt zO&pjg*yvgo(leRzvY$^lXN`W7T2>P{|zo*u5bVV literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/__pycache__/__main__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34f3bf4927c95abaa24aa9e91010a05705d94d70 GIT binary patch literal 480 zcmYjMyKWmX5M1&ey4aG7z%bxXP@y9bH!h68MasZU5Ep_0;v}vtM!Y+M$H-CO!hnB7 zSLPR7`zzR{$}hO`QkH=%z#(@y+!=l!jShh7=~?LO1mJIM{;!OhOIqCnfda)0Oy71k zWypST1*f1m!Zzt)!B@Ha6xnU6pl1v)Q+&nU4FYlb9tS<^*_7{eg#zR~C*=o~kn)~9 zQgcurPKU&??FfgDyGe?DgK_qcQovP!P)J!dwc$`oTg_M1ElL>%O1q}2F-}=%6RBJ`9XcPGctgyI)wYzn@km-z&W$dkk4q^* z$ad}_#b4V00r!!(9lU6g5wbY|=}&PUUJpl$i4A*ifh zky{3oE24JB%uBl}mQb=Rovw+xsEK9K_|*ZaY@ak&CNH}eG8$Ux%L^XacuSZ?J^#~s9(^qTC>f&I<{6jA&& zUwC!_wgAinK!VJbSZf2_Hx>OhmQd%N;C-X1DcW_>zCA$DMa{tLdiK3 z1UPB0Npr_o-DEjyKhEjsH=@ysztYZ0I*=VXk!MQAoo8|+<1|B~JJ0!%>11Pjk|wR} zNvp4=JW{e{HIL4u?u;Oaomd^sNuIPn?-*reD}!f#EP+*JoiGI4(!(T?It<&{I4^G; z%S2vedb5E>qt`BBWb)BSYNOI51S{JcaT@X1Y_{JLgZ=JlsuI{sbIQVj87hO6yn0W< zGRns25UQ5|-{E=kWs&JQ*}Oh{kxIyVYJD@zFpd7qW&g#c|EZSeL#5$;^NSak%{C~n z>~eeZwMvB4Xo^r83noHM_ZPbL6u>+LBndPY37B-CwJ@nz{;t53F60k)RkmH!&z=Nn zGO{W;o+;=>DCW(AJ|+l7jXZ|K6uRxq(KXDlB{y_REjy-D=hnG&r>~8Od`#XnwTiyL|Z)fKquYYBX)D{xb)jHK+sB2{0 z&%ORIj?v}Z%^(u_qUHKnT6hIH|H_D712rE5m|_|+kGiZu8+7vM|9x)1H44n_fm$yN zQC%43l`xz&9-M1ocs}HXA#kfkF*jx)p;Seo^qwJDxQS!RU int: + """This is preserved for old console scripts that may still be referencing + it. + + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pip._internal.utils.entrypoints import _wrapper + + return _wrapper(args) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc76306ffb0dd48c6fe11d9f83df7a8a1f7c9ee6 GIT binary patch literal 714 zcmZuvv5wR*5VezRHn;3aNYGq`+m+b3I3x~qLI@Dm=_r6^n5(Of{|xDV|&K$js5oUaK=c!|B#wTjQtG3O-U$tO6s1GQA}~e zTE60Kw2F9$BNMmDD&Y*0FO#o~)}{~vhxCXYF_^04Q=}#;{Ww`=Dt^rt*)OWY7SWKL zY3GORmG!#sO0(cYQkcFz4_hVHZb%XQ7Q2dvgli#$Ea-DmcbAOfgn$se=bT;fEB1lY z9r5MoH;s0JhDC6IJ5XZNqv(wiRo}VZfN&LR>z(jT=|x-ah4b1Nu?B�fes7T|E;d z+UK*;c}XQorSxCKLV+)}arcD-5RLcNJ(jZ8zS*wxs&A#;+frI>rFL!$PCk6_=p-Kt zVc2fxEwlJO3evA#<3I pHR<7N${1qjafpfTSZ(BY!-V^W?6mFG*1(g{Lzj}d9nF(0{sYxz!I%I5 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/build_env.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/build_env.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75b4c35e8c7bff8060fa9fdd34f38287ae669f67 GIT binary patch literal 9840 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 MI5}1LE$xT@4Z(}@yZ`_I literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/cache.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/cache.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f36f2b2ae69b2944a1dd6da8e5ae13e3b94a021 GIT binary patch literal 9031 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 SY*^+=XX^IM?U~Bd$^Qdpq}AyF literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/configuration.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/configuration.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02bd37737937b23a5ad5ea3fe7aa77c8a44b549e GIT binary patch literal 11724 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/exceptions.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/exceptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d6ffefbcf74b7fcf38a5aa62a142a413783a743 GIT binary patch literal 28360 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/main.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/main.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d94eced762ef73eb13e80747ea47964f37cb364 GIT binary patch literal 638 zcmYjPv2GMG5Vd#jmg7Vu3Yt{7J&9WvK_Vc8kcg^)cIQ%vUwL!iM!b2;PBnuSR(jFN zh3tw&A%(4=014%xI216l<=4WF?D%%P7~A5bm_7K-Ibv1}qfu=|+BgHEs5a1T9S_M?WTh)Y~ zf(Jznx|m3%)+E)LoucHtS4&U`D?sQQ*RQ9F&82#}C-1q0vDW=lsw^bqJUvqcpxTro zz0!K^QoC8!P3UyoMWdsO+7WGl^o!>&kL!IQ%stKIFuAxw6C)ru{gfl?3)d$=9z(rh zHB>{Na2Erg5>`2^N1QnkBJ})P{Z@kLD`` zSUQ*|G|eTT-a^0C-Yx%!9M#8~T!MMzYqMs>S^S+O-5J-{aW{-I$ucN|s2&xV|C5bZ{sl^wt55&{ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/pyproject.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/pyproject.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ebcdddda853d846cbad8442b5e499f760cc50d5 GIT binary patch literal 3795 zcmb7H&2JmW6`xt|lFQ|a`m`-McDr@c$^V=GSUj=C{-;!LJqDJ*VYR zZB;4Zr9F??`;OQ!617$>@nPn~es88#Gcx74-kWXBnt3Ij>&>_3 zdkd|F-ePOf(A;>bccyg)bXE9SR()nMkJTRAt#f>tFEbz3Gpr8l74EQEHpk}K0$bd% zkF3^tUgc+32=c+F%FnM5w)D(lXV_Wbe}~a~q;c*gct24 zf})Dw6G|RJ4uBxtY*^Zhz-(|f>2O_=N&v}fUe$J_c<*mSEP;Y=)%tGQ)D~&O_R0ky|u33d^_T6N`#$#F4hltaBPxz;qxOEO)L7!}5k-?>QB-=4Wx1Dy|U~%(i zoOZ%k-fB(+8YM9Y?Ak^)U4lq9sHg+Q_)`pLkYV->U1xzP5kW%53XFipNb<{%`Sl}c@Aq1x5< zUK%A}>3NWR)6&IWxX;@Y;Wi2&-`Lvk!CWtxq`Y@1A$YgB3ZqzRca(HeSGLiQglVU< z%RBqpAFYk9hNHc{gdKQx?I%CeCuX+MOzlcY(+u`E`r3Hj3ir|7@q>8a0Wn>j-lBtHjzp99d&epNiqiF~X|tc+S&Wk`|-xhq~UH+T2T z;>C#TE8zMHxT?q1+-2?x>@nfLYc zv`{jyeP$tYfK%;HwFl%6B(WZlr}W`g?gEM~py->~(hhlU4QF=9u%7!P558tIL*vD; zt`_rZ?&tM<=E#OrJ`;WjC2AuLS)g`#FiFfNrc2O`V&3$;0G_Q-K(fFAmp9BBX(x#m ztOdUcrBMMy5n!@%NM9CgeY6!oCI(OuI^5LOUj&LHMYY*9K3&n}P5g=(0YI8g=TRWiRGp+> z!)!meAF!0mAW2maW*H9!N^K+DTjpBDxMRO@BS?6J!3b8z$+QYmu$<(vIeV)?H|-~^ zIdJ-_yLKH~50@tpPylA&8wl2YVRTTWXLNdVY1M>obcoS3>Bf_ot% z-p?SdKmK;6P(4Csu>F6l9v@H%YKusXbm58k;}C3lBcrruc2{W%x1956m-BeAa6-EE z^PgUAUTAhRYo1k;KCh+NSN>h*iN81 z&~*T4vI&w`fFGNG}r} z%Ic(z6+Z5TosGbYTFl^3#o_xfG-k!SIR6n2KgMAd20a7S^rZ99Ra_dJHSGdWSQzuwErZ|ZopxPhea0v(OFQ@vkMRf9HFQXZm<)2|7%Rcojms-?)Z96ut z(`8uOu1kHOS+8xo3^SbDR{0gIowDy*HuYaymj9hq@ogL4c%DtISLJHG>`|}eQV(U3 zQbx@_T`+6+m0hyFTXsC4*wpiFTDHXdU_;}Au21l8BHZE{kWCPwfj*SF)=8lzbr5qY zu$R8Y(v+~QXyQVT$-XZi=+fq%_(H1 z37}p!jN?|i$v^FI^WR6BY%0Qy(bpGGGolOfM6*c=ZEwS=D)M9=WCV7XCPfSeQ&BVV yGp)3HvzM}d%x_^6Eb&;l6hA&)vH)qzhtHDZm*(s_8}KIHYr9;AejYxw{OZ3oY&B;9 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd8a4421058a1c8109b2121d16867ac61e0a424d GIT binary patch literal 6828 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 Union[Tuple[str], Tuple[str, str]]: + return (a, b) if a != b else (a,) + + +class _Prefix: + def __init__(self, path: str) -> None: + self.path = path + self.setup = False + scheme = get_scheme("", prefix=path) + self.bin_dir = scheme.scripts + self.lib_dirs = _dedup(scheme.purelib, scheme.platlib) + + +def get_runnable_pip() -> str: + """Get a file to pass to a Python executable, to run the currently-running pip. + + This is used to run a pip subprocess, for installing requirements into the build + environment. + """ + source = pathlib.Path(pip_location).resolve().parent + + if not source.is_dir(): + # This would happen if someone is using pip from inside a zip file. In that + # case, we can use that directly. + return str(source) + + return os.fsdecode(source / "__pip-runner__.py") + + +def _get_system_sitepackages() -> Set[str]: + """Get system site packages + + Usually from site.getsitepackages, + but fallback on `get_purelib()/get_platlib()` if unavailable + (e.g. in a virtualenv created by virtualenv<20) + + Returns normalized set of strings. + """ + if hasattr(site, "getsitepackages"): + system_sites = site.getsitepackages() + else: + # virtualenv < 20 overwrites site.py without getsitepackages + # fallback on get_purelib/get_platlib. + # this is known to miss things, but shouldn't in the cases + # where getsitepackages() has been removed (inside a virtualenv) + system_sites = [get_purelib(), get_platlib()] + return {os.path.normcase(path) for path in system_sites} + + +class BuildEnvironment: + """Creates and manages an isolated environment to install build deps""" + + def __init__(self) -> None: + temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True) + + self._prefixes = OrderedDict( + (name, _Prefix(os.path.join(temp_dir.path, name))) + for name in ("normal", "overlay") + ) + + self._bin_dirs: List[str] = [] + self._lib_dirs: List[str] = [] + for prefix in reversed(list(self._prefixes.values())): + self._bin_dirs.append(prefix.bin_dir) + self._lib_dirs.extend(prefix.lib_dirs) + + # Customize site to: + # - ensure .pth files are honored + # - prevent access to system site packages + system_sites = _get_system_sitepackages() + + self._site_dir = os.path.join(temp_dir.path, "site") + if not os.path.exists(self._site_dir): + os.mkdir(self._site_dir) + with open( + os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8" + ) as fp: + fp.write( + textwrap.dedent( + """ + import os, site, sys + + # First, drop system-sites related paths. + original_sys_path = sys.path[:] + known_paths = set() + for path in {system_sites!r}: + site.addsitedir(path, known_paths=known_paths) + system_paths = set( + os.path.normcase(path) + for path in sys.path[len(original_sys_path):] + ) + original_sys_path = [ + path for path in original_sys_path + if os.path.normcase(path) not in system_paths + ] + sys.path = original_sys_path + + # Second, add lib directories. + # ensuring .pth file are processed. + for path in {lib_dirs!r}: + assert not path in sys.path + site.addsitedir(path) + """ + ).format(system_sites=system_sites, lib_dirs=self._lib_dirs) + ) + + def __enter__(self) -> None: + self._save_env = { + name: os.environ.get(name, None) + for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH") + } + + path = self._bin_dirs[:] + old_path = self._save_env["PATH"] + if old_path: + path.extend(old_path.split(os.pathsep)) + + pythonpath = [self._site_dir] + + os.environ.update( + { + "PATH": os.pathsep.join(path), + "PYTHONNOUSERSITE": "1", + "PYTHONPATH": os.pathsep.join(pythonpath), + } + ) + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + for varname, old_value in self._save_env.items(): + if old_value is None: + os.environ.pop(varname, None) + else: + os.environ[varname] = old_value + + def check_requirements( + self, reqs: Iterable[str] + ) -> Tuple[Set[Tuple[str, str]], Set[str]]: + """Return 2 sets: + - conflicting requirements: set of (installed, wanted) reqs tuples + - missing requirements: set of reqs + """ + missing = set() + conflicting = set() + if reqs: + env = ( + get_environment(self._lib_dirs) + if hasattr(self, "_lib_dirs") + else get_default_environment() + ) + for req_str in reqs: + req = get_requirement(req_str) + # We're explicitly evaluating with an empty extra value, since build + # environments are not provided any mechanism to select specific extras. + if req.marker is not None and not req.marker.evaluate({"extra": ""}): + continue + dist = env.get_distribution(req.name) + if not dist: + missing.add(req_str) + continue + if isinstance(dist.version, Version): + installed_req_str = f"{req.name}=={dist.version}" + else: + installed_req_str = f"{req.name}==={dist.version}" + if not req.specifier.contains(dist.version, prereleases=True): + conflicting.add((installed_req_str, req_str)) + # FIXME: Consider direct URL? + return conflicting, missing + + def install_requirements( + self, + finder: "PackageFinder", + requirements: Iterable[str], + prefix_as_string: str, + *, + kind: str, + ) -> None: + prefix = self._prefixes[prefix_as_string] + assert not prefix.setup + prefix.setup = True + if not requirements: + return + self._install_requirements( + get_runnable_pip(), + finder, + requirements, + prefix, + kind=kind, + ) + + @staticmethod + def _install_requirements( + pip_runnable: str, + finder: "PackageFinder", + requirements: Iterable[str], + prefix: _Prefix, + *, + kind: str, + ) -> None: + args: List[str] = [ + sys.executable, + pip_runnable, + "install", + "--ignore-installed", + "--no-user", + "--prefix", + prefix.path, + "--no-warn-script-location", + "--disable-pip-version-check", + # The prefix specified two lines above, thus + # target from config file or env var should be ignored + "--target", + "", + ] + if logger.getEffectiveLevel() <= logging.DEBUG: + args.append("-vv") + elif logger.getEffectiveLevel() <= VERBOSE: + args.append("-v") + for format_control in ("no_binary", "only_binary"): + formats = getattr(finder.format_control, format_control) + args.extend( + ( + "--" + format_control.replace("_", "-"), + ",".join(sorted(formats or {":none:"})), + ) + ) + + index_urls = finder.index_urls + if index_urls: + args.extend(["-i", index_urls[0]]) + for extra_index in index_urls[1:]: + args.extend(["--extra-index-url", extra_index]) + else: + args.append("--no-index") + for link in finder.find_links: + args.extend(["--find-links", link]) + + if finder.proxy: + args.extend(["--proxy", finder.proxy]) + for host in finder.trusted_hosts: + args.extend(["--trusted-host", host]) + if finder.custom_cert: + args.extend(["--cert", finder.custom_cert]) + if finder.client_cert: + args.extend(["--client-cert", finder.client_cert]) + if finder.allow_all_prereleases: + args.append("--pre") + if finder.prefer_binary: + args.append("--prefer-binary") + args.append("--") + args.extend(requirements) + with open_spinner(f"Installing {kind}") as spinner: + call_subprocess( + args, + command_desc=f"pip subprocess to install {kind}", + spinner=spinner, + ) + + +class NoOpBuildEnvironment(BuildEnvironment): + """A no-op drop-in replacement for BuildEnvironment""" + + def __init__(self) -> None: + pass + + def __enter__(self) -> None: + pass + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + pass + + def cleanup(self) -> None: + pass + + def install_requirements( + self, + finder: "PackageFinder", + requirements: Iterable[str], + prefix_as_string: str, + *, + kind: str, + ) -> None: + raise NotImplementedError() diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cache.py b/venv/lib/python3.8/site-packages/pip/_internal/cache.py new file mode 100644 index 000000000..6b4512672 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cache.py @@ -0,0 +1,290 @@ +"""Cache Management +""" + +import hashlib +import json +import logging +import os +from pathlib import Path +from typing import Any, Dict, List, Optional + +from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.exceptions import InvalidWheelFilename +from pip._internal.models.direct_url import DirectUrl +from pip._internal.models.link import Link +from pip._internal.models.wheel import Wheel +from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds +from pip._internal.utils.urls import path_to_url + +logger = logging.getLogger(__name__) + +ORIGIN_JSON_NAME = "origin.json" + + +def _hash_dict(d: Dict[str, str]) -> str: + """Return a stable sha224 of a dictionary.""" + s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + return hashlib.sha224(s.encode("ascii")).hexdigest() + + +class Cache: + """An abstract class - provides cache directories for data from links + + :param cache_dir: The root of the cache. + """ + + def __init__(self, cache_dir: str) -> None: + super().__init__() + assert not cache_dir or os.path.isabs(cache_dir) + self.cache_dir = cache_dir or None + + def _get_cache_path_parts(self, link: Link) -> List[str]: + """Get parts of part that must be os.path.joined with cache_dir""" + + # We want to generate an url to use as our cache key, we don't want to + # just reuse the URL because it might have other items in the fragment + # and we don't care about those. + key_parts = {"url": link.url_without_fragment} + if link.hash_name is not None and link.hash is not None: + key_parts[link.hash_name] = link.hash + if link.subdirectory_fragment: + key_parts["subdirectory"] = link.subdirectory_fragment + + # Include interpreter name, major and minor version in cache key + # to cope with ill-behaved sdists that build a different wheel + # depending on the python version their setup.py is being run on, + # and don't encode the difference in compatibility tags. + # https://github.com/pypa/pip/issues/7296 + key_parts["interpreter_name"] = interpreter_name() + key_parts["interpreter_version"] = interpreter_version() + + # Encode our key url with sha224, we'll use this because it has similar + # security properties to sha256, but with a shorter total output (and + # thus less secure). However the differences don't make a lot of + # difference for our use case here. + hashed = _hash_dict(key_parts) + + # We want to nest the directories some to prevent having a ton of top + # level directories where we might run out of sub directories on some + # FS. + parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]] + + return parts + + def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]: + can_not_cache = not self.cache_dir or not canonical_package_name or not link + if can_not_cache: + return [] + + path = self.get_path_for_link(link) + if os.path.isdir(path): + return [(candidate, path) for candidate in os.listdir(path)] + return [] + + def get_path_for_link(self, link: Link) -> str: + """Return a directory to store cached items in for link.""" + raise NotImplementedError() + + def get( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Link: + """Returns a link to a cached item if it exists, otherwise returns the + passed link. + """ + raise NotImplementedError() + + +class SimpleWheelCache(Cache): + """A cache of wheels for future installs.""" + + def __init__(self, cache_dir: str) -> None: + super().__init__(cache_dir) + + def get_path_for_link(self, link: Link) -> str: + """Return a directory to store cached wheels for link + + Because there are M wheels for any one sdist, we provide a directory + to cache them in, and then consult that directory when looking up + cache hits. + + We only insert things into the cache if they have plausible version + numbers, so that we don't contaminate the cache with things that were + not unique. E.g. ./package might have dozens of installs done for it + and build a version of 0.0...and if we built and cached a wheel, we'd + end up using the same wheel even if the source has been edited. + + :param link: The link of the sdist for which this will cache wheels. + """ + parts = self._get_cache_path_parts(link) + assert self.cache_dir + # Store wheels within the root cache_dir + return os.path.join(self.cache_dir, "wheels", *parts) + + def get( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Link: + candidates = [] + + if not package_name: + return link + + canonical_package_name = canonicalize_name(package_name) + for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name): + try: + wheel = Wheel(wheel_name) + except InvalidWheelFilename: + continue + if canonicalize_name(wheel.name) != canonical_package_name: + logger.debug( + "Ignoring cached wheel %s for %s as it " + "does not match the expected distribution name %s.", + wheel_name, + link, + package_name, + ) + continue + if not wheel.supported(supported_tags): + # Built for a different python/arch/etc + continue + candidates.append( + ( + wheel.support_index_min(supported_tags), + wheel_name, + wheel_dir, + ) + ) + + if not candidates: + return link + + _, wheel_name, wheel_dir = min(candidates) + return Link(path_to_url(os.path.join(wheel_dir, wheel_name))) + + +class EphemWheelCache(SimpleWheelCache): + """A SimpleWheelCache that creates it's own temporary cache directory""" + + def __init__(self) -> None: + self._temp_dir = TempDirectory( + kind=tempdir_kinds.EPHEM_WHEEL_CACHE, + globally_managed=True, + ) + + super().__init__(self._temp_dir.path) + + +class CacheEntry: + def __init__( + self, + link: Link, + persistent: bool, + ): + self.link = link + self.persistent = persistent + self.origin: Optional[DirectUrl] = None + origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME + if origin_direct_url_path.exists(): + try: + self.origin = DirectUrl.from_json( + origin_direct_url_path.read_text(encoding="utf-8") + ) + except Exception as e: + logger.warning( + "Ignoring invalid cache entry origin file %s for %s (%s)", + origin_direct_url_path, + link.filename, + e, + ) + + +class WheelCache(Cache): + """Wraps EphemWheelCache and SimpleWheelCache into a single Cache + + This Cache allows for gracefully degradation, using the ephem wheel cache + when a certain link is not found in the simple wheel cache first. + """ + + def __init__(self, cache_dir: str) -> None: + super().__init__(cache_dir) + self._wheel_cache = SimpleWheelCache(cache_dir) + self._ephem_cache = EphemWheelCache() + + def get_path_for_link(self, link: Link) -> str: + return self._wheel_cache.get_path_for_link(link) + + def get_ephem_path_for_link(self, link: Link) -> str: + return self._ephem_cache.get_path_for_link(link) + + def get( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Link: + cache_entry = self.get_cache_entry(link, package_name, supported_tags) + if cache_entry is None: + return link + return cache_entry.link + + def get_cache_entry( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Optional[CacheEntry]: + """Returns a CacheEntry with a link to a cached item if it exists or + None. The cache entry indicates if the item was found in the persistent + or ephemeral cache. + """ + retval = self._wheel_cache.get( + link=link, + package_name=package_name, + supported_tags=supported_tags, + ) + if retval is not link: + return CacheEntry(retval, persistent=True) + + retval = self._ephem_cache.get( + link=link, + package_name=package_name, + supported_tags=supported_tags, + ) + if retval is not link: + return CacheEntry(retval, persistent=False) + + return None + + @staticmethod + def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None: + origin_path = Path(cache_dir) / ORIGIN_JSON_NAME + if origin_path.exists(): + try: + origin = DirectUrl.from_json(origin_path.read_text(encoding="utf-8")) + except Exception as e: + logger.warning( + "Could not read origin file %s in cache entry (%s). " + "Will attempt to overwrite it.", + origin_path, + e, + ) + else: + # TODO: use DirectUrl.equivalent when + # https://github.com/pypa/pip/pull/10564 is merged. + if origin.url != download_info.url: + logger.warning( + "Origin URL %s in cache entry %s does not match download URL " + "%s. This is likely a pip bug or a cache corruption issue. " + "Will overwrite it with the new value.", + origin.url, + cache_dir, + download_info.url, + ) + origin_path.write_text(download_info.to_json(), encoding="utf-8") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/__init__.py new file mode 100644 index 000000000..e589bb917 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/__init__.py @@ -0,0 +1,4 @@ +"""Subpackage containing all of pip's command line interface related code +""" + +# This file intentionally does not import submodules diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..057be609951e3a47558aa7228877fcca949a0936 GIT binary patch literal 293 zcmYjMyG{c!5cH)Xij;qFhf-X&NJD`T1riOSiD+mn-+3-s#b;YSCn%};8GeD9uSm-$ z&{468QZUl&YWAU>eVI-tO7Qtb{4V+XB>tlkaW5Rk5}bNfu3DJbV(*=C@$FY6fUw5YobG>p1r@qpw#E2h84CK>z>% literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e07c44fb67a66380e0f31a3bcac79dadb94b6f9 GIT binary patch literal 5454 zcmb7I&u<&Y6`t8$F3A-|NwjQBj*~9ZG!0Wr3X=W^T*rtL8;wy{HY_J;gt+92JCash z?ozWW*%FpO0j02f2;c@m5d=9%Ko9k;=c0!m@(=XV!<-8Alz+iM;(l+IlqksoS`s^) zoq02F-kbNm`G)V0jyepVzkNLy{PP@R|Dl)RPXRA)<4OLC!e@)j=e`m0MXrC1MMM9Z ziza@}(CQQx3!G%E(C!o$i=EP9sZ(Aocbr8>w->^ZPGzy8>vlNWsV-JKV~b<@y%>&n zCKe}9H~i99+%NmiC)Q$3l*OrOCMHEm6!GiOn?LfEQ|;e=8vQG zjBhNk`o#7-H{z9`<)*7%>UQIMLME=ak;bjK(+x!$#E~0BN$Q26@ZF^?x7q9lUH7oJ z*_?I0f#qzz&Q+nc>IG5VQs$LtOO>vtLV8P~P}YqgNmc1)m*(?ANY+|Xc&TV~Am8YE zG7++Fs8P(@@ghHI_(3aG)xJ=_6~oT1h_vC0Wp5))8zR~aWE^!wl%mU)BHfTti`Fvu zoI<69$HbF3D1Dx?HQp}DDp^nUJ;OKmxqP`_xAuAR>Y&dzr`evl&-{XK{K1%J4|!^l zoNrCD9o}QxRz8}jS4yyy6Zea_h~+Z*vq z`@F*9f1$VBwtIGFKjQL2T7i}#`bYQKL*oH|U@RNHMKhht6`*3_Zky+4H z$tr8LwNsFM{h_&Qu1%(=*Uq#}%#vm119O@CVIfGurRWwZ8Nn^<^zV7Q^PIkH0@8Wf5+^NW~1=vd0Klmx?}XJ{v^$wRrB!~ z=DB;hH9wO9KVHWh7ou27GUlS>G#jJ4c$8KPhp+ouzp-yZ>+n~m;SV#ldqlA zvrc3ad(UNj?L6&V`uyGt*~C8Y)dqWrdH#{rp!cOCzA@w%nQ`b5Xo1G{FCw>ZQSHE* zeEV>yIsH(quL znl8ZTCL2qA1iG{AD~GC$4{zSSy3nt`OE2GW zpiePnt%|VwgwFuyR=Q;`J=IJA*mU148!K3sM_+PI0{-`*oeH2P!2MgOp|*-x{6+7_BCbsNKL5Q zt$VTb6J><0$LvXOZZ+E6s($QeWha**E8sAQdxRbbBiTLxb*I10EL=nQ*QZ26IQ#?QC~TIhR7L zijwT*pOY6ThuMa~E4=pY_HcR@vzNjc)+Luw84S6DZ0P9bMjVA(s@V6kvbtWndcxNh zCfB|`gNpWbm3+O+*4VO|Ock(w*5kaTp=y(P^+F( zYh}l@(iw1Is*4wG>eR^I8ntu5m6@CiTn{{lnH<<=X&Ie``4t5r7Tjb zMaD=1voe#dL|Ni~km|ja#4%MNB>tt&BVS0Qh(7BA7s9AuX@kg9q6%F}lb@oj(V!Fo zmL@=<($z$kX%;)DQ)+C;gAMREIYGT8v}yr0y72z3Teq(*EHvJ`cH@?E8etr*=$KX` z4J2rp<}XvkxoG}@HY}r&Zgqtk?Q2+(@kTdMw%6^7$d_}_BPj^f=(3DE4KgREfonKw z>Zd>xDc8!&Brm7zQ@=zvNdp@)-IrUTw z=R3QoYuqs>pBko(Q58s(j7na6S}C04HKT?@gTD5+7UUiDVMfD6S+&M#1Rm7+8MA_! ztHyaQKf;>oR&K_1)a8d%jG$-{bO#>-6$SOXc#=P%Kmy@y859at-rJWBI&J{y&E__ekD@eaD+=?OWcO`cbM+H(rhjVp=dTQ z;DX}^GOv(Sv+05v&Bu|bJC2LbMWDy)xH(<8=q8{Qu9xW9hb|;u=x%y4pn$`Shs3Da zJTb}tGlwQrN9Uk`yy)J6ao(mEAWESzk6SD%c3Z2W)y{S17o>|PjMq;2-#kDLkh9or zN!fe;37^wRUg!N2d6&W^p@&W{P&iF1eAc!Nmu7! zoqi8;uqt0#0qE{`<>gOlUOLDuu#4@Vd|!$hqDO;~^-`X3DI3bSsknjy2|1N{?k;vj z3Rg;HdeIh8tpRDE3<&LX0M=SOPQHrbkEL5_S+0Sy6A` z2fEGCZFwEnOkmc?d-e9(UJCSGUj_z_2xO7aqII&zQRyIOU zx+Ik7Ag22WVnnpkSOy|-HJ=Zn6_+SIX~`K;v-wLm2H@qrAORe}g7Zm-hU)3Qclhj4 zu;;VpFr$-*khLTR8ASL;Kz;Dfi9RKtjkhCkJIpivIuC9fl1p&Qy9 z+K*c{e*%t#mNzJ>UP1vlp2r^&`VWu{H>y2TlOYh{J~)v>>wF#u!RwIDAzF%J*=c$B ztA+rrNfJnCi8hBWS3_LsF8cqBM{^}YXpL>Z{0zeK40y_qY3^|p^^rr}ePrV5MZN;l zXthKF0(HA_^Vf0-!m3C?11HG$=*1emUiLoFq;74g3he=MFY~*7^w50SYY11T~wUTJ3dLFmO*@N5TEN+jpL;s-V zEc47Unf_mDns??5@t6eVJKw{_m`JET|K;zf_A@H3QbE=ZCA`CgT)NbW{f$su2Fi0^ ZSg@VG6+Q(n0>7Bm$i|@z=JH)->c1DySlR#p literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c55273d243df918f9a5fd11510fd2519cd10b13 GIT binary patch literal 6645 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 F{}+541ONa4 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45a815a4ab97fb8a53d4d6f929ab4256c9eaff28 GIT binary patch literal 23734 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%0D!Oh1dXAoIw3{LxzFB|S5y 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e2ed76725fac20de9742631867305e8888b6f33 GIT binary patch literal 1309 zcmZuw&2AJ&5bo~z*_~n67;s3Cd`OOSu;+jxA%qa%074=wB{(FSOKUvch6(dWb$ey6 ztQ=xXoDeVI0~~pUobwcO%_*;tQ>uEr(OPj!{Y_7Gchy&4RsS@d#tg?Ff2L*VG4>bz ztPUT49^f>O5EN6qVl~frEi%FBUsPW0XMP=IK^TF&WTU4{g=%!l)aXQH zV>N!klIU;r!xGU)j}J@x!sf-BeywO5s}F6RH~A|KiPx{~43_T)OCLSejfUK|(2sVH zI{h+-UdY|ug!fU=&T}a9#unsxbr5$N<~f398CNV5iYsx(-?4p8JWnT?r+po$;1=d` z#vNb(glC~1>G3V5k@cBSk%~|KEPBU$mc^LGmHx&~TO(h!Bw8LmYi39%1Z@wXDY%O~ z0Ida1)R&n}1j|m?NB)VSg8Z0CVZ9TEUtclwEoM)WpbyMJr=cH9SvIAWvR}uAtaF5O zwSZjq(+jrGy!VZ+_VF7@SyZ_(QhsLt{Fc6MYn|$aepBiy{Z7wy)poURZ2COkGii5Z zU$@OxcYA9ewB9TAmIbVghIFo*d0Lfw=SOaD-$_kr^;U=5SS-9+b}3S`8k)RHi>geE zL764b?d}LBNOervhX{NV{9_4D${4%&ICGypxof{M8u%U;<*gSm-7 zsT!9mrTEc1=ASrr3ZEsuO9H;YLymbr#hPkZYM%&Lr>!puF6IMYp^c_-&xFTf@y%uQ zFZ#Pe9ogiy#*U1otXp+Z(Rw1~+k?Csc3ioEx*Y-nN&zSej0h+w_U+J~g{~?oSGZlH zBP2LbwdsPP&KdLTf#4glv(A#JkI!|)-gGa)#p^?R)RoPvA({(L9XG@!e=C8U9ImqC SdqW=g>7lw7UB_xpyng|rmpNeo literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea8a171d78034aa9e19f3ee42cf504a4bfc7d91a GIT binary patch literal 4971 zcmZ`+&2JmW72hxJk}Fc8CE1pp#N8%s9HtSO#K=d{piLkd-A1!K+_iey;)M0Y;_kiJ3I5l+BMzwlIh+|d&V@lCr02cC-vTi_60pw zNoIRwYmS72b85<=0 zwLtLC!twO4fhm^bkwE5gk^}=4oc3WAnxR?!c7Ui4;neZ1E2mL&w-$qc(l5&jfM%ZGI$35&=uNtSD!M(V@ z%B7666vt%2hKPB}WYd2P1u#|`rgDEBB?B%SwyLdtdhb@a^z&Owzr3?@TRF>EU6t?l zb1+8LH-X{wU-#T}K$@2Xq$~Q9613xW1o{h7{w6f*C zRMqju!Z_W?l$&Imn_Q?8P{>40-O=iFONdM;Z#NPt_MoQvalaYvK%Fun>NtqL zST%R={@t~^OLv#UwdK`tY5C5rl{IxS$sz_?5pV2=N!E!-r=)PfrAoV9E_lNh7ikWf zNZFly?^{E>fcC#et!~!iEk5MiF;7}I`3_IAevhYlYdKn%t-hA-V*keChTwcX=8HKj zdz*_Ewzkts;`K4fjpn;88RvYlkKt&OgEj8AaBA2!^t;tb;+BSiiD5VU`|4WQ5gdjV zJ`r~ak$^-i`}5eL3{V-SkJqs*vuaMGRYt2mZc&f;9%#;}Ix*4t>5+X@+yD$41i^q` zi`kFNFO3aAFDpH)wjEyLF86quSFRXuPQc4Py(2>Bi~smLK5xpg>J%9Qp$2&jh|s(3CKT zG(KD(#0d+5c%7r)K{NM&v9i!*-zdRnvjEb!`n_I#=jt`Nb4i&j_4^PeRuM ze;o}#Fv5@RGDHhaL9s^WFL;wI=XTex~ znkx-Qyo8xm4G6-;#E{kWSv6MEA0T9ax64uudkstc09;yJPs?SFjx}CyqNaC1EXk7@ zmT%5F)8_E{c@jB?6Z_M5;=w(_Sxe=SM~C|_^i!)f&5MWT_fWLB{)vW;-i)4k;J{Cr zTh>8oR1z=e_Lg&m*0CvH&fPB%*J*+gEaecVDp4&zY+O+< zXg4?U63t0)IMyev!>Xc}2w5zYl}Y9Fqr9t16cb4CHm&g-<9&f}00CUM0e>zCeR031dW~d`izrWO^ZeO7TAZzDMhNlhbQ( zI6(;U76w;QByk#U)pE^w=zrNF$2bp%=$K{G`p&JmX4$6VoAb77Ii~a9vP1t>)3=5% zo@Xg%&9h;xtTblGKcrEQ!$0_8ScK+iSHtkLL6nR~yf9>02R?#41IZA%K#wCLV}jsD z5Z(x~D#SXu&~g{#K$ML;*C$f7v7_nmB$+rHedr$&uXZ~DT5qDrtEkGR(LMlTg@*)-*eWm^=1j$qw~XlaLO7ghkrc!}{r$5_tZ610ML{GUV3pIGg541F(_nWwJb#fd!9Mo6RZIK^e;t%F z4;|=z6df>~j&``C;~h)KI!+tO9=S(#;SRaD+aeqE#{tUln<6$@$4yc8wjLq8);D@O zLP3H>SskTvgZxz4yUIxHnR`UZcGMiDn0T5>u!;jVzr@+xXuT%+R---ZJN!ZJGt_M+cC zw1BQQWgma2BcAYZ>-x?=G2SvhGVU7>D35s!nNJy+&*$SjrgG#noyX*rttko+S>-F} z$)`qYJutLb{o&{O{1oHeEKd8(p@MiLNV7a(I;-IfnJ|&lqs5j%y#&yLQe?KR2qe4dp#qbyjE|=k`i#No4Hl+v z_khZTwOXZB<nKdjQRiiDo`xwR8k^!IP7@UK6qzDfm-rb~6i2EGVyR`FyB5dS_d{7%fw_c8 t`b_5?UzOi0yvI)|N74A?l*{l{WyG`dRu%VB#jW6L`S$Fk`p2{G{{UXXJGcM< literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/main.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/main.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3746d787b962d46b48cda1629c11da90358b382f GIT binary patch literal 1519 zcmZV;O>Y}TbY}L$YsYq6CrukfD+(`h02e)%=b@D@V9 z8|M1w0`nRCasz;(2vJN4#}T%eM8skzax8Wu*J3a706W~z8&M+wzSVXc{l1>y1}>eUevR6z;ERJsE-jNn;o`$fuozW%>ve7d;4e~v0JqHBcUz2 z^{p4(rsPXB>?{XQMV2RGF-WS4XKA9cvKX*JiA7arg&Mcsz&+5xkZ5<3Nu`4)l~GCf z5bJg_S7ln}6=#Y8->DKISqzdqDJTfKsbGn^E(xAy)ng$_0lQn2Rf5$=8vluyO6?1# z=AuZkIrj4N0h_EpgkSyvUxC2yo*S zz-3PbEA$F~L)Lgrj!-dik!sFb;(LtNpuHS}x7KEhdMoD#^b@(jU}+_)ecGwHO_yRr z9ia~)g0=I~d4|451$l;+eYL&msqNDn)W0A`qxb;)Z=d$zgx}x<*G90j*`p>m@MZaWpe0f zKS@ntbN~ru(^4$7pORdf;6Xbk>sqVZ1XwnMXE8=jmsAP8Hpk2{wc$g zGG`%MvRTG>_=ue`URF7bNjOQ4WmqlLNm-1l2czeLvEz)5lt|JU6X6*v&O)9YU#dJ9 ze-ui%)2M>&WXixatHKxtjR}~kFy&cjf*e;1@jjd!!Y}UwKsdl%-^B#M`pfqM;uC^< zu===*-*pM#KDmo~#M%w@@dmxt9K2UOzPm1dM-XS6D3VDTql6^)~iR_ K_;Qif-M;}AsH?32 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32888484d788c1c7aa29e154aa563a27f78a0c3b GIT binary patch literal 3034 zcmZuz%WoV>8Lz5-PS3;RM;zP9Ld$}{Anai`LLl%)3r-AKMUJ(z(Jl>9)1E4i+wRv^ z*LdSm4+p&5K!S62rSWn91H?Ih0mr&>+6xDcLE7c3>hWR{X4KVH-}hD3_x|;hcH2Yn z``e3t{I4!T|4@tTKMNM`z*GJm28seiF(nBOuzr((=(iCV@HP@NwE`=(13PsB2P;`K zannZ7&~q#C(q_=qb319J?Vz1@f{tE0$wJx{*d3DPJ5jKaJwdFCtzt?tKAn zLHj*JnvddANcV9e;yjBIX}v!#64t}AJ7OZ_<1C8~6BZV60W@!z(p*cHJtCKO^Xw=d z)msnrx$=!C1&!48_t{Z2PDJ@jmK1wAPa`21hfVEao~EE$8Pa$t1inDj5Clf^Pu3JwXl7g@jD&ds^UxCZ%n|m9TcJxb#lUS;_!2{e zE#P|89%1TG_Y)+Vv(_cLk1p^{G$ph#YggnF=i%GsT)_RtQRmOvzjt%7fpo2!Uc7ppR~6z#l;KR_V3hBNOj1&gK!H zEDf}|S4pzXo-xRKl7mBvNE}O>J%dn{;H8qroPQTo>KXj|@Cpo&m83X-#U1iiOd<8+ zqw}z~vd*6Z3X}1-U}LGQMrI4Q3>SAWyoqUO4tU1YQGU%YDlHUHX}|mticFvj1Wg0a zpb=bRC@C^Epp*!IL9sAS%u|QI5SF4`;Hg=e6rH+!PuP?w8WbGRe~&9uI2UL{rdDOm zR3lRD$58tz-hs9?wI?g0G4m?x5>K7VIcZMq4^XyaBGEW$iS`jW_4r>gnz|TG*5Guj zGM^)AS1#OOsvAB)KLpQD-QT$%qFT;cS3hmxH(s)krSbwEB7H>(Et z!lOU~9lg(t5uP?H4|u<=nzJ&|r+o&2^|3=k1 zQXtyi4>rPshX>(%Pxkj8?*AfuxE~Il{QTg($GZ;(gUPpc^KnA`EEm3N0D}1kx+D3z zyz9j9C4Q5u<~dp0i{gY)U*tZZrVky+A7_+te?orZOH;MsFSOv|I6KYqk264PjfDQ@ zOT0OOZg76c{PTS5r!2~Zzd14dEx&h5m#Oq>K+{>KvY2ndARXv~G0!udg}kZgs&T9A z7c8UFE60b>;fJg&r2$$=Zw`Yw1#}pE&D1C>oyUWEVwC4lM^Hh6tM)JLQqX)Xr1dd} zrX!ss&REF`X(SAeEI9Z>!;EfBT;;p8qoM!}bm1xRP4hF>YxBEG#qTKr0Q9=m&GAb% zlx-nPN+z4v94sA$`taSs-&WfcU=1ad@KCxy3^j^bqp`LyEk`v~yGdcpwQjcn%Lnk3 z@4|qv-RKxy;$av1o%fY#LD&1zG(FSP9nixbUd4A*|ASKRVAt3LN!M`I4${FkULqZ% zg+0={EuAo=`7jK1O!!aXbp8$u(iZ0hdqut+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/parser.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/parser.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58aa8f0920bf92710c17c591841aec3558836a2d GIT binary patch literal 9984 zcmbVSOKcoRdhXZsyhr4Sq8^s*_G_haMABJ%<3wQ;$&xL5BT!y>WqT)9f}rY?VtrXhdL zrYV1|rX_!K%^ZHs(C*}$d7Z{sVWCrO7CWV8sZ(y2JC$aoQ*Bl|wPvkzsCh`j3 z3RmTHUMO-u>~%wiNvhYE{%GmyJ4^1X*REZ?)-Xi%VjQgm?H*>p%A!8ee3kQ<189ab z-)p6=*GpI36&`oo9uGw^NqHJC$8p#q9LFC!Yb5`O3e#|28l6?sWEQi0!#D4nA8RW( z7e9Bm(#$b?Ezb(Rec$l&e&MdwEc%bDK2|_~`MxEom}{0~y9T&J{uJQKvOSG<-9L zud}fGTFg6Mnlj#h>4F35r)j|WLWomvZZf_c;xx(|ZVS@;fMeePH;;qUM|UwHCEh8|pinpF6Jk_Aw28dGr}~v^8@8 z$WL|fs0PdnOJ6>L(PFye-F68rw;Lw`o$HshoR|tCpGBz~L@mxbsB!B4(x7j-TY;ah zHY{N$tGzS`#dMpcE;il?A}@54pwC1O9LTsRxR@bGU6(eOuyBP>VFo^p;(;!TuG+zMxGIbd@s_gd>9 z@l6)pTnK~Z4CKZ6-(A2~*jyK%y*9(ppt}G(fFbZ(XobOolA8JMCO-*Oj^IrSC^Vy> z7xWpufAWB1ogeMxfgx-qeY}n_Eh5V3p*o$Kf);6H`j~F`)U3ony=6esc%@^(YA7OHe*;CK?SsS_jf0vx>QQFM$oL1cFcyri|2W`cCCxp6l;+2us%&6{ok?(X^>VX2u zd}()@vox%e0iSgo=++P#cf~oKti~J@b<;CW7)NcYHO@F}zJ1m?eQIvy)EVd0+?=BP zsmA{KGD4pnEt#nTM`5!c8dC#*te)*6=DQG57&)=+lF%9zQFbL>#8XfoUbo94UzA+J zQ1zK1i-a9SFcqmNr`|eC+?!t5W5V(o3__giwJOZaaA=%I18B=H_e;_snz z-$q0&lc{WN0!gI(g4VF~{?iBTU~&j7WUiM$D?N(0iP`daCEg_6z7Ev^b)FhSDDNG} z&yKO9Zy5t}1I&JZmmOY>k{)MHy2?-ni4#ZRCJN?6>{b}ycbTQP7>gXavSfO|Ix7K( zP-J`Aoh(?{n<$DMut$prl2G%zF#U4rI&I>t^TAf>q2Z7&A!8&enc1LK6H!Rn?erG+ zy2ADunUjX8IOh)t*6bwh{RFOL)RKsv15_gQQ0|!CcfN(V5*(y0k70W-fpeHs61a)V zKwAazOiLQmT!W@`ViI>#D+2&novSI4#G-vLt7q1ZpT26@fmFFKu2kP zZV}{LnxDt3?5nUkUI54HB59(?Pel3B9w~)h;

    f`&1DB!BfkxdI=l|NSiWG!VXC@ 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0adb5db2e488ace85b7cd91afe28b683c624019 GIT binary patch literal 2643 zcmZWrTaOzx6t?GJN z5fbU^g2XFDY9IR-c;Ja2z_VX@>OVj&0?u)g?Lr;-*gih?*ynubb3Sb}Y6QwJzjflD zq4x(4j+P1xE<%-mf?$NvgoNXkh7`tDVx@Lyr%vdkZs?|7=%tmglKP=f(auh)X)Ua! zGvQ2H59?_oY^2Swna+l@>0CIM&WH2qLb#AFhKt7DNsgt*!{es!CMVLRaETIbbC)l# zkZsC6Z2Q&<30KBCev()CV=II^e3?;3`}PW9-Yth!n19m=Pfe^IwW;2+S&hwr&Ev4H zev2ONw89!N*JQIdz3?=n8>BUN4{lG^TAubVMM)BEC0tjp@QjN{obvaQsE~ZR?;@(!c^bu8oNZ6+7B@wd$vzhw1)o~BsGeDm z6#pU!onbSoJ|e8XM2?ZtW{m@D=#}mdXDKPk zW%AXjVWq5mMquP#ZIqsBmX#4@%>#>)Yd?NYUM8QAb@BxO89*?*rmZ~NU)|h^WgvI* zLBfJOOZI{-9t^@_20E(S%!9`c{UY@agE*RdY!9DainyXwz33MtE zyB=jd-kR0TcckP3WB8sB09UsU$Wx*TD?|eYQh=C8F^d8NCopni4#f-z?MIAt@wj3U zhu)xIfEH~hJZt&l7}_02aRSAYXj17Js7vin@2)GjK{saF?d=S*-Md6Q50>}K&Q6~4 z4&UdyF;6;|`E{P;MGC;{d>C!XPO+zU@@%y@w+grCTQOf%BI@mO(YemE>zyRtnwXqx zzt)j}(bWQ`qiqgLEj~GW2|?0++Pxu{Vq(mOycs6PO!Frw%HiN&!N_4`~$$ z$71$PX9#&Pv`bo?g4TiTa3MR~(kkuJ838H{YF`5kL&lWeh`vg|+P!VxA#xLKb}i9A zV(s2`?jX87#Q3$B4353EmJQJv2mu~8u@dH-h@fe~KAv?0syquqk*oB!#b`->g6u6R zNc(5^4yEH6_&!oLWQnu3U;TLE%V6IL@_wLc4amcmds2H+v3#JkdqY5+5Qzi1)3QVd zJ~2KDBErBztU^yWjp1Q*-T|FN2NHUc?zg%kx^Xy%k!zn%@?Mn43+?}|6X^U&TK)cZ z%f+-*VgZ!m84%hPJYsv;NUmCyBO!blZ4QqY)`9VPHyN{}cn&B3cjEa+4s{-^B;tX% z3l6{u096B7r*i-+pNiLE<}Nj<1L%2}MkXq_3=2jCE9UzpsPYvMB_IbPzKqcUJRrkb z2lfW)ZP2%k9?-;`18ajQXM->oV&v_AdxfhY%7T3BD(@+vtw;qu7*DB8B}I3wc}Uu`SDgN+U20mg6V@1z=mgTh^RQzHY-hv-L)QGR@)Ft z@dn&!4Wd)+53-)h^F-=O5dn2YiAgTwD(&E|;!Uvqzwb}uo5d!leF%*4Rk&D3znT9# z6(5}M)21aZg0A(1cnf!X5rp>OS%bGyptFUI0`Vr$@4`cYd7)Q;S=wFTAC`+Y5=eXM znxE)sOwxV4&hewv&8&8xLv- z+1=VxTKC4XhU$ hby~MhIG$wz-_;+~pZ0(a>wndktbZJ85szAb{|lttt=Rwo literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6191ee12391739c45219b72bf97f8f15327e553 GIT binary patch literal 8690 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2070137cd9b3681f6504167d3e4201bfae10c2e GIT binary patch literal 4974 zcmaJ_O>-Mb8Sb7hjbvGo6FW|v>>>goC}4})CB+sjiK)cdb#0I#PBuYop^`Oid+gCj zbdQp?O1Z3a;I>z)2vzbie}NMRennrQisCQ$K=HgiBgL_0M%C~1*L1(1Prt3Fv$F+; z=WqY0h5x<8*gvUp^6}7EMN$6(30S}*)=1yH!A;w0SmtdvZ1Z*+4&GMecD#ny$u)8v zzu|NG#*XrxLZe{nPBhagHj11HYcwlLN4#<2GZwgk_nHOXq17m(l?!~dd{I8M8*^yq zg96$G)4quIOi)C-h}o9}+nW~)A}7iL5BRoyl?AhJte_NJcgSPbSGSIj(5 zEL>$G7hJ@wOPDnuESOnW1HQ?sm;VDvS-om&YkfoKKNGQ#t)wfpztKy=ZrqBhT<4yx z*B@*=+eG!kj!2qe9Edn+(RiCaKl#{btfHvfAcB$FpfVm+)tVjIsTCoCvwyYK!_?u01J|zKq)(tBoJ3Q@M|#$e;;3WZz(;P4<@4N0;i| zWWCdiL)p`fX zZZ}@;eZ0Iah1d$kaw1#peIaWvMEs%_gfr9Nh@*& z*M19Q5;u&7jIaN>l&1T%{h-On4n$1o(=top9Hk*I! zx1w}J&e5{AN~BzPe?OAfY$|i85l6}a@p;KMJ!jl#5{b_-ZQ@2{0p}5}fXBn9ZQ%vZ z8^^?v+sFwUZk2;u`9U6T<)c-A(^1?wjl3D1L`kDyoNi8EG~U)~C*cc`$zDHpmcJU- z{*8_C{QX|l(YcOL3bxhGb{Gj=AOkhqyDh2owGOKNUeHQJYV#LlGSTkFfoi+y8irDx zWHw&=6)HnMWKA{%p6vT_KH+=TD{E+>&rX~n;utlA2B??hM*h$~qI!PF_Uut1u#%ZW zHqX#vi@=&icEo`+4rUZ74ytbb;W4ia+{((zN=1^JRA*tru^hHKP});T(CsJMZ5pX& z9z%vlVF$h}^=uO`AevFu%wr1qiU@R`#*=P0Qrc}Fw4sifW~vXKAK=#vK^JN$$ zyHz*!!4J?UKP2)UA{8RWF(%$YPKJGwW^)mPiae9~9{0J2Ux9m8(YnqD*Us8(W&9n& z24YPQ7=k9U!aEBBJp!W1)1ucZ5j9 zIP63{rdpAL6A;zfuaS18N#XlFR6nH;Me1hV4ki+TD)&YFFN(4p#~# zlB%6*?NJI=cJeX#47g6*l=y;J zP!frR8Xs9lOkGJ^BXU%>=N=lBP@ln_s%ONV$j}N7c4F~$B8f>kOXPjq8}Oic z2GSV}?tpiP4;H6zPYx%^J4r>LCdeZ+@`PxM(MKn0o6)E#3o3cRu5G%q}X z(~{8)=kJUpX;nE-3^#BGgP(f@)~B#>mbz`d70|u`SJ>pt3=2a zlAxYkquWa43U!d4jKaM+MrVVP0b~onRMrdi zZhY3H8Qj)ak&8%!6cucqo_b`{@k4bg_fgdMK~fksK*qd&mrC&wJ~vXxJE&hA0S#GNqS z?iyjMSsFkfa@uq6q3H z*^lEe-obHIbyA_oD)JK|#NoM+@eG5fA!BJe`{$#^#1N->AJNgk|AG|%uQ-5V0C-l0 z6Ha|_d3-8XUWUnTB|Ug5w~qO(C(yXT55xO;fbj?7ee6aKzsLvIr*od+1((0X7dTI* zA;m99O9maxo|KE7j}D(=^a~WTyU*vnvI{hz-Zsj657~7UMUgGWXkgMQ3$dN%^hnB( z(eo^$_e_4zvqUUsQmuj{)z+bw3f|MRS%Nte*OZ=~jO)x-(mck}*PH(*=|V~xwNx^W z=FIWUrc(lW8#|d)lmdA$eoisAxm9RTuBX-1I!BgP({0gNJCVkj<$7iCg zoh0lC`2e#e$(X^6w&U(g?FJ$OIGy5;^u33Y1*T7dId_snDR8!)IlU*GmGEuK8GP1U zkJ9tml4}@AS($ba6NYqYUs?$llv8_BPU4^Tz=`u|XA8Mu8n?92389R5WLo120u>{f z&&y89HGS|71H0y~LrLrSAx=yu-s;aFNtTPq8wt|2BP3*d-abbwH)MzW6_5SIPiuop zlBd~+{99bWhx|*x>N9qS{T8729JTna!;->&LGE#k(2UVcicsIM-?8{MMvEBz(}_`Y zu9(r{^K^cS4TNY;UHk1;+!hgXWmnMEQ#(O77S&n#Juoim%+g+{T1j#sSE(_FJN4T3 zwlJ5FM|2U@IYMy2p?22m4>!yyZGuzJ?uG#_-_3SkDy%&Bg*e#iwq#%qRN3z(CggQ~ z?d!HM7x+x2IQ@_^;dXt(9%Kq#?Dnu+HmJ~}C?#OIE4*O&+~P$(&r9$;&rsizPcS<0 zuKoVrM|Yy^y^Hg;PN8j*`!mKPLr#qM8#EFoaZ4dX#H%={`4NNRkd(oU&YrvP$lKZI z4UFRUL%=&g3OY8O>Gi4ixk=elx6VsKaY;wB(X0G5)hLCL8$>9X)ia;3KUiztfBJZH zG-*9q!l;{~s>$1+^pNhM7Y`!VCemQtNYagv%aU|QyEIKgc3Y@Ek+0y{ zr^wY)U%`{Rwt_RvH~h>$3=FaDV?)KqC*o4|FD?Fusl<&caiPGB#9(G(vR2Y!?WE1D z#A2PK!@7~t@9mVs*y)8`OQWq}X`CtGez)mGWIXnwsOflN7=(>Y@I4NmFx|`3*bir9 zH1%*yaWJEI0l{u_{up|(ACQRN5`XGa*AEZ=6CBWo(QG{NqP_RssBLxX7vFDaQLX^N zD=azW$b}8$WwnArqG_55RBh#=ESzfSJl6mj2S?WFd+0wJO; None: + """Entry Point for completion of main and subcommand options.""" + # Don't complete if user hasn't sourced bash_completion file. + if "PIP_AUTO_COMPLETE" not in os.environ: + return + # Don't complete if autocompletion environment variables + # are not present + if not os.environ.get("COMP_WORDS") or not os.environ.get("COMP_CWORD"): + return + cwords = os.environ["COMP_WORDS"].split()[1:] + cword = int(os.environ["COMP_CWORD"]) + try: + current = cwords[cword - 1] + except IndexError: + current = "" + + parser = create_main_parser() + subcommands = list(commands_dict) + options = [] + + # subcommand + subcommand_name: Optional[str] = None + for word in cwords: + if word in subcommands: + subcommand_name = word + break + # subcommand options + if subcommand_name is not None: + # special case: 'help' subcommand has no options + if subcommand_name == "help": + sys.exit(1) + # special case: list locally installed dists for show and uninstall + should_list_installed = not current.startswith("-") and subcommand_name in [ + "show", + "uninstall", + ] + if should_list_installed: + env = get_default_environment() + lc = current.lower() + installed = [ + dist.canonical_name + for dist in env.iter_installed_distributions(local_only=True) + if dist.canonical_name.startswith(lc) + and dist.canonical_name not in cwords[1:] + ] + # if there are no dists installed, fall back to option completion + if installed: + for dist in installed: + print(dist) + sys.exit(1) + + should_list_installables = ( + not current.startswith("-") and subcommand_name == "install" + ) + if should_list_installables: + for path in auto_complete_paths(current, "path"): + print(path) + sys.exit(1) + + subcommand = create_command(subcommand_name) + + for opt in subcommand.parser.option_list_all: + if opt.help != optparse.SUPPRESS_HELP: + options += [ + (opt_str, opt.nargs) for opt_str in opt._long_opts + opt._short_opts + ] + + # filter out previously specified options from available options + prev_opts = [x.split("=")[0] for x in cwords[1 : cword - 1]] + options = [(x, v) for (x, v) in options if x not in prev_opts] + # filter options by current input + options = [(k, v) for k, v in options if k.startswith(current)] + # get completion type given cwords and available subcommand options + completion_type = get_path_completion_type( + cwords, + cword, + subcommand.parser.option_list_all, + ) + # get completion files and directories if ``completion_type`` is + # ````, ``

    `` or ```` + if completion_type: + paths = auto_complete_paths(current, completion_type) + options = [(path, 0) for path in paths] + for option in options: + opt_label = option[0] + # append '=' to options which require args + if option[1] and option[0][:2] == "--": + opt_label += "=" + print(opt_label) + else: + # show main parser options only when necessary + + opts = [i.option_list for i in parser.option_groups] + opts.append(parser.option_list) + flattened_opts = chain.from_iterable(opts) + if current.startswith("-"): + for opt in flattened_opts: + if opt.help != optparse.SUPPRESS_HELP: + subcommands += opt._long_opts + opt._short_opts + else: + # get completion type given cwords and all available options + completion_type = get_path_completion_type(cwords, cword, flattened_opts) + if completion_type: + subcommands = list(auto_complete_paths(current, completion_type)) + + print(" ".join([x for x in subcommands if x.startswith(current)])) + sys.exit(1) + + +def get_path_completion_type( + cwords: List[str], cword: int, opts: Iterable[Any] +) -> Optional[str]: + """Get the type of path completion (``file``, ``dir``, ``path`` or None) + + :param cwords: same as the environmental variable ``COMP_WORDS`` + :param cword: same as the environmental variable ``COMP_CWORD`` + :param opts: The available options to check + :return: path completion type (``file``, ``dir``, ``path`` or None) + """ + if cword < 2 or not cwords[cword - 2].startswith("-"): + return None + for opt in opts: + if opt.help == optparse.SUPPRESS_HELP: + continue + for o in str(opt).split("/"): + if cwords[cword - 2].split("=")[0] == o: + if not opt.metavar or any( + x in ("path", "file", "dir") for x in opt.metavar.split("/") + ): + return opt.metavar + return None + + +def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]: + """If ``completion_type`` is ``file`` or ``path``, list all regular files + and directories starting with ``current``; otherwise only list directories + starting with ``current``. + + :param current: The word to be completed + :param completion_type: path completion type(``file``, ``path`` or ``dir``) + :return: A generator of regular files and/or directories + """ + directory, filename = os.path.split(current) + current_path = os.path.abspath(directory) + # Don't complete paths if they can't be accessed + if not os.access(current_path, os.R_OK): + return + filename = os.path.normcase(filename) + # list all files that start with ``filename`` + file_list = ( + x for x in os.listdir(current_path) if os.path.normcase(x).startswith(filename) + ) + for f in file_list: + opt = os.path.join(current_path, f) + comp_file = os.path.normcase(os.path.join(directory, f)) + # complete regular files when there is not ```` after option + # complete directories when there is ````, ```` or + # ````after option + if completion_type != "dir" and os.path.isfile(opt): + yield comp_file + elif os.path.isdir(opt): + yield os.path.join(comp_file, "") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/base_command.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/base_command.py new file mode 100644 index 000000000..362f84b6b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/base_command.py @@ -0,0 +1,240 @@ +"""Base Command class, and related routines""" + +import logging +import logging.config +import optparse +import os +import sys +import traceback +from optparse import Values +from typing import List, Optional, Tuple + +from pip._vendor.rich import reconfigure +from pip._vendor.rich import traceback as rich_traceback + +from pip._internal.cli import cmdoptions +from pip._internal.cli.command_context import CommandContextMixIn +from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter +from pip._internal.cli.status_codes import ( + ERROR, + PREVIOUS_BUILD_DIR_ERROR, + UNKNOWN_ERROR, + VIRTUALENV_NOT_FOUND, +) +from pip._internal.exceptions import ( + BadCommand, + CommandError, + DiagnosticPipError, + InstallationError, + NetworkConnectionError, + PreviousBuildDirError, +) +from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.filesystem import check_path_owner +from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging +from pip._internal.utils.misc import get_prog, normalize_path +from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry +from pip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry +from pip._internal.utils.virtualenv import running_under_virtualenv + +__all__ = ["Command"] + +logger = logging.getLogger(__name__) + + +class Command(CommandContextMixIn): + usage: str = "" + ignore_require_venv: bool = False + + def __init__(self, name: str, summary: str, isolated: bool = False) -> None: + super().__init__() + + self.name = name + self.summary = summary + self.parser = ConfigOptionParser( + usage=self.usage, + prog=f"{get_prog()} {name}", + formatter=UpdatingDefaultsHelpFormatter(), + add_help_option=False, + name=name, + description=self.__doc__, + isolated=isolated, + ) + + self.tempdir_registry: Optional[TempDirRegistry] = None + + # Commands should add options to this option group + optgroup_name = f"{self.name.capitalize()} Options" + self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name) + + # Add the general options + gen_opts = cmdoptions.make_option_group( + cmdoptions.general_group, + self.parser, + ) + self.parser.add_option_group(gen_opts) + + self.add_options() + + def add_options(self) -> None: + pass + + def handle_pip_version_check(self, options: Values) -> None: + """ + This is a no-op so that commands by default do not do the pip version + check. + """ + # Make sure we do the pip version check if the index_group options + # are present. + assert not hasattr(options, "no_index") + + def run(self, options: Values, args: List[str]) -> int: + raise NotImplementedError + + def _run_wrapper(self, level_number: int, options: Values, args: List[str]) -> int: + def _inner_run() -> int: + try: + return self.run(options, args) + finally: + self.handle_pip_version_check(options) + + if options.debug_mode: + rich_traceback.install(show_locals=True) + return _inner_run() + + try: + status = _inner_run() + assert isinstance(status, int) + return status + except DiagnosticPipError as exc: + logger.error("%s", exc, extra={"rich": True}) + logger.debug("Exception information:", exc_info=True) + + return ERROR + except PreviousBuildDirError as exc: + logger.critical(str(exc)) + logger.debug("Exception information:", exc_info=True) + + return PREVIOUS_BUILD_DIR_ERROR + except ( + InstallationError, + BadCommand, + NetworkConnectionError, + ) as exc: + logger.critical(str(exc)) + logger.debug("Exception information:", exc_info=True) + + return ERROR + except CommandError as exc: + logger.critical("%s", exc) + logger.debug("Exception information:", exc_info=True) + + return ERROR + except BrokenStdoutLoggingError: + # Bypass our logger and write any remaining messages to + # stderr because stdout no longer works. + print("ERROR: Pipe to stdout was broken", file=sys.stderr) + if level_number <= logging.DEBUG: + traceback.print_exc(file=sys.stderr) + + return ERROR + except KeyboardInterrupt: + logger.critical("Operation cancelled by user") + logger.debug("Exception information:", exc_info=True) + + return ERROR + except BaseException: + logger.critical("Exception:", exc_info=True) + + return UNKNOWN_ERROR + + def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]: + # factored out for testability + return self.parser.parse_args(args) + + def main(self, args: List[str]) -> int: + try: + with self.main_context(): + return self._main(args) + finally: + logging.shutdown() + + def _main(self, args: List[str]) -> int: + # We must initialize this before the tempdir manager, otherwise the + # configuration would not be accessible by the time we clean up the + # tempdir manager. + self.tempdir_registry = self.enter_context(tempdir_registry()) + # Intentionally set as early as possible so globally-managed temporary + # directories are available to the rest of the code. + self.enter_context(global_tempdir_manager()) + + options, args = self.parse_args(args) + + # Set verbosity so that it can be used elsewhere. + self.verbosity = options.verbose - options.quiet + + reconfigure(no_color=options.no_color) + level_number = setup_logging( + verbosity=self.verbosity, + no_color=options.no_color, + user_log_file=options.log, + ) + + always_enabled_features = set(options.features_enabled) & set( + cmdoptions.ALWAYS_ENABLED_FEATURES + ) + if always_enabled_features: + logger.warning( + "The following features are always enabled: %s. ", + ", ".join(sorted(always_enabled_features)), + ) + + # Make sure that the --python argument isn't specified after the + # subcommand. We can tell, because if --python was specified, + # we should only reach this point if we're running in the created + # subprocess, which has the _PIP_RUNNING_IN_SUBPROCESS environment + # variable set. + if options.python and "_PIP_RUNNING_IN_SUBPROCESS" not in os.environ: + logger.critical( + "The --python option must be placed before the pip subcommand name" + ) + sys.exit(ERROR) + + # TODO: Try to get these passing down from the command? + # without resorting to os.environ to hold these. + # This also affects isolated builds and it should. + + if options.no_input: + os.environ["PIP_NO_INPUT"] = "1" + + if options.exists_action: + os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action) + + if options.require_venv and not self.ignore_require_venv: + # If a venv is required check if it can really be found + if not running_under_virtualenv(): + logger.critical("Could not find an activated virtualenv (required).") + sys.exit(VIRTUALENV_NOT_FOUND) + + if options.cache_dir: + options.cache_dir = normalize_path(options.cache_dir) + if not check_path_owner(options.cache_dir): + logger.warning( + "The directory '%s' or its parent directory is not owned " + "or is not writable by the current user. The cache " + "has been disabled. Check the permissions and owner of " + "that directory. If executing pip with sudo, you should " + "use sudo's -H flag.", + options.cache_dir, + ) + options.cache_dir = None + + if options.no_python_version_warning: + deprecated( + reason="--no-python-version-warning is deprecated.", + replacement="to remove the flag as it's a no-op", + gone_in="25.1", + issue=13154, + ) + + return self._run_wrapper(level_number, options, args) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/cmdoptions.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/cmdoptions.py new file mode 100644 index 000000000..eeb7e651b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/cmdoptions.py @@ -0,0 +1,1075 @@ +""" +shared options and groups + +The principle here is to define options once, but *not* instantiate them +globally. One reason being that options with action='append' can carry state +between parses. pip parses general options twice internally, and shouldn't +pass on state. To be consistent, all options will follow this design. +""" + +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + +import importlib.util +import logging +import os +import textwrap +from functools import partial +from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values +from textwrap import dedent +from typing import Any, Callable, Dict, Optional, Tuple + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.cli.parser import ConfigOptionParser +from pip._internal.exceptions import CommandError +from pip._internal.locations import USER_CACHE_DIR, get_src_prefix +from pip._internal.models.format_control import FormatControl +from pip._internal.models.index import PyPI +from pip._internal.models.target_python import TargetPython +from pip._internal.utils.hashes import STRONG_HASHES +from pip._internal.utils.misc import strtobool + +logger = logging.getLogger(__name__) + + +def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None: + """ + Raise an option parsing error using parser.error(). + + Args: + parser: an OptionParser instance. + option: an Option instance. + msg: the error text. + """ + msg = f"{option} error: {msg}" + msg = textwrap.fill(" ".join(msg.split())) + parser.error(msg) + + +def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup: + """ + Return an OptionGroup object + group -- assumed to be dict with 'name' and 'options' keys + parser -- an optparse Parser + """ + option_group = OptionGroup(parser, group["name"]) + for option in group["options"]: + option_group.add_option(option()) + return option_group + + +def check_dist_restriction(options: Values, check_target: bool = False) -> None: + """Function for determining if custom platform options are allowed. + + :param options: The OptionParser options. + :param check_target: Whether or not to check if --target is being used. + """ + dist_restriction_set = any( + [ + options.python_version, + options.platforms, + options.abis, + options.implementation, + ] + ) + + binary_only = FormatControl(set(), {":all:"}) + sdist_dependencies_allowed = ( + options.format_control != binary_only and not options.ignore_dependencies + ) + + # Installations or downloads using dist restrictions must not combine + # source distributions and dist-specific wheels, as they are not + # guaranteed to be locally compatible. + if dist_restriction_set and sdist_dependencies_allowed: + raise CommandError( + "When restricting platform and interpreter constraints using " + "--python-version, --platform, --abi, or --implementation, " + "either --no-deps must be set, or --only-binary=:all: must be " + "set and --no-binary must not be set (or must be set to " + ":none:)." + ) + + if check_target: + if not options.dry_run and dist_restriction_set and not options.target_dir: + raise CommandError( + "Can not use any platform or abi specific options unless " + "installing via '--target' or using '--dry-run'" + ) + + +def _path_option_check(option: Option, opt: str, value: str) -> str: + return os.path.expanduser(value) + + +def _package_name_option_check(option: Option, opt: str, value: str) -> str: + return canonicalize_name(value) + + +class PipOption(Option): + TYPES = Option.TYPES + ("path", "package_name") + TYPE_CHECKER = Option.TYPE_CHECKER.copy() + TYPE_CHECKER["package_name"] = _package_name_option_check + TYPE_CHECKER["path"] = _path_option_check + + +########### +# options # +########### + +help_: Callable[..., Option] = partial( + Option, + "-h", + "--help", + dest="help", + action="help", + help="Show help.", +) + +debug_mode: Callable[..., Option] = partial( + Option, + "--debug", + dest="debug_mode", + action="store_true", + default=False, + help=( + "Let unhandled exceptions propagate outside the main subroutine, " + "instead of logging them to stderr." + ), +) + +isolated_mode: Callable[..., Option] = partial( + Option, + "--isolated", + dest="isolated_mode", + action="store_true", + default=False, + help=( + "Run pip in an isolated mode, ignoring environment variables and user " + "configuration." + ), +) + +require_virtualenv: Callable[..., Option] = partial( + Option, + "--require-virtualenv", + "--require-venv", + dest="require_venv", + action="store_true", + default=False, + help=( + "Allow pip to only run in a virtual environment; " + "exit with an error otherwise." + ), +) + +override_externally_managed: Callable[..., Option] = partial( + Option, + "--break-system-packages", + dest="override_externally_managed", + action="store_true", + help="Allow pip to modify an EXTERNALLY-MANAGED Python installation", +) + +python: Callable[..., Option] = partial( + Option, + "--python", + dest="python", + help="Run pip with the specified Python interpreter.", +) + +verbose: Callable[..., Option] = partial( + Option, + "-v", + "--verbose", + dest="verbose", + action="count", + default=0, + help="Give more output. Option is additive, and can be used up to 3 times.", +) + +no_color: Callable[..., Option] = partial( + Option, + "--no-color", + dest="no_color", + action="store_true", + default=False, + help="Suppress colored output.", +) + +version: Callable[..., Option] = partial( + Option, + "-V", + "--version", + dest="version", + action="store_true", + help="Show version and exit.", +) + +quiet: Callable[..., Option] = partial( + Option, + "-q", + "--quiet", + dest="quiet", + action="count", + default=0, + help=( + "Give less output. Option is additive, and can be used up to 3" + " times (corresponding to WARNING, ERROR, and CRITICAL logging" + " levels)." + ), +) + +progress_bar: Callable[..., Option] = partial( + Option, + "--progress-bar", + dest="progress_bar", + type="choice", + choices=["on", "off", "raw"], + default="on", + help="Specify whether the progress bar should be used [on, off, raw] (default: on)", +) + +log: Callable[..., Option] = partial( + PipOption, + "--log", + "--log-file", + "--local-log", + dest="log", + metavar="path", + type="path", + help="Path to a verbose appending log.", +) + +no_input: Callable[..., Option] = partial( + Option, + # Don't ask for input + "--no-input", + dest="no_input", + action="store_true", + default=False, + help="Disable prompting for input.", +) + +keyring_provider: Callable[..., Option] = partial( + Option, + "--keyring-provider", + dest="keyring_provider", + choices=["auto", "disabled", "import", "subprocess"], + default="auto", + help=( + "Enable the credential lookup via the keyring library if user input is allowed." + " Specify which mechanism to use [auto, disabled, import, subprocess]." + " (default: %default)" + ), +) + +proxy: Callable[..., Option] = partial( + Option, + "--proxy", + dest="proxy", + type="str", + default="", + help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.", +) + +retries: Callable[..., Option] = partial( + Option, + "--retries", + dest="retries", + type="int", + default=5, + help="Maximum number of retries each connection should attempt " + "(default %default times).", +) + +timeout: Callable[..., Option] = partial( + Option, + "--timeout", + "--default-timeout", + metavar="sec", + dest="timeout", + type="float", + default=15, + help="Set the socket timeout (default %default seconds).", +) + + +def exists_action() -> Option: + return Option( + # Option when path already exist + "--exists-action", + dest="exists_action", + type="choice", + choices=["s", "i", "w", "b", "a"], + default=[], + action="append", + metavar="action", + help="Default action when a path already exists: " + "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.", + ) + + +cert: Callable[..., Option] = partial( + PipOption, + "--cert", + dest="cert", + type="path", + metavar="path", + help=( + "Path to PEM-encoded CA certificate bundle. " + "If provided, overrides the default. " + "See 'SSL Certificate Verification' in pip documentation " + "for more information." + ), +) + +client_cert: Callable[..., Option] = partial( + PipOption, + "--client-cert", + dest="client_cert", + type="path", + default=None, + metavar="path", + help="Path to SSL client certificate, a single file containing the " + "private key and the certificate in PEM format.", +) + +index_url: Callable[..., Option] = partial( + Option, + "-i", + "--index-url", + "--pypi-url", + dest="index_url", + metavar="URL", + default=PyPI.simple_url, + help="Base URL of the Python Package Index (default %default). " + "This should point to a repository compliant with PEP 503 " + "(the simple repository API) or a local directory laid out " + "in the same format.", +) + + +def extra_index_url() -> Option: + return Option( + "--extra-index-url", + dest="extra_index_urls", + metavar="URL", + action="append", + default=[], + help="Extra URLs of package indexes to use in addition to " + "--index-url. Should follow the same rules as " + "--index-url.", + ) + + +no_index: Callable[..., Option] = partial( + Option, + "--no-index", + dest="no_index", + action="store_true", + default=False, + help="Ignore package index (only looking at --find-links URLs instead).", +) + + +def find_links() -> Option: + return Option( + "-f", + "--find-links", + dest="find_links", + action="append", + default=[], + metavar="url", + help="If a URL or path to an html file, then parse for links to " + "archives such as sdist (.tar.gz) or wheel (.whl) files. " + "If a local path or file:// URL that's a directory, " + "then look for archives in the directory listing. " + "Links to VCS project URLs are not supported.", + ) + + +def trusted_host() -> Option: + return Option( + "--trusted-host", + dest="trusted_hosts", + action="append", + metavar="HOSTNAME", + default=[], + help="Mark this host or host:port pair as trusted, even though it " + "does not have valid or any HTTPS.", + ) + + +def constraints() -> Option: + return Option( + "-c", + "--constraint", + dest="constraints", + action="append", + default=[], + metavar="file", + help="Constrain versions using the given constraints file. " + "This option can be used multiple times.", + ) + + +def requirements() -> Option: + return Option( + "-r", + "--requirement", + dest="requirements", + action="append", + default=[], + metavar="file", + help="Install from the given requirements file. " + "This option can be used multiple times.", + ) + + +def editable() -> Option: + return Option( + "-e", + "--editable", + dest="editables", + action="append", + default=[], + metavar="path/url", + help=( + "Install a project in editable mode (i.e. setuptools " + '"develop mode") from a local project path or a VCS url.' + ), + ) + + +def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None: + value = os.path.abspath(value) + setattr(parser.values, option.dest, value) + + +src: Callable[..., Option] = partial( + PipOption, + "--src", + "--source", + "--source-dir", + "--source-directory", + dest="src_dir", + type="path", + metavar="dir", + default=get_src_prefix(), + action="callback", + callback=_handle_src, + help="Directory to check out editable projects into. " + 'The default in a virtualenv is "/src". ' + 'The default for global installs is "/src".', +) + + +def _get_format_control(values: Values, option: Option) -> Any: + """Get a format_control object.""" + return getattr(values, option.dest) + + +def _handle_no_binary( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + existing = _get_format_control(parser.values, option) + FormatControl.handle_mutual_excludes( + value, + existing.no_binary, + existing.only_binary, + ) + + +def _handle_only_binary( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + existing = _get_format_control(parser.values, option) + FormatControl.handle_mutual_excludes( + value, + existing.only_binary, + existing.no_binary, + ) + + +def no_binary() -> Option: + format_control = FormatControl(set(), set()) + return Option( + "--no-binary", + dest="format_control", + action="callback", + callback=_handle_no_binary, + type="str", + default=format_control, + help="Do not use binary packages. Can be supplied multiple times, and " + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all binary packages, ":none:" to empty the set (notice ' + "the colons), or one or more package names with commas between " + "them (no colons). Note that some packages are tricky to compile " + "and may fail to install when this option is used on them.", + ) + + +def only_binary() -> Option: + format_control = FormatControl(set(), set()) + return Option( + "--only-binary", + dest="format_control", + action="callback", + callback=_handle_only_binary, + type="str", + default=format_control, + help="Do not use source packages. Can be supplied multiple times, and " + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all source packages, ":none:" to empty the set, or one ' + "or more package names with commas between them. Packages " + "without binary distributions will fail to install when this " + "option is used on them.", + ) + + +platforms: Callable[..., Option] = partial( + Option, + "--platform", + dest="platforms", + metavar="platform", + action="append", + default=None, + help=( + "Only use wheels compatible with . Defaults to the " + "platform of the running system. Use this option multiple times to " + "specify multiple platforms supported by the target interpreter." + ), +) + + +# This was made a separate function for unit-testing purposes. +def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]: + """ + Convert a version string like "3", "37", or "3.7.3" into a tuple of ints. + + :return: A 2-tuple (version_info, error_msg), where `error_msg` is + non-None if and only if there was a parsing error. + """ + if not value: + # The empty string is the same as not providing a value. + return (None, None) + + parts = value.split(".") + if len(parts) > 3: + return ((), "at most three version parts are allowed") + + if len(parts) == 1: + # Then we are in the case of "3" or "37". + value = parts[0] + if len(value) > 1: + parts = [value[0], value[1:]] + + try: + version_info = tuple(int(part) for part in parts) + except ValueError: + return ((), "each version part must be an integer") + + return (version_info, None) + + +def _handle_python_version( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + """ + Handle a provided --python-version value. + """ + version_info, error_msg = _convert_python_version(value) + if error_msg is not None: + msg = f"invalid --python-version value: {value!r}: {error_msg}" + raise_option_error(parser, option=option, msg=msg) + + parser.values.python_version = version_info + + +python_version: Callable[..., Option] = partial( + Option, + "--python-version", + dest="python_version", + metavar="python_version", + action="callback", + callback=_handle_python_version, + type="str", + default=None, + help=dedent( + """\ + The Python interpreter version to use for wheel and "Requires-Python" + compatibility checks. Defaults to a version derived from the running + interpreter. The version can be specified using up to three dot-separated + integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor + version can also be given as a string without dots (e.g. "37" for 3.7.0). + """ + ), +) + + +implementation: Callable[..., Option] = partial( + Option, + "--implementation", + dest="implementation", + metavar="implementation", + default=None, + help=( + "Only use wheels compatible with Python " + "implementation , e.g. 'pp', 'jy', 'cp', " + " or 'ip'. If not specified, then the current " + "interpreter implementation is used. Use 'py' to force " + "implementation-agnostic wheels." + ), +) + + +abis: Callable[..., Option] = partial( + Option, + "--abi", + dest="abis", + metavar="abi", + action="append", + default=None, + help=( + "Only use wheels compatible with Python abi , e.g. 'pypy_41'. " + "If not specified, then the current interpreter abi tag is used. " + "Use this option multiple times to specify multiple abis supported " + "by the target interpreter. Generally you will need to specify " + "--implementation, --platform, and --python-version when using this " + "option." + ), +) + + +def add_target_python_options(cmd_opts: OptionGroup) -> None: + cmd_opts.add_option(platforms()) + cmd_opts.add_option(python_version()) + cmd_opts.add_option(implementation()) + cmd_opts.add_option(abis()) + + +def make_target_python(options: Values) -> TargetPython: + target_python = TargetPython( + platforms=options.platforms, + py_version_info=options.python_version, + abis=options.abis, + implementation=options.implementation, + ) + + return target_python + + +def prefer_binary() -> Option: + return Option( + "--prefer-binary", + dest="prefer_binary", + action="store_true", + default=False, + help=( + "Prefer binary packages over source packages, even if the " + "source packages are newer." + ), + ) + + +cache_dir: Callable[..., Option] = partial( + PipOption, + "--cache-dir", + dest="cache_dir", + default=USER_CACHE_DIR, + metavar="dir", + type="path", + help="Store the cache data in .", +) + + +def _handle_no_cache_dir( + option: Option, opt: str, value: str, parser: OptionParser +) -> None: + """ + Process a value provided for the --no-cache-dir option. + + This is an optparse.Option callback for the --no-cache-dir option. + """ + # The value argument will be None if --no-cache-dir is passed via the + # command-line, since the option doesn't accept arguments. However, + # the value can be non-None if the option is triggered e.g. by an + # environment variable, like PIP_NO_CACHE_DIR=true. + if value is not None: + # Then parse the string value to get argument error-checking. + try: + strtobool(value) + except ValueError as exc: + raise_option_error(parser, option=option, msg=str(exc)) + + # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool() + # converted to 0 (like "false" or "no") caused cache_dir to be disabled + # rather than enabled (logic would say the latter). Thus, we disable + # the cache directory not just on values that parse to True, but (for + # backwards compatibility reasons) also on values that parse to False. + # In other words, always set it to False if the option is provided in + # some (valid) form. + parser.values.cache_dir = False + + +no_cache: Callable[..., Option] = partial( + Option, + "--no-cache-dir", + dest="cache_dir", + action="callback", + callback=_handle_no_cache_dir, + help="Disable the cache.", +) + +no_deps: Callable[..., Option] = partial( + Option, + "--no-deps", + "--no-dependencies", + dest="ignore_dependencies", + action="store_true", + default=False, + help="Don't install package dependencies.", +) + +ignore_requires_python: Callable[..., Option] = partial( + Option, + "--ignore-requires-python", + dest="ignore_requires_python", + action="store_true", + help="Ignore the Requires-Python information.", +) + +no_build_isolation: Callable[..., Option] = partial( + Option, + "--no-build-isolation", + dest="build_isolation", + action="store_false", + default=True, + help="Disable isolation when building a modern source distribution. " + "Build dependencies specified by PEP 518 must be already installed " + "if this option is used.", +) + +check_build_deps: Callable[..., Option] = partial( + Option, + "--check-build-dependencies", + dest="check_build_deps", + action="store_true", + default=False, + help="Check the build dependencies when PEP517 is used.", +) + + +def _handle_no_use_pep517( + option: Option, opt: str, value: str, parser: OptionParser +) -> None: + """ + Process a value provided for the --no-use-pep517 option. + + This is an optparse.Option callback for the no_use_pep517 option. + """ + # Since --no-use-pep517 doesn't accept arguments, the value argument + # will be None if --no-use-pep517 is passed via the command-line. + # However, the value can be non-None if the option is triggered e.g. + # by an environment variable, for example "PIP_NO_USE_PEP517=true". + if value is not None: + msg = """A value was passed for --no-use-pep517, + probably using either the PIP_NO_USE_PEP517 environment variable + or the "no-use-pep517" config file option. Use an appropriate value + of the PIP_USE_PEP517 environment variable or the "use-pep517" + config file option instead. + """ + raise_option_error(parser, option=option, msg=msg) + + # If user doesn't wish to use pep517, we check if setuptools and wheel are installed + # and raise error if it is not. + packages = ("setuptools", "wheel") + if not all(importlib.util.find_spec(package) for package in packages): + msg = ( + f"It is not possible to use --no-use-pep517 " + f"without {' and '.join(packages)} installed." + ) + raise_option_error(parser, option=option, msg=msg) + + # Otherwise, --no-use-pep517 was passed via the command-line. + parser.values.use_pep517 = False + + +use_pep517: Any = partial( + Option, + "--use-pep517", + dest="use_pep517", + action="store_true", + default=None, + help="Use PEP 517 for building source distributions " + "(use --no-use-pep517 to force legacy behaviour).", +) + +no_use_pep517: Any = partial( + Option, + "--no-use-pep517", + dest="use_pep517", + action="callback", + callback=_handle_no_use_pep517, + default=None, + help=SUPPRESS_HELP, +) + + +def _handle_config_settings( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + key, sep, val = value.partition("=") + if sep != "=": + parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL") + dest = getattr(parser.values, option.dest) + if dest is None: + dest = {} + setattr(parser.values, option.dest, dest) + if key in dest: + if isinstance(dest[key], list): + dest[key].append(val) + else: + dest[key] = [dest[key], val] + else: + dest[key] = val + + +config_settings: Callable[..., Option] = partial( + Option, + "-C", + "--config-settings", + dest="config_settings", + type=str, + action="callback", + callback=_handle_config_settings, + metavar="settings", + help="Configuration settings to be passed to the PEP 517 build backend. " + "Settings take the form KEY=VALUE. Use multiple --config-settings options " + "to pass multiple keys to the backend.", +) + +build_options: Callable[..., Option] = partial( + Option, + "--build-option", + dest="build_options", + metavar="options", + action="append", + help="Extra arguments to be supplied to 'setup.py bdist_wheel'.", +) + +global_options: Callable[..., Option] = partial( + Option, + "--global-option", + dest="global_options", + action="append", + metavar="options", + help="Extra global options to be supplied to the setup.py " + "call before the install or bdist_wheel command.", +) + +no_clean: Callable[..., Option] = partial( + Option, + "--no-clean", + action="store_true", + default=False, + help="Don't clean up build directories.", +) + +pre: Callable[..., Option] = partial( + Option, + "--pre", + action="store_true", + default=False, + help="Include pre-release and development versions. By default, " + "pip only finds stable versions.", +) + +disable_pip_version_check: Callable[..., Option] = partial( + Option, + "--disable-pip-version-check", + dest="disable_pip_version_check", + action="store_true", + default=False, + help="Don't periodically check PyPI to determine whether a new version " + "of pip is available for download. Implied with --no-index.", +) + +root_user_action: Callable[..., Option] = partial( + Option, + "--root-user-action", + dest="root_user_action", + default="warn", + choices=["warn", "ignore"], + help="Action if pip is run as a root user [warn, ignore] (default: warn)", +) + + +def _handle_merge_hash( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + """Given a value spelled "algo:digest", append the digest to a list + pointed to in a dict by the algo name.""" + if not parser.values.hashes: + parser.values.hashes = {} + try: + algo, digest = value.split(":", 1) + except ValueError: + parser.error( + f"Arguments to {opt_str} must be a hash name " + "followed by a value, like --hash=sha256:" + "abcde..." + ) + if algo not in STRONG_HASHES: + parser.error( + "Allowed hash algorithms for {} are {}.".format( + opt_str, ", ".join(STRONG_HASHES) + ) + ) + parser.values.hashes.setdefault(algo, []).append(digest) + + +hash: Callable[..., Option] = partial( + Option, + "--hash", + # Hash values eventually end up in InstallRequirement.hashes due to + # __dict__ copying in process_line(). + dest="hashes", + action="callback", + callback=_handle_merge_hash, + type="string", + help="Verify that the package's archive matches this " + "hash before installing. Example: --hash=sha256:abcdef...", +) + + +require_hashes: Callable[..., Option] = partial( + Option, + "--require-hashes", + dest="require_hashes", + action="store_true", + default=False, + help="Require a hash to check each requirement against, for " + "repeatable installs. This option is implied when any package in a " + "requirements file has a --hash option.", +) + + +list_path: Callable[..., Option] = partial( + PipOption, + "--path", + dest="path", + type="path", + action="append", + help="Restrict to the specified installation path for listing " + "packages (can be used multiple times).", +) + + +def check_list_path_option(options: Values) -> None: + if options.path and (options.user or options.local): + raise CommandError("Cannot combine '--path' with '--user' or '--local'") + + +list_exclude: Callable[..., Option] = partial( + PipOption, + "--exclude", + dest="excludes", + action="append", + metavar="package", + type="package_name", + help="Exclude specified package from the output", +) + + +no_python_version_warning: Callable[..., Option] = partial( + Option, + "--no-python-version-warning", + dest="no_python_version_warning", + action="store_true", + default=False, + help="Silence deprecation warnings for upcoming unsupported Pythons.", +) + + +# Features that are now always on. A warning is printed if they are used. +ALWAYS_ENABLED_FEATURES = [ + "truststore", # always on since 24.2 + "no-binary-enable-wheel-cache", # always on since 23.1 +] + +use_new_feature: Callable[..., Option] = partial( + Option, + "--use-feature", + dest="features_enabled", + metavar="feature", + action="append", + default=[], + choices=[ + "fast-deps", + ] + + ALWAYS_ENABLED_FEATURES, + help="Enable new functionality, that may be backward incompatible.", +) + +use_deprecated_feature: Callable[..., Option] = partial( + Option, + "--use-deprecated", + dest="deprecated_features_enabled", + metavar="feature", + action="append", + default=[], + choices=[ + "legacy-resolver", + "legacy-certs", + ], + help=("Enable deprecated functionality, that will be removed in the future."), +) + + +########## +# groups # +########## + +general_group: Dict[str, Any] = { + "name": "General Options", + "options": [ + help_, + debug_mode, + isolated_mode, + require_virtualenv, + python, + verbose, + version, + quiet, + log, + no_input, + keyring_provider, + proxy, + retries, + timeout, + exists_action, + trusted_host, + cert, + client_cert, + cache_dir, + no_cache, + disable_pip_version_check, + no_color, + no_python_version_warning, + use_new_feature, + use_deprecated_feature, + ], +} + +index_group: Dict[str, Any] = { + "name": "Package Index Options", + "options": [ + index_url, + extra_index_url, + no_index, + find_links, + ], +} diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/command_context.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/command_context.py new file mode 100644 index 000000000..139995ac3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/command_context.py @@ -0,0 +1,27 @@ +from contextlib import ExitStack, contextmanager +from typing import ContextManager, Generator, TypeVar + +_T = TypeVar("_T", covariant=True) + + +class CommandContextMixIn: + def __init__(self) -> None: + super().__init__() + self._in_main_context = False + self._main_context = ExitStack() + + @contextmanager + def main_context(self) -> Generator[None, None, None]: + assert not self._in_main_context + + self._in_main_context = True + try: + with self._main_context: + yield + finally: + self._in_main_context = False + + def enter_context(self, context_provider: ContextManager[_T]) -> _T: + assert self._in_main_context + + return self._main_context.enter_context(context_provider) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/index_command.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/index_command.py new file mode 100644 index 000000000..295108ed6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/index_command.py @@ -0,0 +1,171 @@ +""" +Contains command classes which may interact with an index / the network. + +Unlike its sister module, req_command, this module still uses lazy imports +so commands which don't always hit the network (e.g. list w/o --outdated or +--uptodate) don't need waste time importing PipSession and friends. +""" + +import logging +import os +import sys +from optparse import Values +from typing import TYPE_CHECKING, List, Optional + +from pip._vendor import certifi + +from pip._internal.cli.base_command import Command +from pip._internal.cli.command_context import CommandContextMixIn + +if TYPE_CHECKING: + from ssl import SSLContext + + from pip._internal.network.session import PipSession + +logger = logging.getLogger(__name__) + + +def _create_truststore_ssl_context() -> Optional["SSLContext"]: + if sys.version_info < (3, 10): + logger.debug("Disabling truststore because Python version isn't 3.10+") + return None + + try: + import ssl + except ImportError: + logger.warning("Disabling truststore since ssl support is missing") + return None + + try: + from pip._vendor import truststore + except ImportError: + logger.warning("Disabling truststore because platform isn't supported") + return None + + ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.load_verify_locations(certifi.where()) + return ctx + + +class SessionCommandMixin(CommandContextMixIn): + """ + A class mixin for command classes needing _build_session(). + """ + + def __init__(self) -> None: + super().__init__() + self._session: Optional[PipSession] = None + + @classmethod + def _get_index_urls(cls, options: Values) -> Optional[List[str]]: + """Return a list of index urls from user-provided options.""" + index_urls = [] + if not getattr(options, "no_index", False): + url = getattr(options, "index_url", None) + if url: + index_urls.append(url) + urls = getattr(options, "extra_index_urls", None) + if urls: + index_urls.extend(urls) + # Return None rather than an empty list + return index_urls or None + + def get_default_session(self, options: Values) -> "PipSession": + """Get a default-managed session.""" + if self._session is None: + self._session = self.enter_context(self._build_session(options)) + # there's no type annotation on requests.Session, so it's + # automatically ContextManager[Any] and self._session becomes Any, + # then https://github.com/python/mypy/issues/7696 kicks in + assert self._session is not None + return self._session + + def _build_session( + self, + options: Values, + retries: Optional[int] = None, + timeout: Optional[int] = None, + ) -> "PipSession": + from pip._internal.network.session import PipSession + + cache_dir = options.cache_dir + assert not cache_dir or os.path.isabs(cache_dir) + + if "legacy-certs" not in options.deprecated_features_enabled: + ssl_context = _create_truststore_ssl_context() + else: + ssl_context = None + + session = PipSession( + cache=os.path.join(cache_dir, "http-v2") if cache_dir else None, + retries=retries if retries is not None else options.retries, + trusted_hosts=options.trusted_hosts, + index_urls=self._get_index_urls(options), + ssl_context=ssl_context, + ) + + # Handle custom ca-bundles from the user + if options.cert: + session.verify = options.cert + + # Handle SSL client certificate + if options.client_cert: + session.cert = options.client_cert + + # Handle timeouts + if options.timeout or timeout: + session.timeout = timeout if timeout is not None else options.timeout + + # Handle configured proxies + if options.proxy: + session.proxies = { + "http": options.proxy, + "https": options.proxy, + } + session.trust_env = False + session.pip_proxy = options.proxy + + # Determine if we can prompt the user for authentication or not + session.auth.prompting = not options.no_input + session.auth.keyring_provider = options.keyring_provider + + return session + + +def _pip_self_version_check(session: "PipSession", options: Values) -> None: + from pip._internal.self_outdated_check import pip_self_version_check as check + + check(session, options) + + +class IndexGroupCommand(Command, SessionCommandMixin): + """ + Abstract base class for commands with the index_group options. + + This also corresponds to the commands that permit the pip version check. + """ + + def handle_pip_version_check(self, options: Values) -> None: + """ + Do the pip version check if not disabled. + + This overrides the default behavior of not doing the check. + """ + # Make sure the index_group options are present. + assert hasattr(options, "no_index") + + if options.disable_pip_version_check or options.no_index: + return + + try: + # Otherwise, check if we're using the latest version of pip available. + session = self._build_session( + options, + retries=0, + timeout=min(5, options.timeout), + ) + with session: + _pip_self_version_check(session, options) + except Exception: + logger.warning("There was an error checking the latest version of pip.") + logger.debug("See below for error", exc_info=True) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/main.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/main.py new file mode 100644 index 000000000..563ac79c9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/main.py @@ -0,0 +1,80 @@ +"""Primary application entrypoint. +""" + +import locale +import logging +import os +import sys +import warnings +from typing import List, Optional + +from pip._internal.cli.autocompletion import autocomplete +from pip._internal.cli.main_parser import parse_command +from pip._internal.commands import create_command +from pip._internal.exceptions import PipError +from pip._internal.utils import deprecation + +logger = logging.getLogger(__name__) + + +# Do not import and use main() directly! Using it directly is actively +# discouraged by pip's maintainers. The name, location and behavior of +# this function is subject to change, so calling it directly is not +# portable across different pip versions. + +# In addition, running pip in-process is unsupported and unsafe. This is +# elaborated in detail at +# https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program. +# That document also provides suggestions that should work for nearly +# all users that are considering importing and using main() directly. + +# However, we know that certain users will still want to invoke pip +# in-process. If you understand and accept the implications of using pip +# in an unsupported manner, the best approach is to use runpy to avoid +# depending on the exact location of this entry point. + +# The following example shows how to use runpy to invoke pip in that +# case: +# +# sys.argv = ["pip", your, args, here] +# runpy.run_module("pip", run_name="__main__") +# +# Note that this will exit the process after running, unlike a direct +# call to main. As it is not safe to do any processing after calling +# main, this should not be an issue in practice. + + +def main(args: Optional[List[str]] = None) -> int: + if args is None: + args = sys.argv[1:] + + # Suppress the pkg_resources deprecation warning + # Note - we use a module of .*pkg_resources to cover + # the normal case (pip._vendor.pkg_resources) and the + # devendored case (a bare pkg_resources) + warnings.filterwarnings( + action="ignore", category=DeprecationWarning, module=".*pkg_resources" + ) + + # Configure our deprecation warnings to be sent through loggers + deprecation.install_warning_logger() + + autocomplete() + + try: + cmd_name, cmd_args = parse_command(args) + except PipError as exc: + sys.stderr.write(f"ERROR: {exc}") + sys.stderr.write(os.linesep) + sys.exit(1) + + # Needed for locale.getpreferredencoding(False) to work + # in pip._internal.utils.encoding.auto_decode + try: + locale.setlocale(locale.LC_ALL, "") + except locale.Error as e: + # setlocale can apparently crash if locale are uninitialized + logger.debug("Ignoring error %s when setting locale", e) + command = create_command(cmd_name, isolated=("--isolated" in cmd_args)) + + return command.main(cmd_args) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/main_parser.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/main_parser.py new file mode 100644 index 000000000..5ade356b9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/main_parser.py @@ -0,0 +1,134 @@ +"""A single place for constructing and exposing the main parser +""" + +import os +import subprocess +import sys +from typing import List, Optional, Tuple + +from pip._internal.build_env import get_runnable_pip +from pip._internal.cli import cmdoptions +from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter +from pip._internal.commands import commands_dict, get_similar_commands +from pip._internal.exceptions import CommandError +from pip._internal.utils.misc import get_pip_version, get_prog + +__all__ = ["create_main_parser", "parse_command"] + + +def create_main_parser() -> ConfigOptionParser: + """Creates and returns the main parser for pip's CLI""" + + parser = ConfigOptionParser( + usage="\n%prog [options]", + add_help_option=False, + formatter=UpdatingDefaultsHelpFormatter(), + name="global", + prog=get_prog(), + ) + parser.disable_interspersed_args() + + parser.version = get_pip_version() + + # add the general options + gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser) + parser.add_option_group(gen_opts) + + # so the help formatter knows + parser.main = True # type: ignore + + # create command listing for description + description = [""] + [ + f"{name:27} {command_info.summary}" + for name, command_info in commands_dict.items() + ] + parser.description = "\n".join(description) + + return parser + + +def identify_python_interpreter(python: str) -> Optional[str]: + # If the named file exists, use it. + # If it's a directory, assume it's a virtual environment and + # look for the environment's Python executable. + if os.path.exists(python): + if os.path.isdir(python): + # bin/python for Unix, Scripts/python.exe for Windows + # Try both in case of odd cases like cygwin. + for exe in ("bin/python", "Scripts/python.exe"): + py = os.path.join(python, exe) + if os.path.exists(py): + return py + else: + return python + + # Could not find the interpreter specified + return None + + +def parse_command(args: List[str]) -> Tuple[str, List[str]]: + parser = create_main_parser() + + # Note: parser calls disable_interspersed_args(), so the result of this + # call is to split the initial args into the general options before the + # subcommand and everything else. + # For example: + # args: ['--timeout=5', 'install', '--user', 'INITools'] + # general_options: ['--timeout==5'] + # args_else: ['install', '--user', 'INITools'] + general_options, args_else = parser.parse_args(args) + + # --python + if general_options.python and "_PIP_RUNNING_IN_SUBPROCESS" not in os.environ: + # Re-invoke pip using the specified Python interpreter + interpreter = identify_python_interpreter(general_options.python) + if interpreter is None: + raise CommandError( + f"Could not locate Python interpreter {general_options.python}" + ) + + pip_cmd = [ + interpreter, + get_runnable_pip(), + ] + pip_cmd.extend(args) + + # Set a flag so the child doesn't re-invoke itself, causing + # an infinite loop. + os.environ["_PIP_RUNNING_IN_SUBPROCESS"] = "1" + returncode = 0 + try: + proc = subprocess.run(pip_cmd) + returncode = proc.returncode + except (subprocess.SubprocessError, OSError) as exc: + raise CommandError(f"Failed to run pip under {interpreter}: {exc}") + sys.exit(returncode) + + # --version + if general_options.version: + sys.stdout.write(parser.version) + sys.stdout.write(os.linesep) + sys.exit() + + # pip || pip help -> print_help() + if not args_else or (args_else[0] == "help" and len(args_else) == 1): + parser.print_help() + sys.exit() + + # the subcommand name + cmd_name = args_else[0] + + if cmd_name not in commands_dict: + guess = get_similar_commands(cmd_name) + + msg = [f'unknown command "{cmd_name}"'] + if guess: + msg.append(f'maybe you meant "{guess}"') + + raise CommandError(" - ".join(msg)) + + # all the args without the subcommand + cmd_args = args[:] + cmd_args.remove(cmd_name) + + return cmd_name, cmd_args diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/parser.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/parser.py new file mode 100644 index 000000000..bc4aca032 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/parser.py @@ -0,0 +1,294 @@ +"""Base option parser setup""" + +import logging +import optparse +import shutil +import sys +import textwrap +from contextlib import suppress +from typing import Any, Dict, Generator, List, NoReturn, Optional, Tuple + +from pip._internal.cli.status_codes import UNKNOWN_ERROR +from pip._internal.configuration import Configuration, ConfigurationError +from pip._internal.utils.misc import redact_auth_from_url, strtobool + +logger = logging.getLogger(__name__) + + +class PrettyHelpFormatter(optparse.IndentedHelpFormatter): + """A prettier/less verbose help formatter for optparse.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + # help position must be aligned with __init__.parseopts.description + kwargs["max_help_position"] = 30 + kwargs["indent_increment"] = 1 + kwargs["width"] = shutil.get_terminal_size()[0] - 2 + super().__init__(*args, **kwargs) + + def format_option_strings(self, option: optparse.Option) -> str: + return self._format_option_strings(option) + + def _format_option_strings( + self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", " + ) -> str: + """ + Return a comma-separated list of option strings and metavars. + + :param option: tuple of (short opt, long opt), e.g: ('-f', '--format') + :param mvarfmt: metavar format string + :param optsep: separator + """ + opts = [] + + if option._short_opts: + opts.append(option._short_opts[0]) + if option._long_opts: + opts.append(option._long_opts[0]) + if len(opts) > 1: + opts.insert(1, optsep) + + if option.takes_value(): + assert option.dest is not None + metavar = option.metavar or option.dest.lower() + opts.append(mvarfmt.format(metavar.lower())) + + return "".join(opts) + + def format_heading(self, heading: str) -> str: + if heading == "Options": + return "" + return heading + ":\n" + + def format_usage(self, usage: str) -> str: + """ + Ensure there is only one newline between usage and the first heading + if there is no description. + """ + msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " ")) + return msg + + def format_description(self, description: Optional[str]) -> str: + # leave full control over description to us + if description: + if hasattr(self.parser, "main"): + label = "Commands" + else: + label = "Description" + # some doc strings have initial newlines, some don't + description = description.lstrip("\n") + # some doc strings have final newlines and spaces, some don't + description = description.rstrip() + # dedent, then reindent + description = self.indent_lines(textwrap.dedent(description), " ") + description = f"{label}:\n{description}\n" + return description + else: + return "" + + def format_epilog(self, epilog: Optional[str]) -> str: + # leave full control over epilog to us + if epilog: + return epilog + else: + return "" + + def indent_lines(self, text: str, indent: str) -> str: + new_lines = [indent + line for line in text.split("\n")] + return "\n".join(new_lines) + + +class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter): + """Custom help formatter for use in ConfigOptionParser. + + This is updates the defaults before expanding them, allowing + them to show up correctly in the help listing. + + Also redact auth from url type options + """ + + def expand_default(self, option: optparse.Option) -> str: + default_values = None + if self.parser is not None: + assert isinstance(self.parser, ConfigOptionParser) + self.parser._update_defaults(self.parser.defaults) + assert option.dest is not None + default_values = self.parser.defaults.get(option.dest) + help_text = super().expand_default(option) + + if default_values and option.metavar == "URL": + if isinstance(default_values, str): + default_values = [default_values] + + # If its not a list, we should abort and just return the help text + if not isinstance(default_values, list): + default_values = [] + + for val in default_values: + help_text = help_text.replace(val, redact_auth_from_url(val)) + + return help_text + + +class CustomOptionParser(optparse.OptionParser): + def insert_option_group( + self, idx: int, *args: Any, **kwargs: Any + ) -> optparse.OptionGroup: + """Insert an OptionGroup at a given position.""" + group = self.add_option_group(*args, **kwargs) + + self.option_groups.pop() + self.option_groups.insert(idx, group) + + return group + + @property + def option_list_all(self) -> List[optparse.Option]: + """Get a list of all options, including those in option groups.""" + res = self.option_list[:] + for i in self.option_groups: + res.extend(i.option_list) + + return res + + +class ConfigOptionParser(CustomOptionParser): + """Custom option parser which updates its defaults by checking the + configuration files and environmental variables""" + + def __init__( + self, + *args: Any, + name: str, + isolated: bool = False, + **kwargs: Any, + ) -> None: + self.name = name + self.config = Configuration(isolated) + + assert self.name + super().__init__(*args, **kwargs) + + def check_default(self, option: optparse.Option, key: str, val: Any) -> Any: + try: + return option.check_value(key, val) + except optparse.OptionValueError as exc: + print(f"An error occurred during configuration: {exc}") + sys.exit(3) + + def _get_ordered_configuration_items( + self, + ) -> Generator[Tuple[str, Any], None, None]: + # Configuration gives keys in an unordered manner. Order them. + override_order = ["global", self.name, ":env:"] + + # Pool the options into different groups + section_items: Dict[str, List[Tuple[str, Any]]] = { + name: [] for name in override_order + } + for section_key, val in self.config.items(): + # ignore empty values + if not val: + logger.debug( + "Ignoring configuration key '%s' as it's value is empty.", + section_key, + ) + continue + + section, key = section_key.split(".", 1) + if section in override_order: + section_items[section].append((key, val)) + + # Yield each group in their override order + for section in override_order: + for key, val in section_items[section]: + yield key, val + + def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: + """Updates the given defaults with values from the config files and + the environ. Does a little special handling for certain types of + options (lists).""" + + # Accumulate complex default state. + self.values = optparse.Values(self.defaults) + late_eval = set() + # Then set the options with those values + for key, val in self._get_ordered_configuration_items(): + # '--' because configuration supports only long names + option = self.get_option("--" + key) + + # Ignore options not present in this parser. E.g. non-globals put + # in [global] by users that want them to apply to all applicable + # commands. + if option is None: + continue + + assert option.dest is not None + + if option.action in ("store_true", "store_false"): + try: + val = strtobool(val) + except ValueError: + self.error( + f"{val} is not a valid value for {key} option, " + "please specify a boolean value like yes/no, " + "true/false or 1/0 instead." + ) + elif option.action == "count": + with suppress(ValueError): + val = strtobool(val) + with suppress(ValueError): + val = int(val) + if not isinstance(val, int) or val < 0: + self.error( + f"{val} is not a valid value for {key} option, " + "please instead specify either a non-negative integer " + "or a boolean value like yes/no or false/true " + "which is equivalent to 1/0." + ) + elif option.action == "append": + val = val.split() + val = [self.check_default(option, key, v) for v in val] + elif option.action == "callback": + assert option.callback is not None + late_eval.add(option.dest) + opt_str = option.get_opt_string() + val = option.convert_value(opt_str, val) + # From take_action + args = option.callback_args or () + kwargs = option.callback_kwargs or {} + option.callback(option, opt_str, val, self, *args, **kwargs) + else: + val = self.check_default(option, key, val) + + defaults[option.dest] = val + + for key in late_eval: + defaults[key] = getattr(self.values, key) + self.values = None + return defaults + + def get_default_values(self) -> optparse.Values: + """Overriding to make updating the defaults after instantiation of + the option parser possible, _update_defaults() does the dirty work.""" + if not self.process_default_values: + # Old, pre-Optik 1.5 behaviour. + return optparse.Values(self.defaults) + + # Load the configuration, or error out in case of an error + try: + self.config.load() + except ConfigurationError as err: + self.exit(UNKNOWN_ERROR, str(err)) + + defaults = self._update_defaults(self.defaults.copy()) # ours + for option in self._get_all_options(): + assert option.dest is not None + default = defaults.get(option.dest) + if isinstance(default, str): + opt_str = option.get_opt_string() + defaults[option.dest] = option.check_value(opt_str, default) + return optparse.Values(defaults) + + def error(self, msg: str) -> NoReturn: + self.print_usage(sys.stderr) + self.exit(UNKNOWN_ERROR, f"{msg}\n") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/progress_bars.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/progress_bars.py new file mode 100644 index 000000000..3d9dde8ed --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/progress_bars.py @@ -0,0 +1,94 @@ +import functools +import sys +from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple + +from pip._vendor.rich.progress import ( + BarColumn, + DownloadColumn, + FileSizeColumn, + Progress, + ProgressColumn, + SpinnerColumn, + TextColumn, + TimeElapsedColumn, + TimeRemainingColumn, + TransferSpeedColumn, +) + +from pip._internal.cli.spinners import RateLimiter +from pip._internal.utils.logging import get_indentation + +DownloadProgressRenderer = Callable[[Iterable[bytes]], Iterator[bytes]] + + +def _rich_progress_bar( + iterable: Iterable[bytes], + *, + bar_type: str, + size: Optional[int], +) -> Generator[bytes, None, None]: + assert bar_type == "on", "This should only be used in the default mode." + + if not size: + total = float("inf") + columns: Tuple[ProgressColumn, ...] = ( + TextColumn("[progress.description]{task.description}"), + SpinnerColumn("line", speed=1.5), + FileSizeColumn(), + TransferSpeedColumn(), + TimeElapsedColumn(), + ) + else: + total = size + columns = ( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + DownloadColumn(), + TransferSpeedColumn(), + TextColumn("eta"), + TimeRemainingColumn(), + ) + + progress = Progress(*columns, refresh_per_second=5) + task_id = progress.add_task(" " * (get_indentation() + 2), total=total) + with progress: + for chunk in iterable: + yield chunk + progress.update(task_id, advance=len(chunk)) + + +def _raw_progress_bar( + iterable: Iterable[bytes], + *, + size: Optional[int], +) -> Generator[bytes, None, None]: + def write_progress(current: int, total: int) -> None: + sys.stdout.write(f"Progress {current} of {total}\n") + sys.stdout.flush() + + current = 0 + total = size or 0 + rate_limiter = RateLimiter(0.25) + + write_progress(current, total) + for chunk in iterable: + current += len(chunk) + if rate_limiter.ready() or current == total: + write_progress(current, total) + rate_limiter.reset() + yield chunk + + +def get_download_progress_renderer( + *, bar_type: str, size: Optional[int] = None +) -> DownloadProgressRenderer: + """Get an object that can be used to render the download progress. + + Returns a callable, that takes an iterable to "wrap". + """ + if bar_type == "on": + return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size) + elif bar_type == "raw": + return functools.partial(_raw_progress_bar, size=size) + else: + return iter # no-op, when passed an iterator diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/req_command.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/req_command.py new file mode 100644 index 000000000..92900f94f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/req_command.py @@ -0,0 +1,329 @@ +"""Contains the RequirementCommand base class. + +This class is in a separate module so the commands that do not always +need PackageFinder capability don't unnecessarily import the +PackageFinder machinery and all its vendored dependencies, etc. +""" + +import logging +from functools import partial +from optparse import Values +from typing import Any, List, Optional, Tuple + +from pip._internal.cache import WheelCache +from pip._internal.cli import cmdoptions +from pip._internal.cli.index_command import IndexGroupCommand +from pip._internal.cli.index_command import SessionCommandMixin as SessionCommandMixin +from pip._internal.exceptions import CommandError, PreviousBuildDirError +from pip._internal.index.collector import LinkCollector +from pip._internal.index.package_finder import PackageFinder +from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.models.target_python import TargetPython +from pip._internal.network.session import PipSession +from pip._internal.operations.build.build_tracker import BuildTracker +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req.constructors import ( + install_req_from_editable, + install_req_from_line, + install_req_from_parsed_requirement, + install_req_from_req_string, +) +from pip._internal.req.req_file import parse_requirements +from pip._internal.req.req_install import InstallRequirement +from pip._internal.resolution.base import BaseResolver +from pip._internal.utils.temp_dir import ( + TempDirectory, + TempDirectoryTypeRegistry, + tempdir_kinds, +) + +logger = logging.getLogger(__name__) + + +KEEPABLE_TEMPDIR_TYPES = [ + tempdir_kinds.BUILD_ENV, + tempdir_kinds.EPHEM_WHEEL_CACHE, + tempdir_kinds.REQ_BUILD, +] + + +def with_cleanup(func: Any) -> Any: + """Decorator for common logic related to managing temporary + directories. + """ + + def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None: + for t in KEEPABLE_TEMPDIR_TYPES: + registry.set_delete(t, False) + + def wrapper( + self: RequirementCommand, options: Values, args: List[Any] + ) -> Optional[int]: + assert self.tempdir_registry is not None + if options.no_clean: + configure_tempdir_registry(self.tempdir_registry) + + try: + return func(self, options, args) + except PreviousBuildDirError: + # This kind of conflict can occur when the user passes an explicit + # build directory with a pre-existing folder. In that case we do + # not want to accidentally remove it. + configure_tempdir_registry(self.tempdir_registry) + raise + + return wrapper + + +class RequirementCommand(IndexGroupCommand): + def __init__(self, *args: Any, **kw: Any) -> None: + super().__init__(*args, **kw) + + self.cmd_opts.add_option(cmdoptions.no_clean()) + + @staticmethod + def determine_resolver_variant(options: Values) -> str: + """Determines which resolver should be used, based on the given options.""" + if "legacy-resolver" in options.deprecated_features_enabled: + return "legacy" + + return "resolvelib" + + @classmethod + def make_requirement_preparer( + cls, + temp_build_dir: TempDirectory, + options: Values, + build_tracker: BuildTracker, + session: PipSession, + finder: PackageFinder, + use_user_site: bool, + download_dir: Optional[str] = None, + verbosity: int = 0, + ) -> RequirementPreparer: + """ + Create a RequirementPreparer instance for the given parameters. + """ + temp_build_dir_path = temp_build_dir.path + assert temp_build_dir_path is not None + legacy_resolver = False + + resolver_variant = cls.determine_resolver_variant(options) + if resolver_variant == "resolvelib": + lazy_wheel = "fast-deps" in options.features_enabled + if lazy_wheel: + logger.warning( + "pip is using lazily downloaded wheels using HTTP " + "range requests to obtain dependency information. " + "This experimental feature is enabled through " + "--use-feature=fast-deps and it is not ready for " + "production." + ) + else: + legacy_resolver = True + lazy_wheel = False + if "fast-deps" in options.features_enabled: + logger.warning( + "fast-deps has no effect when used with the legacy resolver." + ) + + return RequirementPreparer( + build_dir=temp_build_dir_path, + src_dir=options.src_dir, + download_dir=download_dir, + build_isolation=options.build_isolation, + check_build_deps=options.check_build_deps, + build_tracker=build_tracker, + session=session, + progress_bar=options.progress_bar, + finder=finder, + require_hashes=options.require_hashes, + use_user_site=use_user_site, + lazy_wheel=lazy_wheel, + verbosity=verbosity, + legacy_resolver=legacy_resolver, + ) + + @classmethod + def make_resolver( + cls, + preparer: RequirementPreparer, + finder: PackageFinder, + options: Values, + wheel_cache: Optional[WheelCache] = None, + use_user_site: bool = False, + ignore_installed: bool = True, + ignore_requires_python: bool = False, + force_reinstall: bool = False, + upgrade_strategy: str = "to-satisfy-only", + use_pep517: Optional[bool] = None, + py_version_info: Optional[Tuple[int, ...]] = None, + ) -> BaseResolver: + """ + Create a Resolver instance for the given parameters. + """ + make_install_req = partial( + install_req_from_req_string, + isolated=options.isolated_mode, + use_pep517=use_pep517, + ) + resolver_variant = cls.determine_resolver_variant(options) + # The long import name and duplicated invocation is needed to convince + # Mypy into correctly typechecking. Otherwise it would complain the + # "Resolver" class being redefined. + if resolver_variant == "resolvelib": + import pip._internal.resolution.resolvelib.resolver + + return pip._internal.resolution.resolvelib.resolver.Resolver( + preparer=preparer, + finder=finder, + wheel_cache=wheel_cache, + make_install_req=make_install_req, + use_user_site=use_user_site, + ignore_dependencies=options.ignore_dependencies, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + force_reinstall=force_reinstall, + upgrade_strategy=upgrade_strategy, + py_version_info=py_version_info, + ) + import pip._internal.resolution.legacy.resolver + + return pip._internal.resolution.legacy.resolver.Resolver( + preparer=preparer, + finder=finder, + wheel_cache=wheel_cache, + make_install_req=make_install_req, + use_user_site=use_user_site, + ignore_dependencies=options.ignore_dependencies, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + force_reinstall=force_reinstall, + upgrade_strategy=upgrade_strategy, + py_version_info=py_version_info, + ) + + def get_requirements( + self, + args: List[str], + options: Values, + finder: PackageFinder, + session: PipSession, + ) -> List[InstallRequirement]: + """ + Parse command-line arguments into the corresponding requirements. + """ + requirements: List[InstallRequirement] = [] + for filename in options.constraints: + for parsed_req in parse_requirements( + filename, + constraint=True, + finder=finder, + options=options, + session=session, + ): + req_to_add = install_req_from_parsed_requirement( + parsed_req, + isolated=options.isolated_mode, + user_supplied=False, + ) + requirements.append(req_to_add) + + for req in args: + req_to_add = install_req_from_line( + req, + comes_from=None, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + user_supplied=True, + config_settings=getattr(options, "config_settings", None), + ) + requirements.append(req_to_add) + + for req in options.editables: + req_to_add = install_req_from_editable( + req, + user_supplied=True, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + config_settings=getattr(options, "config_settings", None), + ) + requirements.append(req_to_add) + + # NOTE: options.require_hashes may be set if --require-hashes is True + for filename in options.requirements: + for parsed_req in parse_requirements( + filename, finder=finder, options=options, session=session + ): + req_to_add = install_req_from_parsed_requirement( + parsed_req, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + user_supplied=True, + config_settings=( + parsed_req.options.get("config_settings") + if parsed_req.options + else None + ), + ) + requirements.append(req_to_add) + + # If any requirement has hash options, enable hash checking. + if any(req.has_hash_options for req in requirements): + options.require_hashes = True + + if not (args or options.editables or options.requirements): + opts = {"name": self.name} + if options.find_links: + raise CommandError( + "You must give at least one requirement to {name} " + '(maybe you meant "pip {name} {links}"?)'.format( + **dict(opts, links=" ".join(options.find_links)) + ) + ) + else: + raise CommandError( + "You must give at least one requirement to {name} " + '(see "pip help {name}")'.format(**opts) + ) + + return requirements + + @staticmethod + def trace_basic_info(finder: PackageFinder) -> None: + """ + Trace basic information about the provided objects. + """ + # Display where finder is looking for packages + search_scope = finder.search_scope + locations = search_scope.get_formatted_locations() + if locations: + logger.info(locations) + + def _build_package_finder( + self, + options: Values, + session: PipSession, + target_python: Optional[TargetPython] = None, + ignore_requires_python: Optional[bool] = None, + ) -> PackageFinder: + """ + Create a package finder appropriate to this requirement command. + + :param ignore_requires_python: Whether to ignore incompatible + "Requires-Python" values in links. Defaults to False. + """ + link_collector = LinkCollector.create(session, options=options) + selection_prefs = SelectionPreferences( + allow_yanked=True, + format_control=options.format_control, + allow_all_prereleases=options.pre, + prefer_binary=options.prefer_binary, + ignore_requires_python=ignore_requires_python, + ) + + return PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + target_python=target_python, + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/spinners.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/spinners.py new file mode 100644 index 000000000..cf2b976f3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/spinners.py @@ -0,0 +1,159 @@ +import contextlib +import itertools +import logging +import sys +import time +from typing import IO, Generator, Optional + +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.logging import get_indentation + +logger = logging.getLogger(__name__) + + +class SpinnerInterface: + def spin(self) -> None: + raise NotImplementedError() + + def finish(self, final_status: str) -> None: + raise NotImplementedError() + + +class InteractiveSpinner(SpinnerInterface): + def __init__( + self, + message: str, + file: Optional[IO[str]] = None, + spin_chars: str = "-\\|/", + # Empirically, 8 updates/second looks nice + min_update_interval_seconds: float = 0.125, + ): + self._message = message + if file is None: + file = sys.stdout + self._file = file + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._finished = False + + self._spin_cycle = itertools.cycle(spin_chars) + + self._file.write(" " * get_indentation() + self._message + " ... ") + self._width = 0 + + def _write(self, status: str) -> None: + assert not self._finished + # Erase what we wrote before by backspacing to the beginning, writing + # spaces to overwrite the old text, and then backspacing again + backup = "\b" * self._width + self._file.write(backup + " " * self._width + backup) + # Now we have a blank slate to add our status + self._file.write(status) + self._width = len(status) + self._file.flush() + self._rate_limiter.reset() + + def spin(self) -> None: + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._write(next(self._spin_cycle)) + + def finish(self, final_status: str) -> None: + if self._finished: + return + self._write(final_status) + self._file.write("\n") + self._file.flush() + self._finished = True + + +# Used for dumb terminals, non-interactive installs (no tty), etc. +# We still print updates occasionally (once every 60 seconds by default) to +# act as a keep-alive for systems like Travis-CI that take lack-of-output as +# an indication that a task has frozen. +class NonInteractiveSpinner(SpinnerInterface): + def __init__(self, message: str, min_update_interval_seconds: float = 60.0) -> None: + self._message = message + self._finished = False + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._update("started") + + def _update(self, status: str) -> None: + assert not self._finished + self._rate_limiter.reset() + logger.info("%s: %s", self._message, status) + + def spin(self) -> None: + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._update("still running...") + + def finish(self, final_status: str) -> None: + if self._finished: + return + self._update(f"finished with status '{final_status}'") + self._finished = True + + +class RateLimiter: + def __init__(self, min_update_interval_seconds: float) -> None: + self._min_update_interval_seconds = min_update_interval_seconds + self._last_update: float = 0 + + def ready(self) -> bool: + now = time.time() + delta = now - self._last_update + return delta >= self._min_update_interval_seconds + + def reset(self) -> None: + self._last_update = time.time() + + +@contextlib.contextmanager +def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]: + # Interactive spinner goes directly to sys.stdout rather than being routed + # through the logging system, but it acts like it has level INFO, + # i.e. it's only displayed if we're at level INFO or better. + # Non-interactive spinner goes through the logging system, so it is always + # in sync with logging configuration. + if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO: + spinner: SpinnerInterface = InteractiveSpinner(message) + else: + spinner = NonInteractiveSpinner(message) + try: + with hidden_cursor(sys.stdout): + yield spinner + except KeyboardInterrupt: + spinner.finish("canceled") + raise + except Exception: + spinner.finish("error") + raise + else: + spinner.finish("done") + + +HIDE_CURSOR = "\x1b[?25l" +SHOW_CURSOR = "\x1b[?25h" + + +@contextlib.contextmanager +def hidden_cursor(file: IO[str]) -> Generator[None, None, None]: + # The Windows terminal does not support the hide/show cursor ANSI codes, + # even via colorama. So don't even try. + if WINDOWS: + yield + # We don't want to clutter the output with control characters if we're + # writing to a file, or if the user is running with --quiet. + # See https://github.com/pypa/pip/issues/3418 + elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO: + yield + else: + file.write(HIDE_CURSOR) + try: + yield + finally: + file.write(SHOW_CURSOR) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/cli/status_codes.py b/venv/lib/python3.8/site-packages/pip/_internal/cli/status_codes.py new file mode 100644 index 000000000..5e29502cd --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/cli/status_codes.py @@ -0,0 +1,6 @@ +SUCCESS = 0 +ERROR = 1 +UNKNOWN_ERROR = 2 +VIRTUALENV_NOT_FOUND = 3 +PREVIOUS_BUILD_DIR_ERROR = 4 +NO_MATCHES_FOUND = 23 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/__init__.py new file mode 100644 index 000000000..858a41014 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/__init__.py @@ -0,0 +1,132 @@ +""" +Package containing all pip commands +""" + +import importlib +from collections import namedtuple +from typing import Any, Dict, Optional + +from pip._internal.cli.base_command import Command + +CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary") + +# This dictionary does a bunch of heavy lifting for help output: +# - Enables avoiding additional (costly) imports for presenting `--help`. +# - The ordering matters for help display. +# +# Even though the module path starts with the same "pip._internal.commands" +# prefix, the full path makes testing easier (specifically when modifying +# `commands_dict` in test setup / teardown). +commands_dict: Dict[str, CommandInfo] = { + "install": CommandInfo( + "pip._internal.commands.install", + "InstallCommand", + "Install packages.", + ), + "download": CommandInfo( + "pip._internal.commands.download", + "DownloadCommand", + "Download packages.", + ), + "uninstall": CommandInfo( + "pip._internal.commands.uninstall", + "UninstallCommand", + "Uninstall packages.", + ), + "freeze": CommandInfo( + "pip._internal.commands.freeze", + "FreezeCommand", + "Output installed packages in requirements format.", + ), + "inspect": CommandInfo( + "pip._internal.commands.inspect", + "InspectCommand", + "Inspect the python environment.", + ), + "list": CommandInfo( + "pip._internal.commands.list", + "ListCommand", + "List installed packages.", + ), + "show": CommandInfo( + "pip._internal.commands.show", + "ShowCommand", + "Show information about installed packages.", + ), + "check": CommandInfo( + "pip._internal.commands.check", + "CheckCommand", + "Verify installed packages have compatible dependencies.", + ), + "config": CommandInfo( + "pip._internal.commands.configuration", + "ConfigurationCommand", + "Manage local and global configuration.", + ), + "search": CommandInfo( + "pip._internal.commands.search", + "SearchCommand", + "Search PyPI for packages.", + ), + "cache": CommandInfo( + "pip._internal.commands.cache", + "CacheCommand", + "Inspect and manage pip's wheel cache.", + ), + "index": CommandInfo( + "pip._internal.commands.index", + "IndexCommand", + "Inspect information available from package indexes.", + ), + "wheel": CommandInfo( + "pip._internal.commands.wheel", + "WheelCommand", + "Build wheels from your requirements.", + ), + "hash": CommandInfo( + "pip._internal.commands.hash", + "HashCommand", + "Compute hashes of package archives.", + ), + "completion": CommandInfo( + "pip._internal.commands.completion", + "CompletionCommand", + "A helper command used for command completion.", + ), + "debug": CommandInfo( + "pip._internal.commands.debug", + "DebugCommand", + "Show information useful for debugging.", + ), + "help": CommandInfo( + "pip._internal.commands.help", + "HelpCommand", + "Show help for commands.", + ), +} + + +def create_command(name: str, **kwargs: Any) -> Command: + """ + Create an instance of the Command class with the given name. + """ + module_path, class_name, summary = commands_dict[name] + module = importlib.import_module(module_path) + command_class = getattr(module, class_name) + command = command_class(name=name, summary=summary, **kwargs) + + return command + + +def get_similar_commands(name: str) -> Optional[str]: + """Command name auto-correct.""" + from difflib import get_close_matches + + name = name.lower() + + close_commands = get_close_matches(name, commands_dict.keys()) + + if close_commands: + return close_commands[0] + else: + return None diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4da531f0ad67e61a6bcfedc0b0b1e53525e31f4 GIT binary patch literal 3180 zcmd5;OK;mo5avsitcM*ZZW8CwFwLuSEUKy7yqcy-Yy>r$CIO7Js8E2QI4g-TU)`nb zN{~+Oxz}ENNRB=Ao^yZ2UVG|aC{UoYOVV*#E9Xk!?(A^)+u3hsXZWJgs4Dn-{d?PA zSx}Te#NhC62?r1GVYaF$FBDKZ3aFq3YDdLh542G4=&H~eff1HErO@n{VYyS5x>8UH z=Q?w$LUb^pyr*@lqzV<7gX*4+QVr_RkkULXz%eN;!f`kur6qU|PD<$%oQ7p7oq@A( zPD5!Zr9%N=;aS>rz^U7T8j1!$)vKN*}`-d?KY!;U?UY z(rvf{clUJoY=5y+BPOYnYEN5MNCWQOQ*cxwbMX1T4)@`UpN!5tYRaU(tiXeP4IaWH z)GdIvr8L*aSE|olZ`y(TW%0oiJzb<4BZG=^-n?g)27O+ksA_7he<%1 zI@cdYBW`T?Udk)alGKkQH^52d^=uul<-%svi^o}j0tZ%mVE(nb z41G#M5~a-Q#WZwN`(2bJ#7jGZG9eUOT*${(Iv`duN(XUdk!Z)KaU_84@!1&`2R=)A z?FqI8)M+s|!q(XsY!L7A+7`A2#)=pqMs^&GAIt5=M>1fK&&{yR8xU`s*Vjc?;NJR% zP`@`i!a8f+BQA1+jEJs) zxl%Y@*oBu`7>Z}T#;n}|A!wtRJR8zSihktvP06~@AjG66-M=VsGP!_mJ+uZSNC+)*rZr@Ee}%uiEE&p}%NEUM0Z4b) z=k*QI70_4zb4L)mHw+}WT+qjy{SF?`F605*0`ZhY6!soAklD>eUiR|@S2@U-b8oVq znddDrOJo&p$h2TEaj{DnKzI?&EZVu5hjU3r7B6KHRLcBpNas|Mnu60(c638n#Tz+i z%mJ(|SZEAUD7d-3>(V~sCMD^RMjqDtgI@)u5Pu~+ zy;7&8RG*Y4`kpp5CuR@lObk%>wTb$R_Om)IPs&&owejPsg}-%5T#Obs%JN9$5fL&X zbLLEDRm|G;(}7g?F@z#Z*lJ6FrbbuL6kg6NT+^UUF`o0oB&KQLcX=Zdovb+Wa-XDb zn$l)PuB6w+^xC1y$*XTFCvAzhX1=r_&5;v$Ip^?Kh2F$ze@)thI3#T>JKH`9+8bnt z1aTsge*1~rW$mmOw34-!aB|ltt(0O}CA5ua+G(S$`II&LcANPrX%$tsjW?!!P`2BJ z@7fOP{M2#mWW?t^$)%IO4ACMcKkJNnwTk~GjowCGP8eN9`7QVAs4(zZ$A^jY$WJ^a zRHv$nPJF4oQl`p8ohYgP(s-qyd(qw0o@k&hD_aWa+0!(}XA0j5H(Bm5jayz!DMqtR zML^;A%OW}fS&SKRu*hOqv!~4xFN5FfAr15n&f=vY-X%11r?E{&tf^-nlW*s<)ddyO z16gxREM5{DFh9ghNefEY2;ZPe8F+tD2g!*1w3vk x93$;GbPVAFD$i26m&Mks)sr~_S8#&IDi!N67}9rzy<35-YiLxlmetEiJ6^wJ2U&{ zeg0-XoSDfP_7OzM9v4FD$fYsx$3kwW#}<@Kn20E$Ox&o^H=pXLUOp z&b8;O^QOUP`I$#%b>TgO?INAI^G_TAZSuVkJ;drK@88>BknE zWpkjFCTR2Q3}~mPW-qWsP-my8XW2PW=RnQ!EL#Fc=h-u$%IKNV-q_*;q1PWkM7aRJpGLC78%Y2)1<+N!$%9U-^O6i6Y;hh2 z4VqNR>7nc0jYg_axtdaz1J)9&!TMJGAV{`&I+|!$F&29jJ#9H?MVs+p6g5eMtvCwm z8*w)|nl~IB!gy8#GNFJBA*#2z4C2k8Q*Z2mJsC)>%^6lbK3DK|yvtXETlyKq5f5S! zv@w1-J=hd+`}o98S2U02)x$7o!TjX0(RxSs?p^KFlZ1=t+P!<&bQEmxpdK{Ccq8Bs zJAz9|`@@&(qtDVd(jDIJeX3U>>A%)Ubrksus=l#pY@5tv7PHB# z$ftgX_yf9iU^AcIA6gVkV0+oMoxc}S*7 zmIX`cz*y1=LA@PE&DB6p3obFfS?`8PSqnTBLD^May+Kx{9L8m$GJ`=X)7Xkz4K9_l z#lw!Eos}KsH`=TgcM_>`b*5W1)$7!S1 zjz@sX*rc+pGU?tRmbz$4QN#ibo0b{;JUl69X$$xY4bm`*Ng7A7Q56Al$rW~?G<)vPtTX z5`HY}8zE;EZC1StYaC$;(IK_O;L?k-93*kj;ezbA(z9L(zSag&CFHp6N>PvfSlT%v~F@jK`TY!9*(7_AW1w&6`)=Rs`*5dB-|1%&s51wJLt{Ue%p4T^xsmYv zHSs-gs?5tzGFwT~S>AmWK@tt+X)GY?=dA%~pwu<~yP2kDq-4e)BIN-@G)SvK7&p>f ze~H{__th(vD=(K;wf}#0!k3e566ItT*ShTu zFaU(|{_Wd0HD-~R8+F?$BO1+~JoN^|6NY=iMz<9vDdLhNnPH7o?%zsKCoVPE#>6t; zW+SANX}-YDAS2ZpaW_hi@2H$nb0>&iEq;Se)Ty!2#Fi>f3Qe7v;CyH))tL!K)ai*) zv{3n!_IPCjKB`h}6LvYK3sp!d=`@ua$QM2=D8-2ac|j4~6vgVphB}i%4~ov?WIy2# zlLw*>Ct;i++9+=bYGfi>oui;r&QHgiYxqvJKwBQBdhtX0{#B}eMAe&Ay@g6;0D>j_ zYC1@j@v@2{Uqh7!siIl33MTy#vP}P}_0(~4HlpuSr!a;HOzU5GceZ!_8$uf8or##{ z;sbQV(lPBLG!jI$M*x94j+jpnzbGUl&Rt9s{k|PJ%mEzGv93??3~_DSX6}w9-q+F0 zq1Y4U(02Q}t%JC?CSJnoIvD9hDqg29VM8RQx2dheIq@M%s8ng78$4wPf!MMs(|?Km^sUVaC2tC>ODIlxakB9H2VQDsF4)n=IBS+ zg!tED?4Z#%w+%)!)9^NtTzKZltw)ptI_`M>X? zc0NZcFpc|5dL`Y~E$_fjvZSWN(dxiIfF`kyGt*9>Z2aOmI z6zOy|r_5YAcVyYhih&#Hi`8|r9M@7aDpaC8EF#Q|=M$Vkl(U?(aGii+Lh1#SW ztrv|nOmQ~V%Yl_3K~KogS`$CVU_gr_RB^N?D>XH30CYM?87H75eunA*MUpEK&%Fy1 zb~MK0z;Gv=kl@*f%fFy=vJ=Vx4hdWhhfLkiV!+aHLAW2>Q1_SzN4yDGpy9`%`MLp@ zo$os=lfH*l`JdaM!uq60oRO0{046r9^D;n*tFbr13?Cz@m+hkmEbjn*P#c*2ehe^Pnywd`R2^ z2?m(dag4;FRc9)%8-=ZCM`d=o*objDe1Yas<|t>T;*5?1)l*Si8?s(&%Vu?EV=v*d zHsDCyq4`s`@-k?DK#>xhaZUFFe?}R{&67vZv13Zv)~5^{M7kT4 zfR7zKC)lKDpWx7$!u~!GGng~i;Dw|ku}+otLIU_$`sfr>r&m*qz609dP^1n6W^ZxA z5sy#-b{x?3W1*bHM_KVYilpyCYzXHFNSWO2a2ELIGTKa>5ozo2eE?fC?f5g5VEQbVCB1+9R3s-%%u)CYewHhGH4k z)2Yxnjgba=5o?%rTq(41DthCj@3D>B*eEu;GBCBm?7Y=GH>pJ}!W8M17nNVD(M4RX z2Gmz;$E=IIR0AHY-S5`J!JAC2#^MIZbFF5Cn?tzh{Vq^njV@}G+tn}(xAul0yAdP@ zWdrOf8+SBP-iEX;}08LUzy1?WYCGY zIyX6{o3uiin)~?^-c;!7f_SDA_4N9BZl&Fl4IO#KCdQ=sm?UwMIF)}j)%rCPBjf@q SKx8-tL1wy;(7+iN6#oTGIf^d; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/check.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/check.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbd9f3b48de030896199341522bc31a9ea6a4a05 GIT binary patch literal 1988 zcmZuy&2HO95MKUM5@kEF>m)^5FfGzTMNJk+f%Z@o1)Q`O!v*3d2Q~pgaaR)K<&Srl z4kS<~iX!#Zsg~=Jt|{7}Ms7xCOPkcn?Z|Fvi#oX*xp^n*w7gBb zc`xdH zYhxl}Oh!pfMNEqGjFm-B3NbL`m2^VV)3{2~(_~CyPQ+_bUGTc9N+t-Fw^BwDK{nOm z5i9dJE4WA~B^Y+;t6kPMAn=~D42;TJRJBM|?3>Rz+W|-kiZqNO9cz(+^~l6VWD%P< z#Jzz?2b(_{*uwTRGwNaoGT}<^P{qAE^z5^Tgk__tzYfP=9fwt?=zIE2A} zgGrzTdZf>dxhb^9SZF|7b6e=b5at3sw_YG&FVI3?Xmh8r?xM%R+--~o4*A5HcN$Ak zAl;vLg^RVuZ8}XC>o?E>0C)EF*(Z$+tDj!5u~#-X9~txB+?)5o5_44k>BP^tUzFla@&l$pE@lTygGsVBQkZM)hPCjgWxc$dRxOx)xKv*` zTjR(t3tvn?TG2#|N|w*wIWGMnD^E%BIvC}b`=hcha4_(gs&WPej-*MUI+(g5<|zIq z;E}xnZH(1&2GOWXXFa9joD;@@WuD?(mOPD%Bqz*LPHdP4+tTEOj@XuxCvKY}U6)m$_T^?vYA*onyw5vRJ*D#$*7wC3L_7t^>Pa1F& z(pl~NGh#m2y_$xTGAALKk<*OO@Gdzgw5(Kq!lPu!!)hufWpPm5IT$fQh8Z~!3`(D{ z@ErO=NVDNe~|}hEP$e-SDvOA3-&Rc)qNMg;ZY#Ok>L^M3zH(i+rA*EI+qQNzQ+Ku5&StF#u+8zHC*8Qs zNgT^=9OosjDFNDxc)U`;ux1H5WCs9C>e=Y;DsC(=hCVaX+bTD}s03b? eS?PYUyu>e6@8Ekdxqu)~_p}}O?pccN8vg-dUpM{$ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/completion.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/completion.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56ac6b1a5810ff5759e8240b9bcfbb6a2315cff1 GIT binary patch literal 4345 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77b0b0977b5dc416fdc37207cec222e854264a04 GIT binary patch literal 8905 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/download.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/download.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..136e7b88ddbade851a1eff7cd3100ed369df7d70 GIT binary patch literal 4236 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 F`#&FsJa_;A literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/hash.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/hash.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bb42d4916a548a2dfba9d89ca5d0af9a6f0df42 GIT binary patch literal 2137 zcmZuyUvCpf5Wl_u{ue@NLrPnEs03jN+m#Sfg(w1r6e^)0oItCq(CK`;wlCbj?Cu5P zI#1yh^$YOeJXQS)eeS2&*FN{r=6SKojLFP<~QRftyW!w@%taW_*Yxg z{=~)dFkx{Arg#H_YLq0JKYtUSRBPSW;jJe|YWgO@Gb6E5+qWy-Oq|s9-AcETT3Yw( zX~S<+YddMCEx(nn_$$@gNzSCJ{%U&GKT9-5W*@P0C&d5wzD8YId!^ADbB_)G6SjsA zt-mp7gEn88{&`Avw07$+xS_V)Cdz&qCM6S~nGa(j;q7eZX&Pp0Tq6s=pZ%QQp?&>_A-b>F0hZ&8z4ZwR%i1NXL>!(3MTRAY7OLNqm6husD>A)1D2 zJP5^jaqr3Nx(5SR@T<%Vy)m45`4F@ukHW+&!svN8f)gH%;{(>MSN6MOCYTq6nKxiw zDH!!$#B%I)I+c5zfjejWWz1R1GAX=aoG?%39?fCDV4(~L39G&oXF`TaBD&RTa$%$3 z`N;bzFJzo&;+b~~U2l8cZg;oJ0B5q~Swxn=LA?oc6Q;NcVyf+FdxR3IQ$y<1lm@kC zCbegl)KAC>q0VcEpp`vysEgJoI`Rb2scld6PB>|FI$<)(AtU2d)!wU?kU3|Q%Mkw@ zsna8j4uH7nq(QkTW)jSx0h6J>$ItmL2qx zc(5?J-u_ROm(q_*_{!o?-?hAXc8)5 zO7^THW2)cNo~<95(xzl;P7UdxJl2n))9icF1up2@lhqAzvp#3_x1er_c8%lpRioe# zaZn1V4P}T!NUV^QanA7;yoCb$pK1k&Ly!*xBr=CisI@JQIPi-ozC?lM$}B=Tu97tu zpW~*%%j_yB3(!omLQLHzE<&f_2knOTSnF#)YOrgaU_I-TOZ}>2c=BDz(s%?FIqz|pldjVFplvHN zFBsG%rVH4vjEueDco}7v1q~%rwK01+cZLj}MxmTVYlKP$S7lZG{QsDI1s}Qrv%*Bv zo1{*fWQ}m#Z?E&O@pqggAC2N{#4q8>1-8%Y8xc~rJS_06O2S1bx$bGnb)zKi4no0# zMeT9?w@@E2f>tX_0cOyX=0rt4stf=9Tq$Gd#~2J)VcV%1kXrwcRT_(^LTS$S+K9=A z5Cs&%mFm_Q5-gAs{2B@jcnO?}KNZv6TXUA)##SiSK)6727hq}vT&wu#bz}9)zklp7 A+yDRo literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/help.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/help.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68109319b75388fd249374975db1ee62a58c9e67 GIT binary patch literal 1326 zcmZuw&5qkP5GF^hXy#HfObpB}yTs z&DxMJ-8@0BZGqimU!mtd1+P8z6>{oOwij+t5*!UbiKB168NNC^30_T!6MR$2IDi$DEt#I#q{Of&_9BpvDKWEgT=El{k{?Ouc5LCvu5&u~ zimpA*I`>z??cZ5O*M8?o(fQXdcRFu_-eAodkGt1?=iksP`ZMk9{^Xz=ydGQ!H+^&z z!h0zxGQ)%pcUdPqpEvqwE+K6o*`oS>22G;!GA~lO1J&woD;A=9D|V8FC-3RW>Ta1X zXAmx{HY_0(O?a|m;W!)}=;J!CCrK_EkRmN6yI;x!T+Pr2Z8;0$rF*5pq&gmP-#}GOnX}% ze-QZz9(sQnomVA9u!2PnMf4?HKvC5th$ecG&Qw${oAXMH>qp}=2{6lH+{kpk02y6? zxQL2;w*BPMWFRg4xiqb;nQEkFD6lk)dU^KDrv~%T7_F62-KVx?W{>uq@Sbcc zWrAwAS=PD8Ho-stpc{&OGE3EM&q;H`_5XaNnzU&Zb|D9~>5m8hjRco-*j&%PV|p@s bv`vwv;jaBB8=0P(OQ6h!9nv96j+6Zh)FERN literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/index.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/index.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc578f74d21e170fab9b5258aca062182541dbea GIT binary patch literal 4522 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/install.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/install.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..246d35ab5eb08a1687f1b9deacdd2d46c8fc93b4 GIT binary patch literal 17857 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/list.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/list.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7b896b26f1761471229f8b12bf95a37380aedff GIT binary patch literal 10587 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/search.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/search.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..352096ec382f9fef819f1861a0230f3856c8853e GIT binary patch literal 5288 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 Pg8LR;p)U5H#+m;CVPQf< literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/show.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/show.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27a29766c08de8bee135629340a7db0a2a38ca41 GIT binary patch literal 7099 zcmb_h-ESLLcAqZ}heJ}Ls1Hm2%EU?5VQNwZvfJIduIog$6E|MjmF#@XZZIA3Udba3 zIrN>OZ4u;4WuVyy18(~k1ce0b!y5b4*P`fC|AIcwYk?wp*+0P8Zu&cSh9Z@?DNvNc zoO|x)oVn-RbAI>eezj^V`2F+0tp@*eUQzyoD$~CLD%bGDUxP4(siERhsfB7sb2XKC zJ=8mfYjg^3p<}vcr|1?tmTPrNZb|kVq1`FFWyu%9N~h{pC2xi^omqEQ^2M;$nRDkl zb+<0-Ryf~Ta2Gm@?xL)h!llkB_mrw|<#3s=9I5W<4-{sz@>7MC`Rsx2oPYw4xFYvSIeU1Lu+*byxvw5^TOx;u(3*X>t zmGy?2njiaNpT`X|Eq%bdT=+>OQtMU%ZY$(DM{UOKAWl+y-S2RAuivA-JG~@`x_*dd z>bJU&{4ih}{9!*3yu-W63pJf<``xG;w9zx*UKdj_w73>^I)0Zmw6t(zW8=<7THL(9 zwsvE46D_s({g_|Jn?$hHr`6L_J3R3i-}d`q;_>dIKtx?y9xGKIiy+}%)K7Z-q)lHm z{WDOxh9|xP!WCC#imNfz)tTlROm_>+V1=(#X0jso#l&t}yo4RIne~;T)L%PA3u^y>a4)A!km{Z}0g#JZ{-? z?!5;XB&BwO7{i>6n`_N>)aiwR-%Xs3AB4^W?lUf&Z2gv;Hh5DOj`LPeL_5weBDw$Z z=gyUU{i{x^)w(CuDtOWt-L{HvAODEA@T}p9FM$k|U1b-h!!)MTX3$nVGgWHW4s~Wx z|B*Zb*o4yhKx=Lfip}PB5b`)JWQ85WIJlRHKEFBmsoazVU$@;4LnnkDooIV%ALJbR zCWrg&2jgvNHH_3?9Oh;EZK?EPuJF`&z{8#($uY)icbJEdj#Jxbk|P5#d%lRdNawNL zTqHCA&EAfP`aKwl5%X~Sg(6NI zqHeSIZgX33z7_ChBA_}hRv*FiR>NQ`AM$SNXR9%cuQ}H2YA@)mdO;Tl2Pa|`$|M_) zS7Vq-tGAz)ClbeJNC9;aMKx7z^7qm#4Ngs2T5D=fLr;sjzEi^&JF%F@{I*W3xps*4Iinhl4zS~8R>~W)DP5=kr+c`sO}c>h?0LC_dt1D~fr;O3g6Z*@1eBkA2Y% zx;vr@$UGAdP~616RZaB5#1Cg@=7E z_S52cJgHdfSvb_Ki+=Yan0OgPQ4Q6`V`vs_xMC>Ax2A5WgW2f~$BafLwLIyoJTJ99 zuM@F;$iY`U?_u8$>3dNtdLE0~U<&;hCmYtc)1`wTG6(39y@=QGUZD{&K~f!VE!FXb zf=*v1=}DOdSw#&`Oi{~H4b#vJZKfjI+h{uR(^0#IC;nHEv4^{_aGe`Wy^@t$Ug{_d z95DiY26!{)3sbzAk1ghIQs4d-Wy24qIP;ByGle@f~c*k8`>pA1`Cp{RYl)M7xBKEyr1ifb_z1}E_CNw5mL{^Es3zE)yp5N_8iTuvk z^8`6-X*sVnm7k!Op7>1=Teak0MWsLDr)gFfG`gXQcTi;BV>&vJJ6Xg-Co5(!+C(`} zMlw1)Rfg)JdZb)e4%A-(4lx5QvNqBZ?QfNB6|uv3YQeL>!yITd4ju-6MtqtWhcd1l znnV3arD&2A6DvWWMVQUzQmCzrj3MSdS3m!HR2WpgP=T@0CK|@g94V7A`8rGJ zF^1-Wdfa1S_StOqb5)$q=Chd{b!6ppPGhZEHhZLwO2g7_jn&xPGj(KR-f7I6+p{t6 zZZ>a68QIw^%&8wKqw=uK>U$PO?6Uf((zWpZGWM%7GKEFMx4QsngY>Ay>%g?l5YNI){%pIOO0(fLAG#=$?S+3xX^kns#nUv>cjpTjpm~)P~*Cl5UE9}Cv`p3}Y!-Umw?B5vtPGXu5e4qy5FdWYy zjGb)}b*5kgn*xM!%ehNY!kKQAQRtFfDL>ekoUvQD zgaGALxim!|Q=y7g>w{?X!D~zwUCKG>H(4Ab+1Y#bje>V|SP~d`-`AZbyPl$X^ zHo^%~wtMJ~>HA5x|5dW%X(uG37)VQy`-;zkNXZs(B-D{M)V@&miUKfaT0#=2p-&`C z_1@mj3w07FSSGWBapLD7ALEI$Jv4!!2g`Y&dOL17@Ylez{Vu!gycNIHE;&F#m3ChU zxP4FRh9o;7GI?DRTZ+5{MtV@7@Tb-n>YdfkO)|9+xbD7B0cQmMIu zQ#IczsbdGdY3WMLGwuF0suS&Q{IK15bj!<*ATZ)FESxe-I*f>_FwyY~Qe$0Wg!}6s zt>5`%eH}XekVKdRE*H2m_IQ`^Zactb#Tv(baSJ0}sA2=fM9YwRy!4*k=&q?l}(A{+~VyF$qni`)4Xwvb>uk^;LbP_31_K3 z2zug2G>a}asj&-GDQ2loo*{#g)<=B*h7geupU{{>7(M1fl)$@-a(UrDK6yGy0=Wh+ z;ZKm?k-HZLT^yRZo4Dw&N6AfcZ*s*nTYzzZX2BWKG04-vm_MZ#73l~=^i&6v7UV*> zvg7u+9fgc;X}!>IbM#FB!lq)U8+hKYXMxoHEFpDPvM+7bYGv^Uw6lRhU?+BI;g4(RWeK)lf3;-Oo^ zMXw`MS}beHPJ}$n4o!UZAq=E6_hs08u~48re3dwGpNnvb7sNM$~gtX=X)jb?-((M)>=LS3DHBU*GWn# z7jU<1na^5=gqWXT)*?m~@-a)`7jpg-eKG2bz~r@25hIs}@*9DxFQB%P_b;J;VPwgE zi%>ev$C&rgaylQgEY~Q>F>*i9zdck1eb-VM|7*vKUaqvBz*{ z^OPHMFt~~#FsXs`w(imxXpMcjmkDi8(ky8~`r))Zm03v*3OV0J77e`gw|HWL7>Who z2_J`ChPEWVVV3J4KRP4p(%T`eXXtf!NfZ8(av(PPiPm$TR;a*t+UkNT=nak6k-$NY z5+NkDkYdf!$z+SEne6u{vXE~SWSCi)rK^A-|0pvw=>^;sTa@r)k!WQJO-yQKXjc9! zB$nYfJ@e{;_&-M&OKhEi`P(dGC7}nwMH#*&-jLBHHMXKCl-oI_ o#L2C|x{{?)uM$p+FMyB|98wz|3Uw9D)@yIo7SC_xDe>R`H<h($ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22d6404272bdcbf2f3803dc2331299235897bedd GIT binary patch literal 3350 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 Q@kP}`Eq+bvb&PuHzhm3X-2eap literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/cache.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/cache.py new file mode 100644 index 000000000..ad65641ed --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/cache.py @@ -0,0 +1,228 @@ +import os +import textwrap +from optparse import Values +from typing import Any, List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.exceptions import CommandError, PipError +from pip._internal.utils import filesystem +from pip._internal.utils.logging import getLogger +from pip._internal.utils.misc import format_size + +logger = getLogger(__name__) + + +class CacheCommand(Command): + """ + Inspect and manage pip's wheel cache. + + Subcommands: + + - dir: Show the cache directory. + - info: Show information about the cache. + - list: List filenames of packages stored in the cache. + - remove: Remove one or more package from the cache. + - purge: Remove all items from the cache. + + ```` can be a glob expression or a package name. + """ + + ignore_require_venv = True + usage = """ + %prog dir + %prog info + %prog list [] [--format=[human, abspath]] + %prog remove + %prog purge + """ + + def add_options(self) -> None: + self.cmd_opts.add_option( + "--format", + action="store", + dest="list_format", + default="human", + choices=("human", "abspath"), + help="Select the output format among: human (default) or abspath", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + handlers = { + "dir": self.get_cache_dir, + "info": self.get_cache_info, + "list": self.list_cache_items, + "remove": self.remove_cache_items, + "purge": self.purge_cache, + } + + if not options.cache_dir: + logger.error("pip cache commands can not function since cache is disabled.") + return ERROR + + # Determine action + if not args or args[0] not in handlers: + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), + ) + return ERROR + + action = args[0] + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def get_cache_dir(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError("Too many arguments") + + logger.info(options.cache_dir) + + def get_cache_info(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError("Too many arguments") + + num_http_files = len(self._find_http_files(options)) + num_packages = len(self._find_wheels(options, "*")) + + http_cache_location = self._cache_dir(options, "http-v2") + old_http_cache_location = self._cache_dir(options, "http") + wheels_cache_location = self._cache_dir(options, "wheels") + http_cache_size = filesystem.format_size( + filesystem.directory_size(http_cache_location) + + filesystem.directory_size(old_http_cache_location) + ) + wheels_cache_size = filesystem.format_directory_size(wheels_cache_location) + + message = ( + textwrap.dedent( + """ + Package index page cache location (pip v23.3+): {http_cache_location} + Package index page cache location (older pips): {old_http_cache_location} + Package index page cache size: {http_cache_size} + Number of HTTP files: {num_http_files} + Locally built wheels location: {wheels_cache_location} + Locally built wheels size: {wheels_cache_size} + Number of locally built wheels: {package_count} + """ # noqa: E501 + ) + .format( + http_cache_location=http_cache_location, + old_http_cache_location=old_http_cache_location, + http_cache_size=http_cache_size, + num_http_files=num_http_files, + wheels_cache_location=wheels_cache_location, + package_count=num_packages, + wheels_cache_size=wheels_cache_size, + ) + .strip() + ) + + logger.info(message) + + def list_cache_items(self, options: Values, args: List[Any]) -> None: + if len(args) > 1: + raise CommandError("Too many arguments") + + if args: + pattern = args[0] + else: + pattern = "*" + + files = self._find_wheels(options, pattern) + if options.list_format == "human": + self.format_for_human(files) + else: + self.format_for_abspath(files) + + def format_for_human(self, files: List[str]) -> None: + if not files: + logger.info("No locally built wheels cached.") + return + + results = [] + for filename in files: + wheel = os.path.basename(filename) + size = filesystem.format_file_size(filename) + results.append(f" - {wheel} ({size})") + logger.info("Cache contents:\n") + logger.info("\n".join(sorted(results))) + + def format_for_abspath(self, files: List[str]) -> None: + if files: + logger.info("\n".join(sorted(files))) + + def remove_cache_items(self, options: Values, args: List[Any]) -> None: + if len(args) > 1: + raise CommandError("Too many arguments") + + if not args: + raise CommandError("Please provide a pattern") + + files = self._find_wheels(options, args[0]) + + no_matching_msg = "No matching packages" + if args[0] == "*": + # Only fetch http files if no specific pattern given + files += self._find_http_files(options) + else: + # Add the pattern to the log message + no_matching_msg += f' for pattern "{args[0]}"' + + if not files: + logger.warning(no_matching_msg) + + bytes_removed = 0 + for filename in files: + bytes_removed += os.stat(filename).st_size + os.unlink(filename) + logger.verbose("Removed %s", filename) + logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed)) + + def purge_cache(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError("Too many arguments") + + return self.remove_cache_items(options, ["*"]) + + def _cache_dir(self, options: Values, subdir: str) -> str: + return os.path.join(options.cache_dir, subdir) + + def _find_http_files(self, options: Values) -> List[str]: + old_http_dir = self._cache_dir(options, "http") + new_http_dir = self._cache_dir(options, "http-v2") + return filesystem.find_files(old_http_dir, "*") + filesystem.find_files( + new_http_dir, "*" + ) + + def _find_wheels(self, options: Values, pattern: str) -> List[str]: + wheel_dir = self._cache_dir(options, "wheels") + + # The wheel filename format, as specified in PEP 427, is: + # {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl + # + # Additionally, non-alphanumeric values in the distribution are + # normalized to underscores (_), meaning hyphens can never occur + # before `-{version}`. + # + # Given that information: + # - If the pattern we're given contains a hyphen (-), the user is + # providing at least the version. Thus, we can just append `*.whl` + # to match the rest of it. + # - If the pattern we're given doesn't contain a hyphen (-), the + # user is only providing the name. Thus, we append `-*.whl` to + # match the hyphen before the version, followed by anything else. + # + # PEP 427: https://www.python.org/dev/peps/pep-0427/ + pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl") + + return filesystem.find_files(wheel_dir, pattern) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/check.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/check.py new file mode 100644 index 000000000..f54a16dc0 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/check.py @@ -0,0 +1,67 @@ +import logging +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.metadata import get_default_environment +from pip._internal.operations.check import ( + check_package_set, + check_unsupported, + create_package_set_from_installed, +) +from pip._internal.utils.compatibility_tags import get_supported +from pip._internal.utils.misc import write_output + +logger = logging.getLogger(__name__) + + +class CheckCommand(Command): + """Verify installed packages have compatible dependencies.""" + + ignore_require_venv = True + usage = """ + %prog [options]""" + + def run(self, options: Values, args: List[str]) -> int: + package_set, parsing_probs = create_package_set_from_installed() + missing, conflicting = check_package_set(package_set) + unsupported = list( + check_unsupported( + get_default_environment().iter_installed_distributions(), + get_supported(), + ) + ) + + for project_name in missing: + version = package_set[project_name].version + for dependency in missing[project_name]: + write_output( + "%s %s requires %s, which is not installed.", + project_name, + version, + dependency[0], + ) + + for project_name in conflicting: + version = package_set[project_name].version + for dep_name, dep_version, req in conflicting[project_name]: + write_output( + "%s %s has requirement %s, but you have %s %s.", + project_name, + version, + req, + dep_name, + dep_version, + ) + for package in unsupported: + write_output( + "%s %s is not supported on this platform", + package.raw_name, + package.version, + ) + if missing or conflicting or parsing_probs or unsupported: + return ERROR + else: + write_output("No broken requirements found.") + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/completion.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/completion.py new file mode 100644 index 000000000..9e89e2798 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/completion.py @@ -0,0 +1,130 @@ +import sys +import textwrap +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.utils.misc import get_prog + +BASE_COMPLETION = """ +# pip {shell} completion start{script}# pip {shell} completion end +""" + +COMPLETION_SCRIPTS = { + "bash": """ + _pip_completion() + {{ + COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\ + COMP_CWORD=$COMP_CWORD \\ + PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) ) + }} + complete -o default -F _pip_completion {prog} + """, + "zsh": """ + #compdef -P pip[0-9.]# + __pip() {{ + compadd $( COMP_WORDS="$words[*]" \\ + COMP_CWORD=$((CURRENT-1)) \\ + PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ) + }} + if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + __pip "$@" + else + # eval/source/. command, register function for later + compdef __pip -P 'pip[0-9.]#' + fi + """, + "fish": """ + function __fish_complete_pip + set -lx COMP_WORDS (commandline -o) "" + set -lx COMP_CWORD ( \\ + math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\ + ) + set -lx PIP_AUTO_COMPLETE 1 + string split \\ -- (eval $COMP_WORDS[1]) + end + complete -fa "(__fish_complete_pip)" -c {prog} + """, + "powershell": """ + if ((Test-Path Function:\\TabExpansion) -and -not ` + (Test-Path Function:\\_pip_completeBackup)) {{ + Rename-Item Function:\\TabExpansion _pip_completeBackup + }} + function TabExpansion($line, $lastWord) {{ + $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() + if ($lastBlock.StartsWith("{prog} ")) {{ + $Env:COMP_WORDS=$lastBlock + $Env:COMP_CWORD=$lastBlock.Split().Length - 1 + $Env:PIP_AUTO_COMPLETE=1 + (& {prog}).Split() + Remove-Item Env:COMP_WORDS + Remove-Item Env:COMP_CWORD + Remove-Item Env:PIP_AUTO_COMPLETE + }} + elseif (Test-Path Function:\\_pip_completeBackup) {{ + # Fall back on existing tab expansion + _pip_completeBackup $line $lastWord + }} + }} + """, +} + + +class CompletionCommand(Command): + """A helper command to be used for command completion.""" + + ignore_require_venv = True + + def add_options(self) -> None: + self.cmd_opts.add_option( + "--bash", + "-b", + action="store_const", + const="bash", + dest="shell", + help="Emit completion code for bash", + ) + self.cmd_opts.add_option( + "--zsh", + "-z", + action="store_const", + const="zsh", + dest="shell", + help="Emit completion code for zsh", + ) + self.cmd_opts.add_option( + "--fish", + "-f", + action="store_const", + const="fish", + dest="shell", + help="Emit completion code for fish", + ) + self.cmd_opts.add_option( + "--powershell", + "-p", + action="store_const", + const="powershell", + dest="shell", + help="Emit completion code for powershell", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + """Prints the completion code of the given shell""" + shells = COMPLETION_SCRIPTS.keys() + shell_options = ["--" + shell for shell in sorted(shells)] + if options.shell in shells: + script = textwrap.dedent( + COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog()) + ) + print(BASE_COMPLETION.format(script=script, shell=options.shell)) + return SUCCESS + else: + sys.stderr.write( + "ERROR: You must pass {}\n".format(" or ".join(shell_options)) + ) + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/configuration.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/configuration.py new file mode 100644 index 000000000..1a1dc6b6c --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/configuration.py @@ -0,0 +1,280 @@ +import logging +import os +import subprocess +from optparse import Values +from typing import Any, List, Optional + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.configuration import ( + Configuration, + Kind, + get_configuration_files, + kinds, +) +from pip._internal.exceptions import PipError +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import get_prog, write_output + +logger = logging.getLogger(__name__) + + +class ConfigurationCommand(Command): + """ + Manage local and global configuration. + + Subcommands: + + - list: List the active configuration (or from the file specified) + - edit: Edit the configuration file in an editor + - get: Get the value associated with command.option + - set: Set the command.option=value + - unset: Unset the value associated with command.option + - debug: List the configuration files and values defined under them + + Configuration keys should be dot separated command and option name, + with the special prefix "global" affecting any command. For example, + "pip config set global.index-url https://example.org/" would configure + the index url for all commands, but "pip config set download.timeout 10" + would configure a 10 second timeout only for "pip download" commands. + + If none of --user, --global and --site are passed, a virtual + environment configuration file is used if one is active and the file + exists. Otherwise, all modifications happen to the user file by + default. + """ + + ignore_require_venv = True + usage = """ + %prog [] list + %prog [] [--editor ] edit + + %prog [] get command.option + %prog [] set command.option value + %prog [] unset command.option + %prog [] debug + """ + + def add_options(self) -> None: + self.cmd_opts.add_option( + "--editor", + dest="editor", + action="store", + default=None, + help=( + "Editor to use to edit the file. Uses VISUAL or EDITOR " + "environment variables if not provided." + ), + ) + + self.cmd_opts.add_option( + "--global", + dest="global_file", + action="store_true", + default=False, + help="Use the system-wide configuration file only", + ) + + self.cmd_opts.add_option( + "--user", + dest="user_file", + action="store_true", + default=False, + help="Use the user configuration file only", + ) + + self.cmd_opts.add_option( + "--site", + dest="site_file", + action="store_true", + default=False, + help="Use the current environment configuration file only", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + handlers = { + "list": self.list_values, + "edit": self.open_in_editor, + "get": self.get_name, + "set": self.set_name_value, + "unset": self.unset_name, + "debug": self.list_config_values, + } + + # Determine action + if not args or args[0] not in handlers: + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), + ) + return ERROR + + action = args[0] + + # Determine which configuration files are to be loaded + # Depends on whether the command is modifying. + try: + load_only = self._determine_file( + options, need_value=(action in ["get", "set", "unset", "edit"]) + ) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + # Load a new configuration + self.configuration = Configuration( + isolated=options.isolated_mode, load_only=load_only + ) + self.configuration.load() + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]: + file_options = [ + key + for key, value in ( + (kinds.USER, options.user_file), + (kinds.GLOBAL, options.global_file), + (kinds.SITE, options.site_file), + ) + if value + ] + + if not file_options: + if not need_value: + return None + # Default to user, unless there's a site file. + elif any( + os.path.exists(site_config_file) + for site_config_file in get_configuration_files()[kinds.SITE] + ): + return kinds.SITE + else: + return kinds.USER + elif len(file_options) == 1: + return file_options[0] + + raise PipError( + "Need exactly one file to operate upon " + "(--user, --site, --global) to perform." + ) + + def list_values(self, options: Values, args: List[str]) -> None: + self._get_n_args(args, "list", n=0) + + for key, value in sorted(self.configuration.items()): + write_output("%s=%r", key, value) + + def get_name(self, options: Values, args: List[str]) -> None: + key = self._get_n_args(args, "get [name]", n=1) + value = self.configuration.get_value(key) + + write_output("%s", value) + + def set_name_value(self, options: Values, args: List[str]) -> None: + key, value = self._get_n_args(args, "set [name] [value]", n=2) + self.configuration.set_value(key, value) + + self._save_configuration() + + def unset_name(self, options: Values, args: List[str]) -> None: + key = self._get_n_args(args, "unset [name]", n=1) + self.configuration.unset_value(key) + + self._save_configuration() + + def list_config_values(self, options: Values, args: List[str]) -> None: + """List config key-value pairs across different config files""" + self._get_n_args(args, "debug", n=0) + + self.print_env_var_values() + # Iterate over config files and print if they exist, and the + # key-value pairs present in them if they do + for variant, files in sorted(self.configuration.iter_config_files()): + write_output("%s:", variant) + for fname in files: + with indent_log(): + file_exists = os.path.exists(fname) + write_output("%s, exists: %r", fname, file_exists) + if file_exists: + self.print_config_file_values(variant) + + def print_config_file_values(self, variant: Kind) -> None: + """Get key-value pairs from the file of a variant""" + for name, value in self.configuration.get_values_in_config(variant).items(): + with indent_log(): + write_output("%s: %s", name, value) + + def print_env_var_values(self) -> None: + """Get key-values pairs present as environment variables""" + write_output("%s:", "env_var") + with indent_log(): + for key, value in sorted(self.configuration.get_environ_vars()): + env_var = f"PIP_{key.upper()}" + write_output("%s=%r", env_var, value) + + def open_in_editor(self, options: Values, args: List[str]) -> None: + editor = self._determine_editor(options) + + fname = self.configuration.get_file_to_edit() + if fname is None: + raise PipError("Could not determine appropriate file.") + elif '"' in fname: + # This shouldn't happen, unless we see a username like that. + # If that happens, we'd appreciate a pull request fixing this. + raise PipError( + f'Can not open an editor for a file name containing "\n{fname}' + ) + + try: + subprocess.check_call(f'{editor} "{fname}"', shell=True) + except FileNotFoundError as e: + if not e.filename: + e.filename = editor + raise + except subprocess.CalledProcessError as e: + raise PipError(f"Editor Subprocess exited with exit code {e.returncode}") + + def _get_n_args(self, args: List[str], example: str, n: int) -> Any: + """Helper to make sure the command got the right number of arguments""" + if len(args) != n: + msg = ( + f"Got unexpected number of arguments, expected {n}. " + f'(example: "{get_prog()} config {example}")' + ) + raise PipError(msg) + + if n == 1: + return args[0] + else: + return args + + def _save_configuration(self) -> None: + # We successfully ran a modifying command. Need to save the + # configuration. + try: + self.configuration.save() + except Exception: + logger.exception( + "Unable to save configuration. Please report this as a bug." + ) + raise PipError("Internal Error.") + + def _determine_editor(self, options: Values) -> str: + if options.editor is not None: + return options.editor + elif "VISUAL" in os.environ: + return os.environ["VISUAL"] + elif "EDITOR" in os.environ: + return os.environ["EDITOR"] + else: + raise PipError("Could not determine editor to use.") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/debug.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/debug.py new file mode 100644 index 000000000..567ca967e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/debug.py @@ -0,0 +1,201 @@ +import locale +import logging +import os +import sys +from optparse import Values +from types import ModuleType +from typing import Any, Dict, List, Optional + +import pip._vendor +from pip._vendor.certifi import where +from pip._vendor.packaging.version import parse as parse_version + +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.cli.cmdoptions import make_target_python +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.configuration import Configuration +from pip._internal.metadata import get_environment +from pip._internal.utils.compat import open_text_resource +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import get_pip_version + +logger = logging.getLogger(__name__) + + +def show_value(name: str, value: Any) -> None: + logger.info("%s: %s", name, value) + + +def show_sys_implementation() -> None: + logger.info("sys.implementation:") + implementation_name = sys.implementation.name + with indent_log(): + show_value("name", implementation_name) + + +def create_vendor_txt_map() -> Dict[str, str]: + with open_text_resource("pip._vendor", "vendor.txt") as f: + # Purge non version specifying lines. + # Also, remove any space prefix or suffixes (including comments). + lines = [ + line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line + ] + + # Transform into "module" -> version dict. + return dict(line.split("==", 1) for line in lines) + + +def get_module_from_module_name(module_name: str) -> Optional[ModuleType]: + # Module name can be uppercase in vendor.txt for some reason... + module_name = module_name.lower().replace("-", "_") + # PATCH: setuptools is actually only pkg_resources. + if module_name == "setuptools": + module_name = "pkg_resources" + + try: + __import__(f"pip._vendor.{module_name}", globals(), locals(), level=0) + return getattr(pip._vendor, module_name) + except ImportError: + # We allow 'truststore' to fail to import due + # to being unavailable on Python 3.9 and earlier. + if module_name == "truststore" and sys.version_info < (3, 10): + return None + raise + + +def get_vendor_version_from_module(module_name: str) -> Optional[str]: + module = get_module_from_module_name(module_name) + version = getattr(module, "__version__", None) + + if module and not version: + # Try to find version in debundled module info. + assert module.__file__ is not None + env = get_environment([os.path.dirname(module.__file__)]) + dist = env.get_distribution(module_name) + if dist: + version = str(dist.version) + + return version + + +def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None: + """Log the actual version and print extra info if there is + a conflict or if the actual version could not be imported. + """ + for module_name, expected_version in vendor_txt_versions.items(): + extra_message = "" + actual_version = get_vendor_version_from_module(module_name) + if not actual_version: + extra_message = ( + " (Unable to locate actual module version, using" + " vendor.txt specified version)" + ) + actual_version = expected_version + elif parse_version(actual_version) != parse_version(expected_version): + extra_message = ( + " (CONFLICT: vendor.txt suggests version should" + f" be {expected_version})" + ) + logger.info("%s==%s%s", module_name, actual_version, extra_message) + + +def show_vendor_versions() -> None: + logger.info("vendored library versions:") + + vendor_txt_versions = create_vendor_txt_map() + with indent_log(): + show_actual_vendor_versions(vendor_txt_versions) + + +def show_tags(options: Values) -> None: + tag_limit = 10 + + target_python = make_target_python(options) + tags = target_python.get_sorted_tags() + + # Display the target options that were explicitly provided. + formatted_target = target_python.format_given() + suffix = "" + if formatted_target: + suffix = f" (target: {formatted_target})" + + msg = f"Compatible tags: {len(tags)}{suffix}" + logger.info(msg) + + if options.verbose < 1 and len(tags) > tag_limit: + tags_limited = True + tags = tags[:tag_limit] + else: + tags_limited = False + + with indent_log(): + for tag in tags: + logger.info(str(tag)) + + if tags_limited: + msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]" + logger.info(msg) + + +def ca_bundle_info(config: Configuration) -> str: + levels = {key.split(".", 1)[0] for key, _ in config.items()} + if not levels: + return "Not specified" + + levels_that_override_global = ["install", "wheel", "download"] + global_overriding_level = [ + level for level in levels if level in levels_that_override_global + ] + if not global_overriding_level: + return "global" + + if "global" in levels: + levels.remove("global") + return ", ".join(levels) + + +class DebugCommand(Command): + """ + Display debug information. + """ + + usage = """ + %prog """ + ignore_require_venv = True + + def add_options(self) -> None: + cmdoptions.add_target_python_options(self.cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) + self.parser.config.load() + + def run(self, options: Values, args: List[str]) -> int: + logger.warning( + "This command is only meant for debugging. " + "Do not use this with automation for parsing and getting these " + "details, since the output and options of this command may " + "change without notice." + ) + show_value("pip version", get_pip_version()) + show_value("sys.version", sys.version) + show_value("sys.executable", sys.executable) + show_value("sys.getdefaultencoding", sys.getdefaultencoding()) + show_value("sys.getfilesystemencoding", sys.getfilesystemencoding()) + show_value( + "locale.getpreferredencoding", + locale.getpreferredencoding(), + ) + show_value("sys.platform", sys.platform) + show_sys_implementation() + + show_value("'cert' config value", ca_bundle_info(self.parser.config)) + show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE")) + show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE")) + show_value("pip._vendor.certifi.where()", where()) + show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED) + + show_vendor_versions() + + show_tags(options) + + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/download.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/download.py new file mode 100644 index 000000000..917bbb91d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/download.py @@ -0,0 +1,146 @@ +import logging +import os +from optparse import Values +from typing import List + +from pip._internal.cli import cmdoptions +from pip._internal.cli.cmdoptions import make_target_python +from pip._internal.cli.req_command import RequirementCommand, with_cleanup +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.operations.build.build_tracker import get_build_tracker +from pip._internal.req.req_install import check_legacy_setup_py_options +from pip._internal.utils.misc import ensure_dir, normalize_path, write_output +from pip._internal.utils.temp_dir import TempDirectory + +logger = logging.getLogger(__name__) + + +class DownloadCommand(RequirementCommand): + """ + Download packages from: + + - PyPI (and other indexes) using requirement specifiers. + - VCS project urls. + - Local project directories. + - Local or remote source archives. + + pip also supports downloading from "requirements files", which provide + an easy way to specify a whole environment to be downloaded. + """ + + usage = """ + %prog [options] [package-index-options] ... + %prog [options] -r [package-index-options] ... + %prog [options] ... + %prog [options] ... + %prog [options] ...""" + + def add_options(self) -> None: + self.cmd_opts.add_option(cmdoptions.constraints()) + self.cmd_opts.add_option(cmdoptions.requirements()) + self.cmd_opts.add_option(cmdoptions.no_deps()) + self.cmd_opts.add_option(cmdoptions.global_options()) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + self.cmd_opts.add_option(cmdoptions.prefer_binary()) + self.cmd_opts.add_option(cmdoptions.src()) + self.cmd_opts.add_option(cmdoptions.pre()) + self.cmd_opts.add_option(cmdoptions.require_hashes()) + self.cmd_opts.add_option(cmdoptions.progress_bar()) + self.cmd_opts.add_option(cmdoptions.no_build_isolation()) + self.cmd_opts.add_option(cmdoptions.use_pep517()) + self.cmd_opts.add_option(cmdoptions.no_use_pep517()) + self.cmd_opts.add_option(cmdoptions.check_build_deps()) + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + + self.cmd_opts.add_option( + "-d", + "--dest", + "--destination-dir", + "--destination-directory", + dest="download_dir", + metavar="dir", + default=os.curdir, + help="Download packages into .", + ) + + cmdoptions.add_target_python_options(self.cmd_opts) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: + options.ignore_installed = True + # editable doesn't really make sense for `pip download`, but the bowels + # of the RequirementSet code require that property. + options.editables = [] + + cmdoptions.check_dist_restriction(options) + + options.download_dir = normalize_path(options.download_dir) + ensure_dir(options.download_dir) + + session = self.get_default_session(options) + + target_python = make_target_python(options) + finder = self._build_package_finder( + options=options, + session=session, + target_python=target_python, + ignore_requires_python=options.ignore_requires_python, + ) + + build_tracker = self.enter_context(get_build_tracker()) + + directory = TempDirectory( + delete=not options.no_clean, + kind="download", + globally_managed=True, + ) + + reqs = self.get_requirements(args, options, finder, session) + check_legacy_setup_py_options(options, reqs) + + preparer = self.make_requirement_preparer( + temp_build_dir=directory, + options=options, + build_tracker=build_tracker, + session=session, + finder=finder, + download_dir=options.download_dir, + use_user_site=False, + verbosity=self.verbosity, + ) + + resolver = self.make_resolver( + preparer=preparer, + finder=finder, + options=options, + ignore_requires_python=options.ignore_requires_python, + use_pep517=options.use_pep517, + py_version_info=options.python_version, + ) + + self.trace_basic_info(finder) + + requirement_set = resolver.resolve(reqs, check_supported_wheels=True) + + downloaded: List[str] = [] + for req in requirement_set.requirements.values(): + if req.satisfied_by is None: + assert req.name is not None + preparer.save_linked_requirement(req) + downloaded.append(req.name) + + preparer.prepare_linked_requirements_more(requirement_set.requirements.values()) + + if downloaded: + write_output("Successfully downloaded %s", " ".join(downloaded)) + + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/freeze.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/freeze.py new file mode 100644 index 000000000..885fdfeb8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/freeze.py @@ -0,0 +1,109 @@ +import sys +from optparse import Values +from typing import AbstractSet, List + +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.operations.freeze import freeze +from pip._internal.utils.compat import stdlib_pkgs + + +def _should_suppress_build_backends() -> bool: + return sys.version_info < (3, 12) + + +def _dev_pkgs() -> AbstractSet[str]: + pkgs = {"pip"} + + if _should_suppress_build_backends(): + pkgs |= {"setuptools", "distribute", "wheel"} + + return pkgs + + +class FreezeCommand(Command): + """ + Output installed packages in requirements format. + + packages are listed in a case-insensitive sorted order. + """ + + ignore_require_venv = True + usage = """ + %prog [options]""" + log_streams = ("ext://sys.stderr", "ext://sys.stderr") + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-r", + "--requirement", + dest="requirements", + action="append", + default=[], + metavar="file", + help=( + "Use the order in the given requirements file and its " + "comments when generating output. This option can be " + "used multiple times." + ), + ) + self.cmd_opts.add_option( + "-l", + "--local", + dest="local", + action="store_true", + default=False, + help=( + "If in a virtualenv that has global access, do not output " + "globally-installed packages." + ), + ) + self.cmd_opts.add_option( + "--user", + dest="user", + action="store_true", + default=False, + help="Only output packages installed in user-site.", + ) + self.cmd_opts.add_option(cmdoptions.list_path()) + self.cmd_opts.add_option( + "--all", + dest="freeze_all", + action="store_true", + help=( + "Do not skip these packages in the output:" + " {}".format(", ".join(_dev_pkgs())) + ), + ) + self.cmd_opts.add_option( + "--exclude-editable", + dest="exclude_editable", + action="store_true", + help="Exclude editable package from output.", + ) + self.cmd_opts.add_option(cmdoptions.list_exclude()) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + skip = set(stdlib_pkgs) + if not options.freeze_all: + skip.update(_dev_pkgs()) + + if options.excludes: + skip.update(options.excludes) + + cmdoptions.check_list_path_option(options) + + for line in freeze( + requirement=options.requirements, + local_only=options.local, + user_only=options.user, + paths=options.path, + isolated=options.isolated_mode, + skip=skip, + exclude_editable=options.exclude_editable, + ): + sys.stdout.write(line + "\n") + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/hash.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/hash.py new file mode 100644 index 000000000..042dac813 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/hash.py @@ -0,0 +1,59 @@ +import hashlib +import logging +import sys +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES +from pip._internal.utils.misc import read_chunks, write_output + +logger = logging.getLogger(__name__) + + +class HashCommand(Command): + """ + Compute a hash of a local package archive. + + These can be used with --hash in a requirements file to do repeatable + installs. + """ + + usage = "%prog [options] ..." + ignore_require_venv = True + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-a", + "--algorithm", + dest="algorithm", + choices=STRONG_HASHES, + action="store", + default=FAVORITE_HASH, + help="The hash algorithm to use: one of {}".format( + ", ".join(STRONG_HASHES) + ), + ) + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + if not args: + self.parser.print_usage(sys.stderr) + return ERROR + + algorithm = options.algorithm + for path in args: + write_output( + "%s:\n--hash=%s:%s", path, algorithm, _hash_of_file(path, algorithm) + ) + return SUCCESS + + +def _hash_of_file(path: str, algorithm: str) -> str: + """Return the hash digest of a file.""" + with open(path, "rb") as archive: + hash = hashlib.new(algorithm) + for chunk in read_chunks(archive): + hash.update(chunk) + return hash.hexdigest() diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/help.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/help.py new file mode 100644 index 000000000..62066318b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/help.py @@ -0,0 +1,41 @@ +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.exceptions import CommandError + + +class HelpCommand(Command): + """Show help for commands""" + + usage = """ + %prog """ + ignore_require_venv = True + + def run(self, options: Values, args: List[str]) -> int: + from pip._internal.commands import ( + commands_dict, + create_command, + get_similar_commands, + ) + + try: + # 'pip help' with no args is handled by pip.__init__.parseopt() + cmd_name = args[0] # the command we need help for + except IndexError: + return SUCCESS + + if cmd_name not in commands_dict: + guess = get_similar_commands(cmd_name) + + msg = [f'unknown command "{cmd_name}"'] + if guess: + msg.append(f'maybe you meant "{guess}"') + + raise CommandError(" - ".join(msg)) + + command = create_command(cmd_name) + command.parser.print_help() + + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/index.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/index.py new file mode 100644 index 000000000..2e2661bba --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/index.py @@ -0,0 +1,139 @@ +import logging +from optparse import Values +from typing import Any, Iterable, List, Optional + +from pip._vendor.packaging.version import Version + +from pip._internal.cli import cmdoptions +from pip._internal.cli.req_command import IndexGroupCommand +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.commands.search import print_dist_installation_info +from pip._internal.exceptions import CommandError, DistributionNotFound, PipError +from pip._internal.index.collector import LinkCollector +from pip._internal.index.package_finder import PackageFinder +from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.models.target_python import TargetPython +from pip._internal.network.session import PipSession +from pip._internal.utils.misc import write_output + +logger = logging.getLogger(__name__) + + +class IndexCommand(IndexGroupCommand): + """ + Inspect information available from package indexes. + """ + + ignore_require_venv = True + usage = """ + %prog versions + """ + + def add_options(self) -> None: + cmdoptions.add_target_python_options(self.cmd_opts) + + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + self.cmd_opts.add_option(cmdoptions.pre()) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + handlers = { + "versions": self.get_available_package_versions, + } + + logger.warning( + "pip index is currently an experimental command. " + "It may be removed/changed in a future release " + "without prior warning." + ) + + # Determine action + if not args or args[0] not in handlers: + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), + ) + return ERROR + + action = args[0] + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def _build_package_finder( + self, + options: Values, + session: PipSession, + target_python: Optional[TargetPython] = None, + ignore_requires_python: Optional[bool] = None, + ) -> PackageFinder: + """ + Create a package finder appropriate to the index command. + """ + link_collector = LinkCollector.create(session, options=options) + + # Pass allow_yanked=False to ignore yanked versions. + selection_prefs = SelectionPreferences( + allow_yanked=False, + allow_all_prereleases=options.pre, + ignore_requires_python=ignore_requires_python, + ) + + return PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + target_python=target_python, + ) + + def get_available_package_versions(self, options: Values, args: List[Any]) -> None: + if len(args) != 1: + raise CommandError("You need to specify exactly one argument") + + target_python = cmdoptions.make_target_python(options) + query = args[0] + + with self._build_session(options) as session: + finder = self._build_package_finder( + options=options, + session=session, + target_python=target_python, + ignore_requires_python=options.ignore_requires_python, + ) + + versions: Iterable[Version] = ( + candidate.version for candidate in finder.find_all_candidates(query) + ) + + if not options.pre: + # Remove prereleases + versions = ( + version for version in versions if not version.is_prerelease + ) + versions = set(versions) + + if not versions: + raise DistributionNotFound( + f"No matching distribution found for {query}" + ) + + formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)] + latest = formatted_versions[0] + + write_output(f"{query} ({latest})") + write_output("Available versions: {}".format(", ".join(formatted_versions))) + print_dist_installation_info(query, latest) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/inspect.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/inspect.py new file mode 100644 index 000000000..e810c1316 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/inspect.py @@ -0,0 +1,92 @@ +import logging +from optparse import Values +from typing import Any, Dict, List + +from pip._vendor.packaging.markers import default_environment +from pip._vendor.rich import print_json + +from pip import __version__ +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.metadata import BaseDistribution, get_environment +from pip._internal.utils.compat import stdlib_pkgs +from pip._internal.utils.urls import path_to_url + +logger = logging.getLogger(__name__) + + +class InspectCommand(Command): + """ + Inspect the content of a Python environment and produce a report in JSON format. + """ + + ignore_require_venv = True + usage = """ + %prog [options]""" + + def add_options(self) -> None: + self.cmd_opts.add_option( + "--local", + action="store_true", + default=False, + help=( + "If in a virtualenv that has global access, do not list " + "globally-installed packages." + ), + ) + self.cmd_opts.add_option( + "--user", + dest="user", + action="store_true", + default=False, + help="Only output packages installed in user-site.", + ) + self.cmd_opts.add_option(cmdoptions.list_path()) + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + cmdoptions.check_list_path_option(options) + dists = get_environment(options.path).iter_installed_distributions( + local_only=options.local, + user_only=options.user, + skip=set(stdlib_pkgs), + ) + output = { + "version": "1", + "pip_version": __version__, + "installed": [self._dist_to_dict(dist) for dist in dists], + "environment": default_environment(), + # TODO tags? scheme? + } + print_json(data=output) + return SUCCESS + + def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]: + res: Dict[str, Any] = { + "metadata": dist.metadata_dict, + "metadata_location": dist.info_location, + } + # direct_url. Note that we don't have download_info (as in the installation + # report) since it is not recorded in installed metadata. + direct_url = dist.direct_url + if direct_url is not None: + res["direct_url"] = direct_url.to_dict() + else: + # Emulate direct_url for legacy editable installs. + editable_project_location = dist.editable_project_location + if editable_project_location is not None: + res["direct_url"] = { + "url": path_to_url(editable_project_location), + "dir_info": { + "editable": True, + }, + } + # installer + installer = dist.installer + if dist.installer: + res["installer"] = installer + # requested + if dist.installed_with_dist_info: + res["requested"] = dist.requested + return res diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/install.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/install.py new file mode 100644 index 000000000..232a34a6d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/install.py @@ -0,0 +1,784 @@ +import errno +import json +import operator +import os +import shutil +import site +from optparse import SUPPRESS_HELP, Values +from typing import List, Optional + +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.rich import print_json + +# Eagerly import self_outdated_check to avoid crashes. Otherwise, +# this module would be imported *after* pip was replaced, resulting +# in crashes if the new self_outdated_check module was incompatible +# with the rest of pip that's already imported, or allowing a +# wheel to execute arbitrary code on install by replacing +# self_outdated_check. +import pip._internal.self_outdated_check # noqa: F401 +from pip._internal.cache import WheelCache +from pip._internal.cli import cmdoptions +from pip._internal.cli.cmdoptions import make_target_python +from pip._internal.cli.req_command import ( + RequirementCommand, + with_cleanup, +) +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.exceptions import CommandError, InstallationError +from pip._internal.locations import get_scheme +from pip._internal.metadata import get_environment +from pip._internal.models.installation_report import InstallationReport +from pip._internal.operations.build.build_tracker import get_build_tracker +from pip._internal.operations.check import ConflictDetails, check_install_conflicts +from pip._internal.req import install_given_reqs +from pip._internal.req.req_install import ( + InstallRequirement, + check_legacy_setup_py_options, +) +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.filesystem import test_writable_dir +from pip._internal.utils.logging import getLogger +from pip._internal.utils.misc import ( + check_externally_managed, + ensure_dir, + get_pip_version, + protect_pip_from_modification_on_windows, + warn_if_run_as_root, + write_output, +) +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.virtualenv import ( + running_under_virtualenv, + virtualenv_no_global, +) +from pip._internal.wheel_builder import build, should_build_for_install_command + +logger = getLogger(__name__) + + +class InstallCommand(RequirementCommand): + """ + Install packages from: + + - PyPI (and other indexes) using requirement specifiers. + - VCS project urls. + - Local project directories. + - Local or remote source archives. + + pip also supports installing from "requirements files", which provide + an easy way to specify a whole environment to be installed. + """ + + usage = """ + %prog [options] [package-index-options] ... + %prog [options] -r [package-index-options] ... + %prog [options] [-e] ... + %prog [options] [-e] ... + %prog [options] ...""" + + def add_options(self) -> None: + self.cmd_opts.add_option(cmdoptions.requirements()) + self.cmd_opts.add_option(cmdoptions.constraints()) + self.cmd_opts.add_option(cmdoptions.no_deps()) + self.cmd_opts.add_option(cmdoptions.pre()) + + self.cmd_opts.add_option(cmdoptions.editable()) + self.cmd_opts.add_option( + "--dry-run", + action="store_true", + dest="dry_run", + default=False, + help=( + "Don't actually install anything, just print what would be. " + "Can be used in combination with --ignore-installed " + "to 'resolve' the requirements." + ), + ) + self.cmd_opts.add_option( + "-t", + "--target", + dest="target_dir", + metavar="dir", + default=None, + help=( + "Install packages into . " + "By default this will not replace existing files/folders in " + ". Use --upgrade to replace existing packages in " + "with new versions." + ), + ) + cmdoptions.add_target_python_options(self.cmd_opts) + + self.cmd_opts.add_option( + "--user", + dest="use_user_site", + action="store_true", + help=( + "Install to the Python user install directory for your " + "platform. Typically ~/.local/, or %APPDATA%\\Python on " + "Windows. (See the Python documentation for site.USER_BASE " + "for full details.)" + ), + ) + self.cmd_opts.add_option( + "--no-user", + dest="use_user_site", + action="store_false", + help=SUPPRESS_HELP, + ) + self.cmd_opts.add_option( + "--root", + dest="root_path", + metavar="dir", + default=None, + help="Install everything relative to this alternate root directory.", + ) + self.cmd_opts.add_option( + "--prefix", + dest="prefix_path", + metavar="dir", + default=None, + help=( + "Installation prefix where lib, bin and other top-level " + "folders are placed. Note that the resulting installation may " + "contain scripts and other resources which reference the " + "Python interpreter of pip, and not that of ``--prefix``. " + "See also the ``--python`` option if the intention is to " + "install packages into another (possibly pip-free) " + "environment." + ), + ) + + self.cmd_opts.add_option(cmdoptions.src()) + + self.cmd_opts.add_option( + "-U", + "--upgrade", + dest="upgrade", + action="store_true", + help=( + "Upgrade all specified packages to the newest available " + "version. The handling of dependencies depends on the " + "upgrade-strategy used." + ), + ) + + self.cmd_opts.add_option( + "--upgrade-strategy", + dest="upgrade_strategy", + default="only-if-needed", + choices=["only-if-needed", "eager"], + help=( + "Determines how dependency upgrading should be handled " + "[default: %default]. " + '"eager" - dependencies are upgraded regardless of ' + "whether the currently installed version satisfies the " + "requirements of the upgraded package(s). " + '"only-if-needed" - are upgraded only when they do not ' + "satisfy the requirements of the upgraded package(s)." + ), + ) + + self.cmd_opts.add_option( + "--force-reinstall", + dest="force_reinstall", + action="store_true", + help="Reinstall all packages even if they are already up-to-date.", + ) + + self.cmd_opts.add_option( + "-I", + "--ignore-installed", + dest="ignore_installed", + action="store_true", + help=( + "Ignore the installed packages, overwriting them. " + "This can break your system if the existing package " + "is of a different version or was installed " + "with a different package manager!" + ), + ) + + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + self.cmd_opts.add_option(cmdoptions.no_build_isolation()) + self.cmd_opts.add_option(cmdoptions.use_pep517()) + self.cmd_opts.add_option(cmdoptions.no_use_pep517()) + self.cmd_opts.add_option(cmdoptions.check_build_deps()) + self.cmd_opts.add_option(cmdoptions.override_externally_managed()) + + self.cmd_opts.add_option(cmdoptions.config_settings()) + self.cmd_opts.add_option(cmdoptions.global_options()) + + self.cmd_opts.add_option( + "--compile", + action="store_true", + dest="compile", + default=True, + help="Compile Python source files to bytecode", + ) + + self.cmd_opts.add_option( + "--no-compile", + action="store_false", + dest="compile", + help="Do not compile Python source files to bytecode", + ) + + self.cmd_opts.add_option( + "--no-warn-script-location", + action="store_false", + dest="warn_script_location", + default=True, + help="Do not warn when installing scripts outside PATH", + ) + self.cmd_opts.add_option( + "--no-warn-conflicts", + action="store_false", + dest="warn_about_conflicts", + default=True, + help="Do not warn about broken dependencies", + ) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + self.cmd_opts.add_option(cmdoptions.prefer_binary()) + self.cmd_opts.add_option(cmdoptions.require_hashes()) + self.cmd_opts.add_option(cmdoptions.progress_bar()) + self.cmd_opts.add_option(cmdoptions.root_user_action()) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + self.cmd_opts.add_option( + "--report", + dest="json_report_file", + metavar="file", + default=None, + help=( + "Generate a JSON file describing what pip did to install " + "the provided requirements. " + "Can be used in combination with --dry-run and --ignore-installed " + "to 'resolve' the requirements. " + "When - is used as file name it writes to stdout. " + "When writing to stdout, please combine with the --quiet option " + "to avoid mixing pip logging output with JSON output." + ), + ) + + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: + if options.use_user_site and options.target_dir is not None: + raise CommandError("Can not combine '--user' and '--target'") + + # Check whether the environment we're installing into is externally + # managed, as specified in PEP 668. Specifying --root, --target, or + # --prefix disables the check, since there's no reliable way to locate + # the EXTERNALLY-MANAGED file for those cases. An exception is also + # made specifically for "--dry-run --report" for convenience. + installing_into_current_environment = ( + not (options.dry_run and options.json_report_file) + and options.root_path is None + and options.target_dir is None + and options.prefix_path is None + ) + if ( + installing_into_current_environment + and not options.override_externally_managed + ): + check_externally_managed() + + upgrade_strategy = "to-satisfy-only" + if options.upgrade: + upgrade_strategy = options.upgrade_strategy + + cmdoptions.check_dist_restriction(options, check_target=True) + + logger.verbose("Using %s", get_pip_version()) + options.use_user_site = decide_user_install( + options.use_user_site, + prefix_path=options.prefix_path, + target_dir=options.target_dir, + root_path=options.root_path, + isolated_mode=options.isolated_mode, + ) + + target_temp_dir: Optional[TempDirectory] = None + target_temp_dir_path: Optional[str] = None + if options.target_dir: + options.ignore_installed = True + options.target_dir = os.path.abspath(options.target_dir) + if ( + # fmt: off + os.path.exists(options.target_dir) and + not os.path.isdir(options.target_dir) + # fmt: on + ): + raise CommandError( + "Target path exists but is not a directory, will not continue." + ) + + # Create a target directory for using with the target option + target_temp_dir = TempDirectory(kind="target") + target_temp_dir_path = target_temp_dir.path + self.enter_context(target_temp_dir) + + global_options = options.global_options or [] + + session = self.get_default_session(options) + + target_python = make_target_python(options) + finder = self._build_package_finder( + options=options, + session=session, + target_python=target_python, + ignore_requires_python=options.ignore_requires_python, + ) + build_tracker = self.enter_context(get_build_tracker()) + + directory = TempDirectory( + delete=not options.no_clean, + kind="install", + globally_managed=True, + ) + + try: + reqs = self.get_requirements(args, options, finder, session) + check_legacy_setup_py_options(options, reqs) + + wheel_cache = WheelCache(options.cache_dir) + + # Only when installing is it permitted to use PEP 660. + # In other circumstances (pip wheel, pip download) we generate + # regular (i.e. non editable) metadata and wheels. + for req in reqs: + req.permit_editable_wheels = True + + preparer = self.make_requirement_preparer( + temp_build_dir=directory, + options=options, + build_tracker=build_tracker, + session=session, + finder=finder, + use_user_site=options.use_user_site, + verbosity=self.verbosity, + ) + resolver = self.make_resolver( + preparer=preparer, + finder=finder, + options=options, + wheel_cache=wheel_cache, + use_user_site=options.use_user_site, + ignore_installed=options.ignore_installed, + ignore_requires_python=options.ignore_requires_python, + force_reinstall=options.force_reinstall, + upgrade_strategy=upgrade_strategy, + use_pep517=options.use_pep517, + py_version_info=options.python_version, + ) + + self.trace_basic_info(finder) + + requirement_set = resolver.resolve( + reqs, check_supported_wheels=not options.target_dir + ) + + if options.json_report_file: + report = InstallationReport(requirement_set.requirements_to_install) + if options.json_report_file == "-": + print_json(data=report.to_dict()) + else: + with open(options.json_report_file, "w", encoding="utf-8") as f: + json.dump(report.to_dict(), f, indent=2, ensure_ascii=False) + + if options.dry_run: + would_install_items = sorted( + (r.metadata["name"], r.metadata["version"]) + for r in requirement_set.requirements_to_install + ) + if would_install_items: + write_output( + "Would install %s", + " ".join("-".join(item) for item in would_install_items), + ) + return SUCCESS + + try: + pip_req = requirement_set.get_requirement("pip") + except KeyError: + modifying_pip = False + else: + # If we're not replacing an already installed pip, + # we're not modifying it. + modifying_pip = pip_req.satisfied_by is None + protect_pip_from_modification_on_windows(modifying_pip=modifying_pip) + + reqs_to_build = [ + r + for r in requirement_set.requirements.values() + if should_build_for_install_command(r) + ] + + _, build_failures = build( + reqs_to_build, + wheel_cache=wheel_cache, + verify=True, + build_options=[], + global_options=global_options, + ) + + if build_failures: + raise InstallationError( + "Failed to build installable wheels for some " + "pyproject.toml based projects ({})".format( + ", ".join(r.name for r in build_failures) # type: ignore + ) + ) + + to_install = resolver.get_installation_order(requirement_set) + + # Check for conflicts in the package set we're installing. + conflicts: Optional[ConflictDetails] = None + should_warn_about_conflicts = ( + not options.ignore_dependencies and options.warn_about_conflicts + ) + if should_warn_about_conflicts: + conflicts = self._determine_conflicts(to_install) + + # Don't warn about script install locations if + # --target or --prefix has been specified + warn_script_location = options.warn_script_location + if options.target_dir or options.prefix_path: + warn_script_location = False + + installed = install_given_reqs( + to_install, + global_options, + root=options.root_path, + home=target_temp_dir_path, + prefix=options.prefix_path, + warn_script_location=warn_script_location, + use_user_site=options.use_user_site, + pycompile=options.compile, + ) + + lib_locations = get_lib_location_guesses( + user=options.use_user_site, + home=target_temp_dir_path, + root=options.root_path, + prefix=options.prefix_path, + isolated=options.isolated_mode, + ) + env = get_environment(lib_locations) + + # Display a summary of installed packages, with extra care to + # display a package name as it was requested by the user. + installed.sort(key=operator.attrgetter("name")) + summary = [] + installed_versions = {} + for distribution in env.iter_all_distributions(): + installed_versions[distribution.canonical_name] = distribution.version + for package in installed: + display_name = package.name + version = installed_versions.get(canonicalize_name(display_name), None) + if version: + text = f"{display_name}-{version}" + else: + text = display_name + summary.append(text) + + if conflicts is not None: + self._warn_about_conflicts( + conflicts, + resolver_variant=self.determine_resolver_variant(options), + ) + + installed_desc = " ".join(summary) + if installed_desc: + write_output( + "Successfully installed %s", + installed_desc, + ) + except OSError as error: + show_traceback = self.verbosity >= 1 + + message = create_os_error_message( + error, + show_traceback, + options.use_user_site, + ) + logger.error(message, exc_info=show_traceback) + + return ERROR + + if options.target_dir: + assert target_temp_dir + self._handle_target_dir( + options.target_dir, target_temp_dir, options.upgrade + ) + if options.root_user_action == "warn": + warn_if_run_as_root() + return SUCCESS + + def _handle_target_dir( + self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool + ) -> None: + ensure_dir(target_dir) + + # Checking both purelib and platlib directories for installed + # packages to be moved to target directory + lib_dir_list = [] + + # Checking both purelib and platlib directories for installed + # packages to be moved to target directory + scheme = get_scheme("", home=target_temp_dir.path) + purelib_dir = scheme.purelib + platlib_dir = scheme.platlib + data_dir = scheme.data + + if os.path.exists(purelib_dir): + lib_dir_list.append(purelib_dir) + if os.path.exists(platlib_dir) and platlib_dir != purelib_dir: + lib_dir_list.append(platlib_dir) + if os.path.exists(data_dir): + lib_dir_list.append(data_dir) + + for lib_dir in lib_dir_list: + for item in os.listdir(lib_dir): + if lib_dir == data_dir: + ddir = os.path.join(data_dir, item) + if any(s.startswith(ddir) for s in lib_dir_list[:-1]): + continue + target_item_dir = os.path.join(target_dir, item) + if os.path.exists(target_item_dir): + if not upgrade: + logger.warning( + "Target directory %s already exists. Specify " + "--upgrade to force replacement.", + target_item_dir, + ) + continue + if os.path.islink(target_item_dir): + logger.warning( + "Target directory %s already exists and is " + "a link. pip will not automatically replace " + "links, please remove if replacement is " + "desired.", + target_item_dir, + ) + continue + if os.path.isdir(target_item_dir): + shutil.rmtree(target_item_dir) + else: + os.remove(target_item_dir) + + shutil.move(os.path.join(lib_dir, item), target_item_dir) + + def _determine_conflicts( + self, to_install: List[InstallRequirement] + ) -> Optional[ConflictDetails]: + try: + return check_install_conflicts(to_install) + except Exception: + logger.exception( + "Error while checking for conflicts. Please file an issue on " + "pip's issue tracker: https://github.com/pypa/pip/issues/new" + ) + return None + + def _warn_about_conflicts( + self, conflict_details: ConflictDetails, resolver_variant: str + ) -> None: + package_set, (missing, conflicting) = conflict_details + if not missing and not conflicting: + return + + parts: List[str] = [] + if resolver_variant == "legacy": + parts.append( + "pip's legacy dependency resolver does not consider dependency " + "conflicts when selecting packages. This behaviour is the " + "source of the following dependency conflicts." + ) + else: + assert resolver_variant == "resolvelib" + parts.append( + "pip's dependency resolver does not currently take into account " + "all the packages that are installed. This behaviour is the " + "source of the following dependency conflicts." + ) + + # NOTE: There is some duplication here, with commands/check.py + for project_name in missing: + version = package_set[project_name][0] + for dependency in missing[project_name]: + message = ( + f"{project_name} {version} requires {dependency[1]}, " + "which is not installed." + ) + parts.append(message) + + for project_name in conflicting: + version = package_set[project_name][0] + for dep_name, dep_version, req in conflicting[project_name]: + message = ( + "{name} {version} requires {requirement}, but {you} have " + "{dep_name} {dep_version} which is incompatible." + ).format( + name=project_name, + version=version, + requirement=req, + dep_name=dep_name, + dep_version=dep_version, + you=("you" if resolver_variant == "resolvelib" else "you'll"), + ) + parts.append(message) + + logger.critical("\n".join(parts)) + + +def get_lib_location_guesses( + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + isolated: bool = False, + prefix: Optional[str] = None, +) -> List[str]: + scheme = get_scheme( + "", + user=user, + home=home, + root=root, + isolated=isolated, + prefix=prefix, + ) + return [scheme.purelib, scheme.platlib] + + +def site_packages_writable(root: Optional[str], isolated: bool) -> bool: + return all( + test_writable_dir(d) + for d in set(get_lib_location_guesses(root=root, isolated=isolated)) + ) + + +def decide_user_install( + use_user_site: Optional[bool], + prefix_path: Optional[str] = None, + target_dir: Optional[str] = None, + root_path: Optional[str] = None, + isolated_mode: bool = False, +) -> bool: + """Determine whether to do a user install based on the input options. + + If use_user_site is False, no additional checks are done. + If use_user_site is True, it is checked for compatibility with other + options. + If use_user_site is None, the default behaviour depends on the environment, + which is provided by the other arguments. + """ + # In some cases (config from tox), use_user_site can be set to an integer + # rather than a bool, which 'use_user_site is False' wouldn't catch. + if (use_user_site is not None) and (not use_user_site): + logger.debug("Non-user install by explicit request") + return False + + if use_user_site: + if prefix_path: + raise CommandError( + "Can not combine '--user' and '--prefix' as they imply " + "different installation locations" + ) + if virtualenv_no_global(): + raise InstallationError( + "Can not perform a '--user' install. User site-packages " + "are not visible in this virtualenv." + ) + logger.debug("User install by explicit request") + return True + + # If we are here, user installs have not been explicitly requested/avoided + assert use_user_site is None + + # user install incompatible with --prefix/--target + if prefix_path or target_dir: + logger.debug("Non-user install due to --prefix or --target option") + return False + + # If user installs are not enabled, choose a non-user install + if not site.ENABLE_USER_SITE: + logger.debug("Non-user install because user site-packages disabled") + return False + + # If we have permission for a non-user install, do that, + # otherwise do a user install. + if site_packages_writable(root=root_path, isolated=isolated_mode): + logger.debug("Non-user install because site-packages writeable") + return False + + logger.info( + "Defaulting to user installation because normal site-packages " + "is not writeable" + ) + return True + + +def create_os_error_message( + error: OSError, show_traceback: bool, using_user_site: bool +) -> str: + """Format an error message for an OSError + + It may occur anytime during the execution of the install command. + """ + parts = [] + + # Mention the error if we are not going to show a traceback + parts.append("Could not install packages due to an OSError") + if not show_traceback: + parts.append(": ") + parts.append(str(error)) + else: + parts.append(".") + + # Spilt the error indication from a helper message (if any) + parts[-1] += "\n" + + # Suggest useful actions to the user: + # (1) using user site-packages or (2) verifying the permissions + if error.errno == errno.EACCES: + user_option_part = "Consider using the `--user` option" + permissions_part = "Check the permissions" + + if not running_under_virtualenv() and not using_user_site: + parts.extend( + [ + user_option_part, + " or ", + permissions_part.lower(), + ] + ) + else: + parts.append(permissions_part) + parts.append(".\n") + + # Suggest the user to enable Long Paths if path length is + # more than 260 + if ( + WINDOWS + and error.errno == errno.ENOENT + and error.filename + and len(error.filename) > 260 + ): + parts.append( + "HINT: This error might have occurred since " + "this system does not have Windows Long Path " + "support enabled. You can find information on " + "how to enable this at " + "https://pip.pypa.io/warnings/enable-long-paths\n" + ) + + return "".join(parts).strip() + "\n" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/list.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/list.py new file mode 100644 index 000000000..849437024 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/list.py @@ -0,0 +1,375 @@ +import json +import logging +from optparse import Values +from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast + +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import Version + +from pip._internal.cli import cmdoptions +from pip._internal.cli.index_command import IndexGroupCommand +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.exceptions import CommandError +from pip._internal.metadata import BaseDistribution, get_environment +from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.utils.compat import stdlib_pkgs +from pip._internal.utils.misc import tabulate, write_output + +if TYPE_CHECKING: + from pip._internal.index.package_finder import PackageFinder + from pip._internal.network.session import PipSession + + class _DistWithLatestInfo(BaseDistribution): + """Give the distribution object a couple of extra fields. + + These will be populated during ``get_outdated()``. This is dirty but + makes the rest of the code much cleaner. + """ + + latest_version: Version + latest_filetype: str + + _ProcessedDists = Sequence[_DistWithLatestInfo] + + +logger = logging.getLogger(__name__) + + +class ListCommand(IndexGroupCommand): + """ + List installed packages, including editables. + + Packages are listed in a case-insensitive sorted order. + """ + + ignore_require_venv = True + usage = """ + %prog [options]""" + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-o", + "--outdated", + action="store_true", + default=False, + help="List outdated packages", + ) + self.cmd_opts.add_option( + "-u", + "--uptodate", + action="store_true", + default=False, + help="List uptodate packages", + ) + self.cmd_opts.add_option( + "-e", + "--editable", + action="store_true", + default=False, + help="List editable projects.", + ) + self.cmd_opts.add_option( + "-l", + "--local", + action="store_true", + default=False, + help=( + "If in a virtualenv that has global access, do not list " + "globally-installed packages." + ), + ) + self.cmd_opts.add_option( + "--user", + dest="user", + action="store_true", + default=False, + help="Only output packages installed in user-site.", + ) + self.cmd_opts.add_option(cmdoptions.list_path()) + self.cmd_opts.add_option( + "--pre", + action="store_true", + default=False, + help=( + "Include pre-release and development versions. By default, " + "pip only finds stable versions." + ), + ) + + self.cmd_opts.add_option( + "--format", + action="store", + dest="list_format", + default="columns", + choices=("columns", "freeze", "json"), + help=( + "Select the output format among: columns (default), freeze, or json. " + "The 'freeze' format cannot be used with the --outdated option." + ), + ) + + self.cmd_opts.add_option( + "--not-required", + action="store_true", + dest="not_required", + help="List packages that are not dependencies of installed packages.", + ) + + self.cmd_opts.add_option( + "--exclude-editable", + action="store_false", + dest="include_editable", + help="Exclude editable package from output.", + ) + self.cmd_opts.add_option( + "--include-editable", + action="store_true", + dest="include_editable", + help="Include editable package from output.", + default=True, + ) + self.cmd_opts.add_option(cmdoptions.list_exclude()) + index_opts = cmdoptions.make_option_group(cmdoptions.index_group, self.parser) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + def handle_pip_version_check(self, options: Values) -> None: + if options.outdated or options.uptodate: + super().handle_pip_version_check(options) + + def _build_package_finder( + self, options: Values, session: "PipSession" + ) -> "PackageFinder": + """ + Create a package finder appropriate to this list command. + """ + # Lazy import the heavy index modules as most list invocations won't need 'em. + from pip._internal.index.collector import LinkCollector + from pip._internal.index.package_finder import PackageFinder + + link_collector = LinkCollector.create(session, options=options) + + # Pass allow_yanked=False to ignore yanked versions. + selection_prefs = SelectionPreferences( + allow_yanked=False, + allow_all_prereleases=options.pre, + ) + + return PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + ) + + def run(self, options: Values, args: List[str]) -> int: + if options.outdated and options.uptodate: + raise CommandError("Options --outdated and --uptodate cannot be combined.") + + if options.outdated and options.list_format == "freeze": + raise CommandError( + "List format 'freeze' cannot be used with the --outdated option." + ) + + cmdoptions.check_list_path_option(options) + + skip = set(stdlib_pkgs) + if options.excludes: + skip.update(canonicalize_name(n) for n in options.excludes) + + packages: _ProcessedDists = [ + cast("_DistWithLatestInfo", d) + for d in get_environment(options.path).iter_installed_distributions( + local_only=options.local, + user_only=options.user, + editables_only=options.editable, + include_editables=options.include_editable, + skip=skip, + ) + ] + + # get_not_required must be called firstly in order to find and + # filter out all dependencies correctly. Otherwise a package + # can't be identified as requirement because some parent packages + # could be filtered out before. + if options.not_required: + packages = self.get_not_required(packages, options) + + if options.outdated: + packages = self.get_outdated(packages, options) + elif options.uptodate: + packages = self.get_uptodate(packages, options) + + self.output_package_listing(packages, options) + return SUCCESS + + def get_outdated( + self, packages: "_ProcessedDists", options: Values + ) -> "_ProcessedDists": + return [ + dist + for dist in self.iter_packages_latest_infos(packages, options) + if dist.latest_version > dist.version + ] + + def get_uptodate( + self, packages: "_ProcessedDists", options: Values + ) -> "_ProcessedDists": + return [ + dist + for dist in self.iter_packages_latest_infos(packages, options) + if dist.latest_version == dist.version + ] + + def get_not_required( + self, packages: "_ProcessedDists", options: Values + ) -> "_ProcessedDists": + dep_keys = { + canonicalize_name(dep.name) + for dist in packages + for dep in (dist.iter_dependencies() or ()) + } + + # Create a set to remove duplicate packages, and cast it to a list + # to keep the return type consistent with get_outdated and + # get_uptodate + return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys}) + + def iter_packages_latest_infos( + self, packages: "_ProcessedDists", options: Values + ) -> Generator["_DistWithLatestInfo", None, None]: + with self._build_session(options) as session: + finder = self._build_package_finder(options, session) + + def latest_info( + dist: "_DistWithLatestInfo", + ) -> Optional["_DistWithLatestInfo"]: + all_candidates = finder.find_all_candidates(dist.canonical_name) + if not options.pre: + # Remove prereleases + all_candidates = [ + candidate + for candidate in all_candidates + if not candidate.version.is_prerelease + ] + + evaluator = finder.make_candidate_evaluator( + project_name=dist.canonical_name, + ) + best_candidate = evaluator.sort_best_candidate(all_candidates) + if best_candidate is None: + return None + + remote_version = best_candidate.version + if best_candidate.link.is_wheel: + typ = "wheel" + else: + typ = "sdist" + dist.latest_version = remote_version + dist.latest_filetype = typ + return dist + + for dist in map(latest_info, packages): + if dist is not None: + yield dist + + def output_package_listing( + self, packages: "_ProcessedDists", options: Values + ) -> None: + packages = sorted( + packages, + key=lambda dist: dist.canonical_name, + ) + if options.list_format == "columns" and packages: + data, header = format_for_columns(packages, options) + self.output_package_listing_columns(data, header) + elif options.list_format == "freeze": + for dist in packages: + if options.verbose >= 1: + write_output( + "%s==%s (%s)", dist.raw_name, dist.version, dist.location + ) + else: + write_output("%s==%s", dist.raw_name, dist.version) + elif options.list_format == "json": + write_output(format_for_json(packages, options)) + + def output_package_listing_columns( + self, data: List[List[str]], header: List[str] + ) -> None: + # insert the header first: we need to know the size of column names + if len(data) > 0: + data.insert(0, header) + + pkg_strings, sizes = tabulate(data) + + # Create and add a separator. + if len(data) > 0: + pkg_strings.insert(1, " ".join("-" * x for x in sizes)) + + for val in pkg_strings: + write_output(val) + + +def format_for_columns( + pkgs: "_ProcessedDists", options: Values +) -> Tuple[List[List[str]], List[str]]: + """ + Convert the package data into something usable + by output_package_listing_columns. + """ + header = ["Package", "Version"] + + running_outdated = options.outdated + if running_outdated: + header.extend(["Latest", "Type"]) + + has_editables = any(x.editable for x in pkgs) + if has_editables: + header.append("Editable project location") + + if options.verbose >= 1: + header.append("Location") + if options.verbose >= 1: + header.append("Installer") + + data = [] + for proj in pkgs: + # if we're working on the 'outdated' list, separate out the + # latest_version and type + row = [proj.raw_name, proj.raw_version] + + if running_outdated: + row.append(str(proj.latest_version)) + row.append(proj.latest_filetype) + + if has_editables: + row.append(proj.editable_project_location or "") + + if options.verbose >= 1: + row.append(proj.location or "") + if options.verbose >= 1: + row.append(proj.installer) + + data.append(row) + + return data, header + + +def format_for_json(packages: "_ProcessedDists", options: Values) -> str: + data = [] + for dist in packages: + info = { + "name": dist.raw_name, + "version": str(dist.version), + } + if options.verbose >= 1: + info["location"] = dist.location or "" + info["installer"] = dist.installer + if options.outdated: + info["latest_version"] = str(dist.latest_version) + info["latest_filetype"] = dist.latest_filetype + editable_project_location = dist.editable_project_location + if editable_project_location: + info["editable_project_location"] = editable_project_location + data.append(info) + return json.dumps(data) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/search.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/search.py new file mode 100644 index 000000000..74b8d656b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/search.py @@ -0,0 +1,172 @@ +import logging +import shutil +import sys +import textwrap +import xmlrpc.client +from collections import OrderedDict +from optparse import Values +from typing import TYPE_CHECKING, Dict, List, Optional, TypedDict + +from pip._vendor.packaging.version import parse as parse_version + +from pip._internal.cli.base_command import Command +from pip._internal.cli.req_command import SessionCommandMixin +from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS +from pip._internal.exceptions import CommandError +from pip._internal.metadata import get_default_environment +from pip._internal.models.index import PyPI +from pip._internal.network.xmlrpc import PipXmlrpcTransport +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import write_output + +if TYPE_CHECKING: + + class TransformedHit(TypedDict): + name: str + summary: str + versions: List[str] + + +logger = logging.getLogger(__name__) + + +class SearchCommand(Command, SessionCommandMixin): + """Search for PyPI packages whose name or summary contains .""" + + usage = """ + %prog [options] """ + ignore_require_venv = True + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-i", + "--index", + dest="index", + metavar="URL", + default=PyPI.pypi_url, + help="Base URL of Python Package Index (default %default)", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + if not args: + raise CommandError("Missing required argument (search query).") + query = args + pypi_hits = self.search(query, options) + hits = transform_hits(pypi_hits) + + terminal_width = None + if sys.stdout.isatty(): + terminal_width = shutil.get_terminal_size()[0] + + print_results(hits, terminal_width=terminal_width) + if pypi_hits: + return SUCCESS + return NO_MATCHES_FOUND + + def search(self, query: List[str], options: Values) -> List[Dict[str, str]]: + index_url = options.index + + session = self.get_default_session(options) + + transport = PipXmlrpcTransport(index_url, session) + pypi = xmlrpc.client.ServerProxy(index_url, transport) + try: + hits = pypi.search({"name": query, "summary": query}, "or") + except xmlrpc.client.Fault as fault: + message = ( + f"XMLRPC request failed [code: {fault.faultCode}]\n{fault.faultString}" + ) + raise CommandError(message) + assert isinstance(hits, list) + return hits + + +def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]: + """ + The list from pypi is really a list of versions. We want a list of + packages with the list of versions stored inline. This converts the + list from pypi into one we can use. + """ + packages: Dict[str, TransformedHit] = OrderedDict() + for hit in hits: + name = hit["name"] + summary = hit["summary"] + version = hit["version"] + + if name not in packages.keys(): + packages[name] = { + "name": name, + "summary": summary, + "versions": [version], + } + else: + packages[name]["versions"].append(version) + + # if this is the highest version, replace summary and score + if version == highest_version(packages[name]["versions"]): + packages[name]["summary"] = summary + + return list(packages.values()) + + +def print_dist_installation_info(name: str, latest: str) -> None: + env = get_default_environment() + dist = env.get_distribution(name) + if dist is not None: + with indent_log(): + if dist.version == latest: + write_output("INSTALLED: %s (latest)", dist.version) + else: + write_output("INSTALLED: %s", dist.version) + if parse_version(latest).pre: + write_output( + "LATEST: %s (pre-release; install" + " with `pip install --pre`)", + latest, + ) + else: + write_output("LATEST: %s", latest) + + +def print_results( + hits: List["TransformedHit"], + name_column_width: Optional[int] = None, + terminal_width: Optional[int] = None, +) -> None: + if not hits: + return + if name_column_width is None: + name_column_width = ( + max( + [ + len(hit["name"]) + len(highest_version(hit.get("versions", ["-"]))) + for hit in hits + ] + ) + + 4 + ) + + for hit in hits: + name = hit["name"] + summary = hit["summary"] or "" + latest = highest_version(hit.get("versions", ["-"])) + if terminal_width is not None: + target_width = terminal_width - name_column_width - 5 + if target_width > 10: + # wrap and indent summary to fit terminal + summary_lines = textwrap.wrap(summary, target_width) + summary = ("\n" + " " * (name_column_width + 3)).join(summary_lines) + + name_latest = f"{name} ({latest})" + line = f"{name_latest:{name_column_width}} - {summary}" + try: + write_output(line) + print_dist_installation_info(name, latest) + except UnicodeEncodeError: + pass + + +def highest_version(versions: List[str]) -> str: + return max(versions, key=parse_version) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/show.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/show.py new file mode 100644 index 000000000..b47500cf8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/show.py @@ -0,0 +1,224 @@ +import logging +from optparse import Values +from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional + +from pip._vendor.packaging.requirements import InvalidRequirement +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.metadata import BaseDistribution, get_default_environment +from pip._internal.utils.misc import write_output + +logger = logging.getLogger(__name__) + + +class ShowCommand(Command): + """ + Show information about one or more installed packages. + + The output is in RFC-compliant mail header format. + """ + + usage = """ + %prog [options] ...""" + ignore_require_venv = True + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-f", + "--files", + dest="files", + action="store_true", + default=False, + help="Show the full list of installed files for each package.", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + if not args: + logger.warning("ERROR: Please provide a package name or names.") + return ERROR + query = args + + results = search_packages_info(query) + if not print_results( + results, list_files=options.files, verbose=options.verbose + ): + return ERROR + return SUCCESS + + +class _PackageInfo(NamedTuple): + name: str + version: str + location: str + editable_project_location: Optional[str] + requires: List[str] + required_by: List[str] + installer: str + metadata_version: str + classifiers: List[str] + summary: str + homepage: str + project_urls: List[str] + author: str + author_email: str + license: str + license_expression: str + entry_points: List[str] + files: Optional[List[str]] + + +def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]: + """ + Gather details from installed distributions. Print distribution name, + version, location, and installed files. Installed files requires a + pip generated 'installed-files.txt' in the distributions '.egg-info' + directory. + """ + env = get_default_environment() + + installed = {dist.canonical_name: dist for dist in env.iter_all_distributions()} + query_names = [canonicalize_name(name) for name in query] + missing = sorted( + [name for name, pkg in zip(query, query_names) if pkg not in installed] + ) + if missing: + logger.warning("Package(s) not found: %s", ", ".join(missing)) + + def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]: + return ( + dist.metadata["Name"] or "UNKNOWN" + for dist in installed.values() + if current_dist.canonical_name + in {canonicalize_name(d.name) for d in dist.iter_dependencies()} + ) + + for query_name in query_names: + try: + dist = installed[query_name] + except KeyError: + continue + + try: + requires = sorted( + # Avoid duplicates in requirements (e.g. due to environment markers). + {req.name for req in dist.iter_dependencies()}, + key=str.lower, + ) + except InvalidRequirement: + requires = sorted(dist.iter_raw_dependencies(), key=str.lower) + + try: + required_by = sorted(_get_requiring_packages(dist), key=str.lower) + except InvalidRequirement: + required_by = ["#N/A"] + + try: + entry_points_text = dist.read_text("entry_points.txt") + entry_points = entry_points_text.splitlines(keepends=False) + except FileNotFoundError: + entry_points = [] + + files_iter = dist.iter_declared_entries() + if files_iter is None: + files: Optional[List[str]] = None + else: + files = sorted(files_iter) + + metadata = dist.metadata + + project_urls = metadata.get_all("Project-URL", []) + homepage = metadata.get("Home-page", "") + if not homepage: + # It's common that there is a "homepage" Project-URL, but Home-page + # remains unset (especially as PEP 621 doesn't surface the field). + # + # This logic was taken from PyPI's codebase. + for url in project_urls: + url_label, url = url.split(",", maxsplit=1) + normalized_label = ( + url_label.casefold().replace("-", "").replace("_", "").strip() + ) + if normalized_label == "homepage": + homepage = url.strip() + break + + yield _PackageInfo( + name=dist.raw_name, + version=dist.raw_version, + location=dist.location or "", + editable_project_location=dist.editable_project_location, + requires=requires, + required_by=required_by, + installer=dist.installer, + metadata_version=dist.metadata_version or "", + classifiers=metadata.get_all("Classifier", []), + summary=metadata.get("Summary", ""), + homepage=homepage, + project_urls=project_urls, + author=metadata.get("Author", ""), + author_email=metadata.get("Author-email", ""), + license=metadata.get("License", ""), + license_expression=metadata.get("License-Expression", ""), + entry_points=entry_points, + files=files, + ) + + +def print_results( + distributions: Iterable[_PackageInfo], + list_files: bool, + verbose: bool, +) -> bool: + """ + Print the information from installed distributions found. + """ + results_printed = False + for i, dist in enumerate(distributions): + results_printed = True + if i > 0: + write_output("---") + + metadata_version_tuple = tuple(map(int, dist.metadata_version.split("."))) + + write_output("Name: %s", dist.name) + write_output("Version: %s", dist.version) + write_output("Summary: %s", dist.summary) + write_output("Home-page: %s", dist.homepage) + write_output("Author: %s", dist.author) + write_output("Author-email: %s", dist.author_email) + if metadata_version_tuple >= (2, 4) and dist.license_expression: + write_output("License-Expression: %s", dist.license_expression) + else: + write_output("License: %s", dist.license) + write_output("Location: %s", dist.location) + if dist.editable_project_location is not None: + write_output( + "Editable project location: %s", dist.editable_project_location + ) + write_output("Requires: %s", ", ".join(dist.requires)) + write_output("Required-by: %s", ", ".join(dist.required_by)) + + if verbose: + write_output("Metadata-Version: %s", dist.metadata_version) + write_output("Installer: %s", dist.installer) + write_output("Classifiers:") + for classifier in dist.classifiers: + write_output(" %s", classifier) + write_output("Entry-points:") + for entry in dist.entry_points: + write_output(" %s", entry.strip()) + write_output("Project-URLs:") + for project_url in dist.project_urls: + write_output(" %s", project_url) + if list_files: + write_output("Files:") + if dist.files is None: + write_output("Cannot locate RECORD or installed-files.txt") + else: + for line in dist.files: + write_output(" %s", line.strip()) + return results_printed diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/uninstall.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/uninstall.py new file mode 100644 index 000000000..bc0edeac9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/uninstall.py @@ -0,0 +1,114 @@ +import logging +from optparse import Values +from typing import List + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.cli.index_command import SessionCommandMixin +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.exceptions import InstallationError +from pip._internal.req import parse_requirements +from pip._internal.req.constructors import ( + install_req_from_line, + install_req_from_parsed_requirement, +) +from pip._internal.utils.misc import ( + check_externally_managed, + protect_pip_from_modification_on_windows, + warn_if_run_as_root, +) + +logger = logging.getLogger(__name__) + + +class UninstallCommand(Command, SessionCommandMixin): + """ + Uninstall packages. + + pip is able to uninstall most installed packages. Known exceptions are: + + - Pure distutils packages installed with ``python setup.py install``, which + leave behind no metadata to determine what files were installed. + - Script wrappers installed by ``python setup.py develop``. + """ + + usage = """ + %prog [options] ... + %prog [options] -r ...""" + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-r", + "--requirement", + dest="requirements", + action="append", + default=[], + metavar="file", + help=( + "Uninstall all the packages listed in the given requirements " + "file. This option can be used multiple times." + ), + ) + self.cmd_opts.add_option( + "-y", + "--yes", + dest="yes", + action="store_true", + help="Don't ask for confirmation of uninstall deletions.", + ) + self.cmd_opts.add_option(cmdoptions.root_user_action()) + self.cmd_opts.add_option(cmdoptions.override_externally_managed()) + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + session = self.get_default_session(options) + + reqs_to_uninstall = {} + for name in args: + req = install_req_from_line( + name, + isolated=options.isolated_mode, + ) + if req.name: + reqs_to_uninstall[canonicalize_name(req.name)] = req + else: + logger.warning( + "Invalid requirement: %r ignored -" + " the uninstall command expects named" + " requirements.", + name, + ) + for filename in options.requirements: + for parsed_req in parse_requirements( + filename, options=options, session=session + ): + req = install_req_from_parsed_requirement( + parsed_req, isolated=options.isolated_mode + ) + if req.name: + reqs_to_uninstall[canonicalize_name(req.name)] = req + if not reqs_to_uninstall: + raise InstallationError( + f"You must give at least one requirement to {self.name} (see " + f'"pip help {self.name}")' + ) + + if not options.override_externally_managed: + check_externally_managed() + + protect_pip_from_modification_on_windows( + modifying_pip="pip" in reqs_to_uninstall + ) + + for req in reqs_to_uninstall.values(): + uninstall_pathset = req.uninstall( + auto_confirm=options.yes, + verbose=self.verbosity > 0, + ) + if uninstall_pathset: + uninstall_pathset.commit() + if options.root_user_action == "warn": + warn_if_run_as_root() + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/commands/wheel.py b/venv/lib/python3.8/site-packages/pip/_internal/commands/wheel.py new file mode 100644 index 000000000..278719f4e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/commands/wheel.py @@ -0,0 +1,182 @@ +import logging +import os +import shutil +from optparse import Values +from typing import List + +from pip._internal.cache import WheelCache +from pip._internal.cli import cmdoptions +from pip._internal.cli.req_command import RequirementCommand, with_cleanup +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.exceptions import CommandError +from pip._internal.operations.build.build_tracker import get_build_tracker +from pip._internal.req.req_install import ( + InstallRequirement, + check_legacy_setup_py_options, +) +from pip._internal.utils.misc import ensure_dir, normalize_path +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.wheel_builder import build, should_build_for_wheel_command + +logger = logging.getLogger(__name__) + + +class WheelCommand(RequirementCommand): + """ + Build Wheel archives for your requirements and dependencies. + + Wheel is a built-package format, and offers the advantage of not + recompiling your software during every install. For more details, see the + wheel docs: https://wheel.readthedocs.io/en/latest/ + + 'pip wheel' uses the build system interface as described here: + https://pip.pypa.io/en/stable/reference/build-system/ + + """ + + usage = """ + %prog [options] ... + %prog [options] -r ... + %prog [options] [-e] ... + %prog [options] [-e] ... + %prog [options] ...""" + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-w", + "--wheel-dir", + dest="wheel_dir", + metavar="dir", + default=os.curdir, + help=( + "Build wheels into , where the default is the " + "current working directory." + ), + ) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + self.cmd_opts.add_option(cmdoptions.prefer_binary()) + self.cmd_opts.add_option(cmdoptions.no_build_isolation()) + self.cmd_opts.add_option(cmdoptions.use_pep517()) + self.cmd_opts.add_option(cmdoptions.no_use_pep517()) + self.cmd_opts.add_option(cmdoptions.check_build_deps()) + self.cmd_opts.add_option(cmdoptions.constraints()) + self.cmd_opts.add_option(cmdoptions.editable()) + self.cmd_opts.add_option(cmdoptions.requirements()) + self.cmd_opts.add_option(cmdoptions.src()) + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + self.cmd_opts.add_option(cmdoptions.no_deps()) + self.cmd_opts.add_option(cmdoptions.progress_bar()) + + self.cmd_opts.add_option( + "--no-verify", + dest="no_verify", + action="store_true", + default=False, + help="Don't verify if built wheel is valid.", + ) + + self.cmd_opts.add_option(cmdoptions.config_settings()) + self.cmd_opts.add_option(cmdoptions.build_options()) + self.cmd_opts.add_option(cmdoptions.global_options()) + + self.cmd_opts.add_option( + "--pre", + action="store_true", + default=False, + help=( + "Include pre-release and development versions. By default, " + "pip only finds stable versions." + ), + ) + + self.cmd_opts.add_option(cmdoptions.require_hashes()) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: + session = self.get_default_session(options) + + finder = self._build_package_finder(options, session) + + options.wheel_dir = normalize_path(options.wheel_dir) + ensure_dir(options.wheel_dir) + + build_tracker = self.enter_context(get_build_tracker()) + + directory = TempDirectory( + delete=not options.no_clean, + kind="wheel", + globally_managed=True, + ) + + reqs = self.get_requirements(args, options, finder, session) + check_legacy_setup_py_options(options, reqs) + + wheel_cache = WheelCache(options.cache_dir) + + preparer = self.make_requirement_preparer( + temp_build_dir=directory, + options=options, + build_tracker=build_tracker, + session=session, + finder=finder, + download_dir=options.wheel_dir, + use_user_site=False, + verbosity=self.verbosity, + ) + + resolver = self.make_resolver( + preparer=preparer, + finder=finder, + options=options, + wheel_cache=wheel_cache, + ignore_requires_python=options.ignore_requires_python, + use_pep517=options.use_pep517, + ) + + self.trace_basic_info(finder) + + requirement_set = resolver.resolve(reqs, check_supported_wheels=True) + + reqs_to_build: List[InstallRequirement] = [] + for req in requirement_set.requirements.values(): + if req.is_wheel: + preparer.save_linked_requirement(req) + elif should_build_for_wheel_command(req): + reqs_to_build.append(req) + + preparer.prepare_linked_requirements_more(requirement_set.requirements.values()) + + # build wheels + build_successes, build_failures = build( + reqs_to_build, + wheel_cache=wheel_cache, + verify=(not options.no_verify), + build_options=options.build_options or [], + global_options=options.global_options or [], + ) + for req in build_successes: + assert req.link and req.link.is_wheel + assert req.local_file_path + # copy from cache to target directory + try: + shutil.copy(req.local_file_path, options.wheel_dir) + except OSError as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, + e, + ) + build_failures.append(req) + if len(build_failures) != 0: + raise CommandError("Failed to build one or more wheels") + + return SUCCESS diff --git a/venv/lib/python3.8/site-packages/pip/_internal/configuration.py b/venv/lib/python3.8/site-packages/pip/_internal/configuration.py new file mode 100644 index 000000000..ffeda1d47 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/configuration.py @@ -0,0 +1,383 @@ +"""Configuration management setup + +Some terminology: +- name + As written in config files. +- value + Value associated with a name +- key + Name combined with it's section (section.name) +- variant + A single word describing where the configuration key-value pair came from +""" + +import configparser +import locale +import os +import sys +from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple + +from pip._internal.exceptions import ( + ConfigurationError, + ConfigurationFileCouldNotBeLoaded, +) +from pip._internal.utils import appdirs +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.logging import getLogger +from pip._internal.utils.misc import ensure_dir, enum + +RawConfigParser = configparser.RawConfigParser # Shorthand +Kind = NewType("Kind", str) + +CONFIG_BASENAME = "pip.ini" if WINDOWS else "pip.conf" +ENV_NAMES_IGNORED = "version", "help" + +# The kinds of configurations there are. +kinds = enum( + USER="user", # User Specific + GLOBAL="global", # System Wide + SITE="site", # [Virtual] Environment Specific + ENV="env", # from PIP_CONFIG_FILE + ENV_VAR="env-var", # from Environment Variables +) +OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR +VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE + +logger = getLogger(__name__) + + +# NOTE: Maybe use the optionx attribute to normalize keynames. +def _normalize_name(name: str) -> str: + """Make a name consistent regardless of source (environment or file)""" + name = name.lower().replace("_", "-") + if name.startswith("--"): + name = name[2:] # only prefer long opts + return name + + +def _disassemble_key(name: str) -> List[str]: + if "." not in name: + error_message = ( + "Key does not contain dot separated section and key. " + f"Perhaps you wanted to use 'global.{name}' instead?" + ) + raise ConfigurationError(error_message) + return name.split(".", 1) + + +def get_configuration_files() -> Dict[Kind, List[str]]: + global_config_files = [ + os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip") + ] + + site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME) + legacy_config_file = os.path.join( + os.path.expanduser("~"), + "pip" if WINDOWS else ".pip", + CONFIG_BASENAME, + ) + new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME) + return { + kinds.GLOBAL: global_config_files, + kinds.SITE: [site_config_file], + kinds.USER: [legacy_config_file, new_config_file], + } + + +class Configuration: + """Handles management of configuration. + + Provides an interface to accessing and managing configuration files. + + This class converts provides an API that takes "section.key-name" style + keys and stores the value associated with it as "key-name" under the + section "section". + + This allows for a clean interface wherein the both the section and the + key-name are preserved in an easy to manage form in the configuration files + and the data stored is also nice. + """ + + def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None: + super().__init__() + + if load_only is not None and load_only not in VALID_LOAD_ONLY: + raise ConfigurationError( + "Got invalid value for load_only - should be one of {}".format( + ", ".join(map(repr, VALID_LOAD_ONLY)) + ) + ) + self.isolated = isolated + self.load_only = load_only + + # Because we keep track of where we got the data from + self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = { + variant: [] for variant in OVERRIDE_ORDER + } + self._config: Dict[Kind, Dict[str, Any]] = { + variant: {} for variant in OVERRIDE_ORDER + } + self._modified_parsers: List[Tuple[str, RawConfigParser]] = [] + + def load(self) -> None: + """Loads configuration from configuration files and environment""" + self._load_config_files() + if not self.isolated: + self._load_environment_vars() + + def get_file_to_edit(self) -> Optional[str]: + """Returns the file with highest priority in configuration""" + assert self.load_only is not None, "Need to be specified a file to be editing" + + try: + return self._get_parser_to_modify()[0] + except IndexError: + return None + + def items(self) -> Iterable[Tuple[str, Any]]: + """Returns key-value pairs like dict.items() representing the loaded + configuration + """ + return self._dictionary.items() + + def get_value(self, key: str) -> Any: + """Get a value from the configuration.""" + orig_key = key + key = _normalize_name(key) + try: + return self._dictionary[key] + except KeyError: + # disassembling triggers a more useful error message than simply + # "No such key" in the case that the key isn't in the form command.option + _disassemble_key(key) + raise ConfigurationError(f"No such key - {orig_key}") + + def set_value(self, key: str, value: Any) -> None: + """Modify a value in the configuration.""" + key = _normalize_name(key) + self._ensure_have_load_only() + + assert self.load_only + fname, parser = self._get_parser_to_modify() + + if parser is not None: + section, name = _disassemble_key(key) + + # Modify the parser and the configuration + if not parser.has_section(section): + parser.add_section(section) + parser.set(section, name, value) + + self._config[self.load_only][key] = value + self._mark_as_modified(fname, parser) + + def unset_value(self, key: str) -> None: + """Unset a value in the configuration.""" + orig_key = key + key = _normalize_name(key) + self._ensure_have_load_only() + + assert self.load_only + if key not in self._config[self.load_only]: + raise ConfigurationError(f"No such key - {orig_key}") + + fname, parser = self._get_parser_to_modify() + + if parser is not None: + section, name = _disassemble_key(key) + if not ( + parser.has_section(section) and parser.remove_option(section, name) + ): + # The option was not removed. + raise ConfigurationError( + "Fatal Internal error [id=1]. Please report as a bug." + ) + + # The section may be empty after the option was removed. + if not parser.items(section): + parser.remove_section(section) + self._mark_as_modified(fname, parser) + + del self._config[self.load_only][key] + + def save(self) -> None: + """Save the current in-memory state.""" + self._ensure_have_load_only() + + for fname, parser in self._modified_parsers: + logger.info("Writing to %s", fname) + + # Ensure directory exists. + ensure_dir(os.path.dirname(fname)) + + # Ensure directory's permission(need to be writeable) + try: + with open(fname, "w") as f: + parser.write(f) + except OSError as error: + raise ConfigurationError( + f"An error occurred while writing to the configuration file " + f"{fname}: {error}" + ) + + # + # Private routines + # + + def _ensure_have_load_only(self) -> None: + if self.load_only is None: + raise ConfigurationError("Needed a specific file to be modifying.") + logger.debug("Will be working with %s variant only", self.load_only) + + @property + def _dictionary(self) -> Dict[str, Any]: + """A dictionary representing the loaded configuration.""" + # NOTE: Dictionaries are not populated if not loaded. So, conditionals + # are not needed here. + retval = {} + + for variant in OVERRIDE_ORDER: + retval.update(self._config[variant]) + + return retval + + def _load_config_files(self) -> None: + """Loads configuration from configuration files""" + config_files = dict(self.iter_config_files()) + if config_files[kinds.ENV][0:1] == [os.devnull]: + logger.debug( + "Skipping loading configuration files due to " + "environment's PIP_CONFIG_FILE being os.devnull" + ) + return + + for variant, files in config_files.items(): + for fname in files: + # If there's specific variant set in `load_only`, load only + # that variant, not the others. + if self.load_only is not None and variant != self.load_only: + logger.debug("Skipping file '%s' (variant: %s)", fname, variant) + continue + + parser = self._load_file(variant, fname) + + # Keeping track of the parsers used + self._parsers[variant].append((fname, parser)) + + def _load_file(self, variant: Kind, fname: str) -> RawConfigParser: + logger.verbose("For variant '%s', will try loading '%s'", variant, fname) + parser = self._construct_parser(fname) + + for section in parser.sections(): + items = parser.items(section) + self._config[variant].update(self._normalized_keys(section, items)) + + return parser + + def _construct_parser(self, fname: str) -> RawConfigParser: + parser = configparser.RawConfigParser() + # If there is no such file, don't bother reading it but create the + # parser anyway, to hold the data. + # Doing this is useful when modifying and saving files, where we don't + # need to construct a parser. + if os.path.exists(fname): + locale_encoding = locale.getpreferredencoding(False) + try: + parser.read(fname, encoding=locale_encoding) + except UnicodeDecodeError: + # See https://github.com/pypa/pip/issues/4963 + raise ConfigurationFileCouldNotBeLoaded( + reason=f"contains invalid {locale_encoding} characters", + fname=fname, + ) + except configparser.Error as error: + # See https://github.com/pypa/pip/issues/4893 + raise ConfigurationFileCouldNotBeLoaded(error=error) + return parser + + def _load_environment_vars(self) -> None: + """Loads configuration from environment variables""" + self._config[kinds.ENV_VAR].update( + self._normalized_keys(":env:", self.get_environ_vars()) + ) + + def _normalized_keys( + self, section: str, items: Iterable[Tuple[str, Any]] + ) -> Dict[str, Any]: + """Normalizes items to construct a dictionary with normalized keys. + + This routine is where the names become keys and are made the same + regardless of source - configuration files or environment. + """ + normalized = {} + for name, val in items: + key = section + "." + _normalize_name(name) + normalized[key] = val + return normalized + + def get_environ_vars(self) -> Iterable[Tuple[str, str]]: + """Returns a generator with all environmental vars with prefix PIP_""" + for key, val in os.environ.items(): + if key.startswith("PIP_"): + name = key[4:].lower() + if name not in ENV_NAMES_IGNORED: + yield name, val + + # XXX: This is patched in the tests. + def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]: + """Yields variant and configuration files associated with it. + + This should be treated like items of a dictionary. The order + here doesn't affect what gets overridden. That is controlled + by OVERRIDE_ORDER. However this does control the order they are + displayed to the user. It's probably most ergonomic to display + things in the same order as OVERRIDE_ORDER + """ + # SMELL: Move the conditions out of this function + + env_config_file = os.environ.get("PIP_CONFIG_FILE", None) + config_files = get_configuration_files() + + yield kinds.GLOBAL, config_files[kinds.GLOBAL] + + # per-user config is not loaded when env_config_file exists + should_load_user_config = not self.isolated and not ( + env_config_file and os.path.exists(env_config_file) + ) + if should_load_user_config: + # The legacy config file is overridden by the new config file + yield kinds.USER, config_files[kinds.USER] + + # virtualenv config + yield kinds.SITE, config_files[kinds.SITE] + + if env_config_file is not None: + yield kinds.ENV, [env_config_file] + else: + yield kinds.ENV, [] + + def get_values_in_config(self, variant: Kind) -> Dict[str, Any]: + """Get values present in a config file""" + return self._config[variant] + + def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]: + # Determine which parser to modify + assert self.load_only + parsers = self._parsers[self.load_only] + if not parsers: + # This should not happen if everything works correctly. + raise ConfigurationError( + "Fatal Internal error [id=2]. Please report as a bug." + ) + + # Use the highest priority parser. + return parsers[-1] + + # XXX: This is patched in the tests. + def _mark_as_modified(self, fname: str, parser: RawConfigParser) -> None: + file_parser_tuple = (fname, parser) + if file_parser_tuple not in self._modified_parsers: + self._modified_parsers.append(file_parser_tuple) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self._dictionary!r})" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/distributions/__init__.py new file mode 100644 index 000000000..9a89a838b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/distributions/__init__.py @@ -0,0 +1,21 @@ +from pip._internal.distributions.base import AbstractDistribution +from pip._internal.distributions.sdist import SourceDistribution +from pip._internal.distributions.wheel import WheelDistribution +from pip._internal.req.req_install import InstallRequirement + + +def make_distribution_for_install_requirement( + install_req: InstallRequirement, +) -> AbstractDistribution: + """Returns a Distribution for the given InstallRequirement""" + # Editable requirements will always be source distributions. They use the + # legacy logic until we create a modern standard for them. + if install_req.editable: + return SourceDistribution(install_req) + + # If it's a wheel, it's a WheelDistribution + if install_req.is_wheel: + return WheelDistribution(install_req) + + # Otherwise, a SourceDistribution + return SourceDistribution(install_req) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bc8c59a14e8eb0aac5f3ca92eaf82c50a3c0fc1 GIT binary patch literal 816 zcmaJ<&2H2%5Vn(SHl-~R6-SVQ#3hG~4?VyEArN~(LL9n6NV!DGX4*CD{B-OrYSjyZ z$KVBc1{Vek{S;(T6)jk@^MfL>gb#P0 zm)c;lG;>M2EPEr{hVX+$+v5_hp?BV{0Ms?`;ANvtq0|wa_7b6n#w6abD=*O;;dBTQ zOpncy+pJf^>K@4p68(^t3-*rTZNo3vN4~I@hxd$sX>HMjpFcWsv2XX5ew}*Kaz)?uh^Z literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/base.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c0eee9ba3ab84d5499b60a6d5d53a79d652bb64 GIT binary patch literal 2555 zcma)8Pj4GV6yMq1_+RYQE#Xf=g;ga)jT&2`7laUMnwAm)p`t~Cy==xi&+#6!*EpFhpG`W`jG0o!KrR~JzT&b_&(O7C2L6YNa>mHBy`5t^K69GD= zv*#P~w9O>=51fz*Cv*i1J@ADqych0sXTXFnR-QG(6{tL{LJjJd5&H`!s-pJHf6BrJ z&aXn#&ecW3&b4Sxtil?s;W^D0zG#UxyK>>V<2zv+nO-Q?H~UC|$N4-J{)_84n?WBL z2C?GC1cOuuL!JmFlf6KeEO}o+z;!MMXl}ZVMu6?X5CVNlzqI7ZQ9gXx60K=nsr1l% zRGJ4H!65{rvB`rzF4GNp8VJZik7R-`i^m5EAA!9WufB#D(KzPGdwCG2T0@+xqY`#$ znWV|aINQ@)&^7~|323~P2xeuROmHakVPGI1XA4}e&#-Z2(kZ~{^=ENPy`PF++i(CX%_!%4U+})^WvF@7TXth(paJ?4HYVQb9YAu>Q?ftV za-;iU&&V7$ror!JGV37@6g}3Oo|r2#rbkKH&5m>v@x4vFeC!<7V8>UMrE+&lsKay} zH^z*%Pa2Ks*}}(AJH;1QIe@NIuQ766Is?=%fchI1yjtm*v=&*|L! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24198488bf341dc2812be28965a8eb916662aaf5 GIT binary patch literal 1517 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 Xz%SEB9c_r7c?P$t@@K&xSatiq&pFmY literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02c51f9802f0030539ca1089fc11669bbf2885b8 GIT binary patch literal 1912 zcmaJ?&2r;J5Y|Yt{1YefQmL?kqDr*|2LrJVd*MKBv1I=!QelfMMWKsU_OxSdB#kmN z&SKZX0p~IH#BE=JGfy#BPP_tK=pH3OCcwBfo@sT@^w<4$>zmEZo`d6;U!&}UmgD?J zo96Iw^BF$sIZoW+EO!bPv%-yCMte8+3P1Mk+RIx-5C?Yc=k1~scZzP@EqZZ}Il$y0 z>bLTZq96B}^Vs15Z(lgP4Z+HbH+kpS8FqiiTb!Y5HlKa>^kH)FtK+~F@1AG*5gFDC08+P=@K?7ewK9!p&wbLIzMl(@B1WP#0Adeov zIpm@$5JhyDj#X4G^h}id)!qH61Q=(qPboYF8J$CU9_88iQswUGlSpM6_Q^v`2%2V9 zlw_p_iKK{{p-_Q3$!fbn>uH3lhO5it^s%3R=tYIpl2mi` zW$$9aj}mMPT*y(~1yEsxZ9Z3)CML8RS1n<*))Gcz{0d0w*JgKJ?TR`zhHK`TJL_u1 of$uNx1tc(i4=hEj%m0SebUv-)d5;*NDCq;Y@AlbF;IqE>7sJ9DD*ylh literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/base.py b/venv/lib/python3.8/site-packages/pip/_internal/distributions/base.py new file mode 100644 index 000000000..6e4d0c91a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/distributions/base.py @@ -0,0 +1,53 @@ +import abc +from typing import TYPE_CHECKING, Optional + +from pip._internal.metadata.base import BaseDistribution +from pip._internal.req import InstallRequirement + +if TYPE_CHECKING: + from pip._internal.index.package_finder import PackageFinder + + +class AbstractDistribution(metaclass=abc.ABCMeta): + """A base class for handling installable artifacts. + + The requirements for anything installable are as follows: + + - we must be able to determine the requirement name + (or we can't correctly handle the non-upgrade case). + + - for packages with setup requirements, we must also be able + to determine their requirements without installing additional + packages (for the same reason as run-time dependencies) + + - we must be able to create a Distribution object exposing the + above metadata. + + - if we need to do work in the build tracker, we must be able to generate a unique + string to identify the requirement in the build tracker. + """ + + def __init__(self, req: InstallRequirement) -> None: + super().__init__() + self.req = req + + @abc.abstractproperty + def build_tracker_id(self) -> Optional[str]: + """A string that uniquely identifies this requirement to the build tracker. + + If None, then this dist has no work to do in the build tracker, and + ``.prepare_distribution_metadata()`` will not be called.""" + raise NotImplementedError() + + @abc.abstractmethod + def get_metadata_distribution(self) -> BaseDistribution: + raise NotImplementedError() + + @abc.abstractmethod + def prepare_distribution_metadata( + self, + finder: "PackageFinder", + build_isolation: bool, + check_build_deps: bool, + ) -> None: + raise NotImplementedError() diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/installed.py b/venv/lib/python3.8/site-packages/pip/_internal/distributions/installed.py new file mode 100644 index 000000000..ab8d53be7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/distributions/installed.py @@ -0,0 +1,29 @@ +from typing import Optional + +from pip._internal.distributions.base import AbstractDistribution +from pip._internal.index.package_finder import PackageFinder +from pip._internal.metadata import BaseDistribution + + +class InstalledDistribution(AbstractDistribution): + """Represents an installed package. + + This does not need any preparation as the required information has already + been computed. + """ + + @property + def build_tracker_id(self) -> Optional[str]: + return None + + def get_metadata_distribution(self) -> BaseDistribution: + assert self.req.satisfied_by is not None, "not actually installed" + return self.req.satisfied_by + + def prepare_distribution_metadata( + self, + finder: PackageFinder, + build_isolation: bool, + check_build_deps: bool, + ) -> None: + pass diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/sdist.py b/venv/lib/python3.8/site-packages/pip/_internal/distributions/sdist.py new file mode 100644 index 000000000..28ea5cea1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/distributions/sdist.py @@ -0,0 +1,158 @@ +import logging +from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple + +from pip._internal.build_env import BuildEnvironment +from pip._internal.distributions.base import AbstractDistribution +from pip._internal.exceptions import InstallationError +from pip._internal.metadata import BaseDistribution +from pip._internal.utils.subprocess import runner_with_spinner_message + +if TYPE_CHECKING: + from pip._internal.index.package_finder import PackageFinder + +logger = logging.getLogger(__name__) + + +class SourceDistribution(AbstractDistribution): + """Represents a source distribution. + + The preparation step for these needs metadata for the packages to be + generated, either using PEP 517 or using the legacy `setup.py egg_info`. + """ + + @property + def build_tracker_id(self) -> Optional[str]: + """Identify this requirement uniquely by its link.""" + assert self.req.link + return self.req.link.url_without_fragment + + def get_metadata_distribution(self) -> BaseDistribution: + return self.req.get_dist() + + def prepare_distribution_metadata( + self, + finder: "PackageFinder", + build_isolation: bool, + check_build_deps: bool, + ) -> None: + # Load pyproject.toml, to determine whether PEP 517 is to be used + self.req.load_pyproject_toml() + + # Set up the build isolation, if this requirement should be isolated + should_isolate = self.req.use_pep517 and build_isolation + if should_isolate: + # Setup an isolated environment and install the build backend static + # requirements in it. + self._prepare_build_backend(finder) + # Check that if the requirement is editable, it either supports PEP 660 or + # has a setup.py or a setup.cfg. This cannot be done earlier because we need + # to setup the build backend to verify it supports build_editable, nor can + # it be done later, because we want to avoid installing build requirements + # needlessly. Doing it here also works around setuptools generating + # UNKNOWN.egg-info when running get_requires_for_build_wheel on a directory + # without setup.py nor setup.cfg. + self.req.isolated_editable_sanity_check() + # Install the dynamic build requirements. + self._install_build_reqs(finder) + # Check if the current environment provides build dependencies + should_check_deps = self.req.use_pep517 and check_build_deps + if should_check_deps: + pyproject_requires = self.req.pyproject_requires + assert pyproject_requires is not None + conflicting, missing = self.req.build_env.check_requirements( + pyproject_requires + ) + if conflicting: + self._raise_conflicts("the backend dependencies", conflicting) + if missing: + self._raise_missing_reqs(missing) + self.req.prepare_metadata() + + def _prepare_build_backend(self, finder: "PackageFinder") -> None: + # Isolate in a BuildEnvironment and install the build-time + # requirements. + pyproject_requires = self.req.pyproject_requires + assert pyproject_requires is not None + + self.req.build_env = BuildEnvironment() + self.req.build_env.install_requirements( + finder, pyproject_requires, "overlay", kind="build dependencies" + ) + conflicting, missing = self.req.build_env.check_requirements( + self.req.requirements_to_check + ) + if conflicting: + self._raise_conflicts("PEP 517/518 supported requirements", conflicting) + if missing: + logger.warning( + "Missing build requirements in pyproject.toml for %s.", + self.req, + ) + logger.warning( + "The project does not specify a build backend, and " + "pip cannot fall back to setuptools without %s.", + " and ".join(map(repr, sorted(missing))), + ) + + def _get_build_requires_wheel(self) -> Iterable[str]: + with self.req.build_env: + runner = runner_with_spinner_message("Getting requirements to build wheel") + backend = self.req.pep517_backend + assert backend is not None + with backend.subprocess_runner(runner): + return backend.get_requires_for_build_wheel() + + def _get_build_requires_editable(self) -> Iterable[str]: + with self.req.build_env: + runner = runner_with_spinner_message( + "Getting requirements to build editable" + ) + backend = self.req.pep517_backend + assert backend is not None + with backend.subprocess_runner(runner): + return backend.get_requires_for_build_editable() + + def _install_build_reqs(self, finder: "PackageFinder") -> None: + # Install any extra build dependencies that the backend requests. + # This must be done in a second pass, as the pyproject.toml + # dependencies must be installed before we can call the backend. + if ( + self.req.editable + and self.req.permit_editable_wheels + and self.req.supports_pyproject_editable + ): + build_reqs = self._get_build_requires_editable() + else: + build_reqs = self._get_build_requires_wheel() + conflicting, missing = self.req.build_env.check_requirements(build_reqs) + if conflicting: + self._raise_conflicts("the backend dependencies", conflicting) + self.req.build_env.install_requirements( + finder, missing, "normal", kind="backend dependencies" + ) + + def _raise_conflicts( + self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]] + ) -> None: + format_string = ( + "Some build dependencies for {requirement} " + "conflict with {conflicting_with}: {description}." + ) + error_message = format_string.format( + requirement=self.req, + conflicting_with=conflicting_with, + description=", ".join( + f"{installed} is incompatible with {wanted}" + for installed, wanted in sorted(conflicting_reqs) + ), + ) + raise InstallationError(error_message) + + def _raise_missing_reqs(self, missing: Set[str]) -> None: + format_string = ( + "Some build dependencies for {requirement} are missing: {missing}." + ) + error_message = format_string.format( + requirement=self.req, missing=", ".join(map(repr, sorted(missing))) + ) + raise InstallationError(error_message) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/distributions/wheel.py b/venv/lib/python3.8/site-packages/pip/_internal/distributions/wheel.py new file mode 100644 index 000000000..bfadd39dc --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/distributions/wheel.py @@ -0,0 +1,42 @@ +from typing import TYPE_CHECKING, Optional + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.distributions.base import AbstractDistribution +from pip._internal.metadata import ( + BaseDistribution, + FilesystemWheel, + get_wheel_distribution, +) + +if TYPE_CHECKING: + from pip._internal.index.package_finder import PackageFinder + + +class WheelDistribution(AbstractDistribution): + """Represents a wheel distribution. + + This does not need any preparation as wheels can be directly unpacked. + """ + + @property + def build_tracker_id(self) -> Optional[str]: + return None + + def get_metadata_distribution(self) -> BaseDistribution: + """Loads the metadata from the wheel file into memory and returns a + Distribution that uses it, not relying on the wheel file or + requirement. + """ + assert self.req.local_file_path, "Set as part of preparation during download" + assert self.req.name, "Wheels are never unnamed" + wheel = FilesystemWheel(self.req.local_file_path) + return get_wheel_distribution(wheel, canonicalize_name(self.req.name)) + + def prepare_distribution_metadata( + self, + finder: "PackageFinder", + build_isolation: bool, + check_build_deps: bool, + ) -> None: + pass diff --git a/venv/lib/python3.8/site-packages/pip/_internal/exceptions.py b/venv/lib/python3.8/site-packages/pip/_internal/exceptions.py new file mode 100644 index 000000000..45a876a85 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/exceptions.py @@ -0,0 +1,809 @@ +"""Exceptions used throughout package. + +This module MUST NOT try to import from anything within `pip._internal` to +operate. This is expected to be importable from any/all files within the +subpackage and, thus, should not depend on them. +""" + +import configparser +import contextlib +import locale +import logging +import pathlib +import re +import sys +from itertools import chain, groupby, repeat +from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union + +from pip._vendor.packaging.requirements import InvalidRequirement +from pip._vendor.packaging.version import InvalidVersion +from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult +from pip._vendor.rich.markup import escape +from pip._vendor.rich.text import Text + +if TYPE_CHECKING: + from hashlib import _Hash + + from pip._vendor.requests.models import Request, Response + + from pip._internal.metadata import BaseDistribution + from pip._internal.req.req_install import InstallRequirement + +logger = logging.getLogger(__name__) + + +# +# Scaffolding +# +def _is_kebab_case(s: str) -> bool: + return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None + + +def _prefix_with_indent( + s: Union[Text, str], + console: Console, + *, + prefix: str, + indent: str, +) -> Text: + if isinstance(s, Text): + text = s + else: + text = console.render_str(s) + + return console.render_str(prefix, overflow="ignore") + console.render_str( + f"\n{indent}", overflow="ignore" + ).join(text.split(allow_blank=True)) + + +class PipError(Exception): + """The base pip error.""" + + +class DiagnosticPipError(PipError): + """An error, that presents diagnostic information to the user. + + This contains a bunch of logic, to enable pretty presentation of our error + messages. Each error gets a unique reference. Each error can also include + additional context, a hint and/or a note -- which are presented with the + main error message in a consistent style. + + This is adapted from the error output styling in `sphinx-theme-builder`. + """ + + reference: str + + def __init__( + self, + *, + kind: 'Literal["error", "warning"]' = "error", + reference: Optional[str] = None, + message: Union[str, Text], + context: Optional[Union[str, Text]], + hint_stmt: Optional[Union[str, Text]], + note_stmt: Optional[Union[str, Text]] = None, + link: Optional[str] = None, + ) -> None: + # Ensure a proper reference is provided. + if reference is None: + assert hasattr(self, "reference"), "error reference not provided!" + reference = self.reference + assert _is_kebab_case(reference), "error reference must be kebab-case!" + + self.kind = kind + self.reference = reference + + self.message = message + self.context = context + + self.note_stmt = note_stmt + self.hint_stmt = hint_stmt + + self.link = link + + super().__init__(f"<{self.__class__.__name__}: {self.reference}>") + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__}(" + f"reference={self.reference!r}, " + f"message={self.message!r}, " + f"context={self.context!r}, " + f"note_stmt={self.note_stmt!r}, " + f"hint_stmt={self.hint_stmt!r}" + ")>" + ) + + def __rich_console__( + self, + console: Console, + options: ConsoleOptions, + ) -> RenderResult: + colour = "red" if self.kind == "error" else "yellow" + + yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]" + yield "" + + if not options.ascii_only: + # Present the main message, with relevant context indented. + if self.context is not None: + yield _prefix_with_indent( + self.message, + console, + prefix=f"[{colour}]×[/] ", + indent=f"[{colour}]│[/] ", + ) + yield _prefix_with_indent( + self.context, + console, + prefix=f"[{colour}]╰─>[/] ", + indent=f"[{colour}] [/] ", + ) + else: + yield _prefix_with_indent( + self.message, + console, + prefix="[red]×[/] ", + indent=" ", + ) + else: + yield self.message + if self.context is not None: + yield "" + yield self.context + + if self.note_stmt is not None or self.hint_stmt is not None: + yield "" + + if self.note_stmt is not None: + yield _prefix_with_indent( + self.note_stmt, + console, + prefix="[magenta bold]note[/]: ", + indent=" ", + ) + if self.hint_stmt is not None: + yield _prefix_with_indent( + self.hint_stmt, + console, + prefix="[cyan bold]hint[/]: ", + indent=" ", + ) + + if self.link is not None: + yield "" + yield f"Link: {self.link}" + + +# +# Actual Errors +# +class ConfigurationError(PipError): + """General exception in configuration""" + + +class InstallationError(PipError): + """General exception during installation""" + + +class MissingPyProjectBuildRequires(DiagnosticPipError): + """Raised when pyproject.toml has `build-system`, but no `build-system.requires`.""" + + reference = "missing-pyproject-build-system-requires" + + def __init__(self, *, package: str) -> None: + super().__init__( + message=f"Can not process {escape(package)}", + context=Text( + "This package has an invalid pyproject.toml file.\n" + "The [build-system] table is missing the mandatory `requires` key." + ), + note_stmt="This is an issue with the package mentioned above, not pip.", + hint_stmt=Text("See PEP 518 for the detailed specification."), + ) + + +class InvalidPyProjectBuildRequires(DiagnosticPipError): + """Raised when pyproject.toml an invalid `build-system.requires`.""" + + reference = "invalid-pyproject-build-system-requires" + + def __init__(self, *, package: str, reason: str) -> None: + super().__init__( + message=f"Can not process {escape(package)}", + context=Text( + "This package has an invalid `build-system.requires` key in " + f"pyproject.toml.\n{reason}" + ), + note_stmt="This is an issue with the package mentioned above, not pip.", + hint_stmt=Text("See PEP 518 for the detailed specification."), + ) + + +class NoneMetadataError(PipError): + """Raised when accessing a Distribution's "METADATA" or "PKG-INFO". + + This signifies an inconsistency, when the Distribution claims to have + the metadata file (if not, raise ``FileNotFoundError`` instead), but is + not actually able to produce its content. This may be due to permission + errors. + """ + + def __init__( + self, + dist: "BaseDistribution", + metadata_name: str, + ) -> None: + """ + :param dist: A Distribution object. + :param metadata_name: The name of the metadata being accessed + (can be "METADATA" or "PKG-INFO"). + """ + self.dist = dist + self.metadata_name = metadata_name + + def __str__(self) -> str: + # Use `dist` in the error message because its stringification + # includes more information, like the version and location. + return f"None {self.metadata_name} metadata found for distribution: {self.dist}" + + +class UserInstallationInvalid(InstallationError): + """A --user install is requested on an environment without user site.""" + + def __str__(self) -> str: + return "User base directory is not specified" + + +class InvalidSchemeCombination(InstallationError): + def __str__(self) -> str: + before = ", ".join(str(a) for a in self.args[:-1]) + return f"Cannot set {before} and {self.args[-1]} together" + + +class DistributionNotFound(InstallationError): + """Raised when a distribution cannot be found to satisfy a requirement""" + + +class RequirementsFileParseError(InstallationError): + """Raised when a general error occurs parsing a requirements file line.""" + + +class BestVersionAlreadyInstalled(PipError): + """Raised when the most up-to-date version of a package is already + installed.""" + + +class BadCommand(PipError): + """Raised when virtualenv or a command is not found""" + + +class CommandError(PipError): + """Raised when there is an error in command-line arguments""" + + +class PreviousBuildDirError(PipError): + """Raised when there's a previous conflicting build directory""" + + +class NetworkConnectionError(PipError): + """HTTP connection error""" + + def __init__( + self, + error_msg: str, + response: Optional["Response"] = None, + request: Optional["Request"] = None, + ) -> None: + """ + Initialize NetworkConnectionError with `request` and `response` + objects. + """ + self.response = response + self.request = request + self.error_msg = error_msg + if ( + self.response is not None + and not self.request + and hasattr(response, "request") + ): + self.request = self.response.request + super().__init__(error_msg, response, request) + + def __str__(self) -> str: + return str(self.error_msg) + + +class InvalidWheelFilename(InstallationError): + """Invalid wheel filename.""" + + +class UnsupportedWheel(InstallationError): + """Unsupported wheel.""" + + +class InvalidWheel(InstallationError): + """Invalid (e.g. corrupt) wheel.""" + + def __init__(self, location: str, name: str): + self.location = location + self.name = name + + def __str__(self) -> str: + return f"Wheel '{self.name}' located at {self.location} is invalid." + + +class MetadataInconsistent(InstallationError): + """Built metadata contains inconsistent information. + + This is raised when the metadata contains values (e.g. name and version) + that do not match the information previously obtained from sdist filename, + user-supplied ``#egg=`` value, or an install requirement name. + """ + + def __init__( + self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str + ) -> None: + self.ireq = ireq + self.field = field + self.f_val = f_val + self.m_val = m_val + + def __str__(self) -> str: + return ( + f"Requested {self.ireq} has inconsistent {self.field}: " + f"expected {self.f_val!r}, but metadata has {self.m_val!r}" + ) + + +class MetadataInvalid(InstallationError): + """Metadata is invalid.""" + + def __init__(self, ireq: "InstallRequirement", error: str) -> None: + self.ireq = ireq + self.error = error + + def __str__(self) -> str: + return f"Requested {self.ireq} has invalid metadata: {self.error}" + + +class InstallationSubprocessError(DiagnosticPipError, InstallationError): + """A subprocess call failed.""" + + reference = "subprocess-exited-with-error" + + def __init__( + self, + *, + command_description: str, + exit_code: int, + output_lines: Optional[List[str]], + ) -> None: + if output_lines is None: + output_prompt = Text("See above for output.") + else: + output_prompt = ( + Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n") + + Text("".join(output_lines)) + + Text.from_markup(R"[red]\[end of output][/]") + ) + + super().__init__( + message=( + f"[green]{escape(command_description)}[/] did not run successfully.\n" + f"exit code: {exit_code}" + ), + context=output_prompt, + hint_stmt=None, + note_stmt=( + "This error originates from a subprocess, and is likely not a " + "problem with pip." + ), + ) + + self.command_description = command_description + self.exit_code = exit_code + + def __str__(self) -> str: + return f"{self.command_description} exited with {self.exit_code}" + + +class MetadataGenerationFailed(InstallationSubprocessError, InstallationError): + reference = "metadata-generation-failed" + + def __init__( + self, + *, + package_details: str, + ) -> None: + super(InstallationSubprocessError, self).__init__( + message="Encountered error while generating package metadata.", + context=escape(package_details), + hint_stmt="See above for details.", + note_stmt="This is an issue with the package mentioned above, not pip.", + ) + + def __str__(self) -> str: + return "metadata generation failed" + + +class HashErrors(InstallationError): + """Multiple HashError instances rolled into one for reporting""" + + def __init__(self) -> None: + self.errors: List[HashError] = [] + + def append(self, error: "HashError") -> None: + self.errors.append(error) + + def __str__(self) -> str: + lines = [] + self.errors.sort(key=lambda e: e.order) + for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__): + lines.append(cls.head) + lines.extend(e.body() for e in errors_of_cls) + if lines: + return "\n".join(lines) + return "" + + def __bool__(self) -> bool: + return bool(self.errors) + + +class HashError(InstallationError): + """ + A failure to verify a package against known-good hashes + + :cvar order: An int sorting hash exception classes by difficulty of + recovery (lower being harder), so the user doesn't bother fretting + about unpinned packages when he has deeper issues, like VCS + dependencies, to deal with. Also keeps error reports in a + deterministic order. + :cvar head: A section heading for display above potentially many + exceptions of this kind + :ivar req: The InstallRequirement that triggered this error. This is + pasted on after the exception is instantiated, because it's not + typically available earlier. + + """ + + req: Optional["InstallRequirement"] = None + head = "" + order: int = -1 + + def body(self) -> str: + """Return a summary of me for display under the heading. + + This default implementation simply prints a description of the + triggering requirement. + + :param req: The InstallRequirement that provoked this error, with + its link already populated by the resolver's _populate_link(). + + """ + return f" {self._requirement_name()}" + + def __str__(self) -> str: + return f"{self.head}\n{self.body()}" + + def _requirement_name(self) -> str: + """Return a description of the requirement that triggered me. + + This default implementation returns long description of the req, with + line numbers + + """ + return str(self.req) if self.req else "unknown package" + + +class VcsHashUnsupported(HashError): + """A hash was provided for a version-control-system-based requirement, but + we don't have a method for hashing those.""" + + order = 0 + head = ( + "Can't verify hashes for these requirements because we don't " + "have a way to hash version control repositories:" + ) + + +class DirectoryUrlHashUnsupported(HashError): + """A hash was provided for a version-control-system-based requirement, but + we don't have a method for hashing those.""" + + order = 1 + head = ( + "Can't verify hashes for these file:// requirements because they " + "point to directories:" + ) + + +class HashMissing(HashError): + """A hash was needed for a requirement but is absent.""" + + order = 2 + head = ( + "Hashes are required in --require-hashes mode, but they are " + "missing from some requirements. Here is a list of those " + "requirements along with the hashes their downloaded archives " + "actually had. Add lines like these to your requirements files to " + "prevent tampering. (If you did not enable --require-hashes " + "manually, note that it turns on automatically when any package " + "has a hash.)" + ) + + def __init__(self, gotten_hash: str) -> None: + """ + :param gotten_hash: The hash of the (possibly malicious) archive we + just downloaded + """ + self.gotten_hash = gotten_hash + + def body(self) -> str: + # Dodge circular import. + from pip._internal.utils.hashes import FAVORITE_HASH + + package = None + if self.req: + # In the case of URL-based requirements, display the original URL + # seen in the requirements file rather than the package name, + # so the output can be directly copied into the requirements file. + package = ( + self.req.original_link + if self.req.is_direct + # In case someone feeds something downright stupid + # to InstallRequirement's constructor. + else getattr(self.req, "req", None) + ) + return " {} --hash={}:{}".format( + package or "unknown package", FAVORITE_HASH, self.gotten_hash + ) + + +class HashUnpinned(HashError): + """A requirement had a hash specified but was not pinned to a specific + version.""" + + order = 3 + head = ( + "In --require-hashes mode, all requirements must have their " + "versions pinned with ==. These do not:" + ) + + +class HashMismatch(HashError): + """ + Distribution file hash values don't match. + + :ivar package_name: The name of the package that triggered the hash + mismatch. Feel free to write to this after the exception is raise to + improve its error message. + + """ + + order = 4 + head = ( + "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS " + "FILE. If you have updated the package versions, please update " + "the hashes. Otherwise, examine the package contents carefully; " + "someone may have tampered with them." + ) + + def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None: + """ + :param allowed: A dict of algorithm names pointing to lists of allowed + hex digests + :param gots: A dict of algorithm names pointing to hashes we + actually got from the files under suspicion + """ + self.allowed = allowed + self.gots = gots + + def body(self) -> str: + return f" {self._requirement_name()}:\n{self._hash_comparison()}" + + def _hash_comparison(self) -> str: + """ + Return a comparison of actual and expected hash values. + + Example:: + + Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde + or 123451234512345123451234512345123451234512345 + Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef + + """ + + def hash_then_or(hash_name: str) -> "chain[str]": + # For now, all the decent hashes have 6-char names, so we can get + # away with hard-coding space literals. + return chain([hash_name], repeat(" or")) + + lines: List[str] = [] + for hash_name, expecteds in self.allowed.items(): + prefix = hash_then_or(hash_name) + lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds) + lines.append( + f" Got {self.gots[hash_name].hexdigest()}\n" + ) + return "\n".join(lines) + + +class UnsupportedPythonVersion(InstallationError): + """Unsupported python version according to Requires-Python package + metadata.""" + + +class ConfigurationFileCouldNotBeLoaded(ConfigurationError): + """When there are errors while loading a configuration file""" + + def __init__( + self, + reason: str = "could not be loaded", + fname: Optional[str] = None, + error: Optional[configparser.Error] = None, + ) -> None: + super().__init__(error) + self.reason = reason + self.fname = fname + self.error = error + + def __str__(self) -> str: + if self.fname is not None: + message_part = f" in {self.fname}." + else: + assert self.error is not None + message_part = f".\n{self.error}\n" + return f"Configuration file {self.reason}{message_part}" + + +_DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\ +The Python environment under {sys.prefix} is managed externally, and may not be +manipulated by the user. Please use specific tooling from the distributor of +the Python installation to interact with this environment instead. +""" + + +class ExternallyManagedEnvironment(DiagnosticPipError): + """The current environment is externally managed. + + This is raised when the current environment is externally managed, as + defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked + and displayed when the error is bubbled up to the user. + + :param error: The error message read from ``EXTERNALLY-MANAGED``. + """ + + reference = "externally-managed-environment" + + def __init__(self, error: Optional[str]) -> None: + if error is None: + context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR) + else: + context = Text(error) + super().__init__( + message="This environment is externally managed", + context=context, + note_stmt=( + "If you believe this is a mistake, please contact your " + "Python installation or OS distribution provider. " + "You can override this, at the risk of breaking your Python " + "installation or OS, by passing --break-system-packages." + ), + hint_stmt=Text("See PEP 668 for the detailed specification."), + ) + + @staticmethod + def _iter_externally_managed_error_keys() -> Iterator[str]: + # LC_MESSAGES is in POSIX, but not the C standard. The most common + # platform that does not implement this category is Windows, where + # using other categories for console message localization is equally + # unreliable, so we fall back to the locale-less vendor message. This + # can always be re-evaluated when a vendor proposes a new alternative. + try: + category = locale.LC_MESSAGES + except AttributeError: + lang: Optional[str] = None + else: + lang, _ = locale.getlocale(category) + if lang is not None: + yield f"Error-{lang}" + for sep in ("-", "_"): + before, found, _ = lang.partition(sep) + if not found: + continue + yield f"Error-{before}" + yield "Error" + + @classmethod + def from_config( + cls, + config: Union[pathlib.Path, str], + ) -> "ExternallyManagedEnvironment": + parser = configparser.ConfigParser(interpolation=None) + try: + parser.read(config, encoding="utf-8") + section = parser["externally-managed"] + for key in cls._iter_externally_managed_error_keys(): + with contextlib.suppress(KeyError): + return cls(section[key]) + except KeyError: + pass + except (OSError, UnicodeDecodeError, configparser.ParsingError): + from pip._internal.utils._log import VERBOSE + + exc_info = logger.isEnabledFor(VERBOSE) + logger.warning("Failed to read %s", config, exc_info=exc_info) + return cls(None) + + +class UninstallMissingRecord(DiagnosticPipError): + reference = "uninstall-no-record-file" + + def __init__(self, *, distribution: "BaseDistribution") -> None: + installer = distribution.installer + if not installer or installer == "pip": + dep = f"{distribution.raw_name}=={distribution.version}" + hint = Text.assemble( + "You might be able to recover from this via: ", + (f"pip install --force-reinstall --no-deps {dep}", "green"), + ) + else: + hint = Text( + f"The package was installed by {installer}. " + "You should check if it can uninstall the package." + ) + + super().__init__( + message=Text(f"Cannot uninstall {distribution}"), + context=( + "The package's contents are unknown: " + f"no RECORD file was found for {distribution.raw_name}." + ), + hint_stmt=hint, + ) + + +class LegacyDistutilsInstall(DiagnosticPipError): + reference = "uninstall-distutils-installed-package" + + def __init__(self, *, distribution: "BaseDistribution") -> None: + super().__init__( + message=Text(f"Cannot uninstall {distribution}"), + context=( + "It is a distutils installed project and thus we cannot accurately " + "determine which files belong to it which would lead to only a partial " + "uninstall." + ), + hint_stmt=None, + ) + + +class InvalidInstalledPackage(DiagnosticPipError): + reference = "invalid-installed-package" + + def __init__( + self, + *, + dist: "BaseDistribution", + invalid_exc: Union[InvalidRequirement, InvalidVersion], + ) -> None: + installed_location = dist.installed_location + + if isinstance(invalid_exc, InvalidRequirement): + invalid_type = "requirement" + else: + invalid_type = "version" + + super().__init__( + message=Text( + f"Cannot process installed package {dist} " + + (f"in {installed_location!r} " if installed_location else "") + + f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}" + ), + context=( + "Starting with pip 24.1, packages with invalid " + f"{invalid_type}s can not be processed." + ), + hint_stmt="To proceed this package must be uninstalled.", + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/index/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/index/__init__.py new file mode 100644 index 000000000..7a17b7b3b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/index/__init__.py @@ -0,0 +1,2 @@ +"""Index interaction code +""" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/index/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/index/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..103c0a1b5ed3f4f3bf8bb56f15a4a90c05bfa4d7 GIT binary patch literal 247 zcmYjMyKcfj5IoZaAyLysv=n9w8VZCcXpj(1l!oSH`vzEXzB}26h54iWLTbK(HlL6# zRn{m)M%r1;zBKzj91fJ=^G^IK`KuHEk%(AH)O{hN_R1u0YSO!nHqPKWW*1RqMP%>B zg*O=NCduPPYvT*8KUCF|ttb7obn+z#H=G%k)@WdTDB&V)v%IFz#*=qxn5Bmb$Qfyr z!)H{~z%^t!{~MW2=Tvh<8l;^aA*&onKb^SD5_iT(OM#=-)6hQpi_)8m#j<;3=J-KJ98M$YxS-Iz`Il1SndAS#=MY)g3-EEcHEo^aP$Lj)2_aw#)DUaV;|b6|7tLY`g5BO z-hTnluLZB;`E@mk_I@rnr;eyY7}=YgIYzTP_{@F_tvSIPAA7;^;IkiOs+WTE>KXN} z`a*C5WB*)m67LpJZ&vLO=1}$&-kuKL#CX1l_Rj=wq5ZeiKD2)s&z}#@;`uCQ^QFMK zYLzeazF54rqWpHf+40vm^m;c`es{@_R+^#T?gkqzY?cGb4>pD~#PuVZmTN^4YkHz`3 z^;WCC*ix}~uGxs<{5z4-=J`@HjN*xRHzGd1TVG#qc9!GfLcOhmYa8pRk^ka))a-WZ ztvG*KccU%_6X&n08yl+AP;tf-%DmS>8T6Ug{1QeSP5z1s*Snoim7TbFMMe7dd9Aw| zwXa^19LnrjP|>Zf#$-DkOcsr#Up%e>N{n4qb=_FG+UTw;JQgoE*RQHDG~G?;r~N|xPt z`fR-uG_jg0ynqsy>&q%Gatv2x)#K9QMza+pdvM>0T{IDAwTd=$r@=PTA4HVp;W~>e z{0x%FTD8wv-+bwg-M6-!d)AiQx9{0H+qWXGZ*STlCwnc6XWOxC>#B7lD@5b=mM?X? zYd$9DfBEcHKWwb1whDcaPy||f@5-f0vFPK6rNtOPK0;v;iUlJTW_{FO=ypJ1Bsitj zi=p4?f)-J-A`SGMmrpE|Gx{I~8|RXK;!LZ1OX;|Xh1F4bs~N4tZcBB_j((QqvZma9 zOPBHZpf|tLZL4|JQ)^Atnm?y*s#bTstvb>CrTStxzkWMf>2~JUU!7ahN-Z|kT%_xb zHKpfos?N>%R&y~a@@nPyeAtZC+&VsE6Hv9eK3{8gK(e61`DQ0j@6RVJ0p?i09q+2i zWNO&Wni4{U+kNMrV_UdeHK*?&bpw#YeS4wo#hGwp9lMdQVI`VTt)}NtQCEVWg$qcs%K`p<1o9SLvF2Vfvp%?li8DERwf1pCKC{GmM-tOEt<% z=~A6#E%H*|TE$XPecyAyPd54K4i;<64P5Zl;lR5pT!nI()$FG8i%{y^wBGLsrJr3< zjWuDEWb@{(VZ;0tRS%RF`P&IMw2~6(sC@1%1b67y{j1G3WTG?$0s!Zkfv1c}VHEn; zw=e1Sgz=;u(2GwRp}6mKBQ$xl-f9Nvw>f?@O!=TRI>b2lUT3Y-z11nZy+WkkkLFjR zcB}Us1a_<0s8evx-|Ph7hV^D8lu=f0zVb2)J@{#bR>N**p`43zW*NfRU52oJ9;>a% z7qU%pE}`i-+g9M@pzP`s{75p4Cq~J?)-ohFpu2dwres-b_4Q`WbaEcm!U-f+Zo@L5{ZW);aB+Cy1tgT|TVT<9_BS$joICCv?@s1Uw(mfa z=k(#o-L!rY)Vb>Q2`k(WojbWG)6Z?%wiRW~J)z70BgAx7bpp`B#q)2S^OJ=pH70U? z)`x2H%^E(IG|i^}ETqZR8W1Ja$bc4a)|-M7MCEKB4Yi20{r^DRLimQbPjhi&ZzYP> z;|$aA8Z|diO7~U`%DSlm20{{L7HSsCAOmyOh< zE`5XrvKONxgZaWTL1w$QK64EP_LNWS-)uN_npesc8y3CTpj{0h6cJqjr-V?C_G_c z$SW299YGj`CVmUhCG^9MaJ|`Rb~nP-ZNJrAQyfPE&Sw2^gzA@?I)rHK3XuWS0L45? zQezavxWqI@7IQkYt*E=3s(Fql+i{+Z{veRf*Il5 zsCOuw7nL6apaqk;hkU~rFOf@YNRQ98g>U2uqo?2lFaw;B<&CsqU69LTPnEVh8= zbaWgTEP;j+{KUJf(>sWPBYPXcXEGlQXasxt`P1lN& zMnLt$lk5^b-oo;k&hOj8XT8a{RTOc(*MZjj=fj>e>+4e>{oYGIgLe9yf8*5A$}7EH z1Epr(9DSu%XxHDLLz}0LLe6JN$LmpV{A?YhF-P5~yTI_8fTH)r1!Zt=G6ugMVkG)= z_@+-HDVOwq=AS?U_6H>pzXY> zmX$v7vifT%546n*J{lQO*odIv6#JGw9zfK1|<0XM01;xa<#tKC? z@Np*2D44#jI0ySBq+ykNH^~&ZT96|3wm#13x-#9^ZtE(iy<>3?$l|60GL~EdT7Cz` z4WMvJ!27&B$Z35Y?;E(b{kU*vaD{|0LqI`Q4QUJM3%lM`o`Mv{Gh{HHvnm%9KDL8m zFovhRm}sSVM#z!2GYv*kCDlsl9U3Xg(*le|s2`BnbLefGwxH(VC7U>d3?PFQ79*SB zfmQ(Vzn;ej8mJ9Yeys(Qlq(j}P@i}!gsnd?n{|Mq_Z@u2wbQfIF)&beXO}8<|;naj}Vk!=$A1z$4l< z<9rq^f!1Z|%Y~ijl-5|B?JlkY>crk+x7*TdeC5K_ z)m`L9)oE1i^E#>5n^41kYs&FCn3fq#gO89XXL3 zaZ$khV)9b}ls4^e+mW@ExeJURWRns(miU&vk&~B?Lt7G)bI9nuJbp2x}xOuh`5;b8{~810%tR$7NpsgJiLOJs!%k(kBE4;sD*mZc{)KugRM(B7r<*YcT^u%h zbmdJ*4T?O7p;W4kE;aL9=G|Os+IW$5S6FQm&aRZzAQM}_lEP}x;_ssvZZ~l=TP;XD zcS{38DE&jW#2vJp-b;k6ARDYv0AC2?Y%RfVm6JgF829N)Qf-8eA3HuuE;<$T^xaf? zVT_T(5s!}7)e0qr)%0!TFxW)c%9siDM+QEbjG&g>|0_NluZsiaZugG;?2|~F0E-rc z79c3XxX{uYW)aMgXmc2JlVO)3WT}j+KN#2FV@_6e|6@DE$E#QqK0iA^ai2 z2^>7c$Y4C-B0tgR##FiuFK6I4VGKc)t-u^nEqG@;d=7nbw{IkSg46{N#~C8eaG{sk zh?eG#gO~E;AUdPt{9+xBY~;$jj3e)Ptf%;2fJ5P+p*yx7gg{jWV+cewJ6;fvHVcXM z8&A*x;Sgjl@EN9nDfX6>3gT>_fasLoL=SP!3_q-vq?KeA<)YDnpJx$|AqLdN*vYh= zwKyvhP3v!SjL$NWwz8(J5m_ztHYTM-#DxD1g~i)yWs3sSrr?Im1M|WM7f)w!g_n_}TJAl7-UPonCoPo)j&pI(sVw3>nZQflWlmZ_ zwwhN3yvy+&Y;8Lz1Vv~?pga&oNeI9G%a9&d)H=kkY%qQ4DYEyEVC%Vw5WDqO7gms1 z)TE1{gZ=~-pxzz;=@TNE(@ml6YBoe(FN+3AC7O_AQXRBmYSO$G&2ej`*;tWUVshRB z<)!wo3XJSd>OL&wPx!Z1;K+m5lRFw39Tk{%B`n2Bf%MU<3<6f~<9^{vcLTftYuQNU zihubwd<-I+Ss!iyDe=?V5sO3mhSk7j5hXM$-YE5cot{@SDg8^>Y{Ml33cP0Ia|>U# zMPn%VsX5N#647Q*--seI7N1aY2ZeT0Z?1;WlCISdAs#@_84(DHJOGH10t@A_9T+A? z-Jsim(>$|y8*!z0vQ`t%g4j?YyyUcb^cWM-F0>Q%KiEEqA>3KShace*omzCfLLNL) z;^jQV7Zl6vfrdkBZok!z(Q+Ets7`!JU$eA{>3ZjFF=mc1xipr{}MoBB3Y%GRJy+C z*P}=`7dNn+2|CfwptpyBDg^_t@GuevSW>i?23P{psF@LKfmma-WxHq|+F5SwM)l=5 z!`X$#cH!vL9}TU6v2_s-bRw=fdKP*Q2O^*~kwXZ{ftGdq2pg>x^f_wQn$_7y>mOy$ zp)BGg#_p%iOlA$+1A7qLb4SH)*LRa>s@Yv`*!hGk$u4;tawt@C|RDhW;*U;ABR+iR3ky z%sV$RT`s7L9RY}c$R35qNe)^-8Ue9k`FqT<3;kIp!@h--s5&(n2}e-O0H7i>Gr4E; z^0w?$5Ej(GiUN#6*9j3khGrfn9xl~@cm(gluK<#npb11!Mz;|H(eN7!NjM9==g*2r zrxSFEo;7jlJ|CWdQ_Y|A!?mWI6Tk+2nWOv)5_oPn7X43{{8J{w`3MTw3zJ!BV#Gg2 zZ{{4QC*mptx4NyrXBs=fCPXMchuk*y=6q-!%J#p2Oe)ghBS%OEUI$@zxV`v<5Ic-K z+PLn5xT!GGZ=;goW%dBNzs?upXrqud`h|prvcrK$tsS%4jnMO&+HQxu!5K}Vw&fOq zZ0Lq!>inJlEz_hd)OqB$J1J1jDkhiA?E&6k27`cS;Om?oCHiSXbaA2DQFoePnmec~-P*t*HOR253iof@1<`zlRvd*ZBPy zzE9)(-`dulylwUNNr^GgTz+*ou9MXk=KsPn{H@gszElUfte z9@f}It2@g65M}pD*~>q*?DtT1pOpPhQuc6Ax>M*EqWx&w#@q|iPa$$w4+P^MVpVta z7NCVER}V@XKSZ?}t6igIr_32>h zLwL)C4|gp*u%FY1NGBwX-a9yAgy9z$r2$sA#EbeVq$PzeS6ji$BKg=9`&mn$n~p< z0FdT(48R0_9*Xr)yAQ)EFy3+YLwp#BGcw#U8v3mCzW>-B=+wZB(;z7W8VL@QP(CG8 z#lMC^-422*$s(Yq(L%7%G<@4XIdo2Y3Yh*U7vjR95au=rU-Vu>?`WQAyzZ~-E*vFo zhR%R@5J*}UydH)vzd^_C5`*{Zgb#NDg=TLW1(SnNzT#=A6F%boO`OJpzdb@J5m>%4 z^fD$A%A+kJh~wO?y6&J4{SIG!0f_`m#r}$&Zm1XdD$@+Hx%v$ACE)X8jE#yGgryjm zlRi1Tm+%K=yF?=NIp$`WRG7&56Z))0V(V$<=Mdfn2*LgV$f;R~NxCqiJ@C<7o*H2vMhAp>veRR8xQ+@Mw6H z#Qgij%XscS`yga=KEtSt186eAua5IcF*C)SOS$bmn8{Du2k@1b2gL2$Gq|ShDFlJ_ z@1f-en;-qTcsPSA=xR z6B%09aGAo3957zO_{eUtAqs&N4PwDyebU6vdBi#;E1I=7gG5Q)(C9 zj(_X~6Tu|fm{#Lzx0(s2(CV&W8gHJEILk~tPJc$SEM49E7h)7#G2yi)I82Ph_v5XF_vYx};>0gHY=YzL4mzyl5>Sj(_SR_(nOd~VJvB947GJT9sqfDA_$7=Ez&-Z{8g@7V+k z%cf*@{S0y$`0e1u>q}kn++}e9!tQ5-EP+Qq2SAtW{XjN_j)}z4k(}T;Au8Z1294u9 zbR|$b;wHiHbaTz7j8@(`(G&hx5+@1jHkUhH$aN~9xw%evPT0^lt27t}TZ3bzBWEQd zy@4n|BJxL$u_lB$?7uhj4jX4tX;lf*eqhgTb7hbuC|E*Xd$`EfHz)2ONWnv-;xHY? z3-tkfXDp<0)BxS>^bi+LE~^d>8|%~GOR2(xpQQKVcB*K=vWV2su|B8;g8M#t&24^?~`Izsg5xk~535rf1DyWjPTmo(pG+ClflCu!*CBen8~BMC4$ zc5B>%LlPC_hyA6xGa@I6*8Y3G*g;(rVl)npKm7h+eqQh3j``8L92De9S|BDyMCIsd zJOjv>9LhG)evbBGfl2d`mvF~FLk596n}ND-IZJYu_k;Xhh@dU^E<}wHI9uL53jv7H za&F*Axy(ojFM>5in+c(eUl&b>fW+1q#FEP(2E!6jsShppoA4#yWt#i02f>$t*vp{q zc#wx3h2RGrF3}`xC;YDhQg>4eE3;v(1_t5_rEQz)Pr!)yJK2!}T z#u}v1a z2UpPe_ao+M1pbbMU6<2 z3ixj$BFW-?^xq5wdss_lQW$5&hZdT~DB0suGd$m6q&T<$Ld@}?6+}#&Id}f^@4XH6 zfU|Xxgflbnw&JYSi>M^tB%=<9NKLRcYY2~&YlwW;U<4`Fil>LNa_}zG!qL5CxrGF{ zfAQoFI{YK9IUPw>cDhjfo6^{H!TXz1sf1IW#ZR-@=^w)o_{8(3up{u|e%d?q6L+L+ zUnuX{3CqP3xR=AHdNU{H#{@_kfT&4f`UVqfDg*{Kp<#5}I1P{UgKUzVh>(cEBH#TP z6Z-T7zU%+N+)x+~37&fNmpIKM$iNCJ2!sBply@@Lk6`f_hW)#VXB>ORoynW8M*G@` z|6eO#(K3a!gaNRFCrjTBZrERGh`pA&`>J;6YRLYr;>nSQMYS$VAqu#GIH# z)Qfr&Nj!%CDXG+ORwL+YO@v*Y8F`ZqEY!RB>FGy(stnI(4sG9fpA=31S7o#eE#-Ls z_RsjA3e^fLpwTrG$$jRL-?teHf{qStFPB&?mGt;J=`4Qo(PcKGCZN%1k)*;JKXk%Q=LVpNJ=CKf+UUwAweq$04ZuDdN`Qw-JQi8 z%h$66b~GEu0uxh?Y0;MKC>B*&I49#IPT48P<+78C%Spw~t;9~!dUP9n#N|FENy zX&(9gzSrF|Jv*Q+SE^E#0p?BjyN~aE_j~*N=xE-+=VyQMc>4m;O%%FVEoX z{g`DKj!`lk)3NGi$&_EKWXW%$l#t(KDJj3HQVPF`db*J*WlWYy*0YUVDc8uC@{N(w zNTX0HG)7CKjj_^LW4tupm?%w1zEpj(F;$w9=X8BXW4bgg&zbtp#;(#XdCt~%H)cvR zjXkA3@}9%{-qJpK&e!j0+*`W0vA?vxabM}ahF!8Hex!b&aj(wsa`*5@0?OULDTs{T;p;nKtM zyrX`i@kr?rd7iF6+IXz=m^|;Sf28rz(nsZaSN-wE6Qw8Qd3XI}|ail&bg1d=Wm;( z1$V+7ckbI`xZ~~x_qio&kHK&0qP$-~+#Yw-y@2$KOKHS^%z4}`hG|O)#DCoVxO3pG zq;t@@|Fu-<(gnjgL+d|N{fhjz&V1LBe$*6C*6X37^OztClEfT-<(;L zFFHqGOO`IX8RUP^IfnelZd>T#r`(J)=ghxhBL28@FXHc&_$zpS$axs=52N0zcst?j z$J>6qT|>>!xfh&A-by%+I**~==Ot#;eQu8-u^(|hir5z%>#9+FqI2L%YrS1-x}L4v zdZq0;cDrR?-ne}6m|bf+?k(5*CINi0X!<#)(ymnN70)YX{n2ZmxqQBS_PO(CKYnrX zf}dMZt&ZEg>bCvt#kQ*|*XypIyj1h>{>im=t<|j5{RER!*VfmNmU_O4NYtA5*4%1s zspcvVZ;5M_W&BK5E6rB3R;|=)9k<-9G~A-)k6moOiU{WgWJOuOP(D-f+~5s`Qfn3E zxy7Wv?~LoUgZO9b%B?sX7n@$YQm?y?KYb3JQnl+Cb*s79YA>|bn}`kS{`89L))#7Z zmu>nJ&o{mGwY8SQyhsXqF?y-iym7XLx~lD#@<*i6avhQE|IVPH3Tr=GX*xAbnu~mSF5cx=9|9ivSyUNtlTA6xy`D}iVN2&YT0dH z-e|ALw54=^On)k9@rwJ(dQG_vx7kMa9P(ajEibzYFXNwn?)>>nE|lmid3BEGB?}hdW#cZxV~PiJLRU^uD7aq-&tL8t2fF@SMHV9!uF@HK7aZ0C$C&P zf38>WRTRiw+gNiitXGtyW&k(-4z5Ks5m#Jqz25dmFITEJD$DKy+g5KI3s|%yr_e9F zsu#ChYc93?UA5(AOSyy1`)TF2*HyEcAZP}k6n?Y#p2gSuQwTa!ZKG?fnq45@H>}OX zr;X+VNuzD8CT`@_Y;L=OnwyAb%1_C3RKTk&eY?u(h(4ifI^m8=ouETl?mU+g@>%jlf>6z3Mix&$ya) zFlM{v+0|BK4X}gVW8bW`SL}+-X*uFaJqLp|_U3fKgEscVx;vkj)RTb3N+YaHCh(;F zto_Jbn;XttV%f&1LC|u1l(5XB(B(mYQqSF!2bhsuk8q#30Zx-{0Ku z6*_4HJoR8(w$37LZ5u(^3VCP;UqXsQRf*e5O$R5+%Z#uAuy*%0Am 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/index/collector.py b/venv/lib/python3.8/site-packages/pip/_internal/index/collector.py new file mode 100644 index 000000000..5f8fdee3d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/index/collector.py @@ -0,0 +1,494 @@ +""" +The main purpose of this module is to expose LinkCollector.collect_sources(). +""" + +import collections +import email.message +import functools +import itertools +import json +import logging +import os +import urllib.parse +import urllib.request +from dataclasses import dataclass +from html.parser import HTMLParser +from optparse import Values +from typing import ( + Callable, + Dict, + Iterable, + List, + MutableMapping, + NamedTuple, + Optional, + Protocol, + Sequence, + Tuple, + Union, +) + +from pip._vendor import requests +from pip._vendor.requests import Response +from pip._vendor.requests.exceptions import RetryError, SSLError + +from pip._internal.exceptions import NetworkConnectionError +from pip._internal.models.link import Link +from pip._internal.models.search_scope import SearchScope +from pip._internal.network.session import PipSession +from pip._internal.network.utils import raise_for_status +from pip._internal.utils.filetypes import is_archive_file +from pip._internal.utils.misc import redact_auth_from_url +from pip._internal.vcs import vcs + +from .sources import CandidatesFromPage, LinkSource, build_source + +logger = logging.getLogger(__name__) + +ResponseHeaders = MutableMapping[str, str] + + +def _match_vcs_scheme(url: str) -> Optional[str]: + """Look for VCS schemes in the URL. + + Returns the matched VCS scheme, or None if there's no match. + """ + for scheme in vcs.schemes: + if url.lower().startswith(scheme) and url[len(scheme)] in "+:": + return scheme + return None + + +class _NotAPIContent(Exception): + def __init__(self, content_type: str, request_desc: str) -> None: + super().__init__(content_type, request_desc) + self.content_type = content_type + self.request_desc = request_desc + + +def _ensure_api_header(response: Response) -> None: + """ + Check the Content-Type header to ensure the response contains a Simple + API Response. + + Raises `_NotAPIContent` if the content type is not a valid content-type. + """ + content_type = response.headers.get("Content-Type", "Unknown") + + content_type_l = content_type.lower() + if content_type_l.startswith( + ( + "text/html", + "application/vnd.pypi.simple.v1+html", + "application/vnd.pypi.simple.v1+json", + ) + ): + return + + raise _NotAPIContent(content_type, response.request.method) + + +class _NotHTTP(Exception): + pass + + +def _ensure_api_response(url: str, session: PipSession) -> None: + """ + Send a HEAD request to the URL, and ensure the response contains a simple + API Response. + + Raises `_NotHTTP` if the URL is not available for a HEAD request, or + `_NotAPIContent` if the content type is not a valid content type. + """ + scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url) + if scheme not in {"http", "https"}: + raise _NotHTTP() + + resp = session.head(url, allow_redirects=True) + raise_for_status(resp) + + _ensure_api_header(resp) + + +def _get_simple_response(url: str, session: PipSession) -> Response: + """Access an Simple API response with GET, and return the response. + + This consists of three parts: + + 1. If the URL looks suspiciously like an archive, send a HEAD first to + check the Content-Type is HTML or Simple API, to avoid downloading a + large file. Raise `_NotHTTP` if the content type cannot be determined, or + `_NotAPIContent` if it is not HTML or a Simple API. + 2. Actually perform the request. Raise HTTP exceptions on network failures. + 3. Check the Content-Type header to make sure we got a Simple API response, + and raise `_NotAPIContent` otherwise. + """ + if is_archive_file(Link(url).filename): + _ensure_api_response(url, session=session) + + logger.debug("Getting page %s", redact_auth_from_url(url)) + + resp = session.get( + url, + headers={ + "Accept": ", ".join( + [ + "application/vnd.pypi.simple.v1+json", + "application/vnd.pypi.simple.v1+html; q=0.1", + "text/html; q=0.01", + ] + ), + # We don't want to blindly returned cached data for + # /simple/, because authors generally expecting that + # twine upload && pip install will function, but if + # they've done a pip install in the last ~10 minutes + # it won't. Thus by setting this to zero we will not + # blindly use any cached data, however the benefit of + # using max-age=0 instead of no-cache, is that we will + # still support conditional requests, so we will still + # minimize traffic sent in cases where the page hasn't + # changed at all, we will just always incur the round + # trip for the conditional GET now instead of only + # once per 10 minutes. + # For more information, please see pypa/pip#5670. + "Cache-Control": "max-age=0", + }, + ) + raise_for_status(resp) + + # The check for archives above only works if the url ends with + # something that looks like an archive. However that is not a + # requirement of an url. Unless we issue a HEAD request on every + # url we cannot know ahead of time for sure if something is a + # Simple API response or not. However we can check after we've + # downloaded it. + _ensure_api_header(resp) + + logger.debug( + "Fetched page %s as %s", + redact_auth_from_url(url), + resp.headers.get("Content-Type", "Unknown"), + ) + + return resp + + +def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]: + """Determine if we have any encoding information in our headers.""" + if headers and "Content-Type" in headers: + m = email.message.Message() + m["content-type"] = headers["Content-Type"] + charset = m.get_param("charset") + if charset: + return str(charset) + return None + + +class CacheablePageContent: + def __init__(self, page: "IndexContent") -> None: + assert page.cache_link_parsing + self.page = page + + def __eq__(self, other: object) -> bool: + return isinstance(other, type(self)) and self.page.url == other.page.url + + def __hash__(self) -> int: + return hash(self.page.url) + + +class ParseLinks(Protocol): + def __call__(self, page: "IndexContent") -> Iterable[Link]: ... + + +def with_cached_index_content(fn: ParseLinks) -> ParseLinks: + """ + Given a function that parses an Iterable[Link] from an IndexContent, cache the + function's result (keyed by CacheablePageContent), unless the IndexContent + `page` has `page.cache_link_parsing == False`. + """ + + @functools.lru_cache(maxsize=None) + def wrapper(cacheable_page: CacheablePageContent) -> List[Link]: + return list(fn(cacheable_page.page)) + + @functools.wraps(fn) + def wrapper_wrapper(page: "IndexContent") -> List[Link]: + if page.cache_link_parsing: + return wrapper(CacheablePageContent(page)) + return list(fn(page)) + + return wrapper_wrapper + + +@with_cached_index_content +def parse_links(page: "IndexContent") -> Iterable[Link]: + """ + Parse a Simple API's Index Content, and yield its anchor elements as Link objects. + """ + + content_type_l = page.content_type.lower() + if content_type_l.startswith("application/vnd.pypi.simple.v1+json"): + data = json.loads(page.content) + for file in data.get("files", []): + link = Link.from_json(file, page.url) + if link is None: + continue + yield link + return + + parser = HTMLLinkParser(page.url) + encoding = page.encoding or "utf-8" + parser.feed(page.content.decode(encoding)) + + url = page.url + base_url = parser.base_url or url + for anchor in parser.anchors: + link = Link.from_element(anchor, page_url=url, base_url=base_url) + if link is None: + continue + yield link + + +@dataclass(frozen=True) +class IndexContent: + """Represents one response (or page), along with its URL. + + :param encoding: the encoding to decode the given content. + :param url: the URL from which the HTML was downloaded. + :param cache_link_parsing: whether links parsed from this page's url + should be cached. PyPI index urls should + have this set to False, for example. + """ + + content: bytes + content_type: str + encoding: Optional[str] + url: str + cache_link_parsing: bool = True + + def __str__(self) -> str: + return redact_auth_from_url(self.url) + + +class HTMLLinkParser(HTMLParser): + """ + HTMLParser that keeps the first base HREF and a list of all anchor + elements' attributes. + """ + + def __init__(self, url: str) -> None: + super().__init__(convert_charrefs=True) + + self.url: str = url + self.base_url: Optional[str] = None + self.anchors: List[Dict[str, Optional[str]]] = [] + + def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None: + if tag == "base" and self.base_url is None: + href = self.get_href(attrs) + if href is not None: + self.base_url = href + elif tag == "a": + self.anchors.append(dict(attrs)) + + def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]: + for name, value in attrs: + if name == "href": + return value + return None + + +def _handle_get_simple_fail( + link: Link, + reason: Union[str, Exception], + meth: Optional[Callable[..., None]] = None, +) -> None: + if meth is None: + meth = logger.debug + meth("Could not fetch URL %s: %s - skipping", link, reason) + + +def _make_index_content( + response: Response, cache_link_parsing: bool = True +) -> IndexContent: + encoding = _get_encoding_from_headers(response.headers) + return IndexContent( + response.content, + response.headers["Content-Type"], + encoding=encoding, + url=response.url, + cache_link_parsing=cache_link_parsing, + ) + + +def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexContent"]: + url = link.url.split("#", 1)[0] + + # Check for VCS schemes that do not support lookup as web pages. + vcs_scheme = _match_vcs_scheme(url) + if vcs_scheme: + logger.warning( + "Cannot look at %s URL %s because it does not support lookup as web pages.", + vcs_scheme, + link, + ) + return None + + # Tack index.html onto file:// URLs that point to directories + scheme, _, path, _, _, _ = urllib.parse.urlparse(url) + if scheme == "file" and os.path.isdir(urllib.request.url2pathname(path)): + # add trailing slash if not present so urljoin doesn't trim + # final segment + if not url.endswith("/"): + url += "/" + # TODO: In the future, it would be nice if pip supported PEP 691 + # style responses in the file:// URLs, however there's no + # standard file extension for application/vnd.pypi.simple.v1+json + # so we'll need to come up with something on our own. + url = urllib.parse.urljoin(url, "index.html") + logger.debug(" file: URL is directory, getting %s", url) + + try: + resp = _get_simple_response(url, session=session) + except _NotHTTP: + logger.warning( + "Skipping page %s because it looks like an archive, and cannot " + "be checked by a HTTP HEAD request.", + link, + ) + except _NotAPIContent as exc: + logger.warning( + "Skipping page %s because the %s request got Content-Type: %s. " + "The only supported Content-Types are application/vnd.pypi.simple.v1+json, " + "application/vnd.pypi.simple.v1+html, and text/html", + link, + exc.request_desc, + exc.content_type, + ) + except NetworkConnectionError as exc: + _handle_get_simple_fail(link, exc) + except RetryError as exc: + _handle_get_simple_fail(link, exc) + except SSLError as exc: + reason = "There was a problem confirming the ssl certificate: " + reason += str(exc) + _handle_get_simple_fail(link, reason, meth=logger.info) + except requests.ConnectionError as exc: + _handle_get_simple_fail(link, f"connection error: {exc}") + except requests.Timeout: + _handle_get_simple_fail(link, "timed out") + else: + return _make_index_content(resp, cache_link_parsing=link.cache_link_parsing) + return None + + +class CollectedSources(NamedTuple): + find_links: Sequence[Optional[LinkSource]] + index_urls: Sequence[Optional[LinkSource]] + + +class LinkCollector: + """ + Responsible for collecting Link objects from all configured locations, + making network requests as needed. + + The class's main method is its collect_sources() method. + """ + + def __init__( + self, + session: PipSession, + search_scope: SearchScope, + ) -> None: + self.search_scope = search_scope + self.session = session + + @classmethod + def create( + cls, + session: PipSession, + options: Values, + suppress_no_index: bool = False, + ) -> "LinkCollector": + """ + :param session: The Session to use to make requests. + :param suppress_no_index: Whether to ignore the --no-index option + when constructing the SearchScope object. + """ + index_urls = [options.index_url] + options.extra_index_urls + if options.no_index and not suppress_no_index: + logger.debug( + "Ignoring indexes: %s", + ",".join(redact_auth_from_url(url) for url in index_urls), + ) + index_urls = [] + + # Make sure find_links is a list before passing to create(). + find_links = options.find_links or [] + + search_scope = SearchScope.create( + find_links=find_links, + index_urls=index_urls, + no_index=options.no_index, + ) + link_collector = LinkCollector( + session=session, + search_scope=search_scope, + ) + return link_collector + + @property + def find_links(self) -> List[str]: + return self.search_scope.find_links + + def fetch_response(self, location: Link) -> Optional[IndexContent]: + """ + Fetch an HTML page containing package links. + """ + return _get_index_content(location, session=self.session) + + def collect_sources( + self, + project_name: str, + candidates_from_page: CandidatesFromPage, + ) -> CollectedSources: + # The OrderedDict calls deduplicate sources by URL. + index_url_sources = collections.OrderedDict( + build_source( + loc, + candidates_from_page=candidates_from_page, + page_validator=self.session.is_secure_origin, + expand_dir=False, + cache_link_parsing=False, + project_name=project_name, + ) + for loc in self.search_scope.get_index_urls_locations(project_name) + ).values() + find_links_sources = collections.OrderedDict( + build_source( + loc, + candidates_from_page=candidates_from_page, + page_validator=self.session.is_secure_origin, + expand_dir=True, + cache_link_parsing=True, + project_name=project_name, + ) + for loc in self.find_links + ).values() + + if logger.isEnabledFor(logging.DEBUG): + lines = [ + f"* {s.link}" + for s in itertools.chain(find_links_sources, index_url_sources) + if s is not None and s.link is not None + ] + lines = [ + f"{len(lines)} location(s) to search " + f"for versions of {project_name}:" + ] + lines + logger.debug("\n".join(lines)) + + return CollectedSources( + find_links=list(find_links_sources), + index_urls=list(index_url_sources), + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/index/package_finder.py b/venv/lib/python3.8/site-packages/pip/_internal/index/package_finder.py new file mode 100644 index 000000000..85628ee5d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/index/package_finder.py @@ -0,0 +1,1029 @@ +"""Routines related to PyPI, indexes""" + +import enum +import functools +import itertools +import logging +import re +from dataclasses import dataclass +from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union + +from pip._vendor.packaging import specifiers +from pip._vendor.packaging.tags import Tag +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import InvalidVersion, _BaseVersion +from pip._vendor.packaging.version import parse as parse_version + +from pip._internal.exceptions import ( + BestVersionAlreadyInstalled, + DistributionNotFound, + InvalidWheelFilename, + UnsupportedWheel, +) +from pip._internal.index.collector import LinkCollector, parse_links +from pip._internal.models.candidate import InstallationCandidate +from pip._internal.models.format_control import FormatControl +from pip._internal.models.link import Link +from pip._internal.models.search_scope import SearchScope +from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.models.target_python import TargetPython +from pip._internal.models.wheel import Wheel +from pip._internal.req import InstallRequirement +from pip._internal.utils._log import getLogger +from pip._internal.utils.filetypes import WHEEL_EXTENSION +from pip._internal.utils.hashes import Hashes +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import build_netloc +from pip._internal.utils.packaging import check_requires_python +from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS + +if TYPE_CHECKING: + from pip._vendor.typing_extensions import TypeGuard + +__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"] + + +logger = getLogger(__name__) + +BuildTag = Union[Tuple[()], Tuple[int, str]] +CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag] + + +def _check_link_requires_python( + link: Link, + version_info: Tuple[int, int, int], + ignore_requires_python: bool = False, +) -> bool: + """ + Return whether the given Python version is compatible with a link's + "Requires-Python" value. + + :param version_info: A 3-tuple of ints representing the Python + major-minor-micro version to check. + :param ignore_requires_python: Whether to ignore the "Requires-Python" + value if the given Python version isn't compatible. + """ + try: + is_compatible = check_requires_python( + link.requires_python, + version_info=version_info, + ) + except specifiers.InvalidSpecifier: + logger.debug( + "Ignoring invalid Requires-Python (%r) for link: %s", + link.requires_python, + link, + ) + else: + if not is_compatible: + version = ".".join(map(str, version_info)) + if not ignore_requires_python: + logger.verbose( + "Link requires a different Python (%s not in: %r): %s", + version, + link.requires_python, + link, + ) + return False + + logger.debug( + "Ignoring failed Requires-Python check (%s not in: %r) for link: %s", + version, + link.requires_python, + link, + ) + + return True + + +class LinkType(enum.Enum): + candidate = enum.auto() + different_project = enum.auto() + yanked = enum.auto() + format_unsupported = enum.auto() + format_invalid = enum.auto() + platform_mismatch = enum.auto() + requires_python_mismatch = enum.auto() + + +class LinkEvaluator: + """ + Responsible for evaluating links for a particular project. + """ + + _py_version_re = re.compile(r"-py([123]\.?[0-9]?)$") + + # Don't include an allow_yanked default value to make sure each call + # site considers whether yanked releases are allowed. This also causes + # that decision to be made explicit in the calling code, which helps + # people when reading the code. + def __init__( + self, + project_name: str, + canonical_name: str, + formats: FrozenSet[str], + target_python: TargetPython, + allow_yanked: bool, + ignore_requires_python: Optional[bool] = None, + ) -> None: + """ + :param project_name: The user supplied package name. + :param canonical_name: The canonical package name. + :param formats: The formats allowed for this package. Should be a set + with 'binary' or 'source' or both in it. + :param target_python: The target Python interpreter to use when + evaluating link compatibility. This is used, for example, to + check wheel compatibility, as well as when checking the Python + version, e.g. the Python version embedded in a link filename + (or egg fragment) and against an HTML link's optional PEP 503 + "data-requires-python" attribute. + :param allow_yanked: Whether files marked as yanked (in the sense + of PEP 592) are permitted to be candidates for install. + :param ignore_requires_python: Whether to ignore incompatible + PEP 503 "data-requires-python" values in HTML links. Defaults + to False. + """ + if ignore_requires_python is None: + ignore_requires_python = False + + self._allow_yanked = allow_yanked + self._canonical_name = canonical_name + self._ignore_requires_python = ignore_requires_python + self._formats = formats + self._target_python = target_python + + self.project_name = project_name + + def evaluate_link(self, link: Link) -> Tuple[LinkType, str]: + """ + Determine whether a link is a candidate for installation. + + :return: A tuple (result, detail), where *result* is an enum + representing whether the evaluation found a candidate, or the reason + why one is not found. If a candidate is found, *detail* will be the + candidate's version string; if one is not found, it contains the + reason the link fails to qualify. + """ + version = None + if link.is_yanked and not self._allow_yanked: + reason = link.yanked_reason or "" + return (LinkType.yanked, f"yanked for reason: {reason}") + + if link.egg_fragment: + egg_info = link.egg_fragment + ext = link.ext + else: + egg_info, ext = link.splitext() + if not ext: + return (LinkType.format_unsupported, "not a file") + if ext not in SUPPORTED_EXTENSIONS: + return ( + LinkType.format_unsupported, + f"unsupported archive format: {ext}", + ) + if "binary" not in self._formats and ext == WHEEL_EXTENSION: + reason = f"No binaries permitted for {self.project_name}" + return (LinkType.format_unsupported, reason) + if "macosx10" in link.path and ext == ".zip": + return (LinkType.format_unsupported, "macosx10 one") + if ext == WHEEL_EXTENSION: + try: + wheel = Wheel(link.filename) + except InvalidWheelFilename: + return ( + LinkType.format_invalid, + "invalid wheel filename", + ) + if canonicalize_name(wheel.name) != self._canonical_name: + reason = f"wrong project name (not {self.project_name})" + return (LinkType.different_project, reason) + + supported_tags = self._target_python.get_unsorted_tags() + if not wheel.supported(supported_tags): + # Include the wheel's tags in the reason string to + # simplify troubleshooting compatibility issues. + file_tags = ", ".join(wheel.get_formatted_file_tags()) + reason = ( + f"none of the wheel's tags ({file_tags}) are compatible " + f"(run pip debug --verbose to show compatible tags)" + ) + return (LinkType.platform_mismatch, reason) + + version = wheel.version + + # This should be up by the self.ok_binary check, but see issue 2700. + if "source" not in self._formats and ext != WHEEL_EXTENSION: + reason = f"No sources permitted for {self.project_name}" + return (LinkType.format_unsupported, reason) + + if not version: + version = _extract_version_from_fragment( + egg_info, + self._canonical_name, + ) + if not version: + reason = f"Missing project version for {self.project_name}" + return (LinkType.format_invalid, reason) + + match = self._py_version_re.search(version) + if match: + version = version[: match.start()] + py_version = match.group(1) + if py_version != self._target_python.py_version: + return ( + LinkType.platform_mismatch, + "Python version is incorrect", + ) + + supports_python = _check_link_requires_python( + link, + version_info=self._target_python.py_version_info, + ignore_requires_python=self._ignore_requires_python, + ) + if not supports_python: + reason = f"{version} Requires-Python {link.requires_python}" + return (LinkType.requires_python_mismatch, reason) + + logger.debug("Found link %s, version: %s", link, version) + + return (LinkType.candidate, version) + + +def filter_unallowed_hashes( + candidates: List[InstallationCandidate], + hashes: Optional[Hashes], + project_name: str, +) -> List[InstallationCandidate]: + """ + Filter out candidates whose hashes aren't allowed, and return a new + list of candidates. + + If at least one candidate has an allowed hash, then all candidates with + either an allowed hash or no hash specified are returned. Otherwise, + the given candidates are returned. + + Including the candidates with no hash specified when there is a match + allows a warning to be logged if there is a more preferred candidate + with no hash specified. Returning all candidates in the case of no + matches lets pip report the hash of the candidate that would otherwise + have been installed (e.g. permitting the user to more easily update + their requirements file with the desired hash). + """ + if not hashes: + logger.debug( + "Given no hashes to check %s links for project %r: " + "discarding no candidates", + len(candidates), + project_name, + ) + # Make sure we're not returning back the given value. + return list(candidates) + + matches_or_no_digest = [] + # Collect the non-matches for logging purposes. + non_matches = [] + match_count = 0 + for candidate in candidates: + link = candidate.link + if not link.has_hash: + pass + elif link.is_hash_allowed(hashes=hashes): + match_count += 1 + else: + non_matches.append(candidate) + continue + + matches_or_no_digest.append(candidate) + + if match_count: + filtered = matches_or_no_digest + else: + # Make sure we're not returning back the given value. + filtered = list(candidates) + + if len(filtered) == len(candidates): + discard_message = "discarding no candidates" + else: + discard_message = "discarding {} non-matches:\n {}".format( + len(non_matches), + "\n ".join(str(candidate.link) for candidate in non_matches), + ) + + logger.debug( + "Checked %s links for project %r against %s hashes " + "(%s matches, %s no digest): %s", + len(candidates), + project_name, + hashes.digest_count, + match_count, + len(matches_or_no_digest) - match_count, + discard_message, + ) + + return filtered + + +@dataclass +class CandidatePreferences: + """ + Encapsulates some of the preferences for filtering and sorting + InstallationCandidate objects. + """ + + prefer_binary: bool = False + allow_all_prereleases: bool = False + + +@dataclass(frozen=True) +class BestCandidateResult: + """A collection of candidates, returned by `PackageFinder.find_best_candidate`. + + This class is only intended to be instantiated by CandidateEvaluator's + `compute_best_candidate()` method. + + :param all_candidates: A sequence of all available candidates found. + :param applicable_candidates: The applicable candidates. + :param best_candidate: The most preferred candidate found, or None + if no applicable candidates were found. + """ + + all_candidates: List[InstallationCandidate] + applicable_candidates: List[InstallationCandidate] + best_candidate: Optional[InstallationCandidate] + + def __post_init__(self) -> None: + assert set(self.applicable_candidates) <= set(self.all_candidates) + + if self.best_candidate is None: + assert not self.applicable_candidates + else: + assert self.best_candidate in self.applicable_candidates + + +class CandidateEvaluator: + """ + Responsible for filtering and sorting candidates for installation based + on what tags are valid. + """ + + @classmethod + def create( + cls, + project_name: str, + target_python: Optional[TargetPython] = None, + prefer_binary: bool = False, + allow_all_prereleases: bool = False, + specifier: Optional[specifiers.BaseSpecifier] = None, + hashes: Optional[Hashes] = None, + ) -> "CandidateEvaluator": + """Create a CandidateEvaluator object. + + :param target_python: The target Python interpreter to use when + checking compatibility. If None (the default), a TargetPython + object will be constructed from the running Python. + :param specifier: An optional object implementing `filter` + (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable + versions. + :param hashes: An optional collection of allowed hashes. + """ + if target_python is None: + target_python = TargetPython() + if specifier is None: + specifier = specifiers.SpecifierSet() + + supported_tags = target_python.get_sorted_tags() + + return cls( + project_name=project_name, + supported_tags=supported_tags, + specifier=specifier, + prefer_binary=prefer_binary, + allow_all_prereleases=allow_all_prereleases, + hashes=hashes, + ) + + def __init__( + self, + project_name: str, + supported_tags: List[Tag], + specifier: specifiers.BaseSpecifier, + prefer_binary: bool = False, + allow_all_prereleases: bool = False, + hashes: Optional[Hashes] = None, + ) -> None: + """ + :param supported_tags: The PEP 425 tags supported by the target + Python in order of preference (most preferred first). + """ + self._allow_all_prereleases = allow_all_prereleases + self._hashes = hashes + self._prefer_binary = prefer_binary + self._project_name = project_name + self._specifier = specifier + self._supported_tags = supported_tags + # Since the index of the tag in the _supported_tags list is used + # as a priority, precompute a map from tag to index/priority to be + # used in wheel.find_most_preferred_tag. + self._wheel_tag_preferences = { + tag: idx for idx, tag in enumerate(supported_tags) + } + + def get_applicable_candidates( + self, + candidates: List[InstallationCandidate], + ) -> List[InstallationCandidate]: + """ + Return the applicable candidates from a list of candidates. + """ + # Using None infers from the specifier instead. + allow_prereleases = self._allow_all_prereleases or None + specifier = self._specifier + + # We turn the version object into a str here because otherwise + # when we're debundled but setuptools isn't, Python will see + # packaging.version.Version and + # pkg_resources._vendor.packaging.version.Version as different + # types. This way we'll use a str as a common data interchange + # format. If we stop using the pkg_resources provided specifier + # and start using our own, we can drop the cast to str(). + candidates_and_versions = [(c, str(c.version)) for c in candidates] + versions = set( + specifier.filter( + (v for _, v in candidates_and_versions), + prereleases=allow_prereleases, + ) + ) + + applicable_candidates = [c for c, v in candidates_and_versions if v in versions] + filtered_applicable_candidates = filter_unallowed_hashes( + candidates=applicable_candidates, + hashes=self._hashes, + project_name=self._project_name, + ) + + return sorted(filtered_applicable_candidates, key=self._sort_key) + + def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey: + """ + Function to pass as the `key` argument to a call to sorted() to sort + InstallationCandidates by preference. + + Returns a tuple such that tuples sorting as greater using Python's + default comparison operator are more preferred. + + The preference is as follows: + + First and foremost, candidates with allowed (matching) hashes are + always preferred over candidates without matching hashes. This is + because e.g. if the only candidate with an allowed hash is yanked, + we still want to use that candidate. + + Second, excepting hash considerations, candidates that have been + yanked (in the sense of PEP 592) are always less preferred than + candidates that haven't been yanked. Then: + + If not finding wheels, they are sorted by version only. + If finding wheels, then the sort order is by version, then: + 1. existing installs + 2. wheels ordered via Wheel.support_index_min(self._supported_tags) + 3. source archives + If prefer_binary was set, then all wheels are sorted above sources. + + Note: it was considered to embed this logic into the Link + comparison operators, but then different sdist links + with the same version, would have to be considered equal + """ + valid_tags = self._supported_tags + support_num = len(valid_tags) + build_tag: BuildTag = () + binary_preference = 0 + link = candidate.link + if link.is_wheel: + # can raise InvalidWheelFilename + wheel = Wheel(link.filename) + try: + pri = -( + wheel.find_most_preferred_tag( + valid_tags, self._wheel_tag_preferences + ) + ) + except ValueError: + raise UnsupportedWheel( + f"{wheel.filename} is not a supported wheel for this platform. It " + "can't be sorted." + ) + if self._prefer_binary: + binary_preference = 1 + if wheel.build_tag is not None: + match = re.match(r"^(\d+)(.*)$", wheel.build_tag) + assert match is not None, "guaranteed by filename validation" + build_tag_groups = match.groups() + build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) + else: # sdist + pri = -(support_num) + has_allowed_hash = int(link.is_hash_allowed(self._hashes)) + yank_value = -1 * int(link.is_yanked) # -1 for yanked. + return ( + has_allowed_hash, + yank_value, + binary_preference, + candidate.version, + pri, + build_tag, + ) + + def sort_best_candidate( + self, + candidates: List[InstallationCandidate], + ) -> Optional[InstallationCandidate]: + """ + Return the best candidate per the instance's sort order, or None if + no candidate is acceptable. + """ + if not candidates: + return None + best_candidate = max(candidates, key=self._sort_key) + return best_candidate + + def compute_best_candidate( + self, + candidates: List[InstallationCandidate], + ) -> BestCandidateResult: + """ + Compute and return a `BestCandidateResult` instance. + """ + applicable_candidates = self.get_applicable_candidates(candidates) + + best_candidate = self.sort_best_candidate(applicable_candidates) + + return BestCandidateResult( + candidates, + applicable_candidates=applicable_candidates, + best_candidate=best_candidate, + ) + + +class PackageFinder: + """This finds packages. + + This is meant to match easy_install's technique for looking for + packages, by reading pages and looking for appropriate links. + """ + + def __init__( + self, + link_collector: LinkCollector, + target_python: TargetPython, + allow_yanked: bool, + format_control: Optional[FormatControl] = None, + candidate_prefs: Optional[CandidatePreferences] = None, + ignore_requires_python: Optional[bool] = None, + ) -> None: + """ + This constructor is primarily meant to be used by the create() class + method and from tests. + + :param format_control: A FormatControl object, used to control + the selection of source packages / binary packages when consulting + the index and links. + :param candidate_prefs: Options to use when creating a + CandidateEvaluator object. + """ + if candidate_prefs is None: + candidate_prefs = CandidatePreferences() + + format_control = format_control or FormatControl(set(), set()) + + self._allow_yanked = allow_yanked + self._candidate_prefs = candidate_prefs + self._ignore_requires_python = ignore_requires_python + self._link_collector = link_collector + self._target_python = target_python + + self.format_control = format_control + + # These are boring links that have already been logged somehow. + self._logged_links: Set[Tuple[Link, LinkType, str]] = set() + + # Don't include an allow_yanked default value to make sure each call + # site considers whether yanked releases are allowed. This also causes + # that decision to be made explicit in the calling code, which helps + # people when reading the code. + @classmethod + def create( + cls, + link_collector: LinkCollector, + selection_prefs: SelectionPreferences, + target_python: Optional[TargetPython] = None, + ) -> "PackageFinder": + """Create a PackageFinder. + + :param selection_prefs: The candidate selection preferences, as a + SelectionPreferences object. + :param target_python: The target Python interpreter to use when + checking compatibility. If None (the default), a TargetPython + object will be constructed from the running Python. + """ + if target_python is None: + target_python = TargetPython() + + candidate_prefs = CandidatePreferences( + prefer_binary=selection_prefs.prefer_binary, + allow_all_prereleases=selection_prefs.allow_all_prereleases, + ) + + return cls( + candidate_prefs=candidate_prefs, + link_collector=link_collector, + target_python=target_python, + allow_yanked=selection_prefs.allow_yanked, + format_control=selection_prefs.format_control, + ignore_requires_python=selection_prefs.ignore_requires_python, + ) + + @property + def target_python(self) -> TargetPython: + return self._target_python + + @property + def search_scope(self) -> SearchScope: + return self._link_collector.search_scope + + @search_scope.setter + def search_scope(self, search_scope: SearchScope) -> None: + self._link_collector.search_scope = search_scope + + @property + def find_links(self) -> List[str]: + return self._link_collector.find_links + + @property + def index_urls(self) -> List[str]: + return self.search_scope.index_urls + + @property + def proxy(self) -> Optional[str]: + return self._link_collector.session.pip_proxy + + @property + def trusted_hosts(self) -> Iterable[str]: + for host_port in self._link_collector.session.pip_trusted_origins: + yield build_netloc(*host_port) + + @property + def custom_cert(self) -> Optional[str]: + # session.verify is either a boolean (use default bundle/no SSL + # verification) or a string path to a custom CA bundle to use. We only + # care about the latter. + verify = self._link_collector.session.verify + return verify if isinstance(verify, str) else None + + @property + def client_cert(self) -> Optional[str]: + cert = self._link_collector.session.cert + assert not isinstance(cert, tuple), "pip only supports PEM client certs" + return cert + + @property + def allow_all_prereleases(self) -> bool: + return self._candidate_prefs.allow_all_prereleases + + def set_allow_all_prereleases(self) -> None: + self._candidate_prefs.allow_all_prereleases = True + + @property + def prefer_binary(self) -> bool: + return self._candidate_prefs.prefer_binary + + def set_prefer_binary(self) -> None: + self._candidate_prefs.prefer_binary = True + + def requires_python_skipped_reasons(self) -> List[str]: + reasons = { + detail + for _, result, detail in self._logged_links + if result == LinkType.requires_python_mismatch + } + return sorted(reasons) + + def make_link_evaluator(self, project_name: str) -> LinkEvaluator: + canonical_name = canonicalize_name(project_name) + formats = self.format_control.get_allowed_formats(canonical_name) + + return LinkEvaluator( + project_name=project_name, + canonical_name=canonical_name, + formats=formats, + target_python=self._target_python, + allow_yanked=self._allow_yanked, + ignore_requires_python=self._ignore_requires_python, + ) + + def _sort_links(self, links: Iterable[Link]) -> List[Link]: + """ + Returns elements of links in order, non-egg links first, egg links + second, while eliminating duplicates + """ + eggs, no_eggs = [], [] + seen: Set[Link] = set() + for link in links: + if link not in seen: + seen.add(link) + if link.egg_fragment: + eggs.append(link) + else: + no_eggs.append(link) + return no_eggs + eggs + + def _log_skipped_link(self, link: Link, result: LinkType, detail: str) -> None: + # This is a hot method so don't waste time hashing links unless we're + # actually going to log 'em. + if not logger.isEnabledFor(logging.DEBUG): + return + + entry = (link, result, detail) + if entry not in self._logged_links: + # Put the link at the end so the reason is more visible and because + # the link string is usually very long. + logger.debug("Skipping link: %s: %s", detail, link) + self._logged_links.add(entry) + + def get_install_candidate( + self, link_evaluator: LinkEvaluator, link: Link + ) -> Optional[InstallationCandidate]: + """ + If the link is a candidate for install, convert it to an + InstallationCandidate and return it. Otherwise, return None. + """ + result, detail = link_evaluator.evaluate_link(link) + if result != LinkType.candidate: + self._log_skipped_link(link, result, detail) + return None + + try: + return InstallationCandidate( + name=link_evaluator.project_name, + link=link, + version=detail, + ) + except InvalidVersion: + return None + + def evaluate_links( + self, link_evaluator: LinkEvaluator, links: Iterable[Link] + ) -> List[InstallationCandidate]: + """ + Convert links that are candidates to InstallationCandidate objects. + """ + candidates = [] + for link in self._sort_links(links): + candidate = self.get_install_candidate(link_evaluator, link) + if candidate is not None: + candidates.append(candidate) + + return candidates + + def process_project_url( + self, project_url: Link, link_evaluator: LinkEvaluator + ) -> List[InstallationCandidate]: + logger.debug( + "Fetching project page and analyzing links: %s", + project_url, + ) + index_response = self._link_collector.fetch_response(project_url) + if index_response is None: + return [] + + page_links = list(parse_links(index_response)) + + with indent_log(): + package_links = self.evaluate_links( + link_evaluator, + links=page_links, + ) + + return package_links + + @functools.lru_cache(maxsize=None) + def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]: + """Find all available InstallationCandidate for project_name + + This checks index_urls and find_links. + All versions found are returned as an InstallationCandidate list. + + See LinkEvaluator.evaluate_link() for details on which files + are accepted. + """ + link_evaluator = self.make_link_evaluator(project_name) + + collected_sources = self._link_collector.collect_sources( + project_name=project_name, + candidates_from_page=functools.partial( + self.process_project_url, + link_evaluator=link_evaluator, + ), + ) + + page_candidates_it = itertools.chain.from_iterable( + source.page_candidates() + for sources in collected_sources + for source in sources + if source is not None + ) + page_candidates = list(page_candidates_it) + + file_links_it = itertools.chain.from_iterable( + source.file_links() + for sources in collected_sources + for source in sources + if source is not None + ) + file_candidates = self.evaluate_links( + link_evaluator, + sorted(file_links_it, reverse=True), + ) + + if logger.isEnabledFor(logging.DEBUG) and file_candidates: + paths = [] + for candidate in file_candidates: + assert candidate.link.url # we need to have a URL + try: + paths.append(candidate.link.file_path) + except Exception: + paths.append(candidate.link.url) # it's not a local file + + logger.debug("Local files found: %s", ", ".join(paths)) + + # This is an intentional priority ordering + return file_candidates + page_candidates + + def make_candidate_evaluator( + self, + project_name: str, + specifier: Optional[specifiers.BaseSpecifier] = None, + hashes: Optional[Hashes] = None, + ) -> CandidateEvaluator: + """Create a CandidateEvaluator object to use.""" + candidate_prefs = self._candidate_prefs + return CandidateEvaluator.create( + project_name=project_name, + target_python=self._target_python, + prefer_binary=candidate_prefs.prefer_binary, + allow_all_prereleases=candidate_prefs.allow_all_prereleases, + specifier=specifier, + hashes=hashes, + ) + + @functools.lru_cache(maxsize=None) + def find_best_candidate( + self, + project_name: str, + specifier: Optional[specifiers.BaseSpecifier] = None, + hashes: Optional[Hashes] = None, + ) -> BestCandidateResult: + """Find matches for the given project and specifier. + + :param specifier: An optional object implementing `filter` + (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable + versions. + + :return: A `BestCandidateResult` instance. + """ + candidates = self.find_all_candidates(project_name) + candidate_evaluator = self.make_candidate_evaluator( + project_name=project_name, + specifier=specifier, + hashes=hashes, + ) + return candidate_evaluator.compute_best_candidate(candidates) + + def find_requirement( + self, req: InstallRequirement, upgrade: bool + ) -> Optional[InstallationCandidate]: + """Try to find a Link matching req + + Expects req, an InstallRequirement and upgrade, a boolean + Returns a InstallationCandidate if found, + Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise + """ + hashes = req.hashes(trust_internet=False) + best_candidate_result = self.find_best_candidate( + req.name, + specifier=req.specifier, + hashes=hashes, + ) + best_candidate = best_candidate_result.best_candidate + + installed_version: Optional[_BaseVersion] = None + if req.satisfied_by is not None: + installed_version = req.satisfied_by.version + + def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: + # This repeated parse_version and str() conversion is needed to + # handle different vendoring sources from pip and pkg_resources. + # If we stop using the pkg_resources provided specifier and start + # using our own, we can drop the cast to str(). + return ( + ", ".join( + sorted( + {str(c.version) for c in cand_iter}, + key=parse_version, + ) + ) + or "none" + ) + + if installed_version is None and best_candidate is None: + logger.critical( + "Could not find a version that satisfies the requirement %s " + "(from versions: %s)", + req, + _format_versions(best_candidate_result.all_candidates), + ) + + raise DistributionNotFound(f"No matching distribution found for {req}") + + def _should_install_candidate( + candidate: Optional[InstallationCandidate], + ) -> "TypeGuard[InstallationCandidate]": + if installed_version is None: + return True + if best_candidate is None: + return False + return best_candidate.version > installed_version + + if not upgrade and installed_version is not None: + if _should_install_candidate(best_candidate): + logger.debug( + "Existing installed version (%s) satisfies requirement " + "(most up-to-date version is %s)", + installed_version, + best_candidate.version, + ) + else: + logger.debug( + "Existing installed version (%s) is most up-to-date and " + "satisfies requirement", + installed_version, + ) + return None + + if _should_install_candidate(best_candidate): + logger.debug( + "Using version %s (newest of versions: %s)", + best_candidate.version, + _format_versions(best_candidate_result.applicable_candidates), + ) + return best_candidate + + # We have an existing version, and its the best version + logger.debug( + "Installed version (%s) is most up-to-date (past versions: %s)", + installed_version, + _format_versions(best_candidate_result.applicable_candidates), + ) + raise BestVersionAlreadyInstalled + + +def _find_name_version_sep(fragment: str, canonical_name: str) -> int: + """Find the separator's index based on the package's canonical name. + + :param fragment: A + filename "fragment" (stem) or + egg fragment. + :param canonical_name: The package's canonical name. + + This function is needed since the canonicalized name does not necessarily + have the same length as the egg info's name part. An example:: + + >>> fragment = 'foo__bar-1.0' + >>> canonical_name = 'foo-bar' + >>> _find_name_version_sep(fragment, canonical_name) + 8 + """ + # Project name and version must be separated by one single dash. Find all + # occurrences of dashes; if the string in front of it matches the canonical + # name, this is the one separating the name and version parts. + for i, c in enumerate(fragment): + if c != "-": + continue + if canonicalize_name(fragment[:i]) == canonical_name: + return i + raise ValueError(f"{fragment} does not match {canonical_name}") + + +def _extract_version_from_fragment(fragment: str, canonical_name: str) -> Optional[str]: + """Parse the version string from a + filename + "fragment" (stem) or egg fragment. + + :param fragment: The string to parse. E.g. foo-2.1 + :param canonical_name: The canonicalized name of the package this + belongs to. + """ + try: + version_start = _find_name_version_sep(fragment, canonical_name) + 1 + except ValueError: + return None + version = fragment[version_start:] + if not version: + return None + return version diff --git a/venv/lib/python3.8/site-packages/pip/_internal/index/sources.py b/venv/lib/python3.8/site-packages/pip/_internal/index/sources.py new file mode 100644 index 000000000..3dafb30e6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/index/sources.py @@ -0,0 +1,284 @@ +import logging +import mimetypes +import os +from collections import defaultdict +from typing import Callable, Dict, Iterable, List, Optional, Tuple + +from pip._vendor.packaging.utils import ( + InvalidSdistFilename, + InvalidWheelFilename, + canonicalize_name, + parse_sdist_filename, + parse_wheel_filename, +) + +from pip._internal.models.candidate import InstallationCandidate +from pip._internal.models.link import Link +from pip._internal.utils.urls import path_to_url, url_to_path +from pip._internal.vcs import is_url + +logger = logging.getLogger(__name__) + +FoundCandidates = Iterable[InstallationCandidate] +FoundLinks = Iterable[Link] +CandidatesFromPage = Callable[[Link], Iterable[InstallationCandidate]] +PageValidator = Callable[[Link], bool] + + +class LinkSource: + @property + def link(self) -> Optional[Link]: + """Returns the underlying link, if there's one.""" + raise NotImplementedError() + + def page_candidates(self) -> FoundCandidates: + """Candidates found by parsing an archive listing HTML file.""" + raise NotImplementedError() + + def file_links(self) -> FoundLinks: + """Links found by specifying archives directly.""" + raise NotImplementedError() + + +def _is_html_file(file_url: str) -> bool: + return mimetypes.guess_type(file_url, strict=False)[0] == "text/html" + + +class _FlatDirectoryToUrls: + """Scans directory and caches results""" + + def __init__(self, path: str) -> None: + self._path = path + self._page_candidates: List[str] = [] + self._project_name_to_urls: Dict[str, List[str]] = defaultdict(list) + self._scanned_directory = False + + def _scan_directory(self) -> None: + """Scans directory once and populates both page_candidates + and project_name_to_urls at the same time + """ + for entry in os.scandir(self._path): + url = path_to_url(entry.path) + if _is_html_file(url): + self._page_candidates.append(url) + continue + + # File must have a valid wheel or sdist name, + # otherwise not worth considering as a package + try: + project_filename = parse_wheel_filename(entry.name)[0] + except InvalidWheelFilename: + try: + project_filename = parse_sdist_filename(entry.name)[0] + except InvalidSdistFilename: + continue + + self._project_name_to_urls[project_filename].append(url) + self._scanned_directory = True + + @property + def page_candidates(self) -> List[str]: + if not self._scanned_directory: + self._scan_directory() + + return self._page_candidates + + @property + def project_name_to_urls(self) -> Dict[str, List[str]]: + if not self._scanned_directory: + self._scan_directory() + + return self._project_name_to_urls + + +class _FlatDirectorySource(LinkSource): + """Link source specified by ``--find-links=``. + + This looks the content of the directory, and returns: + + * ``page_candidates``: Links listed on each HTML file in the directory. + * ``file_candidates``: Archives in the directory. + """ + + _paths_to_urls: Dict[str, _FlatDirectoryToUrls] = {} + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + path: str, + project_name: str, + ) -> None: + self._candidates_from_page = candidates_from_page + self._project_name = canonicalize_name(project_name) + + # Get existing instance of _FlatDirectoryToUrls if it exists + if path in self._paths_to_urls: + self._path_to_urls = self._paths_to_urls[path] + else: + self._path_to_urls = _FlatDirectoryToUrls(path=path) + self._paths_to_urls[path] = self._path_to_urls + + @property + def link(self) -> Optional[Link]: + return None + + def page_candidates(self) -> FoundCandidates: + for url in self._path_to_urls.page_candidates: + yield from self._candidates_from_page(Link(url)) + + def file_links(self) -> FoundLinks: + for url in self._path_to_urls.project_name_to_urls[self._project_name]: + yield Link(url) + + +class _LocalFileSource(LinkSource): + """``--find-links=`` or ``--[extra-]index-url=``. + + If a URL is supplied, it must be a ``file:`` URL. If a path is supplied to + the option, it is converted to a URL first. This returns: + + * ``page_candidates``: Links listed on an HTML file. + * ``file_candidates``: The non-HTML file. + """ + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + link: Link, + ) -> None: + self._candidates_from_page = candidates_from_page + self._link = link + + @property + def link(self) -> Optional[Link]: + return self._link + + def page_candidates(self) -> FoundCandidates: + if not _is_html_file(self._link.url): + return + yield from self._candidates_from_page(self._link) + + def file_links(self) -> FoundLinks: + if _is_html_file(self._link.url): + return + yield self._link + + +class _RemoteFileSource(LinkSource): + """``--find-links=`` or ``--[extra-]index-url=``. + + This returns: + + * ``page_candidates``: Links listed on an HTML file. + * ``file_candidates``: The non-HTML file. + """ + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + page_validator: PageValidator, + link: Link, + ) -> None: + self._candidates_from_page = candidates_from_page + self._page_validator = page_validator + self._link = link + + @property + def link(self) -> Optional[Link]: + return self._link + + def page_candidates(self) -> FoundCandidates: + if not self._page_validator(self._link): + return + yield from self._candidates_from_page(self._link) + + def file_links(self) -> FoundLinks: + yield self._link + + +class _IndexDirectorySource(LinkSource): + """``--[extra-]index-url=``. + + This is treated like a remote URL; ``candidates_from_page`` contains logic + for this by appending ``index.html`` to the link. + """ + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + link: Link, + ) -> None: + self._candidates_from_page = candidates_from_page + self._link = link + + @property + def link(self) -> Optional[Link]: + return self._link + + def page_candidates(self) -> FoundCandidates: + yield from self._candidates_from_page(self._link) + + def file_links(self) -> FoundLinks: + return () + + +def build_source( + location: str, + *, + candidates_from_page: CandidatesFromPage, + page_validator: PageValidator, + expand_dir: bool, + cache_link_parsing: bool, + project_name: str, +) -> Tuple[Optional[str], Optional[LinkSource]]: + path: Optional[str] = None + url: Optional[str] = None + if os.path.exists(location): # Is a local path. + url = path_to_url(location) + path = location + elif location.startswith("file:"): # A file: URL. + url = location + path = url_to_path(location) + elif is_url(location): + url = location + + if url is None: + msg = ( + "Location '%s' is ignored: " + "it is either a non-existing path or lacks a specific scheme." + ) + logger.warning(msg, location) + return (None, None) + + if path is None: + source: LinkSource = _RemoteFileSource( + candidates_from_page=candidates_from_page, + page_validator=page_validator, + link=Link(url, cache_link_parsing=cache_link_parsing), + ) + return (url, source) + + if os.path.isdir(path): + if expand_dir: + source = _FlatDirectorySource( + candidates_from_page=candidates_from_page, + path=path, + project_name=project_name, + ) + else: + source = _IndexDirectorySource( + candidates_from_page=candidates_from_page, + link=Link(url, cache_link_parsing=cache_link_parsing), + ) + return (url, source) + elif os.path.isfile(path): + source = _LocalFileSource( + candidates_from_page=candidates_from_page, + link=Link(url, cache_link_parsing=cache_link_parsing), + ) + return (url, source) + logger.warning( + "Location '%s' is ignored: it is neither a file nor a directory.", + location, + ) + return (url, None) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/locations/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/locations/__init__.py new file mode 100644 index 000000000..32382be7f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/locations/__init__.py @@ -0,0 +1,456 @@ +import functools +import logging +import os +import pathlib +import sys +import sysconfig +from typing import Any, Dict, Generator, Optional, Tuple + +from pip._internal.models.scheme import SCHEME_KEYS, Scheme +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.virtualenv import running_under_virtualenv + +from . import _sysconfig +from .base import ( + USER_CACHE_DIR, + get_major_minor_version, + get_src_prefix, + is_osx_framework, + site_packages, + user_site, +) + +__all__ = [ + "USER_CACHE_DIR", + "get_bin_prefix", + "get_bin_user", + "get_major_minor_version", + "get_platlib", + "get_purelib", + "get_scheme", + "get_src_prefix", + "site_packages", + "user_site", +] + + +logger = logging.getLogger(__name__) + + +_PLATLIBDIR: str = getattr(sys, "platlibdir", "lib") + +_USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10) + + +def _should_use_sysconfig() -> bool: + """This function determines the value of _USE_SYSCONFIG. + + By default, pip uses sysconfig on Python 3.10+. + But Python distributors can override this decision by setting: + sysconfig._PIP_USE_SYSCONFIG = True / False + Rationale in https://github.com/pypa/pip/issues/10647 + + This is a function for testability, but should be constant during any one + run. + """ + return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT)) + + +_USE_SYSCONFIG = _should_use_sysconfig() + +if not _USE_SYSCONFIG: + # Import distutils lazily to avoid deprecation warnings, + # but import it soon enough that it is in memory and available during + # a pip reinstall. + from . import _distutils + +# Be noisy about incompatibilities if this platforms "should" be using +# sysconfig, but is explicitly opting out and using distutils instead. +if _USE_SYSCONFIG_DEFAULT and not _USE_SYSCONFIG: + _MISMATCH_LEVEL = logging.WARNING +else: + _MISMATCH_LEVEL = logging.DEBUG + + +def _looks_like_bpo_44860() -> bool: + """The resolution to bpo-44860 will change this incorrect platlib. + + See . + """ + from distutils.command.install import INSTALL_SCHEMES + + try: + unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"] + except KeyError: + return False + return unix_user_platlib == "$usersite" + + +def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool: + platlib = scheme["platlib"] + if "/$platlibdir/" in platlib: + platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/") + if "/lib64/" not in platlib: + return False + unpatched = platlib.replace("/lib64/", "/lib/") + return unpatched.replace("$platbase/", "$base/") == scheme["purelib"] + + +@functools.lru_cache(maxsize=None) +def _looks_like_red_hat_lib() -> bool: + """Red Hat patches platlib in unix_prefix and unix_home, but not purelib. + + This is the only way I can see to tell a Red Hat-patched Python. + """ + from distutils.command.install import INSTALL_SCHEMES + + return all( + k in INSTALL_SCHEMES + and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k]) + for k in ("unix_prefix", "unix_home") + ) + + +@functools.lru_cache(maxsize=None) +def _looks_like_debian_scheme() -> bool: + """Debian adds two additional schemes.""" + from distutils.command.install import INSTALL_SCHEMES + + return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES + + +@functools.lru_cache(maxsize=None) +def _looks_like_red_hat_scheme() -> bool: + """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``. + + Red Hat's ``00251-change-user-install-location.patch`` changes the install + command's ``prefix`` and ``exec_prefix`` to append ``"/local"``. This is + (fortunately?) done quite unconditionally, so we create a default command + object without any configuration to detect this. + """ + from distutils.command.install import install + from distutils.dist import Distribution + + cmd: Any = install(Distribution()) + cmd.finalize_options() + return ( + cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local" + and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local" + ) + + +@functools.lru_cache(maxsize=None) +def _looks_like_slackware_scheme() -> bool: + """Slackware patches sysconfig but fails to patch distutils and site. + + Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib + path, but does not do the same to the site module. + """ + if user_site is None: # User-site not available. + return False + try: + paths = sysconfig.get_paths(scheme="posix_user", expand=False) + except KeyError: # User-site not available. + return False + return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site + + +@functools.lru_cache(maxsize=None) +def _looks_like_msys2_mingw_scheme() -> bool: + """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme. + + However, MSYS2 incorrectly patches sysconfig ``nt`` scheme. The fix is + likely going to be included in their 3.10 release, so we ignore the warning. + See msys2/MINGW-packages#9319. + + MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase, + and is missing the final ``"site-packages"``. + """ + paths = sysconfig.get_paths("nt", expand=False) + return all( + "Lib" not in p and "lib" in p and not p.endswith("site-packages") + for p in (paths[key] for key in ("platlib", "purelib")) + ) + + +def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]: + ldversion = sysconfig.get_config_var("LDVERSION") + abiflags = getattr(sys, "abiflags", None) + + # LDVERSION does not end with sys.abiflags. Just return the path unchanged. + if not ldversion or not abiflags or not ldversion.endswith(abiflags): + yield from parts + return + + # Strip sys.abiflags from LDVERSION-based path components. + for part in parts: + if part.endswith(ldversion): + part = part[: (0 - len(abiflags))] + yield part + + +@functools.lru_cache(maxsize=None) +def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None: + issue_url = "https://github.com/pypa/pip/issues/10151" + message = ( + "Value for %s does not match. Please report this to <%s>" + "\ndistutils: %s" + "\nsysconfig: %s" + ) + logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new) + + +def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool: + if old == new: + return False + _warn_mismatched(old, new, key=key) + return True + + +@functools.lru_cache(maxsize=None) +def _log_context( + *, + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + prefix: Optional[str] = None, +) -> None: + parts = [ + "Additional context:", + "user = %r", + "home = %r", + "root = %r", + "prefix = %r", + ] + + logger.log(_MISMATCH_LEVEL, "\n".join(parts), user, home, root, prefix) + + +def get_scheme( + dist_name: str, + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + isolated: bool = False, + prefix: Optional[str] = None, +) -> Scheme: + new = _sysconfig.get_scheme( + dist_name, + user=user, + home=home, + root=root, + isolated=isolated, + prefix=prefix, + ) + if _USE_SYSCONFIG: + return new + + old = _distutils.get_scheme( + dist_name, + user=user, + home=home, + root=root, + isolated=isolated, + prefix=prefix, + ) + + warning_contexts = [] + for k in SCHEME_KEYS: + old_v = pathlib.Path(getattr(old, k)) + new_v = pathlib.Path(getattr(new, k)) + + if old_v == new_v: + continue + + # distutils incorrectly put PyPy packages under ``site-packages/python`` + # in the ``posix_home`` scheme, but PyPy devs said they expect the + # directory name to be ``pypy`` instead. So we treat this as a bug fix + # and not warn about it. See bpo-43307 and python/cpython#24628. + skip_pypy_special_case = ( + sys.implementation.name == "pypy" + and home is not None + and k in ("platlib", "purelib") + and old_v.parent == new_v.parent + and old_v.name.startswith("python") + and new_v.name.startswith("pypy") + ) + if skip_pypy_special_case: + continue + + # sysconfig's ``osx_framework_user`` does not include ``pythonX.Y`` in + # the ``include`` value, but distutils's ``headers`` does. We'll let + # CPython decide whether this is a bug or feature. See bpo-43948. + skip_osx_framework_user_special_case = ( + user + and is_osx_framework() + and k == "headers" + and old_v.parent.parent == new_v.parent + and old_v.parent.name.startswith("python") + ) + if skip_osx_framework_user_special_case: + continue + + # On Red Hat and derived Linux distributions, distutils is patched to + # use "lib64" instead of "lib" for platlib. + if k == "platlib" and _looks_like_red_hat_lib(): + continue + + # On Python 3.9+, sysconfig's posix_user scheme sets platlib against + # sys.platlibdir, but distutils's unix_user incorrectly coninutes + # using the same $usersite for both platlib and purelib. This creates a + # mismatch when sys.platlibdir is not "lib". + skip_bpo_44860 = ( + user + and k == "platlib" + and not WINDOWS + and sys.version_info >= (3, 9) + and _PLATLIBDIR != "lib" + and _looks_like_bpo_44860() + ) + if skip_bpo_44860: + continue + + # Slackware incorrectly patches posix_user to use lib64 instead of lib, + # but not usersite to match the location. + skip_slackware_user_scheme = ( + user + and k in ("platlib", "purelib") + and not WINDOWS + and _looks_like_slackware_scheme() + ) + if skip_slackware_user_scheme: + continue + + # Both Debian and Red Hat patch Python to place the system site under + # /usr/local instead of /usr. Debian also places lib in dist-packages + # instead of site-packages, but the /usr/local check should cover it. + skip_linux_system_special_case = ( + not (user or home or prefix or running_under_virtualenv()) + and old_v.parts[1:3] == ("usr", "local") + and len(new_v.parts) > 1 + and new_v.parts[1] == "usr" + and (len(new_v.parts) < 3 or new_v.parts[2] != "local") + and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme()) + ) + if skip_linux_system_special_case: + continue + + # MSYS2 MINGW's sysconfig patch does not include the "site-packages" + # part of the path. This is incorrect and will be fixed in MSYS. + skip_msys2_mingw_bug = ( + WINDOWS and k in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme() + ) + if skip_msys2_mingw_bug: + continue + + # CPython's POSIX install script invokes pip (via ensurepip) against the + # interpreter located in the source tree, not the install site. This + # triggers special logic in sysconfig that's not present in distutils. + # https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194 + skip_cpython_build = ( + sysconfig.is_python_build(check_home=True) + and not WINDOWS + and k in ("headers", "include", "platinclude") + ) + if skip_cpython_build: + continue + + warning_contexts.append((old_v, new_v, f"scheme.{k}")) + + if not warning_contexts: + return old + + # Check if this path mismatch is caused by distutils config files. Those + # files will no longer work once we switch to sysconfig, so this raises a + # deprecation message for them. + default_old = _distutils.distutils_scheme( + dist_name, + user, + home, + root, + isolated, + prefix, + ignore_config_files=True, + ) + if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS): + deprecated( + reason=( + "Configuring installation scheme with distutils config files " + "is deprecated and will no longer work in the near future. If you " + "are using a Homebrew or Linuxbrew Python, please see discussion " + "at https://github.com/Homebrew/homebrew-core/issues/76621" + ), + replacement=None, + gone_in=None, + ) + return old + + # Post warnings about this mismatch so user can report them back. + for old_v, new_v, key in warning_contexts: + _warn_mismatched(old_v, new_v, key=key) + _log_context(user=user, home=home, root=root, prefix=prefix) + + return old + + +def get_bin_prefix() -> str: + new = _sysconfig.get_bin_prefix() + if _USE_SYSCONFIG: + return new + + old = _distutils.get_bin_prefix() + if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"): + _log_context() + return old + + +def get_bin_user() -> str: + return _sysconfig.get_scheme("", user=True).scripts + + +def _looks_like_deb_system_dist_packages(value: str) -> bool: + """Check if the value is Debian's APT-controlled dist-packages. + + Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the + default package path controlled by APT, but does not patch ``sysconfig`` to + do the same. This is similar to the bug worked around in ``get_scheme()``, + but here the default is ``deb_system`` instead of ``unix_local``. Ultimately + we can't do anything about this Debian bug, and this detection allows us to + skip the warning when needed. + """ + if not _looks_like_debian_scheme(): + return False + if value == "/usr/lib/python3/dist-packages": + return True + return False + + +def get_purelib() -> str: + """Return the default pure-Python lib location.""" + new = _sysconfig.get_purelib() + if _USE_SYSCONFIG: + return new + + old = _distutils.get_purelib() + if _looks_like_deb_system_dist_packages(old): + return old + if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"): + _log_context() + return old + + +def get_platlib() -> str: + """Return the default platform-shared lib location.""" + new = _sysconfig.get_platlib() + if _USE_SYSCONFIG: + return new + + from . import _distutils + + old = _distutils.get_platlib() + if _looks_like_deb_system_dist_packages(old): + return old + if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"): + _log_context() + return old diff --git a/venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfde6d2ef2c9a72a5f00889a22e9e118c0167629 GIT binary patch literal 10965 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 O?L>YkZ;kBD=l>rrv^LNH literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abbb5387dfe5d5368ceea1cca124b982eadbab8d GIT binary patch literal 4604 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 g(K3gBU)cGQS;!h@`u9aLLaQx${!v^h(^b literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/base.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/locations/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..399ca302115471af181012055ec702d90f0119e7 GIT binary patch literal 2430 zcmZuy&2Jk;6rb5$e>QR41lpt&G^#+sqOn00A3_CDwLw%s0!gcag|u4ljN^6Iv&+ml zPE2y4aD)R_Kyr@!3!M2AIL(z4cTNZi-kWtwT6OK6ee?0&y!p*$`8z4cxcXEy34I%cDxSLea-h ztD{=C=IkJ?k1ljCP$Fux1ztL(-9-`18oUhFl4y#HymFI>Ca?ZPd5zb<3A)SB3O|0~ zC!a6yMQB->_c!uuQ>jcr zVo+p5(TYkCjN1o?X<55n&$1`_Z02$FdS; ztYnN6oqs&2+=f>#K*8yd7&`R6CyGu_ zobG$-CRpCgKc=Ttzv%4Xm_W_j#WCK@)wsv~Z@o2wt#Zft2H<<_MwHO^3-81`@sE98 zS|hv+eG=@s*NOiqlJDY{c?DzqNO<*U{~`UFavyk5i$0}<%vrF0ww84^Nz8z)pRID8 zVW0|s@#Q!8YHGM4E!MsE+ZM813$_c}Y9 zu~pDz8;GbTiH0zVftV0N2@TZT2dU(!rXqC& zO+3+NY?2g`%=kDJ#X4*$&vbJ5CtypqtS5~^>u>tSbosvAm)S(J9Pq*;@iv>jUcVhl zCNsn0K_vGCLuNT>5chs^hV0arK0^ZHat3fY7x6G6iGOGKL-tZ6$(Nd{s8v*+nQww&i3871hI#pldKL?Z$QoLBui0|30k#a<5Kp+RZ0|bZwt+ZI42XA6Bl4iZbx0&E zMn8#Y1BK=P?l&Q@Mj;`zOqT)1O8{&$P%q+ywXp{2N8!S|VH6$eWGWs57#%TR2QY>) zAnP*^0HhBQ6mQEeee(Ie54N}7-?{bi=7a5>4?(elUY4b{2I3oMvY+f(msDYg2a!6L zv*UxKLNn1}rVqou0**~GwZ9G16;kM_SHNyPZ)^Rrj12_W4nP}V?hZWFf)TcCj&j%q zy9(RTek8wbaTtpn??LUz*!~PLZx+(=E*>3?V?9!(kA5DW XwgtcLFZ+$^^3_Ju^RLhbw$T3oX Dict[str, str]: + """ + Return a distutils install scheme + """ + from distutils.dist import Distribution + + dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name} + if isolated: + dist_args["script_args"] = ["--no-user-cfg"] + + d = Distribution(dist_args) + if not ignore_config_files: + try: + d.parse_config_files() + except UnicodeDecodeError: + paths = d.find_config_files() + logger.warning( + "Ignore distutils configs in %s due to encoding errors.", + ", ".join(os.path.basename(p) for p in paths), + ) + obj: Optional[DistutilsCommand] = None + obj = d.get_command_obj("install", create=True) + assert obj is not None + i: distutils_install_command = obj + # NOTE: setting user or home has the side-effect of creating the home dir + # or user base for installations during finalize_options() + # ideally, we'd prefer a scheme class that has no side-effects. + assert not (user and prefix), f"user={user} prefix={prefix}" + assert not (home and prefix), f"home={home} prefix={prefix}" + i.user = user or i.user + if user or home: + i.prefix = "" + i.prefix = prefix or i.prefix + i.home = home or i.home + i.root = root or i.root + i.finalize_options() + + scheme: Dict[str, str] = {} + for key in SCHEME_KEYS: + scheme[key] = getattr(i, "install_" + key) + + # install_lib specified in setup.cfg should install *everything* + # into there (i.e. it takes precedence over both purelib and + # platlib). Note, i.install_lib is *always* set after + # finalize_options(); we only want to override here if the user + # has explicitly requested it hence going back to the config + if "install_lib" in d.get_option_dict("install"): + scheme.update({"purelib": i.install_lib, "platlib": i.install_lib}) + + if running_under_virtualenv(): + if home: + prefix = home + elif user: + prefix = i.install_userbase + else: + prefix = i.prefix + scheme["headers"] = os.path.join( + prefix, + "include", + "site", + f"python{get_major_minor_version()}", + dist_name, + ) + + if root is not None: + path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1] + scheme["headers"] = os.path.join(root, path_no_drive[1:]) + + return scheme + + +def get_scheme( + dist_name: str, + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + isolated: bool = False, + prefix: Optional[str] = None, +) -> Scheme: + """ + Get the "scheme" corresponding to the input parameters. The distutils + documentation provides the context for the available schemes: + https://docs.python.org/3/install/index.html#alternate-installation + + :param dist_name: the name of the package to retrieve the scheme for, used + in the headers scheme path + :param user: indicates to use the "user" scheme + :param home: indicates to use the "home" scheme and provides the base + directory for the same + :param root: root under which other directories are re-based + :param isolated: equivalent to --no-user-cfg, i.e. do not consider + ~/.pydistutils.cfg (posix) or ~/pydistutils.cfg (non-posix) for + scheme paths + :param prefix: indicates to use the "prefix" scheme and provides the + base directory for the same + """ + scheme = distutils_scheme(dist_name, user, home, root, isolated, prefix) + return Scheme( + platlib=scheme["platlib"], + purelib=scheme["purelib"], + headers=scheme["headers"], + scripts=scheme["scripts"], + data=scheme["data"], + ) + + +def get_bin_prefix() -> str: + # XXX: In old virtualenv versions, sys.prefix can contain '..' components, + # so we need to call normpath to eliminate them. + prefix = os.path.normpath(sys.prefix) + if WINDOWS: + bin_py = os.path.join(prefix, "Scripts") + # buildout uses 'bin' on Windows too? + if not os.path.exists(bin_py): + bin_py = os.path.join(prefix, "bin") + return bin_py + # Forcing to use /usr/local/bin for standard macOS framework installs + # Also log to ~/Library/Logs/ for use with the Console.app log viewer + if sys.platform[:6] == "darwin" and prefix[:16] == "/System/Library/": + return "/usr/local/bin" + return os.path.join(prefix, "bin") + + +def get_purelib() -> str: + return get_python_lib(plat_specific=False) + + +def get_platlib() -> str: + return get_python_lib(plat_specific=True) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/locations/_sysconfig.py b/venv/lib/python3.8/site-packages/pip/_internal/locations/_sysconfig.py new file mode 100644 index 000000000..ca860ea56 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/locations/_sysconfig.py @@ -0,0 +1,214 @@ +import logging +import os +import sys +import sysconfig +import typing + +from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid +from pip._internal.models.scheme import SCHEME_KEYS, Scheme +from pip._internal.utils.virtualenv import running_under_virtualenv + +from .base import change_root, get_major_minor_version, is_osx_framework + +logger = logging.getLogger(__name__) + + +# Notes on _infer_* functions. +# Unfortunately ``get_default_scheme()`` didn't exist before 3.10, so there's no +# way to ask things like "what is the '_prefix' scheme on this platform". These +# functions try to answer that with some heuristics while accounting for ad-hoc +# platforms not covered by CPython's default sysconfig implementation. If the +# ad-hoc implementation does not fully implement sysconfig, we'll fall back to +# a POSIX scheme. + +_AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names()) + +_PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None) + + +def _should_use_osx_framework_prefix() -> bool: + """Check for Apple's ``osx_framework_library`` scheme. + + Python distributed by Apple's Command Line Tools has this special scheme + that's used when: + + * This is a framework build. + * We are installing into the system prefix. + + This does not account for ``pip install --prefix`` (also means we're not + installing to the system prefix), which should use ``posix_prefix``, but + logic here means ``_infer_prefix()`` outputs ``osx_framework_library``. But + since ``prefix`` is not available for ``sysconfig.get_default_scheme()``, + which is the stdlib replacement for ``_infer_prefix()``, presumably Apple + wouldn't be able to magically switch between ``osx_framework_library`` and + ``posix_prefix``. ``_infer_prefix()`` returning ``osx_framework_library`` + means its behavior is consistent whether we use the stdlib implementation + or our own, and we deal with this special case in ``get_scheme()`` instead. + """ + return ( + "osx_framework_library" in _AVAILABLE_SCHEMES + and not running_under_virtualenv() + and is_osx_framework() + ) + + +def _infer_prefix() -> str: + """Try to find a prefix scheme for the current platform. + + This tries: + + * A special ``osx_framework_library`` for Python distributed by Apple's + Command Line Tools, when not running in a virtual environment. + * Implementation + OS, used by PyPy on Windows (``pypy_nt``). + * Implementation without OS, used by PyPy on POSIX (``pypy``). + * OS + "prefix", used by CPython on POSIX (``posix_prefix``). + * Just the OS name, used by CPython on Windows (``nt``). + + If none of the above works, fall back to ``posix_prefix``. + """ + if _PREFERRED_SCHEME_API: + return _PREFERRED_SCHEME_API("prefix") + if _should_use_osx_framework_prefix(): + return "osx_framework_library" + implementation_suffixed = f"{sys.implementation.name}_{os.name}" + if implementation_suffixed in _AVAILABLE_SCHEMES: + return implementation_suffixed + if sys.implementation.name in _AVAILABLE_SCHEMES: + return sys.implementation.name + suffixed = f"{os.name}_prefix" + if suffixed in _AVAILABLE_SCHEMES: + return suffixed + if os.name in _AVAILABLE_SCHEMES: # On Windows, prefx is just called "nt". + return os.name + return "posix_prefix" + + +def _infer_user() -> str: + """Try to find a user scheme for the current platform.""" + if _PREFERRED_SCHEME_API: + return _PREFERRED_SCHEME_API("user") + if is_osx_framework() and not running_under_virtualenv(): + suffixed = "osx_framework_user" + else: + suffixed = f"{os.name}_user" + if suffixed in _AVAILABLE_SCHEMES: + return suffixed + if "posix_user" not in _AVAILABLE_SCHEMES: # User scheme unavailable. + raise UserInstallationInvalid() + return "posix_user" + + +def _infer_home() -> str: + """Try to find a home for the current platform.""" + if _PREFERRED_SCHEME_API: + return _PREFERRED_SCHEME_API("home") + suffixed = f"{os.name}_home" + if suffixed in _AVAILABLE_SCHEMES: + return suffixed + return "posix_home" + + +# Update these keys if the user sets a custom home. +_HOME_KEYS = [ + "installed_base", + "base", + "installed_platbase", + "platbase", + "prefix", + "exec_prefix", +] +if sysconfig.get_config_var("userbase") is not None: + _HOME_KEYS.append("userbase") + + +def get_scheme( + dist_name: str, + user: bool = False, + home: typing.Optional[str] = None, + root: typing.Optional[str] = None, + isolated: bool = False, + prefix: typing.Optional[str] = None, +) -> Scheme: + """ + Get the "scheme" corresponding to the input parameters. + + :param dist_name: the name of the package to retrieve the scheme for, used + in the headers scheme path + :param user: indicates to use the "user" scheme + :param home: indicates to use the "home" scheme + :param root: root under which other directories are re-based + :param isolated: ignored, but kept for distutils compatibility (where + this controls whether the user-site pydistutils.cfg is honored) + :param prefix: indicates to use the "prefix" scheme and provides the + base directory for the same + """ + if user and prefix: + raise InvalidSchemeCombination("--user", "--prefix") + if home and prefix: + raise InvalidSchemeCombination("--home", "--prefix") + + if home is not None: + scheme_name = _infer_home() + elif user: + scheme_name = _infer_user() + else: + scheme_name = _infer_prefix() + + # Special case: When installing into a custom prefix, use posix_prefix + # instead of osx_framework_library. See _should_use_osx_framework_prefix() + # docstring for details. + if prefix is not None and scheme_name == "osx_framework_library": + scheme_name = "posix_prefix" + + if home is not None: + variables = {k: home for k in _HOME_KEYS} + elif prefix is not None: + variables = {k: prefix for k in _HOME_KEYS} + else: + variables = {} + + paths = sysconfig.get_paths(scheme=scheme_name, vars=variables) + + # Logic here is very arbitrary, we're doing it for compatibility, don't ask. + # 1. Pip historically uses a special header path in virtual environments. + # 2. If the distribution name is not known, distutils uses 'UNKNOWN'. We + # only do the same when not running in a virtual environment because + # pip's historical header path logic (see point 1) did not do this. + if running_under_virtualenv(): + if user: + base = variables.get("userbase", sys.prefix) + else: + base = variables.get("base", sys.prefix) + python_xy = f"python{get_major_minor_version()}" + paths["include"] = os.path.join(base, "include", "site", python_xy) + elif not dist_name: + dist_name = "UNKNOWN" + + scheme = Scheme( + platlib=paths["platlib"], + purelib=paths["purelib"], + headers=os.path.join(paths["include"], dist_name), + scripts=paths["scripts"], + data=paths["data"], + ) + if root is not None: + converted_keys = {} + for key in SCHEME_KEYS: + converted_keys[key] = change_root(root, getattr(scheme, key)) + scheme = Scheme(**converted_keys) + return scheme + + +def get_bin_prefix() -> str: + # Forcing to use /usr/local/bin for standard macOS framework installs. + if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/": + return "/usr/local/bin" + return sysconfig.get_paths()["scripts"] + + +def get_purelib() -> str: + return sysconfig.get_paths()["purelib"] + + +def get_platlib() -> str: + return sysconfig.get_paths()["platlib"] diff --git a/venv/lib/python3.8/site-packages/pip/_internal/locations/base.py b/venv/lib/python3.8/site-packages/pip/_internal/locations/base.py new file mode 100644 index 000000000..3f9f896e6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/locations/base.py @@ -0,0 +1,81 @@ +import functools +import os +import site +import sys +import sysconfig +import typing + +from pip._internal.exceptions import InstallationError +from pip._internal.utils import appdirs +from pip._internal.utils.virtualenv import running_under_virtualenv + +# Application Directories +USER_CACHE_DIR = appdirs.user_cache_dir("pip") + +# FIXME doesn't account for venv linked to global site-packages +site_packages: str = sysconfig.get_path("purelib") + + +def get_major_minor_version() -> str: + """ + Return the major-minor version of the current Python as a string, e.g. + "3.7" or "3.10". + """ + return "{}.{}".format(*sys.version_info) + + +def change_root(new_root: str, pathname: str) -> str: + """Return 'pathname' with 'new_root' prepended. + + If 'pathname' is relative, this is equivalent to os.path.join(new_root, pathname). + Otherwise, it requires making 'pathname' relative and then joining the + two, which is tricky on DOS/Windows and Mac OS. + + This is borrowed from Python's standard library's distutils module. + """ + if os.name == "posix": + if not os.path.isabs(pathname): + return os.path.join(new_root, pathname) + else: + return os.path.join(new_root, pathname[1:]) + + elif os.name == "nt": + (drive, path) = os.path.splitdrive(pathname) + if path[0] == "\\": + path = path[1:] + return os.path.join(new_root, path) + + else: + raise InstallationError( + f"Unknown platform: {os.name}\n" + "Can not change root path prefix on unknown platform." + ) + + +def get_src_prefix() -> str: + if running_under_virtualenv(): + src_prefix = os.path.join(sys.prefix, "src") + else: + # FIXME: keep src in cwd for now (it is not a temporary folder) + try: + src_prefix = os.path.join(os.getcwd(), "src") + except OSError: + # In case the current working directory has been renamed or deleted + sys.exit("The folder you are executing pip from can no longer be found.") + + # under macOS + virtualenv sys.prefix is not properly resolved + # it is something like /path/to/python/bin/.. + return os.path.abspath(src_prefix) + + +try: + # Use getusersitepackages if this is present, as it ensures that the + # value is initialised properly. + user_site: typing.Optional[str] = site.getusersitepackages() +except AttributeError: + user_site = site.USER_SITE + + +@functools.lru_cache(maxsize=None) +def is_osx_framework() -> bool: + return bool(sysconfig.get_config_var("PYTHONFRAMEWORK")) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/main.py b/venv/lib/python3.8/site-packages/pip/_internal/main.py new file mode 100644 index 000000000..33c6d24cd --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/main.py @@ -0,0 +1,12 @@ +from typing import List, Optional + + +def main(args: Optional[List[str]] = None) -> int: + """This is preserved for old console scripts that may still be referencing + it. + + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pip._internal.utils.entrypoints import _wrapper + + return _wrapper(args) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/metadata/__init__.py new file mode 100644 index 000000000..1ea1e7fd2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/metadata/__init__.py @@ -0,0 +1,128 @@ +import contextlib +import functools +import os +import sys +from typing import TYPE_CHECKING, List, Optional, Type, cast + +from pip._internal.utils.misc import strtobool + +from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel + +if TYPE_CHECKING: + from typing import Literal, Protocol +else: + Protocol = object + +__all__ = [ + "BaseDistribution", + "BaseEnvironment", + "FilesystemWheel", + "MemoryWheel", + "Wheel", + "get_default_environment", + "get_environment", + "get_wheel_distribution", + "select_backend", +] + + +def _should_use_importlib_metadata() -> bool: + """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend. + + By default, pip uses ``importlib.metadata`` on Python 3.11+, and + ``pkg_resources`` otherwise. This can be overridden by a couple of ways: + + * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it + dictates whether ``importlib.metadata`` is used, regardless of Python + version. + * On Python 3.11+, Python distributors can patch ``importlib.metadata`` + to add a global constant ``_PIP_USE_IMPORTLIB_METADATA = False``. This + makes pip use ``pkg_resources`` (unless the user set the aforementioned + environment variable to *True*). + """ + with contextlib.suppress(KeyError, ValueError): + return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"])) + if sys.version_info < (3, 11): + return False + import importlib.metadata + + return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True)) + + +class Backend(Protocol): + NAME: 'Literal["importlib", "pkg_resources"]' + Distribution: Type[BaseDistribution] + Environment: Type[BaseEnvironment] + + +@functools.lru_cache(maxsize=None) +def select_backend() -> Backend: + if _should_use_importlib_metadata(): + from . import importlib + + return cast(Backend, importlib) + from . import pkg_resources + + return cast(Backend, pkg_resources) + + +def get_default_environment() -> BaseEnvironment: + """Get the default representation for the current environment. + + This returns an Environment instance from the chosen backend. The default + Environment instance should be built from ``sys.path`` and may use caching + to share instance state across calls. + """ + return select_backend().Environment.default() + + +def get_environment(paths: Optional[List[str]]) -> BaseEnvironment: + """Get a representation of the environment specified by ``paths``. + + This returns an Environment instance from the chosen backend based on the + given import paths. The backend must build a fresh instance representing + the state of installed distributions when this function is called. + """ + return select_backend().Environment.from_paths(paths) + + +def get_directory_distribution(directory: str) -> BaseDistribution: + """Get the distribution metadata representation in the specified directory. + + This returns a Distribution instance from the chosen backend based on + the given on-disk ``.dist-info`` directory. + """ + return select_backend().Distribution.from_directory(directory) + + +def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution: + """Get the representation of the specified wheel's distribution metadata. + + This returns a Distribution instance from the chosen backend based on + the given wheel's ``.dist-info`` directory. + + :param canonical_name: Normalized project name of the given wheel. + """ + return select_backend().Distribution.from_wheel(wheel, canonical_name) + + +def get_metadata_distribution( + metadata_contents: bytes, + filename: str, + canonical_name: str, +) -> BaseDistribution: + """Get the dist representation of the specified METADATA file contents. + + This returns a Distribution instance from the chosen backend sourced from the data + in `metadata_contents`. + + :param metadata_contents: Contents of a METADATA file within a dist, or one served + via PEP 658. + :param filename: Filename for the dist this metadata represents. + :param canonical_name: Normalized project name of the given dist. + """ + return select_backend().Distribution.from_metadata_file_contents( + metadata_contents, + filename, + canonical_name, + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..569f4d4492cd18f943307e18002b0a596a689505 GIT binary patch literal 4843 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 X*SiI}=fLpGCIi#|_wP@$Qa1kqlj&jG literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/base.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8e0f091d2a74edbc0dcb873e33eff8bfb7f7d76 GIT binary patch literal 27287 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
      2nH*>GeI_FF$W?^)Qp-}fv1Y_a0UG@8 zLS8aZ%zF(9Z^lpmJ32Ok_^B~t1ULVW8C&xs*oBZ^=BPQG9oxh$9`i>d4~-SZzMSE| Mh0#l6#j(==1sZU2j{pDw literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b578bdf8c749c370a76efd05aa2bf5af8ecc2cf GIT binary patch literal 11049 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 str: + return field.lower().replace("-", "_") + + +def msg_to_json(msg: Message) -> Dict[str, Any]: + """Convert a Message object into a JSON-compatible dictionary.""" + + def sanitise_header(h: Union[Header, str]) -> str: + if isinstance(h, Header): + chunks = [] + for bytes, encoding in decode_header(h): + if encoding == "unknown-8bit": + try: + # See if UTF-8 works + bytes.decode("utf-8") + encoding = "utf-8" + except UnicodeDecodeError: + # If not, latin1 at least won't fail + encoding = "latin1" + chunks.append((bytes, encoding)) + return str(make_header(chunks)) + return str(h) + + result = {} + for field, multi in METADATA_FIELDS: + if field not in msg: + continue + key = json_name(field) + if multi: + value: Union[str, List[str]] = [ + sanitise_header(v) for v in msg.get_all(field) # type: ignore + ] + else: + value = sanitise_header(msg.get(field)) # type: ignore + if key == "keywords": + # Accept both comma-separated and space-separated + # forms, for better compatibility with old data. + if "," in value: + value = [v.strip() for v in value.split(",")] + else: + value = value.split() + result[key] = value + + payload = cast(str, msg.get_payload()) + if payload: + result["description"] = payload + + return result diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/base.py b/venv/lib/python3.8/site-packages/pip/_internal/metadata/base.py new file mode 100644 index 000000000..9eabcdb27 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/metadata/base.py @@ -0,0 +1,688 @@ +import csv +import email.message +import functools +import json +import logging +import pathlib +import re +import zipfile +from typing import ( + IO, + Any, + Collection, + Container, + Dict, + Iterable, + Iterator, + List, + NamedTuple, + Optional, + Protocol, + Tuple, + Union, +) + +from pip._vendor.packaging.requirements import Requirement +from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.version import Version + +from pip._internal.exceptions import NoneMetadataError +from pip._internal.locations import site_packages, user_site +from pip._internal.models.direct_url import ( + DIRECT_URL_METADATA_NAME, + DirectUrl, + DirectUrlValidationError, +) +from pip._internal.utils.compat import stdlib_pkgs # TODO: Move definition here. +from pip._internal.utils.egg_link import egg_link_path_from_sys_path +from pip._internal.utils.misc import is_local, normalize_path +from pip._internal.utils.urls import url_to_path + +from ._json import msg_to_json + +InfoPath = Union[str, pathlib.PurePath] + +logger = logging.getLogger(__name__) + + +class BaseEntryPoint(Protocol): + @property + def name(self) -> str: + raise NotImplementedError() + + @property + def value(self) -> str: + raise NotImplementedError() + + @property + def group(self) -> str: + raise NotImplementedError() + + +def _convert_installed_files_path( + entry: Tuple[str, ...], + info: Tuple[str, ...], +) -> str: + """Convert a legacy installed-files.txt path into modern RECORD path. + + The legacy format stores paths relative to the info directory, while the + modern format stores paths relative to the package root, e.g. the + site-packages directory. + + :param entry: Path parts of the installed-files.txt entry. + :param info: Path parts of the egg-info directory relative to package root. + :returns: The converted entry. + + For best compatibility with symlinks, this does not use ``abspath()`` or + ``Path.resolve()``, but tries to work with path parts: + + 1. While ``entry`` starts with ``..``, remove the equal amounts of parts + from ``info``; if ``info`` is empty, start appending ``..`` instead. + 2. Join the two directly. + """ + while entry and entry[0] == "..": + if not info or info[-1] == "..": + info += ("..",) + else: + info = info[:-1] + entry = entry[1:] + return str(pathlib.Path(*info, *entry)) + + +class RequiresEntry(NamedTuple): + requirement: str + extra: str + marker: str + + +class BaseDistribution(Protocol): + @classmethod + def from_directory(cls, directory: str) -> "BaseDistribution": + """Load the distribution from a metadata directory. + + :param directory: Path to a metadata directory, e.g. ``.dist-info``. + """ + raise NotImplementedError() + + @classmethod + def from_metadata_file_contents( + cls, + metadata_contents: bytes, + filename: str, + project_name: str, + ) -> "BaseDistribution": + """Load the distribution from the contents of a METADATA file. + + This is used to implement PEP 658 by generating a "shallow" dist object that can + be used for resolution without downloading or building the actual dist yet. + + :param metadata_contents: The contents of a METADATA file. + :param filename: File name for the dist with this metadata. + :param project_name: Name of the project this dist represents. + """ + raise NotImplementedError() + + @classmethod + def from_wheel(cls, wheel: "Wheel", name: str) -> "BaseDistribution": + """Load the distribution from a given wheel. + + :param wheel: A concrete wheel definition. + :param name: File name of the wheel. + + :raises InvalidWheel: Whenever loading of the wheel causes a + :py:exc:`zipfile.BadZipFile` exception to be thrown. + :raises UnsupportedWheel: If the wheel is a valid zip, but malformed + internally. + """ + raise NotImplementedError() + + def __repr__(self) -> str: + return f"{self.raw_name} {self.raw_version} ({self.location})" + + def __str__(self) -> str: + return f"{self.raw_name} {self.raw_version}" + + @property + def location(self) -> Optional[str]: + """Where the distribution is loaded from. + + A string value is not necessarily a filesystem path, since distributions + can be loaded from other sources, e.g. arbitrary zip archives. ``None`` + means the distribution is created in-memory. + + Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If + this is a symbolic link, we want to preserve the relative path between + it and files in the distribution. + """ + raise NotImplementedError() + + @property + def editable_project_location(self) -> Optional[str]: + """The project location for editable distributions. + + This is the directory where pyproject.toml or setup.py is located. + None if the distribution is not installed in editable mode. + """ + # TODO: this property is relatively costly to compute, memoize it ? + direct_url = self.direct_url + if direct_url: + if direct_url.is_local_editable(): + return url_to_path(direct_url.url) + else: + # Search for an .egg-link file by walking sys.path, as it was + # done before by dist_is_editable(). + egg_link_path = egg_link_path_from_sys_path(self.raw_name) + if egg_link_path: + # TODO: get project location from second line of egg_link file + # (https://github.com/pypa/pip/issues/10243) + return self.location + return None + + @property + def installed_location(self) -> Optional[str]: + """The distribution's "installed" location. + + This should generally be a ``site-packages`` directory. This is + usually ``dist.location``, except for legacy develop-installed packages, + where ``dist.location`` is the source code location, and this is where + the ``.egg-link`` file is. + + The returned location is normalized (in particular, with symlinks removed). + """ + raise NotImplementedError() + + @property + def info_location(self) -> Optional[str]: + """Location of the .[egg|dist]-info directory or file. + + Similarly to ``location``, a string value is not necessarily a + filesystem path. ``None`` means the distribution is created in-memory. + + For a modern .dist-info installation on disk, this should be something + like ``{location}/{raw_name}-{version}.dist-info``. + + Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If + this is a symbolic link, we want to preserve the relative path between + it and other files in the distribution. + """ + raise NotImplementedError() + + @property + def installed_by_distutils(self) -> bool: + """Whether this distribution is installed with legacy distutils format. + + A distribution installed with "raw" distutils not patched by setuptools + uses one single file at ``info_location`` to store metadata. We need to + treat this specially on uninstallation. + """ + info_location = self.info_location + if not info_location: + return False + return pathlib.Path(info_location).is_file() + + @property + def installed_as_egg(self) -> bool: + """Whether this distribution is installed as an egg. + + This usually indicates the distribution was installed by (older versions + of) easy_install. + """ + location = self.location + if not location: + return False + return location.endswith(".egg") + + @property + def installed_with_setuptools_egg_info(self) -> bool: + """Whether this distribution is installed with the ``.egg-info`` format. + + This usually indicates the distribution was installed with setuptools + with an old pip version or with ``single-version-externally-managed``. + + Note that this ensure the metadata store is a directory. distutils can + also installs an ``.egg-info``, but as a file, not a directory. This + property is *False* for that case. Also see ``installed_by_distutils``. + """ + info_location = self.info_location + if not info_location: + return False + if not info_location.endswith(".egg-info"): + return False + return pathlib.Path(info_location).is_dir() + + @property + def installed_with_dist_info(self) -> bool: + """Whether this distribution is installed with the "modern format". + + This indicates a "modern" installation, e.g. storing metadata in the + ``.dist-info`` directory. This applies to installations made by + setuptools (but through pip, not directly), or anything using the + standardized build backend interface (PEP 517). + """ + info_location = self.info_location + if not info_location: + return False + if not info_location.endswith(".dist-info"): + return False + return pathlib.Path(info_location).is_dir() + + @property + def canonical_name(self) -> NormalizedName: + raise NotImplementedError() + + @property + def version(self) -> Version: + raise NotImplementedError() + + @property + def raw_version(self) -> str: + raise NotImplementedError() + + @property + def setuptools_filename(self) -> str: + """Convert a project name to its setuptools-compatible filename. + + This is a copy of ``pkg_resources.to_filename()`` for compatibility. + """ + return self.raw_name.replace("-", "_") + + @property + def direct_url(self) -> Optional[DirectUrl]: + """Obtain a DirectUrl from this distribution. + + Returns None if the distribution has no `direct_url.json` metadata, + or if `direct_url.json` is invalid. + """ + try: + content = self.read_text(DIRECT_URL_METADATA_NAME) + except FileNotFoundError: + return None + try: + return DirectUrl.from_json(content) + except ( + UnicodeDecodeError, + json.JSONDecodeError, + DirectUrlValidationError, + ) as e: + logger.warning( + "Error parsing %s for %s: %s", + DIRECT_URL_METADATA_NAME, + self.canonical_name, + e, + ) + return None + + @property + def installer(self) -> str: + try: + installer_text = self.read_text("INSTALLER") + except (OSError, ValueError, NoneMetadataError): + return "" # Fail silently if the installer file cannot be read. + for line in installer_text.splitlines(): + cleaned_line = line.strip() + if cleaned_line: + return cleaned_line + return "" + + @property + def requested(self) -> bool: + return self.is_file("REQUESTED") + + @property + def editable(self) -> bool: + return bool(self.editable_project_location) + + @property + def local(self) -> bool: + """If distribution is installed in the current virtual environment. + + Always True if we're not in a virtualenv. + """ + if self.installed_location is None: + return False + return is_local(self.installed_location) + + @property + def in_usersite(self) -> bool: + if self.installed_location is None or user_site is None: + return False + return self.installed_location.startswith(normalize_path(user_site)) + + @property + def in_site_packages(self) -> bool: + if self.installed_location is None or site_packages is None: + return False + return self.installed_location.startswith(normalize_path(site_packages)) + + def is_file(self, path: InfoPath) -> bool: + """Check whether an entry in the info directory is a file.""" + raise NotImplementedError() + + def iter_distutils_script_names(self) -> Iterator[str]: + """Find distutils 'scripts' entries metadata. + + If 'scripts' is supplied in ``setup.py``, distutils records those in the + installed distribution's ``scripts`` directory, a file for each script. + """ + raise NotImplementedError() + + def read_text(self, path: InfoPath) -> str: + """Read a file in the info directory. + + :raise FileNotFoundError: If ``path`` does not exist in the directory. + :raise NoneMetadataError: If ``path`` exists in the info directory, but + cannot be read. + """ + raise NotImplementedError() + + def iter_entry_points(self) -> Iterable[BaseEntryPoint]: + raise NotImplementedError() + + def _metadata_impl(self) -> email.message.Message: + raise NotImplementedError() + + @functools.cached_property + def metadata(self) -> email.message.Message: + """Metadata of distribution parsed from e.g. METADATA or PKG-INFO. + + This should return an empty message if the metadata file is unavailable. + + :raises NoneMetadataError: If the metadata file is available, but does + not contain valid metadata. + """ + metadata = self._metadata_impl() + self._add_egg_info_requires(metadata) + return metadata + + @property + def metadata_dict(self) -> Dict[str, Any]: + """PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO. + + This should return an empty dict if the metadata file is unavailable. + + :raises NoneMetadataError: If the metadata file is available, but does + not contain valid metadata. + """ + return msg_to_json(self.metadata) + + @property + def metadata_version(self) -> Optional[str]: + """Value of "Metadata-Version:" in distribution metadata, if available.""" + return self.metadata.get("Metadata-Version") + + @property + def raw_name(self) -> str: + """Value of "Name:" in distribution metadata.""" + # The metadata should NEVER be missing the Name: key, but if it somehow + # does, fall back to the known canonical name. + return self.metadata.get("Name", self.canonical_name) + + @property + def requires_python(self) -> SpecifierSet: + """Value of "Requires-Python:" in distribution metadata. + + If the key does not exist or contains an invalid value, an empty + SpecifierSet should be returned. + """ + value = self.metadata.get("Requires-Python") + if value is None: + return SpecifierSet() + try: + # Convert to str to satisfy the type checker; this can be a Header object. + spec = SpecifierSet(str(value)) + except InvalidSpecifier as e: + message = "Package %r has an invalid Requires-Python: %s" + logger.warning(message, self.raw_name, e) + return SpecifierSet() + return spec + + def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]: + """Dependencies of this distribution. + + For modern .dist-info distributions, this is the collection of + "Requires-Dist:" entries in distribution metadata. + """ + raise NotImplementedError() + + def iter_raw_dependencies(self) -> Iterable[str]: + """Raw Requires-Dist metadata.""" + return self.metadata.get_all("Requires-Dist", []) + + def iter_provided_extras(self) -> Iterable[NormalizedName]: + """Extras provided by this distribution. + + For modern .dist-info distributions, this is the collection of + "Provides-Extra:" entries in distribution metadata. + + The return value of this function is expected to be normalised names, + per PEP 685, with the returned value being handled appropriately by + `iter_dependencies`. + """ + raise NotImplementedError() + + def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]: + try: + text = self.read_text("RECORD") + except FileNotFoundError: + return None + # This extra Path-str cast normalizes entries. + return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines())) + + def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]: + try: + text = self.read_text("installed-files.txt") + except FileNotFoundError: + return None + paths = (p for p in text.splitlines(keepends=False) if p) + root = self.location + info = self.info_location + if root is None or info is None: + return paths + try: + info_rel = pathlib.Path(info).relative_to(root) + except ValueError: # info is not relative to root. + return paths + if not info_rel.parts: # info *is* root. + return paths + return ( + _convert_installed_files_path(pathlib.Path(p).parts, info_rel.parts) + for p in paths + ) + + def iter_declared_entries(self) -> Optional[Iterator[str]]: + """Iterate through file entries declared in this distribution. + + For modern .dist-info distributions, this is the files listed in the + ``RECORD`` metadata file. For legacy setuptools distributions, this + comes from ``installed-files.txt``, with entries normalized to be + compatible with the format used by ``RECORD``. + + :return: An iterator for listed entries, or None if the distribution + contains neither ``RECORD`` nor ``installed-files.txt``. + """ + return ( + self._iter_declared_entries_from_record() + or self._iter_declared_entries_from_legacy() + ) + + def _iter_requires_txt_entries(self) -> Iterator[RequiresEntry]: + """Parse a ``requires.txt`` in an egg-info directory. + + This is an INI-ish format where an egg-info stores dependencies. A + section name describes extra other environment markers, while each entry + is an arbitrary string (not a key-value pair) representing a dependency + as a requirement string (no markers). + + There is a construct in ``importlib.metadata`` called ``Sectioned`` that + does mostly the same, but the format is currently considered private. + """ + try: + content = self.read_text("requires.txt") + except FileNotFoundError: + return + extra = marker = "" # Section-less entries don't have markers. + for line in content.splitlines(): + line = line.strip() + if not line or line.startswith("#"): # Comment; ignored. + continue + if line.startswith("[") and line.endswith("]"): # A section header. + extra, _, marker = line.strip("[]").partition(":") + continue + yield RequiresEntry(requirement=line, extra=extra, marker=marker) + + def _iter_egg_info_extras(self) -> Iterable[str]: + """Get extras from the egg-info directory.""" + known_extras = {""} + for entry in self._iter_requires_txt_entries(): + extra = canonicalize_name(entry.extra) + if extra in known_extras: + continue + known_extras.add(extra) + yield extra + + def _iter_egg_info_dependencies(self) -> Iterable[str]: + """Get distribution dependencies from the egg-info directory. + + To ease parsing, this converts a legacy dependency entry into a PEP 508 + requirement string. Like ``_iter_requires_txt_entries()``, there is code + in ``importlib.metadata`` that does mostly the same, but not do exactly + what we need. + + Namely, ``importlib.metadata`` does not normalize the extra name before + putting it into the requirement string, which causes marker comparison + to fail because the dist-info format do normalize. This is consistent in + all currently available PEP 517 backends, although not standardized. + """ + for entry in self._iter_requires_txt_entries(): + extra = canonicalize_name(entry.extra) + if extra and entry.marker: + marker = f'({entry.marker}) and extra == "{extra}"' + elif extra: + marker = f'extra == "{extra}"' + elif entry.marker: + marker = entry.marker + else: + marker = "" + if marker: + yield f"{entry.requirement} ; {marker}" + else: + yield entry.requirement + + def _add_egg_info_requires(self, metadata: email.message.Message) -> None: + """Add egg-info requires.txt information to the metadata.""" + if not metadata.get_all("Requires-Dist"): + for dep in self._iter_egg_info_dependencies(): + metadata["Requires-Dist"] = dep + if not metadata.get_all("Provides-Extra"): + for extra in self._iter_egg_info_extras(): + metadata["Provides-Extra"] = extra + + +class BaseEnvironment: + """An environment containing distributions to introspect.""" + + @classmethod + def default(cls) -> "BaseEnvironment": + raise NotImplementedError() + + @classmethod + def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment": + raise NotImplementedError() + + def get_distribution(self, name: str) -> Optional["BaseDistribution"]: + """Given a requirement name, return the installed distributions. + + The name may not be normalized. The implementation must canonicalize + it for lookup. + """ + raise NotImplementedError() + + def _iter_distributions(self) -> Iterator["BaseDistribution"]: + """Iterate through installed distributions. + + This function should be implemented by subclass, but never called + directly. Use the public ``iter_distribution()`` instead, which + implements additional logic to make sure the distributions are valid. + """ + raise NotImplementedError() + + def iter_all_distributions(self) -> Iterator[BaseDistribution]: + """Iterate through all installed distributions without any filtering.""" + for dist in self._iter_distributions(): + # Make sure the distribution actually comes from a valid Python + # packaging distribution. Pip's AdjacentTempDirectory leaves folders + # e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The + # valid project name pattern is taken from PEP 508. + project_name_valid = re.match( + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", + dist.canonical_name, + flags=re.IGNORECASE, + ) + if not project_name_valid: + logger.warning( + "Ignoring invalid distribution %s (%s)", + dist.canonical_name, + dist.location, + ) + continue + yield dist + + def iter_installed_distributions( + self, + local_only: bool = True, + skip: Container[str] = stdlib_pkgs, + include_editables: bool = True, + editables_only: bool = False, + user_only: bool = False, + ) -> Iterator[BaseDistribution]: + """Return a list of installed distributions. + + This is based on ``iter_all_distributions()`` with additional filtering + options. Note that ``iter_installed_distributions()`` without arguments + is *not* equal to ``iter_all_distributions()``, since some of the + configurations exclude packages by default. + + :param local_only: If True (default), only return installations + local to the current virtualenv, if in a virtualenv. + :param skip: An iterable of canonicalized project names to ignore; + defaults to ``stdlib_pkgs``. + :param include_editables: If False, don't report editables. + :param editables_only: If True, only report editables. + :param user_only: If True, only report installations in the user + site directory. + """ + it = self.iter_all_distributions() + if local_only: + it = (d for d in it if d.local) + if not include_editables: + it = (d for d in it if not d.editable) + if editables_only: + it = (d for d in it if d.editable) + if user_only: + it = (d for d in it if d.in_usersite) + return (d for d in it if d.canonical_name not in skip) + + +class Wheel(Protocol): + location: str + + def as_zipfile(self) -> zipfile.ZipFile: + raise NotImplementedError() + + +class FilesystemWheel(Wheel): + def __init__(self, location: str) -> None: + self.location = location + + def as_zipfile(self) -> zipfile.ZipFile: + return zipfile.ZipFile(self.location, allowZip64=True) + + +class MemoryWheel(Wheel): + def __init__(self, location: str, stream: IO[bytes]) -> None: + self.location = location + self.stream = stream + + def as_zipfile(self) -> zipfile.ZipFile: + return zipfile.ZipFile(self.stream, allowZip64=True) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__init__.py new file mode 100644 index 000000000..a779138db --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__init__.py @@ -0,0 +1,6 @@ +from ._dists import Distribution +from ._envs import Environment + +__all__ = ["NAME", "Distribution", "Environment"] + +NAME = "importlib" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ded5f28e9ebab87533a171d130cd2ae23f72289 GIT binary patch literal 368 zcmYjNJ5B>J5MA$XHX$Sj3DU+z8VZC!N;*h`(rjaS@sL>YPqwpCH~^f5D^PQaZK=2d z6=Oh%kvyKK_uf3qo9T3%VZFXl`Hc5_0{^Q(aD^wFVH24kD$}GPB+a?Xbm(4~W1PwFNPnI%7FWW_z>+9Q<&r#`9P=6w|vpy=> zu4h$KF@fIj%{K!ErVBr^A!A%A#(asUm&^~-g48~r=+0`O@CbVeiY}o8WnIdmH@pqh z9plcL+AZn_570`eqv!W~@U+AA6oY>yi}^VXGD7W;=379kbd*UG!5glqhR6kvoc_I- cGGt|BZ0?Tz=t5g@P;i-kAyn8VWRj2bF9(ukrT_o{ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..954a2097708755545f08fad6380938ddf9c0d53c GIT binary patch literal 3525 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 E0l8C}1poj5 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6f477f3272383cfaf972099ad4f6a9ff4dfca10 GIT binary patch literal 8837 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_compat.py b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_compat.py new file mode 100644 index 000000000..ec1e815cd --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_compat.py @@ -0,0 +1,85 @@ +import importlib.metadata +import os +from typing import Any, Optional, Protocol, Tuple, cast + +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name + + +class BadMetadata(ValueError): + def __init__(self, dist: importlib.metadata.Distribution, *, reason: str) -> None: + self.dist = dist + self.reason = reason + + def __str__(self) -> str: + return f"Bad metadata in {self.dist} ({self.reason})" + + +class BasePath(Protocol): + """A protocol that various path objects conform. + + This exists because importlib.metadata uses both ``pathlib.Path`` and + ``zipfile.Path``, and we need a common base for type hints (Union does not + work well since ``zipfile.Path`` is too new for our linter setup). + + This does not mean to be exhaustive, but only contains things that present + in both classes *that we need*. + """ + + @property + def name(self) -> str: + raise NotImplementedError() + + @property + def parent(self) -> "BasePath": + raise NotImplementedError() + + +def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]: + """Find the path to the distribution's metadata directory. + + HACK: This relies on importlib.metadata's private ``_path`` attribute. Not + all distributions exist on disk, so importlib.metadata is correct to not + expose the attribute as public. But pip's code base is old and not as clean, + so we do this to avoid having to rewrite too many things. Hopefully we can + eliminate this some day. + """ + return getattr(d, "_path", None) + + +def parse_name_and_version_from_info_directory( + dist: importlib.metadata.Distribution, +) -> Tuple[Optional[str], Optional[str]]: + """Get a name and version from the metadata directory name. + + This is much faster than reading distribution metadata. + """ + info_location = get_info_location(dist) + if info_location is None: + return None, None + + stem, suffix = os.path.splitext(info_location.name) + if suffix == ".dist-info": + name, sep, version = stem.partition("-") + if sep: + return name, version + + if suffix == ".egg-info": + name = stem.split("-", 1)[0] + return name, None + + return None, None + + +def get_dist_canonical_name(dist: importlib.metadata.Distribution) -> NormalizedName: + """Get the distribution's normalized name. + + The ``name`` attribute is only available in Python 3.10 or later. We are + targeting exactly that, but Mypy does not know this. + """ + if name := parse_name_and_version_from_info_directory(dist)[0]: + return canonicalize_name(name) + + name = cast(Any, dist).name + if not isinstance(name, str): + raise BadMetadata(dist, reason="invalid metadata entry 'name'") + return canonicalize_name(name) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_dists.py b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_dists.py new file mode 100644 index 000000000..d220b616e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_dists.py @@ -0,0 +1,228 @@ +import email.message +import importlib.metadata +import pathlib +import zipfile +from os import PathLike +from typing import ( + Collection, + Dict, + Iterable, + Iterator, + Mapping, + Optional, + Sequence, + Union, + cast, +) + +from pip._vendor.packaging.requirements import Requirement +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.version import Version +from pip._vendor.packaging.version import parse as parse_version + +from pip._internal.exceptions import InvalidWheel, UnsupportedWheel +from pip._internal.metadata.base import ( + BaseDistribution, + BaseEntryPoint, + InfoPath, + Wheel, +) +from pip._internal.utils.misc import normalize_path +from pip._internal.utils.packaging import get_requirement +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file + +from ._compat import ( + BasePath, + get_dist_canonical_name, + parse_name_and_version_from_info_directory, +) + + +class WheelDistribution(importlib.metadata.Distribution): + """An ``importlib.metadata.Distribution`` read from a wheel. + + Although ``importlib.metadata.PathDistribution`` accepts ``zipfile.Path``, + its implementation is too "lazy" for pip's needs (we can't keep the ZipFile + handle open for the entire lifetime of the distribution object). + + This implementation eagerly reads the entire metadata directory into the + memory instead, and operates from that. + """ + + def __init__( + self, + files: Mapping[pathlib.PurePosixPath, bytes], + info_location: pathlib.PurePosixPath, + ) -> None: + self._files = files + self.info_location = info_location + + @classmethod + def from_zipfile( + cls, + zf: zipfile.ZipFile, + name: str, + location: str, + ) -> "WheelDistribution": + info_dir, _ = parse_wheel(zf, name) + paths = ( + (name, pathlib.PurePosixPath(name.split("/", 1)[-1])) + for name in zf.namelist() + if name.startswith(f"{info_dir}/") + ) + files = { + relpath: read_wheel_metadata_file(zf, fullpath) + for fullpath, relpath in paths + } + info_location = pathlib.PurePosixPath(location, info_dir) + return cls(files, info_location) + + def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]: + # Only allow iterating through the metadata directory. + if pathlib.PurePosixPath(str(path)) in self._files: + return iter(self._files) + raise FileNotFoundError(path) + + def read_text(self, filename: str) -> Optional[str]: + try: + data = self._files[pathlib.PurePosixPath(filename)] + except KeyError: + return None + try: + text = data.decode("utf-8") + except UnicodeDecodeError as e: + wheel = self.info_location.parent + error = f"Error decoding metadata for {wheel}: {e} in {filename} file" + raise UnsupportedWheel(error) + return text + + def locate_file(self, path: Union[str, "PathLike[str]"]) -> pathlib.Path: + # This method doesn't make sense for our in-memory wheel, but the API + # requires us to define it. + raise NotImplementedError + + +class Distribution(BaseDistribution): + def __init__( + self, + dist: importlib.metadata.Distribution, + info_location: Optional[BasePath], + installed_location: Optional[BasePath], + ) -> None: + self._dist = dist + self._info_location = info_location + self._installed_location = installed_location + + @classmethod + def from_directory(cls, directory: str) -> BaseDistribution: + info_location = pathlib.Path(directory) + dist = importlib.metadata.Distribution.at(info_location) + return cls(dist, info_location, info_location.parent) + + @classmethod + def from_metadata_file_contents( + cls, + metadata_contents: bytes, + filename: str, + project_name: str, + ) -> BaseDistribution: + # Generate temp dir to contain the metadata file, and write the file contents. + temp_dir = pathlib.Path( + TempDirectory(kind="metadata", globally_managed=True).path + ) + metadata_path = temp_dir / "METADATA" + metadata_path.write_bytes(metadata_contents) + # Construct dist pointing to the newly created directory. + dist = importlib.metadata.Distribution.at(metadata_path.parent) + return cls(dist, metadata_path.parent, None) + + @classmethod + def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution: + try: + with wheel.as_zipfile() as zf: + dist = WheelDistribution.from_zipfile(zf, name, wheel.location) + except zipfile.BadZipFile as e: + raise InvalidWheel(wheel.location, name) from e + return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location)) + + @property + def location(self) -> Optional[str]: + if self._info_location is None: + return None + return str(self._info_location.parent) + + @property + def info_location(self) -> Optional[str]: + if self._info_location is None: + return None + return str(self._info_location) + + @property + def installed_location(self) -> Optional[str]: + if self._installed_location is None: + return None + return normalize_path(str(self._installed_location)) + + @property + def canonical_name(self) -> NormalizedName: + return get_dist_canonical_name(self._dist) + + @property + def version(self) -> Version: + if version := parse_name_and_version_from_info_directory(self._dist)[1]: + return parse_version(version) + return parse_version(self._dist.version) + + @property + def raw_version(self) -> str: + return self._dist.version + + def is_file(self, path: InfoPath) -> bool: + return self._dist.read_text(str(path)) is not None + + def iter_distutils_script_names(self) -> Iterator[str]: + # A distutils installation is always "flat" (not in e.g. egg form), so + # if this distribution's info location is NOT a pathlib.Path (but e.g. + # zipfile.Path), it can never contain any distutils scripts. + if not isinstance(self._info_location, pathlib.Path): + return + for child in self._info_location.joinpath("scripts").iterdir(): + yield child.name + + def read_text(self, path: InfoPath) -> str: + content = self._dist.read_text(str(path)) + if content is None: + raise FileNotFoundError(path) + return content + + def iter_entry_points(self) -> Iterable[BaseEntryPoint]: + # importlib.metadata's EntryPoint structure satisfies BaseEntryPoint. + return self._dist.entry_points + + def _metadata_impl(self) -> email.message.Message: + # From Python 3.10+, importlib.metadata declares PackageMetadata as the + # return type. This protocol is unfortunately a disaster now and misses + # a ton of fields that we need, including get() and get_payload(). We + # rely on the implementation that the object is actually a Message now, + # until upstream can improve the protocol. (python/cpython#94952) + return cast(email.message.Message, self._dist.metadata) + + def iter_provided_extras(self) -> Iterable[NormalizedName]: + return [ + canonicalize_name(extra) + for extra in self.metadata.get_all("Provides-Extra", []) + ] + + def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]: + contexts: Sequence[Dict[str, str]] = [{"extra": e} for e in extras] + for req_string in self.metadata.get_all("Requires-Dist", []): + # strip() because email.message.Message.get_all() may return a leading \n + # in case a long header was wrapped. + req = get_requirement(req_string.strip()) + if not req.marker: + yield req + elif not extras and req.marker.evaluate({"extra": ""}): + yield req + elif any(req.marker.evaluate(context) for context in contexts): + yield req diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_envs.py b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_envs.py new file mode 100644 index 000000000..4d906fd31 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/metadata/importlib/_envs.py @@ -0,0 +1,189 @@ +import functools +import importlib.metadata +import logging +import os +import pathlib +import sys +import zipfile +import zipimport +from typing import Iterator, List, Optional, Sequence, Set, Tuple + +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name + +from pip._internal.metadata.base import BaseDistribution, BaseEnvironment +from pip._internal.models.wheel import Wheel +from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.filetypes import WHEEL_EXTENSION + +from ._compat import BadMetadata, BasePath, get_dist_canonical_name, get_info_location +from ._dists import Distribution + +logger = logging.getLogger(__name__) + + +def _looks_like_wheel(location: str) -> bool: + if not location.endswith(WHEEL_EXTENSION): + return False + if not os.path.isfile(location): + return False + if not Wheel.wheel_file_re.match(os.path.basename(location)): + return False + return zipfile.is_zipfile(location) + + +class _DistributionFinder: + """Finder to locate distributions. + + The main purpose of this class is to memoize found distributions' names, so + only one distribution is returned for each package name. At lot of pip code + assumes this (because it is setuptools's behavior), and not doing the same + can potentially cause a distribution in lower precedence path to override a + higher precedence one if the caller is not careful. + + Eventually we probably want to make it possible to see lower precedence + installations as well. It's useful feature, after all. + """ + + FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]] + + def __init__(self) -> None: + self._found_names: Set[NormalizedName] = set() + + def _find_impl(self, location: str) -> Iterator[FoundResult]: + """Find distributions in a location.""" + # Skip looking inside a wheel. Since a package inside a wheel is not + # always valid (due to .data directories etc.), its .dist-info entry + # should not be considered an installed distribution. + if _looks_like_wheel(location): + return + # To know exactly where we find a distribution, we have to feed in the + # paths one by one, instead of dumping the list to importlib.metadata. + for dist in importlib.metadata.distributions(path=[location]): + info_location = get_info_location(dist) + try: + name = get_dist_canonical_name(dist) + except BadMetadata as e: + logger.warning("Skipping %s due to %s", info_location, e.reason) + continue + if name in self._found_names: + continue + self._found_names.add(name) + yield dist, info_location + + def find(self, location: str) -> Iterator[BaseDistribution]: + """Find distributions in a location. + + The path can be either a directory, or a ZIP archive. + """ + for dist, info_location in self._find_impl(location): + if info_location is None: + installed_location: Optional[BasePath] = None + else: + installed_location = info_location.parent + yield Distribution(dist, info_location, installed_location) + + def find_linked(self, location: str) -> Iterator[BaseDistribution]: + """Read location in egg-link files and return distributions in there. + + The path should be a directory; otherwise this returns nothing. This + follows how setuptools does this for compatibility. The first non-empty + line in the egg-link is read as a path (resolved against the egg-link's + containing directory if relative). Distributions found at that linked + location are returned. + """ + path = pathlib.Path(location) + if not path.is_dir(): + return + for child in path.iterdir(): + if child.suffix != ".egg-link": + continue + with child.open() as f: + lines = (line.strip() for line in f) + target_rel = next((line for line in lines if line), "") + if not target_rel: + continue + target_location = str(path.joinpath(target_rel)) + for dist, info_location in self._find_impl(target_location): + yield Distribution(dist, info_location, path) + + def _find_eggs_in_dir(self, location: str) -> Iterator[BaseDistribution]: + from pip._vendor.pkg_resources import find_distributions + + from pip._internal.metadata import pkg_resources as legacy + + with os.scandir(location) as it: + for entry in it: + if not entry.name.endswith(".egg"): + continue + for dist in find_distributions(entry.path): + yield legacy.Distribution(dist) + + def _find_eggs_in_zip(self, location: str) -> Iterator[BaseDistribution]: + from pip._vendor.pkg_resources import find_eggs_in_zip + + from pip._internal.metadata import pkg_resources as legacy + + try: + importer = zipimport.zipimporter(location) + except zipimport.ZipImportError: + return + for dist in find_eggs_in_zip(importer, location): + yield legacy.Distribution(dist) + + def find_eggs(self, location: str) -> Iterator[BaseDistribution]: + """Find eggs in a location. + + This actually uses the old *pkg_resources* backend. We likely want to + deprecate this so we can eventually remove the *pkg_resources* + dependency entirely. Before that, this should first emit a deprecation + warning for some versions when using the fallback since importing + *pkg_resources* is slow for those who don't need it. + """ + if os.path.isdir(location): + yield from self._find_eggs_in_dir(location) + if zipfile.is_zipfile(location): + yield from self._find_eggs_in_zip(location) + + +@functools.lru_cache(maxsize=None) # Warn a distribution exactly once. +def _emit_egg_deprecation(location: Optional[str]) -> None: + deprecated( + reason=f"Loading egg at {location} is deprecated.", + replacement="to use pip for package installation", + gone_in="25.1", + issue=12330, + ) + + +class Environment(BaseEnvironment): + def __init__(self, paths: Sequence[str]) -> None: + self._paths = paths + + @classmethod + def default(cls) -> BaseEnvironment: + return cls(sys.path) + + @classmethod + def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment: + if paths is None: + return cls(sys.path) + return cls(paths) + + def _iter_distributions(self) -> Iterator[BaseDistribution]: + finder = _DistributionFinder() + for location in self._paths: + yield from finder.find(location) + for dist in finder.find_eggs(location): + _emit_egg_deprecation(dist.location) + yield dist + # This must go last because that's how pkg_resources tie-breaks. + yield from finder.find_linked(location) + + def get_distribution(self, name: str) -> Optional[BaseDistribution]: + canonical_name = canonicalize_name(name) + matches = ( + distribution + for distribution in self.iter_all_distributions() + if distribution.canonical_name == canonical_name + ) + return next(matches, None) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/metadata/pkg_resources.py b/venv/lib/python3.8/site-packages/pip/_internal/metadata/pkg_resources.py new file mode 100644 index 000000000..4ea84f93a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/metadata/pkg_resources.py @@ -0,0 +1,301 @@ +import email.message +import email.parser +import logging +import os +import zipfile +from typing import ( + Collection, + Iterable, + Iterator, + List, + Mapping, + NamedTuple, + Optional, +) + +from pip._vendor import pkg_resources +from pip._vendor.packaging.requirements import Requirement +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.version import Version +from pip._vendor.packaging.version import parse as parse_version + +from pip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel +from pip._internal.utils.egg_link import egg_link_path_from_location +from pip._internal.utils.misc import display_path, normalize_path +from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file + +from .base import ( + BaseDistribution, + BaseEntryPoint, + BaseEnvironment, + InfoPath, + Wheel, +) + +__all__ = ["NAME", "Distribution", "Environment"] + +logger = logging.getLogger(__name__) + +NAME = "pkg_resources" + + +class EntryPoint(NamedTuple): + name: str + value: str + group: str + + +class InMemoryMetadata: + """IMetadataProvider that reads metadata files from a dictionary. + + This also maps metadata decoding exceptions to our internal exception type. + """ + + def __init__(self, metadata: Mapping[str, bytes], wheel_name: str) -> None: + self._metadata = metadata + self._wheel_name = wheel_name + + def has_metadata(self, name: str) -> bool: + return name in self._metadata + + def get_metadata(self, name: str) -> str: + try: + return self._metadata[name].decode() + except UnicodeDecodeError as e: + # Augment the default error with the origin of the file. + raise UnsupportedWheel( + f"Error decoding metadata for {self._wheel_name}: {e} in {name} file" + ) + + def get_metadata_lines(self, name: str) -> Iterable[str]: + return pkg_resources.yield_lines(self.get_metadata(name)) + + def metadata_isdir(self, name: str) -> bool: + return False + + def metadata_listdir(self, name: str) -> List[str]: + return [] + + def run_script(self, script_name: str, namespace: str) -> None: + pass + + +class Distribution(BaseDistribution): + def __init__(self, dist: pkg_resources.Distribution) -> None: + self._dist = dist + # This is populated lazily, to avoid loading metadata for all possible + # distributions eagerly. + self.__extra_mapping: Optional[Mapping[NormalizedName, str]] = None + + @property + def _extra_mapping(self) -> Mapping[NormalizedName, str]: + if self.__extra_mapping is None: + self.__extra_mapping = { + canonicalize_name(extra): extra for extra in self._dist.extras + } + + return self.__extra_mapping + + @classmethod + def from_directory(cls, directory: str) -> BaseDistribution: + dist_dir = directory.rstrip(os.sep) + + # Build a PathMetadata object, from path to metadata. :wink: + base_dir, dist_dir_name = os.path.split(dist_dir) + metadata = pkg_resources.PathMetadata(base_dir, dist_dir) + + # Determine the correct Distribution object type. + if dist_dir.endswith(".egg-info"): + dist_cls = pkg_resources.Distribution + dist_name = os.path.splitext(dist_dir_name)[0] + else: + assert dist_dir.endswith(".dist-info") + dist_cls = pkg_resources.DistInfoDistribution + dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0] + + dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata) + return cls(dist) + + @classmethod + def from_metadata_file_contents( + cls, + metadata_contents: bytes, + filename: str, + project_name: str, + ) -> BaseDistribution: + metadata_dict = { + "METADATA": metadata_contents, + } + dist = pkg_resources.DistInfoDistribution( + location=filename, + metadata=InMemoryMetadata(metadata_dict, filename), + project_name=project_name, + ) + return cls(dist) + + @classmethod + def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution: + try: + with wheel.as_zipfile() as zf: + info_dir, _ = parse_wheel(zf, name) + metadata_dict = { + path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path) + for path in zf.namelist() + if path.startswith(f"{info_dir}/") + } + except zipfile.BadZipFile as e: + raise InvalidWheel(wheel.location, name) from e + except UnsupportedWheel as e: + raise UnsupportedWheel(f"{name} has an invalid wheel, {e}") + dist = pkg_resources.DistInfoDistribution( + location=wheel.location, + metadata=InMemoryMetadata(metadata_dict, wheel.location), + project_name=name, + ) + return cls(dist) + + @property + def location(self) -> Optional[str]: + return self._dist.location + + @property + def installed_location(self) -> Optional[str]: + egg_link = egg_link_path_from_location(self.raw_name) + if egg_link: + location = egg_link + elif self.location: + location = self.location + else: + return None + return normalize_path(location) + + @property + def info_location(self) -> Optional[str]: + return self._dist.egg_info + + @property + def installed_by_distutils(self) -> bool: + # A distutils-installed distribution is provided by FileMetadata. This + # provider has a "path" attribute not present anywhere else. Not the + # best introspection logic, but pip has been doing this for a long time. + try: + return bool(self._dist._provider.path) + except AttributeError: + return False + + @property + def canonical_name(self) -> NormalizedName: + return canonicalize_name(self._dist.project_name) + + @property + def version(self) -> Version: + return parse_version(self._dist.version) + + @property + def raw_version(self) -> str: + return self._dist.version + + def is_file(self, path: InfoPath) -> bool: + return self._dist.has_metadata(str(path)) + + def iter_distutils_script_names(self) -> Iterator[str]: + yield from self._dist.metadata_listdir("scripts") + + def read_text(self, path: InfoPath) -> str: + name = str(path) + if not self._dist.has_metadata(name): + raise FileNotFoundError(name) + content = self._dist.get_metadata(name) + if content is None: + raise NoneMetadataError(self, name) + return content + + def iter_entry_points(self) -> Iterable[BaseEntryPoint]: + for group, entries in self._dist.get_entry_map().items(): + for name, entry_point in entries.items(): + name, _, value = str(entry_point).partition("=") + yield EntryPoint(name=name.strip(), value=value.strip(), group=group) + + def _metadata_impl(self) -> email.message.Message: + """ + :raises NoneMetadataError: if the distribution reports `has_metadata()` + True but `get_metadata()` returns None. + """ + if isinstance(self._dist, pkg_resources.DistInfoDistribution): + metadata_name = "METADATA" + else: + metadata_name = "PKG-INFO" + try: + metadata = self.read_text(metadata_name) + except FileNotFoundError: + if self.location: + displaying_path = display_path(self.location) + else: + displaying_path = repr(self.location) + logger.warning("No metadata found in %s", displaying_path) + metadata = "" + feed_parser = email.parser.FeedParser() + feed_parser.feed(metadata) + return feed_parser.close() + + def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]: + if extras: + relevant_extras = set(self._extra_mapping) & set( + map(canonicalize_name, extras) + ) + extras = [self._extra_mapping[extra] for extra in relevant_extras] + return self._dist.requires(extras) + + def iter_provided_extras(self) -> Iterable[NormalizedName]: + return self._extra_mapping.keys() + + +class Environment(BaseEnvironment): + def __init__(self, ws: pkg_resources.WorkingSet) -> None: + self._ws = ws + + @classmethod + def default(cls) -> BaseEnvironment: + return cls(pkg_resources.working_set) + + @classmethod + def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment: + return cls(pkg_resources.WorkingSet(paths)) + + def _iter_distributions(self) -> Iterator[BaseDistribution]: + for dist in self._ws: + yield Distribution(dist) + + def _search_distribution(self, name: str) -> Optional[BaseDistribution]: + """Find a distribution matching the ``name`` in the environment. + + This searches from *all* distributions available in the environment, to + match the behavior of ``pkg_resources.get_distribution()``. + """ + canonical_name = canonicalize_name(name) + for dist in self.iter_all_distributions(): + if dist.canonical_name == canonical_name: + return dist + return None + + def get_distribution(self, name: str) -> Optional[BaseDistribution]: + # Search the distribution by looking through the working set. + dist = self._search_distribution(name) + if dist: + return dist + + # If distribution could not be found, call working_set.require to + # update the working set, and try to find the distribution again. + # This might happen for e.g. when you install a package twice, once + # using setup.py develop and again using setup.py install. Now when + # running pip uninstall twice, the package gets removed from the + # working set in the first uninstall, so we have to populate the + # working set again so that pip knows about it and the packages gets + # picked up and is successfully uninstalled the second time too. + try: + # We didn't pass in any version specifiers, so this can never + # raise pkg_resources.VersionConflict. + self._ws.require(name) + except pkg_resources.DistributionNotFound: + return None + return self._search_distribution(name) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/models/__init__.py new file mode 100644 index 000000000..7855226e4 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/__init__.py @@ -0,0 +1,2 @@ +"""A package that contains models that represent entities. +""" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8466b3837e899ad154c9631eabacaf4da8ff2cb GIT binary patch literal 281 zcmYjMyKciU43x7Z2#|m9n!)CfB}35%kg@1ebm=0<_O&J=OM;{ti2ta+(79icwV%+f zQ<)}154as>bNQaShzHl59g5Ru?=DdfyZ_3~j|9OqhhzSa6&#hIM_lb?xtKY%b{&#<(^2-^QT}E`q(m5e%aX4#T Shr@AkCFYyO_37Pjq51_(j8yah literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/candidate.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/candidate.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49be21676cf6221d7a262789cfe03e115eabd7d2 GIT binary patch literal 1266 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 SFV+@4=y<92Cek_5^#1|}5+fi0 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/format_control.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/format_control.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4580ba39cafbb38404232917a7905f9dc40ce02a GIT binary patch literal 2733 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 HCjR~hXU<9G literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14845c0f2fc67e381853aecc473bb73a64533814 GIT binary patch literal 1792 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 Kd*OaB34Q|;6cM)o literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..484a0fa86fa5bea25e239626e6fe8451c9981bbd GIT binary patch literal 3500 zcmb7HO^@5g8Rn1_^|@MqY-}fK3&KtctFR>lK?B2BxCY{W0Sm+RIz0#o7%pcdahD>w zGo!VwYTd&wko1s4uZ0itr9iIjt>^xVx%Sk*kfL>;cj*1_+67VyGn{X}^UU+S@96WD zm6n6&Z+{=8e?9Lw|Dek0$4BKJlKK-e?r@el!|Kh3%+}qZYv10`vu}S`!`sb*X?<8{ zG}g-+)8?>g%YN3Hwufz7u4SF+%5cS&gRDDU9j@APJv%o&KRiFZFucGV!Il@r+DkTk zPt%$gKDPIMH{^duU|Vj9h4_84;l zROTX2=&Prn0F`@4>Nzsu5Jp4ybKnMaT<&py>>QBc5B(DG5hsJeY?Zm9anDXm1UQEG!HZPb~hHz!pAI}%r_n|tIdC7cS%rvCIwIm z5*@}PEo6AamNXw*EWbQ~=F18#TIW0V+|(vomujBA4eygwPtG2K6uYxj zitzEq=3sMk<3>0so`^kskPb|#ESwg6mWeRU!-voGq{!*W@&ab<#c39AWuo8n&Kv^h z&{8^t!>uKgIL%Fflg`pDQ!8Vs zglV8!l>(Ja#8j9dE5>6XP5ntMbHLJT7%$1x8C%S`ty!As=maj~6QRH%@x)Tg1a@8i zts}3&l<%GoCdE_?#9Zv8A{*QX8d*_JMXm=M@s=8t7KWSUCpSk@h^brA*lHeU10X9hHK-ULDN;_qd}e~glHo_NMO{bc ztop3MT1Xw{vyLCIb11b?c3=6mz;&7Z{d<1l6nXm9B0VO%SRrAw&g+NpCPi2^{22wP zfg!mIXWNmQa?Q5gG28K~8aRGu-_zd0gF1ZeZ`YOqxX(yC+jaE_c|$$Y&1H)-(gbrB zF4TY#;QiGFTei8o>~L?nvT$EQY1EFdbma@%=U08a#)CuKe&0E;qi7tjV-{-AOXI=( z&SQ|$3O@uL!mA&vt8f@%s?fLxU&?QUWhP=J!crD{DJMl$Z0XN0Z4|{0sRvB9O9*u* z{N4WCy%EmWPW4%DLp^VR<^m@VNm06lY~g&--Dlcaumkp65;#H=jAV!llFGK;4aF!y3)&ef@$aD7BshFv|{o_Rp zYxPzu4@gc9(o3g6fjp(PL|#Fwe4n~VM5@eEEkC4ckPD6F%Q_K1KaYhJ)lF(?SK~KQ*BtTltBl3^iY{j!yYtkN8`uY3->EF zViZ#kJAZJ#a!~ep?XUq3BZeFhH-q_K9@)5aHqecVSFA>1ta-zgknczu*grHQsShSDvgB5)Yz{CJfaNUtaG&BtpXm}0wpmOuqrcH;Ms zr}SKVW^a24!hxLjMI_a?I4s%1sp6upeO>9(-Dl8mK(SN(WWhMQ3_r3UG~5T@u|cnH znq{HVr{whI0IQrLVr_R;zzY)Crhy<}1I255j0a9aMW51?Ym`~lbf-JCwene+T56+1 zk#8fOxDFW6AQw4bKg)y@$ls-+Hy3?N@?@YzVGV^biN3SXzP6(Oqr2;GzBbTW`C|-$ z+3R?nGP|{&;lz?+THgIVdaT+5Qv2=y&(M=)z++EoX%!F1ZqfISu|k72z&2Riu8`2) z1;WmeIZl0TW&Y9kG3P;V#WW&I(I_&lD5@?8D0iZ0cNS-K-9@b)MZ8E*XhxCB3az3@ zlBnceWM(yr;v83q>Mn>;{uTniY1^ChRN#`q<;OJor8UwGav{^nka8mQwOX<%TiV5jzp zB(bjULs#Bhp&BqvRbp>b)~}l;PIklIf#f}WwQ}Qx%Ddn)jrHoV?$TYrV%C6OsvmH3 N@dJA2+?Lm1{{?z=vXB4( literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..165869828eaf834b96ddcae9556af82b988f1d91 GIT binary patch literal 1719 zcmZ`(&5k2A5bo}D_hdq{ds&bc3CUU{L=Z_!tdL-Y(4yH57ZgUUR!F&6PP;NOaofGN zlaNt3AoekM0bYSKPtg|+oOlG53+3))l89MbuJ&J*%is68U-WvN0L$-xWa7`WAo$A< zj!O#%Pq5jK(C~nVGAP1H82WxBqoOrwg~8VWZ}Iq5z~f({Nz9YyK|lQ)_Xqv3YJXcg zp%s(Z?~Sx6n7h=<87*_aoFX$J!4B({s*7`aO*c#}fLCufBeWD_ z8a#kmu+=>big?hx0i^0R2hyof%xtQ6#I@1}sDYPDVZhRILDmA} zp)==bs?>m68kPICeewMW+a)$TM6*qJxM-U$kA6zF;WnVbHbNV2TWF(gjJCB+(8fGO zC|hie?w1BUJdE50E78**5bI&5zP?BJqGW~@ zlVL$wxQ*na=^43tc11q@{F6Rm21p5}5YC~u(`0%?pRJkNvC@!nl+7vN7vw8)BFWt? z2y#8v3Wnr`ZBE!Rbdr)+%|R=epw&y0V`?m4uxpf6Mj9dyGwV4hg0WC|ZZ9IAoi9F| zP#k_FUREfm#ZF6paR=s!J`QLi1kDP%5-s`1WV&=@#ia1vT1+RIb739PE98L@1?zp{ ze)@li-L1isvzslLe6EXl~-gDYa5Ni`{stwKV=eU|Qbz3~F$WOwZ57X#5Ywe&9P{oJQfU{u|#9!n(eW zB0rZl?Vs9b+)t|1ZAw&jeN6Q}LNmODX4pV8Jbi;rF8;`)G(2U-=Tf!5Xm0prh$HK_ Ib)#@~ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/target_python.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/target_python.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fed6215c6ab5c41cdbc30c5c0a5156e9bd56a12c GIT binary patch literal 3827 zcmcIn&yO2P74E8T+ij0$CY#-4lVu4dgdj#U_Oby9MIl-s;g?pk5j!AcSoQX}%AU^j zuT+&M@yH_%2}k$`c9F6Y7f#$c^H=nhQ~m=KR$9JS?HSvXg&W<|&sEj0s$aeL>igdO zYIC#W;rjbO2Fc$pdEP&%vwHdHyobjOA*3f+>J90ar!3<`p7}#R3x+|~7&aJ<`)M<4 z4O^}qq+!+`wzJN#-FPAmUIWvj;iv`kfxRsMcF>G5iFqHJWQWm#yeWOe0OX#@2r$;U-xizqK-VWECC zFLV~C$%%?qRYvr&)hk5jJv`=55Xu`e=?%GLLtk>~%Rn~BU>VBxBpe3vs%ogFYF+oP zduWBKEjPw|(OZt=z3j;DrwzF&x1KeI9o3TCpE7w#zV<8_ZeWEjG-%|qd>td3YU_OD z4S5A4+p+`KyjgYkV~q_znAyW3KY7v-xbEkpxHQu=w#o>5s6?rYQfZr@TZ~04YHg9^ zR_RhJNKxcUB+z!NCHcfHG%hr>%XoAYPw+Z7HcrzWA&bX(TEuckC_Cyq&FX42Nsd(> zm9sT(RzZA_m})D@5Ti`xHm1;3?Gnae^~_YA<&0{3*>>ToHlkxhNJciEm}+~a6B%Xq z`n`tULQpia)6-oNn-EnKTbk;8)IjI#YNFA?^J6?Fg_!fX7ePvP6zUt6Jw!|LKOk18 zJP)lu=TF%wBK$nC4cjC=8PMCO{4;jinEROBng@{KoI$qd4akmLbwkof*U37C8}S|A z^PHzBK)1_S$C)^H!fo+Gks`GzI>u_aAA!SON<2F%sVht&sW#PJz4WlWSHlPRIfnFoBZQ<*u-W zm>MN+s{W)eK88d4tMKvL+qXXYxVN;6Y}-G?$s!jX#>YyeDxcUx@tqyfc|HKxaIMMMHp!Ruly`7 zpTcgTo?I19hT~5|s|_sbiHE zB|+QZ{rJEPN{7U|<*nVZR_Y*8yH*20l^y^dj|XXTuu!?x|Ng)vR_&ghkU^Q01K=Lu z800p{3aL^vs1X^sN&t`0Rbd33#75CqV2-&4;azR=kX_;-zs8$var;ena%Ghq`e*Zc zOuvpvBSupf7de;U(!rzSb9M?4dk9uPYU^)E{+tonJ@aVnp7)di1epZnJ>+99n{x)) zLeIxk6kJqz6S{o<#&NK8U?pQfIas06(@*>|Q;1jW}A)y95f(YQ{ z%8B38S77kkE+{oyzf%RkW9!0O ziyL@VweL(+uAY?o?k{M$03u-AJK0%tYK;pu`StHOSTc9}=Tqx+q984E@Ff##R+GiF zy0iKw8n9aYj-P^lKsz4%&*$tDL}JcA;eYm5_QCgFvL85;d%hN!S@}n5Ry9DHQ*~iK z`s;YVkH-)d2CV6Y9{7`vk-kpCG3hypV!^jDT5T`_!lSF|Enw497N6`qg`_T3=K*`$J;+bixQR9EH2?P za5vmV+)W%#sfdRXwJG@@gHzuzw_5>;;Gg{EJQaWGiUya8OZt3p|AF}KyWc}y1WE?- zgMUXy1lTGiM3;$lMOHkCsXFR+&Q}8w){}^i>w3gdB=AyFxitphtFZzpkH95a0rIa0 zG*M!rja?xTs(o7vlv~EB3J8>YE$u+e1(Ir2O=Zqf%augEz(uphyl>-u70`|@25{Sv zf~%VyQ~U;+bqt#w;0dA!jNy#4fbP$m%_cv2do6|+7FY`-;SJ@w3nLvd1f>q+H4ChH z&OZaLpl~`14nIlnKZ!>c6$9v5m^cF{EQo7q;|{74yYR3PF{-5XFn}28FQb14VpKrL zQGsIEr7C>CB)UDMJTU*i`#^z)1wC@h`&|@g+SB4c=VWeAVL;2DZDH9zk*HRCf)z$9Gk;IZZiI| z+uM5M@^v>GT>K{HR!uuAQA*WzISbvF$++QLpte3BB$tKm*L(-nzRN+fwECt~IOD!P z3c=L$UoBng+@>tAu6@~zEHNWj0XWN6_Ug9oU_HE3JNhoAqoJ*b>>BU#ORVev2a_b| AAOHXW literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/wheel.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/models/__pycache__/wheel.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5327f21f60152255b9a014f046b5b4dda7c7a4a2 GIT binary patch literal 5112 zcmcgwTW=f36`t8$UPVzCE50OdvIzoIOfAawrA-AxP8{233$iB<0FQQ50Q!hqH4z=ghf$ z=Zrp?o~|-HfB4g~|HnDT{z4y|}WLAh6HRZYK${#0vfI9|he#q>+Tbg$m3bM`|f%A)d!iORm!ni1Ab)~F7C z{*jE8)G|!8>$wj$qzv4yA4t~=g&V7A+ZPfuosQJnO(HkhknXmp{HU+p*i+aQb!o5~ zdOeAbm$)0=wsd>a3;l4tS^XOWa@nA>!iU``(tk%KMU8w zZ7=Y}EfV1>Bp`w`4lY`@jbv?-Y&c#OBzVV5r05Wf$;ZLRdwBIP(MSeWvX&)y%a+d5 zg0Q5sS$t--O0q0$;XJWKK@=Z3t%|J5@(CtNPqx>Imx@hh1QcXBNhaW~_i+wy<~Y z`$hHpK`klm@_o+PEf!vNSW>=URZlqEb2u9;CKXWxWlNNHoz1F%4Z)@IWkFQQW@7p` zSapk?XQIC6{KC1-e#S!UHrusxo_pW|zEg(p%rla!X8N;`Yg){ik-8a~pBR}jBS$7i zW`D)HTr7xJ9+h$S`en9HwB|$|_CGqfJyP$A@_dc3I?Ux)$U8z4XSudrU%P%59d0*L zd7O^O4c?#?VU%FT#L$F;k!LkihkjDt{+chPHvD2RV|>v>I!(EQPjV8SNFl6}UCw~ZK%6O09)u+*Huw8a{aSt}%WS809lY8*yO^)8|o=v{LYwg)>JKTP>f$_LMR>S!5s)d|df{a|?`>d9p}koSEV zEMEp{1W`=qSpLvk)64NrvJr(#@!6%Wl5)+LONsJ2_oZ6imf`kt;I9proNd0lto=kT z#hCWiCARwUGSmQg4!vNx7YP~YWn-siypvWgXqj}PUc7pVj$1=huA+XNmWJk0#6^{8=EMyn zCd@t=GuPp|icA14s&$=~hW?pWj0xL`x1JVs9Qa9EHP$rFt6n9tuhF&&g%~ZfU zhK(H6Oilv6wA{{#a%pU!Tw3=QZ7=7?J&7+3A7jj^afjD=kuUHnFLR66_$;@qqXo;c zxb?T=Gc~J-v3dGuSGn`epngzu4X&m@=YXe?*Ac3IfLAY|NlcOO2>wR?wGU(j!q|gz z9~o30N7%QKvvD}rqvWn5g=Fa>q7zEmI#E!kZXV!7cut%&y+_f&qw9M-VT8V)GS0Rv z!Qi?4I%RHoSxNW3~DtB1iG!z+VQZvlFk&qzyQO6F{uY zDM;7}*U7gd^%OJdRgRraxd|(0P31j_rAMrjmbw7?X;~NE6 zN87F*EfY65Z1%{uB#7hGWFnuSR{_|tr0E2bk|qr97Y84vs2Uy0o_zA zxTl+A&MPSEyq=-3VwT^ya>G4$=6vR_NDQ3~*|~3KOfb6My61;P5hsk4kZQ!3s-Xvd zvf=h3a7R(tMfQQC8)+t44zup3UeK3UOoDQ+b&=){S9T_efvBN94sfLGb5KRi(foms zBB!DEEMD!RK}a3HW9^xuh*AX#93+Qva4A{|7$8?~*jadSKjq1U-@k=9^?fwA@oGX) zhZRwM4c^IId?*>v7(UhR`Qaf^bsd?;L3}1@MTUJKDQf9-`eqWvrbZ_RORHmLA<_*( zt`KVzcP{lvl63B0`285enqr1cTl^#tad7M&f zpFo6u^>H+lL3j`fy{{0n6Csup&7-YI5MX3FrP3059D~M$9`ho7904o;54fOQ zLk@1>;&pTY7f1KZZHa*5F*5N9_5xUB;dma4pL6vAezJiPNc1q2frKg9@&w!kI@u-6U~udubnoO+usIY--{jf&C94lyEpw$1J6%VE zXdhlJYe={aKKNo7?1&g3vzka8urbxGNSOGmLzXBe5I;+l|8u8pSVmWA)z`Q~Z$_xA z)%&EO7pj)ZD1~g%(deA4Fua{LM{|t@DmCdo)NZHMHf$#Ps9W1`G=Zr0|Pnx=f% xk+CV9(l-vynBC3%PfEyq=2~HdKJ3(K`9fxcRaA0J&!TahGGDN2yk^%-`%gC_gP#BZ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/candidate.py b/venv/lib/python3.8/site-packages/pip/_internal/models/candidate.py new file mode 100644 index 000000000..f27f28315 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/candidate.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass + +from pip._vendor.packaging.version import Version +from pip._vendor.packaging.version import parse as parse_version + +from pip._internal.models.link import Link + + +@dataclass(frozen=True) +class InstallationCandidate: + """Represents a potential "candidate" for installation.""" + + __slots__ = ["name", "version", "link"] + + name: str + version: Version + link: Link + + def __init__(self, name: str, version: str, link: Link) -> None: + object.__setattr__(self, "name", name) + object.__setattr__(self, "version", parse_version(version)) + object.__setattr__(self, "link", link) + + def __str__(self) -> str: + return f"{self.name!r} candidate (version {self.version} at {self.link})" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/direct_url.py b/venv/lib/python3.8/site-packages/pip/_internal/models/direct_url.py new file mode 100644 index 000000000..fc5ec8d4a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/direct_url.py @@ -0,0 +1,224 @@ +""" PEP 610 """ + +import json +import re +import urllib.parse +from dataclasses import dataclass +from typing import Any, ClassVar, Dict, Iterable, Optional, Type, TypeVar, Union + +__all__ = [ + "DirectUrl", + "DirectUrlValidationError", + "DirInfo", + "ArchiveInfo", + "VcsInfo", +] + +T = TypeVar("T") + +DIRECT_URL_METADATA_NAME = "direct_url.json" +ENV_VAR_RE = re.compile(r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$") + + +class DirectUrlValidationError(Exception): + pass + + +def _get( + d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None +) -> Optional[T]: + """Get value from dictionary and verify expected type.""" + if key not in d: + return default + value = d[key] + if not isinstance(value, expected_type): + raise DirectUrlValidationError( + f"{value!r} has unexpected type for {key} (expected {expected_type})" + ) + return value + + +def _get_required( + d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None +) -> T: + value = _get(d, expected_type, key, default) + if value is None: + raise DirectUrlValidationError(f"{key} must have a value") + return value + + +def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType": + infos = [info for info in infos if info is not None] + if not infos: + raise DirectUrlValidationError( + "missing one of archive_info, dir_info, vcs_info" + ) + if len(infos) > 1: + raise DirectUrlValidationError( + "more than one of archive_info, dir_info, vcs_info" + ) + assert infos[0] is not None + return infos[0] + + +def _filter_none(**kwargs: Any) -> Dict[str, Any]: + """Make dict excluding None values.""" + return {k: v for k, v in kwargs.items() if v is not None} + + +@dataclass +class VcsInfo: + name: ClassVar = "vcs_info" + + vcs: str + commit_id: str + requested_revision: Optional[str] = None + + @classmethod + def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]: + if d is None: + return None + return cls( + vcs=_get_required(d, str, "vcs"), + commit_id=_get_required(d, str, "commit_id"), + requested_revision=_get(d, str, "requested_revision"), + ) + + def _to_dict(self) -> Dict[str, Any]: + return _filter_none( + vcs=self.vcs, + requested_revision=self.requested_revision, + commit_id=self.commit_id, + ) + + +class ArchiveInfo: + name = "archive_info" + + def __init__( + self, + hash: Optional[str] = None, + hashes: Optional[Dict[str, str]] = None, + ) -> None: + # set hashes before hash, since the hash setter will further populate hashes + self.hashes = hashes + self.hash = hash + + @property + def hash(self) -> Optional[str]: + return self._hash + + @hash.setter + def hash(self, value: Optional[str]) -> None: + if value is not None: + # Auto-populate the hashes key to upgrade to the new format automatically. + # We don't back-populate the legacy hash key from hashes. + try: + hash_name, hash_value = value.split("=", 1) + except ValueError: + raise DirectUrlValidationError( + f"invalid archive_info.hash format: {value!r}" + ) + if self.hashes is None: + self.hashes = {hash_name: hash_value} + elif hash_name not in self.hashes: + self.hashes = self.hashes.copy() + self.hashes[hash_name] = hash_value + self._hash = value + + @classmethod + def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]: + if d is None: + return None + return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes")) + + def _to_dict(self) -> Dict[str, Any]: + return _filter_none(hash=self.hash, hashes=self.hashes) + + +@dataclass +class DirInfo: + name: ClassVar = "dir_info" + + editable: bool = False + + @classmethod + def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]: + if d is None: + return None + return cls(editable=_get_required(d, bool, "editable", default=False)) + + def _to_dict(self) -> Dict[str, Any]: + return _filter_none(editable=self.editable or None) + + +InfoType = Union[ArchiveInfo, DirInfo, VcsInfo] + + +@dataclass +class DirectUrl: + url: str + info: InfoType + subdirectory: Optional[str] = None + + def _remove_auth_from_netloc(self, netloc: str) -> str: + if "@" not in netloc: + return netloc + user_pass, netloc_no_user_pass = netloc.split("@", 1) + if ( + isinstance(self.info, VcsInfo) + and self.info.vcs == "git" + and user_pass == "git" + ): + return netloc + if ENV_VAR_RE.match(user_pass): + return netloc + return netloc_no_user_pass + + @property + def redacted_url(self) -> str: + """url with user:password part removed unless it is formed with + environment variables as specified in PEP 610, or it is ``git`` + in the case of a git URL. + """ + purl = urllib.parse.urlsplit(self.url) + netloc = self._remove_auth_from_netloc(purl.netloc) + surl = urllib.parse.urlunsplit( + (purl.scheme, netloc, purl.path, purl.query, purl.fragment) + ) + return surl + + def validate(self) -> None: + self.from_dict(self.to_dict()) + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl": + return DirectUrl( + url=_get_required(d, str, "url"), + subdirectory=_get(d, str, "subdirectory"), + info=_exactly_one_of( + [ + ArchiveInfo._from_dict(_get(d, dict, "archive_info")), + DirInfo._from_dict(_get(d, dict, "dir_info")), + VcsInfo._from_dict(_get(d, dict, "vcs_info")), + ] + ), + ) + + def to_dict(self) -> Dict[str, Any]: + res = _filter_none( + url=self.redacted_url, + subdirectory=self.subdirectory, + ) + res[self.info.name] = self.info._to_dict() + return res + + @classmethod + def from_json(cls, s: str) -> "DirectUrl": + return cls.from_dict(json.loads(s)) + + def to_json(self) -> str: + return json.dumps(self.to_dict(), sort_keys=True) + + def is_local_editable(self) -> bool: + return isinstance(self.info, DirInfo) and self.info.editable diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/format_control.py b/venv/lib/python3.8/site-packages/pip/_internal/models/format_control.py new file mode 100644 index 000000000..ccd11272c --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/format_control.py @@ -0,0 +1,78 @@ +from typing import FrozenSet, Optional, Set + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.exceptions import CommandError + + +class FormatControl: + """Helper for managing formats from which a package can be installed.""" + + __slots__ = ["no_binary", "only_binary"] + + def __init__( + self, + no_binary: Optional[Set[str]] = None, + only_binary: Optional[Set[str]] = None, + ) -> None: + if no_binary is None: + no_binary = set() + if only_binary is None: + only_binary = set() + + self.no_binary = no_binary + self.only_binary = only_binary + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + + if self.__slots__ != other.__slots__: + return False + + return all(getattr(self, k) == getattr(other, k) for k in self.__slots__) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})" + + @staticmethod + def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None: + if value.startswith("-"): + raise CommandError( + "--no-binary / --only-binary option requires 1 argument." + ) + new = value.split(",") + while ":all:" in new: + other.clear() + target.clear() + target.add(":all:") + del new[: new.index(":all:") + 1] + # Without a none, we want to discard everything as :all: covers it + if ":none:" not in new: + return + for name in new: + if name == ":none:": + target.clear() + continue + name = canonicalize_name(name) + other.discard(name) + target.add(name) + + def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]: + result = {"binary", "source"} + if canonical_name in self.only_binary: + result.discard("source") + elif canonical_name in self.no_binary: + result.discard("binary") + elif ":all:" in self.only_binary: + result.discard("source") + elif ":all:" in self.no_binary: + result.discard("binary") + return frozenset(result) + + def disallow_binaries(self) -> None: + self.handle_mutual_excludes( + ":all:", + self.no_binary, + self.only_binary, + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/index.py b/venv/lib/python3.8/site-packages/pip/_internal/models/index.py new file mode 100644 index 000000000..b94c32511 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/index.py @@ -0,0 +1,28 @@ +import urllib.parse + + +class PackageIndex: + """Represents a Package Index and provides easier access to endpoints""" + + __slots__ = ["url", "netloc", "simple_url", "pypi_url", "file_storage_domain"] + + def __init__(self, url: str, file_storage_domain: str) -> None: + super().__init__() + self.url = url + self.netloc = urllib.parse.urlsplit(url).netloc + self.simple_url = self._url_for_path("simple") + self.pypi_url = self._url_for_path("pypi") + + # This is part of a temporary hack used to block installs of PyPI + # packages which depend on external urls only necessary until PyPI can + # block such packages themselves + self.file_storage_domain = file_storage_domain + + def _url_for_path(self, path: str) -> str: + return urllib.parse.urljoin(self.url, path) + + +PyPI = PackageIndex("https://pypi.org/", file_storage_domain="files.pythonhosted.org") +TestPyPI = PackageIndex( + "https://test.pypi.org/", file_storage_domain="test-files.pythonhosted.org" +) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/installation_report.py b/venv/lib/python3.8/site-packages/pip/_internal/models/installation_report.py new file mode 100644 index 000000000..b9c6330df --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/installation_report.py @@ -0,0 +1,56 @@ +from typing import Any, Dict, Sequence + +from pip._vendor.packaging.markers import default_environment + +from pip import __version__ +from pip._internal.req.req_install import InstallRequirement + + +class InstallationReport: + def __init__(self, install_requirements: Sequence[InstallRequirement]): + self._install_requirements = install_requirements + + @classmethod + def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]: + assert ireq.download_info, f"No download_info for {ireq}" + res = { + # PEP 610 json for the download URL. download_info.archive_info.hashes may + # be absent when the requirement was installed from the wheel cache + # and the cache entry was populated by an older pip version that did not + # record origin.json. + "download_info": ireq.download_info.to_dict(), + # is_direct is true if the requirement was a direct URL reference (which + # includes editable requirements), and false if the requirement was + # downloaded from a PEP 503 index or --find-links. + "is_direct": ireq.is_direct, + # is_yanked is true if the requirement was yanked from the index, but + # was still selected by pip to conform to PEP 592. + "is_yanked": ireq.link.is_yanked if ireq.link else False, + # requested is true if the requirement was specified by the user (aka + # top level requirement), and false if it was installed as a dependency of a + # requirement. https://peps.python.org/pep-0376/#requested + "requested": ireq.user_supplied, + # PEP 566 json encoding for metadata + # https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata + "metadata": ireq.get_dist().metadata_dict, + } + if ireq.user_supplied and ireq.extras: + # For top level requirements, the list of requested extras, if any. + res["requested_extras"] = sorted(ireq.extras) + return res + + def to_dict(self) -> Dict[str, Any]: + return { + "version": "1", + "pip_version": __version__, + "install": [ + self._install_req_to_dict(ireq) for ireq in self._install_requirements + ], + # https://peps.python.org/pep-0508/#environment-markers + # TODO: currently, the resolver uses the default environment to evaluate + # environment markers, so that is what we report here. In the future, it + # should also take into account options such as --python-version or + # --platform, perhaps under the form of an environment_override field? + # https://github.com/pypa/pip/issues/11198 + "environment": default_environment(), + } diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/link.py b/venv/lib/python3.8/site-packages/pip/_internal/models/link.py new file mode 100644 index 000000000..27ad01609 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/link.py @@ -0,0 +1,604 @@ +import functools +import itertools +import logging +import os +import posixpath +import re +import urllib.parse +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Mapping, + NamedTuple, + Optional, + Tuple, + Union, +) + +from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.filetypes import WHEEL_EXTENSION +from pip._internal.utils.hashes import Hashes +from pip._internal.utils.misc import ( + pairwise, + redact_auth_from_url, + split_auth_from_netloc, + splitext, +) +from pip._internal.utils.urls import path_to_url, url_to_path + +if TYPE_CHECKING: + from pip._internal.index.collector import IndexContent + +logger = logging.getLogger(__name__) + + +# Order matters, earlier hashes have a precedence over later hashes for what +# we will pick to use. +_SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5") + + +@dataclass(frozen=True) +class LinkHash: + """Links to content may have embedded hash values. This class parses those. + + `name` must be any member of `_SUPPORTED_HASHES`. + + This class can be converted to and from `ArchiveInfo`. While ArchiveInfo intends to + be JSON-serializable to conform to PEP 610, this class contains the logic for + parsing a hash name and value for correctness, and then checking whether that hash + conforms to a schema with `.is_hash_allowed()`.""" + + name: str + value: str + + _hash_url_fragment_re = re.compile( + # NB: we do not validate that the second group (.*) is a valid hex + # digest. Instead, we simply keep that string in this class, and then check it + # against Hashes when hash-checking is needed. This is easier to debug than + # proactively discarding an invalid hex digest, as we handle incorrect hashes + # and malformed hashes in the same place. + r"[#&]({choices})=([^&]*)".format( + choices="|".join(re.escape(hash_name) for hash_name in _SUPPORTED_HASHES) + ), + ) + + def __post_init__(self) -> None: + assert self.name in _SUPPORTED_HASHES + + @classmethod + @functools.lru_cache(maxsize=None) + def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]: + """Search a string for a checksum algorithm name and encoded output value.""" + match = cls._hash_url_fragment_re.search(url) + if match is None: + return None + name, value = match.groups() + return cls(name=name, value=value) + + def as_dict(self) -> Dict[str, str]: + return {self.name: self.value} + + def as_hashes(self) -> Hashes: + """Return a Hashes instance which checks only for the current hash.""" + return Hashes({self.name: [self.value]}) + + def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool: + """ + Return True if the current hash is allowed by `hashes`. + """ + if hashes is None: + return False + return hashes.is_hash_allowed(self.name, hex_digest=self.value) + + +@dataclass(frozen=True) +class MetadataFile: + """Information about a core metadata file associated with a distribution.""" + + hashes: Optional[Dict[str, str]] + + def __post_init__(self) -> None: + if self.hashes is not None: + assert all(name in _SUPPORTED_HASHES for name in self.hashes) + + +def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]: + # Remove any unsupported hash types from the mapping. If this leaves no + # supported hashes, return None + if hashes is None: + return None + hashes = {n: v for n, v in hashes.items() if n in _SUPPORTED_HASHES} + if not hashes: + return None + return hashes + + +def _clean_url_path_part(part: str) -> str: + """ + Clean a "part" of a URL path (i.e. after splitting on "@" characters). + """ + # We unquote prior to quoting to make sure nothing is double quoted. + return urllib.parse.quote(urllib.parse.unquote(part)) + + +def _clean_file_url_path(part: str) -> str: + """ + Clean the first part of a URL path that corresponds to a local + filesystem path (i.e. the first part after splitting on "@" characters). + """ + # We unquote prior to quoting to make sure nothing is double quoted. + # Also, on Windows the path part might contain a drive letter which + # should not be quoted. On Linux where drive letters do not + # exist, the colon should be quoted. We rely on urllib.request + # to do the right thing here. + return urllib.request.pathname2url(urllib.request.url2pathname(part)) + + +# percent-encoded: / +_reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE) + + +def _clean_url_path(path: str, is_local_path: bool) -> str: + """ + Clean the path portion of a URL. + """ + if is_local_path: + clean_func = _clean_file_url_path + else: + clean_func = _clean_url_path_part + + # Split on the reserved characters prior to cleaning so that + # revision strings in VCS URLs are properly preserved. + parts = _reserved_chars_re.split(path) + + cleaned_parts = [] + for to_clean, reserved in pairwise(itertools.chain(parts, [""])): + cleaned_parts.append(clean_func(to_clean)) + # Normalize %xx escapes (e.g. %2f -> %2F) + cleaned_parts.append(reserved.upper()) + + return "".join(cleaned_parts) + + +def _ensure_quoted_url(url: str) -> str: + """ + Make sure a link is fully quoted. + For example, if ' ' occurs in the URL, it will be replaced with "%20", + and without double-quoting other characters. + """ + # Split the URL into parts according to the general structure + # `scheme://netloc/path?query#fragment`. + result = urllib.parse.urlsplit(url) + # If the netloc is empty, then the URL refers to a local filesystem path. + is_local_path = not result.netloc + path = _clean_url_path(result.path, is_local_path=is_local_path) + return urllib.parse.urlunsplit(result._replace(path=path)) + + +def _absolute_link_url(base_url: str, url: str) -> str: + """ + A faster implementation of urllib.parse.urljoin with a shortcut + for absolute http/https URLs. + """ + if url.startswith(("https://", "http://")): + return url + else: + return urllib.parse.urljoin(base_url, url) + + +@functools.total_ordering +class Link: + """Represents a parsed link from a Package Index's simple URL""" + + __slots__ = [ + "_parsed_url", + "_url", + "_path", + "_hashes", + "comes_from", + "requires_python", + "yanked_reason", + "metadata_file_data", + "cache_link_parsing", + "egg_fragment", + ] + + def __init__( + self, + url: str, + comes_from: Optional[Union[str, "IndexContent"]] = None, + requires_python: Optional[str] = None, + yanked_reason: Optional[str] = None, + metadata_file_data: Optional[MetadataFile] = None, + cache_link_parsing: bool = True, + hashes: Optional[Mapping[str, str]] = None, + ) -> None: + """ + :param url: url of the resource pointed to (href of the link) + :param comes_from: instance of IndexContent where the link was found, + or string. + :param requires_python: String containing the `Requires-Python` + metadata field, specified in PEP 345. This may be specified by + a data-requires-python attribute in the HTML link tag, as + described in PEP 503. + :param yanked_reason: the reason the file has been yanked, if the + file has been yanked, or None if the file hasn't been yanked. + This is the value of the "data-yanked" attribute, if present, in + a simple repository HTML link. If the file has been yanked but + no reason was provided, this should be the empty string. See + PEP 592 for more information and the specification. + :param metadata_file_data: the metadata attached to the file, or None if + no such metadata is provided. This argument, if not None, indicates + that a separate metadata file exists, and also optionally supplies + hashes for that file. + :param cache_link_parsing: A flag that is used elsewhere to determine + whether resources retrieved from this link should be cached. PyPI + URLs should generally have this set to False, for example. + :param hashes: A mapping of hash names to digests to allow us to + determine the validity of a download. + """ + + # The comes_from, requires_python, and metadata_file_data arguments are + # only used by classmethods of this class, and are not used in client + # code directly. + + # url can be a UNC windows share + if url.startswith("\\\\"): + url = path_to_url(url) + + self._parsed_url = urllib.parse.urlsplit(url) + # Store the url as a private attribute to prevent accidentally + # trying to set a new value. + self._url = url + # The .path property is hot, so calculate its value ahead of time. + self._path = urllib.parse.unquote(self._parsed_url.path) + + link_hash = LinkHash.find_hash_url_fragment(url) + hashes_from_link = {} if link_hash is None else link_hash.as_dict() + if hashes is None: + self._hashes = hashes_from_link + else: + self._hashes = {**hashes, **hashes_from_link} + + self.comes_from = comes_from + self.requires_python = requires_python if requires_python else None + self.yanked_reason = yanked_reason + self.metadata_file_data = metadata_file_data + + self.cache_link_parsing = cache_link_parsing + self.egg_fragment = self._egg_fragment() + + @classmethod + def from_json( + cls, + file_data: Dict[str, Any], + page_url: str, + ) -> Optional["Link"]: + """ + Convert an pypi json document from a simple repository page into a Link. + """ + file_url = file_data.get("url") + if file_url is None: + return None + + url = _ensure_quoted_url(_absolute_link_url(page_url, file_url)) + pyrequire = file_data.get("requires-python") + yanked_reason = file_data.get("yanked") + hashes = file_data.get("hashes", {}) + + # PEP 714: Indexes must use the name core-metadata, but + # clients should support the old name as a fallback for compatibility. + metadata_info = file_data.get("core-metadata") + if metadata_info is None: + metadata_info = file_data.get("dist-info-metadata") + + # The metadata info value may be a boolean, or a dict of hashes. + if isinstance(metadata_info, dict): + # The file exists, and hashes have been supplied + metadata_file_data = MetadataFile(supported_hashes(metadata_info)) + elif metadata_info: + # The file exists, but there are no hashes + metadata_file_data = MetadataFile(None) + else: + # False or not present: the file does not exist + metadata_file_data = None + + # The Link.yanked_reason expects an empty string instead of a boolean. + if yanked_reason and not isinstance(yanked_reason, str): + yanked_reason = "" + # The Link.yanked_reason expects None instead of False. + elif not yanked_reason: + yanked_reason = None + + return cls( + url, + comes_from=page_url, + requires_python=pyrequire, + yanked_reason=yanked_reason, + hashes=hashes, + metadata_file_data=metadata_file_data, + ) + + @classmethod + def from_element( + cls, + anchor_attribs: Dict[str, Optional[str]], + page_url: str, + base_url: str, + ) -> Optional["Link"]: + """ + Convert an anchor element's attributes in a simple repository page to a Link. + """ + href = anchor_attribs.get("href") + if not href: + return None + + url = _ensure_quoted_url(_absolute_link_url(base_url, href)) + pyrequire = anchor_attribs.get("data-requires-python") + yanked_reason = anchor_attribs.get("data-yanked") + + # PEP 714: Indexes must use the name data-core-metadata, but + # clients should support the old name as a fallback for compatibility. + metadata_info = anchor_attribs.get("data-core-metadata") + if metadata_info is None: + metadata_info = anchor_attribs.get("data-dist-info-metadata") + # The metadata info value may be the string "true", or a string of + # the form "hashname=hashval" + if metadata_info == "true": + # The file exists, but there are no hashes + metadata_file_data = MetadataFile(None) + elif metadata_info is None: + # The file does not exist + metadata_file_data = None + else: + # The file exists, and hashes have been supplied + hashname, sep, hashval = metadata_info.partition("=") + if sep == "=": + metadata_file_data = MetadataFile(supported_hashes({hashname: hashval})) + else: + # Error - data is wrong. Treat as no hashes supplied. + logger.debug( + "Index returned invalid data-dist-info-metadata value: %s", + metadata_info, + ) + metadata_file_data = MetadataFile(None) + + return cls( + url, + comes_from=page_url, + requires_python=pyrequire, + yanked_reason=yanked_reason, + metadata_file_data=metadata_file_data, + ) + + def __str__(self) -> str: + if self.requires_python: + rp = f" (requires-python:{self.requires_python})" + else: + rp = "" + if self.comes_from: + return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}" + else: + return redact_auth_from_url(str(self._url)) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash(self.url) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Link): + return NotImplemented + return self.url == other.url + + def __lt__(self, other: Any) -> bool: + if not isinstance(other, Link): + return NotImplemented + return self.url < other.url + + @property + def url(self) -> str: + return self._url + + @property + def filename(self) -> str: + path = self.path.rstrip("/") + name = posixpath.basename(path) + if not name: + # Make sure we don't leak auth information if the netloc + # includes a username and password. + netloc, user_pass = split_auth_from_netloc(self.netloc) + return netloc + + name = urllib.parse.unquote(name) + assert name, f"URL {self._url!r} produced no filename" + return name + + @property + def file_path(self) -> str: + return url_to_path(self.url) + + @property + def scheme(self) -> str: + return self._parsed_url.scheme + + @property + def netloc(self) -> str: + """ + This can contain auth information. + """ + return self._parsed_url.netloc + + @property + def path(self) -> str: + return self._path + + def splitext(self) -> Tuple[str, str]: + return splitext(posixpath.basename(self.path.rstrip("/"))) + + @property + def ext(self) -> str: + return self.splitext()[1] + + @property + def url_without_fragment(self) -> str: + scheme, netloc, path, query, fragment = self._parsed_url + return urllib.parse.urlunsplit((scheme, netloc, path, query, "")) + + _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)") + + # Per PEP 508. + _project_name_re = re.compile( + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE + ) + + def _egg_fragment(self) -> Optional[str]: + match = self._egg_fragment_re.search(self._url) + if not match: + return None + + # An egg fragment looks like a PEP 508 project name, along with + # an optional extras specifier. Anything else is invalid. + project_name = match.group(1) + if not self._project_name_re.match(project_name): + deprecated( + reason=f"{self} contains an egg fragment with a non-PEP 508 name.", + replacement="to use the req @ url syntax, and remove the egg fragment", + gone_in="25.1", + issue=13157, + ) + + return project_name + + _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)") + + @property + def subdirectory_fragment(self) -> Optional[str]: + match = self._subdirectory_fragment_re.search(self._url) + if not match: + return None + return match.group(1) + + def metadata_link(self) -> Optional["Link"]: + """Return a link to the associated core metadata file (if any).""" + if self.metadata_file_data is None: + return None + metadata_url = f"{self.url_without_fragment}.metadata" + if self.metadata_file_data.hashes is None: + return Link(metadata_url) + return Link(metadata_url, hashes=self.metadata_file_data.hashes) + + def as_hashes(self) -> Hashes: + return Hashes({k: [v] for k, v in self._hashes.items()}) + + @property + def hash(self) -> Optional[str]: + return next(iter(self._hashes.values()), None) + + @property + def hash_name(self) -> Optional[str]: + return next(iter(self._hashes), None) + + @property + def show_url(self) -> str: + return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0]) + + @property + def is_file(self) -> bool: + return self.scheme == "file" + + def is_existing_dir(self) -> bool: + return self.is_file and os.path.isdir(self.file_path) + + @property + def is_wheel(self) -> bool: + return self.ext == WHEEL_EXTENSION + + @property + def is_vcs(self) -> bool: + from pip._internal.vcs import vcs + + return self.scheme in vcs.all_schemes + + @property + def is_yanked(self) -> bool: + return self.yanked_reason is not None + + @property + def has_hash(self) -> bool: + return bool(self._hashes) + + def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool: + """ + Return True if the link has a hash and it is allowed by `hashes`. + """ + if hashes is None: + return False + return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items()) + + +class _CleanResult(NamedTuple): + """Convert link for equivalency check. + + This is used in the resolver to check whether two URL-specified requirements + likely point to the same distribution and can be considered equivalent. This + equivalency logic avoids comparing URLs literally, which can be too strict + (e.g. "a=1&b=2" vs "b=2&a=1") and produce conflicts unexpecting to users. + + Currently this does three things: + + 1. Drop the basic auth part. This is technically wrong since a server can + serve different content based on auth, but if it does that, it is even + impossible to guarantee two URLs without auth are equivalent, since + the user can input different auth information when prompted. So the + practical solution is to assume the auth doesn't affect the response. + 2. Parse the query to avoid the ordering issue. Note that ordering under the + same key in the query are NOT cleaned; i.e. "a=1&a=2" and "a=2&a=1" are + still considered different. + 3. Explicitly drop most of the fragment part, except ``subdirectory=`` and + hash values, since it should have no impact the downloaded content. Note + that this drops the "egg=" part historically used to denote the requested + project (and extras), which is wrong in the strictest sense, but too many + people are supplying it inconsistently to cause superfluous resolution + conflicts, so we choose to also ignore them. + """ + + parsed: urllib.parse.SplitResult + query: Dict[str, List[str]] + subdirectory: str + hashes: Dict[str, str] + + +def _clean_link(link: Link) -> _CleanResult: + parsed = link._parsed_url + netloc = parsed.netloc.rsplit("@", 1)[-1] + # According to RFC 8089, an empty host in file: means localhost. + if parsed.scheme == "file" and not netloc: + netloc = "localhost" + fragment = urllib.parse.parse_qs(parsed.fragment) + if "egg" in fragment: + logger.debug("Ignoring egg= fragment in %s", link) + try: + # If there are multiple subdirectory values, use the first one. + # This matches the behavior of Link.subdirectory_fragment. + subdirectory = fragment["subdirectory"][0] + except (IndexError, KeyError): + subdirectory = "" + # If there are multiple hash values under the same algorithm, use the + # first one. This matches the behavior of Link.hash_value. + hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment} + return _CleanResult( + parsed=parsed._replace(netloc=netloc, query="", fragment=""), + query=urllib.parse.parse_qs(parsed.query), + subdirectory=subdirectory, + hashes=hashes, + ) + + +@functools.lru_cache(maxsize=None) +def links_equivalent(link1: Link, link2: Link) -> bool: + return _clean_link(link1) == _clean_link(link2) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/scheme.py b/venv/lib/python3.8/site-packages/pip/_internal/models/scheme.py new file mode 100644 index 000000000..06a9a550e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/scheme.py @@ -0,0 +1,25 @@ +""" +For types associated with installation schemes. + +For a general overview of available schemes and their context, see +https://docs.python.org/3/install/index.html#alternate-installation. +""" + +from dataclasses import dataclass + +SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"] + + +@dataclass(frozen=True) +class Scheme: + """A Scheme holds paths which are used as the base directories for + artifacts associated with a Python package. + """ + + __slots__ = SCHEME_KEYS + + platlib: str + purelib: str + headers: str + scripts: str + data: str diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/search_scope.py b/venv/lib/python3.8/site-packages/pip/_internal/models/search_scope.py new file mode 100644 index 000000000..ee7bc8622 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/search_scope.py @@ -0,0 +1,127 @@ +import itertools +import logging +import os +import posixpath +import urllib.parse +from dataclasses import dataclass +from typing import List + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.models.index import PyPI +from pip._internal.utils.compat import has_tls +from pip._internal.utils.misc import normalize_path, redact_auth_from_url + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class SearchScope: + """ + Encapsulates the locations that pip is configured to search. + """ + + __slots__ = ["find_links", "index_urls", "no_index"] + + find_links: List[str] + index_urls: List[str] + no_index: bool + + @classmethod + def create( + cls, + find_links: List[str], + index_urls: List[str], + no_index: bool, + ) -> "SearchScope": + """ + Create a SearchScope object after normalizing the `find_links`. + """ + # Build find_links. If an argument starts with ~, it may be + # a local file relative to a home directory. So try normalizing + # it and if it exists, use the normalized version. + # This is deliberately conservative - it might be fine just to + # blindly normalize anything starting with a ~... + built_find_links: List[str] = [] + for link in find_links: + if link.startswith("~"): + new_link = normalize_path(link) + if os.path.exists(new_link): + link = new_link + built_find_links.append(link) + + # If we don't have TLS enabled, then WARN if anyplace we're looking + # relies on TLS. + if not has_tls(): + for link in itertools.chain(index_urls, built_find_links): + parsed = urllib.parse.urlparse(link) + if parsed.scheme == "https": + logger.warning( + "pip is configured with locations that require " + "TLS/SSL, however the ssl module in Python is not " + "available." + ) + break + + return cls( + find_links=built_find_links, + index_urls=index_urls, + no_index=no_index, + ) + + def get_formatted_locations(self) -> str: + lines = [] + redacted_index_urls = [] + if self.index_urls and self.index_urls != [PyPI.simple_url]: + for url in self.index_urls: + redacted_index_url = redact_auth_from_url(url) + + # Parse the URL + purl = urllib.parse.urlsplit(redacted_index_url) + + # URL is generally invalid if scheme and netloc is missing + # there are issues with Python and URL parsing, so this test + # is a bit crude. See bpo-20271, bpo-23505. Python doesn't + # always parse invalid URLs correctly - it should raise + # exceptions for malformed URLs + if not purl.scheme and not purl.netloc: + logger.warning( + 'The index url "%s" seems invalid, please provide a scheme.', + redacted_index_url, + ) + + redacted_index_urls.append(redacted_index_url) + + lines.append( + "Looking in indexes: {}".format(", ".join(redacted_index_urls)) + ) + + if self.find_links: + lines.append( + "Looking in links: {}".format( + ", ".join(redact_auth_from_url(url) for url in self.find_links) + ) + ) + return "\n".join(lines) + + def get_index_urls_locations(self, project_name: str) -> List[str]: + """Returns the locations found via self.index_urls + + Checks the url_name on the main (first in the list) index and + use this url_name to produce all locations + """ + + def mkurl_pypi_url(url: str) -> str: + loc = posixpath.join( + url, urllib.parse.quote(canonicalize_name(project_name)) + ) + # For maximum compatibility with easy_install, ensure the path + # ends in a trailing slash. Although this isn't in the spec + # (and PyPI can handle it without the slash) some other index + # implementations might break if they relied on easy_install's + # behavior. + if not loc.endswith("/"): + loc = loc + "/" + return loc + + return [mkurl_pypi_url(url) for url in self.index_urls] diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/selection_prefs.py b/venv/lib/python3.8/site-packages/pip/_internal/models/selection_prefs.py new file mode 100644 index 000000000..e9b50aa51 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/selection_prefs.py @@ -0,0 +1,53 @@ +from typing import Optional + +from pip._internal.models.format_control import FormatControl + + +# TODO: This needs Python 3.10's improved slots support for dataclasses +# to be converted into a dataclass. +class SelectionPreferences: + """ + Encapsulates the candidate selection preferences for downloading + and installing files. + """ + + __slots__ = [ + "allow_yanked", + "allow_all_prereleases", + "format_control", + "prefer_binary", + "ignore_requires_python", + ] + + # Don't include an allow_yanked default value to make sure each call + # site considers whether yanked releases are allowed. This also causes + # that decision to be made explicit in the calling code, which helps + # people when reading the code. + def __init__( + self, + allow_yanked: bool, + allow_all_prereleases: bool = False, + format_control: Optional[FormatControl] = None, + prefer_binary: bool = False, + ignore_requires_python: Optional[bool] = None, + ) -> None: + """Create a SelectionPreferences object. + + :param allow_yanked: Whether files marked as yanked (in the sense + of PEP 592) are permitted to be candidates for install. + :param format_control: A FormatControl object or None. Used to control + the selection of source packages / binary packages when consulting + the index and links. + :param prefer_binary: Whether to prefer an old, but valid, binary + dist over a new source dist. + :param ignore_requires_python: Whether to ignore incompatible + "Requires-Python" values in links. Defaults to False. + """ + if ignore_requires_python is None: + ignore_requires_python = False + + self.allow_yanked = allow_yanked + self.allow_all_prereleases = allow_all_prereleases + self.format_control = format_control + self.prefer_binary = prefer_binary + self.ignore_requires_python = ignore_requires_python diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/target_python.py b/venv/lib/python3.8/site-packages/pip/_internal/models/target_python.py new file mode 100644 index 000000000..88925a9fd --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/target_python.py @@ -0,0 +1,121 @@ +import sys +from typing import List, Optional, Set, Tuple + +from pip._vendor.packaging.tags import Tag + +from pip._internal.utils.compatibility_tags import get_supported, version_info_to_nodot +from pip._internal.utils.misc import normalize_version_info + + +class TargetPython: + """ + Encapsulates the properties of a Python interpreter one is targeting + for a package install, download, etc. + """ + + __slots__ = [ + "_given_py_version_info", + "abis", + "implementation", + "platforms", + "py_version", + "py_version_info", + "_valid_tags", + "_valid_tags_set", + ] + + def __init__( + self, + platforms: Optional[List[str]] = None, + py_version_info: Optional[Tuple[int, ...]] = None, + abis: Optional[List[str]] = None, + implementation: Optional[str] = None, + ) -> None: + """ + :param platforms: A list of strings or None. If None, searches for + packages that are supported by the current system. Otherwise, will + find packages that can be built on the platforms passed in. These + packages will only be downloaded for distribution: they will + not be built locally. + :param py_version_info: An optional tuple of ints representing the + Python version information to use (e.g. `sys.version_info[:3]`). + This can have length 1, 2, or 3 when provided. + :param abis: A list of strings or None. This is passed to + compatibility_tags.py's get_supported() function as is. + :param implementation: A string or None. This is passed to + compatibility_tags.py's get_supported() function as is. + """ + # Store the given py_version_info for when we call get_supported(). + self._given_py_version_info = py_version_info + + if py_version_info is None: + py_version_info = sys.version_info[:3] + else: + py_version_info = normalize_version_info(py_version_info) + + py_version = ".".join(map(str, py_version_info[:2])) + + self.abis = abis + self.implementation = implementation + self.platforms = platforms + self.py_version = py_version + self.py_version_info = py_version_info + + # This is used to cache the return value of get_(un)sorted_tags. + self._valid_tags: Optional[List[Tag]] = None + self._valid_tags_set: Optional[Set[Tag]] = None + + def format_given(self) -> str: + """ + Format the given, non-None attributes for display. + """ + display_version = None + if self._given_py_version_info is not None: + display_version = ".".join( + str(part) for part in self._given_py_version_info + ) + + key_values = [ + ("platforms", self.platforms), + ("version_info", display_version), + ("abis", self.abis), + ("implementation", self.implementation), + ] + return " ".join( + f"{key}={value!r}" for key, value in key_values if value is not None + ) + + def get_sorted_tags(self) -> List[Tag]: + """ + Return the supported PEP 425 tags to check wheel candidates against. + + The tags are returned in order of preference (most preferred first). + """ + if self._valid_tags is None: + # Pass versions=None if no py_version_info was given since + # versions=None uses special default logic. + py_version_info = self._given_py_version_info + if py_version_info is None: + version = None + else: + version = version_info_to_nodot(py_version_info) + + tags = get_supported( + version=version, + platforms=self.platforms, + abis=self.abis, + impl=self.implementation, + ) + self._valid_tags = tags + + return self._valid_tags + + def get_unsorted_tags(self) -> Set[Tag]: + """Exactly the same as get_sorted_tags, but returns a set. + + This is important for performance. + """ + if self._valid_tags_set is None: + self._valid_tags_set = set(self.get_sorted_tags()) + + return self._valid_tags_set diff --git a/venv/lib/python3.8/site-packages/pip/_internal/models/wheel.py b/venv/lib/python3.8/site-packages/pip/_internal/models/wheel.py new file mode 100644 index 000000000..ea8560089 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/models/wheel.py @@ -0,0 +1,118 @@ +"""Represents a wheel file and provides access to the various parts of the +name that have meaning. +""" + +import re +from typing import Dict, Iterable, List + +from pip._vendor.packaging.tags import Tag +from pip._vendor.packaging.utils import ( + InvalidWheelFilename as PackagingInvalidWheelName, +) +from pip._vendor.packaging.utils import parse_wheel_filename + +from pip._internal.exceptions import InvalidWheelFilename +from pip._internal.utils.deprecation import deprecated + + +class Wheel: + """A wheel file""" + + wheel_file_re = re.compile( + r"""^(?P(?P[^\s-]+?)-(?P[^\s-]*?)) + ((-(?P\d[^-]*?))?-(?P[^\s-]+?)-(?P[^\s-]+?)-(?P[^\s-]+?) + \.whl|\.dist-info)$""", + re.VERBOSE, + ) + + def __init__(self, filename: str) -> None: + """ + :raises InvalidWheelFilename: when the filename is invalid for a wheel + """ + wheel_info = self.wheel_file_re.match(filename) + if not wheel_info: + raise InvalidWheelFilename(f"{filename} is not a valid wheel filename.") + self.filename = filename + self.name = wheel_info.group("name").replace("_", "-") + _version = wheel_info.group("ver") + if "_" in _version: + try: + parse_wheel_filename(filename) + except PackagingInvalidWheelName as e: + deprecated( + reason=( + f"Wheel filename {filename!r} is not correctly normalised. " + "Future versions of pip will raise the following error:\n" + f"{e.args[0]}\n\n" + ), + replacement=( + "to rename the wheel to use a correctly normalised " + "name (this may require updating the version in " + "the project metadata)" + ), + gone_in="25.1", + issue=12938, + ) + + _version = _version.replace("_", "-") + + self.version = _version + self.build_tag = wheel_info.group("build") + self.pyversions = wheel_info.group("pyver").split(".") + self.abis = wheel_info.group("abi").split(".") + self.plats = wheel_info.group("plat").split(".") + + # All the tag combinations from this file + self.file_tags = { + Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats + } + + def get_formatted_file_tags(self) -> List[str]: + """Return the wheel's tags as a sorted list of strings.""" + return sorted(str(tag) for tag in self.file_tags) + + def support_index_min(self, tags: List[Tag]) -> int: + """Return the lowest index that one of the wheel's file_tag combinations + achieves in the given list of supported tags. + + For example, if there are 8 supported tags and one of the file tags + is first in the list, then return 0. + + :param tags: the PEP 425 tags to check the wheel against, in order + with most preferred first. + + :raises ValueError: If none of the wheel's file tags match one of + the supported tags. + """ + try: + return next(i for i, t in enumerate(tags) if t in self.file_tags) + except StopIteration: + raise ValueError() + + def find_most_preferred_tag( + self, tags: List[Tag], tag_to_priority: Dict[Tag, int] + ) -> int: + """Return the priority of the most preferred tag that one of the wheel's file + tag combinations achieves in the given list of supported tags using the given + tag_to_priority mapping, where lower priorities are more-preferred. + + This is used in place of support_index_min in some cases in order to avoid + an expensive linear scan of a large list of tags. + + :param tags: the PEP 425 tags to check the wheel against. + :param tag_to_priority: a mapping from tag to priority of that tag, where + lower is more preferred. + + :raises ValueError: If none of the wheel's file tags match one of + the supported tags. + """ + return min( + tag_to_priority[tag] for tag in self.file_tags if tag in tag_to_priority + ) + + def supported(self, tags: Iterable[Tag]) -> bool: + """Return whether the wheel is compatible with one of the given tags. + + :param tags: the PEP 425 tags to check the wheel against. + """ + return not self.file_tags.isdisjoint(tags) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/network/__init__.py new file mode 100644 index 000000000..b51bde91b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/__init__.py @@ -0,0 +1,2 @@ +"""Contains purely network-related utilities. +""" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a288556d1fbdd462ddca2e2867f3f3c6e8668704 GIT binary patch literal 269 zcmYjMy-EW?5Z+5;2;?2E4YD_28VeCYOR*6fn`JLMn&I|#m)*IOi%;Y$So;)deFQrz zCz2u`eBbcRA2UzON{mbt=Y-5jrJM0KO+~J5mrXB;sy=@Dm ziMKK6wAL>fu_e@;?WV&FBZd(?wa1oqa|wkQb&|4uMH!_8h5};_*2D2QFp?rRrb*L& Pe$_`ec)UKm_#;$*o0?H3 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/auth.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/auth.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3914839303eee6e8d7192b1a745416775bdd3c97 GIT binary patch literal 14508 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 E19HwrT>t<8 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/cache.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/cache.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51573e3512dcdb3d59b9d844ad12fb4a3386927a GIT binary patch literal 4695 zcma)ATaz0{74DuHU02$hWqX~AGeD|{LM%ZXiWE?g*ujpeunD$vK@SYG>egy@=c07C zcC9U$qF7UT0$1_uO6|gv->E$C%&(YNp7Iy+kno)zX)iKGF;&y*={|ktobR0Tb&o!5 zHtP)6KmXN@%Il2%n;yv^_cic3kk zS57LuN>c4rlUlE4$IEd&S?Vp>ekE=s&0f>?tMPKO(p$0pTD+R9_12R0-nxCR#}|@T zuf>^Mkr&0%6Gt>e^O4uPBwbk%%U76KdBVl2SbO9h^4{g!OstCwkC?b1mk!syi>1-18$^Iy0l;o5&3>kKyVju2Xp%Q09h{oDMs7H07*{Q{@L&@`Q!D8A zY543_#RCoMm(d*51^!ol**Jv{7iEJ*5{Y?cr-$!wG>6L0!gy^BKb#-vXaB=e3`3D=syER zg(Pt4f1r^Jb<28=;5}D*!V&Hh=QB1y^@`G?Mz172jFwSj6;VZvmGNAWRn%AwV|B5F zu^PszSlM{uil$h`NL^f^F}WlgvMHQ_3wZ}s>Dj6*^_K0MNCQ=-*ITh#S7%yV;v&Y@ zW?C;n>t(Tlk@e4zy}b(v&2xG4UO12^6%*ce8MD~E?NfpHTPQLyi_`uv>JR-$`x;Ap zllfyUedH{HM-ilenouFtebkWbqlPDb5>BkTG&BB|oW(vui2O|X`zkUNu1?*`+#gB> z3PEIjq&c!9OGQK!Fw?)E%@aW)eV`mBkP4_xMHl*%31RoIqz6S&5xuD_ z?nYW8JG<9kf1};;{f~hNIW@@O*f88HEDJ^9Rv7znwjK2$0}GRI7cU_SDxSA8Gqf>) zuNh%Si3)$vC3V)Yf|`mdDIEC;76ZXS^nfrzL@Xvc5&#^Y(ZD%qXVBjQdNf(UN#d_j zMTYPOC|dhyT1&Mj5w|Fg{Va_q6n%o72$I&H0uOQzeb{NWTHrst`yO=K6w1ciLxjY7 zMQZy7c3>|je>ejBW&sCq2@d0q>Gehd^aO&gRVl2p&5c^$VdqhWj%gm`f29BXvuN=aXex_j1H+AU9Z%s230 z*b$%dWB$-F&eRcb`A^OjcF3pBHowdsy2jn{c1lyWQy%cC`?>R%!uc;tSlh0s8h-M! zO#7LTxfi2!b0^ceHww)#_ja-%hSYKp#l z%yoz2ZM;MF_)X4C3DVA?^Z45jy{UIpKE{^ouphEtv%Bo?=lb{T*e4h(X0ZuF&r^5v zt5RnR$EMs-TLADCn4-Qz4ejHA@&VDG-RE*I8_6{Hh&tM(0>Cy2H4_|TsImhKbB)v$ zT|QWZ(*-_1$0AJ!9_G|}m2YqlZG#`Iore;VZMQJPBkcuaOX@QDqsv-LX0XRwIk0Bjfq=e>h9Qir z2x;AGaOWGxalS@dZt!&k_XX#mbw0es;l3DU z4i6^<a+P>KZqb=kl z=KE*KnzFHm+h*V|(K)foA)0=?YV*t%@!3qUbNIkQhGCA%oig%#raW~v)%Tzg2-%{f zqoz#=`t(S$J&EG5Bxe2kZ{C!JjHQtR3$^{?1VvYQ3-@9y3c+)p5dtT*^${LUJ^Q~2 zYQt8ZO9XY6mgZDMu~M%>b-TR4JL(!ni#q7g{EV)xr{AZkA5gOpyed&IanCL$?dR87 zep%d`=E6Xc)aD+oe3oJe2D)v4Daq$B6dY|+kHR}kJ@pPm)D1M}Y<`zUU#4c!W+arg z*too}M%k^pK$!OG zytFkjD6#8g4;6@_50>1;XQ85eR^+R?MGbkbZc}pyP2QZH1Nt(K=Vt-y(2FEuPX(9J z^Kn~3sB&+uRa&jK>g$)-Hx75OH-Q?yZj@Imt(qd)_7(Lj{1k~@r{gmI4Zs(yqAIiI z}i7Si_A(0l%@)ck9* zL%bJjMU{{jFPncocg7})b;q7fC)&tF5j9&9%{Q#B1<54ReM{?4?5tXu>b%7 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/download.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/download.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..585c36e62a39c742c408c0550e1094cdf5f92f63 GIT binary patch literal 5506 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 A{{R30 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8507a619c7ca64e55bdccc27bcf14bb4b55ff89 GIT binary patch literal 8382 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 KnssaT$o~S)M=7fS literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/session.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/network/__pycache__/session.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e849468cc51c29de0631c22ac0be91bf6ccf6124 GIT binary patch literal 12640 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 jwip?Czd3gmLrbY#sUak_gqDrxIT^g3b!NtC zV>uk4*Ge3?wMga2jWZ_>{VV3m1@0UW2g;j`T}8}l-~4)SX5ROGGrLPm6@um07qR%e zPRQ@BbFq}La}$3(L9U$ng}7>3;t+ddBj?UsN-8OnWj zjlcpAZxCMUR>0?FzVM9k3SWHecNd}3r{F^s7%bsTx$j*gy!y=JOT6|t=+=07hcxPc z;{K$;tlt(rV;63L1eKaxHK;A$2R+KA25hf%z&y;=U@MmrdPd}OTd7=4>YKeDj7)P| z_Hr)dp)Csz(g<^EOSiW-+uQed1R*}@xGfZ}XF~;MtYnY6jc3Kfx{Uu8{`v(PPS2^r8~lV#U_?s4`LJ!5<1_>NDEKd7jil(=s;>$%wnIJ-||=*G?%_puIq;*kMW zU-4i-)Hf1Qm1}PNDdFLip)FxAd`f-{&ZvBk2Nz;~;AM>YrHEOb#Vq_kW=hp>rZlBf z!YfbMx0DmacClm0Bp921b7N8wI&W@ly#GP-UGY7pF8)F@sYhFxK)9j;%9T_u9!10?5^`TnA!Jmn`jMYDn;-h>BF-%|}AdA~@ z0$Dy9Vh-`$^gzd>Q*)Hd=4idyR{#eBnntC)0jT%{V zon^jH)e3rGYKvIeO7Cba2ZWD8ZuP=%dWDkRUnDRnBGGX3%Kotf`D4~S&@gnOt8f3ki{AQ({^DL5X3 zHaePLw6~*?%1`hHMdk=1o|-f&4y;?BAn~x1LHL+E5#$erRi(#wc+x zHgL`|-nW#`GFMkI9~%^_6-=I+D{izt7gTb-*@rHFhZ^x23n{zG{-~@~Y0ax5(2OdF zd82Mab2`EsuN;KRVb^^fDQqQ}8_!j1o?L5);>)!1(ZjA59&?gg@W?$L_?YNIfXcm! z*28pcJxNk2bCju@O_J_w?R$HjyZ1KR$=2=N&S%Nar`_#>TI=C*s^$*i4t60qCs*c_!L+8m_*HF#Do%Ji>>h&wD zUw*!JP1%Df+I6Wqu?F&f&bLg z*23$#kb~UnNi?kPt>27iz&@RBYycl=Cte(gcQwQp_yU5MlE7dTF>Qhc zHaN^=)?@1t*`Um3&ckNlvKDi}Vjei)vD#_GKJ^L#sI(1|*CUjo_P|6tbHjw5aQJp8+U2S+}fAV9rpes!&Dsal*d*a@dRQLdN+ zr5aNNiA54*oPu&V$SKc8noG}yjT3Y6hzmH8WS<<+(mbS;NMm9iSS3BAdsb;p?2?2| zX`pl&!T9J}*HgBb7QmGkhH)0lFjNkDfFw|Mfhhpxp*o*AVl+lBC^uY8yl~-X*H)H* zWP{hy0p8G}i6W@aL&D=yJfWE2aFj$sgy9qNbhkgwQ|QACwqi*7pTRaHd7-J+zYz^Z zzu1-IJX(x#|_Wyp6t{!AE!~h}obG>e1QR|L3~*3>2;@#%n0L2G^&0I^;3ZRk`CRV+kVI z;BapWLWaYf?J9R0_%Ij1TWCGfS$Z~fpU}@LKH?`7l9W`^eM7XQ@qm_OVwSWt7_MV&)E<(#9-0v&&fLMojlyv~wq3>6B*aYJPuj?Rm0Rdi$97(!w13B{`^<-lTCz zZ(+7ABAZ)2|B;ft8YQ#yWo^$8-X(Mlxlq<5W1uG!H*+M{FoNYD3p(!oMsg(`3b#X z6_^U{XoEMj-m3OO4%o#qB(ce(`&^fe@{ZP=B;VZB%pdVs#-k`v^?54_M>&J8&rj;> zUen^77O!hj-L5M`3T2`f{7t=j0fjPQXT&wF=Hjega~GUAkBWE?1#w$+8Tn=);~bRL z=MTrxT8p-+Pc2--oj)ymcIG9FTwK_tSM|59>o$v0z`FIrFwNOC(ep+a-kV0rV#f_b zmXE@azl{!X%|&Gm^E{~vf$Q$U-`9)HMSYGSNznRvET^=I4v1^w>STxx3XfVg`5QwZ z_@TyM?I!u%Fcv{k=V@i#JB06AVst^fc4 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/auth.py b/venv/lib/python3.8/site-packages/pip/_internal/network/auth.py new file mode 100644 index 000000000..1a2606ed0 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/auth.py @@ -0,0 +1,566 @@ +"""Network Authentication Helpers + +Contains interface (MultiDomainBasicAuth) and associated glue code for +providing credentials in the context of network requests. +""" + +import logging +import os +import shutil +import subprocess +import sysconfig +import typing +import urllib.parse +from abc import ABC, abstractmethod +from functools import lru_cache +from os.path import commonprefix +from pathlib import Path +from typing import Any, Dict, List, NamedTuple, Optional, Tuple + +from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth +from pip._vendor.requests.models import Request, Response +from pip._vendor.requests.utils import get_netrc_auth + +from pip._internal.utils.logging import getLogger +from pip._internal.utils.misc import ( + ask, + ask_input, + ask_password, + remove_auth_from_url, + split_auth_netloc_from_url, +) +from pip._internal.vcs.versioncontrol import AuthInfo + +logger = getLogger(__name__) + +KEYRING_DISABLED = False + + +class Credentials(NamedTuple): + url: str + username: str + password: str + + +class KeyRingBaseProvider(ABC): + """Keyring base provider interface""" + + has_keyring: bool + + @abstractmethod + def get_auth_info( + self, url: str, username: Optional[str] + ) -> Optional[AuthInfo]: ... + + @abstractmethod + def save_auth_info(self, url: str, username: str, password: str) -> None: ... + + +class KeyRingNullProvider(KeyRingBaseProvider): + """Keyring null provider""" + + has_keyring = False + + def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]: + return None + + def save_auth_info(self, url: str, username: str, password: str) -> None: + return None + + +class KeyRingPythonProvider(KeyRingBaseProvider): + """Keyring interface which uses locally imported `keyring`""" + + has_keyring = True + + def __init__(self) -> None: + import keyring + + self.keyring = keyring + + def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]: + # Support keyring's get_credential interface which supports getting + # credentials without a username. This is only available for + # keyring>=15.2.0. + if hasattr(self.keyring, "get_credential"): + logger.debug("Getting credentials from keyring for %s", url) + cred = self.keyring.get_credential(url, username) + if cred is not None: + return cred.username, cred.password + return None + + if username is not None: + logger.debug("Getting password from keyring for %s", url) + password = self.keyring.get_password(url, username) + if password: + return username, password + return None + + def save_auth_info(self, url: str, username: str, password: str) -> None: + self.keyring.set_password(url, username, password) + + +class KeyRingCliProvider(KeyRingBaseProvider): + """Provider which uses `keyring` cli + + Instead of calling the keyring package installed alongside pip + we call keyring on the command line which will enable pip to + use which ever installation of keyring is available first in + PATH. + """ + + has_keyring = True + + def __init__(self, cmd: str) -> None: + self.keyring = cmd + + def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]: + # This is the default implementation of keyring.get_credential + # https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139 + if username is not None: + password = self._get_password(url, username) + if password is not None: + return username, password + return None + + def save_auth_info(self, url: str, username: str, password: str) -> None: + return self._set_password(url, username, password) + + def _get_password(self, service_name: str, username: str) -> Optional[str]: + """Mirror the implementation of keyring.get_password using cli""" + if self.keyring is None: + return None + + cmd = [self.keyring, "get", service_name, username] + env = os.environ.copy() + env["PYTHONIOENCODING"] = "utf-8" + res = subprocess.run( + cmd, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + env=env, + ) + if res.returncode: + return None + return res.stdout.decode("utf-8").strip(os.linesep) + + def _set_password(self, service_name: str, username: str, password: str) -> None: + """Mirror the implementation of keyring.set_password using cli""" + if self.keyring is None: + return None + env = os.environ.copy() + env["PYTHONIOENCODING"] = "utf-8" + subprocess.run( + [self.keyring, "set", service_name, username], + input=f"{password}{os.linesep}".encode(), + env=env, + check=True, + ) + return None + + +@lru_cache(maxsize=None) +def get_keyring_provider(provider: str) -> KeyRingBaseProvider: + logger.verbose("Keyring provider requested: %s", provider) + + # keyring has previously failed and been disabled + if KEYRING_DISABLED: + provider = "disabled" + if provider in ["import", "auto"]: + try: + impl = KeyRingPythonProvider() + logger.verbose("Keyring provider set: import") + return impl + except ImportError: + pass + except Exception as exc: + # In the event of an unexpected exception + # we should warn the user + msg = "Installed copy of keyring fails with exception %s" + if provider == "auto": + msg = msg + ", trying to find a keyring executable as a fallback" + logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + if provider in ["subprocess", "auto"]: + cli = shutil.which("keyring") + if cli and cli.startswith(sysconfig.get_path("scripts")): + # all code within this function is stolen from shutil.which implementation + @typing.no_type_check + def PATH_as_shutil_which_determines_it() -> str: + path = os.environ.get("PATH", None) + if path is None: + try: + path = os.confstr("CS_PATH") + except (AttributeError, ValueError): + # os.confstr() or CS_PATH is not available + path = os.defpath + # bpo-35755: Don't use os.defpath if the PATH environment variable is + # set to an empty string + + return path + + scripts = Path(sysconfig.get_path("scripts")) + + paths = [] + for path in PATH_as_shutil_which_determines_it().split(os.pathsep): + p = Path(path) + try: + if not p.samefile(scripts): + paths.append(path) + except FileNotFoundError: + pass + + path = os.pathsep.join(paths) + + cli = shutil.which("keyring", path=path) + + if cli: + logger.verbose("Keyring provider set: subprocess with executable %s", cli) + return KeyRingCliProvider(cli) + + logger.verbose("Keyring provider set: disabled") + return KeyRingNullProvider() + + +class MultiDomainBasicAuth(AuthBase): + def __init__( + self, + prompting: bool = True, + index_urls: Optional[List[str]] = None, + keyring_provider: str = "auto", + ) -> None: + self.prompting = prompting + self.index_urls = index_urls + self.keyring_provider = keyring_provider # type: ignore[assignment] + self.passwords: Dict[str, AuthInfo] = {} + # When the user is prompted to enter credentials and keyring is + # available, we will offer to save them. If the user accepts, + # this value is set to the credentials they entered. After the + # request authenticates, the caller should call + # ``save_credentials`` to save these. + self._credentials_to_save: Optional[Credentials] = None + + @property + def keyring_provider(self) -> KeyRingBaseProvider: + return get_keyring_provider(self._keyring_provider) + + @keyring_provider.setter + def keyring_provider(self, provider: str) -> None: + # The free function get_keyring_provider has been decorated with + # functools.cache. If an exception occurs in get_keyring_auth that + # cache will be cleared and keyring disabled, take that into account + # if you want to remove this indirection. + self._keyring_provider = provider + + @property + def use_keyring(self) -> bool: + # We won't use keyring when --no-input is passed unless + # a specific provider is requested because it might require + # user interaction + return self.prompting or self._keyring_provider not in ["auto", "disabled"] + + def _get_keyring_auth( + self, + url: Optional[str], + username: Optional[str], + ) -> Optional[AuthInfo]: + """Return the tuple auth for a given url from keyring.""" + # Do nothing if no url was provided + if not url: + return None + + try: + return self.keyring_provider.get_auth_info(url, username) + except Exception as exc: + # Log the full exception (with stacktrace) at debug, so it'll only + # show up when running in verbose mode. + logger.debug("Keyring is skipped due to an exception", exc_info=True) + # Always log a shortened version of the exception. + logger.warning( + "Keyring is skipped due to an exception: %s", + str(exc), + ) + global KEYRING_DISABLED + KEYRING_DISABLED = True + get_keyring_provider.cache_clear() + return None + + def _get_index_url(self, url: str) -> Optional[str]: + """Return the original index URL matching the requested URL. + + Cached or dynamically generated credentials may work against + the original index URL rather than just the netloc. + + The provided url should have had its username and password + removed already. If the original index url had credentials then + they will be included in the return value. + + Returns None if no matching index was found, or if --no-index + was specified by the user. + """ + if not url or not self.index_urls: + return None + + url = remove_auth_from_url(url).rstrip("/") + "/" + parsed_url = urllib.parse.urlsplit(url) + + candidates = [] + + for index in self.index_urls: + index = index.rstrip("/") + "/" + parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index)) + if parsed_url == parsed_index: + return index + + if parsed_url.netloc != parsed_index.netloc: + continue + + candidate = urllib.parse.urlsplit(index) + candidates.append(candidate) + + if not candidates: + return None + + candidates.sort( + reverse=True, + key=lambda candidate: commonprefix( + [ + parsed_url.path, + candidate.path, + ] + ).rfind("/"), + ) + + return urllib.parse.urlunsplit(candidates[0]) + + def _get_new_credentials( + self, + original_url: str, + *, + allow_netrc: bool = True, + allow_keyring: bool = False, + ) -> AuthInfo: + """Find and return credentials for the specified URL.""" + # Split the credentials and netloc from the url. + url, netloc, url_user_password = split_auth_netloc_from_url( + original_url, + ) + + # Start with the credentials embedded in the url + username, password = url_user_password + if username is not None and password is not None: + logger.debug("Found credentials in url for %s", netloc) + return url_user_password + + # Find a matching index url for this request + index_url = self._get_index_url(url) + if index_url: + # Split the credentials from the url. + index_info = split_auth_netloc_from_url(index_url) + if index_info: + index_url, _, index_url_user_password = index_info + logger.debug("Found index url %s", index_url) + + # If an index URL was found, try its embedded credentials + if index_url and index_url_user_password[0] is not None: + username, password = index_url_user_password + if username is not None and password is not None: + logger.debug("Found credentials in index url for %s", netloc) + return index_url_user_password + + # Get creds from netrc if we still don't have them + if allow_netrc: + netrc_auth = get_netrc_auth(original_url) + if netrc_auth: + logger.debug("Found credentials in netrc for %s", netloc) + return netrc_auth + + # If we don't have a password and keyring is available, use it. + if allow_keyring: + # The index url is more specific than the netloc, so try it first + # fmt: off + kr_auth = ( + self._get_keyring_auth(index_url, username) or + self._get_keyring_auth(netloc, username) + ) + # fmt: on + if kr_auth: + logger.debug("Found credentials in keyring for %s", netloc) + return kr_auth + + return username, password + + def _get_url_and_credentials( + self, original_url: str + ) -> Tuple[str, Optional[str], Optional[str]]: + """Return the credentials to use for the provided URL. + + If allowed, netrc and keyring may be used to obtain the + correct credentials. + + Returns (url_without_credentials, username, password). Note + that even if the original URL contains credentials, this + function may return a different username and password. + """ + url, netloc, _ = split_auth_netloc_from_url(original_url) + + # Try to get credentials from original url + username, password = self._get_new_credentials(original_url) + + # If credentials not found, use any stored credentials for this netloc. + # Do this if either the username or the password is missing. + # This accounts for the situation in which the user has specified + # the username in the index url, but the password comes from keyring. + if (username is None or password is None) and netloc in self.passwords: + un, pw = self.passwords[netloc] + # It is possible that the cached credentials are for a different username, + # in which case the cache should be ignored. + if username is None or username == un: + username, password = un, pw + + if username is not None or password is not None: + # Convert the username and password if they're None, so that + # this netloc will show up as "cached" in the conditional above. + # Further, HTTPBasicAuth doesn't accept None, so it makes sense to + # cache the value that is going to be used. + username = username or "" + password = password or "" + + # Store any acquired credentials. + self.passwords[netloc] = (username, password) + + assert ( + # Credentials were found + (username is not None and password is not None) + # Credentials were not found + or (username is None and password is None) + ), f"Could not load credentials from url: {original_url}" + + return url, username, password + + def __call__(self, req: Request) -> Request: + # Get credentials for this request + url, username, password = self._get_url_and_credentials(req.url) + + # Set the url of the request to the url without any credentials + req.url = url + + if username is not None and password is not None: + # Send the basic auth with this request + req = HTTPBasicAuth(username, password)(req) + + # Attach a hook to handle 401 responses + req.register_hook("response", self.handle_401) + + return req + + # Factored out to allow for easy patching in tests + def _prompt_for_password( + self, netloc: str + ) -> Tuple[Optional[str], Optional[str], bool]: + username = ask_input(f"User for {netloc}: ") if self.prompting else None + if not username: + return None, None, False + if self.use_keyring: + auth = self._get_keyring_auth(netloc, username) + if auth and auth[0] is not None and auth[1] is not None: + return auth[0], auth[1], False + password = ask_password("Password: ") + return username, password, True + + # Factored out to allow for easy patching in tests + def _should_save_password_to_keyring(self) -> bool: + if ( + not self.prompting + or not self.use_keyring + or not self.keyring_provider.has_keyring + ): + return False + return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y" + + def handle_401(self, resp: Response, **kwargs: Any) -> Response: + # We only care about 401 responses, anything else we want to just + # pass through the actual response + if resp.status_code != 401: + return resp + + username, password = None, None + + # Query the keyring for credentials: + if self.use_keyring: + username, password = self._get_new_credentials( + resp.url, + allow_netrc=False, + allow_keyring=True, + ) + + # We are not able to prompt the user so simply return the response + if not self.prompting and not username and not password: + return resp + + parsed = urllib.parse.urlparse(resp.url) + + # Prompt the user for a new username and password + save = False + if not username and not password: + username, password, save = self._prompt_for_password(parsed.netloc) + + # Store the new username and password to use for future requests + self._credentials_to_save = None + if username is not None and password is not None: + self.passwords[parsed.netloc] = (username, password) + + # Prompt to save the password to keyring + if save and self._should_save_password_to_keyring(): + self._credentials_to_save = Credentials( + url=parsed.netloc, + username=username, + password=password, + ) + + # Consume content and release the original connection to allow our new + # request to reuse the same one. + # The result of the assignment isn't used, it's just needed to consume + # the content. + _ = resp.content + resp.raw.release_conn() + + # Add our new username and password to the request + req = HTTPBasicAuth(username or "", password or "")(resp.request) + req.register_hook("response", self.warn_on_401) + + # On successful request, save the credentials that were used to + # keyring. (Note that if the user responded "no" above, this member + # is not set and nothing will be saved.) + if self._credentials_to_save: + req.register_hook("response", self.save_credentials) + + # Send our new request + new_resp = resp.connection.send(req, **kwargs) + new_resp.history.append(resp) + + return new_resp + + def warn_on_401(self, resp: Response, **kwargs: Any) -> None: + """Response callback to warn about incorrect credentials.""" + if resp.status_code == 401: + logger.warning( + "401 Error, Credentials not correct for %s", + resp.request.url, + ) + + def save_credentials(self, resp: Response, **kwargs: Any) -> None: + """Response callback to save credentials on success.""" + assert ( + self.keyring_provider.has_keyring + ), "should never reach here without keyring" + + creds = self._credentials_to_save + self._credentials_to_save = None + if creds and resp.status_code < 400: + try: + logger.info("Saving credentials to keyring") + self.keyring_provider.save_auth_info( + creds.url, creds.username, creds.password + ) + except Exception: + logger.exception("Failed to save credentials") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/cache.py b/venv/lib/python3.8/site-packages/pip/_internal/network/cache.py new file mode 100644 index 000000000..fca04e694 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/cache.py @@ -0,0 +1,118 @@ +"""HTTP cache implementation. +""" + +import os +from contextlib import contextmanager +from datetime import datetime +from typing import BinaryIO, Generator, Optional, Union + +from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache +from pip._vendor.cachecontrol.caches import SeparateBodyFileCache +from pip._vendor.requests.models import Response + +from pip._internal.utils.filesystem import adjacent_tmp_file, replace +from pip._internal.utils.misc import ensure_dir + + +def is_from_cache(response: Response) -> bool: + return getattr(response, "from_cache", False) + + +@contextmanager +def suppressed_cache_errors() -> Generator[None, None, None]: + """If we can't access the cache then we can just skip caching and process + requests as if caching wasn't enabled. + """ + try: + yield + except OSError: + pass + + +class SafeFileCache(SeparateBodyBaseCache): + """ + A file based cache which is safe to use even when the target directory may + not be accessible or writable. + + There is a race condition when two processes try to write and/or read the + same entry at the same time, since each entry consists of two separate + files (https://github.com/psf/cachecontrol/issues/324). We therefore have + additional logic that makes sure that both files to be present before + returning an entry; this fixes the read side of the race condition. + + For the write side, we assume that the server will only ever return the + same data for the same URL, which ought to be the case for files pip is + downloading. PyPI does not have a mechanism to swap out a wheel for + another wheel, for example. If this assumption is not true, the + CacheControl issue will need to be fixed. + """ + + def __init__(self, directory: str) -> None: + assert directory is not None, "Cache directory must not be None." + super().__init__() + self.directory = directory + + def _get_cache_path(self, name: str) -> str: + # From cachecontrol.caches.file_cache.FileCache._fn, brought into our + # class for backwards-compatibility and to avoid using a non-public + # method. + hashed = SeparateBodyFileCache.encode(name) + parts = list(hashed[:5]) + [hashed] + return os.path.join(self.directory, *parts) + + def get(self, key: str) -> Optional[bytes]: + # The cache entry is only valid if both metadata and body exist. + metadata_path = self._get_cache_path(key) + body_path = metadata_path + ".body" + if not (os.path.exists(metadata_path) and os.path.exists(body_path)): + return None + with suppressed_cache_errors(): + with open(metadata_path, "rb") as f: + return f.read() + + def _write(self, path: str, data: bytes) -> None: + with suppressed_cache_errors(): + ensure_dir(os.path.dirname(path)) + + with adjacent_tmp_file(path) as f: + f.write(data) + # Inherit the read/write permissions of the cache directory + # to enable multi-user cache use-cases. + mode = ( + os.stat(self.directory).st_mode + & 0o666 # select read/write permissions of cache directory + | 0o600 # set owner read/write permissions + ) + # Change permissions only if there is no risk of following a symlink. + if os.chmod in os.supports_fd: + os.chmod(f.fileno(), mode) + elif os.chmod in os.supports_follow_symlinks: + os.chmod(f.name, mode, follow_symlinks=False) + + replace(f.name, path) + + def set( + self, key: str, value: bytes, expires: Union[int, datetime, None] = None + ) -> None: + path = self._get_cache_path(key) + self._write(path, value) + + def delete(self, key: str) -> None: + path = self._get_cache_path(key) + with suppressed_cache_errors(): + os.remove(path) + with suppressed_cache_errors(): + os.remove(path + ".body") + + def get_body(self, key: str) -> Optional[BinaryIO]: + # The cache entry is only valid if both metadata and body exist. + metadata_path = self._get_cache_path(key) + body_path = metadata_path + ".body" + if not (os.path.exists(metadata_path) and os.path.exists(body_path)): + return None + with suppressed_cache_errors(): + return open(body_path, "rb") + + def set_body(self, key: str, body: bytes) -> None: + path = self._get_cache_path(key) + ".body" + self._write(path, body) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/download.py b/venv/lib/python3.8/site-packages/pip/_internal/network/download.py new file mode 100644 index 000000000..5c3bce3d2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/download.py @@ -0,0 +1,187 @@ +"""Download files with progress indicators. +""" + +import email.message +import logging +import mimetypes +import os +from typing import Iterable, Optional, Tuple + +from pip._vendor.requests.models import Response + +from pip._internal.cli.progress_bars import get_download_progress_renderer +from pip._internal.exceptions import NetworkConnectionError +from pip._internal.models.index import PyPI +from pip._internal.models.link import Link +from pip._internal.network.cache import is_from_cache +from pip._internal.network.session import PipSession +from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks +from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext + +logger = logging.getLogger(__name__) + + +def _get_http_response_size(resp: Response) -> Optional[int]: + try: + return int(resp.headers["content-length"]) + except (ValueError, KeyError, TypeError): + return None + + +def _prepare_download( + resp: Response, + link: Link, + progress_bar: str, +) -> Iterable[bytes]: + total_length = _get_http_response_size(resp) + + if link.netloc == PyPI.file_storage_domain: + url = link.show_url + else: + url = link.url_without_fragment + + logged_url = redact_auth_from_url(url) + + if total_length: + logged_url = f"{logged_url} ({format_size(total_length)})" + + if is_from_cache(resp): + logger.info("Using cached %s", logged_url) + else: + logger.info("Downloading %s", logged_url) + + if logger.getEffectiveLevel() > logging.INFO: + show_progress = False + elif is_from_cache(resp): + show_progress = False + elif not total_length: + show_progress = True + elif total_length > (512 * 1024): + show_progress = True + else: + show_progress = False + + chunks = response_chunks(resp) + + if not show_progress: + return chunks + + renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length) + return renderer(chunks) + + +def sanitize_content_filename(filename: str) -> str: + """ + Sanitize the "filename" value from a Content-Disposition header. + """ + return os.path.basename(filename) + + +def parse_content_disposition(content_disposition: str, default_filename: str) -> str: + """ + Parse the "filename" value from a Content-Disposition header, and + return the default filename if the result is empty. + """ + m = email.message.Message() + m["content-type"] = content_disposition + filename = m.get_param("filename") + if filename: + # We need to sanitize the filename to prevent directory traversal + # in case the filename contains ".." path parts. + filename = sanitize_content_filename(str(filename)) + return filename or default_filename + + +def _get_http_response_filename(resp: Response, link: Link) -> str: + """Get an ideal filename from the given HTTP response, falling back to + the link filename if not provided. + """ + filename = link.filename # fallback + # Have a look at the Content-Disposition header for a better guess + content_disposition = resp.headers.get("content-disposition") + if content_disposition: + filename = parse_content_disposition(content_disposition, filename) + ext: Optional[str] = splitext(filename)[1] + if not ext: + ext = mimetypes.guess_extension(resp.headers.get("content-type", "")) + if ext: + filename += ext + if not ext and link.url != resp.url: + ext = os.path.splitext(resp.url)[1] + if ext: + filename += ext + return filename + + +def _http_get_download(session: PipSession, link: Link) -> Response: + target_url = link.url.split("#", 1)[0] + resp = session.get(target_url, headers=HEADERS, stream=True) + raise_for_status(resp) + return resp + + +class Downloader: + def __init__( + self, + session: PipSession, + progress_bar: str, + ) -> None: + self._session = session + self._progress_bar = progress_bar + + def __call__(self, link: Link, location: str) -> Tuple[str, str]: + """Download the file given by link into location.""" + try: + resp = _http_get_download(self._session, link) + except NetworkConnectionError as e: + assert e.response is not None + logger.critical( + "HTTP error %s while getting %s", e.response.status_code, link + ) + raise + + filename = _get_http_response_filename(resp, link) + filepath = os.path.join(location, filename) + + chunks = _prepare_download(resp, link, self._progress_bar) + with open(filepath, "wb") as content_file: + for chunk in chunks: + content_file.write(chunk) + content_type = resp.headers.get("Content-Type", "") + return filepath, content_type + + +class BatchDownloader: + def __init__( + self, + session: PipSession, + progress_bar: str, + ) -> None: + self._session = session + self._progress_bar = progress_bar + + def __call__( + self, links: Iterable[Link], location: str + ) -> Iterable[Tuple[Link, Tuple[str, str]]]: + """Download the files given by links into location.""" + for link in links: + try: + resp = _http_get_download(self._session, link) + except NetworkConnectionError as e: + assert e.response is not None + logger.critical( + "HTTP error %s while getting %s", + e.response.status_code, + link, + ) + raise + + filename = _get_http_response_filename(resp, link) + filepath = os.path.join(location, filename) + + chunks = _prepare_download(resp, link, self._progress_bar) + with open(filepath, "wb") as content_file: + for chunk in chunks: + content_file.write(chunk) + content_type = resp.headers.get("Content-Type", "") + yield link, (filepath, content_type) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/lazy_wheel.py b/venv/lib/python3.8/site-packages/pip/_internal/network/lazy_wheel.py new file mode 100644 index 000000000..03f883c1f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/lazy_wheel.py @@ -0,0 +1,210 @@ +"""Lazy ZIP over HTTP""" + +__all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"] + +from bisect import bisect_left, bisect_right +from contextlib import contextmanager +from tempfile import NamedTemporaryFile +from typing import Any, Dict, Generator, List, Optional, Tuple +from zipfile import BadZipFile, ZipFile + +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response + +from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution +from pip._internal.network.session import PipSession +from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks + + +class HTTPRangeRequestUnsupported(Exception): + pass + + +def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution: + """Return a distribution object from the given wheel URL. + + This uses HTTP range requests to only fetch the portion of the wheel + containing metadata, just enough for the object to be constructed. + If such requests are not supported, HTTPRangeRequestUnsupported + is raised. + """ + with LazyZipOverHTTP(url, session) as zf: + # For read-only ZIP files, ZipFile only needs methods read, + # seek, seekable and tell, not the whole IO protocol. + wheel = MemoryWheel(zf.name, zf) # type: ignore + # After context manager exit, wheel.name + # is an invalid file by intention. + return get_wheel_distribution(wheel, canonicalize_name(name)) + + +class LazyZipOverHTTP: + """File-like object mapped to a ZIP file over HTTP. + + This uses HTTP range requests to lazily fetch the file's content, + which is supposed to be fed to ZipFile. If such requests are not + supported by the server, raise HTTPRangeRequestUnsupported + during initialization. + """ + + def __init__( + self, url: str, session: PipSession, chunk_size: int = CONTENT_CHUNK_SIZE + ) -> None: + head = session.head(url, headers=HEADERS) + raise_for_status(head) + assert head.status_code == 200 + self._session, self._url, self._chunk_size = session, url, chunk_size + self._length = int(head.headers["Content-Length"]) + self._file = NamedTemporaryFile() + self.truncate(self._length) + self._left: List[int] = [] + self._right: List[int] = [] + if "bytes" not in head.headers.get("Accept-Ranges", "none"): + raise HTTPRangeRequestUnsupported("range request is not supported") + self._check_zip() + + @property + def mode(self) -> str: + """Opening mode, which is always rb.""" + return "rb" + + @property + def name(self) -> str: + """Path to the underlying file.""" + return self._file.name + + def seekable(self) -> bool: + """Return whether random access is supported, which is True.""" + return True + + def close(self) -> None: + """Close the file.""" + self._file.close() + + @property + def closed(self) -> bool: + """Whether the file is closed.""" + return self._file.closed + + def read(self, size: int = -1) -> bytes: + """Read up to size bytes from the object and return them. + + As a convenience, if size is unspecified or -1, + all bytes until EOF are returned. Fewer than + size bytes may be returned if EOF is reached. + """ + download_size = max(size, self._chunk_size) + start, length = self.tell(), self._length + stop = length if size < 0 else min(start + download_size, length) + start = max(0, stop - download_size) + self._download(start, stop - 1) + return self._file.read(size) + + def readable(self) -> bool: + """Return whether the file is readable, which is True.""" + return True + + def seek(self, offset: int, whence: int = 0) -> int: + """Change stream position and return the new absolute position. + + Seek to offset relative position indicated by whence: + * 0: Start of stream (the default). pos should be >= 0; + * 1: Current position - pos may be negative; + * 2: End of stream - pos usually negative. + """ + return self._file.seek(offset, whence) + + def tell(self) -> int: + """Return the current position.""" + return self._file.tell() + + def truncate(self, size: Optional[int] = None) -> int: + """Resize the stream to the given size in bytes. + + If size is unspecified resize to the current position. + The current stream position isn't changed. + + Return the new file size. + """ + return self._file.truncate(size) + + def writable(self) -> bool: + """Return False.""" + return False + + def __enter__(self) -> "LazyZipOverHTTP": + self._file.__enter__() + return self + + def __exit__(self, *exc: Any) -> None: + self._file.__exit__(*exc) + + @contextmanager + def _stay(self) -> Generator[None, None, None]: + """Return a context manager keeping the position. + + At the end of the block, seek back to original position. + """ + pos = self.tell() + try: + yield + finally: + self.seek(pos) + + def _check_zip(self) -> None: + """Check and download until the file is a valid ZIP.""" + end = self._length - 1 + for start in reversed(range(0, end, self._chunk_size)): + self._download(start, end) + with self._stay(): + try: + # For read-only ZIP files, ZipFile only needs + # methods read, seek, seekable and tell. + ZipFile(self) + except BadZipFile: + pass + else: + break + + def _stream_response( + self, start: int, end: int, base_headers: Dict[str, str] = HEADERS + ) -> Response: + """Return HTTP response to a range request from start to end.""" + headers = base_headers.copy() + headers["Range"] = f"bytes={start}-{end}" + # TODO: Get range requests to be correctly cached + headers["Cache-Control"] = "no-cache" + return self._session.get(self._url, headers=headers, stream=True) + + def _merge( + self, start: int, end: int, left: int, right: int + ) -> Generator[Tuple[int, int], None, None]: + """Return a generator of intervals to be fetched. + + Args: + start (int): Start of needed interval + end (int): End of needed interval + left (int): Index of first overlapping downloaded data + right (int): Index after last overlapping downloaded data + """ + lslice, rslice = self._left[left:right], self._right[left:right] + i = start = min([start] + lslice[:1]) + end = max([end] + rslice[-1:]) + for j, k in zip(lslice, rslice): + if j > i: + yield i, j - 1 + i = k + 1 + if i <= end: + yield i, end + self._left[left:right], self._right[left:right] = [start], [end] + + def _download(self, start: int, end: int) -> None: + """Download bytes from start to end inclusively.""" + with self._stay(): + left = bisect_left(self._right, start) + right = bisect_right(self._left, end) + for start, end in self._merge(start, end, left, right): + response = self._stream_response(start, end) + response.raise_for_status() + self.seek(start) + for chunk in response_chunks(response, self._chunk_size): + self._file.write(chunk) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/session.py b/venv/lib/python3.8/site-packages/pip/_internal/network/session.py new file mode 100644 index 000000000..5e10f8f56 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/session.py @@ -0,0 +1,523 @@ +"""PipSession and supporting code, containing all pip-specific +network request configuration and behavior. +""" + +import email.utils +import functools +import io +import ipaddress +import json +import logging +import mimetypes +import os +import platform +import shutil +import subprocess +import sys +import urllib.parse +import warnings +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generator, + List, + Mapping, + Optional, + Sequence, + Tuple, + Union, +) + +from pip._vendor import requests, urllib3 +from pip._vendor.cachecontrol import CacheControlAdapter as _BaseCacheControlAdapter +from pip._vendor.requests.adapters import DEFAULT_POOLBLOCK, BaseAdapter +from pip._vendor.requests.adapters import HTTPAdapter as _BaseHTTPAdapter +from pip._vendor.requests.models import PreparedRequest, Response +from pip._vendor.requests.structures import CaseInsensitiveDict +from pip._vendor.urllib3.connectionpool import ConnectionPool +from pip._vendor.urllib3.exceptions import InsecureRequestWarning + +from pip import __version__ +from pip._internal.metadata import get_default_environment +from pip._internal.models.link import Link +from pip._internal.network.auth import MultiDomainBasicAuth +from pip._internal.network.cache import SafeFileCache + +# Import ssl from compat so the initial import occurs in only one place. +from pip._internal.utils.compat import has_tls +from pip._internal.utils.glibc import libc_ver +from pip._internal.utils.misc import build_url_from_netloc, parse_netloc +from pip._internal.utils.urls import url_to_path + +if TYPE_CHECKING: + from ssl import SSLContext + + from pip._vendor.urllib3.poolmanager import PoolManager + + +logger = logging.getLogger(__name__) + +SecureOrigin = Tuple[str, str, Optional[Union[int, str]]] + + +# Ignore warning raised when using --trusted-host. +warnings.filterwarnings("ignore", category=InsecureRequestWarning) + + +SECURE_ORIGINS: List[SecureOrigin] = [ + # protocol, hostname, port + # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC) + ("https", "*", "*"), + ("*", "localhost", "*"), + ("*", "127.0.0.0/8", "*"), + ("*", "::1/128", "*"), + ("file", "*", None), + # ssh is always secure. + ("ssh", "*", "*"), +] + + +# These are environment variables present when running under various +# CI systems. For each variable, some CI systems that use the variable +# are indicated. The collection was chosen so that for each of a number +# of popular systems, at least one of the environment variables is used. +# This list is used to provide some indication of and lower bound for +# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive. +# For more background, see: https://github.com/pypa/pip/issues/5499 +CI_ENVIRONMENT_VARIABLES = ( + # Azure Pipelines + "BUILD_BUILDID", + # Jenkins + "BUILD_ID", + # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI + "CI", + # Explicit environment variable. + "PIP_IS_CI", +) + + +def looks_like_ci() -> bool: + """ + Return whether it looks like pip is running under CI. + """ + # We don't use the method of checking for a tty (e.g. using isatty()) + # because some CI systems mimic a tty (e.g. Travis CI). Thus that + # method doesn't provide definitive information in either direction. + return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES) + + +@functools.lru_cache(maxsize=1) +def user_agent() -> str: + """ + Return a string representing the user agent. + """ + data: Dict[str, Any] = { + "installer": {"name": "pip", "version": __version__}, + "python": platform.python_version(), + "implementation": { + "name": platform.python_implementation(), + }, + } + + if data["implementation"]["name"] == "CPython": + data["implementation"]["version"] = platform.python_version() + elif data["implementation"]["name"] == "PyPy": + pypy_version_info = sys.pypy_version_info # type: ignore + if pypy_version_info.releaselevel == "final": + pypy_version_info = pypy_version_info[:3] + data["implementation"]["version"] = ".".join( + [str(x) for x in pypy_version_info] + ) + elif data["implementation"]["name"] == "Jython": + # Complete Guess + data["implementation"]["version"] = platform.python_version() + elif data["implementation"]["name"] == "IronPython": + # Complete Guess + data["implementation"]["version"] = platform.python_version() + + if sys.platform.startswith("linux"): + from pip._vendor import distro + + linux_distribution = distro.name(), distro.version(), distro.codename() + distro_infos: Dict[str, Any] = dict( + filter( + lambda x: x[1], + zip(["name", "version", "id"], linux_distribution), + ) + ) + libc = dict( + filter( + lambda x: x[1], + zip(["lib", "version"], libc_ver()), + ) + ) + if libc: + distro_infos["libc"] = libc + if distro_infos: + data["distro"] = distro_infos + + if sys.platform.startswith("darwin") and platform.mac_ver()[0]: + data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]} + + if platform.system(): + data.setdefault("system", {})["name"] = platform.system() + + if platform.release(): + data.setdefault("system", {})["release"] = platform.release() + + if platform.machine(): + data["cpu"] = platform.machine() + + if has_tls(): + import _ssl as ssl + + data["openssl_version"] = ssl.OPENSSL_VERSION + + setuptools_dist = get_default_environment().get_distribution("setuptools") + if setuptools_dist is not None: + data["setuptools_version"] = str(setuptools_dist.version) + + if shutil.which("rustc") is not None: + # If for any reason `rustc --version` fails, silently ignore it + try: + rustc_output = subprocess.check_output( + ["rustc", "--version"], stderr=subprocess.STDOUT, timeout=0.5 + ) + except Exception: + pass + else: + if rustc_output.startswith(b"rustc "): + # The format of `rustc --version` is: + # `b'rustc 1.52.1 (9bc8c42bb 2021-05-09)\n'` + # We extract just the middle (1.52.1) part + data["rustc_version"] = rustc_output.split(b" ")[1].decode() + + # Use None rather than False so as not to give the impression that + # pip knows it is not being run under CI. Rather, it is a null or + # inconclusive result. Also, we include some value rather than no + # value to make it easier to know that the check has been run. + data["ci"] = True if looks_like_ci() else None + + user_data = os.environ.get("PIP_USER_AGENT_USER_DATA") + if user_data is not None: + data["user_data"] = user_data + + return "{data[installer][name]}/{data[installer][version]} {json}".format( + data=data, + json=json.dumps(data, separators=(",", ":"), sort_keys=True), + ) + + +class LocalFSAdapter(BaseAdapter): + def send( + self, + request: PreparedRequest, + stream: bool = False, + timeout: Optional[Union[float, Tuple[float, float]]] = None, + verify: Union[bool, str] = True, + cert: Optional[Union[str, Tuple[str, str]]] = None, + proxies: Optional[Mapping[str, str]] = None, + ) -> Response: + pathname = url_to_path(request.url) + + resp = Response() + resp.status_code = 200 + resp.url = request.url + + try: + stats = os.stat(pathname) + except OSError as exc: + # format the exception raised as a io.BytesIO object, + # to return a better error message: + resp.status_code = 404 + resp.reason = type(exc).__name__ + resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode()) + else: + modified = email.utils.formatdate(stats.st_mtime, usegmt=True) + content_type = mimetypes.guess_type(pathname)[0] or "text/plain" + resp.headers = CaseInsensitiveDict( + { + "Content-Type": content_type, + "Content-Length": stats.st_size, + "Last-Modified": modified, + } + ) + + resp.raw = open(pathname, "rb") + resp.close = resp.raw.close + + return resp + + def close(self) -> None: + pass + + +class _SSLContextAdapterMixin: + """Mixin to add the ``ssl_context`` constructor argument to HTTP adapters. + + The additional argument is forwarded directly to the pool manager. This allows us + to dynamically decide what SSL store to use at runtime, which is used to implement + the optional ``truststore`` backend. + """ + + def __init__( + self, + *, + ssl_context: Optional["SSLContext"] = None, + **kwargs: Any, + ) -> None: + self._ssl_context = ssl_context + super().__init__(**kwargs) + + def init_poolmanager( + self, + connections: int, + maxsize: int, + block: bool = DEFAULT_POOLBLOCK, + **pool_kwargs: Any, + ) -> "PoolManager": + if self._ssl_context is not None: + pool_kwargs.setdefault("ssl_context", self._ssl_context) + return super().init_poolmanager( # type: ignore[misc] + connections=connections, + maxsize=maxsize, + block=block, + **pool_kwargs, + ) + + +class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter): + pass + + +class CacheControlAdapter(_SSLContextAdapterMixin, _BaseCacheControlAdapter): + pass + + +class InsecureHTTPAdapter(HTTPAdapter): + def cert_verify( + self, + conn: ConnectionPool, + url: str, + verify: Union[bool, str], + cert: Optional[Union[str, Tuple[str, str]]], + ) -> None: + super().cert_verify(conn=conn, url=url, verify=False, cert=cert) + + +class InsecureCacheControlAdapter(CacheControlAdapter): + def cert_verify( + self, + conn: ConnectionPool, + url: str, + verify: Union[bool, str], + cert: Optional[Union[str, Tuple[str, str]]], + ) -> None: + super().cert_verify(conn=conn, url=url, verify=False, cert=cert) + + +class PipSession(requests.Session): + timeout: Optional[int] = None + + def __init__( + self, + *args: Any, + retries: int = 0, + cache: Optional[str] = None, + trusted_hosts: Sequence[str] = (), + index_urls: Optional[List[str]] = None, + ssl_context: Optional["SSLContext"] = None, + **kwargs: Any, + ) -> None: + """ + :param trusted_hosts: Domains not to emit warnings for when not using + HTTPS. + """ + super().__init__(*args, **kwargs) + + # Namespace the attribute with "pip_" just in case to prevent + # possible conflicts with the base class. + self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = [] + self.pip_proxy = None + + # Attach our User Agent to the request + self.headers["User-Agent"] = user_agent() + + # Attach our Authentication handler to the session + self.auth = MultiDomainBasicAuth(index_urls=index_urls) + + # Create our urllib3.Retry instance which will allow us to customize + # how we handle retries. + retries = urllib3.Retry( + # Set the total number of retries that a particular request can + # have. + total=retries, + # A 503 error from PyPI typically means that the Fastly -> Origin + # connection got interrupted in some way. A 503 error in general + # is typically considered a transient error so we'll go ahead and + # retry it. + # A 500 may indicate transient error in Amazon S3 + # A 502 may be a transient error from a CDN like CloudFlare or CloudFront + # A 520 or 527 - may indicate transient error in CloudFlare + status_forcelist=[500, 502, 503, 520, 527], + # Add a small amount of back off between failed requests in + # order to prevent hammering the service. + backoff_factor=0.25, + ) # type: ignore + + # Our Insecure HTTPAdapter disables HTTPS validation. It does not + # support caching so we'll use it for all http:// URLs. + # If caching is disabled, we will also use it for + # https:// hosts that we've marked as ignoring + # TLS errors for (trusted-hosts). + insecure_adapter = InsecureHTTPAdapter(max_retries=retries) + + # We want to _only_ cache responses on securely fetched origins or when + # the host is specified as trusted. We do this because + # we can't validate the response of an insecurely/untrusted fetched + # origin, and we don't want someone to be able to poison the cache and + # require manual eviction from the cache to fix it. + if cache: + secure_adapter = CacheControlAdapter( + cache=SafeFileCache(cache), + max_retries=retries, + ssl_context=ssl_context, + ) + self._trusted_host_adapter = InsecureCacheControlAdapter( + cache=SafeFileCache(cache), + max_retries=retries, + ) + else: + secure_adapter = HTTPAdapter(max_retries=retries, ssl_context=ssl_context) + self._trusted_host_adapter = insecure_adapter + + self.mount("https://", secure_adapter) + self.mount("http://", insecure_adapter) + + # Enable file:// urls + self.mount("file://", LocalFSAdapter()) + + for host in trusted_hosts: + self.add_trusted_host(host, suppress_logging=True) + + def update_index_urls(self, new_index_urls: List[str]) -> None: + """ + :param new_index_urls: New index urls to update the authentication + handler with. + """ + self.auth.index_urls = new_index_urls + + def add_trusted_host( + self, host: str, source: Optional[str] = None, suppress_logging: bool = False + ) -> None: + """ + :param host: It is okay to provide a host that has previously been + added. + :param source: An optional source string, for logging where the host + string came from. + """ + if not suppress_logging: + msg = f"adding trusted host: {host!r}" + if source is not None: + msg += f" (from {source})" + logger.info(msg) + + parsed_host, parsed_port = parse_netloc(host) + if parsed_host is None: + raise ValueError(f"Trusted host URL must include a host part: {host!r}") + if (parsed_host, parsed_port) not in self.pip_trusted_origins: + self.pip_trusted_origins.append((parsed_host, parsed_port)) + + self.mount( + build_url_from_netloc(host, scheme="http") + "/", self._trusted_host_adapter + ) + self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter) + if not parsed_port: + self.mount( + build_url_from_netloc(host, scheme="http") + ":", + self._trusted_host_adapter, + ) + # Mount wildcard ports for the same host. + self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter) + + def iter_secure_origins(self) -> Generator[SecureOrigin, None, None]: + yield from SECURE_ORIGINS + for host, port in self.pip_trusted_origins: + yield ("*", host, "*" if port is None else port) + + def is_secure_origin(self, location: Link) -> bool: + # Determine if this url used a secure transport mechanism + parsed = urllib.parse.urlparse(str(location)) + origin_protocol, origin_host, origin_port = ( + parsed.scheme, + parsed.hostname, + parsed.port, + ) + + # The protocol to use to see if the protocol matches. + # Don't count the repository type as part of the protocol: in + # cases such as "git+ssh", only use "ssh". (I.e., Only verify against + # the last scheme.) + origin_protocol = origin_protocol.rsplit("+", 1)[-1] + + # Determine if our origin is a secure origin by looking through our + # hardcoded list of secure origins, as well as any additional ones + # configured on this PackageFinder instance. + for secure_origin in self.iter_secure_origins(): + secure_protocol, secure_host, secure_port = secure_origin + if origin_protocol != secure_protocol and secure_protocol != "*": + continue + + try: + addr = ipaddress.ip_address(origin_host or "") + network = ipaddress.ip_network(secure_host) + except ValueError: + # We don't have both a valid address or a valid network, so + # we'll check this origin against hostnames. + if ( + origin_host + and origin_host.lower() != secure_host.lower() + and secure_host != "*" + ): + continue + else: + # We have a valid address and network, so see if the address + # is contained within the network. + if addr not in network: + continue + + # Check to see if the port matches. + if ( + origin_port != secure_port + and secure_port != "*" + and secure_port is not None + ): + continue + + # If we've gotten here, then this origin matches the current + # secure origin and we should return True + return True + + # If we've gotten to this point, then the origin isn't secure and we + # will not accept it as a valid location to search. We will however + # log a warning that we are ignoring it. + logger.warning( + "The repository located at %s is not a trusted or secure host and " + "is being ignored. If this repository is available via HTTPS we " + "recommend you use HTTPS instead, otherwise you may silence " + "this warning and allow it anyway with '--trusted-host %s'.", + origin_host, + origin_host, + ) + + return False + + def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response: + # Allow setting a default timeout on a session + kwargs.setdefault("timeout", self.timeout) + # Allow setting a default proxies on a session + kwargs.setdefault("proxies", self.proxies) + + # Dispatch the actual request + return super().request(method, url, *args, **kwargs) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/utils.py b/venv/lib/python3.8/site-packages/pip/_internal/network/utils.py new file mode 100644 index 000000000..bba4c265e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/utils.py @@ -0,0 +1,98 @@ +from typing import Dict, Generator + +from pip._vendor.requests.models import Response + +from pip._internal.exceptions import NetworkConnectionError + +# The following comments and HTTP headers were originally added by +# Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03. +# +# We use Accept-Encoding: identity here because requests defaults to +# accepting compressed responses. This breaks in a variety of ways +# depending on how the server is configured. +# - Some servers will notice that the file isn't a compressible file +# and will leave the file alone and with an empty Content-Encoding +# - Some servers will notice that the file is already compressed and +# will leave the file alone, adding a Content-Encoding: gzip header +# - Some servers won't notice anything at all and will take a file +# that's already been compressed and compress it again, and set +# the Content-Encoding: gzip header +# By setting this to request only the identity encoding we're hoping +# to eliminate the third case. Hopefully there does not exist a server +# which when given a file will notice it is already compressed and that +# you're not asking for a compressed file and will then decompress it +# before sending because if that's the case I don't think it'll ever be +# possible to make this work. +HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"} + +DOWNLOAD_CHUNK_SIZE = 256 * 1024 + + +def raise_for_status(resp: Response) -> None: + http_error_msg = "" + if isinstance(resp.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. + try: + reason = resp.reason.decode("utf-8") + except UnicodeDecodeError: + reason = resp.reason.decode("iso-8859-1") + else: + reason = resp.reason + + if 400 <= resp.status_code < 500: + http_error_msg = ( + f"{resp.status_code} Client Error: {reason} for url: {resp.url}" + ) + + elif 500 <= resp.status_code < 600: + http_error_msg = ( + f"{resp.status_code} Server Error: {reason} for url: {resp.url}" + ) + + if http_error_msg: + raise NetworkConnectionError(http_error_msg, response=resp) + + +def response_chunks( + response: Response, chunk_size: int = DOWNLOAD_CHUNK_SIZE +) -> Generator[bytes, None, None]: + """Given a requests Response, provide the data chunks.""" + try: + # Special case for urllib3. + for chunk in response.raw.stream( + chunk_size, + # We use decode_content=False here because we don't + # want urllib3 to mess with the raw bytes we get + # from the server. If we decompress inside of + # urllib3 then we cannot verify the checksum + # because the checksum will be of the compressed + # file. This breakage will only occur if the + # server adds a Content-Encoding header, which + # depends on how the server was configured: + # - Some servers will notice that the file isn't a + # compressible file and will leave the file alone + # and with an empty Content-Encoding + # - Some servers will notice that the file is + # already compressed and will leave the file + # alone and will add a Content-Encoding: gzip + # header + # - Some servers won't notice anything at all and + # will take a file that's already been compressed + # and compress it again and set the + # Content-Encoding: gzip header + # + # By setting this not to decode automatically we + # hope to eliminate problems with the second case. + decode_content=False, + ): + yield chunk + except AttributeError: + # Standard file-like object. + while True: + chunk = response.raw.read(chunk_size) + if not chunk: + break + yield chunk diff --git a/venv/lib/python3.8/site-packages/pip/_internal/network/xmlrpc.py b/venv/lib/python3.8/site-packages/pip/_internal/network/xmlrpc.py new file mode 100644 index 000000000..22ec8d2f4 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/network/xmlrpc.py @@ -0,0 +1,62 @@ +"""xmlrpclib.Transport implementation +""" + +import logging +import urllib.parse +import xmlrpc.client +from typing import TYPE_CHECKING, Tuple + +from pip._internal.exceptions import NetworkConnectionError +from pip._internal.network.session import PipSession +from pip._internal.network.utils import raise_for_status + +if TYPE_CHECKING: + from xmlrpc.client import _HostType, _Marshallable + + from _typeshed import SizedBuffer + +logger = logging.getLogger(__name__) + + +class PipXmlrpcTransport(xmlrpc.client.Transport): + """Provide a `xmlrpclib.Transport` implementation via a `PipSession` + object. + """ + + def __init__( + self, index_url: str, session: PipSession, use_datetime: bool = False + ) -> None: + super().__init__(use_datetime) + index_parts = urllib.parse.urlparse(index_url) + self._scheme = index_parts.scheme + self._session = session + + def request( + self, + host: "_HostType", + handler: str, + request_body: "SizedBuffer", + verbose: bool = False, + ) -> Tuple["_Marshallable", ...]: + assert isinstance(host, str) + parts = (self._scheme, host, handler, None, None, None) + url = urllib.parse.urlunparse(parts) + try: + headers = {"Content-Type": "text/xml"} + response = self._session.post( + url, + data=request_body, + headers=headers, + stream=True, + ) + raise_for_status(response) + self.verbose = verbose + return self.parse_response(response.raw) + except NetworkConnectionError as exc: + assert exc.response + logger.critical( + "HTTP error %s while getting %s", + exc.response.status_code, + url, + ) + raise diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e422ecf47efe2bfeb4b1deb7de605fe3774ad1f GIT binary patch literal 217 zcmYk0F$%&!5Jfj&AwmvfkxY@sLd3#SY{WWj%-0Or>@J%%5DV|)6|6mltw*r4asz^X z_-~j$Gcdb2o`|5^op`9v0so3ZvkY!W0j1cAX|%ZtH^e`@FI1TZgfnV}q7^zQV;g8s zYgx2pdtVuy*ep3XpkO54$$bq+9dt*Ei|lG|Mm)|`Q literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/check.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/check.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dab9b28a4cf9117101279ac5d694978165263017 GIT binary patch literal 4796 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a21c4608e55f16671176323656ed62b4522ae80 GIT binary patch literal 15617 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
        D`MP{+P?+e7JC+}$_;>Psf~|j!MUU4 zn`3GZlV-8$7M`ewSjRY@ShW^D6M)UE9_Agtl!#iU1qi`tc*jjm`PskdEegOl5{KD3 zEEK4rYN(m|423|aU89>Ey+`6kT^Fl@HF6^@#_}%iy@$5*DkH6yFX!mb`A~Jy-}!l! zSFiE#VdCi=Tbt%LJ%Tj9=Mm&OwIy!DDtm*g?_t6TCZxrpG)mHet;kR5?*^zYUw@88 z4B;yZnhdFBx#xF!PD%gPLVLjG*^JllrYmz**%Di}Z&p9b{GxJgg3+1Pq8KcSh&B=I z?X(=^qa9sk(>to;*!;^=UtkGer{Tp5l_ccXUn5uuv z$Nc7+!f>fy#GU$0J{r|Co?|GpiOV*{MHOW61qrv}$dXMpjmN==({40wmGyIM`GP*8 z$J!q&j!y9#{*e@0Z!Ii9L_&lG`G!n!VM2jBBcns5UMCrijYQ;QdS`=LH;m4 zMuP8m9ih6N&qjyY8h*ZF z#2SU?k9!-A<*3wVEw~AWMhlV-rC`y*@aP-*ZawNKpd5V{o~^O_H($}Ejt$Eh<)MD4 d23*=bh7vb_4B=>6QMU4H$F>&=V_V18{BJ1aoyq_J literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf0c8dd330b8d06712f3b3292024bae0d6949520 GIT binary patch literal 42580 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 Lr3VZ93wQosD?bS5 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a62078e2029f348deacbef74fb9aa7947b8e6a4d GIT binary patch literal 17204 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�^ty!C){sy?(NG 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 Wag@rSRF1&EtV$)hDxT4*^8W%Pi+h9s literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..729a8cd6e802bfbc6038f0adb10cbb1cf858334b GIT binary patch literal 5354 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9cc92ca10334ab0750c2ca3aa646844f1bdf969 GIT binary patch literal 11017 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 J*#7E*{r`SMl#BoX literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a09e3d0c788b067862a2e4b90238c8f9324bb35 GIT binary patch literal 11654 zcmbVSU2G%AecvxG$rVLPI-Tyr_IXctx)Yrfd9f46z22RD=aXzT7x{9M?K5qMr8p~T zDRP&dU7k*xWzt$M8U!(7AZZGuFH{2*aEk^(AJU=^0U88B`jDU>Knv_+3KZ~5fi_Py zg1Eo`3`vRd1qn)NhqJRYv-9!)`2Bywm&eB~1)snFQQ7_8i;D6ebaVJ);O048L0(l9 zrZBaswA6~KQeJCn6-|8W6jH)YYUDLAuxYVRDI*jH~Uduru8n`Be(D3v*9u5#f8g&kwl?msGhP#;*m=iknu+x01B$Rqs3A{n~$HG}hH;AGDe_+6J!gU7P)6>GG^S7|XTUm)DjSo|(0S zu;#H^)AyWfv%5|(`|Ne=YApzy)_QZ-Mtd)~HrwIem7u=mv}(aZ%dK-i@HfJRy5G7| z3tFZ7pPaQ@HP78}g7B5YFS~Bp!>*PYX5)stBf}|O#*i$#&HcI)1U|pP>su}@S%+gZ z_nvtg54~Co`MChuaJP-@j_W=3bS%v`HAq zZRftzw40P(o2>=Q-hF@D;j?zfy-}xmUz^>iH3NsHWM3`+nr_R7UtJ!K9-CDj?AG<* z3`Fn?=gyX%XzzwwzPI!2>z5aPr}X9%^RL_4H%m_xpFJP_^qjr$#DXma@`~LFHx{0u z63cCo%weqtImofK!Tpv!H{bGE2T~i@{YrE5GE=3^DF&9iVOK@ITD7lTvoBVwtr{e; zdhv>7RN{PW(p6dEj7#7cbctaUE(? z9CqS7aKrhz`HhZOF9!3)qLpY~m>7-P{aT`y>ZE~#kA_SN*Ymi7StPC!D{r3esV)?H^IT3 zDF=}Jg*G17AW7x6+b)wTGoP248-!F>E?0o%O028Qt%uOS_Vwpzh8ZNLrm4AK845pz zn?xB|4jn~g+Qo9*Lx!obvTgFUSn0vAV=-vSAVF^M(Mf0CS8sUO0k|6 z%nuLyiyT0ALzqwtk}$AuI@%{=u!w^avGOuGiC&W_$9t>eht8oYFQ7b_K%%Ibl%-Cn zQ)*tb)QrkW5bE^c@Q2psIb6YiB5{-ogad9)t>}&cx2D0RJ;74Ud`E>-OS24IM8?TB ztlk(oAbyisYz$>NmSf}i9+&Q=l4qX~4n?gLq${aRIFpqrcAU+i)-iU1oy7MvJ5@Q( zPP0dlpJA8SqwF!f=L9>$9!JSZ=M=Nq*>}i2opw$)9$~Y?yGI+3v2()ZQ7#Kd_cQ0w z#u+N(KV#?F9NIt57T5)L5k1 zd)M~4UFS|MbnL$5*}@(Keuvi&%BWVY*(7fy6}9cu-HpBx4oaeIWo@Z$Vs!@}%9Dl) zaeNyY2$oV+VhyPp>qtrbl&UULBTO|+rfqBOD5*IZdjQf)tQA8?YLqQ+0->qGrs=`~)Uf(b~g zn0l4DJh33Sgr0Y$8ecj=S<_CNVJtQe%B$G^lcCe{V zq9d1N?$@|mgKy$ls+6v7z?=B(#H_kO74}VsElhHKH(qt&vw#|mz-eyq3uwSEQZg@^ zGQL~o9goljb#YWzGF3Ep!)leGb})@ZQO&`hW~&zT#8#u}L!wa{G^vxC3_fI8hvwut z+{9!gR2UMCUyYT9%CCl6L+`=D?yE2-de?}JUW)02!n%fdW@4Y|4i*`!pIS-u&5pa2 zn9^t_>Anvurt!cD`6uxf{s|d#jwVK^q#oSIjoxsfILwJk|->Ll2M4=oyXB?SEq zWTMLpI3q0wC+HES{h%a(L+ZBcbsj`kL%=Y?6j5QsAkW8QEbJ0XcT=9hGTju*#_D$3zZV;EYFq83En_-z zs>0iog2F%84%$21?obG7-w{$ua|pKR%}~XI{yMIRGi}0Hcu=y}wrbwCzz8qacnxd2 z;&Zmiq4b!Sy@i!=y>=%oAw27URvj!vGw`q2TVdF~QZDc8?37$@q0?-Ym{a}|qDuJ9 z`gWQ8px+6~{YjM1H63p=+!}T?=+G7&1?XvOv()k<7wDtp^UZQUzfh;hxz;R`7c7TB z(ydzkpc{T3vb$C(P#han2}8TEZJSFU=qOK2R>K~6F5#08zKoJzkZ#)n*}8LQ#f>Gr z!@0Bl4YC6A+ZQihq#e3w2V+!!_B?{^+4IlMS^ zY9B@yup#B$#dAwolKaD&S(biC&G{qGGomM;_d$;o(@>wVJKu#%7>kjk`!LrS@8y{V zlm8=>7h=2}wG94G;Y6I;qkO72$;POZ>N299ehCYuZKv^_m*24_>=PhoZwfsFBz4Uw zADg{n)KVocKMOBEy=}dx@@M)!{y3Y6%^4v7x7F7aPrC;<1v*PdjYzg7w~^4-W9Jj2uUD~oqm zL3atS^I=Mm28mv8x>0uF-jyYeU^$4!Dcfl_<*iT=`{3!NHG&^~BRoIHivt_V9B>g7 zJjmce^c_9>Th=DKC?_){wruLWI3qwHC!CrT00rb^q`-4R4xAjK)W)lnyG}JT1ndrI zHc8`EVU~o(@|#qVw3WX`$zzm!o|0K4i7BWxhgZdon3j`Fz3jP|pL2_5=}6pTy;5s- zWWKNG#571ohF7S+XQ_*HKd|k?+5XxcRpOk0vHgdCi*~^sB#LVEVSNTFYpS+p>3KEx zk*S-wpZS%c8){C=sn)omW^{`%!AF{wQFEpSw2Oe*uvD2tKOg0^=;Vmf(=zp6IRdZ9 zMv}D?+D=xMbUY-=MZ<+~K%(7KkmUxD!n?YFPsR$LL|uWQ5dE8yZA!)taZHIJIGdVf(GsYz$9kS*}%654iLbR4NX$y40eyM+k{*hLHNI@+eiJT zYAAWd_$Yex!}dRrPYkMabohey9W?ofhAylw!QhdtqMaI48;QUPP?8?V0b}E6t9%QoN`NXj zjaXak3jhA4)G(yGQw1;}g!zg#t!enOv@nEF)w6EXoW~H$uoZPiyi+?rD z^s*sxdsqyd3Z(u>JEe5T!ZBvXV=Voy*3HG)UM|j&$yF6k+~JwHd$k`F!Te=<GvismxfZ#dI?gypDCgz*mQ!}!k(-}at-n>6?`^_sLNc*hUo0?s_f zV}NLSXYd2K7dRwA#})dc!Aw!}=VKgROb-;It2cW2(yIH)_!{qeZ4 zDclL7beqI+t6VkObQH>zQx?j)0|FTH&C`Nit38*7Wz#XB!O|KbOv?$OwQ@C7vq zQZGZeOGtH=QD_~yEhp0PEHdW;5e;2Ah!=N2(H{o4_JR#{1s)@;N*Ed#v57dgW;q~yYtzsXz8234 zMn$j=$+)l;1Ad2-mE#0#gd&rjzVCA1p*Rs>_UGI4eSzPWg;Z#JoYz}r+m*SETY1y8br z?FDSWXp9`bTZtBJR3-<^nV?$QetnBmSem3F;U-c3dlVKZlchm-sSLNc!!RY{(0WmBJjG#f0E7JfLVuS+_tZ8ZF{U53eAM*%9ABAW@d?g$4*>9<*5r#k`Cp(LrVMrzkhsrzNX_(?+C+ z!^r^wL0l&P0wr_`A``*IPYYlmUF1+muF}R3F-BaYCH_t#J+DUg;l+Gd(~(euXf)C~ z;jzAkj0h!kemT~`J!+d0z^C|Sq9A%IL3LPn0_YkA4I=E&v5-crGxJ>?UQUtxWdC^@ z_h57yChBGo2yoFa2!C*zoMpRNP!(1;i(2V819%NT6leES*c{@3##Z?6V@%0}jMF$V zZ&fhFL8QZ+@JH~BJ4oJF1-j$UAitF8IQim#h-@(<%!h!Xyg`-Uq9mf^uP6~~{ujs< zEg2f_Qt7uTA+#uPV`9(?rOKL8E0Qt!EtUDtsOFJ9%K;y3J5^b2iS~+UZw0CWh=_}n zVgi3TToenA;)}zLN5~?A*F*7#Tch35`=AJ7D~Ta^YIRta5;=(WK8Q`RTj>DDQw^X8 zh&!0#U@fFW7XnrAXSC|@qd9~{z#ZKb{HP5-Iu4rjZRthPHXlT9qh>b+U%CVgk?IwI zAqd~}(ANYbe!AaR2y7riGLTdI0_DVM{u5ACdYtY}6KZJx7*YVqfM?CbDW0IWcaEr`wx`owPw>>6v}tiAt+h3ljakOw6oWtMT-4*xFlQW5L} zcr3Ny0zD#5MILm-lq*sq9AwIKh#wW6@K31p79}rG@@XVVhRA24uBD$GUs}Gqx>mit zys}umyO^X}wGgi88r4i~a=+6~QaH5<;ALqB!r=;+$-hNC{wdWn$YXKBJ#+};Y)efg z)#Qsvig~G@-=hN3&%~4q!2g_X)Q!a2a5)GaF(``IWXwk{0&FivDm9o3W&I94|Cq*# zu}O`^k$@7cpcZNDw0aU3v=+2@9)Bhr7dbYe$q4dDr}G-Z8{y1A1H(m<8cp zVII};LoXaTR;GQVOW0&mcStiF)vCh3h@1B{u}5MfimF_e(Yc66U!f@ws8Y1(*y&)R1Iayp{hDrNiJ)BCUtK^=k)j6~xELTWEJFpb~F+1DQ}9 zjw61-Q4ODFdRK$)8C@;Z_TZdRc7d6mM%IF5Fe1cG8whUc)GO3M1+5|0FhWCM^;8I- zM6^tZ^RH9nJFJyNz4HMrUqyTRQx)y0C!;JV$b}kjJ^iW3pl{*JBdhPY0o|hcmp#Fn z0Q=HUi2#86=dz@+p56Z;0YO<)Ah8a@wz0Nd>k)JGOymfapw-GouqJUd3Ejf z^3Cd<8_$3C#tVzQh38-^MF4GVqyNNMcf+7k7%-I8exyx)DbZWO<`F67U%`WK(i&4V ztdKAgiHL7PT;@jx#PSH5A_Oo1k0ZW>SRA^#W!Q6$2X5$pqr;TAx2U#8+P-xYHV(mB}9yT>9t639tls8QRH7_hN!I0_?lf%qCo0y3#aKOAn!n%w%q0*s8Bd@z&3Aq{^Z zWdjUo>(a1?hly@*cu`0DJXpai|Bv-Qcr9Xn1fbXz#(#Eb-SeYwJGAgWMXe(SBN?X^ zt>Tmn2R2RT`?LdO5*3qnVETx$7^z#-*Kpjd3Fv{JWDfpk6a3zRgr-_eEHD(}M{LOF zs@1nTwPycG8o!3{>nJX)K7V)l&RTVC@pE@>gW%vD8sy`Ye1?*nlsrkv0sAYCa6eCF zRZ51KJ(;ZDT3ovE^6j-${onB$Wb81&mnmoS3m|F{@*Y(L$mUkbWY3U@OR$2nL$~` z$b1H=WldzB1X2JgZ0To=Ou;%PdME+|y1w8$k~AIGLy0Blp>c=*m|i5-}C6a>7$N`f#+vI8#_aN66%zz-yw;%6^Xbp^~Ly%P>faP&zQe&M$Gr2zjI zdh#MQOo3Z)+x*`tpO!~>4*xzq{x?d7iv=P?uqyl?sb&WBiil+SPpH-`l^w&+Y>)nc zEHVA{27ZPrR^ed_oMf&_ak@?#AJDesgq(h@~V;>cJ?YMA|qbBc=3+^``_>Xo*5l2B>4NvFYc|j(q7{Cb<_Ws=jI?s z_+#lr!cX{KEitX%WX+pTPA5H=mYPo4v-EVD-&8F#pPkOd&+^lGdp0yZWWR;!g8dFp z58Lm^^oaeAPLJB}*z{P-rL37=^IW37DV?Z{Un&IO>Lv8g_oml;I8jeN@4mctdab>+ zZhD>luAg3SzZ<4E*zaxAx5cGxoZd)jTjJ7wH!e*rx_x?MUx}NhH~DElQ@dk+^YmuE zm-e%@JLk7dZ<)Vq`mXuAr|+JB&-8odi_^vVd#3N1-#Wc@{@&?(=eJF7o8LaYeg3}b z`{s8{@0i~?y>otIdSZUp^sf2)r|+NNJ-vH=&-9-8$?3`Yz0-TWM5S-TwcoDBxcY#<#vgwpF@3;a z>#yVYgZ_Gd1HX^@xA`0Seayez-^A}h|I_{*{^mEl=|ldV{uWXm_wVxW=J&Aw9>2)% zkxFXrsDF>Y^^L?eZ~Bne!}17 z-%lA&lD^yDL;4;|f6AZq_mciJ>HGZmlKx&xpR)SX7jq|XB&SdL5Bd+0>l^)V_uuC~ zOdU`A-{C*vzn`l!{(k=hJUiO=P{;dBD_s`h9&;7IhbKE~?_Z9#7=~@5p`5*SD`Q`=x1^*nST=ZX@u9EX5 zzeLWG<(#wnyi9#A*;SdVnq8geYTlpmecsIa6@QlBdS%V0Q^9WnmluG`#`MDc;`HKt zFdcXa%Dw1UDYshLHWxAqAt`hIB~mU`7PxP4U-Rd=pRY73_gza*H%Y1c4N@AF?Q=`y zS+er~hV%u0k@Q8xw3=jZSG(5zlr;AviFz$kN6*@+}B8d-M>cqHB0{{{|*0} zN&krdG5<||KkC2bzs>LK{JuBHk+2z5>laGRs|%Gd8fsRqH0?Ud zh8LHb)mk(ZlrNX6^@XKols{v6Pn?X_&Xi{^R!Xx$W4;tNmV%i|lv@gFjfG0xF6J6l zE-p5k3w$mbx-wr27G`ugeEjU$Q^(77zg7vN@yE+y<#3}uTfMLpl$(tp8h)i*tNNv? zU#T~vAs#i$)jByxW-Ql2IS9GSKXdx2qd`Fa+;nx}ShZG(^0T2|nQ8c{M}2Xr(Iklq z*$b_)mv+8z?((_GJuluraW34|8jtUH-QP*s{L;+O1@c`m%aTDQFUmm0yj^ZLfnOEl75=69Yu<37BTdW5RU ztIInlcHMvP{txWl``A6Ny?k)n_LuJaVEe$M=UzDXqUtoG5$pWvIu#$`!(8~DpL`?n zcIwTf|VR36?Ve9VV*4o8zt9@x}psU$NP^>rVdzzJCzFIHW!eX^<8P>Q9NI<(yDbnK#iMZDTg$pRwbcaiwoFo zH7a+CV@x}BUs+_?ihHFTRLkdUmEz8DY38CzY=otHdA>3+Y0n47FUOFP4bKB!dT%c_o4H4~eNNjuU%ftbgSNLxnN13Ar@;1!MCSiKC~Z z%zU{yb1}+Z2pUTZVU(f{(eRn8%#ABCHVP^WwKBLcTV4PY`q9Xlv!_o?eW-N!_%l;a zMrp%}*_uAEFp&eZR_5KbioE$K%XC_*H6w2^N;Vdw45+IaMqc<%BG^p}ZZ7S;*qE>E zt+Xnas+HQ_Bb8Sw;M;i+<=&^t=fk}VSDP1^M+*<`nGGtH^VQ0pW>B7CcJF9?4P_Eu7F1z&0=ucvHoGRMMT@k{O0^>jQtnN{3ndb6|pZiaW$z$@7I^Qq<;B=ZX8+a3|H_(7bzy& z$*{Qi1f88U#2BN(ZvNCVgG=Y8{3M<2Esk_MIyDugpkcuS6hD!QQirCFxK2|4ypwkQ z)9V`R6)$*%hkd=#0eA|!%#&~(rv#1ebb;%DzXgza`TvjBCmTVh@yU~?duBMrHBg2th&b7Tyd%^y;=abvdTuQpT>p93%4&s_`=Rquq z;4?OPWG(KkbESNcX<>kkzhmDMgugQ_66+lpz>DYngp{Xz{1pyb=l7Zg5MbPgyw{@a(OdLlt~uP!UGtU;?ZVs$b@&3mqvZcV@{hH1af=3e z+Vw>Jx1GqoJRH|!t+n9~+T0r=&MtfPFEg$=e_elnQ**Hp>$`RNTY9`XMmy+d-aAOy zLvh(7_2-*w+!Kw!r<371O5M=yy>9<6kG2PT-}NRVkPJTA|Av2CdvrDlis8Wpe*T{H|y3{sr%D>b{%(bSi}WERWT<_;-N&HeX-s z6dCR85eV1~Z#KCxu{_pX*G@r-vh7Se*Bh9LJ7*~2K=K2Gq!z#=NLQmb7|@l$tc1%K8v3NJV7jAklI*x7Q0Et8_4a&x>t)DN~5E|yi1a=Z{db?X& zW*P_x-7>}M*>fYQH@$eFwk)suq0oBnW21R zX5L!VUBh6>uT%obdBx_^LX9D>&LS*bEyA;`DkNk)tFzULzq?qe0Vq?Adc}=;_d8s~ z?EL!9c*Nx!6da+a9hcN)=#lM!N*Lv)a!ll-!r_xopE`8<=xQD)TKDYH(`QbcoGSI$ zc4M1@dsOZ=P7|4Eln<7=ytlDQQ9iCjXbe;2z2eAZk4YN2P-(&=TTg_)NI{`!S|TI9 zh=1@%lOmOgo?x}b+@~Xl8y78L9Q}$a5p44wL2v>IH+EqRFX>x|& z!YPr-8#p-;Okg+eZ2;(;5Vip@%f^7Xjwgs@Th zq<#o{^0|BH-kl0A@*Wke>MQmsn;3BB0Qy2>!D=2{(rq@d**P&5jrGb_6{AeBM0W-a z+lIfGNKXl;mFZn;An_fn-^_u!4P@WI}t zZ1Av9Lx2*Md+|UiIOX&rTr!=Xin)=Y?K7gH1vrdkFvNGFOs#Rb5}>C8_@HUcXtZ2Y zl3$v+2qPVhm`aghvWGdpGFvwN-9n7zXI3^oN7k82#eZC_-oz&o@C|v;vJUCf@^A}j zrepB!JR86?>Va-ANTQ|(yrzI_o#Q2!Jy0y%D2Q?soG6?o?cNN!Ox+Lsr#xh6KXGX&e5+Xi0}ml5W$Bxj z7f-cQ&oLR(iRLio_n&qKbLuhz2?LjViP&hK_lMp{EEk%i*T<;kaBHkxK)Os^BL@ce z1H3;vx8_m-iFiY|jPVs+p^x9u52Tc!`Iha3r;VORH+RkIdTBaM0*%D zI!18X`Veg!T^^%tqwTRRl<5NO;1nO_Lzg+6SQC7YRY{zEl$q0yo3RZD95I>rL0gc0U6-}Avat_sW9`ZxwNu9R+b2TjS`aPToq1X(|OeMEP$E(Ka~ zeU$l^nHy_oZ=_%_k1g&dZA~8}$Ey9{b5uI`E>11vto@jjG+SQS9=;fyBynQgKrXnU zTwmm5V0Jl>3DN*0_#r*{0X-Qm`@psv>?mtP69%v7@fAJJ%+|^mLX$+q5jNu?w6Z30 z!QWR#!G{~+)TMb3bQ_^)|7 zqb!|2-bsjJDlv3Oz{qc+#l(LW)c0KCeNHuNN=^APbqG;fpgzA~>{@RAlCPUUd?}b< zo{1tF03v~ot zHn8_KqqS5s=0=)TTwn0qSPQ5OCV^sefLWvNV`Q4a_hJQHjJd)}aS1Vh z%Ft8lgB}loeyKh~Q^2*g+2Gg6v$7re5(vK9ER}v%El_a@ueE8#XD2(Ere;+C&R?2~ zE=#ZeK|d>8G9tDe{KGx;9ClvRt(}-l%)xmHQ;5zxWwEx&b?HM zsG2kerP4$?_*ax3rO3r#)WYDWxLw(jT&X0QQ~LStP%uAbm0Fi<-M+d(mUqG!_TN(B zg;Ht0;UjeM<&jcpaj9H$Uk`p=AN%dWFB&w+U=#dHPXAb+RBz;y`HZEkf?xHU4b*E~ zIQ)%)cFgru2sfY}Gd-q9`eYNgm5zpzbXi)IevI@!V=a848`FT(9gut%H+ zAD?P_VW2xKKZ&{Q9G06;ioeK~e~k*q8clR=&5LU34(IrxOElBvX3<?Tv~vEcPjj6>eVawFI=rvoI}gSGR-k37%A#uc;w%}y&7w#cdL^Q>LI!Hs78CL z;l(1ND;;XuN9efAS3p2PBFi_$~=Nt6e9EnD9 z!8G{}ILSk!d2yBIPM&J&Xj8NNbIlu5B@|*kvGGFiL#Pq(kzo2}z2NsKeuiVpe!17OBCxdgY#?f$SoLXI5Hs$>2duJZo4zm_~h*wfbY zTktpdxA8mdZ}e~Hcf{Z1-@)&wzr)|`-^m+e{uci(Qr7r)`|sg*9KF~*{I2!4`uFm? z&L3r#?Tki_0jMWqJHfGc$K7Hm9PUPB1r3zMyy4~K#DN%5MmayYS_+ox!OxOiY;3p` zRPdp3_?v0d2z+R+FlF%jIt@}RLt}rW4tL@}7VOx%t-pp{EI)v}c%w&UR8yP_pfa7B z#=j}?EU4y=At(bDs*9`VsLh3vxF}>OR9V#^EX}|?Gu6<}SZ!BS^xyK4f48EdcMMk4 zNS6D|s%}m3`eBZ6BPUTEbO277+Lf?6QvUx;j^K|t;TPd|JI}y*uujoh_pwqr>@?-y ztA>KY1TfmVd!T-+G7peVRj^Aok8>k(-~>Qi@<8c!4?@2Y8_(nC7{J1PX2wt8$;Q(; zw8UJIK9{{DJ4CAdtJLaj1=m8XP{LveKVV%U=6VHf4Qb#lJ#bpv7=ai0bL4;u1WOgY zy~E|$ft3)|JuZQu2a1^&{-b>$Y!Dw5)9hJRt|2ECwZnaNhdCp@OHrLXMO=-;`i|x; zYBG!KlU3z>18ip1uv&Cql?S7mB8r8hmRujNHkRraDh^MoD%RdkQ!v{K7vtS2$1cc^ z2F;=4{#vR=Am^t0WM9RmaGO<$O-}kY3RCM@vVzhu`OSbB0qp&bL>6N(?@$HRv)f0G zw{$etk)cjYw$hZXJ*!}AlV%XeJlJ7<)vMWA1ez+~WB6BAA&pZY$cl0xd!b(#Ke#ER z<7R1IoGQu$}vRPiN}P|ZUrX}lnuN%#@-zDNbW<;*E(a0CuWHv2RHtM~Jk>T=+D zz+~`|RV_z59W*8)S-pZz9`zlN zsa6i`Q>{CTmoHXvhP~Was)?5=igdtGWd7&vTg(?!S8V6B*gp-wY9WAB@Ml_8+qO*Uz9`au?vK?%FLxGH6LDB2?j$d z!IV%#2nJ5>O|640%T^|}MFAH$@z_#ev^E~2+zz#y?7lj+3Opo`8iIa+&G4Fm3+l}QaLI?S;NT%a;|Y|ONqm1%Zq~<}g*n~H_c#3s6dtYd<_JUY$%5u& zY!n~EqN>jeD8^!-5JQ}qYTe!$TcGwHBX{5KhX*Pl%QKEW4=36&DNHszK5aneofI`j(f2GXGODIGb zu6NP)Sx>uXn>9=#d-6_n1BLDh)daS7II7mB)kEZ79H38{&Qb~37@gyyZwfPkH6{?$ zbU0ydtAV5LaOhRQvpMgm?*gJ>-Mbc zNOhk9c<{Tr%8)boIbB(YIl0jBpFd7&fM2i-k*~(?!Q7fa@u7lEJWsFl+EriG|r-rEP#$R(p;6zAZOV zr5{kGIV7EL85qSC83(GVDt4<`=SDM2f@FBNUCBpyj;o{zmvikD>cV5F3K7%;yy`*I zINqo+i(BC|%==wJ|F5?$ZUqf~2^Hmh| z>G^Va3B4el7zPCmL|S=Qb~p-TSIGN%;Bj{<4}gqe(#HTi$SQYoui`$tv`-MGi6h+( z;4XbmYN|R<1*%^|!-dbt?OemKo7PbkTUDh4wetLVzx?PSs`S56Tw+BfI?o3xA<)!e zpxNfau&J3BoX_=~Rz_U0RO2zjD;jUdQEC+7MQtXdHb!ww6O0`5B!7KKczC0!1bL@` z86|NYx5OADj07rav{RBnKys=zd|2GRBbyUrJ=7We8I=qEKqpbK;7@dteCZHDZ;XWl zSX^*Pc-A=fuaYn@jU)rw*skb8E9tNXbOSl$rM!_t9v-4$^5JORJ}{YjGBqi|ynHW-UB5;N6WrnG#%m8Gbju@%!jG`WMxBGI+k7#=$hPoU7k01r2k` z3y(Lm=Hif(sn)qTEaxzB;nxv-tcf{Jbd4)i@y(&RVNA`h>342KN_!k=QwI{trxI zKIG&;30_gY!P2|=YKz%yT$t1AugAtOMQDHRSjJ~OF1|(O3wAXYms+}X8M$Y*?%_N= z(W%-=fklxo3M>Q;;~v_Eqa zc&Tsdz0_n;k$^`?7SPFs2Q|&QQ>wtSBWX^D(e3@He#O^naO&zjVQ&aIrwa&+F8k(vHOc5oxiQ;%KDd$KA?l zr#)OnWAQ|F3Ml`)#&RnUG?rSG;U>3M*>H`8tux&j%{y;3;p1`!#&ZCX9-(aC`J^B0 z8`o|0Eu%_`+_aL5?wPSf=4BwA?f%7s_+EJUQ^>#I4=D%&)njwiQ7nj>nPIhHlzy&s z@=4=-2JqVs%Y!~tR>~D!FRCgx&QUryC0 zVI7>kCymcp5@9yuqt}210ek?pF)`$_$14SoF2`|3-lPMHtaIzg!TidzQ1-6syt!wT z9M|=Rx^96Jj>f|-m$BT@u-`y;ixE-^r4&z`Pu(u%m)mV$M;USkGO;wgQn{|7ci4zw zHm`^*Qs_9tKjH<4=*1b{9{3XtJ;-6pxVu_?Th2Y5sxWm}%1zIp&(}h3g+b6F?!24(6U?*_tqPk08HIdBNkVv82tuF17KF)utH~(YU1C zlH`<1mBmu&=hUJRN|3)*Ho46!d@YIAw6B*KZe^T8;c+W`CUdegZy!-*j_LFpq^Z-y z1JiZ4!b__8f7$sqI4Fy^M+$tDw>GtYYUg984oHvq=*|~jI`_hh=g#b!nCOz-;=mu# z89vCNH&}dJ6JPP@&U2UVpV;}r{&SNr>T1{5d3Pr<8r<8aK7EwP5=3m+`9Zz+*v{QN z<0`(TAl<(>@z}f6WdJe@bVC<;{*T=FcRzz+&c>Yem8NL3) zLZG;|n1&EU3$pYx{K_p7Lm}nh5bNh)*=BK)1yL;Lmh<(kq~%#B-RdukH$Xr)JY;0@ zP|gtLIM>O1@Rhh0Uy5t-<^DHW6g5=e;1~SiHxlh3N-?KJIbP!Io@P;69)JF{gUyN2 zso-(i83+lqHpj9WCU#pzG-D!~S$Un8Q@%4?H@*Fa^KGF}AOIfdN5I511Ojx!&z&_b z^bZS1-I^AIG7NOjlL=Pq6hF6=uut>gj|F&XE^IZ+HhET53M6tqIvq?pO-G^x<>2?xfgZ30N^VYmMzys0JI9rr z7Hjxyu9(by^GD<=_!6gIQwtv9Leo~PvxLCp*O)*ME9a;u0;V*Hh)Q!gvdOHehpd!= zUKXYj&ezz5zsyOFpK^|ox4VcvD>QKQ@TKie0;`k!vRZ@q6u@(R#O@5n;5YTebTWZ7 zE@QexS9E9I;Nu&o3u&y4{&hG)HjSz3!oCg|8f%E3l~6 z!z5n#L<&Xa@N&{Q*izb8V#i(2N|o4%U~1pTt&h1T6Uu1$`{^#5v9S+{tZ=dW6uur< z*yKE64ikv*$dMLR2Ui444MZh5;Y^b~@lpmiE3RvF+g(J2z^)Qo&?bw|P^j7ZY}G>% zf2daog5%o~6s6l&yCv?n(qysh)CzOWj>)(pJ7nIp^07O1i;C@hC+o%MSm@HYjKo5= zr%oO|^wgnKr;Z#tdq@?cJOb`m7!`e%H3Ze5QP)aQ1e}YNx+~XxJ`9(%3e)+QlzZZ| z^F1-|8mvGn#z}U)9KFS%3VGqgAu4w29s zy{i3X2M<2+XcYE_UM!!-TxJGk^L$Z>=Bd9}#~FnDqnUEIz!AS@e(Tkj1{U|N;? z#Te|?Ns7`ahr&w%h@$*_`BH_x#xwoo8MAE%|AY*|Kh^18PUu!%(Y4blxazDxiIFWs zvFU1yiT+&J@jhNktPu`G9iAFVNoj^+B`HT|Eo*SgJB_uMJM`HB)=k8rOS+%n29#nh z4^HkuhXNU|L$r;gN}@>y`>>{@B}^Nl12ysv1_|zX+D;KafW>N3ahA1O%Ruk2^DV-J z+x2#0ZUGjIQQrh~kR#M2$1Qxw$__WhC_n&Qu3cy_+85`QMUD^qT@R7I{md-jme!6oG!;@yTk2GHuvm@(w#mzjn5y{WKU zzTf{k1y0%!*|#x4TS%RX@5&EO&?l`FpKK*LvMy`C;)xZZTG(RMb*lIx<6_Iw&^;Ob z9H#B1*pt8n{#%NBti^0zEHxV?2Qa|irWEiZZjDg^Mg%EAiTVuCNA7#v{6=ojaX=x_ zjN`83dm36fMFtmmWYY?-H%)O2C%4iFj>H;4bBLQz1te>`l{Z0o<;Mk&Iu3J$FLUy_ zB`{9Xv2KABE>uFWXWsLK#UU(IE|`=iug;Q4qG}Ld#h#j+jwu?^L;m+b?z3DIkV^8q zM9W!8;_0d2DBp1`nFfadEUf<15IEjOq81(hB0q#?0|}eiA9uPi#0S%NOiYjUH%U8Q z1`GPpB8V#fGJDl>^=2YwQm|lQ|8;AqSU*-#Pv~*yZ(tZy2Qk&=Rccq7k5-n;yxbubbp4PrS?=K|ZFi`kAfDpT@ z2TaF!5Y-U+MEFS1Dgce5=tdkQh_DQwbBk&@1Fk}$m(rx=uID+-@E|7|ae7wP=Xkq- z%!Jc1C1tb_mql|Kz2|}~v{SQ~9T)-ew!etuOT)n4$wl-yABoOQeMNZM!sSlq$&cgRNRzmO;LmT`RZparE%n($t}+kCq6q)F}m}T3$vIo7E#s4*9z~leSUf%)(V_ zYO+k^tqL75W{ASv7gWGmf#FuZm&o?OP@1((7&tZ8@8*yo!$RE40?a*K`5$q-*a`G_ zZf7j1^W{g;3xpF206DR308jI&9-iLM&4ARs8QB)OpcBr<r2xT4@` zLD=)U`Zy;zG*hdJ<8owo1=`<8v!KlSY>AHQgxv@P6-IW4H1;xh1X@ixfKv259fvu> zuW)K6uEl6}VeA5^R>Eo9Ti9&ZPy$n=JOVv5ssJs3s-5JGcH&6l#Ve*vksHZbxHY&k zQH#X`&__{6FZc|qkyLElmZOLV4gE;RZ6tOiK$ng9`(^W;0cpASEhn*(P>`UeiF5XU zL4qjmCEl(;E){MzZ&<3cND+rS#su`>0^-2)$YI(U7ym?83KHOq+0XMT0Z&+L33|u> z-j9ZGQ>mKH%Q46v(Nve}m&*h|TxH`ZI7J8F313&&8;6W`SKe?=XCOU+^Dh#IELSZ3 zLM=X`Vd3ch<+#$&*|5CD1)cz2Ys%qy;a6LfC4VaBo5QcJi=lezq}WfVzDbLLh5L;G z$(S+OGz#be_MrzTpa*!}W9he-KvRG}oR*Wg<$GF%2F1<3mVG{6%xkv2>{Lt3GeNe^ zJ!c7m+Zu<{tW}+SzgG)CTRWfWMOKkRB{~@`%#lBGrhDN@aiE=tkYeFHb4G5?~Tey>jdK&S85=^akc{cZqXVt^{y zP%H~p!v&%R*m}~C6|}@n&?-*6D!ol^OhyZ7?t%aefO9v{gSf5`d03s)Dadt=le?j0C^itb_AUEFBm85Tt& zr#Oi?!m^; z?OCvaq_s?boJ4>S@_TSn=i#Rj&d>Xbz{8(S-M)gBCORvt6Gb* zN$d+-^*+cEYAkw&C>EAzfr%ED6wCxAQnwEAyr`KB>69bl8sgJFz@$jNVcZB-5Kjnxyf_X4RrCv} zxBg)+8F9}(%(Le?VrQ7Gy-uxR)aZd&Uq_8PFnLJ5Vxv8$p1N13|ESadp_BPM8xRT0 zI(I2iIE&5|_ii+dYEt3aG~uJ<>?+xTmpZ+PH3udw zFCp@^wq(#e8L__BZ_HqSG{1{)SH=I{;FucbHJi$(c<>AQqUw##Fqy{Uo5jMHtqygr@*O!L2)Ol_bo3mRE7ZNvyB7|j!&zkqRoW@(PR2^ELBdAny47<94xFtDQi;YRSn=a9@_nCn>d*bf z=PtTM#(d8uN%#kiDBieD#KAh1>+kkqQ~!%x_q`x*C+jW|jkv!$HN{OS_#FKlXtZ7U zyn?Gituou}_@rm-Ep)Hl?sr+!O2Cn7aU~E*V)+jOB3rRg(5lUGvqTciW$b7d;3w(X z0mLt`>VlFcrhyU^*7TU06?&^1JC#(#^%#6?l~k(rUEO{$Cr^g1za#!X1eqAK^-yPP z-BhEmE|L|3DUhLc{_m*sg*F)tqdniPA^H0{eO{+Q=FoBu(^Za(&8c^xsB8QB)qVcm z*&L}vC8arY2C|vsUX!hl_I04!GD5~d7c;aALp?BX(e&PRW*OMHNoALsPflUc-xVsi z*HPA3)XZs%qhj6jQTm|6V_44&_D8fvB5hx#ZdczJ`pUF6_w~w(Yy;gRQC5`sBu7}| zWU-wS*P)k2mya!HS{ra-XA6#Y@(bt;K8fQ6;^|hXs|R&G9-FBZ^~(4~6usHlK#gM4 zIkzv45fw{fFJ;Jm@&+q|+Tsw?WicAvzCYz~KG5sBO_(R{H|=r3bOxW9%18zmjT1Nt z4w#iA-7@<6V>NeqtAUBpRBt3=^eCTnH-=giBl|c~FNr9r>#VS4NTu!9Zx_Gp=vAYG zXzX{OPMs(5XPoo9`w(HsWIg!L{7SJOxO&ZLZS6*JD*8n31WCHywT3&*z*K8*+zp8G z`}KY29JagYs#lb!m>hs*Ikp>6%Zl#TuTCEmM9 ze#^ihaYgPHFq=Q(usp((GGxL;%LNq%Xk8z8$1fNkG_-;b3O=h^84qOIB;(Pn;$PH* z-h{F%|VFM1Lxj;{5NF4YhXW}47_!6fcN941o4f3bCKq7zM zW}yuve|AC}m`@R0(TG1v&b);;Agn>9-~t@rHaX1oIt4l4dc@zrblDgUor!H5gZmLs zu^tB`rHA8y?2H=1!W5v{i`^tKsALt8kzhGl@W;$d=vMz{OLZ2NygPfeUqj+#ar28@ z*R4#KLmI#>LPhGH!yIOZ5moENQR~$v8GOISLgVv}i-XBf@)d~AM4^d0HIfh z&f{YPq*Vy6E9U+vHy|OZAt&Dc>R<@`K{@i82qNVvlmW3st0Gn9SlC_MZFw>hiTm;( zw5VAiHU#3uQ?l7mPVNRHM;RNig%KmRoedY1-p+h5vAAG)GPlU{8S>!cMxM{uA`ezv z3O0%HVZCGkY%0?4Q>TeU1ph#%2Xy)Y<&0^*6`ws(oQC>-9tJjlaxYD{tuRrRXrfS zqPYfbr+e_MwPvl+UXxZbXo4U~M+V9gVEdcW+C;T!8r(h5BCl4i(h$iaL^1`DJQ5R0 z93->^UW?uxiR8`hT0ZPlCqcAOJHm z>R!BG$@MlPgbrvKa?JC=GLMIKOzA8Q3EQ6J2DP+48oBy zY0s%+O?(Ag%jLw0t(~jZc(0@1g<})yu)epNel+0NgU(~1fi?NNdSU)euJri7sg*qx-uja zX&;>%rds7@>7iq0(=6hArhcj3xLg;rZF(?=LWYBXs4=vWH576W?XX1zPIqDRr;AoB zayfFC;tfk>iyY@LisX2WkNVi`;3e?q1Im`*GDTX85c&Q}(~3+nN$e71vG zIsv|MOCr{yRvx%mLNNC0dn3|)e?Y6Gk<+*c&xEHr`Q8QZZTkQ1OqC7gIM}J=ZE2Je zZztGZ&U-WYX6nuKo0&JWv+NRrYB>LP_AOb)HpnUYE%}T*@|Ky}>Cs2tG7i;67#;X- z2N|r_cmEeoZ=s^QBcTnH4s@n6K&V=)IU^6`WW2UfSYs2gJpI^bS!a|Y>LvpeFxo+DodxH{K;Zb+&3JbsIM-jTox z_@{zY>XgD|0KS~M8MXz{=;_ZaMvFhAH7b}c(r=}CnpVw)bl!>M44Wa3LcxXq??Y12jJ_02Fv8SZrvki&IH{ z#adz_LRKe6MruFy=nE`9@L|-dhK&?C(N*G~>dJ`i zy49t8L=`ifnQ{R;@D;#adDFdSxk|RSe7!0RfTuV#O$)=Hvs`FeXbs?;Kz$cO%~edT zQlxB@&Pv`XXsD8+Fosa>W-BUa8PC^&u=E_n6hC{^?j1M)J`xq-PO&`>`&ApS;)jZ_ zwuXxc4aGf=a{fRoqni&z-g4v>koFDB7}ywBb1$`wl`hWBM|8%a5+gf;p_ND_#X_eq ztJ5UZCy3%N#F2-N%e8`~qkYtzoJa@9CPr6E2PPQ2qr!hkCqsb7VHu+QuSg?AS{%(W z_+@4J=Q{P!n_U@jeJ>9l>5QQw3nDg*a~{Pl{SGMKgGnz?o{8Me+$4c~CNH#Z=I!Eq z>w%kTU2$$z7j(@t&gXT$e6cn|vE9F6uEdEL(wnA0A+}i%5E4gT!NjH8c#AQ78{XqB zc#XF`?8I;uwm9u$kS!Kwp+tYwXWc^mxZOi!g42E$Z>1bTWgoIM_A(A;{UOp6Xtro+ z1d|O~{s?KKq>ZpK3n395Khyd-zM!2KzoK=O{wUT#_HPXSpq;U;hH~v3kTk;T3s$}h zg)RPIeV3{0WzX*A9R?Mx&ik*eY;6v;t2r_^>aPK%j$a}abg+x^$1HH}R@P*!X%Eef zH`iWcldCYR>+9?MwHTy^O5=o#&N^vIn>@8MrS-hILH!g*o5@hxS<LaM+VP%+{3#YIf$mS8tH95Z{g_`R9WGk`wUqYszFoA+2Akf>UTK7 z6PevY{wnVf{7ji^V2kzN3oVj=Rms2ZlFzqVnP5M2A^4Xrp&SIS zan-slW@e-=W6w&{#&y^lF=EzV1H781hN>(Bd3c0VYs}f2WU-9nqH7sPA$Sz$3Pd)7 z52dt9^BU^<*P+O`R$#7wjg8739d?TO*85KraEf^<8?WZ8JiO2m?k>vfoIPr=ZJH{m zv#n<@5+fT8M7j9Q=|b<->EUBmM33dh)v+1K+jPD%f4&kp*WMH^5`9R=NXT0AnX#DL zaXtQ%hDxX*0ZB76(Y9L*)T-AeZd(aD^Ar)t3gKAAAJMHS6Xs8xn52gb{s%qHXrS`& zW=alwQK2LG+PM0M4p7ZiK{P4&O}+SgdQoxOK{^8Jwuo}!D$4Qt>G zhmf>q${~Ls?VIQvNj7K1Gpx(xS-Up|!4Cd}FAX3I;b|#W4|9aCaS{lE-Q*PlRyN-W z1P3B7O(QNy_5(eDBae6Xl+jjbFFwLQ6uo&P8$e|Cbjq6bJhtjYZ@=8F^1f-iIRZ zY~-DqA!*<*#oa-UFyPcv*P*Jr!=jKXn)Aviq$^Z)l_96BLoHX}+TxK)T^C0r9U;wP zl9D#cwZ$dndd%Mhle9U?p9&fm(4?~FgMD-HEMARL3%}VqwJEFAXRp&#b)UP z6gSaclNge%HQhG5d;`Qutpx+Q78liGumGbOE5)2kM4J^A9bro`*QlH-W&@X_1gkj& z)J4v!)@Op1RUA^HIWaw~TI!1mx6)eIty?$40NT=*xf12R=O3{jfpWyMMH3ADu(cyfhSVyn(!8y$}bEnv#~#~n|6BR($M3ld;Ugn_uLdCXuNj@7Oc~7tI;&|B@MM2Zv%gyB1hHPQ5rx%;0h0LB_NRS^|%Rj z?8m0uO50?thM_~tvng?xbUnnS7jm5QHV916crnY(w@Ttaz>=Ga(nmo5t@P2ehl;^} zPpzz1o#%EocKOXYfDzVQ(Xrauqfu@KYkAaxperh5MyC}Wnc^ESsPkmFk{}P0m6Dwy zi@1?R6=PevjdVMc?5jGkJ71on2pR?hPj}d=qq+r@R)<)V6U+?E3-DH(=m@RrHlBpA zPjn}p<1OBYmoU!Z^-nqju1_8#5MEG)QPia zI?dlf#iH~QZjdY6&z@B{lGfD&Lb%$GKAY5zGh$6qWb|Q9Wt;-}3c`cG;!L!+LC3FP zSfgQj4MU8I1sez(2L)aI3tf%qYE&nUPq0C!+jZ&D3WfX-=XK~el(#i}?p4B8EWb#!h~?HDFTAw(+_~MazIblexm|6YT*C5; zt+g+`@Y1U+>aCC5TQ3~YRI>`qk8}caLtp4G*Jegp!{|I z$*n_2wFF5HP1a{pDK#jU$`Z7t4g4fXkoTP0H_q(P^>B&K^Eq zI&<_a7Tv1qLU8{fTk5Hs;BUy@H@Y^knlf)_U>(MGi%{HIYg1NBGdU%4gn5S9jfQNX zuE$xO=CL;IqI#ce@N~G_`MrxrMW$gPFnUH?ifMArHU&WP&E_R@$SgW3vzDT5YG<{9 z&EoTm2kW2LiU-v4A9L&An6(9&Pwe@X`fH#q4}U{yVe1WX3N;n%{vQ0ha}FuR;bhm@Zy5Rn0+3b7*7cE@H)lCa|1p*^y% z|K;MZ8YjXQWMZz|rb(jKCWf?@Bvamoo`pHY#$Id4#*pv0dNRP5q?af}kRWWdA!6wwPmW)`SAnK_%1Mo7Fq z3)l1>YKxKMXV0EGbDa4@bn9r$V7$aO+omFj#t*ZX1R>~gI&qQQ1x^Ehl#)2e5pLl$ zAf6==%$zp`<1KzBT4UeB6IvDo6!BAM2grg(MdbLfirY$49mqOnY1XKt0WSb1<>|E) zYuG^}C5-nn%VT9Bwxa+5P)Ih1n*{MB#OJf^WI8c7Li*@Q@O=p-DDS8J1n&n}?@)-& zt>GA#7;byuCFa%!Un2jyb`qsyat>YL+*&{L2}JBBOMZ|x_3bZ-K(YQRcK|VO?l$wc z!q>{xXKpRZNwGj1_1oIkyq)Xp^-VWK>R5R)vc zi*K`1;*?I*lrS3B=60oczp4?Z3H2NE`ph5wl&0-IdQ3}YB!K5pSjiY7GnZ6b zPGh18EQj)S{1aydhjIIgnZ)`hn^Z8&E4YjIQ=8yXoet~NLmoYb;XWSxgsLKrUYH`C z3Y4~LuBUi@ki(YZb>}(?aSXG!+!O`?Ed=*c)aq3eD$6FibU+gq*0z5?lJ z{Bf3<^rI@rF?CzlVcNP0eJwRKDF*AeI*pW6nZ+ZfzrdclgAwcg2`8zl=4~n5?iP69 zQz`D;3yM0oQ&Q&$8&GvcBk;JpRFI#5+di9jSA1rsA5@rBgF`@x6FnG)#%Q( z`4L-g?C=MQGB3t~*W9`!%Jds0#lm+AZesAnyMOeyQZiVVMt-0~3e!3&7yHIS$`B6) z0M%+0Rah}qh^P?~X$AI&JOxZdGrF_{fo$7QT@9h$sw#&J2cI$h&vV+Z7Iz3r0K2s0Nvkp!U_j=VD zH&8#fRfN?YW{y!Y^NB+Nau7Y{_C_(+`FPcr1^h9uQtPIH;$|*3*f$erdu>1g|Jd(q z8V^a&y;E%hTs6ajF1U7($918L8#+n+SOLVuA{DHZ6oV#+1k5s;B-q?Zr~11jkm%<%!n|Jb6ICdtS4W$ zEfy6{qR#>&>V&J)9m1h82jM8o)#9uzP;Kq%MfhYz3)Jls{G{nhXbib#g1{kHBOx zhK>26$=)00w7Lq*n)qfFJFaTi(m{#vK$wz~Igt<5W2V0~;^wbWw;6?=jv z-Lh9t@ZhgiwA!3c2D)F>qw0%;9D|!eI)|bGx<%jSqoK1wxy~wUNI$@{TEj1rU_C4C zhtVrc%50zmLBJgcW=>K@XZyu{)F)U&#^6a^2~OULm+k3MG@=+lQcasqd$9K`_Yg$6 zw-463Zryt1iekEd^h8freS{mE*l63j@|=j)Hq{zs6lQHq+WX_ktW0s7I+3o#Y8ycV z=I&5izCu61zK_bOOH7}|--?luP#tr01ITWisPVm?Sdp6;XUnp2tP1BtdBDWNbmwgoi>QV%Lsd?`LO^|=}$Vik{AcSD7aG4CYv9c zX3jG$8%(J!mZ}{Df>SyPn2cWbLr`#9kIv|1^s+ak)_x81U24C>?&7DVn-lclIDFS& zU%U$h1<$JVUco(BQQzO*>0N^rrL2M#(h>(bz8)$cK4U}=W$Bf{{bwZ#3)e`8A2vB5 ztV;03LB&}REjWm5E-!U-IPzkaL#hj&h28~y4H}h$%{1OW>8AO#Gd6^G{)Eavk<8}q z=vO4PHgj_Xc?O4RoRZq>!n7r9G2I)Zr)f}>F=EIhv%4W?y~CEU`U}+3WxqpL9~JR6 zFg4^i&D#zaKCdf*E>7~c=%lRdHjMT$L7po<-PC@x>e#+<=mPjI4S_8=@z$jzJjpvi z8xm-kTO0a%whQjJATBnCZZ*oP=1zTrUlAAG4-ncE={jtP9_B*MTtcg;(-OfZDXYH$ z1-0$^jRyZf0?j=xTWl@DH0X4+h>lZPn@TG3&O5t!j_u#@m8_V%Opp=|PW4aZUb){0&8IHOeK zjEAY8{(K4rkBbvN7^I;PE?saH!iS7On?NK_Z zm84;@E+a4u=!AF=l}Ai)Rkf}~)4{_Zle7XdXtV<3qSq*$=?os*I<1ZC1SG^y2R@`B z*6|?@Q?P=k%>SdGo+sVr=08H74%c&5UEc#9fu>?fv%HnPlArDS{&?T_?co%L{S|DE zF|>Gj&6ZiU6$U;P3MxAFKG4HKB_2Gb4~+25yczJaf1SoI2xb zPKf=>p5pPWWEA_|u|w4Rw1sgQ=&u3L*L)R+iE!``K+goQ-~h%yV6Vvptik~+rQ^6c zPQKTka@cJYBihei6VH)uY7=FQU@ToE026zj;b zCcU1gPC=3W?t@?J-bJ@uD*DSIbztlszOzY?P5;GcZu76fh^Pd zkccThzQY!?U8yr94E&LnILNU=k=+ry{50Yh_>MV|jfSY@bHp!2L#Wp}e7&+L+ zg_K3|7!yCOApxpAgKfAW(ozQ7@)n^Z;cWz|4rVWM6=iMjxnKZd4c!~q-U4DTtEGYn z=Qz@>WDidJYdZiwQ&!tP;`OK&#7aLmQW4=)t=T7aq;)TS8BRcV1Of9c!mJx$@)D zLQ|HxY1X*0nd}scGv`W7Yfz+u`arXXTIYGN)agvXCLJhc706gu(~9sorw&T3u#v`G z2KOAXw^BZVcYVD04bt`D^~sV(3{NV=>p(2%3fX#b`iZG zUxr2y4deGCVF=AgGfHtYH6tMF{OP)L*g~Bq;wVN`MRT*kXeSpoOSF^i0SB|LaGE9F z;*w%lEvg&bw4$uwztL%lQ;a1Q=UvsYw0As_S5v5Urd$_ce!A0R+8(oYTYn>N4S`}u zc*m+A{xBC>@YC^(f7^R20ly1`K}$eS*Sv7oTWXZtv+(eBW*hpJU%ee4p`6@ zm1a=bHsCQ%_4`uOSNd z0SF@BI`VfwB<}Df)z^A`u+l%QO6x;{k-Qc;n{;?wqkE_`#28??{Q+{}&st$GZCvRK zZuqR+Z_|^^0tA zMWFryCMcg(-DZGwifY~AQ$Ncm-o+rGIEwNaD3!jfCW_AyL$!`(wKfej2IjJpb)X$; z!w`qH;}{oiMUsd|DDyMBx`-#2X#gO}%;9_{vB6J$BE=rWNyRocoSf?OU$>CltzDe} z{WEUnquQ4`UrR0^V}x)da!{k$U>lK1NA16huLo#Q#m`rDJ;#Zqgaq{uUGOf^CCA`v zEkiFdWry#y02Kv2G7i*0vImz69`qG;lL^9%zs7u3NA=NLZI3p*l6SD-S*x0j<8QoV zno1!_mfSirs-X)$s*^q#jm89Xrc$eQh^E!)6=g9H6$$>hDmFr~=#!8%#?tv>K5N8z zhQxtC(VBxCtHUP}#}78+RzC3>lHq90nWgh|y4IY#NM47{kY(34IBe0bjSduL@|2sWmsPXi<;VP%m}IqLQtj2>TI~Sm@F`jh!Rq4XWWE|KtW2|WsI3#zdT@e(;moR4W9%k%7IkHgFX`5o zy4FHT(gen-)2W@f%iWH~lg#?Gi0PE=$*tEbgyd)1bOP6uHDj!&x+Qk(v&Eh4r_$Z7 zV?thO#{YKTV9q(OR-7zkpd>omBz{XUfIZvflyia7JY)*m0B9(*qP*;zGU^(S*i6XY zb6bV~CbLcMqf6mM=LZq*W@vKtr~(OAC2Kp~vv7 z#0`Gq%3)TGR>)tSK{om83w%o0OO84GDp+CC_dXBS{Z6!-$TSQfK!6N0heHosXNp}V z38Eak67`H&G&LVuwoDiMzT~#NSU7pRs5a?(*+~h0)5pAUs2x8Tzb#Pe02@SUJ%k{(Mm{K_Yp~Kp zfxaR8M5?vhxnZzfa*?S=bPlG@f(v#V2Qe?!h>(8tRPlZL9&BY_*i)K(@qP=L4JKRA z#7HqnF6_AH;{W#Qtm;N%3Q$Uz)s_o5{=cf`)D@v9_?+sj%l^LpoIcDN#!BlwrpJd0FD=MRYEt2oCmX?qy|v1P z^32t}xOS1U8ntKm@ZnAk;03g{!S=hqt~nNOq6wOmPC$qsi$q88)S9L<^hzsdUkhdc zvM!x>kocU2In?P{zR{rvruI-m-;mpA3m|g2c(k=C2M*|3ztC-Fj~q~D_pc*)T01_% z6A4*Xd+Is|LdR{d%~g6qPJ9`-9CRCy2t^2&)1K))92QvC$}NI}@Ww7u3W6cpPQVYT zjKCP+fg9*iOwP<+P@sm_Fas!@NZZe{&OclKL0dWaeakua8Ov2A$Wad7u)Uwes@$`2y}4t- zgJLStb8(t#-lS9N`ixI;j zCzD?`sTggq2RNoA!}PY=Lu?T;3Z7t?W7$Q!;mOlvB{ZET-yMP`Q4+~5BZ=0%eXt~~ zdMokO>>I&%Q?=mpI;p}@fvA{@L)Qqdve|E?T$|_XK2M1F?moY#*0_Mnq0-P$=_R`k z{)=uwU*Kx>zTe&H5v@CN^w^6STXY1Gkcr)~Z7$4+PSh=yJHn}a} zu9NiA|0$be?Ot!LYkUTjV;aY`WiYRB6Lb3LaUYo2kQin(foDa3=P3Sh^|y#yf_`=JHe6^YDDc|8esilH zkwvFa*#4Hj{cm)Nbow4u^M+2swGNI{cHj8gmkk|%7Y~y9s1R(P0b}(^Hvhgr>FR~< zpRPs93;|0ujBT}p&6*G(bVZ0W^MVIX2 z5y#kq0AgCAYPcx&>hgEI;1@I(20IRaYI7PE@e5{7sxiC;&Ix|tR*gvW$QrSc3vKoh zuLl*4fcNW(J8A0#w+gcd5Z|vFt*F2#k(V0i&s+_j?bg5xzN8wcGM&F+F2v_p4gQM8 zh=$ops$+gq76++)p)^}W|Je((moh0+hM(D>(|fATf<5oA{bn~ znZuu~fcdEDax0BV$zr*TlJq3gG|3vTfMlrw!JjW-wH zfb+Od>R_$Ku|s?KDNbUcg|Y5DWPz( z=r%aOLD@Dbkfed{akF7lQ1gkBmWb}%ePd5&lUQPDRN3C36GK2>kh-8#@?buvFHm^| z2#3@w7!#jns!5C`E^K|KwnUT@tK`Wux7aAig_*W$SzHNx2vAE@2zu?1K&|RiQQ~C2=BghfvpM^TKbCi#Of1=t+@Ex+SF9htV20MD)-4VA1GGXp@G|{ zs`aHSZp%+Ra9MNF-9)Jxve~H>!{$NF>dX?`X1QaT92qmKG{1T%jT*P$58wGzp2-CaG!zRjF7aTtY?CNMjJhmk1CXClVY=0i_%&uoTxb8uk zvs+Qip4-~m+F0R&TO0mWP^d|rk_JoXUXdM7Nnc5??VFG|+M-)uR(r*Po@t2#P4lfK zr{5~vua5Gor7T~mm)^Be&vijUdxrla&AGspqTYqw;~aa9BA=<6Za=x&ew49~Y~b5s zShZ48lkU>%zN8d9uiGa`bCZp!dfi9i`hs&y$y)nH=?^JQfckWxZj)rjIc;|Oa5CMe z5+`_{*}eq{v05d>%&)bX*{1EC7UYTKEV@Rtfx2O%DI+I!sibUes}@S)D+=P^4$_>< zy}!PB;kCeurr_G^glAZMPw1q+VF)J(Q2#Mgy6kWuv++4) zOkXeKex~hEpBZ;mSxDU#B14GRh&N2$pAKl7V8xTiZN%zgurw;LCnHLaDNKZBQ5h)D z-A?`gP}OaVAT5Z~605cB$NgT8+erO^vJ;fO0$c=M=h&|l`Igk@z92iqLd`g4qlRYz7I}S02w=yKf^D$5U8Iah z7$F5^>23p%kUCT=Qyg?2SDVDy8NL1&Im|mZMrsb21&#oZ01pCB0e1qA0?z>(z?;BZ z!27@@;49!8L0ay1wZwaJ8{yn{JV#yz)`1Ryu*TwPxeKJ;2HpYQ1ug*}3DOEf8WZf9Q_Y!$ zMtyOiVJ@G{^^hCkMq~W$_LABSi~?2Q05A<;4d^XP4w9+^$ABjR^g+GS4jOlE89-6Q zYg8a|G#Q>J{NWyqfVZjduJe;|zhk;=^HFK^u#XOfAC$iKp8_m-k)V3?(o}GVFHFmi z6fqi4S17BMGN-~~9&UNZD~L{uR;|hNAj+fsEL?pjee;HIDW~Bl5gii1P70ab0JY!=N}FRl*>C z+Ws|=($$`PW;kEDBR*6qR?2dfFjN_h2I6u&7T+2_5Fd_);(1-67&YPQv;XD`{{gRX BWxxOc literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c705db706aa71d0963590b3c31e3a06f502e4a61 GIT binary patch literal 20572 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edee9fa6c95dfac6b386160256becd1260e5c373 GIT binary patch literal 27712 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 WXQyM~?DvNH|8^&a5@j5p;r|DUZXG%R literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/compat.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/compat.py new file mode 100644 index 000000000..ca561dd2e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/compat.py @@ -0,0 +1,1137 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2017 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import absolute_import + +import os +import re +import shutil +import sys + +try: + import ssl +except ImportError: # pragma: no cover + ssl = None + +if sys.version_info[0] < 3: # pragma: no cover + from StringIO import StringIO + string_types = basestring, + text_type = unicode + from types import FileType as file_type + import __builtin__ as builtins + import ConfigParser as configparser + from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit + from urllib import (urlretrieve, quote as _quote, unquote, url2pathname, + pathname2url, ContentTooShortError, splittype) + + def quote(s): + if isinstance(s, unicode): + s = s.encode('utf-8') + return _quote(s) + + import urllib2 + from urllib2 import (Request, urlopen, URLError, HTTPError, + HTTPBasicAuthHandler, HTTPPasswordMgr, HTTPHandler, + HTTPRedirectHandler, build_opener) + if ssl: + from urllib2 import HTTPSHandler + import httplib + import xmlrpclib + import Queue as queue + from HTMLParser import HTMLParser + import htmlentitydefs + raw_input = raw_input + from itertools import ifilter as filter + from itertools import ifilterfalse as filterfalse + + # Leaving this around for now, in case it needs resurrecting in some way + # _userprog = None + # def splituser(host): + # """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" + # global _userprog + # if _userprog is None: + # import re + # _userprog = re.compile('^(.*)@(.*)$') + + # match = _userprog.match(host) + # if match: return match.group(1, 2) + # return None, host + +else: # pragma: no cover + from io import StringIO + string_types = str, + text_type = str + from io import TextIOWrapper as file_type + import builtins + import configparser + from urllib.parse import (urlparse, urlunparse, urljoin, quote, unquote, + urlsplit, urlunsplit, splittype) + from urllib.request import (urlopen, urlretrieve, Request, url2pathname, + pathname2url, HTTPBasicAuthHandler, + HTTPPasswordMgr, HTTPHandler, + HTTPRedirectHandler, build_opener) + if ssl: + from urllib.request import HTTPSHandler + from urllib.error import HTTPError, URLError, ContentTooShortError + import http.client as httplib + import urllib.request as urllib2 + import xmlrpc.client as xmlrpclib + import queue + from html.parser import HTMLParser + import html.entities as htmlentitydefs + raw_input = input + from itertools import filterfalse + filter = filter + +try: + from ssl import match_hostname, CertificateError +except ImportError: # pragma: no cover + + class CertificateError(ValueError): + pass + + def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + parts = dn.split('.') + leftmost, remainder = parts[0], parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" % + (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" % + (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") + + +try: + from types import SimpleNamespace as Container +except ImportError: # pragma: no cover + + class Container(object): + """ + A generic container for when multiple values need to be returned + """ + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +try: + from shutil import which +except ImportError: # pragma: no cover + # Implementation from Python 3.3 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + + # Check that a given file can be accessed with the correct mode. + # Additionally check that `file` is not a directory, as on Windows + # directories pass the os.access check. + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn)) + + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + if os.curdir not in path: + path.insert(0, os.curdir) + + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if normdir not in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None + + +# ZipFile is a context manager in 2.7, but not in 2.6 + +from zipfile import ZipFile as BaseZipFile + +if hasattr(BaseZipFile, '__enter__'): # pragma: no cover + ZipFile = BaseZipFile +else: # pragma: no cover + from zipfile import ZipExtFile as BaseZipExtFile + + class ZipExtFile(BaseZipExtFile): + + def __init__(self, base): + self.__dict__.update(base.__dict__) + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + # return None, so if an exception occurred, it will propagate + + class ZipFile(BaseZipFile): + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + # return None, so if an exception occurred, it will propagate + + def open(self, *args, **kwargs): + base = BaseZipFile.open(self, *args, **kwargs) + return ZipExtFile(base) + + +try: + from platform import python_implementation +except ImportError: # pragma: no cover + + def python_implementation(): + """Return a string identifying the Python implementation.""" + if 'PyPy' in sys.version: + return 'PyPy' + if os.name == 'java': + return 'Jython' + if sys.version.startswith('IronPython'): + return 'IronPython' + return 'CPython' + + +import sysconfig + +try: + callable = callable +except NameError: # pragma: no cover + from collections.abc import Callable + + def callable(obj): + return isinstance(obj, Callable) + + +try: + fsencode = os.fsencode + fsdecode = os.fsdecode +except AttributeError: # pragma: no cover + # Issue #99: on some systems (e.g. containerised), + # sys.getfilesystemencoding() returns None, and we need a real value, + # so fall back to utf-8. From the CPython 2.7 docs relating to Unix and + # sys.getfilesystemencoding(): the return value is "the user’s preference + # according to the result of nl_langinfo(CODESET), or None if the + # nl_langinfo(CODESET) failed." + _fsencoding = sys.getfilesystemencoding() or 'utf-8' + if _fsencoding == 'mbcs': + _fserrors = 'strict' + else: + _fserrors = 'surrogateescape' + + def fsencode(filename): + if isinstance(filename, bytes): + return filename + elif isinstance(filename, text_type): + return filename.encode(_fsencoding, _fserrors) + else: + raise TypeError("expect bytes or str, not %s" % + type(filename).__name__) + + def fsdecode(filename): + if isinstance(filename, text_type): + return filename + elif isinstance(filename, bytes): + return filename.decode(_fsencoding, _fserrors) + else: + raise TypeError("expect bytes or str, not %s" % + type(filename).__name__) + + +try: + from tokenize import detect_encoding +except ImportError: # pragma: no cover + from codecs import BOM_UTF8, lookup + + cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)") + + def _get_normal_name(orig_enc): + """Imitates get_normal_name in tokenizer.c.""" + # Only care about the first 12 characters. + enc = orig_enc[:12].lower().replace("_", "-") + if enc == "utf-8" or enc.startswith("utf-8-"): + return "utf-8" + if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \ + enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")): + return "iso-8859-1" + return orig_enc + + def detect_encoding(readline): + """ + The detect_encoding() function is used to detect the encoding that should + be used to decode a Python source file. It requires one argument, readline, + in the same way as the tokenize() generator. + + It will call readline a maximum of twice, and return the encoding used + (as a string) and a list of any lines (left as bytes) it has read in. + + It detects the encoding from the presence of a utf-8 bom or an encoding + cookie as specified in pep-0263. If both a bom and a cookie are present, + but disagree, a SyntaxError will be raised. If the encoding cookie is an + invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found, + 'utf-8-sig' is returned. + + If no encoding is specified, then the default of 'utf-8' will be returned. + """ + try: + filename = readline.__self__.name + except AttributeError: + filename = None + bom_found = False + encoding = None + default = 'utf-8' + + def read_or_stop(): + try: + return readline() + except StopIteration: + return b'' + + def find_cookie(line): + try: + # Decode as UTF-8. Either the line is an encoding declaration, + # in which case it should be pure ASCII, or it must be UTF-8 + # per default encoding. + line_string = line.decode('utf-8') + except UnicodeDecodeError: + msg = "invalid or missing encoding declaration" + if filename is not None: + msg = '{} for {!r}'.format(msg, filename) + raise SyntaxError(msg) + + matches = cookie_re.findall(line_string) + if not matches: + return None + encoding = _get_normal_name(matches[0]) + try: + codec = lookup(encoding) + except LookupError: + # This behaviour mimics the Python interpreter + if filename is None: + msg = "unknown encoding: " + encoding + else: + msg = "unknown encoding for {!r}: {}".format( + filename, encoding) + raise SyntaxError(msg) + + if bom_found: + if codec.name != 'utf-8': + # This behaviour mimics the Python interpreter + if filename is None: + msg = 'encoding problem: utf-8' + else: + msg = 'encoding problem for {!r}: utf-8'.format( + filename) + raise SyntaxError(msg) + encoding += '-sig' + return encoding + + first = read_or_stop() + if first.startswith(BOM_UTF8): + bom_found = True + first = first[3:] + default = 'utf-8-sig' + if not first: + return default, [] + + encoding = find_cookie(first) + if encoding: + return encoding, [first] + + second = read_or_stop() + if not second: + return default, [first] + + encoding = find_cookie(second) + if encoding: + return encoding, [first, second] + + return default, [first, second] + + +# For converting & <-> & etc. +try: + from html import escape +except ImportError: + from cgi import escape +if sys.version_info[:2] < (3, 4): + unescape = HTMLParser().unescape +else: + from html import unescape + +try: + from collections import ChainMap +except ImportError: # pragma: no cover + from collections import MutableMapping + + try: + from reprlib import recursive_repr as _recursive_repr + except ImportError: + + def _recursive_repr(fillvalue='...'): + ''' + Decorator to make a repr function return fillvalue for a recursive + call + ''' + + def decorating_function(user_function): + repr_running = set() + + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result + + # Can't use functools.wraps() here because of bootstrap issues + wrapper.__module__ = getattr(user_function, '__module__') + wrapper.__doc__ = getattr(user_function, '__doc__') + wrapper.__name__ = getattr(user_function, '__name__') + wrapper.__annotations__ = getattr(user_function, + '__annotations__', {}) + return wrapper + + return decorating_function + + class ChainMap(MutableMapping): + ''' + A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + accessed or updated using the *maps* attribute. There is no other state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + ''' + + def __init__(self, *maps): + '''Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + ''' + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + return mapping[ + key] # can't use 'key in mapping' with defaultdict + except KeyError: + pass + return self.__missing__( + key) # support subclasses that define __missing__ + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + return len(set().union( + *self.maps)) # reuses stored hash values if possible + + def __iter__(self): + return iter(set().union(*self.maps)) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + @_recursive_repr() + def __repr__(self): + return '{0.__class__.__name__}({1})'.format( + self, ', '.join(map(repr, self.maps))) + + @classmethod + def fromkeys(cls, iterable, *args): + 'Create a ChainMap with a single dict created from the iterable.' + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self): # like Django's Context.push() + 'New ChainMap with a new dict followed by all previous maps.' + return self.__class__({}, *self.maps) + + @property + def parents(self): # like Django's Context.pop() + 'New ChainMap from maps[1:].' + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError( + 'Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError( + 'Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() + + +try: + from importlib.util import cache_from_source # Python >= 3.4 +except ImportError: # pragma: no cover + + def cache_from_source(path, debug_override=None): + assert path.endswith('.py') + if debug_override is None: + debug_override = __debug__ + if debug_override: + suffix = 'c' + else: + suffix = 'o' + return path + suffix + + +try: + from collections import OrderedDict +except ImportError: # pragma: no cover + # {{{ http://code.activestate.com/recipes/576693/ (r9) + # Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. + # Passes Python2.7's test suite and incorporates all the latest updates. + try: + from thread import get_ident as _get_ident + except ImportError: + from dummy_thread import get_ident as _get_ident + + try: + from _abcoll import KeysView, ValuesView, ItemsView + except ImportError: + pass + + class OrderedDict(dict): + 'Dictionary that remembers insertion order' + + # An inherited dict maps keys to values. + # The inherited dict provides __getitem__, __len__, __contains__, and get. + # The remaining methods are order-aware. + # Big-O running times for all methods are the same as for regular dictionaries. + + # The internal self.__map dictionary maps keys to links in a doubly linked list. + # The circular doubly linked list starts and ends with a sentinel element. + # The sentinel element never gets deleted (this simplifies the algorithm). + # Each link is stored as a list of length three: [PREV, NEXT, KEY]. + + def __init__(self, *args, **kwds): + '''Initialize an ordered dictionary. Signature is the same as for + regular dictionaries, but keyword arguments are not recommended + because their insertion order is arbitrary. + + ''' + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % + len(args)) + try: + self.__root + except AttributeError: + self.__root = root = [] # sentinel node + root[:] = [root, root, None] + self.__map = {} + self.__update(*args, **kwds) + + def __setitem__(self, key, value, dict_setitem=dict.__setitem__): + 'od.__setitem__(i, y) <==> od[i]=y' + # Setting a new item creates a new link which goes at the end of the linked + # list, and the inherited dictionary is updated with the new key/value pair. + if key not in self: + root = self.__root + last = root[0] + last[1] = root[0] = self.__map[key] = [last, root, key] + dict_setitem(self, key, value) + + def __delitem__(self, key, dict_delitem=dict.__delitem__): + 'od.__delitem__(y) <==> del od[y]' + # Deleting an existing item uses self.__map to find the link which is + # then removed by updating the links in the predecessor and successor nodes. + dict_delitem(self, key) + link_prev, link_next, key = self.__map.pop(key) + link_prev[1] = link_next + link_next[0] = link_prev + + def __iter__(self): + 'od.__iter__() <==> iter(od)' + root = self.__root + curr = root[1] + while curr is not root: + yield curr[2] + curr = curr[1] + + def __reversed__(self): + 'od.__reversed__() <==> reversed(od)' + root = self.__root + curr = root[0] + while curr is not root: + yield curr[2] + curr = curr[0] + + def clear(self): + 'od.clear() -> None. Remove all items from od.' + try: + for node in self.__map.itervalues(): + del node[:] + root = self.__root + root[:] = [root, root, None] + self.__map.clear() + except AttributeError: + pass + dict.clear(self) + + def popitem(self, last=True): + '''od.popitem() -> (k, v), return and remove a (key, value) pair. + Pairs are returned in LIFO order if last is true or FIFO order if false. + + ''' + if not self: + raise KeyError('dictionary is empty') + root = self.__root + if last: + link = root[0] + link_prev = link[0] + link_prev[1] = root + root[0] = link_prev + else: + link = root[1] + link_next = link[1] + root[1] = link_next + link_next[0] = root + key = link[2] + del self.__map[key] + value = dict.pop(self, key) + return key, value + + # -- the following methods do not depend on the internal structure -- + + def keys(self): + 'od.keys() -> list of keys in od' + return list(self) + + def values(self): + 'od.values() -> list of values in od' + return [self[key] for key in self] + + def items(self): + 'od.items() -> list of (key, value) pairs in od' + return [(key, self[key]) for key in self] + + def iterkeys(self): + 'od.iterkeys() -> an iterator over the keys in od' + return iter(self) + + def itervalues(self): + 'od.itervalues -> an iterator over the values in od' + for k in self: + yield self[k] + + def iteritems(self): + 'od.iteritems -> an iterator over the (key, value) items in od' + for k in self: + yield (k, self[k]) + + def update(*args, **kwds): + '''od.update(E, **F) -> None. Update od from dict/iterable E and F. + + If E is a dict instance, does: for k in E: od[k] = E[k] + If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] + Or if E is an iterable of items, does: for k, v in E: od[k] = v + In either case, this is followed by: for k, v in F.items(): od[k] = v + + ''' + if len(args) > 2: + raise TypeError('update() takes at most 2 positional ' + 'arguments (%d given)' % (len(args), )) + elif not args: + raise TypeError('update() takes at least 1 argument (0 given)') + self = args[0] + # Make progressively weaker assumptions about "other" + other = () + if len(args) == 2: + other = args[1] + if isinstance(other, dict): + for key in other: + self[key] = other[key] + elif hasattr(other, 'keys'): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + __update = update # let subclasses override update without breaking __init__ + + __marker = object() + + def pop(self, key, default=__marker): + '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + + ''' + if key in self: + result = self[key] + del self[key] + return result + if default is self.__marker: + raise KeyError(key) + return default + + def setdefault(self, key, default=None): + 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' + if key in self: + return self[key] + self[key] = default + return default + + def __repr__(self, _repr_running=None): + 'od.__repr__() <==> repr(od)' + if not _repr_running: + _repr_running = {} + call_key = id(self), _get_ident() + if call_key in _repr_running: + return '...' + _repr_running[call_key] = 1 + try: + if not self: + return '%s()' % (self.__class__.__name__, ) + return '%s(%r)' % (self.__class__.__name__, self.items()) + finally: + del _repr_running[call_key] + + def __reduce__(self): + 'Return state information for pickling' + items = [[k, self[k]] for k in self] + inst_dict = vars(self).copy() + for k in vars(OrderedDict()): + inst_dict.pop(k, None) + if inst_dict: + return (self.__class__, (items, ), inst_dict) + return self.__class__, (items, ) + + def copy(self): + 'od.copy() -> a shallow copy of od' + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S + and values equal to v (which defaults to None). + + ''' + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + if isinstance(other, OrderedDict): + return len(self) == len( + other) and self.items() == other.items() + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + # -- the following methods are only used in Python 2.7 -- + + def viewkeys(self): + "od.viewkeys() -> a set-like object providing a view on od's keys" + return KeysView(self) + + def viewvalues(self): + "od.viewvalues() -> an object providing a view on od's values" + return ValuesView(self) + + def viewitems(self): + "od.viewitems() -> a set-like object providing a view on od's items" + return ItemsView(self) + + +try: + from logging.config import BaseConfigurator, valid_ident +except ImportError: # pragma: no cover + IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) + + def valid_ident(s): + m = IDENTIFIER.match(s) + if not m: + raise ValueError('Not a valid Python identifier: %r' % s) + return True + + # The ConvertingXXX classes are wrappers around standard Python containers, + # and they serve to convert any suitable values in the container. The + # conversion converts base dicts, lists and tuples to their wrapped + # equivalents, whereas strings which match a conversion format are converted + # appropriately. + # + # Each wrapper should have a configurator attribute holding the actual + # configurator to use for conversion. + + class ConvertingDict(dict): + """A converting dictionary wrapper.""" + + def __getitem__(self, key): + value = dict.__getitem__(self, key) + result = self.configurator.convert(value) + # If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def get(self, key, default=None): + value = dict.get(self, key, default) + result = self.configurator.convert(value) + # If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def pop(self, key, default=None): + value = dict.pop(self, key, default) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + class ConvertingList(list): + """A converting list wrapper.""" + + def __getitem__(self, key): + value = list.__getitem__(self, key) + result = self.configurator.convert(value) + # If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def pop(self, idx=-1): + value = list.pop(self, idx) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + return result + + class ConvertingTuple(tuple): + """A converting tuple wrapper.""" + + def __getitem__(self, key): + value = tuple.__getitem__(self, key) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + class BaseConfigurator(object): + """ + The configurator base class which defines some useful defaults. + """ + + CONVERT_PATTERN = re.compile(r'^(?P[a-z]+)://(?P.*)$') + + WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') + DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') + INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') + DIGIT_PATTERN = re.compile(r'^\d+$') + + value_converters = { + 'ext': 'ext_convert', + 'cfg': 'cfg_convert', + } + + # We might want to use a different one, e.g. importlib + importer = staticmethod(__import__) + + def __init__(self, config): + self.config = ConvertingDict(config) + self.config.configurator = self + + def resolve(self, s): + """ + Resolve strings to objects using standard import and attribute + syntax. + """ + name = s.split('.') + used = name.pop(0) + try: + found = self.importer(used) + for frag in name: + used += '.' + frag + try: + found = getattr(found, frag) + except AttributeError: + self.importer(used) + found = getattr(found, frag) + return found + except ImportError: + e, tb = sys.exc_info()[1:] + v = ValueError('Cannot resolve %r: %s' % (s, e)) + v.__cause__, v.__traceback__ = e, tb + raise v + + def ext_convert(self, value): + """Default converter for the ext:// protocol.""" + return self.resolve(value) + + def cfg_convert(self, value): + """Default converter for the cfg:// protocol.""" + rest = value + m = self.WORD_PATTERN.match(rest) + if m is None: + raise ValueError("Unable to convert %r" % value) + else: + rest = rest[m.end():] + d = self.config[m.groups()[0]] + while rest: + m = self.DOT_PATTERN.match(rest) + if m: + d = d[m.groups()[0]] + else: + m = self.INDEX_PATTERN.match(rest) + if m: + idx = m.groups()[0] + if not self.DIGIT_PATTERN.match(idx): + d = d[idx] + else: + try: + n = int( + idx + ) # try as number first (most likely) + d = d[n] + except TypeError: + d = d[idx] + if m: + rest = rest[m.end():] + else: + raise ValueError('Unable to convert ' + '%r at %r' % (value, rest)) + # rest should be empty + return d + + def convert(self, value): + """ + Convert values to an appropriate type. dicts, lists and tuples are + replaced by their converting alternatives. Strings are checked to + see if they have a conversion format and are converted if they do. + """ + if not isinstance(value, ConvertingDict) and isinstance( + value, dict): + value = ConvertingDict(value) + value.configurator = self + elif not isinstance(value, ConvertingList) and isinstance( + value, list): + value = ConvertingList(value) + value.configurator = self + elif not isinstance(value, ConvertingTuple) and isinstance(value, tuple): + value = ConvertingTuple(value) + value.configurator = self + elif isinstance(value, string_types): + m = self.CONVERT_PATTERN.match(value) + if m: + d = m.groupdict() + prefix = d['prefix'] + converter = self.value_converters.get(prefix, None) + if converter: + suffix = d['suffix'] + converter = getattr(self, converter) + value = converter(suffix) + return value + + def configure_custom(self, config): + """Configure an object with a user-supplied factory.""" + c = config.pop('()') + if not callable(c): + c = self.resolve(c) + props = config.pop('.', None) + # Check for valid identifiers + kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) + result = c(**kwargs) + if props: + for name, value in props.items(): + setattr(result, name, value) + return result + + def as_tuple(self, value): + """Utility function which converts lists to tuples.""" + if isinstance(value, list): + value = tuple(value) + return value diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/database.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/database.py new file mode 100644 index 000000000..c0f896a7d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/database.py @@ -0,0 +1,1329 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2023 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""PEP 376 implementation.""" + +from __future__ import unicode_literals + +import base64 +import codecs +import contextlib +import hashlib +import logging +import os +import posixpath +import sys +import zipimport + +from . import DistlibException, resources +from .compat import StringIO +from .version import get_scheme, UnsupportedVersionError +from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME) +from .util import (parse_requirement, cached_property, parse_name_and_version, read_exports, write_exports, CSVReader, + CSVWriter) + +__all__ = [ + 'Distribution', 'BaseInstalledDistribution', 'InstalledDistribution', 'EggInfoDistribution', 'DistributionPath' +] + +logger = logging.getLogger(__name__) + +EXPORTS_FILENAME = 'pydist-exports.json' +COMMANDS_FILENAME = 'pydist-commands.json' + +DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', 'RESOURCES', EXPORTS_FILENAME, 'SHARED') + +DISTINFO_EXT = '.dist-info' + + +class _Cache(object): + """ + A simple cache mapping names and .dist-info paths to distributions + """ + + def __init__(self): + """ + Initialise an instance. There is normally one for each DistributionPath. + """ + self.name = {} + self.path = {} + self.generated = False + + def clear(self): + """ + Clear the cache, setting it to its initial state. + """ + self.name.clear() + self.path.clear() + self.generated = False + + def add(self, dist): + """ + Add a distribution to the cache. + :param dist: The distribution to add. + """ + if dist.path not in self.path: + self.path[dist.path] = dist + self.name.setdefault(dist.key, []).append(dist) + + +class DistributionPath(object): + """ + Represents a set of distributions installed on a path (typically sys.path). + """ + + def __init__(self, path=None, include_egg=False): + """ + Create an instance from a path, optionally including legacy (distutils/ + setuptools/distribute) distributions. + :param path: The path to use, as a list of directories. If not specified, + sys.path is used. + :param include_egg: If True, this instance will look for and return legacy + distributions as well as those based on PEP 376. + """ + if path is None: + path = sys.path + self.path = path + self._include_dist = True + self._include_egg = include_egg + + self._cache = _Cache() + self._cache_egg = _Cache() + self._cache_enabled = True + self._scheme = get_scheme('default') + + def _get_cache_enabled(self): + return self._cache_enabled + + def _set_cache_enabled(self, value): + self._cache_enabled = value + + cache_enabled = property(_get_cache_enabled, _set_cache_enabled) + + def clear_cache(self): + """ + Clears the internal cache. + """ + self._cache.clear() + self._cache_egg.clear() + + def _yield_distributions(self): + """ + Yield .dist-info and/or .egg(-info) distributions. + """ + # We need to check if we've seen some resources already, because on + # some Linux systems (e.g. some Debian/Ubuntu variants) there are + # symlinks which alias other files in the environment. + seen = set() + for path in self.path: + finder = resources.finder_for_path(path) + if finder is None: + continue + r = finder.find('') + if not r or not r.is_container: + continue + rset = sorted(r.resources) + for entry in rset: + r = finder.find(entry) + if not r or r.path in seen: + continue + try: + if self._include_dist and entry.endswith(DISTINFO_EXT): + possible_filenames = [METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME] + for metadata_filename in possible_filenames: + metadata_path = posixpath.join(entry, metadata_filename) + pydist = finder.find(metadata_path) + if pydist: + break + else: + continue + + with contextlib.closing(pydist.as_stream()) as stream: + metadata = Metadata(fileobj=stream, scheme='legacy') + logger.debug('Found %s', r.path) + seen.add(r.path) + yield new_dist_class(r.path, metadata=metadata, env=self) + elif self._include_egg and entry.endswith(('.egg-info', '.egg')): + logger.debug('Found %s', r.path) + seen.add(r.path) + yield old_dist_class(r.path, self) + except Exception as e: + msg = 'Unable to read distribution at %s, perhaps due to bad metadata: %s' + logger.warning(msg, r.path, e) + import warnings + warnings.warn(msg % (r.path, e), stacklevel=2) + + def _generate_cache(self): + """ + Scan the path for distributions and populate the cache with + those that are found. + """ + gen_dist = not self._cache.generated + gen_egg = self._include_egg and not self._cache_egg.generated + if gen_dist or gen_egg: + for dist in self._yield_distributions(): + if isinstance(dist, InstalledDistribution): + self._cache.add(dist) + else: + self._cache_egg.add(dist) + + if gen_dist: + self._cache.generated = True + if gen_egg: + self._cache_egg.generated = True + + @classmethod + def distinfo_dirname(cls, name, version): + """ + The *name* and *version* parameters are converted into their + filename-escaped form, i.e. any ``'-'`` characters are replaced + with ``'_'`` other than the one in ``'dist-info'`` and the one + separating the name from the version number. + + :parameter name: is converted to a standard distribution name by replacing + any runs of non- alphanumeric characters with a single + ``'-'``. + :type name: string + :parameter version: is converted to a standard version string. Spaces + become dots, and all other non-alphanumeric characters + (except dots) become dashes, with runs of multiple + dashes condensed to a single dash. + :type version: string + :returns: directory name + :rtype: string""" + name = name.replace('-', '_') + return '-'.join([name, version]) + DISTINFO_EXT + + def get_distributions(self): + """ + Provides an iterator that looks for distributions and returns + :class:`InstalledDistribution` or + :class:`EggInfoDistribution` instances for each one of them. + + :rtype: iterator of :class:`InstalledDistribution` and + :class:`EggInfoDistribution` instances + """ + if not self._cache_enabled: + for dist in self._yield_distributions(): + yield dist + else: + self._generate_cache() + + for dist in self._cache.path.values(): + yield dist + + if self._include_egg: + for dist in self._cache_egg.path.values(): + yield dist + + def get_distribution(self, name): + """ + Looks for a named distribution on the path. + + This function only returns the first result found, as no more than one + value is expected. If nothing is found, ``None`` is returned. + + :rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution` + or ``None`` + """ + result = None + name = name.lower() + if not self._cache_enabled: + for dist in self._yield_distributions(): + if dist.key == name: + result = dist + break + else: + self._generate_cache() + + if name in self._cache.name: + result = self._cache.name[name][0] + elif self._include_egg and name in self._cache_egg.name: + result = self._cache_egg.name[name][0] + return result + + def provides_distribution(self, name, version=None): + """ + Iterates over all distributions to find which distributions provide *name*. + If a *version* is provided, it will be used to filter the results. + + This function only returns the first result found, since no more than + one values are expected. If the directory is not found, returns ``None``. + + :parameter version: a version specifier that indicates the version + required, conforming to the format in ``PEP-345`` + + :type name: string + :type version: string + """ + matcher = None + if version is not None: + try: + matcher = self._scheme.matcher('%s (%s)' % (name, version)) + except ValueError: + raise DistlibException('invalid name or version: %r, %r' % (name, version)) + + for dist in self.get_distributions(): + # We hit a problem on Travis where enum34 was installed and doesn't + # have a provides attribute ... + if not hasattr(dist, 'provides'): + logger.debug('No "provides": %s', dist) + else: + provided = dist.provides + + for p in provided: + p_name, p_ver = parse_name_and_version(p) + if matcher is None: + if p_name == name: + yield dist + break + else: + if p_name == name and matcher.match(p_ver): + yield dist + break + + def get_file_path(self, name, relative_path): + """ + Return the path to a resource file. + """ + dist = self.get_distribution(name) + if dist is None: + raise LookupError('no distribution named %r found' % name) + return dist.get_resource_path(relative_path) + + def get_exported_entries(self, category, name=None): + """ + Return all of the exported entries in a particular category. + + :param category: The category to search for entries. + :param name: If specified, only entries with that name are returned. + """ + for dist in self.get_distributions(): + r = dist.exports + if category in r: + d = r[category] + if name is not None: + if name in d: + yield d[name] + else: + for v in d.values(): + yield v + + +class Distribution(object): + """ + A base class for distributions, whether installed or from indexes. + Either way, it must have some metadata, so that's all that's needed + for construction. + """ + + build_time_dependency = False + """ + Set to True if it's known to be only a build-time dependency (i.e. + not needed after installation). + """ + + requested = False + """A boolean that indicates whether the ``REQUESTED`` metadata file is + present (in other words, whether the package was installed by user + request or it was installed as a dependency).""" + + def __init__(self, metadata): + """ + Initialise an instance. + :param metadata: The instance of :class:`Metadata` describing this + distribution. + """ + self.metadata = metadata + self.name = metadata.name + self.key = self.name.lower() # for case-insensitive comparisons + self.version = metadata.version + self.locator = None + self.digest = None + self.extras = None # additional features requested + self.context = None # environment marker overrides + self.download_urls = set() + self.digests = {} + + @property + def source_url(self): + """ + The source archive download URL for this distribution. + """ + return self.metadata.source_url + + download_url = source_url # Backward compatibility + + @property + def name_and_version(self): + """ + A utility property which displays the name and version in parentheses. + """ + return '%s (%s)' % (self.name, self.version) + + @property + def provides(self): + """ + A set of distribution names and versions provided by this distribution. + :return: A set of "name (version)" strings. + """ + plist = self.metadata.provides + s = '%s (%s)' % (self.name, self.version) + if s not in plist: + plist.append(s) + return plist + + def _get_requirements(self, req_attr): + md = self.metadata + reqts = getattr(md, req_attr) + logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr, reqts) + return set(md.get_requirements(reqts, extras=self.extras, env=self.context)) + + @property + def run_requires(self): + return self._get_requirements('run_requires') + + @property + def meta_requires(self): + return self._get_requirements('meta_requires') + + @property + def build_requires(self): + return self._get_requirements('build_requires') + + @property + def test_requires(self): + return self._get_requirements('test_requires') + + @property + def dev_requires(self): + return self._get_requirements('dev_requires') + + def matches_requirement(self, req): + """ + Say if this instance matches (fulfills) a requirement. + :param req: The requirement to match. + :rtype req: str + :return: True if it matches, else False. + """ + # Requirement may contain extras - parse to lose those + # from what's passed to the matcher + r = parse_requirement(req) + scheme = get_scheme(self.metadata.scheme) + try: + matcher = scheme.matcher(r.requirement) + except UnsupportedVersionError: + # XXX compat-mode if cannot read the version + logger.warning('could not read version %r - using name only', req) + name = req.split()[0] + matcher = scheme.matcher(name) + + name = matcher.key # case-insensitive + + result = False + for p in self.provides: + p_name, p_ver = parse_name_and_version(p) + if p_name != name: + continue + try: + result = matcher.match(p_ver) + break + except UnsupportedVersionError: + pass + return result + + def __repr__(self): + """ + Return a textual representation of this instance, + """ + if self.source_url: + suffix = ' [%s]' % self.source_url + else: + suffix = '' + return '' % (self.name, self.version, suffix) + + def __eq__(self, other): + """ + See if this distribution is the same as another. + :param other: The distribution to compare with. To be equal to one + another. distributions must have the same type, name, + version and source_url. + :return: True if it is the same, else False. + """ + if type(other) is not type(self): + result = False + else: + result = (self.name == other.name and self.version == other.version and self.source_url == other.source_url) + return result + + def __hash__(self): + """ + Compute hash in a way which matches the equality test. + """ + return hash(self.name) + hash(self.version) + hash(self.source_url) + + +class BaseInstalledDistribution(Distribution): + """ + This is the base class for installed distributions (whether PEP 376 or + legacy). + """ + + hasher = None + + def __init__(self, metadata, path, env=None): + """ + Initialise an instance. + :param metadata: An instance of :class:`Metadata` which describes the + distribution. This will normally have been initialised + from a metadata file in the ``path``. + :param path: The path of the ``.dist-info`` or ``.egg-info`` + directory for the distribution. + :param env: This is normally the :class:`DistributionPath` + instance where this distribution was found. + """ + super(BaseInstalledDistribution, self).__init__(metadata) + self.path = path + self.dist_path = env + + def get_hash(self, data, hasher=None): + """ + Get the hash of some data, using a particular hash algorithm, if + specified. + + :param data: The data to be hashed. + :type data: bytes + :param hasher: The name of a hash implementation, supported by hashlib, + or ``None``. Examples of valid values are ``'sha1'``, + ``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and + ``'sha512'``. If no hasher is specified, the ``hasher`` + attribute of the :class:`InstalledDistribution` instance + is used. If the hasher is determined to be ``None``, MD5 + is used as the hashing algorithm. + :returns: The hash of the data. If a hasher was explicitly specified, + the returned hash will be prefixed with the specified hasher + followed by '='. + :rtype: str + """ + if hasher is None: + hasher = self.hasher + if hasher is None: + hasher = hashlib.md5 + prefix = '' + else: + hasher = getattr(hashlib, hasher) + prefix = '%s=' % self.hasher + digest = hasher(data).digest() + digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii') + return '%s%s' % (prefix, digest) + + +class InstalledDistribution(BaseInstalledDistribution): + """ + Created with the *path* of the ``.dist-info`` directory provided to the + constructor. It reads the metadata contained in ``pydist.json`` when it is + instantiated., or uses a passed in Metadata instance (useful for when + dry-run mode is being used). + """ + + hasher = 'sha256' + + def __init__(self, path, metadata=None, env=None): + self.modules = [] + self.finder = finder = resources.finder_for_path(path) + if finder is None: + raise ValueError('finder unavailable for %s' % path) + if env and env._cache_enabled and path in env._cache.path: + metadata = env._cache.path[path].metadata + elif metadata is None: + r = finder.find(METADATA_FILENAME) + # Temporary - for Wheel 0.23 support + if r is None: + r = finder.find(WHEEL_METADATA_FILENAME) + # Temporary - for legacy support + if r is None: + r = finder.find(LEGACY_METADATA_FILENAME) + if r is None: + raise ValueError('no %s found in %s' % (METADATA_FILENAME, path)) + with contextlib.closing(r.as_stream()) as stream: + metadata = Metadata(fileobj=stream, scheme='legacy') + + super(InstalledDistribution, self).__init__(metadata, path, env) + + if env and env._cache_enabled: + env._cache.add(self) + + r = finder.find('REQUESTED') + self.requested = r is not None + p = os.path.join(path, 'top_level.txt') + if os.path.exists(p): + with open(p, 'rb') as f: + data = f.read().decode('utf-8') + self.modules = data.splitlines() + + def __repr__(self): + return '' % (self.name, self.version, self.path) + + def __str__(self): + return "%s %s" % (self.name, self.version) + + def _get_records(self): + """ + Get the list of installed files for the distribution + :return: A list of tuples of path, hash and size. Note that hash and + size might be ``None`` for some entries. The path is exactly + as stored in the file (which is as in PEP 376). + """ + results = [] + r = self.get_distinfo_resource('RECORD') + with contextlib.closing(r.as_stream()) as stream: + with CSVReader(stream=stream) as record_reader: + # Base location is parent dir of .dist-info dir + # base_location = os.path.dirname(self.path) + # base_location = os.path.abspath(base_location) + for row in record_reader: + missing = [None for i in range(len(row), 3)] + path, checksum, size = row + missing + # if not os.path.isabs(path): + # path = path.replace('/', os.sep) + # path = os.path.join(base_location, path) + results.append((path, checksum, size)) + return results + + @cached_property + def exports(self): + """ + Return the information exported by this distribution. + :return: A dictionary of exports, mapping an export category to a dict + of :class:`ExportEntry` instances describing the individual + export entries, and keyed by name. + """ + result = {} + r = self.get_distinfo_resource(EXPORTS_FILENAME) + if r: + result = self.read_exports() + return result + + def read_exports(self): + """ + Read exports data from a file in .ini format. + + :return: A dictionary of exports, mapping an export category to a list + of :class:`ExportEntry` instances describing the individual + export entries. + """ + result = {} + r = self.get_distinfo_resource(EXPORTS_FILENAME) + if r: + with contextlib.closing(r.as_stream()) as stream: + result = read_exports(stream) + return result + + def write_exports(self, exports): + """ + Write a dictionary of exports to a file in .ini format. + :param exports: A dictionary of exports, mapping an export category to + a list of :class:`ExportEntry` instances describing the + individual export entries. + """ + rf = self.get_distinfo_file(EXPORTS_FILENAME) + with open(rf, 'w') as f: + write_exports(exports, f) + + def get_resource_path(self, relative_path): + """ + NOTE: This API may change in the future. + + Return the absolute path to a resource file with the given relative + path. + + :param relative_path: The path, relative to .dist-info, of the resource + of interest. + :return: The absolute path where the resource is to be found. + """ + r = self.get_distinfo_resource('RESOURCES') + with contextlib.closing(r.as_stream()) as stream: + with CSVReader(stream=stream) as resources_reader: + for relative, destination in resources_reader: + if relative == relative_path: + return destination + raise KeyError('no resource file with relative path %r ' + 'is installed' % relative_path) + + def list_installed_files(self): + """ + Iterates over the ``RECORD`` entries and returns a tuple + ``(path, hash, size)`` for each line. + + :returns: iterator of (path, hash, size) + """ + for result in self._get_records(): + yield result + + def write_installed_files(self, paths, prefix, dry_run=False): + """ + Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any + existing ``RECORD`` file is silently overwritten. + + prefix is used to determine when to write absolute paths. + """ + prefix = os.path.join(prefix, '') + base = os.path.dirname(self.path) + base_under_prefix = base.startswith(prefix) + base = os.path.join(base, '') + record_path = self.get_distinfo_file('RECORD') + logger.info('creating %s', record_path) + if dry_run: + return None + with CSVWriter(record_path) as writer: + for path in paths: + if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')): + # do not put size and hash, as in PEP-376 + hash_value = size = '' + else: + size = '%d' % os.path.getsize(path) + with open(path, 'rb') as fp: + hash_value = self.get_hash(fp.read()) + if path.startswith(base) or (base_under_prefix and path.startswith(prefix)): + path = os.path.relpath(path, base) + writer.writerow((path, hash_value, size)) + + # add the RECORD file itself + if record_path.startswith(base): + record_path = os.path.relpath(record_path, base) + writer.writerow((record_path, '', '')) + return record_path + + def check_installed_files(self): + """ + Checks that the hashes and sizes of the files in ``RECORD`` are + matched by the files themselves. Returns a (possibly empty) list of + mismatches. Each entry in the mismatch list will be a tuple consisting + of the path, 'exists', 'size' or 'hash' according to what didn't match + (existence is checked first, then size, then hash), the expected + value and the actual value. + """ + mismatches = [] + base = os.path.dirname(self.path) + record_path = self.get_distinfo_file('RECORD') + for path, hash_value, size in self.list_installed_files(): + if not os.path.isabs(path): + path = os.path.join(base, path) + if path == record_path: + continue + if not os.path.exists(path): + mismatches.append((path, 'exists', True, False)) + elif os.path.isfile(path): + actual_size = str(os.path.getsize(path)) + if size and actual_size != size: + mismatches.append((path, 'size', size, actual_size)) + elif hash_value: + if '=' in hash_value: + hasher = hash_value.split('=', 1)[0] + else: + hasher = None + + with open(path, 'rb') as f: + actual_hash = self.get_hash(f.read(), hasher) + if actual_hash != hash_value: + mismatches.append((path, 'hash', hash_value, actual_hash)) + return mismatches + + @cached_property + def shared_locations(self): + """ + A dictionary of shared locations whose keys are in the set 'prefix', + 'purelib', 'platlib', 'scripts', 'headers', 'data' and 'namespace'. + The corresponding value is the absolute path of that category for + this distribution, and takes into account any paths selected by the + user at installation time (e.g. via command-line arguments). In the + case of the 'namespace' key, this would be a list of absolute paths + for the roots of namespace packages in this distribution. + + The first time this property is accessed, the relevant information is + read from the SHARED file in the .dist-info directory. + """ + result = {} + shared_path = os.path.join(self.path, 'SHARED') + if os.path.isfile(shared_path): + with codecs.open(shared_path, 'r', encoding='utf-8') as f: + lines = f.read().splitlines() + for line in lines: + key, value = line.split('=', 1) + if key == 'namespace': + result.setdefault(key, []).append(value) + else: + result[key] = value + return result + + def write_shared_locations(self, paths, dry_run=False): + """ + Write shared location information to the SHARED file in .dist-info. + :param paths: A dictionary as described in the documentation for + :meth:`shared_locations`. + :param dry_run: If True, the action is logged but no file is actually + written. + :return: The path of the file written to. + """ + shared_path = os.path.join(self.path, 'SHARED') + logger.info('creating %s', shared_path) + if dry_run: + return None + lines = [] + for key in ('prefix', 'lib', 'headers', 'scripts', 'data'): + path = paths[key] + if os.path.isdir(paths[key]): + lines.append('%s=%s' % (key, path)) + for ns in paths.get('namespace', ()): + lines.append('namespace=%s' % ns) + + with codecs.open(shared_path, 'w', encoding='utf-8') as f: + f.write('\n'.join(lines)) + return shared_path + + def get_distinfo_resource(self, path): + if path not in DIST_FILES: + raise DistlibException('invalid path for a dist-info file: ' + '%r at %r' % (path, self.path)) + finder = resources.finder_for_path(self.path) + if finder is None: + raise DistlibException('Unable to get a finder for %s' % self.path) + return finder.find(path) + + def get_distinfo_file(self, path): + """ + Returns a path located under the ``.dist-info`` directory. Returns a + string representing the path. + + :parameter path: a ``'/'``-separated path relative to the + ``.dist-info`` directory or an absolute path; + If *path* is an absolute path and doesn't start + with the ``.dist-info`` directory path, + a :class:`DistlibException` is raised + :type path: str + :rtype: str + """ + # Check if it is an absolute path # XXX use relpath, add tests + if path.find(os.sep) >= 0: + # it's an absolute path? + distinfo_dirname, path = path.split(os.sep)[-2:] + if distinfo_dirname != self.path.split(os.sep)[-1]: + raise DistlibException('dist-info file %r does not belong to the %r %s ' + 'distribution' % (path, self.name, self.version)) + + # The file must be relative + if path not in DIST_FILES: + raise DistlibException('invalid path for a dist-info file: ' + '%r at %r' % (path, self.path)) + + return os.path.join(self.path, path) + + def list_distinfo_files(self): + """ + Iterates over the ``RECORD`` entries and returns paths for each line if + the path is pointing to a file located in the ``.dist-info`` directory + or one of its subdirectories. + + :returns: iterator of paths + """ + base = os.path.dirname(self.path) + for path, checksum, size in self._get_records(): + # XXX add separator or use real relpath algo + if not os.path.isabs(path): + path = os.path.join(base, path) + if path.startswith(self.path): + yield path + + def __eq__(self, other): + return (isinstance(other, InstalledDistribution) and self.path == other.path) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + + +class EggInfoDistribution(BaseInstalledDistribution): + """Created with the *path* of the ``.egg-info`` directory or file provided + to the constructor. It reads the metadata contained in the file itself, or + if the given path happens to be a directory, the metadata is read from the + file ``PKG-INFO`` under that directory.""" + + requested = True # as we have no way of knowing, assume it was + shared_locations = {} + + def __init__(self, path, env=None): + + def set_name_and_version(s, n, v): + s.name = n + s.key = n.lower() # for case-insensitive comparisons + s.version = v + + self.path = path + self.dist_path = env + if env and env._cache_enabled and path in env._cache_egg.path: + metadata = env._cache_egg.path[path].metadata + set_name_and_version(self, metadata.name, metadata.version) + else: + metadata = self._get_metadata(path) + + # Need to be set before caching + set_name_and_version(self, metadata.name, metadata.version) + + if env and env._cache_enabled: + env._cache_egg.add(self) + super(EggInfoDistribution, self).__init__(metadata, path, env) + + def _get_metadata(self, path): + requires = None + + def parse_requires_data(data): + """Create a list of dependencies from a requires.txt file. + + *data*: the contents of a setuptools-produced requires.txt file. + """ + reqs = [] + lines = data.splitlines() + for line in lines: + line = line.strip() + # sectioned files have bare newlines (separating sections) + if not line: # pragma: no cover + continue + if line.startswith('['): # pragma: no cover + logger.warning('Unexpected line: quitting requirement scan: %r', line) + break + r = parse_requirement(line) + if not r: # pragma: no cover + logger.warning('Not recognised as a requirement: %r', line) + continue + if r.extras: # pragma: no cover + logger.warning('extra requirements in requires.txt are ' + 'not supported') + if not r.constraints: + reqs.append(r.name) + else: + cons = ', '.join('%s%s' % c for c in r.constraints) + reqs.append('%s (%s)' % (r.name, cons)) + return reqs + + def parse_requires_path(req_path): + """Create a list of dependencies from a requires.txt file. + + *req_path*: the path to a setuptools-produced requires.txt file. + """ + + reqs = [] + try: + with codecs.open(req_path, 'r', 'utf-8') as fp: + reqs = parse_requires_data(fp.read()) + except IOError: + pass + return reqs + + tl_path = tl_data = None + if path.endswith('.egg'): + if os.path.isdir(path): + p = os.path.join(path, 'EGG-INFO') + meta_path = os.path.join(p, 'PKG-INFO') + metadata = Metadata(path=meta_path, scheme='legacy') + req_path = os.path.join(p, 'requires.txt') + tl_path = os.path.join(p, 'top_level.txt') + requires = parse_requires_path(req_path) + else: + # FIXME handle the case where zipfile is not available + zipf = zipimport.zipimporter(path) + fileobj = StringIO(zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8')) + metadata = Metadata(fileobj=fileobj, scheme='legacy') + try: + data = zipf.get_data('EGG-INFO/requires.txt') + tl_data = zipf.get_data('EGG-INFO/top_level.txt').decode('utf-8') + requires = parse_requires_data(data.decode('utf-8')) + except IOError: + requires = None + elif path.endswith('.egg-info'): + if os.path.isdir(path): + req_path = os.path.join(path, 'requires.txt') + requires = parse_requires_path(req_path) + path = os.path.join(path, 'PKG-INFO') + tl_path = os.path.join(path, 'top_level.txt') + metadata = Metadata(path=path, scheme='legacy') + else: + raise DistlibException('path must end with .egg-info or .egg, ' + 'got %r' % path) + + if requires: + metadata.add_requirements(requires) + # look for top-level modules in top_level.txt, if present + if tl_data is None: + if tl_path is not None and os.path.exists(tl_path): + with open(tl_path, 'rb') as f: + tl_data = f.read().decode('utf-8') + if not tl_data: + tl_data = [] + else: + tl_data = tl_data.splitlines() + self.modules = tl_data + return metadata + + def __repr__(self): + return '' % (self.name, self.version, self.path) + + def __str__(self): + return "%s %s" % (self.name, self.version) + + def check_installed_files(self): + """ + Checks that the hashes and sizes of the files in ``RECORD`` are + matched by the files themselves. Returns a (possibly empty) list of + mismatches. Each entry in the mismatch list will be a tuple consisting + of the path, 'exists', 'size' or 'hash' according to what didn't match + (existence is checked first, then size, then hash), the expected + value and the actual value. + """ + mismatches = [] + record_path = os.path.join(self.path, 'installed-files.txt') + if os.path.exists(record_path): + for path, _, _ in self.list_installed_files(): + if path == record_path: + continue + if not os.path.exists(path): + mismatches.append((path, 'exists', True, False)) + return mismatches + + def list_installed_files(self): + """ + Iterates over the ``installed-files.txt`` entries and returns a tuple + ``(path, hash, size)`` for each line. + + :returns: a list of (path, hash, size) + """ + + def _md5(path): + f = open(path, 'rb') + try: + content = f.read() + finally: + f.close() + return hashlib.md5(content).hexdigest() + + def _size(path): + return os.stat(path).st_size + + record_path = os.path.join(self.path, 'installed-files.txt') + result = [] + if os.path.exists(record_path): + with codecs.open(record_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + p = os.path.normpath(os.path.join(self.path, line)) + # "./" is present as a marker between installed files + # and installation metadata files + if not os.path.exists(p): + logger.warning('Non-existent file: %s', p) + if p.endswith(('.pyc', '.pyo')): + continue + # otherwise fall through and fail + if not os.path.isdir(p): + result.append((p, _md5(p), _size(p))) + result.append((record_path, None, None)) + return result + + def list_distinfo_files(self, absolute=False): + """ + Iterates over the ``installed-files.txt`` entries and returns paths for + each line if the path is pointing to a file located in the + ``.egg-info`` directory or one of its subdirectories. + + :parameter absolute: If *absolute* is ``True``, each returned path is + transformed into a local absolute path. Otherwise the + raw value from ``installed-files.txt`` is returned. + :type absolute: boolean + :returns: iterator of paths + """ + record_path = os.path.join(self.path, 'installed-files.txt') + if os.path.exists(record_path): + skip = True + with codecs.open(record_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line == './': + skip = False + continue + if not skip: + p = os.path.normpath(os.path.join(self.path, line)) + if p.startswith(self.path): + if absolute: + yield p + else: + yield line + + def __eq__(self, other): + return (isinstance(other, EggInfoDistribution) and self.path == other.path) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + + +new_dist_class = InstalledDistribution +old_dist_class = EggInfoDistribution + + +class DependencyGraph(object): + """ + Represents a dependency graph between distributions. + + The dependency relationships are stored in an ``adjacency_list`` that maps + distributions to a list of ``(other, label)`` tuples where ``other`` + is a distribution and the edge is labeled with ``label`` (i.e. the version + specifier, if such was provided). Also, for more efficient traversal, for + every distribution ``x``, a list of predecessors is kept in + ``reverse_list[x]``. An edge from distribution ``a`` to + distribution ``b`` means that ``a`` depends on ``b``. If any missing + dependencies are found, they are stored in ``missing``, which is a + dictionary that maps distributions to a list of requirements that were not + provided by any other distributions. + """ + + def __init__(self): + self.adjacency_list = {} + self.reverse_list = {} + self.missing = {} + + def add_distribution(self, distribution): + """Add the *distribution* to the graph. + + :type distribution: :class:`distutils2.database.InstalledDistribution` + or :class:`distutils2.database.EggInfoDistribution` + """ + self.adjacency_list[distribution] = [] + self.reverse_list[distribution] = [] + # self.missing[distribution] = [] + + def add_edge(self, x, y, label=None): + """Add an edge from distribution *x* to distribution *y* with the given + *label*. + + :type x: :class:`distutils2.database.InstalledDistribution` or + :class:`distutils2.database.EggInfoDistribution` + :type y: :class:`distutils2.database.InstalledDistribution` or + :class:`distutils2.database.EggInfoDistribution` + :type label: ``str`` or ``None`` + """ + self.adjacency_list[x].append((y, label)) + # multiple edges are allowed, so be careful + if x not in self.reverse_list[y]: + self.reverse_list[y].append(x) + + def add_missing(self, distribution, requirement): + """ + Add a missing *requirement* for the given *distribution*. + + :type distribution: :class:`distutils2.database.InstalledDistribution` + or :class:`distutils2.database.EggInfoDistribution` + :type requirement: ``str`` + """ + logger.debug('%s missing %r', distribution, requirement) + self.missing.setdefault(distribution, []).append(requirement) + + def _repr_dist(self, dist): + return '%s %s' % (dist.name, dist.version) + + def repr_node(self, dist, level=1): + """Prints only a subgraph""" + output = [self._repr_dist(dist)] + for other, label in self.adjacency_list[dist]: + dist = self._repr_dist(other) + if label is not None: + dist = '%s [%s]' % (dist, label) + output.append(' ' * level + str(dist)) + suboutput = self.repr_node(other, level + 1) + subs = suboutput.split('\n') + output.extend(subs[1:]) + return '\n'.join(output) + + def to_dot(self, f, skip_disconnected=True): + """Writes a DOT output for the graph to the provided file *f*. + + If *skip_disconnected* is set to ``True``, then all distributions + that are not dependent on any other distribution are skipped. + + :type f: has to support ``file``-like operations + :type skip_disconnected: ``bool`` + """ + disconnected = [] + + f.write("digraph dependencies {\n") + for dist, adjs in self.adjacency_list.items(): + if len(adjs) == 0 and not skip_disconnected: + disconnected.append(dist) + for other, label in adjs: + if label is not None: + f.write('"%s" -> "%s" [label="%s"]\n' % (dist.name, other.name, label)) + else: + f.write('"%s" -> "%s"\n' % (dist.name, other.name)) + if not skip_disconnected and len(disconnected) > 0: + f.write('subgraph disconnected {\n') + f.write('label = "Disconnected"\n') + f.write('bgcolor = red\n') + + for dist in disconnected: + f.write('"%s"' % dist.name) + f.write('\n') + f.write('}\n') + f.write('}\n') + + def topological_sort(self): + """ + Perform a topological sort of the graph. + :return: A tuple, the first element of which is a topologically sorted + list of distributions, and the second element of which is a + list of distributions that cannot be sorted because they have + circular dependencies and so form a cycle. + """ + result = [] + # Make a shallow copy of the adjacency list + alist = {} + for k, v in self.adjacency_list.items(): + alist[k] = v[:] + while True: + # See what we can remove in this run + to_remove = [] + for k, v in list(alist.items())[:]: + if not v: + to_remove.append(k) + del alist[k] + if not to_remove: + # What's left in alist (if anything) is a cycle. + break + # Remove from the adjacency list of others + for k, v in alist.items(): + alist[k] = [(d, r) for d, r in v if d not in to_remove] + logger.debug('Moving to result: %s', ['%s (%s)' % (d.name, d.version) for d in to_remove]) + result.extend(to_remove) + return result, list(alist.keys()) + + def __repr__(self): + """Representation of the graph""" + output = [] + for dist, adjs in self.adjacency_list.items(): + output.append(self.repr_node(dist)) + return '\n'.join(output) + + +def make_graph(dists, scheme='default'): + """Makes a dependency graph from the given distributions. + + :parameter dists: a list of distributions + :type dists: list of :class:`distutils2.database.InstalledDistribution` and + :class:`distutils2.database.EggInfoDistribution` instances + :rtype: a :class:`DependencyGraph` instance + """ + scheme = get_scheme(scheme) + graph = DependencyGraph() + provided = {} # maps names to lists of (version, dist) tuples + + # first, build the graph and find out what's provided + for dist in dists: + graph.add_distribution(dist) + + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Add to provided: %s, %s, %s', name, version, dist) + provided.setdefault(name, []).append((version, dist)) + + # now make the edges + for dist in dists: + requires = (dist.run_requires | dist.meta_requires | dist.build_requires | dist.dev_requires) + for req in requires: + try: + matcher = scheme.matcher(req) + except UnsupportedVersionError: + # XXX compat-mode if cannot read the version + logger.warning('could not read version %r - using name only', req) + name = req.split()[0] + matcher = scheme.matcher(name) + + name = matcher.key # case-insensitive + + matched = False + if name in provided: + for version, provider in provided[name]: + try: + match = matcher.match(version) + except UnsupportedVersionError: + match = False + + if match: + graph.add_edge(dist, provider, req) + matched = True + break + if not matched: + graph.add_missing(dist, req) + return graph + + +def get_dependent_dists(dists, dist): + """Recursively generate a list of distributions from *dists* that are + dependent on *dist*. + + :param dists: a list of distributions + :param dist: a distribution, member of *dists* for which we are interested + """ + if dist not in dists: + raise DistlibException('given distribution %r is not a member ' + 'of the list' % dist.name) + graph = make_graph(dists) + + dep = [dist] # dependent distributions + todo = graph.reverse_list[dist] # list of nodes we should inspect + + while todo: + d = todo.pop() + dep.append(d) + for succ in graph.reverse_list[d]: + if succ not in dep: + todo.append(succ) + + dep.pop(0) # remove dist from dep, was there to prevent infinite loops + return dep + + +def get_required_dists(dists, dist): + """Recursively generate a list of distributions from *dists* that are + required by *dist*. + + :param dists: a list of distributions + :param dist: a distribution, member of *dists* for which we are interested + in finding the dependencies. + """ + if dist not in dists: + raise DistlibException('given distribution %r is not a member ' + 'of the list' % dist.name) + graph = make_graph(dists) + + req = set() # required distributions + todo = graph.adjacency_list[dist] # list of nodes we should inspect + seen = set(t[0] for t in todo) # already added to todo + + while todo: + d = todo.pop()[0] + req.add(d) + pred_list = graph.adjacency_list[d] + for pred in pred_list: + d = pred[0] + if d not in req and d not in seen: + seen.add(d) + todo.append(pred) + return req + + +def make_dist(name, version, **kwargs): + """ + A convenience method for making a dist given just a name and version. + """ + summary = kwargs.pop('summary', 'Placeholder for summary') + md = Metadata(**kwargs) + md.name = name + md.version = version + md.summary = summary or 'Placeholder for summary' + return Distribution(md) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/index.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/index.py new file mode 100644 index 000000000..56cd28671 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/index.py @@ -0,0 +1,508 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2023 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import hashlib +import logging +import os +import shutil +import subprocess +import tempfile +try: + from threading import Thread +except ImportError: # pragma: no cover + from dummy_threading import Thread + +from . import DistlibException +from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr, + urlparse, build_opener, string_types) +from .util import zip_dir, ServerProxy + +logger = logging.getLogger(__name__) + +DEFAULT_INDEX = 'https://pypi.org/pypi' +DEFAULT_REALM = 'pypi' + + +class PackageIndex(object): + """ + This class represents a package index compatible with PyPI, the Python + Package Index. + """ + + boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$' + + def __init__(self, url=None): + """ + Initialise an instance. + + :param url: The URL of the index. If not specified, the URL for PyPI is + used. + """ + self.url = url or DEFAULT_INDEX + self.read_configuration() + scheme, netloc, path, params, query, frag = urlparse(self.url) + if params or query or frag or scheme not in ('http', 'https'): + raise DistlibException('invalid repository: %s' % self.url) + self.password_handler = None + self.ssl_verifier = None + self.gpg = None + self.gpg_home = None + with open(os.devnull, 'w') as sink: + # Use gpg by default rather than gpg2, as gpg2 insists on + # prompting for passwords + for s in ('gpg', 'gpg2'): + try: + rc = subprocess.check_call([s, '--version'], stdout=sink, + stderr=sink) + if rc == 0: + self.gpg = s + break + except OSError: + pass + + def _get_pypirc_command(self): + """ + Get the distutils command for interacting with PyPI configurations. + :return: the command. + """ + from .util import _get_pypirc_command as cmd + return cmd() + + def read_configuration(self): + """ + Read the PyPI access configuration as supported by distutils. This populates + ``username``, ``password``, ``realm`` and ``url`` attributes from the + configuration. + """ + from .util import _load_pypirc + cfg = _load_pypirc(self) + self.username = cfg.get('username') + self.password = cfg.get('password') + self.realm = cfg.get('realm', 'pypi') + self.url = cfg.get('repository', self.url) + + def save_configuration(self): + """ + Save the PyPI access configuration. You must have set ``username`` and + ``password`` attributes before calling this method. + """ + self.check_credentials() + from .util import _store_pypirc + _store_pypirc(self) + + def check_credentials(self): + """ + Check that ``username`` and ``password`` have been set, and raise an + exception if not. + """ + if self.username is None or self.password is None: + raise DistlibException('username and password must be set') + pm = HTTPPasswordMgr() + _, netloc, _, _, _, _ = urlparse(self.url) + pm.add_password(self.realm, netloc, self.username, self.password) + self.password_handler = HTTPBasicAuthHandler(pm) + + def register(self, metadata): # pragma: no cover + """ + Register a distribution on PyPI, using the provided metadata. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the distribution to be + registered. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + metadata.validate() + d = metadata.todict() + d[':action'] = 'verify' + request = self.encode_request(d.items(), []) + self.send_request(request) + d[':action'] = 'submit' + request = self.encode_request(d.items(), []) + return self.send_request(request) + + def _reader(self, name, stream, outbuf): + """ + Thread runner for reading lines of from a subprocess into a buffer. + + :param name: The logical name of the stream (used for logging only). + :param stream: The stream to read from. This will typically a pipe + connected to the output stream of a subprocess. + :param outbuf: The list to append the read lines to. + """ + while True: + s = stream.readline() + if not s: + break + s = s.decode('utf-8').rstrip() + outbuf.append(s) + logger.debug('%s: %s' % (name, s)) + stream.close() + + def get_sign_command(self, filename, signer, sign_password, keystore=None): # pragma: no cover + """ + Return a suitable command for signing a file. + + :param filename: The pathname to the file to be signed. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: The signing command as a list suitable to be + passed to :class:`subprocess.Popen`. + """ + cmd = [self.gpg, '--status-fd', '2', '--no-tty'] + if keystore is None: + keystore = self.gpg_home + if keystore: + cmd.extend(['--homedir', keystore]) + if sign_password is not None: + cmd.extend(['--batch', '--passphrase-fd', '0']) + td = tempfile.mkdtemp() + sf = os.path.join(td, os.path.basename(filename) + '.asc') + cmd.extend(['--detach-sign', '--armor', '--local-user', + signer, '--output', sf, filename]) + logger.debug('invoking: %s', ' '.join(cmd)) + return cmd, sf + + def run_command(self, cmd, input_data=None): + """ + Run a command in a child process , passing it any input data specified. + + :param cmd: The command to run. + :param input_data: If specified, this must be a byte string containing + data to be sent to the child process. + :return: A tuple consisting of the subprocess' exit code, a list of + lines read from the subprocess' ``stdout``, and a list of + lines read from the subprocess' ``stderr``. + """ + kwargs = { + 'stdout': subprocess.PIPE, + 'stderr': subprocess.PIPE, + } + if input_data is not None: + kwargs['stdin'] = subprocess.PIPE + stdout = [] + stderr = [] + p = subprocess.Popen(cmd, **kwargs) + # We don't use communicate() here because we may need to + # get clever with interacting with the command + t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout)) + t1.start() + t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr)) + t2.start() + if input_data is not None: + p.stdin.write(input_data) + p.stdin.close() + + p.wait() + t1.join() + t2.join() + return p.returncode, stdout, stderr + + def sign_file(self, filename, signer, sign_password, keystore=None): # pragma: no cover + """ + Sign a file. + + :param filename: The pathname to the file to be signed. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param keystore: The path to a directory which contains the keys + used in signing. If not specified, the instance's + ``gpg_home`` attribute is used instead. + :return: The absolute pathname of the file where the signature is + stored. + """ + cmd, sig_file = self.get_sign_command(filename, signer, sign_password, + keystore) + rc, stdout, stderr = self.run_command(cmd, + sign_password.encode('utf-8')) + if rc != 0: + raise DistlibException('sign command failed with error ' + 'code %s' % rc) + return sig_file + + def upload_file(self, metadata, filename, signer=None, sign_password=None, + filetype='sdist', pyversion='source', keystore=None): + """ + Upload a release file to the index. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the file to be uploaded. + :param filename: The pathname of the file to be uploaded. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param filetype: The type of the file being uploaded. This is the + distutils command which produced that file, e.g. + ``sdist`` or ``bdist_wheel``. + :param pyversion: The version of Python which the release relates + to. For code compatible with any Python, this would + be ``source``, otherwise it would be e.g. ``3.2``. + :param keystore: The path to a directory which contains the keys + used in signing. If not specified, the instance's + ``gpg_home`` attribute is used instead. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + if not os.path.exists(filename): + raise DistlibException('not found: %s' % filename) + metadata.validate() + d = metadata.todict() + sig_file = None + if signer: + if not self.gpg: + logger.warning('no signing program available - not signed') + else: + sig_file = self.sign_file(filename, signer, sign_password, + keystore) + with open(filename, 'rb') as f: + file_data = f.read() + md5_digest = hashlib.md5(file_data).hexdigest() + sha256_digest = hashlib.sha256(file_data).hexdigest() + d.update({ + ':action': 'file_upload', + 'protocol_version': '1', + 'filetype': filetype, + 'pyversion': pyversion, + 'md5_digest': md5_digest, + 'sha256_digest': sha256_digest, + }) + files = [('content', os.path.basename(filename), file_data)] + if sig_file: + with open(sig_file, 'rb') as f: + sig_data = f.read() + files.append(('gpg_signature', os.path.basename(sig_file), + sig_data)) + shutil.rmtree(os.path.dirname(sig_file)) + request = self.encode_request(d.items(), files) + return self.send_request(request) + + def upload_documentation(self, metadata, doc_dir): # pragma: no cover + """ + Upload documentation to the index. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the documentation to be + uploaded. + :param doc_dir: The pathname of the directory which contains the + documentation. This should be the directory that + contains the ``index.html`` for the documentation. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + if not os.path.isdir(doc_dir): + raise DistlibException('not a directory: %r' % doc_dir) + fn = os.path.join(doc_dir, 'index.html') + if not os.path.exists(fn): + raise DistlibException('not found: %r' % fn) + metadata.validate() + name, version = metadata.name, metadata.version + zip_data = zip_dir(doc_dir).getvalue() + fields = [(':action', 'doc_upload'), + ('name', name), ('version', version)] + files = [('content', name, zip_data)] + request = self.encode_request(fields, files) + return self.send_request(request) + + def get_verify_command(self, signature_filename, data_filename, + keystore=None): + """ + Return a suitable command for verifying a file. + + :param signature_filename: The pathname to the file containing the + signature. + :param data_filename: The pathname to the file containing the + signed data. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: The verifying command as a list suitable to be + passed to :class:`subprocess.Popen`. + """ + cmd = [self.gpg, '--status-fd', '2', '--no-tty'] + if keystore is None: + keystore = self.gpg_home + if keystore: + cmd.extend(['--homedir', keystore]) + cmd.extend(['--verify', signature_filename, data_filename]) + logger.debug('invoking: %s', ' '.join(cmd)) + return cmd + + def verify_signature(self, signature_filename, data_filename, + keystore=None): + """ + Verify a signature for a file. + + :param signature_filename: The pathname to the file containing the + signature. + :param data_filename: The pathname to the file containing the + signed data. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: True if the signature was verified, else False. + """ + if not self.gpg: + raise DistlibException('verification unavailable because gpg ' + 'unavailable') + cmd = self.get_verify_command(signature_filename, data_filename, + keystore) + rc, stdout, stderr = self.run_command(cmd) + if rc not in (0, 1): + raise DistlibException('verify command failed with error code %s' % rc) + return rc == 0 + + def download_file(self, url, destfile, digest=None, reporthook=None): + """ + This is a convenience method for downloading a file from an URL. + Normally, this will be a file from the index, though currently + no check is made for this (i.e. a file can be downloaded from + anywhere). + + The method is just like the :func:`urlretrieve` function in the + standard library, except that it allows digest computation to be + done during download and checking that the downloaded data + matched any expected value. + + :param url: The URL of the file to be downloaded (assumed to be + available via an HTTP GET request). + :param destfile: The pathname where the downloaded file is to be + saved. + :param digest: If specified, this must be a (hasher, value) + tuple, where hasher is the algorithm used (e.g. + ``'md5'``) and ``value`` is the expected value. + :param reporthook: The same as for :func:`urlretrieve` in the + standard library. + """ + if digest is None: + digester = None + logger.debug('No digest specified') + else: + if isinstance(digest, (list, tuple)): + hasher, digest = digest + else: + hasher = 'md5' + digester = getattr(hashlib, hasher)() + logger.debug('Digest specified: %s' % digest) + # The following code is equivalent to urlretrieve. + # We need to do it this way so that we can compute the + # digest of the file as we go. + with open(destfile, 'wb') as dfp: + # addinfourl is not a context manager on 2.x + # so we have to use try/finally + sfp = self.send_request(Request(url)) + try: + headers = sfp.info() + blocksize = 8192 + size = -1 + read = 0 + blocknum = 0 + if "content-length" in headers: + size = int(headers["Content-Length"]) + if reporthook: + reporthook(blocknum, blocksize, size) + while True: + block = sfp.read(blocksize) + if not block: + break + read += len(block) + dfp.write(block) + if digester: + digester.update(block) + blocknum += 1 + if reporthook: + reporthook(blocknum, blocksize, size) + finally: + sfp.close() + + # check that we got the whole file, if we can + if size >= 0 and read < size: + raise DistlibException( + 'retrieval incomplete: got only %d out of %d bytes' + % (read, size)) + # if we have a digest, it must match. + if digester: + actual = digester.hexdigest() + if digest != actual: + raise DistlibException('%s digest mismatch for %s: expected ' + '%s, got %s' % (hasher, destfile, + digest, actual)) + logger.debug('Digest verified: %s', digest) + + def send_request(self, req): + """ + Send a standard library :class:`Request` to PyPI and return its + response. + + :param req: The request to send. + :return: The HTTP response from PyPI (a standard library HTTPResponse). + """ + handlers = [] + if self.password_handler: + handlers.append(self.password_handler) + if self.ssl_verifier: + handlers.append(self.ssl_verifier) + opener = build_opener(*handlers) + return opener.open(req) + + def encode_request(self, fields, files): + """ + Encode fields and files for posting to an HTTP server. + + :param fields: The fields to send as a list of (fieldname, value) + tuples. + :param files: The files to send as a list of (fieldname, filename, + file_bytes) tuple. + """ + # Adapted from packaging, which in turn was adapted from + # http://code.activestate.com/recipes/146306 + + parts = [] + boundary = self.boundary + for k, values in fields: + if not isinstance(values, (list, tuple)): + values = [values] + + for v in values: + parts.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"' % + k).encode('utf-8'), + b'', + v.encode('utf-8'))) + for key, filename, value in files: + parts.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"; filename="%s"' % + (key, filename)).encode('utf-8'), + b'', + value)) + + parts.extend((b'--' + boundary + b'--', b'')) + + body = b'\r\n'.join(parts) + ct = b'multipart/form-data; boundary=' + boundary + headers = { + 'Content-type': ct, + 'Content-length': str(len(body)) + } + return Request(self.url, body, headers) + + def search(self, terms, operator=None): # pragma: no cover + if isinstance(terms, string_types): + terms = {'name': terms} + rpc_proxy = ServerProxy(self.url, timeout=3.0) + try: + return rpc_proxy.search(terms, operator or 'and') + finally: + rpc_proxy('close')() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/locators.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/locators.py new file mode 100644 index 000000000..222c1bf3e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/locators.py @@ -0,0 +1,1295 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2023 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# + +import gzip +from io import BytesIO +import json +import logging +import os +import posixpath +import re +try: + import threading +except ImportError: # pragma: no cover + import dummy_threading as threading +import zlib + +from . import DistlibException +from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, queue, quote, unescape, build_opener, + HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError) +from .database import Distribution, DistributionPath, make_dist +from .metadata import Metadata, MetadataInvalidError +from .util import (cached_property, ensure_slash, split_filename, get_project_data, parse_requirement, + parse_name_and_version, ServerProxy, normalize_name) +from .version import get_scheme, UnsupportedVersionError +from .wheel import Wheel, is_compatible + +logger = logging.getLogger(__name__) + +HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)') +CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I) +HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml') +DEFAULT_INDEX = 'https://pypi.org/pypi' + + +def get_all_distribution_names(url=None): + """ + Return all distribution names known by an index. + :param url: The URL of the index. + :return: A list of all known distribution names. + """ + if url is None: + url = DEFAULT_INDEX + client = ServerProxy(url, timeout=3.0) + try: + return client.list_packages() + finally: + client('close')() + + +class RedirectHandler(BaseRedirectHandler): + """ + A class to work around a bug in some Python 3.2.x releases. + """ + + # There's a bug in the base version for some 3.2.x + # (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header + # returns e.g. /abc, it bails because it says the scheme '' + # is bogus, when actually it should use the request's + # URL for the scheme. See Python issue #13696. + def http_error_302(self, req, fp, code, msg, headers): + # Some servers (incorrectly) return multiple Location headers + # (so probably same goes for URI). Use first header. + newurl = None + for key in ('location', 'uri'): + if key in headers: + newurl = headers[key] + break + if newurl is None: # pragma: no cover + return + urlparts = urlparse(newurl) + if urlparts.scheme == '': + newurl = urljoin(req.get_full_url(), newurl) + if hasattr(headers, 'replace_header'): + headers.replace_header(key, newurl) + else: + headers[key] = newurl + return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, headers) + + http_error_301 = http_error_303 = http_error_307 = http_error_302 + + +class Locator(object): + """ + A base class for locators - things that locate distributions. + """ + source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz') + binary_extensions = ('.egg', '.exe', '.whl') + excluded_extensions = ('.pdf', ) + + # A list of tags indicating which wheels you want to match. The default + # value of None matches against the tags compatible with the running + # Python. If you want to match other values, set wheel_tags on a locator + # instance to a list of tuples (pyver, abi, arch) which you want to match. + wheel_tags = None + + downloadable_extensions = source_extensions + ('.whl', ) + + def __init__(self, scheme='default'): + """ + Initialise an instance. + :param scheme: Because locators look for most recent versions, they + need to know the version scheme to use. This specifies + the current PEP-recommended scheme - use ``'legacy'`` + if you need to support existing distributions on PyPI. + """ + self._cache = {} + self.scheme = scheme + # Because of bugs in some of the handlers on some of the platforms, + # we use our own opener rather than just using urlopen. + self.opener = build_opener(RedirectHandler()) + # If get_project() is called from locate(), the matcher instance + # is set from the requirement passed to locate(). See issue #18 for + # why this can be useful to know. + self.matcher = None + self.errors = queue.Queue() + + def get_errors(self): + """ + Return any errors which have occurred. + """ + result = [] + while not self.errors.empty(): # pragma: no cover + try: + e = self.errors.get(False) + result.append(e) + except self.errors.Empty: + continue + self.errors.task_done() + return result + + def clear_errors(self): + """ + Clear any errors which may have been logged. + """ + # Just get the errors and throw them away + self.get_errors() + + def clear_cache(self): + self._cache.clear() + + def _get_scheme(self): + return self._scheme + + def _set_scheme(self, value): + self._scheme = value + + scheme = property(_get_scheme, _set_scheme) + + def _get_project(self, name): + """ + For a given project, get a dictionary mapping available versions to Distribution + instances. + + This should be implemented in subclasses. + + If called from a locate() request, self.matcher will be set to a + matcher for the requirement to satisfy, otherwise it will be None. + """ + raise NotImplementedError('Please implement in the subclass') + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Please implement in the subclass') + + def get_project(self, name): + """ + For a given project, get a dictionary mapping available versions to Distribution + instances. + + This calls _get_project to do all the work, and just implements a caching layer on top. + """ + if self._cache is None: # pragma: no cover + result = self._get_project(name) + elif name in self._cache: + result = self._cache[name] + else: + self.clear_errors() + result = self._get_project(name) + self._cache[name] = result + return result + + def score_url(self, url): + """ + Give an url a score which can be used to choose preferred URLs + for a given project release. + """ + t = urlparse(url) + basename = posixpath.basename(t.path) + compatible = True + is_wheel = basename.endswith('.whl') + is_downloadable = basename.endswith(self.downloadable_extensions) + if is_wheel: + compatible = is_compatible(Wheel(basename), self.wheel_tags) + return (t.scheme == 'https', 'pypi.org' in t.netloc, is_downloadable, is_wheel, compatible, basename) + + def prefer_url(self, url1, url2): + """ + Choose one of two URLs where both are candidates for distribution + archives for the same version of a distribution (for example, + .tar.gz vs. zip). + + The current implementation favours https:// URLs over http://, archives + from PyPI over those from other locations, wheel compatibility (if a + wheel) and then the archive name. + """ + result = url2 + if url1: + s1 = self.score_url(url1) + s2 = self.score_url(url2) + if s1 > s2: + result = url1 + if result != url2: + logger.debug('Not replacing %r with %r', url1, url2) + else: + logger.debug('Replacing %r with %r', url1, url2) + return result + + def split_filename(self, filename, project_name): + """ + Attempt to split a filename in project name, version and Python version. + """ + return split_filename(filename, project_name) + + def convert_url_to_download_info(self, url, project_name): + """ + See if a URL is a candidate for a download URL for a project (the URL + has typically been scraped from an HTML page). + + If it is, a dictionary is returned with keys "name", "version", + "filename" and "url"; otherwise, None is returned. + """ + + def same_project(name1, name2): + return normalize_name(name1) == normalize_name(name2) + + result = None + scheme, netloc, path, params, query, frag = urlparse(url) + if frag.lower().startswith('egg='): # pragma: no cover + logger.debug('%s: version hint in fragment: %r', project_name, frag) + m = HASHER_HASH.match(frag) + if m: + algo, digest = m.groups() + else: + algo, digest = None, None + origpath = path + if path and path[-1] == '/': # pragma: no cover + path = path[:-1] + if path.endswith('.whl'): + try: + wheel = Wheel(path) + if not is_compatible(wheel, self.wheel_tags): + logger.debug('Wheel not compatible: %s', path) + else: + if project_name is None: + include = True + else: + include = same_project(wheel.name, project_name) + if include: + result = { + 'name': wheel.name, + 'version': wheel.version, + 'filename': wheel.filename, + 'url': urlunparse((scheme, netloc, origpath, params, query, '')), + 'python-version': ', '.join(['.'.join(list(v[2:])) for v in wheel.pyver]), + } + except Exception: # pragma: no cover + logger.warning('invalid path for wheel: %s', path) + elif not path.endswith(self.downloadable_extensions): # pragma: no cover + logger.debug('Not downloadable: %s', path) + else: # downloadable extension + path = filename = posixpath.basename(path) + for ext in self.downloadable_extensions: + if path.endswith(ext): + path = path[:-len(ext)] + t = self.split_filename(path, project_name) + if not t: # pragma: no cover + logger.debug('No match for project/version: %s', path) + else: + name, version, pyver = t + if not project_name or same_project(project_name, name): + result = { + 'name': name, + 'version': version, + 'filename': filename, + 'url': urlunparse((scheme, netloc, origpath, params, query, '')), + } + if pyver: # pragma: no cover + result['python-version'] = pyver + break + if result and algo: + result['%s_digest' % algo] = digest + return result + + def _get_digest(self, info): + """ + Get a digest from a dictionary by looking at a "digests" dictionary + or keys of the form 'algo_digest'. + + Returns a 2-tuple (algo, digest) if found, else None. Currently + looks only for SHA256, then MD5. + """ + result = None + if 'digests' in info: + digests = info['digests'] + for algo in ('sha256', 'md5'): + if algo in digests: + result = (algo, digests[algo]) + break + if not result: + for algo in ('sha256', 'md5'): + key = '%s_digest' % algo + if key in info: + result = (algo, info[key]) + break + return result + + def _update_version_data(self, result, info): + """ + Update a result dictionary (the final result from _get_project) with a + dictionary for a specific version, which typically holds information + gleaned from a filename or URL for an archive for the distribution. + """ + name = info.pop('name') + version = info.pop('version') + if version in result: + dist = result[version] + md = dist.metadata + else: + dist = make_dist(name, version, scheme=self.scheme) + md = dist.metadata + dist.digest = digest = self._get_digest(info) + url = info['url'] + result['digests'][url] = digest + if md.source_url != info['url']: + md.source_url = self.prefer_url(md.source_url, url) + result['urls'].setdefault(version, set()).add(url) + dist.locator = self + result[version] = dist + + def locate(self, requirement, prereleases=False): + """ + Find the most recent distribution which matches the given + requirement. + + :param requirement: A requirement of the form 'foo (1.0)' or perhaps + 'foo (>= 1.0, < 2.0, != 1.3)' + :param prereleases: If ``True``, allow pre-release versions + to be located. Otherwise, pre-release versions + are not returned. + :return: A :class:`Distribution` instance, or ``None`` if no such + distribution could be located. + """ + result = None + r = parse_requirement(requirement) + if r is None: # pragma: no cover + raise DistlibException('Not a valid requirement: %r' % requirement) + scheme = get_scheme(self.scheme) + self.matcher = matcher = scheme.matcher(r.requirement) + logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__) + versions = self.get_project(r.name) + if len(versions) > 2: # urls and digests keys are present + # sometimes, versions are invalid + slist = [] + vcls = matcher.version_class + for k in versions: + if k in ('urls', 'digests'): + continue + try: + if not matcher.match(k): + pass # logger.debug('%s did not match %r', matcher, k) + else: + if prereleases or not vcls(k).is_prerelease: + slist.append(k) + except Exception: # pragma: no cover + logger.warning('error matching %s with %r', matcher, k) + pass # slist.append(k) + if len(slist) > 1: + slist = sorted(slist, key=scheme.key) + if slist: + logger.debug('sorted list: %s', slist) + version = slist[-1] + result = versions[version] + if result: + if r.extras: + result.extras = r.extras + result.download_urls = versions.get('urls', {}).get(version, set()) + d = {} + sd = versions.get('digests', {}) + for url in result.download_urls: + if url in sd: # pragma: no cover + d[url] = sd[url] + result.digests = d + self.matcher = None + return result + + +class PyPIRPCLocator(Locator): + """ + This locator uses XML-RPC to locate distributions. It therefore + cannot be used with simple mirrors (that only mirror file content). + """ + + def __init__(self, url, **kwargs): + """ + Initialise an instance. + + :param url: The URL to use for XML-RPC. + :param kwargs: Passed to the superclass constructor. + """ + super(PyPIRPCLocator, self).__init__(**kwargs) + self.base_url = url + self.client = ServerProxy(url, timeout=3.0) + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + return set(self.client.list_packages()) + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + versions = self.client.package_releases(name, True) + for v in versions: + urls = self.client.release_urls(name, v) + data = self.client.release_data(name, v) + metadata = Metadata(scheme=self.scheme) + metadata.name = data['name'] + metadata.version = data['version'] + metadata.license = data.get('license') + metadata.keywords = data.get('keywords', []) + metadata.summary = data.get('summary') + dist = Distribution(metadata) + if urls: + info = urls[0] + metadata.source_url = info['url'] + dist.digest = self._get_digest(info) + dist.locator = self + result[v] = dist + for info in urls: + url = info['url'] + digest = self._get_digest(info) + result['urls'].setdefault(v, set()).add(url) + result['digests'][url] = digest + return result + + +class PyPIJSONLocator(Locator): + """ + This locator uses PyPI's JSON interface. It's very limited in functionality + and probably not worth using. + """ + + def __init__(self, url, **kwargs): + super(PyPIJSONLocator, self).__init__(**kwargs) + self.base_url = ensure_slash(url) + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Not available from this locator') + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + url = urljoin(self.base_url, '%s/json' % quote(name)) + try: + resp = self.opener.open(url) + data = resp.read().decode() # for now + d = json.loads(data) + md = Metadata(scheme=self.scheme) + data = d['info'] + md.name = data['name'] + md.version = data['version'] + md.license = data.get('license') + md.keywords = data.get('keywords', []) + md.summary = data.get('summary') + dist = Distribution(md) + dist.locator = self + # urls = d['urls'] + result[md.version] = dist + for info in d['urls']: + url = info['url'] + dist.download_urls.add(url) + dist.digests[url] = self._get_digest(info) + result['urls'].setdefault(md.version, set()).add(url) + result['digests'][url] = self._get_digest(info) + # Now get other releases + for version, infos in d['releases'].items(): + if version == md.version: + continue # already done + omd = Metadata(scheme=self.scheme) + omd.name = md.name + omd.version = version + odist = Distribution(omd) + odist.locator = self + result[version] = odist + for info in infos: + url = info['url'] + odist.download_urls.add(url) + odist.digests[url] = self._get_digest(info) + result['urls'].setdefault(version, set()).add(url) + result['digests'][url] = self._get_digest(info) + + +# for info in urls: +# md.source_url = info['url'] +# dist.digest = self._get_digest(info) +# dist.locator = self +# for info in urls: +# url = info['url'] +# result['urls'].setdefault(md.version, set()).add(url) +# result['digests'][url] = self._get_digest(info) + except Exception as e: + self.errors.put(text_type(e)) + logger.exception('JSON fetch failed: %s', e) + return result + + +class Page(object): + """ + This class represents a scraped HTML page. + """ + # The following slightly hairy-looking regex just looks for the contents of + # an anchor link, which has an attribute "href" either immediately preceded + # or immediately followed by a "rel" attribute. The attribute values can be + # declared with double quotes, single quotes or no quotes - which leads to + # the length of the expression. + _href = re.compile( + """ +(rel\\s*=\\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\\s\n]*))\\s+)? +href\\s*=\\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\\s\n]*)) +(\\s+rel\\s*=\\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\\s\n]*)))? +""", re.I | re.S | re.X) + _base = re.compile(r"""]+)""", re.I | re.S) + + def __init__(self, data, url): + """ + Initialise an instance with the Unicode page contents and the URL they + came from. + """ + self.data = data + self.base_url = self.url = url + m = self._base.search(self.data) + if m: + self.base_url = m.group(1) + + _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I) + + @cached_property + def links(self): + """ + Return the URLs of all the links on a page together with information + about their "rel" attribute, for determining which ones to treat as + downloads and which ones to queue for further scraping. + """ + + def clean(url): + "Tidy up an URL." + scheme, netloc, path, params, query, frag = urlparse(url) + return urlunparse((scheme, netloc, quote(path), params, query, frag)) + + result = set() + for match in self._href.finditer(self.data): + d = match.groupdict('') + rel = (d['rel1'] or d['rel2'] or d['rel3'] or d['rel4'] or d['rel5'] or d['rel6']) + url = d['url1'] or d['url2'] or d['url3'] + url = urljoin(self.base_url, url) + url = unescape(url) + url = self._clean_re.sub(lambda m: '%%%2x' % ord(m.group(0)), url) + result.add((url, rel)) + # We sort the result, hoping to bring the most recent versions + # to the front + result = sorted(result, key=lambda t: t[0], reverse=True) + return result + + +class SimpleScrapingLocator(Locator): + """ + A locator which scrapes HTML pages to locate downloads for a distribution. + This runs multiple threads to do the I/O; performance is at least as good + as pip's PackageFinder, which works in an analogous fashion. + """ + + # These are used to deal with various Content-Encoding schemes. + decoders = { + 'deflate': zlib.decompress, + 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(b)).read(), + 'none': lambda b: b, + } + + def __init__(self, url, timeout=None, num_workers=10, **kwargs): + """ + Initialise an instance. + :param url: The root URL to use for scraping. + :param timeout: The timeout, in seconds, to be applied to requests. + This defaults to ``None`` (no timeout specified). + :param num_workers: The number of worker threads you want to do I/O, + This defaults to 10. + :param kwargs: Passed to the superclass. + """ + super(SimpleScrapingLocator, self).__init__(**kwargs) + self.base_url = ensure_slash(url) + self.timeout = timeout + self._page_cache = {} + self._seen = set() + self._to_fetch = queue.Queue() + self._bad_hosts = set() + self.skip_externals = False + self.num_workers = num_workers + self._lock = threading.RLock() + # See issue #45: we need to be resilient when the locator is used + # in a thread, e.g. with concurrent.futures. We can't use self._lock + # as it is for coordinating our internal threads - the ones created + # in _prepare_threads. + self._gplock = threading.RLock() + self.platform_check = False # See issue #112 + + def _prepare_threads(self): + """ + Threads are created only when get_project is called, and terminate + before it returns. They are there primarily to parallelise I/O (i.e. + fetching web pages). + """ + self._threads = [] + for i in range(self.num_workers): + t = threading.Thread(target=self._fetch) + t.daemon = True + t.start() + self._threads.append(t) + + def _wait_threads(self): + """ + Tell all the threads to terminate (by sending a sentinel value) and + wait for them to do so. + """ + # Note that you need two loops, since you can't say which + # thread will get each sentinel + for t in self._threads: + self._to_fetch.put(None) # sentinel + for t in self._threads: + t.join() + self._threads = [] + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + with self._gplock: + self.result = result + self.project_name = name + url = urljoin(self.base_url, '%s/' % quote(name)) + self._seen.clear() + self._page_cache.clear() + self._prepare_threads() + try: + logger.debug('Queueing %s', url) + self._to_fetch.put(url) + self._to_fetch.join() + finally: + self._wait_threads() + del self.result + return result + + platform_dependent = re.compile(r'\b(linux_(i\d86|x86_64|arm\w+)|' + r'win(32|_amd64)|macosx_?\d+)\b', re.I) + + def _is_platform_dependent(self, url): + """ + Does an URL refer to a platform-specific download? + """ + return self.platform_dependent.search(url) + + def _process_download(self, url): + """ + See if an URL is a suitable download for a project. + + If it is, register information in the result dictionary (for + _get_project) about the specific version it's for. + + Note that the return value isn't actually used other than as a boolean + value. + """ + if self.platform_check and self._is_platform_dependent(url): + info = None + else: + info = self.convert_url_to_download_info(url, self.project_name) + logger.debug('process_download: %s -> %s', url, info) + if info: + with self._lock: # needed because self.result is shared + self._update_version_data(self.result, info) + return info + + def _should_queue(self, link, referrer, rel): + """ + Determine whether a link URL from a referring page and with a + particular "rel" attribute should be queued for scraping. + """ + scheme, netloc, path, _, _, _ = urlparse(link) + if path.endswith(self.source_extensions + self.binary_extensions + self.excluded_extensions): + result = False + elif self.skip_externals and not link.startswith(self.base_url): + result = False + elif not referrer.startswith(self.base_url): + result = False + elif rel not in ('homepage', 'download'): + result = False + elif scheme not in ('http', 'https', 'ftp'): + result = False + elif self._is_platform_dependent(link): + result = False + else: + host = netloc.split(':', 1)[0] + if host.lower() == 'localhost': + result = False + else: + result = True + logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, referrer, result) + return result + + def _fetch(self): + """ + Get a URL to fetch from the work queue, get the HTML page, examine its + links for download candidates and candidates for further scraping. + + This is a handy method to run in a thread. + """ + while True: + url = self._to_fetch.get() + try: + if url: + page = self.get_page(url) + if page is None: # e.g. after an error + continue + for link, rel in page.links: + if link not in self._seen: + try: + self._seen.add(link) + if (not self._process_download(link) and self._should_queue(link, url, rel)): + logger.debug('Queueing %s from %s', link, url) + self._to_fetch.put(link) + except MetadataInvalidError: # e.g. invalid versions + pass + except Exception as e: # pragma: no cover + self.errors.put(text_type(e)) + finally: + # always do this, to avoid hangs :-) + self._to_fetch.task_done() + if not url: + # logger.debug('Sentinel seen, quitting.') + break + + def get_page(self, url): + """ + Get the HTML for an URL, possibly from an in-memory cache. + + XXX TODO Note: this cache is never actually cleared. It's assumed that + the data won't get stale over the lifetime of a locator instance (not + necessarily true for the default_locator). + """ + # http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api + scheme, netloc, path, _, _, _ = urlparse(url) + if scheme == 'file' and os.path.isdir(url2pathname(path)): + url = urljoin(ensure_slash(url), 'index.html') + + if url in self._page_cache: + result = self._page_cache[url] + logger.debug('Returning %s from cache: %s', url, result) + else: + host = netloc.split(':', 1)[0] + result = None + if host in self._bad_hosts: + logger.debug('Skipping %s due to bad host %s', url, host) + else: + req = Request(url, headers={'Accept-encoding': 'identity'}) + try: + logger.debug('Fetching %s', url) + resp = self.opener.open(req, timeout=self.timeout) + logger.debug('Fetched %s', url) + headers = resp.info() + content_type = headers.get('Content-Type', '') + if HTML_CONTENT_TYPE.match(content_type): + final_url = resp.geturl() + data = resp.read() + encoding = headers.get('Content-Encoding') + if encoding: + decoder = self.decoders[encoding] # fail if not found + data = decoder(data) + encoding = 'utf-8' + m = CHARSET.search(content_type) + if m: + encoding = m.group(1) + try: + data = data.decode(encoding) + except UnicodeError: # pragma: no cover + data = data.decode('latin-1') # fallback + result = Page(data, final_url) + self._page_cache[final_url] = result + except HTTPError as e: + if e.code != 404: + logger.exception('Fetch failed: %s: %s', url, e) + except URLError as e: # pragma: no cover + logger.exception('Fetch failed: %s: %s', url, e) + with self._lock: + self._bad_hosts.add(host) + except Exception as e: # pragma: no cover + logger.exception('Fetch failed: %s: %s', url, e) + finally: + self._page_cache[url] = result # even if None (failure) + return result + + _distname_re = re.compile(']*>([^<]+)<') + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + page = self.get_page(self.base_url) + if not page: + raise DistlibException('Unable to get %s' % self.base_url) + for match in self._distname_re.finditer(page.data): + result.add(match.group(1)) + return result + + +class DirectoryLocator(Locator): + """ + This class locates distributions in a directory tree. + """ + + def __init__(self, path, **kwargs): + """ + Initialise an instance. + :param path: The root of the directory tree to search. + :param kwargs: Passed to the superclass constructor, + except for: + * recursive - if True (the default), subdirectories are + recursed into. If False, only the top-level directory + is searched, + """ + self.recursive = kwargs.pop('recursive', True) + super(DirectoryLocator, self).__init__(**kwargs) + path = os.path.abspath(path) + if not os.path.isdir(path): # pragma: no cover + raise DistlibException('Not a directory: %r' % path) + self.base_dir = path + + def should_include(self, filename, parent): + """ + Should a filename be considered as a candidate for a distribution + archive? As well as the filename, the directory which contains it + is provided, though not used by the current implementation. + """ + return filename.endswith(self.downloadable_extensions) + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + for root, dirs, files in os.walk(self.base_dir): + for fn in files: + if self.should_include(fn, root): + fn = os.path.join(root, fn) + url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', '')) + info = self.convert_url_to_download_info(url, name) + if info: + self._update_version_data(result, info) + if not self.recursive: + break + return result + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + for root, dirs, files in os.walk(self.base_dir): + for fn in files: + if self.should_include(fn, root): + fn = os.path.join(root, fn) + url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', '')) + info = self.convert_url_to_download_info(url, None) + if info: + result.add(info['name']) + if not self.recursive: + break + return result + + +class JSONLocator(Locator): + """ + This locator uses special extended metadata (not available on PyPI) and is + the basis of performant dependency resolution in distlib. Other locators + require archive downloads before dependencies can be determined! As you + might imagine, that can be slow. + """ + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Not available from this locator') + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + data = get_project_data(name) + if data: + for info in data.get('files', []): + if info['ptype'] != 'sdist' or info['pyversion'] != 'source': + continue + # We don't store summary in project metadata as it makes + # the data bigger for no benefit during dependency + # resolution + dist = make_dist(data['name'], + info['version'], + summary=data.get('summary', 'Placeholder for summary'), + scheme=self.scheme) + md = dist.metadata + md.source_url = info['url'] + # TODO SHA256 digest + if 'digest' in info and info['digest']: + dist.digest = ('md5', info['digest']) + md.dependencies = info.get('requirements', {}) + dist.exports = info.get('exports', {}) + result[dist.version] = dist + result['urls'].setdefault(dist.version, set()).add(info['url']) + return result + + +class DistPathLocator(Locator): + """ + This locator finds installed distributions in a path. It can be useful for + adding to an :class:`AggregatingLocator`. + """ + + def __init__(self, distpath, **kwargs): + """ + Initialise an instance. + + :param distpath: A :class:`DistributionPath` instance to search. + """ + super(DistPathLocator, self).__init__(**kwargs) + assert isinstance(distpath, DistributionPath) + self.distpath = distpath + + def _get_project(self, name): + dist = self.distpath.get_distribution(name) + if dist is None: + result = {'urls': {}, 'digests': {}} + else: + result = { + dist.version: dist, + 'urls': { + dist.version: set([dist.source_url]) + }, + 'digests': { + dist.version: set([None]) + } + } + return result + + +class AggregatingLocator(Locator): + """ + This class allows you to chain and/or merge a list of locators. + """ + + def __init__(self, *locators, **kwargs): + """ + Initialise an instance. + + :param locators: The list of locators to search. + :param kwargs: Passed to the superclass constructor, + except for: + * merge - if False (the default), the first successful + search from any of the locators is returned. If True, + the results from all locators are merged (this can be + slow). + """ + self.merge = kwargs.pop('merge', False) + self.locators = locators + super(AggregatingLocator, self).__init__(**kwargs) + + def clear_cache(self): + super(AggregatingLocator, self).clear_cache() + for locator in self.locators: + locator.clear_cache() + + def _set_scheme(self, value): + self._scheme = value + for locator in self.locators: + locator.scheme = value + + scheme = property(Locator.scheme.fget, _set_scheme) + + def _get_project(self, name): + result = {} + for locator in self.locators: + d = locator.get_project(name) + if d: + if self.merge: + files = result.get('urls', {}) + digests = result.get('digests', {}) + # next line could overwrite result['urls'], result['digests'] + result.update(d) + df = result.get('urls') + if files and df: + for k, v in files.items(): + if k in df: + df[k] |= v + else: + df[k] = v + dd = result.get('digests') + if digests and dd: + dd.update(digests) + else: + # See issue #18. If any dists are found and we're looking + # for specific constraints, we only return something if + # a match is found. For example, if a DirectoryLocator + # returns just foo (1.0) while we're looking for + # foo (>= 2.0), we'll pretend there was nothing there so + # that subsequent locators can be queried. Otherwise we + # would just return foo (1.0) which would then lead to a + # failure to find foo (>= 2.0), because other locators + # weren't searched. Note that this only matters when + # merge=False. + if self.matcher is None: + found = True + else: + found = False + for k in d: + if self.matcher.match(k): + found = True + break + if found: + result = d + break + return result + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + for locator in self.locators: + try: + result |= locator.get_distribution_names() + except NotImplementedError: + pass + return result + + +# We use a legacy scheme simply because most of the dists on PyPI use legacy +# versions which don't conform to PEP 440. +default_locator = AggregatingLocator( + # JSONLocator(), # don't use as PEP 426 is withdrawn + SimpleScrapingLocator('https://pypi.org/simple/', timeout=3.0), + scheme='legacy') + +locate = default_locator.locate + + +class DependencyFinder(object): + """ + Locate dependencies for distributions. + """ + + def __init__(self, locator=None): + """ + Initialise an instance, using the specified locator + to locate distributions. + """ + self.locator = locator or default_locator + self.scheme = get_scheme(self.locator.scheme) + + def add_distribution(self, dist): + """ + Add a distribution to the finder. This will update internal information + about who provides what. + :param dist: The distribution to add. + """ + logger.debug('adding distribution %s', dist) + name = dist.key + self.dists_by_name[name] = dist + self.dists[(name, dist.version)] = dist + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Add to provided: %s, %s, %s', name, version, dist) + self.provided.setdefault(name, set()).add((version, dist)) + + def remove_distribution(self, dist): + """ + Remove a distribution from the finder. This will update internal + information about who provides what. + :param dist: The distribution to remove. + """ + logger.debug('removing distribution %s', dist) + name = dist.key + del self.dists_by_name[name] + del self.dists[(name, dist.version)] + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Remove from provided: %s, %s, %s', name, version, dist) + s = self.provided[name] + s.remove((version, dist)) + if not s: + del self.provided[name] + + def get_matcher(self, reqt): + """ + Get a version matcher for a requirement. + :param reqt: The requirement + :type reqt: str + :return: A version matcher (an instance of + :class:`distlib.version.Matcher`). + """ + try: + matcher = self.scheme.matcher(reqt) + except UnsupportedVersionError: # pragma: no cover + # XXX compat-mode if cannot read the version + name = reqt.split()[0] + matcher = self.scheme.matcher(name) + return matcher + + def find_providers(self, reqt): + """ + Find the distributions which can fulfill a requirement. + + :param reqt: The requirement. + :type reqt: str + :return: A set of distribution which can fulfill the requirement. + """ + matcher = self.get_matcher(reqt) + name = matcher.key # case-insensitive + result = set() + provided = self.provided + if name in provided: + for version, provider in provided[name]: + try: + match = matcher.match(version) + except UnsupportedVersionError: + match = False + + if match: + result.add(provider) + break + return result + + def try_to_replace(self, provider, other, problems): + """ + Attempt to replace one provider with another. This is typically used + when resolving dependencies from multiple sources, e.g. A requires + (B >= 1.0) while C requires (B >= 1.1). + + For successful replacement, ``provider`` must meet all the requirements + which ``other`` fulfills. + + :param provider: The provider we are trying to replace with. + :param other: The provider we're trying to replace. + :param problems: If False is returned, this will contain what + problems prevented replacement. This is currently + a tuple of the literal string 'cantreplace', + ``provider``, ``other`` and the set of requirements + that ``provider`` couldn't fulfill. + :return: True if we can replace ``other`` with ``provider``, else + False. + """ + rlist = self.reqts[other] + unmatched = set() + for s in rlist: + matcher = self.get_matcher(s) + if not matcher.match(provider.version): + unmatched.add(s) + if unmatched: + # can't replace other with provider + problems.add(('cantreplace', provider, other, frozenset(unmatched))) + result = False + else: + # can replace other with provider + self.remove_distribution(other) + del self.reqts[other] + for s in rlist: + self.reqts.setdefault(provider, set()).add(s) + self.add_distribution(provider) + result = True + return result + + def find(self, requirement, meta_extras=None, prereleases=False): + """ + Find a distribution and all distributions it depends on. + + :param requirement: The requirement specifying the distribution to + find, or a Distribution instance. + :param meta_extras: A list of meta extras such as :test:, :build: and + so on. + :param prereleases: If ``True``, allow pre-release versions to be + returned - otherwise, don't return prereleases + unless they're all that's available. + + Return a set of :class:`Distribution` instances and a set of + problems. + + The distributions returned should be such that they have the + :attr:`required` attribute set to ``True`` if they were + from the ``requirement`` passed to ``find()``, and they have the + :attr:`build_time_dependency` attribute set to ``True`` unless they + are post-installation dependencies of the ``requirement``. + + The problems should be a tuple consisting of the string + ``'unsatisfied'`` and the requirement which couldn't be satisfied + by any distribution known to the locator. + """ + + self.provided = {} + self.dists = {} + self.dists_by_name = {} + self.reqts = {} + + meta_extras = set(meta_extras or []) + if ':*:' in meta_extras: + meta_extras.remove(':*:') + # :meta: and :run: are implicitly included + meta_extras |= set([':test:', ':build:', ':dev:']) + + if isinstance(requirement, Distribution): + dist = odist = requirement + logger.debug('passed %s as requirement', odist) + else: + dist = odist = self.locator.locate(requirement, prereleases=prereleases) + if dist is None: + raise DistlibException('Unable to locate %r' % requirement) + logger.debug('located %s', odist) + dist.requested = True + problems = set() + todo = set([dist]) + install_dists = set([odist]) + while todo: + dist = todo.pop() + name = dist.key # case-insensitive + if name not in self.dists_by_name: + self.add_distribution(dist) + else: + # import pdb; pdb.set_trace() + other = self.dists_by_name[name] + if other != dist: + self.try_to_replace(dist, other, problems) + + ireqts = dist.run_requires | dist.meta_requires + sreqts = dist.build_requires + ereqts = set() + if meta_extras and dist in install_dists: + for key in ('test', 'build', 'dev'): + e = ':%s:' % key + if e in meta_extras: + ereqts |= getattr(dist, '%s_requires' % key) + all_reqts = ireqts | sreqts | ereqts + for r in all_reqts: + providers = self.find_providers(r) + if not providers: + logger.debug('No providers found for %r', r) + provider = self.locator.locate(r, prereleases=prereleases) + # If no provider is found and we didn't consider + # prereleases, consider them now. + if provider is None and not prereleases: + provider = self.locator.locate(r, prereleases=True) + if provider is None: + logger.debug('Cannot satisfy %r', r) + problems.add(('unsatisfied', r)) + else: + n, v = provider.key, provider.version + if (n, v) not in self.dists: + todo.add(provider) + providers.add(provider) + if r in ireqts and dist in install_dists: + install_dists.add(provider) + logger.debug('Adding %s to install_dists', provider.name_and_version) + for p in providers: + name = p.key + if name not in self.dists_by_name: + self.reqts.setdefault(p, set()).add(r) + else: + other = self.dists_by_name[name] + if other != p: + # see if other can be replaced by p + self.try_to_replace(p, other, problems) + + dists = set(self.dists.values()) + for dist in dists: + dist.build_time_dependency = dist not in install_dists + if dist.build_time_dependency: + logger.debug('%s is a build-time dependency only.', dist.name_and_version) + logger.debug('find done for %s', odist) + return dists, problems diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/manifest.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/manifest.py new file mode 100644 index 000000000..420dcf12e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/manifest.py @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2023 Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +""" +Class representing the list of files in a distribution. + +Equivalent to distutils.filelist, but fixes some problems. +""" +import fnmatch +import logging +import os +import re +import sys + +from . import DistlibException +from .compat import fsdecode +from .util import convert_path + + +__all__ = ['Manifest'] + +logger = logging.getLogger(__name__) + +# a \ followed by some spaces + EOL +_COLLAPSE_PATTERN = re.compile('\\\\w*\n', re.M) +_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S) + +# +# Due to the different results returned by fnmatch.translate, we need +# to do slightly different processing for Python 2.7 and 3.2 ... this needed +# to be brought in for Python 3.6 onwards. +# +_PYTHON_VERSION = sys.version_info[:2] + + +class Manifest(object): + """ + A list of files built by exploring the filesystem and filtered by applying various + patterns to what we find there. + """ + + def __init__(self, base=None): + """ + Initialise an instance. + + :param base: The base directory to explore under. + """ + self.base = os.path.abspath(os.path.normpath(base or os.getcwd())) + self.prefix = self.base + os.sep + self.allfiles = None + self.files = set() + + # + # Public API + # + + def findall(self): + """Find all files under the base and set ``allfiles`` to the absolute + pathnames of files found. + """ + from stat import S_ISREG, S_ISDIR, S_ISLNK + + self.allfiles = allfiles = [] + root = self.base + stack = [root] + pop = stack.pop + push = stack.append + + while stack: + root = pop() + names = os.listdir(root) + + for name in names: + fullname = os.path.join(root, name) + + # Avoid excess stat calls -- just one will do, thank you! + stat = os.stat(fullname) + mode = stat.st_mode + if S_ISREG(mode): + allfiles.append(fsdecode(fullname)) + elif S_ISDIR(mode) and not S_ISLNK(mode): + push(fullname) + + def add(self, item): + """ + Add a file to the manifest. + + :param item: The pathname to add. This can be relative to the base. + """ + if not item.startswith(self.prefix): + item = os.path.join(self.base, item) + self.files.add(os.path.normpath(item)) + + def add_many(self, items): + """ + Add a list of files to the manifest. + + :param items: The pathnames to add. These can be relative to the base. + """ + for item in items: + self.add(item) + + def sorted(self, wantdirs=False): + """ + Return sorted files in directory order + """ + + def add_dir(dirs, d): + dirs.add(d) + logger.debug('add_dir added %s', d) + if d != self.base: + parent, _ = os.path.split(d) + assert parent not in ('', '/') + add_dir(dirs, parent) + + result = set(self.files) # make a copy! + if wantdirs: + dirs = set() + for f in result: + add_dir(dirs, os.path.dirname(f)) + result |= dirs + return [os.path.join(*path_tuple) for path_tuple in + sorted(os.path.split(path) for path in result)] + + def clear(self): + """Clear all collected files.""" + self.files = set() + self.allfiles = [] + + def process_directive(self, directive): + """ + Process a directive which either adds some files from ``allfiles`` to + ``files``, or removes some files from ``files``. + + :param directive: The directive to process. This should be in a format + compatible with distutils ``MANIFEST.in`` files: + + http://docs.python.org/distutils/sourcedist.html#commands + """ + # Parse the line: split it up, make sure the right number of words + # is there, and return the relevant words. 'action' is always + # defined: it's the first word of the line. Which of the other + # three are defined depends on the action; it'll be either + # patterns, (dir and patterns), or (dirpattern). + action, patterns, thedir, dirpattern = self._parse_directive(directive) + + # OK, now we know that the action is valid and we have the + # right number of words on the line for that action -- so we + # can proceed with minimal error-checking. + if action == 'include': + for pattern in patterns: + if not self._include_pattern(pattern, anchor=True): + logger.warning('no files found matching %r', pattern) + + elif action == 'exclude': + for pattern in patterns: + self._exclude_pattern(pattern, anchor=True) + + elif action == 'global-include': + for pattern in patterns: + if not self._include_pattern(pattern, anchor=False): + logger.warning('no files found matching %r ' + 'anywhere in distribution', pattern) + + elif action == 'global-exclude': + for pattern in patterns: + self._exclude_pattern(pattern, anchor=False) + + elif action == 'recursive-include': + for pattern in patterns: + if not self._include_pattern(pattern, prefix=thedir): + logger.warning('no files found matching %r ' + 'under directory %r', pattern, thedir) + + elif action == 'recursive-exclude': + for pattern in patterns: + self._exclude_pattern(pattern, prefix=thedir) + + elif action == 'graft': + if not self._include_pattern(None, prefix=dirpattern): + logger.warning('no directories found matching %r', + dirpattern) + + elif action == 'prune': + if not self._exclude_pattern(None, prefix=dirpattern): + logger.warning('no previously-included directories found ' + 'matching %r', dirpattern) + else: # pragma: no cover + # This should never happen, as it should be caught in + # _parse_template_line + raise DistlibException( + 'invalid action %r' % action) + + # + # Private API + # + + def _parse_directive(self, directive): + """ + Validate a directive. + :param directive: The directive to validate. + :return: A tuple of action, patterns, thedir, dir_patterns + """ + words = directive.split() + if len(words) == 1 and words[0] not in ('include', 'exclude', + 'global-include', + 'global-exclude', + 'recursive-include', + 'recursive-exclude', + 'graft', 'prune'): + # no action given, let's use the default 'include' + words.insert(0, 'include') + + action = words[0] + patterns = thedir = dir_pattern = None + + if action in ('include', 'exclude', + 'global-include', 'global-exclude'): + if len(words) < 2: + raise DistlibException( + '%r expects ...' % action) + + patterns = [convert_path(word) for word in words[1:]] + + elif action in ('recursive-include', 'recursive-exclude'): + if len(words) < 3: + raise DistlibException( + '%r expects ...' % action) + + thedir = convert_path(words[1]) + patterns = [convert_path(word) for word in words[2:]] + + elif action in ('graft', 'prune'): + if len(words) != 2: + raise DistlibException( + '%r expects a single ' % action) + + dir_pattern = convert_path(words[1]) + + else: + raise DistlibException('unknown action %r' % action) + + return action, patterns, thedir, dir_pattern + + def _include_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Select strings (presumably filenames) from 'self.files' that + match 'pattern', a Unix-style wildcard (glob) pattern. + + Patterns are not quite the same as implemented by the 'fnmatch' + module: '*' and '?' match non-special characters, where "special" + is platform-dependent: slash on Unix; colon, slash, and backslash on + DOS/Windows; and colon on Mac OS. + + If 'anchor' is true (the default), then the pattern match is more + stringent: "*.py" will match "foo.py" but not "foo/bar.py". If + 'anchor' is false, both of these will match. + + If 'prefix' is supplied, then only filenames starting with 'prefix' + (itself a pattern) and ending with 'pattern', with anything in between + them, will match. 'anchor' is ignored in this case. + + If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and + 'pattern' is assumed to be either a string containing a regex or a + regex object -- no translation is done, the regex is just compiled + and used as-is. + + Selected strings will be added to self.files. + + Return True if files are found. + """ + # XXX docstring lying about what the special chars are? + found = False + pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex) + + # delayed loading of allfiles list + if self.allfiles is None: + self.findall() + + for name in self.allfiles: + if pattern_re.search(name): + self.files.add(name) + found = True + return found + + def _exclude_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Remove strings (presumably filenames) from 'files' that match + 'pattern'. + + Other parameters are the same as for 'include_pattern()', above. + The list 'self.files' is modified in place. Return True if files are + found. + + This API is public to allow e.g. exclusion of SCM subdirs, e.g. when + packaging source distributions + """ + found = False + pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex) + for f in list(self.files): + if pattern_re.search(f): + self.files.remove(f) + found = True + return found + + def _translate_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Translate a shell-like wildcard pattern to a compiled regular + expression. + + Return the compiled regex. If 'is_regex' true, + then 'pattern' is directly compiled to a regex (if it's a string) + or just returned as-is (assumes it's a regex object). + """ + if is_regex: + if isinstance(pattern, str): + return re.compile(pattern) + else: + return pattern + + if _PYTHON_VERSION > (3, 2): + # ditch start and end characters + start, _, end = self._glob_to_re('_').partition('_') + + if pattern: + pattern_re = self._glob_to_re(pattern) + if _PYTHON_VERSION > (3, 2): + assert pattern_re.startswith(start) and pattern_re.endswith(end) + else: + pattern_re = '' + + base = re.escape(os.path.join(self.base, '')) + if prefix is not None: + # ditch end of pattern character + if _PYTHON_VERSION <= (3, 2): + empty_pattern = self._glob_to_re('') + prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)] + else: + prefix_re = self._glob_to_re(prefix) + assert prefix_re.startswith(start) and prefix_re.endswith(end) + prefix_re = prefix_re[len(start): len(prefix_re) - len(end)] + sep = os.sep + if os.sep == '\\': + sep = r'\\' + if _PYTHON_VERSION <= (3, 2): + pattern_re = '^' + base + sep.join((prefix_re, + '.*' + pattern_re)) + else: + pattern_re = pattern_re[len(start): len(pattern_re) - len(end)] + pattern_re = r'%s%s%s%s.*%s%s' % (start, base, prefix_re, sep, + pattern_re, end) + else: # no prefix -- respect anchor flag + if anchor: + if _PYTHON_VERSION <= (3, 2): + pattern_re = '^' + base + pattern_re + else: + pattern_re = r'%s%s%s' % (start, base, pattern_re[len(start):]) + + return re.compile(pattern_re) + + def _glob_to_re(self, pattern): + """Translate a shell-like glob pattern to a regular expression. + + Return a string containing the regex. Differs from + 'fnmatch.translate()' in that '*' does not match "special characters" + (which are platform-specific). + """ + pattern_re = fnmatch.translate(pattern) + + # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which + # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, + # and by extension they shouldn't match such "special characters" under + # any OS. So change all non-escaped dots in the RE to match any + # character except the special characters (currently: just os.sep). + sep = os.sep + if os.sep == '\\': + # we're using a regex to manipulate a regex, so we need + # to escape the backslash twice + sep = r'\\\\' + escaped = r'\1[^%s]' % sep + pattern_re = re.sub(r'((? y, + '!=': lambda x, y: x != y, + '<': lambda x, y: x < y, + '<=': lambda x, y: x == y or x < y, + '>': lambda x, y: x > y, + '>=': lambda x, y: x == y or x > y, + 'and': lambda x, y: x and y, + 'or': lambda x, y: x or y, + 'in': lambda x, y: x in y, + 'not in': lambda x, y: x not in y, + } + + def evaluate(self, expr, context): + """ + Evaluate a marker expression returned by the :func:`parse_requirement` + function in the specified context. + """ + if isinstance(expr, string_types): + if expr[0] in '\'"': + result = expr[1:-1] + else: + if expr not in context: + raise SyntaxError('unknown variable: %s' % expr) + result = context[expr] + else: + assert isinstance(expr, dict) + op = expr['op'] + if op not in self.operations: + raise NotImplementedError('op not implemented: %s' % op) + elhs = expr['lhs'] + erhs = expr['rhs'] + if _is_literal(expr['lhs']) and _is_literal(expr['rhs']): + raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs)) + + lhs = self.evaluate(elhs, context) + rhs = self.evaluate(erhs, context) + if ((_is_version_marker(elhs) or _is_version_marker(erhs)) and + op in ('<', '<=', '>', '>=', '===', '==', '!=', '~=')): + lhs = LV(lhs) + rhs = LV(rhs) + elif _is_version_marker(elhs) and op in ('in', 'not in'): + lhs = LV(lhs) + rhs = _get_versions(rhs) + result = self.operations[op](lhs, rhs) + return result + + +_DIGITS = re.compile(r'\d+\.\d+') + + +def default_context(): + + def format_full_version(info): + version = '%s.%s.%s' % (info.major, info.minor, info.micro) + kind = info.releaselevel + if kind != 'final': + version += kind[0] + str(info.serial) + return version + + if hasattr(sys, 'implementation'): + implementation_version = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + else: + implementation_version = '0' + implementation_name = '' + + ppv = platform.python_version() + m = _DIGITS.match(ppv) + pv = m.group(0) + result = { + 'implementation_name': implementation_name, + 'implementation_version': implementation_version, + 'os_name': os.name, + 'platform_machine': platform.machine(), + 'platform_python_implementation': platform.python_implementation(), + 'platform_release': platform.release(), + 'platform_system': platform.system(), + 'platform_version': platform.version(), + 'platform_in_venv': str(in_venv()), + 'python_full_version': ppv, + 'python_version': pv, + 'sys_platform': sys.platform, + } + return result + + +DEFAULT_CONTEXT = default_context() +del default_context + +evaluator = Evaluator() + + +def interpret(marker, execution_context=None): + """ + Interpret a marker and return a result depending on environment. + + :param marker: The marker to interpret. + :type marker: str + :param execution_context: The context used for name lookup. + :type execution_context: mapping + """ + try: + expr, rest = parse_marker(marker) + except Exception as e: + raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e)) + if rest and rest[0] != '#': + raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest)) + context = dict(DEFAULT_CONTEXT) + if execution_context: + context.update(execution_context) + return evaluator.evaluate(expr, context) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/metadata.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/metadata.py new file mode 100644 index 000000000..ce9a34b3e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/metadata.py @@ -0,0 +1,1031 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""Implementation of the Metadata for Python packages PEPs. + +Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and 2.2). +""" +from __future__ import unicode_literals + +import codecs +from email import message_from_file +import json +import logging +import re + +from . import DistlibException, __version__ +from .compat import StringIO, string_types, text_type +from .markers import interpret +from .util import extract_by_key, get_extras +from .version import get_scheme, PEP440_VERSION_RE + +logger = logging.getLogger(__name__) + + +class MetadataMissingError(DistlibException): + """A required metadata is missing""" + + +class MetadataConflictError(DistlibException): + """Attempt to read or write metadata fields that are conflictual.""" + + +class MetadataUnrecognizedVersionError(DistlibException): + """Unknown metadata version number.""" + + +class MetadataInvalidError(DistlibException): + """A metadata value is invalid""" + + +# public API of this module +__all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION'] + +# Encoding used for the PKG-INFO files +PKG_INFO_ENCODING = 'utf-8' + +# preferred version. Hopefully will be changed +# to 1.2 once PEP 345 is supported everywhere +PKG_INFO_PREFERRED_VERSION = '1.1' + +_LINE_PREFIX_1_2 = re.compile('\n \\|') +_LINE_PREFIX_PRE_1_2 = re.compile('\n ') +_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Summary', 'Description', 'Keywords', 'Home-page', + 'Author', 'Author-email', 'License') + +_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes', + 'Provides', 'Requires') + +_314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', 'Download-URL') + +_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', + 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist', + 'Requires-Python', 'Requires-External') + +_345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Obsoletes-Dist', 'Requires-External', + 'Maintainer', 'Maintainer-email', 'Project-URL') + +_426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', + 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist', + 'Requires-Python', 'Requires-External', 'Private-Version', 'Obsoleted-By', 'Setup-Requires-Dist', + 'Extension', 'Provides-Extra') + +_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension') + +# See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in +# the metadata. Include them in the tuple literal below to allow them +# (for now). +# Ditto for Obsoletes - see issue #140. +_566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires', 'Provides', 'Obsoletes') + +_566_MARKERS = ('Description-Content-Type', ) + +_643_MARKERS = ('Dynamic', 'License-File') + +_643_FIELDS = _566_FIELDS + _643_MARKERS + +_ALL_FIELDS = set() +_ALL_FIELDS.update(_241_FIELDS) +_ALL_FIELDS.update(_314_FIELDS) +_ALL_FIELDS.update(_345_FIELDS) +_ALL_FIELDS.update(_426_FIELDS) +_ALL_FIELDS.update(_566_FIELDS) +_ALL_FIELDS.update(_643_FIELDS) + +EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''') + + +def _version2fieldlist(version): + if version == '1.0': + return _241_FIELDS + elif version == '1.1': + return _314_FIELDS + elif version == '1.2': + return _345_FIELDS + elif version in ('1.3', '2.1'): + # avoid adding field names if already there + return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS) + elif version == '2.0': + raise ValueError('Metadata 2.0 is withdrawn and not supported') + # return _426_FIELDS + elif version == '2.2': + return _643_FIELDS + raise MetadataUnrecognizedVersionError(version) + + +def _best_version(fields): + """Detect the best version depending on the fields used.""" + + def _has_marker(keys, markers): + return any(marker in keys for marker in markers) + + keys = [key for key, value in fields.items() if value not in ([], 'UNKNOWN', None)] + possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.1', '2.2'] # 2.0 removed + + # first let's try to see if a field is not part of one of the version + for key in keys: + if key not in _241_FIELDS and '1.0' in possible_versions: + possible_versions.remove('1.0') + logger.debug('Removed 1.0 due to %s', key) + if key not in _314_FIELDS and '1.1' in possible_versions: + possible_versions.remove('1.1') + logger.debug('Removed 1.1 due to %s', key) + if key not in _345_FIELDS and '1.2' in possible_versions: + possible_versions.remove('1.2') + logger.debug('Removed 1.2 due to %s', key) + if key not in _566_FIELDS and '1.3' in possible_versions: + possible_versions.remove('1.3') + logger.debug('Removed 1.3 due to %s', key) + if key not in _566_FIELDS and '2.1' in possible_versions: + if key != 'Description': # In 2.1, description allowed after headers + possible_versions.remove('2.1') + logger.debug('Removed 2.1 due to %s', key) + if key not in _643_FIELDS and '2.2' in possible_versions: + possible_versions.remove('2.2') + logger.debug('Removed 2.2 due to %s', key) + # if key not in _426_FIELDS and '2.0' in possible_versions: + # possible_versions.remove('2.0') + # logger.debug('Removed 2.0 due to %s', key) + + # possible_version contains qualified versions + if len(possible_versions) == 1: + return possible_versions[0] # found ! + elif len(possible_versions) == 0: + logger.debug('Out of options - unknown metadata set: %s', fields) + raise MetadataConflictError('Unknown metadata set') + + # let's see if one unique marker is found + is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS) + is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS) + is_2_1 = '2.1' in possible_versions and _has_marker(keys, _566_MARKERS) + # is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS) + is_2_2 = '2.2' in possible_versions and _has_marker(keys, _643_MARKERS) + if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_2) > 1: + raise MetadataConflictError('You used incompatible 1.1/1.2/2.1/2.2 fields') + + # we have the choice, 1.0, or 1.2, 2.1 or 2.2 + # - 1.0 has a broken Summary field but works with all tools + # - 1.1 is to avoid + # - 1.2 fixes Summary but has little adoption + # - 2.1 adds more features + # - 2.2 is the latest + if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_2: + # we couldn't find any specific marker + if PKG_INFO_PREFERRED_VERSION in possible_versions: + return PKG_INFO_PREFERRED_VERSION + if is_1_1: + return '1.1' + if is_1_2: + return '1.2' + if is_2_1: + return '2.1' + # if is_2_2: + # return '2.2' + + return '2.2' + + +# This follows the rules about transforming keys as described in +# https://www.python.org/dev/peps/pep-0566/#id17 +_ATTR2FIELD = {name.lower().replace("-", "_"): name for name in _ALL_FIELDS} +_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()} + +_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist') +_VERSIONS_FIELDS = ('Requires-Python', ) +_VERSION_FIELDS = ('Version', ) +_LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', 'Requires', 'Provides', 'Obsoletes-Dist', 'Provides-Dist', + 'Requires-Dist', 'Requires-External', 'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist', + 'Provides-Extra', 'Extension', 'License-File') +_LISTTUPLEFIELDS = ('Project-URL', ) + +_ELEMENTSFIELD = ('Keywords', ) + +_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description') + +_MISSING = object() + +_FILESAFE = re.compile('[^A-Za-z0-9.]+') + + +def _get_name_and_version(name, version, for_filename=False): + """Return the distribution name with version. + + If for_filename is true, return a filename-escaped form.""" + if for_filename: + # For both name and version any runs of non-alphanumeric or '.' + # characters are replaced with a single '-'. Additionally any + # spaces in the version string become '.' + name = _FILESAFE.sub('-', name) + version = _FILESAFE.sub('-', version.replace(' ', '.')) + return '%s-%s' % (name, version) + + +class LegacyMetadata(object): + """The legacy metadata of a release. + + Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can + instantiate the class with one of these arguments (or none): + - *path*, the path to a metadata file + - *fileobj* give a file-like object with metadata as content + - *mapping* is a dict-like object + - *scheme* is a version scheme name + """ + + # TODO document the mapping API and UNKNOWN default key + + def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'): + if [path, fileobj, mapping].count(None) < 2: + raise TypeError('path, fileobj and mapping are exclusive') + self._fields = {} + self.requires_files = [] + self._dependencies = None + self.scheme = scheme + if path is not None: + self.read(path) + elif fileobj is not None: + self.read_file(fileobj) + elif mapping is not None: + self.update(mapping) + self.set_metadata_version() + + def set_metadata_version(self): + self._fields['Metadata-Version'] = _best_version(self._fields) + + def _write_field(self, fileobj, name, value): + fileobj.write('%s: %s\n' % (name, value)) + + def __getitem__(self, name): + return self.get(name) + + def __setitem__(self, name, value): + return self.set(name, value) + + def __delitem__(self, name): + field_name = self._convert_name(name) + try: + del self._fields[field_name] + except KeyError: + raise KeyError(name) + + def __contains__(self, name): + return (name in self._fields or self._convert_name(name) in self._fields) + + def _convert_name(self, name): + if name in _ALL_FIELDS: + return name + name = name.replace('-', '_').lower() + return _ATTR2FIELD.get(name, name) + + def _default_value(self, name): + if name in _LISTFIELDS or name in _ELEMENTSFIELD: + return [] + return 'UNKNOWN' + + def _remove_line_prefix(self, value): + if self.metadata_version in ('1.0', '1.1'): + return _LINE_PREFIX_PRE_1_2.sub('\n', value) + else: + return _LINE_PREFIX_1_2.sub('\n', value) + + def __getattr__(self, name): + if name in _ATTR2FIELD: + return self[name] + raise AttributeError(name) + + # + # Public API + # + + def get_fullname(self, filesafe=False): + """ + Return the distribution name with version. + + If filesafe is true, return a filename-escaped form. + """ + return _get_name_and_version(self['Name'], self['Version'], filesafe) + + def is_field(self, name): + """return True if name is a valid metadata key""" + name = self._convert_name(name) + return name in _ALL_FIELDS + + def is_multi_field(self, name): + name = self._convert_name(name) + return name in _LISTFIELDS + + def read(self, filepath): + """Read the metadata values from a file path.""" + fp = codecs.open(filepath, 'r', encoding='utf-8') + try: + self.read_file(fp) + finally: + fp.close() + + def read_file(self, fileob): + """Read the metadata values from a file object.""" + msg = message_from_file(fileob) + self._fields['Metadata-Version'] = msg['metadata-version'] + + # When reading, get all the fields we can + for field in _ALL_FIELDS: + if field not in msg: + continue + if field in _LISTFIELDS: + # we can have multiple lines + values = msg.get_all(field) + if field in _LISTTUPLEFIELDS and values is not None: + values = [tuple(value.split(',')) for value in values] + self.set(field, values) + else: + # single line + value = msg[field] + if value is not None and value != 'UNKNOWN': + self.set(field, value) + + # PEP 566 specifies that the body be used for the description, if + # available + body = msg.get_payload() + self["Description"] = body if body else self["Description"] + # logger.debug('Attempting to set metadata for %s', self) + # self.set_metadata_version() + + def write(self, filepath, skip_unknown=False): + """Write the metadata fields to filepath.""" + fp = codecs.open(filepath, 'w', encoding='utf-8') + try: + self.write_file(fp, skip_unknown) + finally: + fp.close() + + def write_file(self, fileobject, skip_unknown=False): + """Write the PKG-INFO format data to a file object.""" + self.set_metadata_version() + + for field in _version2fieldlist(self['Metadata-Version']): + values = self.get(field) + if skip_unknown and values in ('UNKNOWN', [], ['UNKNOWN']): + continue + if field in _ELEMENTSFIELD: + self._write_field(fileobject, field, ','.join(values)) + continue + if field not in _LISTFIELDS: + if field == 'Description': + if self.metadata_version in ('1.0', '1.1'): + values = values.replace('\n', '\n ') + else: + values = values.replace('\n', '\n |') + values = [values] + + if field in _LISTTUPLEFIELDS: + values = [','.join(value) for value in values] + + for value in values: + self._write_field(fileobject, field, value) + + def update(self, other=None, **kwargs): + """Set metadata values from the given iterable `other` and kwargs. + + Behavior is like `dict.update`: If `other` has a ``keys`` method, + they are looped over and ``self[key]`` is assigned ``other[key]``. + Else, ``other`` is an iterable of ``(key, value)`` iterables. + + Keys that don't match a metadata field or that have an empty value are + dropped. + """ + + def _set(key, value): + if key in _ATTR2FIELD and value: + self.set(self._convert_name(key), value) + + if not other: + # other is None or empty container + pass + elif hasattr(other, 'keys'): + for k in other.keys(): + _set(k, other[k]) + else: + for k, v in other: + _set(k, v) + + if kwargs: + for k, v in kwargs.items(): + _set(k, v) + + def set(self, name, value): + """Control then set a metadata field.""" + name = self._convert_name(name) + + if ((name in _ELEMENTSFIELD or name == 'Platform') and not isinstance(value, (list, tuple))): + if isinstance(value, string_types): + value = [v.strip() for v in value.split(',')] + else: + value = [] + elif (name in _LISTFIELDS and not isinstance(value, (list, tuple))): + if isinstance(value, string_types): + value = [value] + else: + value = [] + + if logger.isEnabledFor(logging.WARNING): + project_name = self['Name'] + + scheme = get_scheme(self.scheme) + if name in _PREDICATE_FIELDS and value is not None: + for v in value: + # check that the values are valid + if not scheme.is_valid_matcher(v.split(';')[0]): + logger.warning("'%s': '%s' is not valid (field '%s')", project_name, v, name) + # FIXME this rejects UNKNOWN, is that right? + elif name in _VERSIONS_FIELDS and value is not None: + if not scheme.is_valid_constraint_list(value): + logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name) + elif name in _VERSION_FIELDS and value is not None: + if not scheme.is_valid_version(value): + logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name) + + if name in _UNICODEFIELDS: + if name == 'Description': + value = self._remove_line_prefix(value) + + self._fields[name] = value + + def get(self, name, default=_MISSING): + """Get a metadata field.""" + name = self._convert_name(name) + if name not in self._fields: + if default is _MISSING: + default = self._default_value(name) + return default + if name in _UNICODEFIELDS: + value = self._fields[name] + return value + elif name in _LISTFIELDS: + value = self._fields[name] + if value is None: + return [] + res = [] + for val in value: + if name not in _LISTTUPLEFIELDS: + res.append(val) + else: + # That's for Project-URL + res.append((val[0], val[1])) + return res + + elif name in _ELEMENTSFIELD: + value = self._fields[name] + if isinstance(value, string_types): + return value.split(',') + return self._fields[name] + + def check(self, strict=False): + """Check if the metadata is compliant. If strict is True then raise if + no Name or Version are provided""" + self.set_metadata_version() + + # XXX should check the versions (if the file was loaded) + missing, warnings = [], [] + + for attr in ('Name', 'Version'): # required by PEP 345 + if attr not in self: + missing.append(attr) + + if strict and missing != []: + msg = 'missing required metadata: %s' % ', '.join(missing) + raise MetadataMissingError(msg) + + for attr in ('Home-page', 'Author'): + if attr not in self: + missing.append(attr) + + # checking metadata 1.2 (XXX needs to check 1.1, 1.0) + if self['Metadata-Version'] != '1.2': + return missing, warnings + + scheme = get_scheme(self.scheme) + + def are_valid_constraints(value): + for v in value: + if not scheme.is_valid_matcher(v.split(';')[0]): + return False + return True + + for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints), + (_VERSIONS_FIELDS, scheme.is_valid_constraint_list), (_VERSION_FIELDS, + scheme.is_valid_version)): + for field in fields: + value = self.get(field, None) + if value is not None and not controller(value): + warnings.append("Wrong value for '%s': %s" % (field, value)) + + return missing, warnings + + def todict(self, skip_missing=False): + """Return fields as a dict. + + Field names will be converted to use the underscore-lowercase style + instead of hyphen-mixed case (i.e. home_page instead of Home-page). + This is as per https://www.python.org/dev/peps/pep-0566/#id17. + """ + self.set_metadata_version() + + fields = _version2fieldlist(self['Metadata-Version']) + + data = {} + + for field_name in fields: + if not skip_missing or field_name in self._fields: + key = _FIELD2ATTR[field_name] + if key != 'project_url': + data[key] = self[field_name] + else: + data[key] = [','.join(u) for u in self[field_name]] + + return data + + def add_requirements(self, requirements): + if self['Metadata-Version'] == '1.1': + # we can't have 1.1 metadata *and* Setuptools requires + for field in ('Obsoletes', 'Requires', 'Provides'): + if field in self: + del self[field] + self['Requires-Dist'] += requirements + + # Mapping API + # TODO could add iter* variants + + def keys(self): + return list(_version2fieldlist(self['Metadata-Version'])) + + def __iter__(self): + for key in self.keys(): + yield key + + def values(self): + return [self[key] for key in self.keys()] + + def items(self): + return [(key, self[key]) for key in self.keys()] + + def __repr__(self): + return '<%s %s %s>' % (self.__class__.__name__, self.name, self.version) + + +METADATA_FILENAME = 'pydist.json' +WHEEL_METADATA_FILENAME = 'metadata.json' +LEGACY_METADATA_FILENAME = 'METADATA' + + +class Metadata(object): + """ + The metadata of a release. This implementation uses 2.1 + metadata where possible. If not possible, it wraps a LegacyMetadata + instance which handles the key-value metadata format. + """ + + METADATA_VERSION_MATCHER = re.compile(r'^\d+(\.\d+)*$') + + NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I) + + FIELDNAME_MATCHER = re.compile('^[A-Z]([0-9A-Z-]*[0-9A-Z])?$', re.I) + + VERSION_MATCHER = PEP440_VERSION_RE + + SUMMARY_MATCHER = re.compile('.{1,2047}') + + METADATA_VERSION = '2.0' + + GENERATOR = 'distlib (%s)' % __version__ + + MANDATORY_KEYS = { + 'name': (), + 'version': (), + 'summary': ('legacy', ), + } + + INDEX_KEYS = ('name version license summary description author ' + 'author_email keywords platform home_page classifiers ' + 'download_url') + + DEPENDENCY_KEYS = ('extras run_requires test_requires build_requires ' + 'dev_requires provides meta_requires obsoleted_by ' + 'supports_environments') + + SYNTAX_VALIDATORS = { + 'metadata_version': (METADATA_VERSION_MATCHER, ()), + 'name': (NAME_MATCHER, ('legacy', )), + 'version': (VERSION_MATCHER, ('legacy', )), + 'summary': (SUMMARY_MATCHER, ('legacy', )), + 'dynamic': (FIELDNAME_MATCHER, ('legacy', )), + } + + __slots__ = ('_legacy', '_data', 'scheme') + + def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'): + if [path, fileobj, mapping].count(None) < 2: + raise TypeError('path, fileobj and mapping are exclusive') + self._legacy = None + self._data = None + self.scheme = scheme + # import pdb; pdb.set_trace() + if mapping is not None: + try: + self._validate_mapping(mapping, scheme) + self._data = mapping + except MetadataUnrecognizedVersionError: + self._legacy = LegacyMetadata(mapping=mapping, scheme=scheme) + self.validate() + else: + data = None + if path: + with open(path, 'rb') as f: + data = f.read() + elif fileobj: + data = fileobj.read() + if data is None: + # Initialised with no args - to be added + self._data = { + 'metadata_version': self.METADATA_VERSION, + 'generator': self.GENERATOR, + } + else: + if not isinstance(data, text_type): + data = data.decode('utf-8') + try: + self._data = json.loads(data) + self._validate_mapping(self._data, scheme) + except ValueError: + # Note: MetadataUnrecognizedVersionError does not + # inherit from ValueError (it's a DistlibException, + # which should not inherit from ValueError). + # The ValueError comes from the json.load - if that + # succeeds and we get a validation error, we want + # that to propagate + self._legacy = LegacyMetadata(fileobj=StringIO(data), scheme=scheme) + self.validate() + + common_keys = set(('name', 'version', 'license', 'keywords', 'summary')) + + none_list = (None, list) + none_dict = (None, dict) + + mapped_keys = { + 'run_requires': ('Requires-Dist', list), + 'build_requires': ('Setup-Requires-Dist', list), + 'dev_requires': none_list, + 'test_requires': none_list, + 'meta_requires': none_list, + 'extras': ('Provides-Extra', list), + 'modules': none_list, + 'namespaces': none_list, + 'exports': none_dict, + 'commands': none_dict, + 'classifiers': ('Classifier', list), + 'source_url': ('Download-URL', None), + 'metadata_version': ('Metadata-Version', None), + } + + del none_list, none_dict + + def __getattribute__(self, key): + common = object.__getattribute__(self, 'common_keys') + mapped = object.__getattribute__(self, 'mapped_keys') + if key in mapped: + lk, maker = mapped[key] + if self._legacy: + if lk is None: + result = None if maker is None else maker() + else: + result = self._legacy.get(lk) + else: + value = None if maker is None else maker() + if key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'): + result = self._data.get(key, value) + else: + # special cases for PEP 459 + sentinel = object() + result = sentinel + d = self._data.get('extensions') + if d: + if key == 'commands': + result = d.get('python.commands', value) + elif key == 'classifiers': + d = d.get('python.details') + if d: + result = d.get(key, value) + else: + d = d.get('python.exports') + if not d: + d = self._data.get('python.exports') + if d: + result = d.get(key, value) + if result is sentinel: + result = value + elif key not in common: + result = object.__getattribute__(self, key) + elif self._legacy: + result = self._legacy.get(key) + else: + result = self._data.get(key) + return result + + def _validate_value(self, key, value, scheme=None): + if key in self.SYNTAX_VALIDATORS: + pattern, exclusions = self.SYNTAX_VALIDATORS[key] + if (scheme or self.scheme) not in exclusions: + m = pattern.match(value) + if not m: + raise MetadataInvalidError("'%s' is an invalid value for " + "the '%s' property" % (value, key)) + + def __setattr__(self, key, value): + self._validate_value(key, value) + common = object.__getattribute__(self, 'common_keys') + mapped = object.__getattribute__(self, 'mapped_keys') + if key in mapped: + lk, _ = mapped[key] + if self._legacy: + if lk is None: + raise NotImplementedError + self._legacy[lk] = value + elif key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'): + self._data[key] = value + else: + # special cases for PEP 459 + d = self._data.setdefault('extensions', {}) + if key == 'commands': + d['python.commands'] = value + elif key == 'classifiers': + d = d.setdefault('python.details', {}) + d[key] = value + else: + d = d.setdefault('python.exports', {}) + d[key] = value + elif key not in common: + object.__setattr__(self, key, value) + else: + if key == 'keywords': + if isinstance(value, string_types): + value = value.strip() + if value: + value = value.split() + else: + value = [] + if self._legacy: + self._legacy[key] = value + else: + self._data[key] = value + + @property + def name_and_version(self): + return _get_name_and_version(self.name, self.version, True) + + @property + def provides(self): + if self._legacy: + result = self._legacy['Provides-Dist'] + else: + result = self._data.setdefault('provides', []) + s = '%s (%s)' % (self.name, self.version) + if s not in result: + result.append(s) + return result + + @provides.setter + def provides(self, value): + if self._legacy: + self._legacy['Provides-Dist'] = value + else: + self._data['provides'] = value + + def get_requirements(self, reqts, extras=None, env=None): + """ + Base method to get dependencies, given a set of extras + to satisfy and an optional environment context. + :param reqts: A list of sometimes-wanted dependencies, + perhaps dependent on extras and environment. + :param extras: A list of optional components being requested. + :param env: An optional environment for marker evaluation. + """ + if self._legacy: + result = reqts + else: + result = [] + extras = get_extras(extras or [], self.extras) + for d in reqts: + if 'extra' not in d and 'environment' not in d: + # unconditional + include = True + else: + if 'extra' not in d: + # Not extra-dependent - only environment-dependent + include = True + else: + include = d.get('extra') in extras + if include: + # Not excluded because of extras, check environment + marker = d.get('environment') + if marker: + include = interpret(marker, env) + if include: + result.extend(d['requires']) + for key in ('build', 'dev', 'test'): + e = ':%s:' % key + if e in extras: + extras.remove(e) + # A recursive call, but it should terminate since 'test' + # has been removed from the extras + reqts = self._data.get('%s_requires' % key, []) + result.extend(self.get_requirements(reqts, extras=extras, env=env)) + return result + + @property + def dictionary(self): + if self._legacy: + return self._from_legacy() + return self._data + + @property + def dependencies(self): + if self._legacy: + raise NotImplementedError + else: + return extract_by_key(self._data, self.DEPENDENCY_KEYS) + + @dependencies.setter + def dependencies(self, value): + if self._legacy: + raise NotImplementedError + else: + self._data.update(value) + + def _validate_mapping(self, mapping, scheme): + if mapping.get('metadata_version') != self.METADATA_VERSION: + raise MetadataUnrecognizedVersionError() + missing = [] + for key, exclusions in self.MANDATORY_KEYS.items(): + if key not in mapping: + if scheme not in exclusions: + missing.append(key) + if missing: + msg = 'Missing metadata items: %s' % ', '.join(missing) + raise MetadataMissingError(msg) + for k, v in mapping.items(): + self._validate_value(k, v, scheme) + + def validate(self): + if self._legacy: + missing, warnings = self._legacy.check(True) + if missing or warnings: + logger.warning('Metadata: missing: %s, warnings: %s', missing, warnings) + else: + self._validate_mapping(self._data, self.scheme) + + def todict(self): + if self._legacy: + return self._legacy.todict(True) + else: + result = extract_by_key(self._data, self.INDEX_KEYS) + return result + + def _from_legacy(self): + assert self._legacy and not self._data + result = { + 'metadata_version': self.METADATA_VERSION, + 'generator': self.GENERATOR, + } + lmd = self._legacy.todict(True) # skip missing ones + for k in ('name', 'version', 'license', 'summary', 'description', 'classifier'): + if k in lmd: + if k == 'classifier': + nk = 'classifiers' + else: + nk = k + result[nk] = lmd[k] + kw = lmd.get('Keywords', []) + if kw == ['']: + kw = [] + result['keywords'] = kw + keys = (('requires_dist', 'run_requires'), ('setup_requires_dist', 'build_requires')) + for ok, nk in keys: + if ok in lmd and lmd[ok]: + result[nk] = [{'requires': lmd[ok]}] + result['provides'] = self.provides + # author = {} + # maintainer = {} + return result + + LEGACY_MAPPING = { + 'name': 'Name', + 'version': 'Version', + ('extensions', 'python.details', 'license'): 'License', + 'summary': 'Summary', + 'description': 'Description', + ('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page', + ('extensions', 'python.project', 'contacts', 0, 'name'): 'Author', + ('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email', + 'source_url': 'Download-URL', + ('extensions', 'python.details', 'classifiers'): 'Classifier', + } + + def _to_legacy(self): + + def process_entries(entries): + reqts = set() + for e in entries: + extra = e.get('extra') + env = e.get('environment') + rlist = e['requires'] + for r in rlist: + if not env and not extra: + reqts.add(r) + else: + marker = '' + if extra: + marker = 'extra == "%s"' % extra + if env: + if marker: + marker = '(%s) and %s' % (env, marker) + else: + marker = env + reqts.add(';'.join((r, marker))) + return reqts + + assert self._data and not self._legacy + result = LegacyMetadata() + nmd = self._data + # import pdb; pdb.set_trace() + for nk, ok in self.LEGACY_MAPPING.items(): + if not isinstance(nk, tuple): + if nk in nmd: + result[ok] = nmd[nk] + else: + d = nmd + found = True + for k in nk: + try: + d = d[k] + except (KeyError, IndexError): + found = False + break + if found: + result[ok] = d + r1 = process_entries(self.run_requires + self.meta_requires) + r2 = process_entries(self.build_requires + self.dev_requires) + if self.extras: + result['Provides-Extra'] = sorted(self.extras) + result['Requires-Dist'] = sorted(r1) + result['Setup-Requires-Dist'] = sorted(r2) + # TODO: any other fields wanted + return result + + def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True): + if [path, fileobj].count(None) != 1: + raise ValueError('Exactly one of path and fileobj is needed') + self.validate() + if legacy: + if self._legacy: + legacy_md = self._legacy + else: + legacy_md = self._to_legacy() + if path: + legacy_md.write(path, skip_unknown=skip_unknown) + else: + legacy_md.write_file(fileobj, skip_unknown=skip_unknown) + else: + if self._legacy: + d = self._from_legacy() + else: + d = self._data + if fileobj: + json.dump(d, fileobj, ensure_ascii=True, indent=2, sort_keys=True) + else: + with codecs.open(path, 'w', 'utf-8') as f: + json.dump(d, f, ensure_ascii=True, indent=2, sort_keys=True) + + def add_requirements(self, requirements): + if self._legacy: + self._legacy.add_requirements(requirements) + else: + run_requires = self._data.setdefault('run_requires', []) + always = None + for entry in run_requires: + if 'environment' not in entry and 'extra' not in entry: + always = entry + break + if always is None: + always = {'requires': requirements} + run_requires.insert(0, always) + else: + rset = set(always['requires']) | set(requirements) + always['requires'] = sorted(rset) + + def __repr__(self): + name = self.name or '(no name)' + version = self.version or 'no version' + return '<%s %s %s (%s)>' % (self.__class__.__name__, self.metadata_version, name, version) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/resources.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/resources.py new file mode 100644 index 000000000..fef52aa10 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/resources.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2017 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import unicode_literals + +import bisect +import io +import logging +import os +import pkgutil +import sys +import types +import zipimport + +from . import DistlibException +from .util import cached_property, get_cache_base, Cache + +logger = logging.getLogger(__name__) + + +cache = None # created when needed + + +class ResourceCache(Cache): + def __init__(self, base=None): + if base is None: + # Use native string to avoid issues on 2.x: see Python #20140. + base = os.path.join(get_cache_base(), str('resource-cache')) + super(ResourceCache, self).__init__(base) + + def is_stale(self, resource, path): + """ + Is the cache stale for the given resource? + + :param resource: The :class:`Resource` being cached. + :param path: The path of the resource in the cache. + :return: True if the cache is stale. + """ + # Cache invalidation is a hard problem :-) + return True + + def get(self, resource): + """ + Get a resource into the cache, + + :param resource: A :class:`Resource` instance. + :return: The pathname of the resource in the cache. + """ + prefix, path = resource.finder.get_cache_info(resource) + if prefix is None: + result = path + else: + result = os.path.join(self.base, self.prefix_to_dir(prefix), path) + dirname = os.path.dirname(result) + if not os.path.isdir(dirname): + os.makedirs(dirname) + if not os.path.exists(result): + stale = True + else: + stale = self.is_stale(resource, path) + if stale: + # write the bytes of the resource to the cache location + with open(result, 'wb') as f: + f.write(resource.bytes) + return result + + +class ResourceBase(object): + def __init__(self, finder, name): + self.finder = finder + self.name = name + + +class Resource(ResourceBase): + """ + A class representing an in-package resource, such as a data file. This is + not normally instantiated by user code, but rather by a + :class:`ResourceFinder` which manages the resource. + """ + is_container = False # Backwards compatibility + + def as_stream(self): + """ + Get the resource as a stream. + + This is not a property to make it obvious that it returns a new stream + each time. + """ + return self.finder.get_stream(self) + + @cached_property + def file_path(self): + global cache + if cache is None: + cache = ResourceCache() + return cache.get(self) + + @cached_property + def bytes(self): + return self.finder.get_bytes(self) + + @cached_property + def size(self): + return self.finder.get_size(self) + + +class ResourceContainer(ResourceBase): + is_container = True # Backwards compatibility + + @cached_property + def resources(self): + return self.finder.get_resources(self) + + +class ResourceFinder(object): + """ + Resource finder for file system resources. + """ + + if sys.platform.startswith('java'): + skipped_extensions = ('.pyc', '.pyo', '.class') + else: + skipped_extensions = ('.pyc', '.pyo') + + def __init__(self, module): + self.module = module + self.loader = getattr(module, '__loader__', None) + self.base = os.path.dirname(getattr(module, '__file__', '')) + + def _adjust_path(self, path): + return os.path.realpath(path) + + def _make_path(self, resource_name): + # Issue #50: need to preserve type of path on Python 2.x + # like os.path._get_sep + if isinstance(resource_name, bytes): # should only happen on 2.x + sep = b'/' + else: + sep = '/' + parts = resource_name.split(sep) + parts.insert(0, self.base) + result = os.path.join(*parts) + return self._adjust_path(result) + + def _find(self, path): + return os.path.exists(path) + + def get_cache_info(self, resource): + return None, resource.path + + def find(self, resource_name): + path = self._make_path(resource_name) + if not self._find(path): + result = None + else: + if self._is_directory(path): + result = ResourceContainer(self, resource_name) + else: + result = Resource(self, resource_name) + result.path = path + return result + + def get_stream(self, resource): + return open(resource.path, 'rb') + + def get_bytes(self, resource): + with open(resource.path, 'rb') as f: + return f.read() + + def get_size(self, resource): + return os.path.getsize(resource.path) + + def get_resources(self, resource): + def allowed(f): + return (f != '__pycache__' and not + f.endswith(self.skipped_extensions)) + return set([f for f in os.listdir(resource.path) if allowed(f)]) + + def is_container(self, resource): + return self._is_directory(resource.path) + + _is_directory = staticmethod(os.path.isdir) + + def iterator(self, resource_name): + resource = self.find(resource_name) + if resource is not None: + todo = [resource] + while todo: + resource = todo.pop(0) + yield resource + if resource.is_container: + rname = resource.name + for name in resource.resources: + if not rname: + new_name = name + else: + new_name = '/'.join([rname, name]) + child = self.find(new_name) + if child.is_container: + todo.append(child) + else: + yield child + + +class ZipResourceFinder(ResourceFinder): + """ + Resource finder for resources in .zip files. + """ + def __init__(self, module): + super(ZipResourceFinder, self).__init__(module) + archive = self.loader.archive + self.prefix_len = 1 + len(archive) + # PyPy doesn't have a _files attr on zipimporter, and you can't set one + if hasattr(self.loader, '_files'): + self._files = self.loader._files + else: + self._files = zipimport._zip_directory_cache[archive] + self.index = sorted(self._files) + + def _adjust_path(self, path): + return path + + def _find(self, path): + path = path[self.prefix_len:] + if path in self._files: + result = True + else: + if path and path[-1] != os.sep: + path = path + os.sep + i = bisect.bisect(self.index, path) + try: + result = self.index[i].startswith(path) + except IndexError: + result = False + if not result: + logger.debug('_find failed: %r %r', path, self.loader.prefix) + else: + logger.debug('_find worked: %r %r', path, self.loader.prefix) + return result + + def get_cache_info(self, resource): + prefix = self.loader.archive + path = resource.path[1 + len(prefix):] + return prefix, path + + def get_bytes(self, resource): + return self.loader.get_data(resource.path) + + def get_stream(self, resource): + return io.BytesIO(self.get_bytes(resource)) + + def get_size(self, resource): + path = resource.path[self.prefix_len:] + return self._files[path][3] + + def get_resources(self, resource): + path = resource.path[self.prefix_len:] + if path and path[-1] != os.sep: + path += os.sep + plen = len(path) + result = set() + i = bisect.bisect(self.index, path) + while i < len(self.index): + if not self.index[i].startswith(path): + break + s = self.index[i][plen:] + result.add(s.split(os.sep, 1)[0]) # only immediate children + i += 1 + return result + + def _is_directory(self, path): + path = path[self.prefix_len:] + if path and path[-1] != os.sep: + path += os.sep + i = bisect.bisect(self.index, path) + try: + result = self.index[i].startswith(path) + except IndexError: + result = False + return result + + +_finder_registry = { + type(None): ResourceFinder, + zipimport.zipimporter: ZipResourceFinder +} + +try: + # In Python 3.6, _frozen_importlib -> _frozen_importlib_external + try: + import _frozen_importlib_external as _fi + except ImportError: + import _frozen_importlib as _fi + _finder_registry[_fi.SourceFileLoader] = ResourceFinder + _finder_registry[_fi.FileFinder] = ResourceFinder + # See issue #146 + _finder_registry[_fi.SourcelessFileLoader] = ResourceFinder + del _fi +except (ImportError, AttributeError): + pass + + +def register_finder(loader, finder_maker): + _finder_registry[type(loader)] = finder_maker + + +_finder_cache = {} + + +def finder(package): + """ + Return a resource finder for a package. + :param package: The name of the package. + :return: A :class:`ResourceFinder` instance for the package. + """ + if package in _finder_cache: + result = _finder_cache[package] + else: + if package not in sys.modules: + __import__(package) + module = sys.modules[package] + path = getattr(module, '__path__', None) + if path is None: + raise DistlibException('You cannot get a finder for a module, ' + 'only for a package') + loader = getattr(module, '__loader__', None) + finder_maker = _finder_registry.get(type(loader)) + if finder_maker is None: + raise DistlibException('Unable to locate finder for %r' % package) + result = finder_maker(module) + _finder_cache[package] = result + return result + + +_dummy_module = types.ModuleType(str('__dummy__')) + + +def finder_for_path(path): + """ + Return a resource finder for a path, which should represent a container. + + :param path: The path. + :return: A :class:`ResourceFinder` instance for the path. + """ + result = None + # calls any path hooks, gets importer into cache + pkgutil.get_importer(path) + loader = sys.path_importer_cache.get(path) + finder = _finder_registry.get(type(loader)) + if finder: + module = _dummy_module + module.__file__ = os.path.join(path, '') + module.__loader__ = loader + result = finder(module) + return result diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/scripts.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/scripts.py new file mode 100644 index 000000000..b1fc705b7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/scripts.py @@ -0,0 +1,447 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2023 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from io import BytesIO +import logging +import os +import re +import struct +import sys +import time +from zipfile import ZipInfo + +from .compat import sysconfig, detect_encoding, ZipFile +from .resources import finder +from .util import (FileOperator, get_export_entry, convert_path, get_executable, get_platform, in_venv) + +logger = logging.getLogger(__name__) + +_DEFAULT_MANIFEST = ''' + + + + + + + + + + + + +'''.strip() + +# check if Python is called on the first line with this expression +FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$') +SCRIPT_TEMPLATE = r'''# -*- coding: utf-8 -*- +import re +import sys +from %(module)s import %(import_name)s +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(%(func)s()) +''' + +# Pre-fetch the contents of all executable wrapper stubs. +# This is to address https://github.com/pypa/pip/issues/12666. +# When updating pip, we rename the old pip in place before installing the +# new version. If we try to fetch a wrapper *after* that rename, the finder +# machinery will be confused as the package is no longer available at the +# location where it was imported from. So we load everything into memory in +# advance. + +if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'): + # Issue 31: don't hardcode an absolute package name, but + # determine it relative to the current package + DISTLIB_PACKAGE = __name__.rsplit('.', 1)[0] + + WRAPPERS = { + r.name: r.bytes + for r in finder(DISTLIB_PACKAGE).iterator("") + if r.name.endswith(".exe") + } + + +def enquote_executable(executable): + if ' ' in executable: + # make sure we quote only the executable in case of env + # for example /usr/bin/env "/dir with spaces/bin/jython" + # instead of "/usr/bin/env /dir with spaces/bin/jython" + # otherwise whole + if executable.startswith('/usr/bin/env '): + env, _executable = executable.split(' ', 1) + if ' ' in _executable and not _executable.startswith('"'): + executable = '%s "%s"' % (env, _executable) + else: + if not executable.startswith('"'): + executable = '"%s"' % executable + return executable + + +# Keep the old name around (for now), as there is at least one project using it! +_enquote_executable = enquote_executable + + +class ScriptMaker(object): + """ + A class to copy or create scripts from source scripts or callable + specifications. + """ + script_template = SCRIPT_TEMPLATE + + executable = None # for shebangs + + def __init__(self, source_dir, target_dir, add_launchers=True, dry_run=False, fileop=None): + self.source_dir = source_dir + self.target_dir = target_dir + self.add_launchers = add_launchers + self.force = False + self.clobber = False + # It only makes sense to set mode bits on POSIX. + self.set_mode = (os.name == 'posix') or (os.name == 'java' and os._name == 'posix') + self.variants = set(('', 'X.Y')) + self._fileop = fileop or FileOperator(dry_run) + + self._is_nt = os.name == 'nt' or (os.name == 'java' and os._name == 'nt') + self.version_info = sys.version_info + + def _get_alternate_executable(self, executable, options): + if options.get('gui', False) and self._is_nt: # pragma: no cover + dn, fn = os.path.split(executable) + fn = fn.replace('python', 'pythonw') + executable = os.path.join(dn, fn) + return executable + + if sys.platform.startswith('java'): # pragma: no cover + + def _is_shell(self, executable): + """ + Determine if the specified executable is a script + (contains a #! line) + """ + try: + with open(executable) as fp: + return fp.read(2) == '#!' + except (OSError, IOError): + logger.warning('Failed to open %s', executable) + return False + + def _fix_jython_executable(self, executable): + if self._is_shell(executable): + # Workaround for Jython is not needed on Linux systems. + import java + + if java.lang.System.getProperty('os.name') == 'Linux': + return executable + elif executable.lower().endswith('jython.exe'): + # Use wrapper exe for Jython on Windows + return executable + return '/usr/bin/env %s' % executable + + def _build_shebang(self, executable, post_interp): + """ + Build a shebang line. In the simple case (on Windows, or a shebang line + which is not too long or contains spaces) use a simple formulation for + the shebang. Otherwise, use /bin/sh as the executable, with a contrived + shebang which allows the script to run either under Python or sh, using + suitable quoting. Thanks to Harald Nordgren for his input. + + See also: http://www.in-ulm.de/~mascheck/various/shebang/#length + https://hg.mozilla.org/mozilla-central/file/tip/mach + """ + if os.name != 'posix': + simple_shebang = True + elif getattr(sys, "cross_compiling", False): + # In a cross-compiling environment, the shebang will likely be a + # script; this *must* be invoked with the "safe" version of the + # shebang, or else using os.exec() to run the entry script will + # fail, raising "OSError 8 [Errno 8] Exec format error". + simple_shebang = False + else: + # Add 3 for '#!' prefix and newline suffix. + shebang_length = len(executable) + len(post_interp) + 3 + if sys.platform == 'darwin': + max_shebang_length = 512 + else: + max_shebang_length = 127 + simple_shebang = ((b' ' not in executable) and (shebang_length <= max_shebang_length)) + + if simple_shebang: + result = b'#!' + executable + post_interp + b'\n' + else: + result = b'#!/bin/sh\n' + result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n' + result += b"' '''\n" + return result + + def _get_shebang(self, encoding, post_interp=b'', options=None): + enquote = True + if self.executable: + executable = self.executable + enquote = False # assume this will be taken care of + elif not sysconfig.is_python_build(): + executable = get_executable() + elif in_venv(): # pragma: no cover + executable = os.path.join(sysconfig.get_path('scripts'), 'python%s' % sysconfig.get_config_var('EXE')) + else: # pragma: no cover + if os.name == 'nt': + # for Python builds from source on Windows, no Python executables with + # a version suffix are created, so we use python.exe + executable = os.path.join(sysconfig.get_config_var('BINDIR'), + 'python%s' % (sysconfig.get_config_var('EXE'))) + else: + executable = os.path.join( + sysconfig.get_config_var('BINDIR'), + 'python%s%s' % (sysconfig.get_config_var('VERSION'), sysconfig.get_config_var('EXE'))) + if options: + executable = self._get_alternate_executable(executable, options) + + if sys.platform.startswith('java'): # pragma: no cover + executable = self._fix_jython_executable(executable) + + # Normalise case for Windows - COMMENTED OUT + # executable = os.path.normcase(executable) + # N.B. The normalising operation above has been commented out: See + # issue #124. Although paths in Windows are generally case-insensitive, + # they aren't always. For example, a path containing a ẞ (which is a + # LATIN CAPITAL LETTER SHARP S - U+1E9E) is normcased to ß (which is a + # LATIN SMALL LETTER SHARP S' - U+00DF). The two are not considered by + # Windows as equivalent in path names. + + # If the user didn't specify an executable, it may be necessary to + # cater for executable paths with spaces (not uncommon on Windows) + if enquote: + executable = enquote_executable(executable) + # Issue #51: don't use fsencode, since we later try to + # check that the shebang is decodable using utf-8. + executable = executable.encode('utf-8') + # in case of IronPython, play safe and enable frames support + if (sys.platform == 'cli' and '-X:Frames' not in post_interp and + '-X:FullFrames' not in post_interp): # pragma: no cover + post_interp += b' -X:Frames' + shebang = self._build_shebang(executable, post_interp) + # Python parser starts to read a script using UTF-8 until + # it gets a #coding:xxx cookie. The shebang has to be the + # first line of a file, the #coding:xxx cookie cannot be + # written before. So the shebang has to be decodable from + # UTF-8. + try: + shebang.decode('utf-8') + except UnicodeDecodeError: # pragma: no cover + raise ValueError('The shebang (%r) is not decodable from utf-8' % shebang) + # If the script is encoded to a custom encoding (use a + # #coding:xxx cookie), the shebang has to be decodable from + # the script encoding too. + if encoding != 'utf-8': + try: + shebang.decode(encoding) + except UnicodeDecodeError: # pragma: no cover + raise ValueError('The shebang (%r) is not decodable ' + 'from the script encoding (%r)' % (shebang, encoding)) + return shebang + + def _get_script_text(self, entry): + return self.script_template % dict( + module=entry.prefix, import_name=entry.suffix.split('.')[0], func=entry.suffix) + + manifest = _DEFAULT_MANIFEST + + def get_manifest(self, exename): + base = os.path.basename(exename) + return self.manifest % base + + def _write_script(self, names, shebang, script_bytes, filenames, ext): + use_launcher = self.add_launchers and self._is_nt + if not use_launcher: + script_bytes = shebang + script_bytes + else: # pragma: no cover + if ext == 'py': + launcher = self._get_launcher('t') + else: + launcher = self._get_launcher('w') + stream = BytesIO() + with ZipFile(stream, 'w') as zf: + source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') + if source_date_epoch: + date_time = time.gmtime(int(source_date_epoch))[:6] + zinfo = ZipInfo(filename='__main__.py', date_time=date_time) + zf.writestr(zinfo, script_bytes) + else: + zf.writestr('__main__.py', script_bytes) + zip_data = stream.getvalue() + script_bytes = launcher + shebang + zip_data + for name in names: + outname = os.path.join(self.target_dir, name) + if use_launcher: # pragma: no cover + n, e = os.path.splitext(outname) + if e.startswith('.py'): + outname = n + outname = '%s.exe' % outname + try: + self._fileop.write_binary_file(outname, script_bytes) + except Exception: + # Failed writing an executable - it might be in use. + logger.warning('Failed to write executable - trying to ' + 'use .deleteme logic') + dfname = '%s.deleteme' % outname + if os.path.exists(dfname): + os.remove(dfname) # Not allowed to fail here + os.rename(outname, dfname) # nor here + self._fileop.write_binary_file(outname, script_bytes) + logger.debug('Able to replace executable using ' + '.deleteme logic') + try: + os.remove(dfname) + except Exception: + pass # still in use - ignore error + else: + if self._is_nt and not outname.endswith('.' + ext): # pragma: no cover + outname = '%s.%s' % (outname, ext) + if os.path.exists(outname) and not self.clobber: + logger.warning('Skipping existing file %s', outname) + continue + self._fileop.write_binary_file(outname, script_bytes) + if self.set_mode: + self._fileop.set_executable_mode([outname]) + filenames.append(outname) + + variant_separator = '-' + + def get_script_filenames(self, name): + result = set() + if '' in self.variants: + result.add(name) + if 'X' in self.variants: + result.add('%s%s' % (name, self.version_info[0])) + if 'X.Y' in self.variants: + result.add('%s%s%s.%s' % (name, self.variant_separator, self.version_info[0], self.version_info[1])) + return result + + def _make_script(self, entry, filenames, options=None): + post_interp = b'' + if options: + args = options.get('interpreter_args', []) + if args: + args = ' %s' % ' '.join(args) + post_interp = args.encode('utf-8') + shebang = self._get_shebang('utf-8', post_interp, options=options) + script = self._get_script_text(entry).encode('utf-8') + scriptnames = self.get_script_filenames(entry.name) + if options and options.get('gui', False): + ext = 'pyw' + else: + ext = 'py' + self._write_script(scriptnames, shebang, script, filenames, ext) + + def _copy_script(self, script, filenames): + adjust = False + script = os.path.join(self.source_dir, convert_path(script)) + outname = os.path.join(self.target_dir, os.path.basename(script)) + if not self.force and not self._fileop.newer(script, outname): + logger.debug('not copying %s (up-to-date)', script) + return + + # Always open the file, but ignore failures in dry-run mode -- + # that way, we'll get accurate feedback if we can read the + # script. + try: + f = open(script, 'rb') + except IOError: # pragma: no cover + if not self.dry_run: + raise + f = None + else: + first_line = f.readline() + if not first_line: # pragma: no cover + logger.warning('%s is an empty file (skipping)', script) + return + + match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n')) + if match: + adjust = True + post_interp = match.group(1) or b'' + + if not adjust: + if f: + f.close() + self._fileop.copy_file(script, outname) + if self.set_mode: + self._fileop.set_executable_mode([outname]) + filenames.append(outname) + else: + logger.info('copying and adjusting %s -> %s', script, self.target_dir) + if not self._fileop.dry_run: + encoding, lines = detect_encoding(f.readline) + f.seek(0) + shebang = self._get_shebang(encoding, post_interp) + if b'pythonw' in first_line: # pragma: no cover + ext = 'pyw' + else: + ext = 'py' + n = os.path.basename(outname) + self._write_script([n], shebang, f.read(), filenames, ext) + if f: + f.close() + + @property + def dry_run(self): + return self._fileop.dry_run + + @dry_run.setter + def dry_run(self, value): + self._fileop.dry_run = value + + if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'): # pragma: no cover + # Executable launcher support. + # Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/ + + def _get_launcher(self, kind): + if struct.calcsize('P') == 8: # 64-bit + bits = '64' + else: + bits = '32' + platform_suffix = '-arm' if get_platform() == 'win-arm64' else '' + name = '%s%s%s.exe' % (kind, bits, platform_suffix) + if name not in WRAPPERS: + msg = ('Unable to find resource %s in package %s' % + (name, DISTLIB_PACKAGE)) + raise ValueError(msg) + return WRAPPERS[name] + + # Public API follows + + def make(self, specification, options=None): + """ + Make a script. + + :param specification: The specification, which is either a valid export + entry specification (to make a script from a + callable) or a filename (to make a script by + copying from a source location). + :param options: A dictionary of options controlling script generation. + :return: A list of all absolute pathnames written to. + """ + filenames = [] + entry = get_export_entry(specification) + if entry is None: + self._copy_script(specification, filenames) + else: + self._make_script(entry, filenames, options=options) + return filenames + + def make_multiple(self, specifications, options=None): + """ + Take a list of specifications and make scripts from them, + :param specifications: A list of specifications. + :return: A list of all absolute pathnames written to, + """ + filenames = [] + for specification in specifications: + filenames.extend(self.make(specification, options)) + return filenames diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/t32.exe b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/t32.exe new file mode 100644 index 0000000000000000000000000000000000000000..52154f0be32cc2bdbf98af131d477900667d0abd GIT binary patch literal 97792 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

        XMwe#*Lv$g8jIpIBEV zfyTkQYBaa5a-Z%R^)4A#C3|SisRuwhmT}d&>x>Pp@EQY1#x9+ zuQ!wyOF<(>Q5t`C*owXSRoB!ao|{V@l1~o}g!5(*WbDbT72V zFHKmpCS@n~^6OlA&gqa3bPa{aEGgt8< z-i0Ddf}@W2wYjj_kkBU%U0`+=z!0he=-Epg!QfU_9~m6A+TI5drENQmXN(gI&dUjk zT@Je+le);tYK9qfpzys^k3QC1-5{wJU&pTeM&W;fJraP>Ns{HPqlFiyu4 z*3m|c0{%z_|9Q#i1N4C_>+H4w+DXh^zM_=JN;8ZWN89^m4CfI+@O{wN?^a^Dgyuy$0 z_X_^T)O&>~_zUCjVWcTj`$>y2H4c*f1MC-1ky3{vUN@VEjnmXtpk4*1f!L{mMulT* z0rzll%u5?Arfy3Idmw+8pIwGr+!@>WZ#!+n55+nf3cgMqsQO8-Js%lTdTlptB|@)# z3GY?9P9f3DY7vnjlTx5Iqqn_4T`UZ=Fy^1+f9nw5Q**7{o{ znObXviv6dm+x(>JHVdj7ewh!tn;#Y|m+wW*K4%)VH{4n#_01i*3i0C*AL9Dk;qTJl z&UIx0$2GZ@{)Ul)tI$}Ctncu%Z}i+oc6(~{xBQvlPi|GojZH0g7u+3%=`w+(fr8jo zP{mDG7F>TT#;2vfMRyPxlG_Qczmer>Qh)m!M1hh%&w%uXxO)uL0ji6e9;@80P;GgV zVjk+ov)1#j*z`EyBWWS~8Feoi6oSR$DQ;blXlPhTXBPX#T@5?4pa+`HEHqx$4_mXu zT}{P@5g&23F~@S#gqnV5^g1vpC6OuqcsR;|dy5GV(*1{TxVP9zEwLP19OGdVGlvn5 zcSA1`cXPd~5^JY4v#3KVENYW-`9ew_DYIG#JiZ%2(_q{Ye>xSm#s33g(^9yB*2@Pd z2R9>+M-i-5pmh~#*eP-(2Zsg(8;xphst_n$6dAt(F<^uUaz_-F(#4ZX3HXUqND|_g zB+H$8PPvpw<>v5m>C1?x@e(OKv_$%je?~$qIu6$si^;WxchI%Pt4OkppAm}h#RI?G zxJn*jKVwBheu)1kqD_RjjirET#)@B`L;tQ%I! zS#XB}#D>#f#Gq~hEm>X;Op?-;^_$lBV$&HY5IEi#HpCCZxvne>@%|C$kLk)H6Csm* zn64}?E`V}EH#`PiS#Z;AxUx7#nhCkG_z3MM)$Ac^H_H^8PJy2inoM67MCe0(SrGP< zg#S3ggT5@D!54g4(CdHlWl=i`^(1{+&`kcPzAOgk8hY~2eOV;w!}4W8pWorj;)aQ& zw;hHK#`U(pf>gw&*YG^om&IpTo=I=Jlk~R2Z~C%;1c}n6TdlbOOzMFYW56h~cP5yc z+Ip_3B>-${YQ!tC-JEh{VejNjoX9#(-b*Yh?2Ju5^bS9OAm*TF*h)AM;g0JJ@)JD_K{@{!jzx0NedOmTu0;p2ct5Yb=B#(0(SK$14&~XL#6h!LPU(lU%9ar$;faw=RoUH47gfcZN8mR^o`=(~K0gyu-SI~ky#BoK8mZ5kMK$hzUM)K7C+`%7E#(aeOVBe z&-G<-@Q>dp9lk6$WBV>&7JQ1BjQE?rEaLeXweRv}!JEfLICc~2`rrGqF#ARPjP0^C zEcy`6@nyj~1YZ^eJczQBbg_lILQA^;Q1%ix;wlye*mD{np5SpI=xK=3_^g05f}MTN z9O;K?GPT3;yMfJ8E?wf(g}cPa6r(n-b+z&`aa{2N0^C@gI}=H1T``rkF8Fr*ljarv zqhf>=cIH7Gs^GUeahZAExxWV5k#;TpqURy{<;;W9OX6<0MQ_9JqFq@X zAm^-M8+ewVoh5yq41eSa?DW&%g7`rkQ+WWIEnELi9hX!FC=(F0M<%8Hh$x0%Q$H>>TtP5kdKcq&zhF1<8z7owv-Nr0i$8uXZ5K|C35_@s*857~ zTmhH7``NB$6s7C9n5xa9-w_DnvOI{1&W*PLfl#!{B-?@jD+LNeF{vW?JLR8NM;r@+ z%#D`dmJAHd=@$w>wfv`pZa2%)XTek#`N(N-D7M}vO^M)#**|!bH{Oo6&I<(ntm!dA z6IpUxgViiHb0fe7`6YulhmA)2l&RD*0rvZy>GXpL#6I!sAn=3P4kKCA%X)uIP!yy) zIXc`un7@DZs|UGx4X&*_X>Xzzr+X*p;COj^ZE23gf26w9DJO9Epce1m>Y|R=(lq!| zI)yuS=GFJ%$m^iIow!fd4$qqGk;z2DF8$Ng-7>m+rAU)-|7sj=gR$Gt82F|<4R(N5 zsu|?-vci$PY6UXTgvQz2>FUMGdbeFJ#BW=c6{P>$SShdUJbLAnO`%t2nc#iICa1Ia zrci(BCx65LhrM@!tGY=4$A_z^=)u%P(>y9FiY10OMDie@K%yuLninJife;YR@zScG z2TD9*TJ3JvEz`;_yK7gwDu{V0ORcudEUm0Qgqv8VnB@H4@A;e$91zWRzx)0Df3N?3 z;5l=jd7hbP?laHyxZi?9xfIZC+_z8Ce%=LSapM&Ww8$C9#964jj>=@b*^FZozU8m2 zM|MGi$*Gl^FvsXyneddLJHyfVe6=4be_|Kf$`%;w^vZ-u4$gtBnc?}(uyB|7HJ zEt@zOI~(@%KBm%F;G7*W!?9wPJuMXP!BqOLJkR)-{S#u5SEPln6QaV_de68tJ1lEf zgu_iYucSFQ9MxAhv!2$>3BT7cm(n~jyxcIaqWR`lNXS4yah=R&O`XiEQ7iTF?gP3VAD`EIgC7Kw96`V@RwCNz!5wpqLe4 zIxB!%qIMLg-QbtE`o<)os!rrsM^%9FnD~19MA;pCr9wgLQcA`qHUf)8EwDcm)J~Vs z7qv~9#=`zprqDCcO|3TG00)_AXW$BBC1HP@S7sY)29^@v6`fP#*)6_=v0T_5ke##=`F zu67%T1dv?GXV#n4duLA~@^=>94AK@%nZ<{Ax zOl?PrxF7>WfQCcW=mf5bRf)UL8ViE<*-n!dl%RE*!qEpP+%megbv&*(hT}Y80zCK8 zMn>sGwp-&3@^s=P`XqzWns!=erx%ZLvK5@B`HrhjLX(H!t#-Q$<`@I`Rkw#FD48=8 zdFEj^3+dJ%Gv8Oe9v0ezk~C1;?|&2L=si(B+xCR?$7kCZFhDRg2LmyJBixi;hugv9qlDBgw`injj5?vco8+OjtYWpO_T;o^z zlg-z$yIqIzLf~{z+uAZEa{S!KWy6??%ac(jhk|j=M*9`xKoBm_uAY6dY0rA zh1;~&)@8Fl+8r(ZFxig&i1mH-Y*`+CG(r15Qn9tE^b2oXHx|WMM2PmYU2`tlPgq)k z1w*5cT0M*7Wih0h*^he!RlSD56+A0YY>kYu2F6&;TKwafu=qoavK9=Te)j6laM!82EdET*b-)b`45Jl@_M|R#+ybFM)6U=kezxBXESZe}3O4 zS#v$^LwF#vn$FN*^+VM7LPJUr$86k@^m`o<ZSH_>= zGY2?z>G`x0YVFZ_dF>XnwYBcNMdM&;7;u`QnG|LizEe$h(!gP&Qr^0ZGY9?gw-C=4tUo*rr(8(mkoCzK^xe)*LdPrI4iHI;=XSXL;X4 zyu-*Vj1_o>KJ1(c&(-b08RAUX>#h8Dc*nxP4D@ao3@_)?wdhQ~2wmPCk6}c2>RW_@ z=4~2_^&Q+0#qxxjk|*?69rc8@US!%`{)P|$Tm#`q3`P#b zeQ<4r&n&xTVCdN{Bg`FA03hIRr+dw}urzIJ6I9uF##xQ)5XSjD807J)cp^r7RIVJU zUIjZ9@`3YRuo!>SeLEdH8w2xvwDbO`BWAP*mWd)dB_^4+E%XlgAQ>k@{V9nHThO?T z8+N{+HD2ZL_!NRd+KjbV1VWMkq~NI=d`-?k&`09eiAP<7!SPE z;ei(nsr1)e@X`x1ot`q$C<$$#${SqBvQEYL`v%(K>>phclnX_MjdEB5Dz%>H&{N=c zO+AVihb9`|gi%kzRH^sELOxi@H`6K<<$Ny8>7CL$ng<{qz#e!`V~d^{xW$6r&qq6X zvkVaM-((Bc?uNuQt`D-VgC}Bm_=zBm_*+I)NzrE}u)D4?zkD z85N8~obTc2hq!m4tqAbc^9H^G`4$RaL6O!`_GJNsq}4mj8ZcI1})IxN0OsG9%WsjE9V`(OztD(tNPvS@XrEtrH&ObMv$xw-~kKq&}io4}I`=GZjmAYO z4Ox+-J)#GeRz$SHC8E-15!o&i5msLwRS`{fi73V;qS4JF8gxlScy*}QI$O0(XR^Im zPZG50LsWMP&ToaaQWWUy5E;6>c119*3og0Fftz{ZXK;}l!3y7&*_T^li zU(I2R{k+?XzM=7c%giAkgw96)v~vmycydy9YnczJ&0IDvt$=S-LfmOoK?=+x=iu#V zM=`@AF;hu~YUS$esrkQF-wM4pA0rk09-1pMT>c7!Gw+qNCq$QxE4HV3<7*vddw~=-8zTCOVhkT$SN@%= zB#hBVNjc){mW@3`5}V7m6}CVW3SWit4f){QX`Io-y&0&rj;eEMu;1(0P1WO2&3Nc% z!G_m(;O9{Fq0bI|f9P=AeYP{_esa{p7jB5a#qZp=9)}9rhr!=sp8``D{4Q!eF$fOV zO$~rVH0XSw^F4P2!vBv5$2)lZ>QkmOXQyAoMvmS9zl}8w$25Pn$P@PAik=ujINpgJ z=??)zkgImqzE3)K235v{?Og6=DU0^U96oe*fUPaKgYV0EmPH3RhOtCh0D)NKxA~(K z$WESbqs4+aG+Tf!jjI;Z5U%7-f%S?=UCjA83~CETlt+#62^W}rE$*n@S_ux^1@7#= zN9^B^x(|66!@!j@VAXn$`22IQSA`FjW;n+_6FU2h^*NmJ!<5k#a+)OhdlIMQ|u z<0bjTMBRfb@sHIug)S&hP0LF$`WWDdHglj(-s3xqcW=@W@9r8s&huU*u1OVE~pk(wf}zYz-t`WY%lB%&Y*FS4Mz8tvWW*7xz8~V9RTFQYv)l8+;KUrR9~%Hseam*O6{b5s#JgN5Y^$@JIZL* zb}BVM6{B1fsJ)=fgS2Oq8mv98)S=pgO6{)Qqtubw8l{fbRzbDzvdBdiZLzY7((;rV zqs>?91T96W30ksJr)rawI$ayD)LGgXrOwqxC^bzRtkg`czfyCwo=VNvx+vAEnUuOj zYo*j>nnb1&g;$DEleE(?$yZ9K{Z?sLQ~R0HuBG;%(qan+XK0o7K5AcA+WV>fyV5>P z?PjHYjM@j3b~Cl>ly(cXcBOrj+9gU`LG1#i-9_zfO3OA~OIBJ`R>%;5R-w$LeeGuD zz?CO0L}?FG8>qBLsqLY($Em$WX-`nwN@+D}>u)E~C#gNHv>#FXt5roJ zw9>{<`z^Ez-yAfK9>y0f-z1mr4IK0EW zl#FUz5MKc=5M=8a<~15aH!Ey%bQ_G#El!(Tb(`QYuS};+jIvoYG;COovPsg0)23-c zrjqYbI@nYV^NMnY^V4JU3-gL`+FZm!4btxv<~6}-b5ghQ5A#ZJ+8ohsx`%m9b=vIJ zZOma_)15Z2=r#diUbCDwn{}I!VP5%8n|pMd(P3Uzr%kbLV+r$G;K@YmKqM@j3gn z-8CUtF2vr7=W6$wuN*%H)vobb?dxqR*~cgBe?pKB*ul~!GnDf}^lr}3U9eU6p*=D5 z@;SGYV{+SQhx;fu+l@e~->6hKTi-S(tUW3WR zqsprF*Hs)kZm)2wI=2(NC#?Q!usGp{Xubo12z2z^PIuaG&Tw$m0uJxPb~odH8}Yw| z#f+NOMg}7Pk~yctxEF%o9hHtl9=pRXF7gShGp%|I(XTvTjJOKA8#lE(CMVgyGugj2 zgU^-xP-T;B;l7C;)H&mnA{w!-5LtH-;o8Rw2@SC7XV)*kjfD)Rjek)uo- z{3>5=xVK{8<4zQwZ1A;S_haAfMg(!89ImSyToK22Nb@7We!wokn}C-9&jB_A98v2| zlttZr0;&d8gL)F`NvO3@YoVTjdIoA8)H+6Bmw8A%RcfrZq_xXCn`^Jnav9mY6x zxvN##=FEe{bv4dpIOP)_R#%kT7Tsd)?5M(Z6>MVm2H+k5Ubd{XMQSs!bx4`UhtZsN z;#8U)@OGlOkv}!lg^YNtZnQU+FHHr7d&m(IVR05i~p!>{9M@EZgxycD~mt4 zXM7sRl(ZS-XlFromc<{%JLS-LC}U{+(dAto@kf!Nj^tToQ5}3_^wFBnwQC>6hN`!7 z!BpdlIOFIk7pEo4X~$+Y>K7J@E`<=erL& z*E78Gj2%(0Z2#$y57`fk1bBnMH?x*}dVCe?R2}M+s!Uiu;lj4-Llmt^C8Vs>!()~9 z34H1q$fcHT;7IOqKtLx>DLNPS?I!~o=c%YoiP+(Q^pT`w$THMMJi?St3VV10avSqO z&9*%AC2$3VL-VAAdXtBvXGgda>%Rp~>d2z0bqsvSVbnV6D32TmwgzF! z8>r4tb}f%TIysw5RFkvaARiMkd!LQ2bF*P*-cJa#s0rCfT*9I?>i|Rq_udks%TGen zbtAE&3mAef*oZ!<93k}|F3Jw;5cr4C$K%S8<=n79nu>#8U`haK19fMIYYF|J?C$hJBR4yVpD2C)v(`s_%jhao?xRvkeAG-2TD94noDz z7VSg;Z4qO14(*=NcW$Qns0)M1ILYaUgMogb(|m2$IQL~-$C6sSE(JTH5ko{+()7(v$S8}7yEwf%4c16fd#)^3)@TZ_0L1ILhOpmPPjo{+?1v`YwK>qAVnfeZI`kXfW*Dh{gYg@RqY7M^{2NZ! zV6U~OW&&P#p6BP~4SCPG5&K66yy)jjE+&?DFQphC+*4g};2uwij*tqG zn(J^#Rri#j-QePfk`6gH&GssfKZC}xyqpnD#!W2di8HjmgsLA$4o3jUXH(QB;Bed- z+}|Tn4uP!Gh2}HI+rXPcCG99Dw|G>Rm6CTtUuHI$)}FH`Cg!FvwEIFIDW-cEmIq8{DJaZM{s}LPGZ0FCwQaA<@UKkZ~`2 zne>&FGi9g8DN@;KZ}ddP$p}+9X)29nIx13kC!W>;kPDYkrrfo4O+0s3c#u9F8_dB& zReGjX=YIv0`oP`MeoaRm%hgj&LmlnQqD&*JPl204_K`J5k~KHNSo{`w2EsjvDDEP$ z8&#Z!y&=<`zru&(-H@|EiV@07W8et_9t-=e=?FoLkFd~+e}U{p9gp3jon;)}1|7HZ zqU?w%@;@B4dXON<4lFMXgm09gilpUYjAU}kpPR}H+_zpEM!TvY)gVA_oM{!8@2V-k zMn@e2Jz1bGNXNj<{sb!oaMv*>%M$lFL_X5At)s^m9`Eb;+kt;Kx0hL}+3jX#l=(Xp zSsuIHUS<^fZ$_D)$L^JH(c&U0d-eBHmc>C!Gs;qF=StalQg(~-sn>N?$5{?GT9(u3 zB+P}Z^G>qD9Dh{IzU>+}#q}QVleG3aQI{oW3mUY7ijcEONtx5I`CeXLTZ0N&ULJ#J zgDRDrp#(=|LXuPTBR4l3hr_-wmHgRRi&UwCjNJ+4CBqoFrJ}X^db~`JVP6!zTZ7ep z_Dni!!)BI4Rd2nB^?Iucx-GJ!W(;s)NJ<`+FEd-@(2+*P$COFssr?CZFJfe!Xd1s= z^&!HRV}DEevEBe_iJ__rLTc=`#q2#eh}CvLTNYe1##ue9V4P7i#At2Um|HO4Q*2l~ zvYXl>3D6d+@ZA}{2wJ;Y9iI1fC`5u(Q^Eb7l#uu4XnPYmFoV!VzfKrg-wE$(=64Y? zawGMyTA+%K=&&E-0(%qT6y2TP+#0-TNw=Z$kRIaEkXBEollSO)nsblctW^tcIva%f_a(T%(0IIBWN$ zf`%iGDjbV2Wc&W4pTcsq!m$BwPaLVSzMrU7rn}JK4Jq|Q616DWVUju$e2H08TPJIQ z7`!@p0>}R`5$vjc&Be<26JJVT>iHj|WMr6F5;OxwTY9HuzsZ|ux zVaQr!G}ZsavP4Uw5&r@EMrnN2yXmcE(TLB&$qeGeFh)ZZ{PxfAslfVI%-qNG>$;(5>0o%j%Kd~;fPx>WLLrwDS-(g z4B?202&c;?`;Q7oynxX%CYDzej<^qgl=KY_Ho^ExIO2=ImBJBu^}#80?@}p@R}hZK z7xXYy!VDI|5!s@V_TLtcxRl8?5so-o7mhgWs)QpBB`JE*R|i7hL^|RRpMn9ZJJ3sA zzUgj0djlCSs{9}wQF2&b2kD3}0#QoYSVAcq1C^AGui@sDaHr>m-p2igWW;o&#~Di; zP8RE-FDn_*$e^p2jQBb7)8nFJe4Qh?zPdGta2Ap-PZ2lS75iT*An|_K|Bs1AyzoaC z(THs6QQQrp5t-7}i$-K8iMb(b$)%zZ8P)F-ji{o#l4wN3gBgoFSl>H1V@7e965UaY zv;;D@K{O&85?vx~Svfmyr&vTQ8VHI-oQW<-iS5{qKzbqUDWyLw0UJ!?Di(1V{I5|S zE-x0b1N@w;NOB^i;L2FZx4X`p1p zSoCPBWv66B+EOwiRY(`%uP(!}Q8MB}hnDjXII6kR~!#-kpd_88=ylP5`i#%|N`z!k733aMwnvGinZ8?tb2{ zIM0Y1zbV{*?Ub3o+22bD!3&0bhxIsic6jiH81nuc$psL=eb_!m^X~4$&G}UUgc|<2T!3tfma76gnQ}N`T+w7_g)2WKdiG0Y2 z&jA-f;_(@6}x+Vr;+6oL=QP zeC{)Qm0u8^h@0a05a(6k<`xlW`=zpmVGxt&Ew;K_tEXY@B^wW?j0VSS;wuG){0Iu1 z;m$_LXAto3hbPNAgk4zF4lIE4!0u)K*yH$vZt$@C$d!mi7A@+ig<YfNcT?$l5QrNBonFHg1CbF{xo9{J$(s;;+hjZr`}|*6w7yy`X6yQ~ zbL|^w9Xy?mj=@Fn3qth^yRg`-y$$#5-gC-=wd;^7{gv{X`dpk~Ukb!lV>g&+d+S$T z)hYMUus8e!Lth^6J2^2q2`k6(ohB!i&GuC%=D9M4fSvHNkf~H{AmJ_Ecdc)gxz)U; zS1fzo9j4L*M1bw#5}b176T5d}>r0Z9Zy!;lRL_K2Rcjtscj4lD6&H7MZY&|U@qWb$ zNEU%O)rao|7h+e}5slZ))Qe>P+G=Hoy}xc?%xkfU>`$*eX-mPX7T{WQt>gWmz1a43 zpI*}z8+!Xp4^)Ae_BbV@EtO+Ye2`)E6H%L7>rcJ*_3M z$Bx}WUKF5GS?7N4Fym=7*VO856|Tx-ukrvk7AN41=hisKWx;c3wsT3AY|jMEYb}Vc zg?1&PC^-iwW9ZOy<-sx5IM;rfs%=V!s-1Pw>2uiXWVa@=Jo~ zCEFQ1iQ=z)Jg;G0Bxn*Sz)}?JOd_6+!(M&Z7uNLb@I);l0_kGo3bI9~g&ng_#%5^f zVt4MDMAvO3ML4#HUt1e(IErVJve|80vXSC4@aBg#1Of*JYoELZH;S3jP9wAM&|I>X zsx;P#+QbGtfP#8fEYwzq%8xMx@VqYDQ`@x$lx|{-i0nvNaHz_DTSquOt(^2!tvkws z9ZFITkbg0VcL(ba;VNRSHVzyDBJc{r+FwCpXo#ugXQ<^v-@v)$Yd|3+$?)X7y2Xk` zNj$)Ymdh>VEGbx=?LHF+vFo+_wgC-~sswE*5_RUut#F{*5~o|T)^UcD8%(9&APv;k zkbUKzxKpmb5Izn0|dX6t^5A;L5Z9l12V~oi@5&56(iE|gs4J{8iDyGEr-zb>sAm|XS z4LPyg{oJwg@$R^tyS5ZDhQkGM@I~!}Y)@w#L3$iqI7(%zkb>d+YFUS}g`UiwA*!&e zKZOhAP9!C1_f9}0z_)UYfiE^CgBXyvR=|I#ENsNC13kE#f>VTN)=cYEGS#hz?Rxb{ z44wdVxvkO%FI8dp9{lZr9k!MZtmEJYS^^$s!5$(zGGlM~q(3h0g?xscHWZu>9vQ=3 z3+?-Jn3qIr>Rxl%G`zhetuy!@9;@*CaC7YK^A6(RYkCnpk#UQNv0^q<``1x43 zj>gvYvNlDYVLugkI;wE5Z8w?`aFLJp>zAs~h}!T6nEDd$q7PBs4>)+sqK;k;0SCUP zSP#+-6moz<$Y<(#EUhEbwU6|&wbjn15Se!HZ+~B+($~BdcA;qDG9+7gJBUVUs2fBd zo|V#g4A4}9{sm>|qqRbdz{TS$^?brK>D7-?pcq{uj65Gsm6a5O@V)zrAEypz5;-9{hPc)Y(R0`Ji! zn_gM!iPhzm-yuiEp}Lh9gaxZe2~c5S9SapxQENY{xQl;1Rot`;FOKyrcC;yrnak!-*Kv{YT7lr6!$7FrA!uED<#Qeb`>+Duc#1^Xn~}i z42Rl@@`wDlISozf;aGOz)8V0A8qTKhqMFmQ^G(X*P-!ZyBuR(tUlvt<-^!X^<6Zs` zEHE<6^9D@B$TBalv6tcp@#m6u_`Pi_K!7R|-aihMZChh0$~?1~n{ zuBa%#{FP$FS-yk6akfo3coU0zL`bq@`_T~xDa-TSvfY!kKZknmoSOYYa^#F|#t z{y6>K2PbK9^n28I1>U2+UhMAv?WjG_RYA9V)N#C~h&RR#h3p%3+}aw$8Yv6h&wc8s zoXL@vI>6zPknNEPnk|`w+VHp%1u$$P{Z&RTSRqp4B94a2&Xl>*cs~JGBYk+)vh7~{ z`r2B+<8nq_oegNC9Y6<=oc`*^!RWloMhiEh~xhspXar1;u=5z;3mKXz#PCL zz&gM)fHwgj05pJS7dO!jFbFUjkN}tq$OjYy)&ZUbYzK_P&7z@z06-gnKQ0{w0D=KA zfVqGIz&gNlfC|7tz;VDCfKONW1&jqu2c!WC04o500{ji|Dqt7jW58E{TEInsPd7Kw z9WV$m7BCes7f=9L1Goq9IN)!93cwM-89D zI=~dr#;+DM9|6<@ntx@zE#la8+&$fH5sw_Ph?M&+4PGj~wTMxD8qEz3PK|rJA8w=8 z9S{#VZbBZRAFe|MNEjR;=Hagp_S3{vF;3hdM!M*Sh~Z*_h=vW_CyH1RDJF?1WfLP} z#3-=#4#26 zd6cVxd`Uk=jr?oG{1+elA< zQ~1&rHoyd?G%6a9Y}6Xz(SV_#*&O z>GRST+?K8id}7|B2K}UY);wA#rsrD?%Lv_du5Kh3qfIk600`B4#)T$}qo@ms# z5OI^Z5%p{W{#1H7h2sj+)5W-)!o+lIQfdyuK|kI_ACZ%jH&1!SkDI%PrK z`nGFt^6PL-$4;HE?c(3H+jZT0T;J2&t9PG(zWw?K4j33T=!P2y2M@U^WazNrBSwaf z3L71M^B7CS*vP2papPlR<8GM{KXFpR!=~fJ@RrSsH2> z%ZD{87o3#^AL(czSkDWTKeHmGoxtG=d{dDRlfYGpP5kJtiD;E3it#PDfR_*|9yNX!Bijyz?6aba$zRT z#ECZK0Olttx-_S96^`T_z1=5$EROT$uN| zi=M;GE8=(Ke}pfZ(-GW2hk=ukZ!J-f)-1no!kEISUo7oZRV#FQSi4$^!>o9Yd~4v+ zSbjH&5e@MfcwAliEk;@`r+8@@x~4b$Ur3KNiFNc}NpDy~dimfmm*LWKFhxlC@ z&jRE!TRBpmsq#NnwOx7}aQkn@|F7^%)08--V;<|dYtY|V51IRH6|87a*^XwyFM9!w zf{nb@TW02rQAT<^!7k-<_58qCbsi*Nu)k){?TTNpOZi-Vyc|(;Y;MF@*nq83?!g!h zF&*~L87P-Ll{aiP*w^L5Og&q1_D<~S^qgVup||dO7;;x*UXUL+Cz^W^ce%R z)?Y113-a`!a!JPzDrB0At@Rdh4egJQZnb!(kP^nzM()&f38JWx5fEP;9l)J&-LQ0GFGP%Tj7Ro!6!$U2ju zXikD1>DF6?ND+iTGinSun;fi}m81sDPi~TQKYCSaL zx?SbwV*K6=-z=M^DMTZjK6dMcnA>1J=Nj86MROoRvu5RhXZZ!@+H!KB#le1WEEFTZ zJ-mgtyPs!tbhH=0VZn%p9?c^YrnmADBZ7mI6L5ynY-W)OpShWH@d>dce+fPx_z@4IY{+od)!D48q$m559Kn)+WU8voqH)&04~uu3%kxuf1* z&7Tq8{9hipr2N@q{4aBW+scMNx2yb&5~RAyt?|#@?dtxF@RxG*@3r}@8o-n~;m=5s zrbfLV#4!GzuetxLqqfnPpJ>$UIybbgILN(nmA$0&&bwBZt#Pa^Uw8NV4fou8-yi?< z=ldUc@S%qv`OBk^ZG3#w<|m$fYRl8lJp0^V|MvH-&%f~EOE15&?bX*_f8))!w(qE@ z+*!5j?cIC!?tACm{qMbh;NYPTKK$q(AAfTA(<7f9{rrn#$G`mQ>l5F6`<+((ea*># z{&1@H^p8KC`T6X*y7RwWsQ(p0qW|56iOVlMT-CzF|84sJZ>Rrn=l^dl3|-z`Ee!qN zrr!(0Eu8CDUk~VUlwN&*a?zJVZ;Tt(x#;;Kk72*wMZdvCe~*j)UKc%I=P|;*?4tKT z>Kt<-K6NP`3wq8qv*fJYw7kWM)>La2)(2rftuVb{(rwx4^Q^JC8F>W@ltVJY!EHin zp*3QjHEU6NLSDWtU)kwqJsJ^@Rk+mo>0|SjBx4;vQLQvKUt>g6!n?Tm&f=BD7>Ie? zS$yZJ;!a*(7>SjZmLlVVgNqZ2S7THa*nea~axunP7>p$(tVS?%VDai=gc2BzWoBmj z__SJ*pT9UizsWd^I|ma1+-C@Y-*IXbua8GN#X z)Z22nNIWf<8oFg5ug$5s=G1(6N`rr{8Y44rbn#P=o|fs%SkUVH>a<$tmda%QB+x0^Sr#YbaP&Y zIp0`Ez7gpy0q`3P{wM?%gHhH@c`)obL**B}Bp($;FL}Lm5!ZqRNZE`MW;P-Z``-wD z=ucf?X8LWZu*}KIO$QI<5Zz1TSe%(ObOw9*R}WaWZyfK6JS4Gen2LR+3S-Ha;l!oHV7dh{jr zCIvHIt{dGfW+9=eF461pF3z%Ml3xm~sRb(4-;-W1ks5&u;r?)bqDTe`B{K z^UR!x95svmQQ@p4FzeD;=(E@{70xQmT9BWUo?^7ev(mB(t+T8lH{F<@rW)jf5!mOB zL|?L{W#lg6^afo%a;k=T{`D3Wz%Vy=PwjbS_rL*HcAq@(%I;}{uk4;Tq^bMfX7~>c z(cJ~U{F^M|a&(Lt-ZY-(biOd+%I;@IHg!MP43DX!n!4|Iaep?)6*_TcL!?X)7u-d~xPy!fC@tQ}w^+l>T$)l)AdPZuQ^ghWSXOKGvFmo-0}O@JvkS zfI1=7nwcyX!sI%G!AM!3)rc)3k|XAHV*-Shl73^Xy)uLno}Ji?qZH4S{vMdA`QfI>eBplaRXf= z^KuLGa?&HM1vz>wIK`suZ3P7wm_%h2pr^)I0#r>AkvVyV>CVJ%iJmesdII|Dv>Y7G za0sF~1;dU-DkBmxpiUOiZi&_c{Kq2nDCGl1jR7$8C^2S2azsL`3Vy&nCOtJ@9a$0Y zD!qO@3YQgaf43a7Us)MG+=>Z0^Z?6dUzC_p)rLZtGc=d|2R#S&@J zOXlGy5W*t^5GhKDbY9-sBepOq{WjbD`RN4-1?h#z4Dmhi;fOOKFN=7IcX;~9#a!Vz z)j4o&IS~=LX^Ht+xsiD`q<3et+7Tf>RJjAZU3d$7 zM4nTuIn#16NKF&(DIa>>z=;_ZLjrw^Q%Dt9W4w5KdRlBQCY&64;z+uyJqR-FGZ{7_ zGC_zD%-2cL@zl3LzTiYrR+_2@SS1i|drr+Mbe<>LLp?m}XO8yL1Y~v7Zkkjf`!hbh z*67@Xk}70rxbW`VF8MY!lk@4=G>opJWah0)ekG+AWN}K9EE08>v`HC^9%~~;iKDdHN?^3F*8$WWAp-Q$ceY|6 z<-gzmhjU==c(6V`s`?It6Inl}>Y{M39iz6(esm}N_xo=STx|}JpC;lN>F2xdh+-I; zuLT&q#5D}+JpujzFF-p0&Ksz8cbp_p>+U{Im22_704^faje7-#8{=hIswM#AC7aWZ z=R&Ha?-ND<3`1BX7d8qFAY0;JGIbs$mN3xIH zbgBOj4}Wp|ybwcH=$LW6EfT(Bezu4xfCVrbFcL5n5DW+em;wF(Uw{DAox%Gp01a>g za2#+Ha2W6r;2>Z>U@u@7paSqF;3dEoz+-^>0rbBfuo_SdumW-ba{*HU34jRz3t$ky z3}}tc(#=VkKg@mrAAkVV{bUif01a>$uov(uU^8Gnpcs$?NCV6QOa)8;L;*$vh5`lw z0s!3sodCXo`X3P|;3(iAfc|$ueHE|;a37!qkPnyzm{4L7Po zPoPd;1sGv&goY3dUJnfWZ76bnL*aiA{9g`OcgR1C+m(+vc6I%X{f`E_Tf8`g{_pp1 z<^Z}3e4c90I2hBB_Aud0d$AwX^-wwY+5#1d`gH%TR9RKPp5=5HD!OBC>lD-72Uo&5 zUo%5xA&rE}IavZ!DC*PWaZ@KkGF!0EfUn{f?8zhdw_wjmx4JNp5kKuKE@OZ2GWN$W zV_$n2d*Q;YuIc(+#@>7x``{Mr*{54zpKu%Q;X^&4Cm=?qyNN*HW0>b=xQQ*8*coP@ zJU5YvLNLtvR=D?VH2XgYGYZ*ouXqOj5w~Fu{5#CZJHxylX1@4fnD2kVP2B2|ejxms z0TuwH1>pDc-;FokD8`Q;FOrj!MOIdpu-R;)nBI$v@n0_!<`@eDE|!4FXtT0^+^A~Ze>OyjQ<$ZSKm3L58)gK zxT#7z;#Ys*{4to$(u2Z(RsEX%_505<4w^4cnH38U^*M+iX0JE*bsF&S{sSN(gZblg z_^&HFG6V3{(fn)aO&uoeKL8(V;T`cm{os(1x(8*hd+)&J-p}nICnHA7Is{j1 z3BdpTO;h*R)gLIXpgZzM{yJ?(UA=Oqf8=lV!*xhN-*VN-C4!2aAV|* zZ?*HQP)1STv!dv@NAZ|qA$q!i2lQ?L#Tu7U=}v#H;TTU#Py~BfEZhJdfK~t#pr<6C z`vJ^=-{SM3LxH*CW<@mxI^J)@c8=m>%}wAJR@Fu=_SSEAAkI@_~x5$8u&b) z%QjoFZnSkD zcW$dtT>J;T%igo6^lQ~q3J)iMc4S@4VECbbf=SOO=8;9^6B?!~e0}@&6+uBkIHwvc z!o$NwLn>=~4fJ8J1>Y5>D&z>z-q>U5v=g$|}+1X;jf(4?W0QZT}ek@&5@!-na#J2nM#qPUr5eGNj zE&{hpF=(e0Lv~9sW|tJh-jQPT`%=t$Pm0Koq`2jX6qAohQ3#m!l@v3-lVaX+DQ-I{ z#mbc{MOj&y*sx)P`17CtEFO5^0rBv|4~xehdrUm>#1pD~w{G1kUViyy)t0^a=9}Wf zs;y$xX(?W=mE!HU-xmA!?Gx|4_ntU(=#cp6qmRUqBS-MG{paG;H;2WCKT7fCmtQKr zsIIOSXHI=DzCJHSZEdYMckZ0x6=tjZVD#V#SQ`b?_^iiz;#M#ZDtr4{@Tt5;gv%jf zrkpI6%7x;7xk0=vpT{|^Lq>ZlIw5@T1dQ$Sg$Q3S#DuL_$3*z~wHOP?4q^?$KaB8y zL--1W{{Z1nxP#;;PL;_@y0$Tr)(-M)`;3wg&vA^*HV$XA{h@}onI;YE9#C+>nZ-d-3p1)7RaflRWCQEU4 zp%mvfNOAsoDJ~q64dJgt_!|&@48l)A_<0Dw2;o;F`~w}NcyfpoFHM$W=RzqCY>?vU z^HS6tY7Fm#@XUeE2!9>I_d@u72tNqnZ|)$)^dV9#oGiuKg@|i|6g!@m;>e-K@J*YB z6#a9xjEo!`F*YJfSxp!i*f*ee?|$POJVcC*i;aznii;dGCL${3hQR*)`v#03-zUZC zAu1Nm_>YAqDk>#t06mN!Z+5!J#YaZRjY0I$kr89YMx+d)hrWG#_j0<&##)5fY?yhhQ=xqj)u?_L3bneu#<1OJTf*?g@FSrB$D29)SgL|ir zJ#KO0Hen!9K=_m~5%Ck_;-ceHx-o*T{$09U>+9>=eFAZ$I|aMMBbAhHO+2J98|WSx z9UmJP7atwpvxx_|_aCU@9~&PP7aboJKkoW&ot;S_g_Hq-H%tKij6YH(VdPrMXn&wU z6Y@{RKO`(3SsWi77q5s=I2!p6{8OTaScpPge0)l`apO96>H}_ehhMX=MB0=(wnOIvto-u~BHQobD;1gZ)~2ySa6U0fs~|K5h&csnI=R#IWFy)*e3B z#KJLhEO>e7(8$O}_mq^e5tG8&xAGc}6e3d?du&u=`YDm*yGdc~+*?`b9ytc=8fOGY z{xN^RV3xKW+D}S}j2{yQUTbiMyYa>NbqI}*Kt9I0I5YpFln>aC2#E|sRdIDz@uPg> zqQHo;(ZfbHb9RXz1sxkPs%dD{kEUOIlgQD&%8H^oQ}LMN7+h2UigoyR#&bo8qdCZy z#&X4COtbOamB%T@cy7v+DQY~jB7JY0@baYG6(cO)X?n%)>z9fdo?P9XrD;CNR#Rhp) zJTJdN|J~Tnh{5O(6Z{b#o$dNT2tOR*EeIcn@G}v9A;RB<@Q)z;3kbgt;g2=+Gyff@ zAp8D1PWgXwoYGC`U$<`EP>F=-s#a6G`S%?>cK-Fo%u-yeQE`1KqdG&o3f?>#VZaE~^FJHSKxcI~e3-m7om;I?f7 zuXkFQ&4B|4`}p_>T6=l<4ltRzd3tvn*rTma8(IwZ>)E-L zTdUsiJ9u!9)?SSNT7UPBFtzQ|rgiHe%maG$z0RXMToI%P%t$||d#C<=f(8!`>JR*p z{=lH1fS{mWL5jZa_{{iv^-@MrWP+TDR`7_ivH~}#Eq$~%{LnwbiJlMx0R_WzF&E$_ z%=rDcK6ix1h+Q2A!KhuSUQn6x|HgMY>aUbG@#RsB&OLkf>;;t&0O;4cb!*Ika2-hH z6o$|^%nWD;FmfE)|B;XR!+0^fxvGzO>jxnFn;(7j(PZ>HZ=5=H>f0ZF_~ASBJzxFv zpa1*@>ajCt&YZ%$^u*DlN1xxbXO9E>7eS*(j|S(n^wkj_#>1m6%!l8Te=KWu%-8hm z*RKy$<`tKzujccgIdkTi;rQVBI;>2@su*Of&V ztW|{6)z$q1Jvz>-#=k4_`vB_TP{uK0#E5>x4|pm3-+AX9i8WFQ{N$^zzN+xX8j}3> z+i&H;g9jzn6qG-P!5D$|ClKb*=bwLm`L=D_mSfl#ZMWMe<>%)|ty{M)0pn`oHUJRR zrAwC(tZm$S(@i%q@BE{pq5>H&^uG)LpMLsDVl17W2Cz&FApXoNs)n9sfM##qTy6dBdiLyD ziEc>o|HzRe`;m_cECZ~Y$v573L!!(j#>}b?vkX`!SXWa(-1qL?D_K_#A3m(~q=9V! z`Hl62WklRw`9aF`BY3^xeLVlWPs+h>^L1h=Z>*4V$~#g%^No~0pFR5_>I&=s)qD#7 z?c29+2nYxmhB5p)w3ia)p=e;6!!lsLk{*@?ai^Z;|HT(yD4?Es&ip36?LXHy7G0{*vr zrqeJMm+Wo^uA@K$){|vE=pz1EA4(bUrd} zny)MIM_arBYy87d4(nLY*tWDp2l?H#ObouF%KWE0`vrz&JLCE#oj@HW4WCOH3mT$9 z!`6Sg`natAG+Z9M3^85LpFc0r&olwjV1PkKOJ!ov;!4Mb3m4>}*B+5iEQ^y5E*vBO zlxIho-W<>)Dm|cxG z5S%n59FuYaaEbv94fUBcIP3F`Y?IKY_C=f23pDh6Rc~7n&B|spDExc(?%mM-qmO4l ze@S_;{<1$X%7gWRW}}`Ef3$nD;?HUF*`=f9lZ(UU(%ri+1-_y1Ki3?8)-(1m7-uyAX)vHU9Y$SorGt9Xz;9bosC;4BDEYTL!sOFS zK*OSN`E%{}@)J~N(vSigu*W8oUFtL2B-ZB$)CIOlBheU2r{yXr}iH?8scBw1z z$M}Bjuwlb`V~kQlI@m5!CC`u!j(Mn(7TU3|HO6IhV}Hvr1HadcL**+gLgn+I;jf@! z3ut)rfjoKU6qx5KzG~390A(_g{WEAV#u)v#>w%l+Kidm~|3{4))sJOi0P8F9CT_&t zpoMKe+k4Jy*lx0~V&BGZ`>K)hHPG<#icxawvM~7^XlP!a|A;ne!-5F8_V%%IbylRb z&ySQvX;IQ)yH$Si$w5gPT*nxyKBGusk^En^`eykSXm}MgytEuNpf0dJlLppjwn-15T_p|cNkjHnc~>TA$cU0F(xMd& z#FJwT_Rq!`V;K53e3$S)+YI($97hngY}q28efC+&aj5}Dc~HGlJ;A!HJouMXS!o|3 z-&_eAP#0bV4S#pmXSPWjZ8ytD3T~G7=Z}&1EVJg^3VAec`s;K2O8FZhEnj+%Je9ODBGk6modhRsQ36TY5$48!T(`l zVf}~$@x-Z+1{zo{u5?_9mS2CZmtR&sDBoTiDRY-wim&5SN>q`wnqsePkV2v0{ay z!Ii(7)6%?5Xl9$hy6{`yC%gXEFvehgE_c@FX5p?l{vrM>`8I7$i(?wB z56DG}7B$dtB|5HDCd7wr(o1ZUeycu%27WE%bxT%VnNK)tQ;D`y_V3?crlh1uoXwCN z*A*5PN~_f>*REZw>NS>pRJ+5mP)oEh@7Xu7t>RdS<5A8B__018c+w{8f5CiX(<0SB zyN)p)aMtJbs0;jBLe{XuT7?N?{i!f3{BOA7hT%BclZ$6KYA}vgNDhbN+?3zcOrvt|b3}*Ee9^KMdz!o@2g&az*$OJQd060&M3)M zt5*Gd>#euSoSYn$@0eFAULp;w7hLxtZlt3*EnI_OKTN*o7>RX->k&IJpQIhfNbDy_ zOA8;e;QSXXuiv}=!?;j)AHy2uu|NFb4;P6i&W$Qyoi=C~IB=lE*)>%T>@&#o9Qzn_ z5MSa?o+q808&G9^U>#w7Fv^4~d5~ihqwnF|oAYPz%kNtMWZfeFgO@hqyx2#qr`Qpf z%a$!uh9vv;cgMn*wIz^QO-5JK9fvUk*MwuA( zg!O~C8!GvTa|ZGi$AK(6V|@eobGywKa5bN5KSB3eYytE}9+t2^zxCEziU#Hn*9}oW z6b-4VsdB=E35vI{C$I2lUNg^03-M+>VEaRR&JoBz9Bc1DJzyPRonX9dgMs@+#8cw$ z?{7i5KaO=L4M=m(hxJzp`CUR9jCRqW#h`&I z^P6oG`ffE(AWyNcbK*;7|{VCfrJFIKoYhQNEB37 z32ulByMU;GBFHGK50Oz+L}VNu4v0KuP!YVd+S8)!KPt+gObzF~<$Ar9Hv}jTA*HDgojD7y zgq<$mNjuY}q$hb@B>holAENwoW5PP{OmtWeWsLW*Z={#9wH&&WeTis?eLC|r<-LaL zqyLBTAIz`N|Hb+}oP8nxSw{U}8TErDc}o4lxxmb^;ddMd>Q2s6ju~|ku>@jJ>7=CHchsb7MmLp7sy>MO==Vvc+=s>eZp%B@Z}XDet78^8oTV z1I_RReC z_!J?ofjK5?Z~>n|`2WmogU>$jIgMYPf+{R7%LfcN6ssE?_6H2uiuZH&)|B`w{QU1L zYbEMSNtAf|1??gI$}*}isy~VDDprrrD-z|_UsF~l=nQJgltfu8QPxeA=OxP2MCm8W z%0wB|lqoFvoK_lgYvj{>PK(d?(7t7+JfGB7#7N(Zj3{_;0697FGZB^JjtuyAeKDsGQlG)*{1*E5A6N^%f?nDL`+OSWWW8Vmr{j7Vxe4rz zJw1VO5GP|Sv^C%jqs@cVpVX1`mq-J1gkdCX z{=?9#`=E0_gx-9O6%#Wh-bmb<_zGj<#D_lG9|Y^r_g{eZrczHMu8@P+Q#NU!?L^;` z>m&6#*COg_+Oo7$_#6Lb`XjI^VxGj>i1G0G1!7dhcZmBFV`Dm5=ka|ieE|5NCGZz| zasB1GMn9MB(vBui68#Ttr`-qjjvN!qH6aGyQD_I`G_URAOMqnll z%+&A95+J zGMUOX1^#Oe?A&b95z)f&rwvS6xcU zOBdXWIhITVdBpSk;hf;JG?d5VaJp2s4?HCYafR$;I@mVloFx z4~TcY3>p~IWNe%m3+0hG7x5KhjUmRBXn#nL>)BK|Cn9-t!3U%x<4@ZRI7ANKyUeC8 zpubQ1oc$&(KTFcVOnxwv4)TfP@N?tI4)UCSHGOKvO-ToNL4CyEn26u^}{7rPzvKJ){*))SW@ zwgfvXz;oYWza#&PbTE@2q~rLpA$?r8s5|JFqIw;f_TA5=RQjN>KXwvJBrbvXOasPW zNkiYheM7!eA8;LH-H{BiE$W46u40`y1YD*kWVswR689|QGf$`TsZz-+@V**{H?1bd z!x%kY_zyT1#MiJchwacFAm7Id-i9@I4%c*QQXO;_MlDU?6XLDogv zj{IgUmoms$I&lro57HUMt~dtN3%rlCkQdb3)S0An`SRuC(C}|h)i{0+=Rfe<9K1)D z&9+%5$C)%>pMU`S3IrWGbO>o94a~fTbAZ=TZfGY_U+}p^#KNbdUiu2ZB!&8y`UlU^ z_hNlp5R>PcPn|<-0ecpN7&GSu>0=@vq8KmjWnwC{+tG)o(daL2oAib4NAgU*GLsHw zw#W4ddnAOqfzPV4@02-?7kSNoQzs+#GV@<-J8YlrK-Yx&|3q{|HZa~f2=KmR7%Qe- z$e0u5@mI7Rw$J`X>n~-A{bilZ)Onn9oZIXtWsJ}7@YxHz7ZB=))9g3fW=duMkLP#kx*#U^EAy zqjuxDkcVf^oOz7i0S2%Kb(GGTGbj8W*VLXqeR?~@vzNX2;)~&UMr#7iJ9`sdzo_Jp3nz=wZkqUI?JfEr^gZbN(AK5D@xs$11J*%5JOgxa{IK^-h;vhikq=u}O$`=RxuHKv z+nshh{Ra9@L-KA2+sN7y1he7S;yv%6va&LKMufN;$CC0A@jtv6|E*g&KeYRY!1qZc zc6K58hjk&;Tg2q}`~dHbWbUP*Ua)iPhao@dL)~8(w!c4;AIcteJ@pR967Q~uJf*JY zSpNNuX~E0)-4H&{{mj6dg129r#pV}A_v77_&{m`VqTEn#)8^uMP@g8#!oRteNB6Si z_0jl~cahz-1?wc+=Xyjxfpt?aW9_d@zMp!Ubh94v0>6jziEB3FNxYYSHrH-oF~{7? zdWP10p-n^NSgk~kp*&fRuHp5}=keMU1TxdoaIPu(H=Z{FW@egDjlk!8VvvB@nS$N)JPt;b39m?}i za|zC(FU8f@hSx|j6X8+|IgWj3Kz!^*m?J5%>u^mmc6Z6a^@Bi7&FS)NBnvh17uJ@B z{cDar`!fyaQI_MEGF}?H8?Uye9BDfp-!nBQ&koxyN9|F)GIp6{mco+!W^lM3?jav|I%XM4o@;uvhZV7!(^jL?D|^K* z$Mx-TZO`yZa;6V>!d~>mj(HWR^QYgX!I-`dFIJ|VvTw=!GDF&KNz~1gGD%xBcKlQf zIPX$k-h8C~q$_^Xx@dI_ zt)kVDPUCjbD&nV?*(S41dV0GKH+1TRKWH^>$7s}-+DLav71Hz4Drvj)os=qHEw_>D zDFw%~HFnrRoB8sk%n}K;5PuR$Il##9ghgwodymUFaG5&3bpeNH5bz z>l5_Z`dod1{;|GWZ)CJEt~JVxvBm^rf$^rX!PsPMHO?`AZ(dltgAwb=@+PIkUM)?Q}6Wxr>CW*@eH=d^Q*ogvN}&bN*u zI*1%mCLR&5icdsucceShUF53XHgAYO%zwLjNqlA&@J*B_(T2i{xttBp9+*OWt}B8lCF_% zmZnH+q)(+Er9a4b$hmU0JV~AYemLy`Mf- z|3ZJryvWM3x>)_JN!B{+pp|crwx6({wO_KA+W&2TWPf7suvi#o{v2R5TZgutaNd1NfCK?iTsr**&64JRqvYL@`6m7YoIk;%%``Y!qLKec~*4 zx%ai#&~N2G>@W2H>Tl!X(wZ|DAe+=ox=rdX^^pch4@wiINzymcZ)I6dm;1^O$=l>5 z$|`X5JEeo#PrX+i6W<+=X-l=4`a*r3zRQxFKZ%uMmpISej(ysoVqy)jSluaik#poc zxlk@fE&b%-;LS#Pv;4VykFr>KU-9Fa@lo*!@u~57@x}2s;s@d>+PPX&t)njhOyN6i?P|*4LLjCycAN_&df7Q%nEaed7nAnTx@=7eq)|(-DXX- z{$aUx%xUS&bXGVYIQ4}s+F&HJL|2g~N<@ViB1VdFVuE-^JSXOfC1SN$3l1F=zj0N! z+#Tdjcb{|Txo^6kxV5|?-V|>RByXpu`j&r--_h^v=lX?yso&op><{-x`_=vv{#5@( z|7HJGf2qF`^0C%m5AJ^MA0mIdVUq*!;w-7YM0ss2HI=SL5XYC&r5mLzsazT)jh8az zY`LqPFOQIaleE6kADG+9`QTsdB$kt;|stDod4jl@FEe%4Odk6L$a6n+ zih6DQ_IS_u-1zGFy7-szeev((bu~xJ(7I?3Y0qolX;AzcDT{uQxlH517xH^US}Q+sxhO6;?~jw#u!+)(GoO>pg3ewa>cHzRXUy zZ?&`S9=7k~Vy;#@TbwJ!HNp_>M7NNy6T~FU*muMSViRQHeD_xOS#a@e$lE*If{vmV zlVn3~t8~W9yj-0fzgsKN8tcvVX`pSm@sQEdEHWpVtIe3b#_sKmbmlqdiQhxUJNTUN z8Mr@P`dI!?(K?25TH+#F$!F{{l-%_q#K%_-(ibFaDIJZK&=bFE^?$2jXz>j~>= zYpL~xb*Y_ZH?^DDEo{k-+omn-2kbF+wf!ixn(Fj&`Z)cZVa^C=tn;ul4Kwqvp#(L@ ztn4NFivF0F_d#=gEq)L&@Nj}V3tU{}Ug4_ra0)z<^dHhB`C0jStP^#W3zQp`T;*#e zQ{AUt5U-`R05|sO?;6jT@0r^n`{~xF)&;g>FT{Ef*qY-zZ#%_ew0KE0a<6f-T-3L& zX1|->Qm(vD(bZgan3}JxH#TA&{JXKu_}thDJymKBHXkz=n0w5Np_8WCGwoNMkDTse zlvpO-6V>kH?lkv#cbWU6dk$ozsrSBjrSG5@3~RRaf;HcI#ae7Fu~t~CFi+QFHQj1$w{}=xS$nPhSXmEY z?$);J*$wQ5_J#H(cH^H&YHMh>47E`rs3Y-dOtGnIZ;eO@DyxLwpuK{NLg&5PunE6dT(-U56FWt-V w+IyK^N3WCD*(>mhAj=irKyR>D9b+D<@QBhV6U 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

        qx{CqlSNQ_YhQ()VtG-D; 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 z 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 bB!6W7g#4-b3-g!cLzth+PZapSNrC?c%T_@< literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/wheel.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/wheel.py new file mode 100644 index 000000000..62ab10fb3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/wheel.py @@ -0,0 +1,1100 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2023 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import unicode_literals + +import base64 +import codecs +import datetime +from email import message_from_file +import hashlib +import json +import logging +import os +import posixpath +import re +import shutil +import sys +import tempfile +import zipfile + +from . import __version__, DistlibException +from .compat import sysconfig, ZipFile, fsdecode, text_type, filter +from .database import InstalledDistribution +from .metadata import Metadata, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME +from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, cached_property, get_cache_base, + read_exports, tempdir, get_platform) +from .version import NormalizedVersion, UnsupportedVersionError + +logger = logging.getLogger(__name__) + +cache = None # created when needed + +if hasattr(sys, 'pypy_version_info'): # pragma: no cover + IMP_PREFIX = 'pp' +elif sys.platform.startswith('java'): # pragma: no cover + IMP_PREFIX = 'jy' +elif sys.platform == 'cli': # pragma: no cover + IMP_PREFIX = 'ip' +else: + IMP_PREFIX = 'cp' + +VER_SUFFIX = sysconfig.get_config_var('py_version_nodot') +if not VER_SUFFIX: # pragma: no cover + VER_SUFFIX = '%s%s' % sys.version_info[:2] +PYVER = 'py' + VER_SUFFIX +IMPVER = IMP_PREFIX + VER_SUFFIX + +ARCH = get_platform().replace('-', '_').replace('.', '_') + +ABI = sysconfig.get_config_var('SOABI') +if ABI and ABI.startswith('cpython-'): + ABI = ABI.replace('cpython-', 'cp').split('-')[0] +else: + + def _derive_abi(): + parts = ['cp', VER_SUFFIX] + if sysconfig.get_config_var('Py_DEBUG'): + parts.append('d') + if IMP_PREFIX == 'cp': + vi = sys.version_info[:2] + if vi < (3, 8): + wpm = sysconfig.get_config_var('WITH_PYMALLOC') + if wpm is None: + wpm = True + if wpm: + parts.append('m') + if vi < (3, 3): + us = sysconfig.get_config_var('Py_UNICODE_SIZE') + if us == 4 or (us is None and sys.maxunicode == 0x10FFFF): + parts.append('u') + return ''.join(parts) + + ABI = _derive_abi() + del _derive_abi + +FILENAME_RE = re.compile( + r''' +(?P[^-]+) +-(?P\d+[^-]*) +(-(?P\d+[^-]*))? +-(?P\w+\d+(\.\w+\d+)*) +-(?P\w+) +-(?P\w+(\.\w+)*) +\.whl$ +''', re.IGNORECASE | re.VERBOSE) + +NAME_VERSION_RE = re.compile(r''' +(?P[^-]+) +-(?P\d+[^-]*) +(-(?P\d+[^-]*))?$ +''', re.IGNORECASE | re.VERBOSE) + +SHEBANG_RE = re.compile(br'\s*#![^\r\n]*') +SHEBANG_DETAIL_RE = re.compile(br'^(\s*#!("[^"]+"|\S+))\s+(.*)$') +SHEBANG_PYTHON = b'#!python' +SHEBANG_PYTHONW = b'#!pythonw' + +if os.sep == '/': + to_posix = lambda o: o +else: + to_posix = lambda o: o.replace(os.sep, '/') + +if sys.version_info[0] < 3: + import imp +else: + imp = None + import importlib.machinery + import importlib.util + + +def _get_suffixes(): + if imp: + return [s[0] for s in imp.get_suffixes()] + else: + return importlib.machinery.EXTENSION_SUFFIXES + + +def _load_dynamic(name, path): + # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly + if imp: + return imp.load_dynamic(name, path) + else: + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + + +class Mounter(object): + + def __init__(self): + self.impure_wheels = {} + self.libs = {} + + def add(self, pathname, extensions): + self.impure_wheels[pathname] = extensions + self.libs.update(extensions) + + def remove(self, pathname): + extensions = self.impure_wheels.pop(pathname) + for k, v in extensions: + if k in self.libs: + del self.libs[k] + + def find_module(self, fullname, path=None): + if fullname in self.libs: + result = self + else: + result = None + return result + + def load_module(self, fullname): + if fullname in sys.modules: + result = sys.modules[fullname] + else: + if fullname not in self.libs: + raise ImportError('unable to find extension for %s' % fullname) + result = _load_dynamic(fullname, self.libs[fullname]) + result.__loader__ = self + parts = fullname.rsplit('.', 1) + if len(parts) > 1: + result.__package__ = parts[0] + return result + + +_hook = Mounter() + + +class Wheel(object): + """ + Class to build and install from Wheel files (PEP 427). + """ + + wheel_version = (1, 1) + hash_kind = 'sha256' + + def __init__(self, filename=None, sign=False, verify=False): + """ + Initialise an instance using a (valid) filename. + """ + self.sign = sign + self.should_verify = verify + self.buildver = '' + self.pyver = [PYVER] + self.abi = ['none'] + self.arch = ['any'] + self.dirname = os.getcwd() + if filename is None: + self.name = 'dummy' + self.version = '0.1' + self._filename = self.filename + else: + m = NAME_VERSION_RE.match(filename) + if m: + info = m.groupdict('') + self.name = info['nm'] + # Reinstate the local version separator + self.version = info['vn'].replace('_', '-') + self.buildver = info['bn'] + self._filename = self.filename + else: + dirname, filename = os.path.split(filename) + m = FILENAME_RE.match(filename) + if not m: + raise DistlibException('Invalid name or ' + 'filename: %r' % filename) + if dirname: + self.dirname = os.path.abspath(dirname) + self._filename = filename + info = m.groupdict('') + self.name = info['nm'] + self.version = info['vn'] + self.buildver = info['bn'] + self.pyver = info['py'].split('.') + self.abi = info['bi'].split('.') + self.arch = info['ar'].split('.') + + @property + def filename(self): + """ + Build and return a filename from the various components. + """ + if self.buildver: + buildver = '-' + self.buildver + else: + buildver = '' + pyver = '.'.join(self.pyver) + abi = '.'.join(self.abi) + arch = '.'.join(self.arch) + # replace - with _ as a local version separator + version = self.version.replace('-', '_') + return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver, pyver, abi, arch) + + @property + def exists(self): + path = os.path.join(self.dirname, self.filename) + return os.path.isfile(path) + + @property + def tags(self): + for pyver in self.pyver: + for abi in self.abi: + for arch in self.arch: + yield pyver, abi, arch + + @cached_property + def metadata(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + wrapper = codecs.getreader('utf-8') + with ZipFile(pathname, 'r') as zf: + self.get_wheel_metadata(zf) + # wv = wheel_metadata['Wheel-Version'].split('.', 1) + # file_version = tuple([int(i) for i in wv]) + # if file_version < (1, 1): + # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, + # LEGACY_METADATA_FILENAME] + # else: + # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME] + fns = [WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME] + result = None + for fn in fns: + try: + metadata_filename = posixpath.join(info_dir, fn) + with zf.open(metadata_filename) as bf: + wf = wrapper(bf) + result = Metadata(fileobj=wf) + if result: + break + except KeyError: + pass + if not result: + raise ValueError('Invalid wheel, because metadata is ' + 'missing: looked in %s' % ', '.join(fns)) + return result + + def get_wheel_metadata(self, zf): + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + metadata_filename = posixpath.join(info_dir, 'WHEEL') + with zf.open(metadata_filename) as bf: + wf = codecs.getreader('utf-8')(bf) + message = message_from_file(wf) + return dict(message) + + @cached_property + def info(self): + pathname = os.path.join(self.dirname, self.filename) + with ZipFile(pathname, 'r') as zf: + result = self.get_wheel_metadata(zf) + return result + + def process_shebang(self, data): + m = SHEBANG_RE.match(data) + if m: + end = m.end() + shebang, data_after_shebang = data[:end], data[end:] + # Preserve any arguments after the interpreter + if b'pythonw' in shebang.lower(): + shebang_python = SHEBANG_PYTHONW + else: + shebang_python = SHEBANG_PYTHON + m = SHEBANG_DETAIL_RE.match(shebang) + if m: + args = b' ' + m.groups()[-1] + else: + args = b'' + shebang = shebang_python + args + data = shebang + data_after_shebang + else: + cr = data.find(b'\r') + lf = data.find(b'\n') + if cr < 0 or cr > lf: + term = b'\n' + else: + if data[cr:cr + 2] == b'\r\n': + term = b'\r\n' + else: + term = b'\r' + data = SHEBANG_PYTHON + term + data + return data + + def get_hash(self, data, hash_kind=None): + if hash_kind is None: + hash_kind = self.hash_kind + try: + hasher = getattr(hashlib, hash_kind) + except AttributeError: + raise DistlibException('Unsupported hash algorithm: %r' % hash_kind) + result = hasher(data).digest() + result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii') + return hash_kind, result + + def write_record(self, records, record_path, archive_record_path): + records = list(records) # make a copy, as mutated + records.append((archive_record_path, '', '')) + with CSVWriter(record_path) as writer: + for row in records: + writer.writerow(row) + + def write_records(self, info, libdir, archive_paths): + records = [] + distinfo, info_dir = info + # hasher = getattr(hashlib, self.hash_kind) + for ap, p in archive_paths: + with open(p, 'rb') as f: + data = f.read() + digest = '%s=%s' % self.get_hash(data) + size = os.path.getsize(p) + records.append((ap, digest, size)) + + p = os.path.join(distinfo, 'RECORD') + ap = to_posix(os.path.join(info_dir, 'RECORD')) + self.write_record(records, p, ap) + archive_paths.append((ap, p)) + + def build_zip(self, pathname, archive_paths): + with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf: + for ap, p in archive_paths: + logger.debug('Wrote %s to %s in wheel', p, ap) + zf.write(p, ap) + + def build(self, paths, tags=None, wheel_version=None): + """ + Build a wheel from files in specified paths, and use any specified tags + when determining the name of the wheel. + """ + if tags is None: + tags = {} + + libkey = list(filter(lambda o: o in paths, ('purelib', 'platlib')))[0] + if libkey == 'platlib': + is_pure = 'false' + default_pyver = [IMPVER] + default_abi = [ABI] + default_arch = [ARCH] + else: + is_pure = 'true' + default_pyver = [PYVER] + default_abi = ['none'] + default_arch = ['any'] + + self.pyver = tags.get('pyver', default_pyver) + self.abi = tags.get('abi', default_abi) + self.arch = tags.get('arch', default_arch) + + libdir = paths[libkey] + + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + archive_paths = [] + + # First, stuff which is not in site-packages + for key in ('data', 'headers', 'scripts'): + if key not in paths: + continue + path = paths[key] + if os.path.isdir(path): + for root, dirs, files in os.walk(path): + for fn in files: + p = fsdecode(os.path.join(root, fn)) + rp = os.path.relpath(p, path) + ap = to_posix(os.path.join(data_dir, key, rp)) + archive_paths.append((ap, p)) + if key == 'scripts' and not p.endswith('.exe'): + with open(p, 'rb') as f: + data = f.read() + data = self.process_shebang(data) + with open(p, 'wb') as f: + f.write(data) + + # Now, stuff which is in site-packages, other than the + # distinfo stuff. + path = libdir + distinfo = None + for root, dirs, files in os.walk(path): + if root == path: + # At the top level only, save distinfo for later + # and skip it for now + for i, dn in enumerate(dirs): + dn = fsdecode(dn) + if dn.endswith('.dist-info'): + distinfo = os.path.join(root, dn) + del dirs[i] + break + assert distinfo, '.dist-info directory expected, not found' + + for fn in files: + # comment out next suite to leave .pyc files in + if fsdecode(fn).endswith(('.pyc', '.pyo')): + continue + p = os.path.join(root, fn) + rp = to_posix(os.path.relpath(p, path)) + archive_paths.append((rp, p)) + + # Now distinfo. Assumed to be flat, i.e. os.listdir is enough. + files = os.listdir(distinfo) + for fn in files: + if fn not in ('RECORD', 'INSTALLER', 'SHARED', 'WHEEL'): + p = fsdecode(os.path.join(distinfo, fn)) + ap = to_posix(os.path.join(info_dir, fn)) + archive_paths.append((ap, p)) + + wheel_metadata = [ + 'Wheel-Version: %d.%d' % (wheel_version or self.wheel_version), + 'Generator: distlib %s' % __version__, + 'Root-Is-Purelib: %s' % is_pure, + ] + for pyver, abi, arch in self.tags: + wheel_metadata.append('Tag: %s-%s-%s' % (pyver, abi, arch)) + p = os.path.join(distinfo, 'WHEEL') + with open(p, 'w') as f: + f.write('\n'.join(wheel_metadata)) + ap = to_posix(os.path.join(info_dir, 'WHEEL')) + archive_paths.append((ap, p)) + + # sort the entries by archive path. Not needed by any spec, but it + # keeps the archive listing and RECORD tidier than they would otherwise + # be. Use the number of path segments to keep directory entries together, + # and keep the dist-info stuff at the end. + def sorter(t): + ap = t[0] + n = ap.count('/') + if '.dist-info' in ap: + n += 10000 + return (n, ap) + + archive_paths = sorted(archive_paths, key=sorter) + + # Now, at last, RECORD. + # Paths in here are archive paths - nothing else makes sense. + self.write_records((distinfo, info_dir), libdir, archive_paths) + # Now, ready to build the zip file + pathname = os.path.join(self.dirname, self.filename) + self.build_zip(pathname, archive_paths) + return pathname + + def skip_entry(self, arcname): + """ + Determine whether an archive entry should be skipped when verifying + or installing. + """ + # The signature file won't be in RECORD, + # and we don't currently don't do anything with it + # We also skip directories, as they won't be in RECORD + # either. See: + # + # https://github.com/pypa/wheel/issues/294 + # https://github.com/pypa/wheel/issues/287 + # https://github.com/pypa/wheel/pull/289 + # + return arcname.endswith(('/', '/RECORD.jws')) + + def install(self, paths, maker, **kwargs): + """ + Install a wheel to the specified paths. If kwarg ``warner`` is + specified, it should be a callable, which will be called with two + tuples indicating the wheel version of this software and the wheel + version in the file, if there is a discrepancy in the versions. + This can be used to issue any warnings to raise any exceptions. + If kwarg ``lib_only`` is True, only the purelib/platlib files are + installed, and the headers, scripts, data and dist-info metadata are + not written. If kwarg ``bytecode_hashed_invalidation`` is True, written + bytecode will try to use file-hash based invalidation (PEP-552) on + supported interpreter versions (CPython 3.7+). + + The return value is a :class:`InstalledDistribution` instance unless + ``options.lib_only`` is True, in which case the return value is ``None``. + """ + + dry_run = maker.dry_run + warner = kwargs.get('warner') + lib_only = kwargs.get('lib_only', False) + bc_hashed_invalidation = kwargs.get('bytecode_hashed_invalidation', False) + + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) + wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') + record_name = posixpath.join(info_dir, 'RECORD') + + wrapper = codecs.getreader('utf-8') + + with ZipFile(pathname, 'r') as zf: + with zf.open(wheel_metadata_name) as bwf: + wf = wrapper(bwf) + message = message_from_file(wf) + wv = message['Wheel-Version'].split('.', 1) + file_version = tuple([int(i) for i in wv]) + if (file_version != self.wheel_version) and warner: + warner(self.wheel_version, file_version) + + if message['Root-Is-Purelib'] == 'true': + libdir = paths['purelib'] + else: + libdir = paths['platlib'] + + records = {} + with zf.open(record_name) as bf: + with CSVReader(stream=bf) as reader: + for row in reader: + p = row[0] + records[p] = row + + data_pfx = posixpath.join(data_dir, '') + info_pfx = posixpath.join(info_dir, '') + script_pfx = posixpath.join(data_dir, 'scripts', '') + + # make a new instance rather than a copy of maker's, + # as we mutate it + fileop = FileOperator(dry_run=dry_run) + fileop.record = True # so we can rollback if needed + + bc = not sys.dont_write_bytecode # Double negatives. Lovely! + + outfiles = [] # for RECORD writing + + # for script copying/shebang processing + workdir = tempfile.mkdtemp() + # set target dir later + # we default add_launchers to False, as the + # Python Launcher should be used instead + maker.source_dir = workdir + maker.target_dir = None + try: + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + if self.skip_entry(u_arcname): + continue + row = records[u_arcname] + if row[2] and str(zinfo.file_size) != row[2]: + raise DistlibException('size mismatch for ' + '%s' % u_arcname) + if row[1]: + kind, value = row[1].split('=', 1) + with zf.open(arcname) as bf: + data = bf.read() + _, digest = self.get_hash(data, kind) + if digest != value: + raise DistlibException('digest mismatch for ' + '%s' % arcname) + + if lib_only and u_arcname.startswith((info_pfx, data_pfx)): + logger.debug('lib_only: skipping %s', u_arcname) + continue + is_script = (u_arcname.startswith(script_pfx) and not u_arcname.endswith('.exe')) + + if u_arcname.startswith(data_pfx): + _, where, rp = u_arcname.split('/', 2) + outfile = os.path.join(paths[where], convert_path(rp)) + else: + # meant for site-packages. + if u_arcname in (wheel_metadata_name, record_name): + continue + outfile = os.path.join(libdir, convert_path(u_arcname)) + if not is_script: + with zf.open(arcname) as bf: + fileop.copy_stream(bf, outfile) + # Issue #147: permission bits aren't preserved. Using + # zf.extract(zinfo, libdir) should have worked, but didn't, + # see https://www.thetopsites.net/article/53834422.shtml + # So ... manually preserve permission bits as given in zinfo + if os.name == 'posix': + # just set the normal permission bits + os.chmod(outfile, (zinfo.external_attr >> 16) & 0x1FF) + outfiles.append(outfile) + # Double check the digest of the written file + if not dry_run and row[1]: + with open(outfile, 'rb') as bf: + data = bf.read() + _, newdigest = self.get_hash(data, kind) + if newdigest != digest: + raise DistlibException('digest mismatch ' + 'on write for ' + '%s' % outfile) + if bc and outfile.endswith('.py'): + try: + pyc = fileop.byte_compile(outfile, hashed_invalidation=bc_hashed_invalidation) + outfiles.append(pyc) + except Exception: + # Don't give up if byte-compilation fails, + # but log it and perhaps warn the user + logger.warning('Byte-compilation failed', exc_info=True) + else: + fn = os.path.basename(convert_path(arcname)) + workname = os.path.join(workdir, fn) + with zf.open(arcname) as bf: + fileop.copy_stream(bf, workname) + + dn, fn = os.path.split(outfile) + maker.target_dir = dn + filenames = maker.make(fn) + fileop.set_executable_mode(filenames) + outfiles.extend(filenames) + + if lib_only: + logger.debug('lib_only: returning None') + dist = None + else: + # Generate scripts + + # Try to get pydist.json so we can see if there are + # any commands to generate. If this fails (e.g. because + # of a legacy wheel), log a warning but don't give up. + commands = None + file_version = self.info['Wheel-Version'] + if file_version == '1.0': + # Use legacy info + ep = posixpath.join(info_dir, 'entry_points.txt') + try: + with zf.open(ep) as bwf: + epdata = read_exports(bwf) + commands = {} + for key in ('console', 'gui'): + k = '%s_scripts' % key + if k in epdata: + commands['wrap_%s' % key] = d = {} + for v in epdata[k].values(): + s = '%s:%s' % (v.prefix, v.suffix) + if v.flags: + s += ' [%s]' % ','.join(v.flags) + d[v.name] = s + except Exception: + logger.warning('Unable to read legacy script ' + 'metadata, so cannot generate ' + 'scripts') + else: + try: + with zf.open(metadata_name) as bwf: + wf = wrapper(bwf) + commands = json.load(wf).get('extensions') + if commands: + commands = commands.get('python.commands') + except Exception: + logger.warning('Unable to read JSON metadata, so ' + 'cannot generate scripts') + if commands: + console_scripts = commands.get('wrap_console', {}) + gui_scripts = commands.get('wrap_gui', {}) + if console_scripts or gui_scripts: + script_dir = paths.get('scripts', '') + if not os.path.isdir(script_dir): + raise ValueError('Valid script path not ' + 'specified') + maker.target_dir = script_dir + for k, v in console_scripts.items(): + script = '%s = %s' % (k, v) + filenames = maker.make(script) + fileop.set_executable_mode(filenames) + + if gui_scripts: + options = {'gui': True} + for k, v in gui_scripts.items(): + script = '%s = %s' % (k, v) + filenames = maker.make(script, options) + fileop.set_executable_mode(filenames) + + p = os.path.join(libdir, info_dir) + dist = InstalledDistribution(p) + + # Write SHARED + paths = dict(paths) # don't change passed in dict + del paths['purelib'] + del paths['platlib'] + paths['lib'] = libdir + p = dist.write_shared_locations(paths, dry_run) + if p: + outfiles.append(p) + + # Write RECORD + dist.write_installed_files(outfiles, paths['prefix'], dry_run) + return dist + except Exception: # pragma: no cover + logger.exception('installation failed.') + fileop.rollback() + raise + finally: + shutil.rmtree(workdir) + + def _get_dylib_cache(self): + global cache + if cache is None: + # Use native string to avoid issues on 2.x: see Python #20140. + base = os.path.join(get_cache_base(), str('dylib-cache'), '%s.%s' % sys.version_info[:2]) + cache = Cache(base) + return cache + + def _get_extensions(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + arcname = posixpath.join(info_dir, 'EXTENSIONS') + wrapper = codecs.getreader('utf-8') + result = [] + with ZipFile(pathname, 'r') as zf: + try: + with zf.open(arcname) as bf: + wf = wrapper(bf) + extensions = json.load(wf) + cache = self._get_dylib_cache() + prefix = cache.prefix_to_dir(self.filename, use_abspath=False) + cache_base = os.path.join(cache.base, prefix) + if not os.path.isdir(cache_base): + os.makedirs(cache_base) + for name, relpath in extensions.items(): + dest = os.path.join(cache_base, convert_path(relpath)) + if not os.path.exists(dest): + extract = True + else: + file_time = os.stat(dest).st_mtime + file_time = datetime.datetime.fromtimestamp(file_time) + info = zf.getinfo(relpath) + wheel_time = datetime.datetime(*info.date_time) + extract = wheel_time > file_time + if extract: + zf.extract(relpath, cache_base) + result.append((name, dest)) + except KeyError: + pass + return result + + def is_compatible(self): + """ + Determine if a wheel is compatible with the running system. + """ + return is_compatible(self) + + def is_mountable(self): + """ + Determine if a wheel is asserted as mountable by its metadata. + """ + return True # for now - metadata details TBD + + def mount(self, append=False): + pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) + if not self.is_compatible(): + msg = 'Wheel %s not compatible with this Python.' % pathname + raise DistlibException(msg) + if not self.is_mountable(): + msg = 'Wheel %s is marked as not mountable.' % pathname + raise DistlibException(msg) + if pathname in sys.path: + logger.debug('%s already in path', pathname) + else: + if append: + sys.path.append(pathname) + else: + sys.path.insert(0, pathname) + extensions = self._get_extensions() + if extensions: + if _hook not in sys.meta_path: + sys.meta_path.append(_hook) + _hook.add(pathname, extensions) + + def unmount(self): + pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) + if pathname not in sys.path: + logger.debug('%s not in path', pathname) + else: + sys.path.remove(pathname) + if pathname in _hook.impure_wheels: + _hook.remove(pathname) + if not _hook.impure_wheels: + if _hook in sys.meta_path: + sys.meta_path.remove(_hook) + + def verify(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + # data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + # metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) + wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') + record_name = posixpath.join(info_dir, 'RECORD') + + wrapper = codecs.getreader('utf-8') + + with ZipFile(pathname, 'r') as zf: + with zf.open(wheel_metadata_name) as bwf: + wf = wrapper(bwf) + message_from_file(wf) + # wv = message['Wheel-Version'].split('.', 1) + # file_version = tuple([int(i) for i in wv]) + # TODO version verification + + records = {} + with zf.open(record_name) as bf: + with CSVReader(stream=bf) as reader: + for row in reader: + p = row[0] + records[p] = row + + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + # See issue #115: some wheels have .. in their entries, but + # in the filename ... e.g. __main__..py ! So the check is + # updated to look for .. in the directory portions + p = u_arcname.split('/') + if '..' in p: + raise DistlibException('invalid entry in ' + 'wheel: %r' % u_arcname) + + if self.skip_entry(u_arcname): + continue + row = records[u_arcname] + if row[2] and str(zinfo.file_size) != row[2]: + raise DistlibException('size mismatch for ' + '%s' % u_arcname) + if row[1]: + kind, value = row[1].split('=', 1) + with zf.open(arcname) as bf: + data = bf.read() + _, digest = self.get_hash(data, kind) + if digest != value: + raise DistlibException('digest mismatch for ' + '%s' % arcname) + + def update(self, modifier, dest_dir=None, **kwargs): + """ + Update the contents of a wheel in a generic way. The modifier should + be a callable which expects a dictionary argument: its keys are + archive-entry paths, and its values are absolute filesystem paths + where the contents the corresponding archive entries can be found. The + modifier is free to change the contents of the files pointed to, add + new entries and remove entries, before returning. This method will + extract the entire contents of the wheel to a temporary location, call + the modifier, and then use the passed (and possibly updated) + dictionary to write a new wheel. If ``dest_dir`` is specified, the new + wheel is written there -- otherwise, the original wheel is overwritten. + + The modifier should return True if it updated the wheel, else False. + This method returns the same value the modifier returns. + """ + + def get_version(path_map, info_dir): + version = path = None + key = '%s/%s' % (info_dir, LEGACY_METADATA_FILENAME) + if key not in path_map: + key = '%s/PKG-INFO' % info_dir + if key in path_map: + path = path_map[key] + version = Metadata(path=path).version + return version, path + + def update_version(version, path): + updated = None + try: + NormalizedVersion(version) + i = version.find('-') + if i < 0: + updated = '%s+1' % version + else: + parts = [int(s) for s in version[i + 1:].split('.')] + parts[-1] += 1 + updated = '%s+%s' % (version[:i], '.'.join(str(i) for i in parts)) + except UnsupportedVersionError: + logger.debug('Cannot update non-compliant (PEP-440) ' + 'version %r', version) + if updated: + md = Metadata(path=path) + md.version = updated + legacy = path.endswith(LEGACY_METADATA_FILENAME) + md.write(path=path, legacy=legacy) + logger.debug('Version updated from %r to %r', version, updated) + + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + record_name = posixpath.join(info_dir, 'RECORD') + with tempdir() as workdir: + with ZipFile(pathname, 'r') as zf: + path_map = {} + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + if u_arcname == record_name: + continue + if '..' in u_arcname: + raise DistlibException('invalid entry in ' + 'wheel: %r' % u_arcname) + zf.extract(zinfo, workdir) + path = os.path.join(workdir, convert_path(u_arcname)) + path_map[u_arcname] = path + + # Remember the version. + original_version, _ = get_version(path_map, info_dir) + # Files extracted. Call the modifier. + modified = modifier(path_map, **kwargs) + if modified: + # Something changed - need to build a new wheel. + current_version, path = get_version(path_map, info_dir) + if current_version and (current_version == original_version): + # Add or update local version to signify changes. + update_version(current_version, path) + # Decide where the new wheel goes. + if dest_dir is None: + fd, newpath = tempfile.mkstemp(suffix='.whl', prefix='wheel-update-', dir=workdir) + os.close(fd) + else: + if not os.path.isdir(dest_dir): + raise DistlibException('Not a directory: %r' % dest_dir) + newpath = os.path.join(dest_dir, self.filename) + archive_paths = list(path_map.items()) + distinfo = os.path.join(workdir, info_dir) + info = distinfo, info_dir + self.write_records(info, workdir, archive_paths) + self.build_zip(newpath, archive_paths) + if dest_dir is None: + shutil.copyfile(newpath, pathname) + return modified + + +def _get_glibc_version(): + import platform + ver = platform.libc_ver() + result = [] + if ver[0] == 'glibc': + for s in ver[1].split('.'): + result.append(int(s) if s.isdigit() else 0) + result = tuple(result) + return result + + +def compatible_tags(): + """ + Return (pyver, abi, arch) tuples compatible with this Python. + """ + class _Version: + def __init__(self, major, minor): + self.major = major + self.major_minor = (major, minor) + self.string = ''.join((str(major), str(minor))) + + def __str__(self): + return self.string + + + versions = [ + _Version(sys.version_info.major, minor_version) + for minor_version in range(sys.version_info.minor, -1, -1) + ] + abis = [] + for suffix in _get_suffixes(): + if suffix.startswith('.abi'): + abis.append(suffix.split('.', 2)[1]) + abis.sort() + if ABI != 'none': + abis.insert(0, ABI) + abis.append('none') + result = [] + + arches = [ARCH] + if sys.platform == 'darwin': + m = re.match(r'(\w+)_(\d+)_(\d+)_(\w+)$', ARCH) + if m: + name, major, minor, arch = m.groups() + minor = int(minor) + matches = [arch] + if arch in ('i386', 'ppc'): + matches.append('fat') + if arch in ('i386', 'ppc', 'x86_64'): + matches.append('fat3') + if arch in ('ppc64', 'x86_64'): + matches.append('fat64') + if arch in ('i386', 'x86_64'): + matches.append('intel') + if arch in ('i386', 'x86_64', 'intel', 'ppc', 'ppc64'): + matches.append('universal') + while minor >= 0: + for match in matches: + s = '%s_%s_%s_%s' % (name, major, minor, match) + if s != ARCH: # already there + arches.append(s) + minor -= 1 + + # Most specific - our Python version, ABI and arch + for i, version_object in enumerate(versions): + version = str(version_object) + add_abis = [] + + if i == 0: + add_abis = abis + + if IMP_PREFIX == 'cp' and version_object.major_minor >= (3, 2): + limited_api_abi = 'abi' + str(version_object.major) + if limited_api_abi not in add_abis: + add_abis.append(limited_api_abi) + + for abi in add_abis: + for arch in arches: + result.append((''.join((IMP_PREFIX, version)), abi, arch)) + # manylinux + if abi != 'none' and sys.platform.startswith('linux'): + arch = arch.replace('linux_', '') + parts = _get_glibc_version() + if len(parts) == 2: + if parts >= (2, 5): + result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux1_%s' % arch)) + if parts >= (2, 12): + result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2010_%s' % arch)) + if parts >= (2, 17): + result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2014_%s' % arch)) + result.append((''.join( + (IMP_PREFIX, version)), abi, 'manylinux_%s_%s_%s' % (parts[0], parts[1], arch))) + + # where no ABI / arch dependency, but IMP_PREFIX dependency + for i, version_object in enumerate(versions): + version = str(version_object) + result.append((''.join((IMP_PREFIX, version)), 'none', 'any')) + if i == 0: + result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any')) + + # no IMP_PREFIX, ABI or arch dependency + for i, version_object in enumerate(versions): + version = str(version_object) + result.append((''.join(('py', version)), 'none', 'any')) + if i == 0: + result.append((''.join(('py', version[0])), 'none', 'any')) + + return set(result) + + +COMPATIBLE_TAGS = compatible_tags() + +del compatible_tags + + +def is_compatible(wheel, tags=None): + if not isinstance(wheel, Wheel): + wheel = Wheel(wheel) # assume it's a filename + result = False + if tags is None: + tags = COMPATIBLE_TAGS + for ver, abi, arch in tags: + if ver in wheel.pyver and abi in wheel.abi and arch in wheel.arch: + result = True + break + return result diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distro/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/distro/__init__.py new file mode 100644 index 000000000..7686fe85a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distro/__init__.py @@ -0,0 +1,54 @@ +from .distro import ( + NORMALIZED_DISTRO_ID, + NORMALIZED_LSB_ID, + NORMALIZED_OS_ID, + LinuxDistribution, + __version__, + build_number, + codename, + distro_release_attr, + distro_release_info, + id, + info, + like, + linux_distribution, + lsb_release_attr, + lsb_release_info, + major_version, + minor_version, + name, + os_release_attr, + os_release_info, + uname_attr, + uname_info, + version, + version_parts, +) + +__all__ = [ + "NORMALIZED_DISTRO_ID", + "NORMALIZED_LSB_ID", + "NORMALIZED_OS_ID", + "LinuxDistribution", + "build_number", + "codename", + "distro_release_attr", + "distro_release_info", + "id", + "info", + "like", + "linux_distribution", + "lsb_release_attr", + "lsb_release_info", + "major_version", + "minor_version", + "name", + "os_release_attr", + "os_release_info", + "uname_attr", + "uname_info", + "version", + "version_parts", +] + +__version__ = __version__ diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distro/__main__.py b/venv/lib/python3.8/site-packages/pip/_vendor/distro/__main__.py new file mode 100644 index 000000000..0c01d5b08 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distro/__main__.py @@ -0,0 +1,4 @@ +from .distro import main + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d85fb152c9292a5f9983b0f00ebd80d083383a6d GIT binary patch literal 1022 zcmcJNy>8S%5XXIYzVGvWemy+weOVhICQ)M zHLpOkEmdBDiXA7BE`o-Fwfxi0-!nTi>-p$;Eyu2J-+lF?;y6Fq*j+PgytZ%l#R45H zMGlpqL}e&b1uEo%OI4^+4Qf<}IyIm{O=waJTGWO%b)Z8Yc+`b1^`J+6=+giOG=w3I zU_@gW(*!0og(=NoMst{#94uo6U94ga>)601wy=#I^stLP?Bf83IKnYbaEddWU(R4p z+8S_g?Humkm29Dhb2C0UJ$wIVbqw#8VtKs&aCRz=mu9e|uGVi^>hDOW>%wWJ;?2!c zWw}=2CRa&p+Cprl&g@nQ(+M{!LJ@B$l-e}T6O?g4(u|Np5}{=zgG`Db&-EXoiZ2pV zR%qN}=SJ#UnqkBlMG^m)vL9vP)1#g!MYKo3r$j%NW;{~yj_DRv^^)xAF}p%>N^8T8 z_vJq93%&l(vhf~!PXnE2x@>P)+hK9E%cwHyj0U60;GxoOMu*`sx{My9&loU{*-~?wi{E?fx};jUE4Q38pSY5Jf_Kb@TzJWBj`DuM(2J{4B4PjQpkC z$|y-m#<{-=!pu)^^Q$CYq(_SjEoG?WBG uP?h@j&&HT&|Gsr!2o48{^7ZE_kRNm3=7c! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..042d29e6db7188e54c4721139b0b3999826d013d GIT binary patch literal 284 zcmYjLyG{f#3`}yX(+Y7vKtaF7o{MWJ5W0dA(I6U{C`$|%ZC*;kilgOg_yuadVp}SH zfeMF*#7G{?p0PF8tJMJ`yuN|iQh%?*e+U}3RQZ@lGtDh?{DyOeLW@zPBjnGFGrhQC zUGu@o)bY7-%J{jJvUVviyT!cJCYKaa{Z|#Q97_4Z(vlY6^!W(8;1KW$M}rp5aY7rS zLto%R4H@FT?1Jy(roT&wgTcNebvt5$3H=1t48NAm`UJ8m*hiApJ<_R(Ajzdef}bGp X7j7N*b9?H7ez15}^XG=>P_+CDqhwBP literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6221b19268206f91c453f414f18df3f03cab20cb GIT binary patch literal 42458 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distro/distro.py b/venv/lib/python3.8/site-packages/pip/_vendor/distro/distro.py new file mode 100644 index 000000000..78ccdfa40 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distro/distro.py @@ -0,0 +1,1403 @@ +#!/usr/bin/env python +# Copyright 2015-2021 Nir Cohen +# +# 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. + +""" +The ``distro`` package (``distro`` stands for Linux Distribution) provides +information about the Linux distribution it runs on, such as a reliable +machine-readable distro ID, or version information. + +It is the recommended replacement for Python's original +:py:func:`platform.linux_distribution` function, but it provides much more +functionality. An alternative implementation became necessary because Python +3.5 deprecated this function, and Python 3.8 removed it altogether. Its +predecessor function :py:func:`platform.dist` was already deprecated since +Python 2.6 and removed in Python 3.8. Still, there are many cases in which +access to OS distribution information is needed. See `Python issue 1322 +`_ for more information. +""" + +import argparse +import json +import logging +import os +import re +import shlex +import subprocess +import sys +import warnings +from typing import ( + Any, + Callable, + Dict, + Iterable, + Optional, + Sequence, + TextIO, + Tuple, + Type, +) + +try: + from typing import TypedDict +except ImportError: + # Python 3.7 + TypedDict = dict + +__version__ = "1.9.0" + + +class VersionDict(TypedDict): + major: str + minor: str + build_number: str + + +class InfoDict(TypedDict): + id: str + version: str + version_parts: VersionDict + like: str + codename: str + + +_UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc") +_UNIXUSRLIBDIR = os.environ.get("UNIXUSRLIBDIR", "/usr/lib") +_OS_RELEASE_BASENAME = "os-release" + +#: Translation table for normalizing the "ID" attribute defined in os-release +#: files, for use by the :func:`distro.id` method. +#: +#: * Key: Value as defined in the os-release file, translated to lower case, +#: with blanks translated to underscores. +#: +#: * Value: Normalized value. +NORMALIZED_OS_ID = { + "ol": "oracle", # Oracle Linux + "opensuse-leap": "opensuse", # Newer versions of OpenSuSE report as opensuse-leap +} + +#: Translation table for normalizing the "Distributor ID" attribute returned by +#: the lsb_release command, for use by the :func:`distro.id` method. +#: +#: * Key: Value as returned by the lsb_release command, translated to lower +#: case, with blanks translated to underscores. +#: +#: * Value: Normalized value. +NORMALIZED_LSB_ID = { + "enterpriseenterpriseas": "oracle", # Oracle Enterprise Linux 4 + "enterpriseenterpriseserver": "oracle", # Oracle Linux 5 + "redhatenterpriseworkstation": "rhel", # RHEL 6, 7 Workstation + "redhatenterpriseserver": "rhel", # RHEL 6, 7 Server + "redhatenterprisecomputenode": "rhel", # RHEL 6 ComputeNode +} + +#: Translation table for normalizing the distro ID derived from the file name +#: of distro release files, for use by the :func:`distro.id` method. +#: +#: * Key: Value as derived from the file name of a distro release file, +#: translated to lower case, with blanks translated to underscores. +#: +#: * Value: Normalized value. +NORMALIZED_DISTRO_ID = { + "redhat": "rhel", # RHEL 6.x, 7.x +} + +# Pattern for content of distro release file (reversed) +_DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile( + r"(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)" +) + +# Pattern for base file name of distro release file +_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$") + +# Base file names to be looked up for if _UNIXCONFDIR is not readable. +_DISTRO_RELEASE_BASENAMES = [ + "SuSE-release", + "altlinux-release", + "arch-release", + "base-release", + "centos-release", + "fedora-release", + "gentoo-release", + "mageia-release", + "mandrake-release", + "mandriva-release", + "mandrivalinux-release", + "manjaro-release", + "oracle-release", + "redhat-release", + "rocky-release", + "sl-release", + "slackware-version", +] + +# Base file names to be ignored when searching for distro release file +_DISTRO_RELEASE_IGNORE_BASENAMES = ( + "debian_version", + "lsb-release", + "oem-release", + _OS_RELEASE_BASENAME, + "system-release", + "plesk-release", + "iredmail-release", + "board-release", + "ec2_version", +) + + +def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]: + """ + .. deprecated:: 1.6.0 + + :func:`distro.linux_distribution()` is deprecated. It should only be + used as a compatibility shim with Python's + :py:func:`platform.linux_distribution()`. Please use :func:`distro.id`, + :func:`distro.version` and :func:`distro.name` instead. + + Return information about the current OS distribution as a tuple + ``(id_name, version, codename)`` with items as follows: + + * ``id_name``: If *full_distribution_name* is false, the result of + :func:`distro.id`. Otherwise, the result of :func:`distro.name`. + + * ``version``: The result of :func:`distro.version`. + + * ``codename``: The extra item (usually in parentheses) after the + os-release version number, or the result of :func:`distro.codename`. + + The interface of this function is compatible with the original + :py:func:`platform.linux_distribution` function, supporting a subset of + its parameters. + + The data it returns may not exactly be the same, because it uses more data + sources than the original function, and that may lead to different data if + the OS distribution is not consistent across multiple data sources it + provides (there are indeed such distributions ...). + + Another reason for differences is the fact that the :func:`distro.id` + method normalizes the distro ID string to a reliable machine-readable value + for a number of popular OS distributions. + """ + warnings.warn( + "distro.linux_distribution() is deprecated. It should only be used as a " + "compatibility shim with Python's platform.linux_distribution(). Please use " + "distro.id(), distro.version() and distro.name() instead.", + DeprecationWarning, + stacklevel=2, + ) + return _distro.linux_distribution(full_distribution_name) + + +def id() -> str: + """ + Return the distro ID of the current distribution, as a + machine-readable string. + + For a number of OS distributions, the returned distro ID value is + *reliable*, in the sense that it is documented and that it does not change + across releases of the distribution. + + This package maintains the following reliable distro ID values: + + ============== ========================================= + Distro ID Distribution + ============== ========================================= + "ubuntu" Ubuntu + "debian" Debian + "rhel" RedHat Enterprise Linux + "centos" CentOS + "fedora" Fedora + "sles" SUSE Linux Enterprise Server + "opensuse" openSUSE + "amzn" Amazon Linux + "arch" Arch Linux + "buildroot" Buildroot + "cloudlinux" CloudLinux OS + "exherbo" Exherbo Linux + "gentoo" GenToo Linux + "ibm_powerkvm" IBM PowerKVM + "kvmibm" KVM for IBM z Systems + "linuxmint" Linux Mint + "mageia" Mageia + "mandriva" Mandriva Linux + "parallels" Parallels + "pidora" Pidora + "raspbian" Raspbian + "oracle" Oracle Linux (and Oracle Enterprise Linux) + "scientific" Scientific Linux + "slackware" Slackware + "xenserver" XenServer + "openbsd" OpenBSD + "netbsd" NetBSD + "freebsd" FreeBSD + "midnightbsd" MidnightBSD + "rocky" Rocky Linux + "aix" AIX + "guix" Guix System + "altlinux" ALT Linux + ============== ========================================= + + If you have a need to get distros for reliable IDs added into this set, + or if you find that the :func:`distro.id` function returns a different + distro ID for one of the listed distros, please create an issue in the + `distro issue tracker`_. + + **Lookup hierarchy and transformations:** + + First, the ID is obtained from the following sources, in the specified + order. The first available and non-empty value is used: + + * the value of the "ID" attribute of the os-release file, + + * the value of the "Distributor ID" attribute returned by the lsb_release + command, + + * the first part of the file name of the distro release file, + + The so determined ID value then passes the following transformations, + before it is returned by this method: + + * it is translated to lower case, + + * blanks (which should not be there anyway) are translated to underscores, + + * a normalization of the ID is performed, based upon + `normalization tables`_. The purpose of this normalization is to ensure + that the ID is as reliable as possible, even across incompatible changes + in the OS distributions. A common reason for an incompatible change is + the addition of an os-release file, or the addition of the lsb_release + command, with ID values that differ from what was previously determined + from the distro release file name. + """ + return _distro.id() + + +def name(pretty: bool = False) -> str: + """ + Return the name of the current OS distribution, as a human-readable + string. + + If *pretty* is false, the name is returned without version or codename. + (e.g. "CentOS Linux") + + If *pretty* is true, the version and codename are appended. + (e.g. "CentOS Linux 7.1.1503 (Core)") + + **Lookup hierarchy:** + + The name is obtained from the following sources, in the specified order. + The first available and non-empty value is used: + + * If *pretty* is false: + + - the value of the "NAME" attribute of the os-release file, + + - the value of the "Distributor ID" attribute returned by the lsb_release + command, + + - the value of the "" field of the distro release file. + + * If *pretty* is true: + + - the value of the "PRETTY_NAME" attribute of the os-release file, + + - the value of the "Description" attribute returned by the lsb_release + command, + + - the value of the "" field of the distro release file, appended + with the value of the pretty version ("" and "" + fields) of the distro release file, if available. + """ + return _distro.name(pretty) + + +def version(pretty: bool = False, best: bool = False) -> str: + """ + Return the version of the current OS distribution, as a human-readable + string. + + If *pretty* is false, the version is returned without codename (e.g. + "7.0"). + + If *pretty* is true, the codename in parenthesis is appended, if the + codename is non-empty (e.g. "7.0 (Maipo)"). + + Some distributions provide version numbers with different precisions in + the different sources of distribution information. Examining the different + sources in a fixed priority order does not always yield the most precise + version (e.g. for Debian 8.2, or CentOS 7.1). + + Some other distributions may not provide this kind of information. In these + cases, an empty string would be returned. This behavior can be observed + with rolling releases distributions (e.g. Arch Linux). + + The *best* parameter can be used to control the approach for the returned + version: + + If *best* is false, the first non-empty version number in priority order of + the examined sources is returned. + + If *best* is true, the most precise version number out of all examined + sources is returned. + + **Lookup hierarchy:** + + In all cases, the version number is obtained from the following sources. + If *best* is false, this order represents the priority order: + + * the value of the "VERSION_ID" attribute of the os-release file, + * the value of the "Release" attribute returned by the lsb_release + command, + * the version number parsed from the "" field of the first line + of the distro release file, + * the version number parsed from the "PRETTY_NAME" attribute of the + os-release file, if it follows the format of the distro release files. + * the version number parsed from the "Description" attribute returned by + the lsb_release command, if it follows the format of the distro release + files. + """ + return _distro.version(pretty, best) + + +def version_parts(best: bool = False) -> Tuple[str, str, str]: + """ + Return the version of the current OS distribution as a tuple + ``(major, minor, build_number)`` with items as follows: + + * ``major``: The result of :func:`distro.major_version`. + + * ``minor``: The result of :func:`distro.minor_version`. + + * ``build_number``: The result of :func:`distro.build_number`. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.version_parts(best) + + +def major_version(best: bool = False) -> str: + """ + Return the major version of the current OS distribution, as a string, + if provided. + Otherwise, the empty string is returned. The major version is the first + part of the dot-separated version string. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.major_version(best) + + +def minor_version(best: bool = False) -> str: + """ + Return the minor version of the current OS distribution, as a string, + if provided. + Otherwise, the empty string is returned. The minor version is the second + part of the dot-separated version string. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.minor_version(best) + + +def build_number(best: bool = False) -> str: + """ + Return the build number of the current OS distribution, as a string, + if provided. + Otherwise, the empty string is returned. The build number is the third part + of the dot-separated version string. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.build_number(best) + + +def like() -> str: + """ + Return a space-separated list of distro IDs of distributions that are + closely related to the current OS distribution in regards to packaging + and programming interfaces, for example distributions the current + distribution is a derivative from. + + **Lookup hierarchy:** + + This information item is only provided by the os-release file. + For details, see the description of the "ID_LIKE" attribute in the + `os-release man page + `_. + """ + return _distro.like() + + +def codename() -> str: + """ + Return the codename for the release of the current OS distribution, + as a string. + + If the distribution does not have a codename, an empty string is returned. + + Note that the returned codename is not always really a codename. For + example, openSUSE returns "x86_64". This function does not handle such + cases in any special way and just returns the string it finds, if any. + + **Lookup hierarchy:** + + * the codename within the "VERSION" attribute of the os-release file, if + provided, + + * the value of the "Codename" attribute returned by the lsb_release + command, + + * the value of the "" field of the distro release file. + """ + return _distro.codename() + + +def info(pretty: bool = False, best: bool = False) -> InfoDict: + """ + Return certain machine-readable information items about the current OS + distribution in a dictionary, as shown in the following example: + + .. sourcecode:: python + + { + 'id': 'rhel', + 'version': '7.0', + 'version_parts': { + 'major': '7', + 'minor': '0', + 'build_number': '' + }, + 'like': 'fedora', + 'codename': 'Maipo' + } + + The dictionary structure and keys are always the same, regardless of which + information items are available in the underlying data sources. The values + for the various keys are as follows: + + * ``id``: The result of :func:`distro.id`. + + * ``version``: The result of :func:`distro.version`. + + * ``version_parts -> major``: The result of :func:`distro.major_version`. + + * ``version_parts -> minor``: The result of :func:`distro.minor_version`. + + * ``version_parts -> build_number``: The result of + :func:`distro.build_number`. + + * ``like``: The result of :func:`distro.like`. + + * ``codename``: The result of :func:`distro.codename`. + + For a description of the *pretty* and *best* parameters, see the + :func:`distro.version` method. + """ + return _distro.info(pretty, best) + + +def os_release_info() -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information items + from the os-release file data source of the current OS distribution. + + See `os-release file`_ for details about these information items. + """ + return _distro.os_release_info() + + +def lsb_release_info() -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information items + from the lsb_release command data source of the current OS distribution. + + See `lsb_release command output`_ for details about these information + items. + """ + return _distro.lsb_release_info() + + +def distro_release_info() -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information items + from the distro release file data source of the current OS distribution. + + See `distro release file`_ for details about these information items. + """ + return _distro.distro_release_info() + + +def uname_info() -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information items + from the distro release file data source of the current OS distribution. + """ + return _distro.uname_info() + + +def os_release_attr(attribute: str) -> str: + """ + Return a single named information item from the os-release file data source + of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + + See `os-release file`_ for details about these information items. + """ + return _distro.os_release_attr(attribute) + + +def lsb_release_attr(attribute: str) -> str: + """ + Return a single named information item from the lsb_release command output + data source of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + + See `lsb_release command output`_ for details about these information + items. + """ + return _distro.lsb_release_attr(attribute) + + +def distro_release_attr(attribute: str) -> str: + """ + Return a single named information item from the distro release file + data source of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + + See `distro release file`_ for details about these information items. + """ + return _distro.distro_release_attr(attribute) + + +def uname_attr(attribute: str) -> str: + """ + Return a single named information item from the distro release file + data source of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + """ + return _distro.uname_attr(attribute) + + +try: + from functools import cached_property +except ImportError: + # Python < 3.8 + class cached_property: # type: ignore + """A version of @property which caches the value. On access, it calls the + underlying function and sets the value in `__dict__` so future accesses + will not re-call the property. + """ + + def __init__(self, f: Callable[[Any], Any]) -> None: + self._fname = f.__name__ + self._f = f + + def __get__(self, obj: Any, owner: Type[Any]) -> Any: + assert obj is not None, f"call {self._fname} on an instance" + ret = obj.__dict__[self._fname] = self._f(obj) + return ret + + +class LinuxDistribution: + """ + Provides information about a OS distribution. + + This package creates a private module-global instance of this class with + default initialization arguments, that is used by the + `consolidated accessor functions`_ and `single source accessor functions`_. + By using default initialization arguments, that module-global instance + returns data about the current OS distribution (i.e. the distro this + package runs on). + + Normally, it is not necessary to create additional instances of this class. + However, in situations where control is needed over the exact data sources + that are used, instances of this class can be created with a specific + distro release file, or a specific os-release file, or without invoking the + lsb_release command. + """ + + def __init__( + self, + include_lsb: Optional[bool] = None, + os_release_file: str = "", + distro_release_file: str = "", + include_uname: Optional[bool] = None, + root_dir: Optional[str] = None, + include_oslevel: Optional[bool] = None, + ) -> None: + """ + The initialization method of this class gathers information from the + available data sources, and stores that in private instance attributes. + Subsequent access to the information items uses these private instance + attributes, so that the data sources are read only once. + + Parameters: + + * ``include_lsb`` (bool): Controls whether the + `lsb_release command output`_ is included as a data source. + + If the lsb_release command is not available in the program execution + path, the data source for the lsb_release command will be empty. + + * ``os_release_file`` (string): The path name of the + `os-release file`_ that is to be used as a data source. + + An empty string (the default) will cause the default path name to + be used (see `os-release file`_ for details). + + If the specified or defaulted os-release file does not exist, the + data source for the os-release file will be empty. + + * ``distro_release_file`` (string): The path name of the + `distro release file`_ that is to be used as a data source. + + An empty string (the default) will cause a default search algorithm + to be used (see `distro release file`_ for details). + + If the specified distro release file does not exist, or if no default + distro release file can be found, the data source for the distro + release file will be empty. + + * ``include_uname`` (bool): Controls whether uname command output is + included as a data source. If the uname command is not available in + the program execution path the data source for the uname command will + be empty. + + * ``root_dir`` (string): The absolute path to the root directory to use + to find distro-related information files. Note that ``include_*`` + parameters must not be enabled in combination with ``root_dir``. + + * ``include_oslevel`` (bool): Controls whether (AIX) oslevel command + output is included as a data source. If the oslevel command is not + available in the program execution path the data source will be + empty. + + Public instance attributes: + + * ``os_release_file`` (string): The path name of the + `os-release file`_ that is actually used as a data source. The + empty string if no distro release file is used as a data source. + + * ``distro_release_file`` (string): The path name of the + `distro release file`_ that is actually used as a data source. The + empty string if no distro release file is used as a data source. + + * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter. + This controls whether the lsb information will be loaded. + + * ``include_uname`` (bool): The result of the ``include_uname`` + parameter. This controls whether the uname information will + be loaded. + + * ``include_oslevel`` (bool): The result of the ``include_oslevel`` + parameter. This controls whether (AIX) oslevel information will be + loaded. + + * ``root_dir`` (string): The result of the ``root_dir`` parameter. + The absolute path to the root directory to use to find distro-related + information files. + + Raises: + + * :py:exc:`ValueError`: Initialization parameters combination is not + supported. + + * :py:exc:`OSError`: Some I/O issue with an os-release file or distro + release file. + + * :py:exc:`UnicodeError`: A data source has unexpected characters or + uses an unexpected encoding. + """ + self.root_dir = root_dir + self.etc_dir = os.path.join(root_dir, "etc") if root_dir else _UNIXCONFDIR + self.usr_lib_dir = ( + os.path.join(root_dir, "usr/lib") if root_dir else _UNIXUSRLIBDIR + ) + + if os_release_file: + self.os_release_file = os_release_file + else: + etc_dir_os_release_file = os.path.join(self.etc_dir, _OS_RELEASE_BASENAME) + usr_lib_os_release_file = os.path.join( + self.usr_lib_dir, _OS_RELEASE_BASENAME + ) + + # NOTE: The idea is to respect order **and** have it set + # at all times for API backwards compatibility. + if os.path.isfile(etc_dir_os_release_file) or not os.path.isfile( + usr_lib_os_release_file + ): + self.os_release_file = etc_dir_os_release_file + else: + self.os_release_file = usr_lib_os_release_file + + self.distro_release_file = distro_release_file or "" # updated later + + is_root_dir_defined = root_dir is not None + if is_root_dir_defined and (include_lsb or include_uname or include_oslevel): + raise ValueError( + "Including subprocess data sources from specific root_dir is disallowed" + " to prevent false information" + ) + self.include_lsb = ( + include_lsb if include_lsb is not None else not is_root_dir_defined + ) + self.include_uname = ( + include_uname if include_uname is not None else not is_root_dir_defined + ) + self.include_oslevel = ( + include_oslevel if include_oslevel is not None else not is_root_dir_defined + ) + + def __repr__(self) -> str: + """Return repr of all info""" + return ( + "LinuxDistribution(" + "os_release_file={self.os_release_file!r}, " + "distro_release_file={self.distro_release_file!r}, " + "include_lsb={self.include_lsb!r}, " + "include_uname={self.include_uname!r}, " + "include_oslevel={self.include_oslevel!r}, " + "root_dir={self.root_dir!r}, " + "_os_release_info={self._os_release_info!r}, " + "_lsb_release_info={self._lsb_release_info!r}, " + "_distro_release_info={self._distro_release_info!r}, " + "_uname_info={self._uname_info!r}, " + "_oslevel_info={self._oslevel_info!r})".format(self=self) + ) + + def linux_distribution( + self, full_distribution_name: bool = True + ) -> Tuple[str, str, str]: + """ + Return information about the OS distribution that is compatible + with Python's :func:`platform.linux_distribution`, supporting a subset + of its parameters. + + For details, see :func:`distro.linux_distribution`. + """ + return ( + self.name() if full_distribution_name else self.id(), + self.version(), + self._os_release_info.get("release_codename") or self.codename(), + ) + + def id(self) -> str: + """Return the distro ID of the OS distribution, as a string. + + For details, see :func:`distro.id`. + """ + + def normalize(distro_id: str, table: Dict[str, str]) -> str: + distro_id = distro_id.lower().replace(" ", "_") + return table.get(distro_id, distro_id) + + distro_id = self.os_release_attr("id") + if distro_id: + return normalize(distro_id, NORMALIZED_OS_ID) + + distro_id = self.lsb_release_attr("distributor_id") + if distro_id: + return normalize(distro_id, NORMALIZED_LSB_ID) + + distro_id = self.distro_release_attr("id") + if distro_id: + return normalize(distro_id, NORMALIZED_DISTRO_ID) + + distro_id = self.uname_attr("id") + if distro_id: + return normalize(distro_id, NORMALIZED_DISTRO_ID) + + return "" + + def name(self, pretty: bool = False) -> str: + """ + Return the name of the OS distribution, as a string. + + For details, see :func:`distro.name`. + """ + name = ( + self.os_release_attr("name") + or self.lsb_release_attr("distributor_id") + or self.distro_release_attr("name") + or self.uname_attr("name") + ) + if pretty: + name = self.os_release_attr("pretty_name") or self.lsb_release_attr( + "description" + ) + if not name: + name = self.distro_release_attr("name") or self.uname_attr("name") + version = self.version(pretty=True) + if version: + name = f"{name} {version}" + return name or "" + + def version(self, pretty: bool = False, best: bool = False) -> str: + """ + Return the version of the OS distribution, as a string. + + For details, see :func:`distro.version`. + """ + versions = [ + self.os_release_attr("version_id"), + self.lsb_release_attr("release"), + self.distro_release_attr("version_id"), + self._parse_distro_release_content(self.os_release_attr("pretty_name")).get( + "version_id", "" + ), + self._parse_distro_release_content( + self.lsb_release_attr("description") + ).get("version_id", ""), + self.uname_attr("release"), + ] + if self.uname_attr("id").startswith("aix"): + # On AIX platforms, prefer oslevel command output. + versions.insert(0, self.oslevel_info()) + elif self.id() == "debian" or "debian" in self.like().split(): + # On Debian-like, add debian_version file content to candidates list. + versions.append(self._debian_version) + version = "" + if best: + # This algorithm uses the last version in priority order that has + # the best precision. If the versions are not in conflict, that + # does not matter; otherwise, using the last one instead of the + # first one might be considered a surprise. + for v in versions: + if v.count(".") > version.count(".") or version == "": + version = v + else: + for v in versions: + if v != "": + version = v + break + if pretty and version and self.codename(): + version = f"{version} ({self.codename()})" + return version + + def version_parts(self, best: bool = False) -> Tuple[str, str, str]: + """ + Return the version of the OS distribution, as a tuple of version + numbers. + + For details, see :func:`distro.version_parts`. + """ + version_str = self.version(best=best) + if version_str: + version_regex = re.compile(r"(\d+)\.?(\d+)?\.?(\d+)?") + matches = version_regex.match(version_str) + if matches: + major, minor, build_number = matches.groups() + return major, minor or "", build_number or "" + return "", "", "" + + def major_version(self, best: bool = False) -> str: + """ + Return the major version number of the current distribution. + + For details, see :func:`distro.major_version`. + """ + return self.version_parts(best)[0] + + def minor_version(self, best: bool = False) -> str: + """ + Return the minor version number of the current distribution. + + For details, see :func:`distro.minor_version`. + """ + return self.version_parts(best)[1] + + def build_number(self, best: bool = False) -> str: + """ + Return the build number of the current distribution. + + For details, see :func:`distro.build_number`. + """ + return self.version_parts(best)[2] + + def like(self) -> str: + """ + Return the IDs of distributions that are like the OS distribution. + + For details, see :func:`distro.like`. + """ + return self.os_release_attr("id_like") or "" + + def codename(self) -> str: + """ + Return the codename of the OS distribution. + + For details, see :func:`distro.codename`. + """ + try: + # Handle os_release specially since distros might purposefully set + # this to empty string to have no codename + return self._os_release_info["codename"] + except KeyError: + return ( + self.lsb_release_attr("codename") + or self.distro_release_attr("codename") + or "" + ) + + def info(self, pretty: bool = False, best: bool = False) -> InfoDict: + """ + Return certain machine-readable information about the OS + distribution. + + For details, see :func:`distro.info`. + """ + return InfoDict( + id=self.id(), + version=self.version(pretty, best), + version_parts=VersionDict( + major=self.major_version(best), + minor=self.minor_version(best), + build_number=self.build_number(best), + ), + like=self.like(), + codename=self.codename(), + ) + + def os_release_info(self) -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information + items from the os-release file data source of the OS distribution. + + For details, see :func:`distro.os_release_info`. + """ + return self._os_release_info + + def lsb_release_info(self) -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information + items from the lsb_release command data source of the OS + distribution. + + For details, see :func:`distro.lsb_release_info`. + """ + return self._lsb_release_info + + def distro_release_info(self) -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information + items from the distro release file data source of the OS + distribution. + + For details, see :func:`distro.distro_release_info`. + """ + return self._distro_release_info + + def uname_info(self) -> Dict[str, str]: + """ + Return a dictionary containing key-value pairs for the information + items from the uname command data source of the OS distribution. + + For details, see :func:`distro.uname_info`. + """ + return self._uname_info + + def oslevel_info(self) -> str: + """ + Return AIX' oslevel command output. + """ + return self._oslevel_info + + def os_release_attr(self, attribute: str) -> str: + """ + Return a single named information item from the os-release file data + source of the OS distribution. + + For details, see :func:`distro.os_release_attr`. + """ + return self._os_release_info.get(attribute, "") + + def lsb_release_attr(self, attribute: str) -> str: + """ + Return a single named information item from the lsb_release command + output data source of the OS distribution. + + For details, see :func:`distro.lsb_release_attr`. + """ + return self._lsb_release_info.get(attribute, "") + + def distro_release_attr(self, attribute: str) -> str: + """ + Return a single named information item from the distro release file + data source of the OS distribution. + + For details, see :func:`distro.distro_release_attr`. + """ + return self._distro_release_info.get(attribute, "") + + def uname_attr(self, attribute: str) -> str: + """ + Return a single named information item from the uname command + output data source of the OS distribution. + + For details, see :func:`distro.uname_attr`. + """ + return self._uname_info.get(attribute, "") + + @cached_property + def _os_release_info(self) -> Dict[str, str]: + """ + Get the information items from the specified os-release file. + + Returns: + A dictionary containing all information items. + """ + if os.path.isfile(self.os_release_file): + with open(self.os_release_file, encoding="utf-8") as release_file: + return self._parse_os_release_content(release_file) + return {} + + @staticmethod + def _parse_os_release_content(lines: TextIO) -> Dict[str, str]: + """ + Parse the lines of an os-release file. + + Parameters: + + * lines: Iterable through the lines in the os-release file. + Each line must be a unicode string or a UTF-8 encoded byte + string. + + Returns: + A dictionary containing all information items. + """ + props = {} + lexer = shlex.shlex(lines, posix=True) + lexer.whitespace_split = True + + tokens = list(lexer) + for token in tokens: + # At this point, all shell-like parsing has been done (i.e. + # comments processed, quotes and backslash escape sequences + # processed, multi-line values assembled, trailing newlines + # stripped, etc.), so the tokens are now either: + # * variable assignments: var=value + # * commands or their arguments (not allowed in os-release) + # Ignore any tokens that are not variable assignments + if "=" in token: + k, v = token.split("=", 1) + props[k.lower()] = v + + if "version" in props: + # extract release codename (if any) from version attribute + match = re.search(r"\((\D+)\)|,\s*(\D+)", props["version"]) + if match: + release_codename = match.group(1) or match.group(2) + props["codename"] = props["release_codename"] = release_codename + + if "version_codename" in props: + # os-release added a version_codename field. Use that in + # preference to anything else Note that some distros purposefully + # do not have code names. They should be setting + # version_codename="" + props["codename"] = props["version_codename"] + elif "ubuntu_codename" in props: + # Same as above but a non-standard field name used on older Ubuntus + props["codename"] = props["ubuntu_codename"] + + return props + + @cached_property + def _lsb_release_info(self) -> Dict[str, str]: + """ + Get the information items from the lsb_release command output. + + Returns: + A dictionary containing all information items. + """ + if not self.include_lsb: + return {} + try: + cmd = ("lsb_release", "-a") + stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) + # Command not found or lsb_release returned error + except (OSError, subprocess.CalledProcessError): + return {} + content = self._to_str(stdout).splitlines() + return self._parse_lsb_release_content(content) + + @staticmethod + def _parse_lsb_release_content(lines: Iterable[str]) -> Dict[str, str]: + """ + Parse the output of the lsb_release command. + + Parameters: + + * lines: Iterable through the lines of the lsb_release output. + Each line must be a unicode string or a UTF-8 encoded byte + string. + + Returns: + A dictionary containing all information items. + """ + props = {} + for line in lines: + kv = line.strip("\n").split(":", 1) + if len(kv) != 2: + # Ignore lines without colon. + continue + k, v = kv + props.update({k.replace(" ", "_").lower(): v.strip()}) + return props + + @cached_property + def _uname_info(self) -> Dict[str, str]: + if not self.include_uname: + return {} + try: + cmd = ("uname", "-rs") + stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) + except OSError: + return {} + content = self._to_str(stdout).splitlines() + return self._parse_uname_content(content) + + @cached_property + def _oslevel_info(self) -> str: + if not self.include_oslevel: + return "" + try: + stdout = subprocess.check_output("oslevel", stderr=subprocess.DEVNULL) + except (OSError, subprocess.CalledProcessError): + return "" + return self._to_str(stdout).strip() + + @cached_property + def _debian_version(self) -> str: + try: + with open( + os.path.join(self.etc_dir, "debian_version"), encoding="ascii" + ) as fp: + return fp.readline().rstrip() + except FileNotFoundError: + return "" + + @staticmethod + def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]: + if not lines: + return {} + props = {} + match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip()) + if match: + name, version = match.groups() + + # This is to prevent the Linux kernel version from + # appearing as the 'best' version on otherwise + # identifiable distributions. + if name == "Linux": + return {} + props["id"] = name.lower() + props["name"] = name + props["release"] = version + return props + + @staticmethod + def _to_str(bytestring: bytes) -> str: + encoding = sys.getfilesystemencoding() + return bytestring.decode(encoding) + + @cached_property + def _distro_release_info(self) -> Dict[str, str]: + """ + Get the information items from the specified distro release file. + + Returns: + A dictionary containing all information items. + """ + if self.distro_release_file: + # If it was specified, we use it and parse what we can, even if + # its file name or content does not match the expected pattern. + distro_info = self._parse_distro_release_file(self.distro_release_file) + basename = os.path.basename(self.distro_release_file) + # The file name pattern for user-specified distro release files + # is somewhat more tolerant (compared to when searching for the + # file), because we want to use what was specified as best as + # possible. + match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) + else: + try: + basenames = [ + basename + for basename in os.listdir(self.etc_dir) + if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES + and os.path.isfile(os.path.join(self.etc_dir, basename)) + ] + # We sort for repeatability in cases where there are multiple + # distro specific files; e.g. CentOS, Oracle, Enterprise all + # containing `redhat-release` on top of their own. + basenames.sort() + except OSError: + # This may occur when /etc is not readable but we can't be + # sure about the *-release files. Check common entries of + # /etc for information. If they turn out to not be there the + # error is handled in `_parse_distro_release_file()`. + basenames = _DISTRO_RELEASE_BASENAMES + for basename in basenames: + match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) + if match is None: + continue + filepath = os.path.join(self.etc_dir, basename) + distro_info = self._parse_distro_release_file(filepath) + # The name is always present if the pattern matches. + if "name" not in distro_info: + continue + self.distro_release_file = filepath + break + else: # the loop didn't "break": no candidate. + return {} + + if match is not None: + distro_info["id"] = match.group(1) + + # CloudLinux < 7: manually enrich info with proper id. + if "cloudlinux" in distro_info.get("name", "").lower(): + distro_info["id"] = "cloudlinux" + + return distro_info + + def _parse_distro_release_file(self, filepath: str) -> Dict[str, str]: + """ + Parse a distro release file. + + Parameters: + + * filepath: Path name of the distro release file. + + Returns: + A dictionary containing all information items. + """ + try: + with open(filepath, encoding="utf-8") as fp: + # Only parse the first line. For instance, on SLES there + # are multiple lines. We don't want them... + return self._parse_distro_release_content(fp.readline()) + except OSError: + # Ignore not being able to read a specific, seemingly version + # related file. + # See https://github.com/python-distro/distro/issues/162 + return {} + + @staticmethod + def _parse_distro_release_content(line: str) -> Dict[str, str]: + """ + Parse a line from a distro release file. + + Parameters: + * line: Line from the distro release file. Must be a unicode string + or a UTF-8 encoded byte string. + + Returns: + A dictionary containing all information items. + """ + matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(line.strip()[::-1]) + distro_info = {} + if matches: + # regexp ensures non-None + distro_info["name"] = matches.group(3)[::-1] + if matches.group(2): + distro_info["version_id"] = matches.group(2)[::-1] + if matches.group(1): + distro_info["codename"] = matches.group(1)[::-1] + elif line: + distro_info["name"] = line.strip() + return distro_info + + +_distro = LinuxDistribution() + + +def main() -> None: + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + logger.addHandler(logging.StreamHandler(sys.stdout)) + + parser = argparse.ArgumentParser(description="OS distro info tool") + parser.add_argument( + "--json", "-j", help="Output in machine readable format", action="store_true" + ) + + parser.add_argument( + "--root-dir", + "-r", + type=str, + dest="root_dir", + help="Path to the root filesystem directory (defaults to /)", + ) + + args = parser.parse_args() + + if args.root_dir: + dist = LinuxDistribution( + include_lsb=False, + include_uname=False, + include_oslevel=False, + root_dir=args.root_dir, + ) + else: + dist = _distro + + if args.json: + logger.info(json.dumps(dist.info(), indent=4, sort_keys=True)) + else: + logger.info("Name: %s", dist.name(pretty=True)) + distribution_version = dist.version(pretty=True) + logger.info("Version: %s", distribution_version) + distribution_codename = dist.codename() + logger.info("Codename: %s", distribution_codename) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distro/py.typed b/venv/lib/python3.8/site-packages/pip/_vendor/distro/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__init__.py new file mode 100644 index 000000000..cfdc030a7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__init__.py @@ -0,0 +1,45 @@ +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain +from .package_data import __version__ + +__all__ = [ + "__version__", + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fe298025ae5cba94c41cffd197f01a519a36802 GIT binary patch literal 947 zcmbu8&2G~`5PqLfD^lRg-{O&iM9NBX1qIp9={t72Z38(zGfWSLGaBhf3Ag7p1ZsJYZzjOBz(#7DsYPmQLfmN(7()K82LQwiSFb{od9zPG+PD{-2Bknnh z>;excbRkhy*=x2bv*NbeX|*oS*4GAc!*T8UneWkL2ULel@Xg(pv(qP8$tzsn1$IYy tk-0`I(8m2%ie;4gIid8lxV8PKQp1hFXMSYMQ-?Q;G)&vA@Sm<9zW_6b`E&pP literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b99008f0298c6b655065948884c13d0020513faf GIT binary patch literal 3336 zcmcIm&2Jk;6yMozJC2h&Y0{KJ(@+5}BrYw`BGHo4RHzaP5~Y9^6>IGoC!4O1n%p3h&<2HBhYupjqT5;R(ipX(!4mmmGj3H+nIe9*U zoDt+4K~4!d1wM+LQBg$R#J1^8Ag{>BkT<4ylgOJCM}>*r0s`( zO@z|sqTb>{+9Jxn9mVey@Q?2o9^Jk7i-p2R><0UpL=rWrI zmZ+XGhc=J)E~funb|MGFd2uMbgVKH|3<5zoDiqiQlk42T=ykb?$g^bWdQ=BNAn?O$ zD*RyA9AHOUjR0d9#z-ew(0|Az%4G`kzhUQPnC~Lqci5K6%`Gc6;hWUt)(l22w`t6+ zOIlP)jacz7X*7c*E7}(3wJj$#m$X}Fw%pY2nu(RV+qf3fQU~s)4tFnTIKxS7;#2=l zjoV!(wRc!*O>0{@C9^VFiX#Nu%3DcX1tG1t9R>-evvwOp032G25G2jDRuGl*Fh!ibc!W1eW~&?*%K`>jKp{@c zQF@6xVS*tXY~sRdj#tQ-RfU=7;3|vNTBp$vP|ZT=B(5EwMWXG+TApFV;Yc;zrV+&; z$unvcS%GbyI*5dY{cA6mbMP9j3@3=ZPUHdw5oSh37SrC_KCS@N6f}5T1WUJm-3NcK7hS&Yf;)|G52uO2hB2KV!gM}}gD6h?s4moz!dvvu+e9dz07ZM2G0WIH9MN!wCcR7K zJtFf&=r;87J^WJk8z6$R+W@^;TD^k2_yZ)v=*{adMX-JV!M(%ZLvTNBA3!jnF!PzB zCPVQo^*$srFn5rV-$%hBwmyJqd;q|dLDVF=+57oe)k(T6+(w+=&bGE@D2k~&LAWO_*MFvJ%UJeYDi_Y3J$te2z{mK%Dj3vlC`-ZM zYRZxu?Biue3bKH>!tMM~ZD)Uk9Q3oCleQ{FVwqC~$Mp#7CAvTddQ<`eIz*sN1?4cy zj2WDSLhD#8e5kKh8=a_5#chc5V@mD>S4ycmv+>ol_itE4NqGh?<45XvQP{~w8#6s9 z%o)hdiUG6U0+B_dx;v->$?T)JlTN+WYzLvpf|87v$3Ig!U+{xDTe)-SgkDHI=W2%i wPf6t>E$xC>31S51Vd#+VCs}E8nGRmL?xO3H`UyR6lqO4arPGtMo|-;J zUm&m0bDu)jp7IJkb>!3#0)YxNe;UotH*-E5_86U?zeIV&8T;*($0sD^1Ks8`5y>R4 zSk0Hb4weCDGLT^v*3mMm<7LblB+!x3k3hyU`5rC15I_ggi{LH2Z0#M{C3_FJOl6P! z`f@;eU#1H-9^O+1HV#}kRjrH9RjJiImE=u{ghESj2`JCW5?A>ODwiPGHLAk>6s=BJzV~Em*54-bHa}| z=%IN&mycm--naZ4myG5U%x~V!PUq9?{r>)8CN(sf(l%S$vML+MR9=HYFquJZb)l;< zcdzH#&T3PEhZ5vRM2%GPOA7t8+V1YZFK}1vE0V_pm)O>n>JmLh=oivOg+_R2-J|Ei x#-fW?t%YU_p-a7-BjzpofaoJVO6jMLTa>y-wU+A&j{Vkbw`CNJ!cp`l`U_qdublt@ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/core.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/core.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b9b88ae6621a0ea0b2fa3a9bae9e1c4c4064274 GIT binary patch literal 9914 zcmbVSTW}mzdhYvNG#ZU1Tb7N12Fzu^k_2gfuS|0CAFv3GdkVl z%h0o-5J~MrYU6B3g%D>WQCq2yhg2R?wG>JADGytlJZW9?U^0KPhs!fIDAtnp? z{&QwVGh=(R?8tNGT>tZ*|NQsk^TWeg1)q=qdD{E_Aw~I=8oi%18prVs$2CP^3R8V$ zLH?=>s%UEq8h$ljUo{qtRdc~q6{a)8w^maNDN#55^lD}ygSxelWvPWBSKY|5^rpHn zd`e*%mVHNISvS9~FN~l)#Bylo+!3@3Xb-bI+Ii95f%XV1pj{B{QFqK8V>_;EY?O_? zV=U}+cj0fG?YyqDU2GgZyV0ItyD?*e?Lm7FR_qe(z3dLO_qwJ#;qF?}_9$4l5A*J1 zccK4|-hPbS4Tv_o2P6B~v$O~L?nB>xcKJ_qJ5Y> zj`sb%GqD@ta)dpBkq6i;<^o^zehrY1vZny~L3Xll1&!0+1Uq)!Vo$SYFy|pQcZ)f@ z*w?R{>^M7t8AYa^Q;Mgyh+|4cElHoPM_w&({KPyTpt7Z6pr|Hkj|C2MA_w&yUJ!97 zSa!p5r4~ev7i?vS!3-2vg2~B74wc`Y`t+^0fANps{>49SzE(lI??XV3;~N%GxC-$h zcu-x?nFeh1By)PUbmAoEHQs#uStoRD_e#YTh}lauZacnj(~LS2A8IN zZ?Qe)(A1ODp%=N6bqqV8*L2;hPnR)^)%Y|~bh=XG?o|CMCn+Ti1QFA!p^2WaOt@hp z%d;NyB=Md)*`?d%6*sUKF~Z%7ykQV+t$`*TEVLc&+CeR{LnrdWCC_CgPM5k(7Fipi zKDxD?eaKSJS4OsQI=JNc9-FB#w_XENHIEJ8>)6Z-aoe@axLaI&1B~g{D&nef^KzB# zg?1zSl?d+udVBj2rk<~i@a{o`XTbjMm8dy0fOoXw+I{#DWyM)-FRY|((l~=}co0RTY^aeIE9>f}+EQbcsa1_1K(F5E z4G%;H)7I6uF-kI6;smg1pAV`*?Q&pORva*SUI3v? z^vViP43e}Yd%^J=u3*Zd#>W6TQ7`rI7O_=QfI4ZfEmwRe3?D#mD6E^BSJUFl??7Ks zmmsXL!o7OLi5Dw$=FX?@{4^RVkxHkJw2OMI?orlB�xM!7Yp?dW|z~QY|GxhHWhQ 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 YRry>Kp@{~`#NUlvQS|?BPw(gd2aGBNc>n+a literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f45fd52b2713dec9b2726caec83ff662760697a7 GIT binary patch literal 42113 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65a32d54824be8fd3092f4380e9e371e3e5e7ce1 GIT binary patch literal 233 zcmYjLF$%&!5WI+mC@Fn}MY2j73lR%Tu@M`apPzo+Hj{bd@ri^BZ$L zs{Q2CKxRq=Y%o_?z#3~Tj4e@n*od?Od-a*okxinLLrfJ$-ic$54r-+vR?7pwt7V*mgE literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b8d4ab0b04cc23a6fe6c7ee888f5572f0036a8d GIT binary patch literal 185417 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 z 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 am0po*m0q5@I=w8_I=wV?O?pY{zyAl6- Tuple[bytes, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return b"", 0 + + return encode(data), len(data) + + def decode(self, data: bytes, errors: str = "strict") -> Tuple[str, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return "", 0 + + return decode(data), len(data) + + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return b"", 0 + + labels = _unicode_dots_re.split(data) + trailing_dot = b"" + if labels: + if not labels[-1]: + trailing_dot = b"." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = b"." + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result_bytes = b".".join(result) + trailing_dot + size += len(trailing_dot) + return result_bytes, size + + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return ("", 0) + + if not isinstance(data, str): + data = str(data, "ascii") + + labels = _unicode_dots_re.split(data) + trailing_dot = "" + if labels: + if not labels[-1]: + trailing_dot = "." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = "." + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result_str = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result_str, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def search_function(name: str) -> Optional[codecs.CodecInfo]: + if name != "idna2008": + return None + return codecs.CodecInfo( + name=name, + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) + + +codecs.register(search_function) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/compat.py b/venv/lib/python3.8/site-packages/pip/_vendor/idna/compat.py new file mode 100644 index 000000000..1df9f2a70 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/idna/compat.py @@ -0,0 +1,15 @@ +from typing import Any, Union + +from .core import decode, encode + + +def ToASCII(label: str) -> bytes: + return encode(label) + + +def ToUnicode(label: Union[bytes, bytearray]) -> str: + return decode(label) + + +def nameprep(s: Any) -> None: + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/core.py b/venv/lib/python3.8/site-packages/pip/_vendor/idna/core.py new file mode 100644 index 000000000..9115f123f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/idna/core.py @@ -0,0 +1,437 @@ +import bisect +import re +import unicodedata +from typing import Optional, Union + +from . import idnadata +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b"xn--" +_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]") + + +class IDNAError(UnicodeError): + """Base exception for all IDNA-encoding related problems""" + + pass + + +class IDNABidiError(IDNAError): + """Exception when bidirectional requirements are not satisfied""" + + pass + + +class InvalidCodepoint(IDNAError): + """Exception when a disallowed or unallocated codepoint is used""" + + pass + + +class InvalidCodepointContext(IDNAError): + """Exception when the codepoint is not valid in the context it is used""" + + pass + + +def _combining_class(cp: int) -> int: + v = unicodedata.combining(chr(cp)) + if v == 0: + if not unicodedata.name(chr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + + +def _is_script(cp: str, script: str) -> bool: + return intranges_contain(ord(cp), idnadata.scripts[script]) + + +def _punycode(s: str) -> bytes: + return s.encode("punycode") + + +def _unot(s: int) -> str: + return "U+{:04X}".format(s) + + +def valid_label_length(label: Union[bytes, str]) -> bool: + if len(label) > 63: + return False + return True + + +def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool: + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label: str, check_ltr: bool = False) -> bool: + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == "": + # String likely comes from a newer version of Unicode + raise IDNABidiError("Unknown directionality in label {} at position {}".format(repr(label), idx)) + if direction in ["R", "AL", "AN"]: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ["R", "AL"]: + rtl = True + elif direction == "L": + rtl = False + else: + raise IDNABidiError("First codepoint in label {} must be directionality L, R or AL".format(repr(label))) + + valid_ending = False + number_type: Optional[str] = None + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if direction not in [ + "R", + "AL", + "AN", + "EN", + "ES", + "CS", + "ET", + "ON", + "BN", + "NSM", + ]: + raise IDNABidiError("Invalid direction for codepoint at position {} in a right-to-left label".format(idx)) + # Bidi rule 3 + if direction in ["R", "AL", "EN", "AN"]: + valid_ending = True + elif direction != "NSM": + valid_ending = False + # Bidi rule 4 + if direction in ["AN", "EN"]: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError("Can not mix numeral types in a right-to-left label") + else: + # Bidi rule 5 + if direction not in ["L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"]: + raise IDNABidiError("Invalid direction for codepoint at position {} in a left-to-right label".format(idx)) + # Bidi rule 6 + if direction in ["L", "EN"]: + valid_ending = True + elif direction != "NSM": + valid_ending = False + + if not valid_ending: + raise IDNABidiError("Label ends with illegal codepoint directionality") + + return True + + +def check_initial_combiner(label: str) -> bool: + if unicodedata.category(label[0])[0] == "M": + raise IDNAError("Label begins with an illegal combining character") + return True + + +def check_hyphen_ok(label: str) -> bool: + if label[2:4] == "--": + raise IDNAError("Label has disallowed hyphens in 3rd and 4th position") + if label[0] == "-" or label[-1] == "-": + raise IDNAError("Label must not start or end with a hyphen") + return True + + +def check_nfc(label: str) -> None: + if unicodedata.normalize("NFC", label) != label: + raise IDNAError("Label must be in Normalization Form C") + + +def valid_contextj(label: str, pos: int) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x200C: + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos - 1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord("T"): + continue + elif joining_type in [ord("L"), ord("D")]: + ok = True + break + else: + break + + if not ok: + return False + + ok = False + for i in range(pos + 1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord("T"): + continue + elif joining_type in [ord("R"), ord("D")]: + ok = True + break + else: + break + return ok + + if cp_value == 0x200D: + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + return False + + +def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x00B7: + if 0 < pos < len(label) - 1: + if ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label) - 1 and len(label) > 1: + return _is_script(label[pos + 1], "Greek") + return False + + elif cp_value == 0x05F3 or cp_value == 0x05F4: + if pos > 0: + return _is_script(label[pos - 1], "Hebrew") + return False + + elif cp_value == 0x30FB: + for cp in label: + if cp == "\u30fb": + continue + if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6F0 <= ord(cp) <= 0x06F9: + return False + return True + + elif 0x6F0 <= cp_value <= 0x6F9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + return False + + +def check_label(label: Union[str, bytes, bytearray]) -> None: + if isinstance(label, (bytes, bytearray)): + label = label.decode("utf-8") + if len(label) == 0: + raise IDNAError("Empty Label") + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for pos, cp in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext( + "Joiner {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label)) + ) + except ValueError: + raise IDNAError( + "Unknown codepoint adjacent to joiner {} at position {} in {}".format( + _unot(cp_value), pos + 1, repr(label) + ) + ) + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]): + if not valid_contexto(label, pos): + raise InvalidCodepointContext( + "Codepoint {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label)) + ) + else: + raise InvalidCodepoint( + "Codepoint {} at position {} of {} not allowed".format(_unot(cp_value), pos + 1, repr(label)) + ) + + check_bidi(label) + + +def alabel(label: str) -> bytes: + try: + label_bytes = label.encode("ascii") + ulabel(label_bytes) + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + return label_bytes + except UnicodeEncodeError: + pass + + check_label(label) + label_bytes = _alabel_prefix + _punycode(label) + + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + + return label_bytes + + +def ulabel(label: Union[str, bytes, bytearray]) -> str: + if not isinstance(label, (bytes, bytearray)): + try: + label_bytes = label.encode("ascii") + except UnicodeEncodeError: + check_label(label) + return label + else: + label_bytes = label + + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix) :] + if not label_bytes: + raise IDNAError("Malformed A-label, no Punycode eligible content found") + if label_bytes.decode("ascii")[-1] == "-": + raise IDNAError("A-label must not end with a hyphen") + else: + check_label(label_bytes) + return label_bytes.decode("ascii") + + try: + label = label_bytes.decode("punycode") + except UnicodeError: + raise IDNAError("Invalid A-label") + check_label(label) + return label + + +def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + + output = "" + + for pos, char in enumerate(domain): + code_point = ord(char) + try: + uts46row = uts46data[code_point if code_point < 256 else bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement: Optional[str] = None + if len(uts46row) == 3: + replacement = uts46row[2] + if ( + status == "V" + or (status == "D" and not transitional) + or (status == "3" and not std3_rules and replacement is None) + ): + output += char + elif replacement is not None and ( + status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional) + ): + output += replacement + elif status != "I": + raise IndexError() + except IndexError: + raise InvalidCodepoint( + "Codepoint {} not allowed at position {} in {}".format(_unot(code_point), pos + 1, repr(domain)) + ) + + return unicodedata.normalize("NFC", output) + + +def encode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, + transitional: bool = False, +) -> bytes: + if not isinstance(s, str): + try: + s = str(s, "ascii") + except UnicodeDecodeError: + raise IDNAError("should pass a unicode string to the function rather than a byte string.") + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split(".") + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if labels[-1] == "": + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append(b"") + s = b".".join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError("Domain too long") + return s + + +def decode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, +) -> str: + try: + if not isinstance(s, str): + s = str(s, "ascii") + except UnicodeDecodeError: + raise IDNAError("Invalid ASCII in A-label") + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split(".") + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append("") + return ".".join(result) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/idnadata.py b/venv/lib/python3.8/site-packages/pip/_vendor/idna/idnadata.py new file mode 100644 index 000000000..4be600462 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/idna/idnadata.py @@ -0,0 +1,4243 @@ +# This file is automatically generated by tools/idna-data + +__version__ = "15.1.0" +scripts = { + "Greek": ( + 0x37000000374, + 0x37500000378, + 0x37A0000037E, + 0x37F00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038B, + 0x38C0000038D, + 0x38E000003A2, + 0x3A3000003E2, + 0x3F000000400, + 0x1D2600001D2B, + 0x1D5D00001D62, + 0x1D6600001D6B, + 0x1DBF00001DC0, + 0x1F0000001F16, + 0x1F1800001F1E, + 0x1F2000001F46, + 0x1F4800001F4E, + 0x1F5000001F58, + 0x1F5900001F5A, + 0x1F5B00001F5C, + 0x1F5D00001F5E, + 0x1F5F00001F7E, + 0x1F8000001FB5, + 0x1FB600001FC5, + 0x1FC600001FD4, + 0x1FD600001FDC, + 0x1FDD00001FF0, + 0x1FF200001FF5, + 0x1FF600001FFF, + 0x212600002127, + 0xAB650000AB66, + 0x101400001018F, + 0x101A0000101A1, + 0x1D2000001D246, + ), + "Han": ( + 0x2E8000002E9A, + 0x2E9B00002EF4, + 0x2F0000002FD6, + 0x300500003006, + 0x300700003008, + 0x30210000302A, + 0x30380000303C, + 0x340000004DC0, + 0x4E000000A000, + 0xF9000000FA6E, + 0xFA700000FADA, + 0x16FE200016FE4, + 0x16FF000016FF2, + 0x200000002A6E0, + 0x2A7000002B73A, + 0x2B7400002B81E, + 0x2B8200002CEA2, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x2F8000002FA1E, + 0x300000003134B, + 0x31350000323B0, + ), + "Hebrew": ( + 0x591000005C8, + 0x5D0000005EB, + 0x5EF000005F5, + 0xFB1D0000FB37, + 0xFB380000FB3D, + 0xFB3E0000FB3F, + 0xFB400000FB42, + 0xFB430000FB45, + 0xFB460000FB50, + ), + "Hiragana": ( + 0x304100003097, + 0x309D000030A0, + 0x1B0010001B120, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1F2000001F201, + ), + "Katakana": ( + 0x30A1000030FB, + 0x30FD00003100, + 0x31F000003200, + 0x32D0000032FF, + 0x330000003358, + 0xFF660000FF70, + 0xFF710000FF9E, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B001, + 0x1B1200001B123, + 0x1B1550001B156, + 0x1B1640001B168, + ), +} +joining_types = { + 0xAD: 84, + 0x300: 84, + 0x301: 84, + 0x302: 84, + 0x303: 84, + 0x304: 84, + 0x305: 84, + 0x306: 84, + 0x307: 84, + 0x308: 84, + 0x309: 84, + 0x30A: 84, + 0x30B: 84, + 0x30C: 84, + 0x30D: 84, + 0x30E: 84, + 0x30F: 84, + 0x310: 84, + 0x311: 84, + 0x312: 84, + 0x313: 84, + 0x314: 84, + 0x315: 84, + 0x316: 84, + 0x317: 84, + 0x318: 84, + 0x319: 84, + 0x31A: 84, + 0x31B: 84, + 0x31C: 84, + 0x31D: 84, + 0x31E: 84, + 0x31F: 84, + 0x320: 84, + 0x321: 84, + 0x322: 84, + 0x323: 84, + 0x324: 84, + 0x325: 84, + 0x326: 84, + 0x327: 84, + 0x328: 84, + 0x329: 84, + 0x32A: 84, + 0x32B: 84, + 0x32C: 84, + 0x32D: 84, + 0x32E: 84, + 0x32F: 84, + 0x330: 84, + 0x331: 84, + 0x332: 84, + 0x333: 84, + 0x334: 84, + 0x335: 84, + 0x336: 84, + 0x337: 84, + 0x338: 84, + 0x339: 84, + 0x33A: 84, + 0x33B: 84, + 0x33C: 84, + 0x33D: 84, + 0x33E: 84, + 0x33F: 84, + 0x340: 84, + 0x341: 84, + 0x342: 84, + 0x343: 84, + 0x344: 84, + 0x345: 84, + 0x346: 84, + 0x347: 84, + 0x348: 84, + 0x349: 84, + 0x34A: 84, + 0x34B: 84, + 0x34C: 84, + 0x34D: 84, + 0x34E: 84, + 0x34F: 84, + 0x350: 84, + 0x351: 84, + 0x352: 84, + 0x353: 84, + 0x354: 84, + 0x355: 84, + 0x356: 84, + 0x357: 84, + 0x358: 84, + 0x359: 84, + 0x35A: 84, + 0x35B: 84, + 0x35C: 84, + 0x35D: 84, + 0x35E: 84, + 0x35F: 84, + 0x360: 84, + 0x361: 84, + 0x362: 84, + 0x363: 84, + 0x364: 84, + 0x365: 84, + 0x366: 84, + 0x367: 84, + 0x368: 84, + 0x369: 84, + 0x36A: 84, + 0x36B: 84, + 0x36C: 84, + 0x36D: 84, + 0x36E: 84, + 0x36F: 84, + 0x483: 84, + 0x484: 84, + 0x485: 84, + 0x486: 84, + 0x487: 84, + 0x488: 84, + 0x489: 84, + 0x591: 84, + 0x592: 84, + 0x593: 84, + 0x594: 84, + 0x595: 84, + 0x596: 84, + 0x597: 84, + 0x598: 84, + 0x599: 84, + 0x59A: 84, + 0x59B: 84, + 0x59C: 84, + 0x59D: 84, + 0x59E: 84, + 0x59F: 84, + 0x5A0: 84, + 0x5A1: 84, + 0x5A2: 84, + 0x5A3: 84, + 0x5A4: 84, + 0x5A5: 84, + 0x5A6: 84, + 0x5A7: 84, + 0x5A8: 84, + 0x5A9: 84, + 0x5AA: 84, + 0x5AB: 84, + 0x5AC: 84, + 0x5AD: 84, + 0x5AE: 84, + 0x5AF: 84, + 0x5B0: 84, + 0x5B1: 84, + 0x5B2: 84, + 0x5B3: 84, + 0x5B4: 84, + 0x5B5: 84, + 0x5B6: 84, + 0x5B7: 84, + 0x5B8: 84, + 0x5B9: 84, + 0x5BA: 84, + 0x5BB: 84, + 0x5BC: 84, + 0x5BD: 84, + 0x5BF: 84, + 0x5C1: 84, + 0x5C2: 84, + 0x5C4: 84, + 0x5C5: 84, + 0x5C7: 84, + 0x610: 84, + 0x611: 84, + 0x612: 84, + 0x613: 84, + 0x614: 84, + 0x615: 84, + 0x616: 84, + 0x617: 84, + 0x618: 84, + 0x619: 84, + 0x61A: 84, + 0x61C: 84, + 0x620: 68, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62A: 68, + 0x62B: 68, + 0x62C: 68, + 0x62D: 68, + 0x62E: 68, + 0x62F: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63A: 68, + 0x63B: 68, + 0x63C: 68, + 0x63D: 68, + 0x63E: 68, + 0x63F: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64A: 68, + 0x64B: 84, + 0x64C: 84, + 0x64D: 84, + 0x64E: 84, + 0x64F: 84, + 0x650: 84, + 0x651: 84, + 0x652: 84, + 0x653: 84, + 0x654: 84, + 0x655: 84, + 0x656: 84, + 0x657: 84, + 0x658: 84, + 0x659: 84, + 0x65A: 84, + 0x65B: 84, + 0x65C: 84, + 0x65D: 84, + 0x65E: 84, + 0x65F: 84, + 0x66E: 68, + 0x66F: 68, + 0x670: 84, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67A: 68, + 0x67B: 68, + 0x67C: 68, + 0x67D: 68, + 0x67E: 68, + 0x67F: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68A: 82, + 0x68B: 82, + 0x68C: 82, + 0x68D: 82, + 0x68E: 82, + 0x68F: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69A: 68, + 0x69B: 68, + 0x69C: 68, + 0x69D: 68, + 0x69E: 68, + 0x69F: 68, + 0x6A0: 68, + 0x6A1: 68, + 0x6A2: 68, + 0x6A3: 68, + 0x6A4: 68, + 0x6A5: 68, + 0x6A6: 68, + 0x6A7: 68, + 0x6A8: 68, + 0x6A9: 68, + 0x6AA: 68, + 0x6AB: 68, + 0x6AC: 68, + 0x6AD: 68, + 0x6AE: 68, + 0x6AF: 68, + 0x6B0: 68, + 0x6B1: 68, + 0x6B2: 68, + 0x6B3: 68, + 0x6B4: 68, + 0x6B5: 68, + 0x6B6: 68, + 0x6B7: 68, + 0x6B8: 68, + 0x6B9: 68, + 0x6BA: 68, + 0x6BB: 68, + 0x6BC: 68, + 0x6BD: 68, + 0x6BE: 68, + 0x6BF: 68, + 0x6C0: 82, + 0x6C1: 68, + 0x6C2: 68, + 0x6C3: 82, + 0x6C4: 82, + 0x6C5: 82, + 0x6C6: 82, + 0x6C7: 82, + 0x6C8: 82, + 0x6C9: 82, + 0x6CA: 82, + 0x6CB: 82, + 0x6CC: 68, + 0x6CD: 82, + 0x6CE: 68, + 0x6CF: 82, + 0x6D0: 68, + 0x6D1: 68, + 0x6D2: 82, + 0x6D3: 82, + 0x6D5: 82, + 0x6D6: 84, + 0x6D7: 84, + 0x6D8: 84, + 0x6D9: 84, + 0x6DA: 84, + 0x6DB: 84, + 0x6DC: 84, + 0x6DF: 84, + 0x6E0: 84, + 0x6E1: 84, + 0x6E2: 84, + 0x6E3: 84, + 0x6E4: 84, + 0x6E7: 84, + 0x6E8: 84, + 0x6EA: 84, + 0x6EB: 84, + 0x6EC: 84, + 0x6ED: 84, + 0x6EE: 82, + 0x6EF: 82, + 0x6FA: 68, + 0x6FB: 68, + 0x6FC: 68, + 0x6FF: 68, + 0x70F: 84, + 0x710: 82, + 0x711: 84, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71A: 68, + 0x71B: 68, + 0x71C: 68, + 0x71D: 68, + 0x71E: 82, + 0x71F: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72A: 82, + 0x72B: 68, + 0x72C: 82, + 0x72D: 68, + 0x72E: 68, + 0x72F: 82, + 0x730: 84, + 0x731: 84, + 0x732: 84, + 0x733: 84, + 0x734: 84, + 0x735: 84, + 0x736: 84, + 0x737: 84, + 0x738: 84, + 0x739: 84, + 0x73A: 84, + 0x73B: 84, + 0x73C: 84, + 0x73D: 84, + 0x73E: 84, + 0x73F: 84, + 0x740: 84, + 0x741: 84, + 0x742: 84, + 0x743: 84, + 0x744: 84, + 0x745: 84, + 0x746: 84, + 0x747: 84, + 0x748: 84, + 0x749: 84, + 0x74A: 84, + 0x74D: 82, + 0x74E: 68, + 0x74F: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75A: 82, + 0x75B: 82, + 0x75C: 68, + 0x75D: 68, + 0x75E: 68, + 0x75F: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76A: 68, + 0x76B: 82, + 0x76C: 82, + 0x76D: 68, + 0x76E: 68, + 0x76F: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77A: 68, + 0x77B: 68, + 0x77C: 68, + 0x77D: 68, + 0x77E: 68, + 0x77F: 68, + 0x7A6: 84, + 0x7A7: 84, + 0x7A8: 84, + 0x7A9: 84, + 0x7AA: 84, + 0x7AB: 84, + 0x7AC: 84, + 0x7AD: 84, + 0x7AE: 84, + 0x7AF: 84, + 0x7B0: 84, + 0x7CA: 68, + 0x7CB: 68, + 0x7CC: 68, + 0x7CD: 68, + 0x7CE: 68, + 0x7CF: 68, + 0x7D0: 68, + 0x7D1: 68, + 0x7D2: 68, + 0x7D3: 68, + 0x7D4: 68, + 0x7D5: 68, + 0x7D6: 68, + 0x7D7: 68, + 0x7D8: 68, + 0x7D9: 68, + 0x7DA: 68, + 0x7DB: 68, + 0x7DC: 68, + 0x7DD: 68, + 0x7DE: 68, + 0x7DF: 68, + 0x7E0: 68, + 0x7E1: 68, + 0x7E2: 68, + 0x7E3: 68, + 0x7E4: 68, + 0x7E5: 68, + 0x7E6: 68, + 0x7E7: 68, + 0x7E8: 68, + 0x7E9: 68, + 0x7EA: 68, + 0x7EB: 84, + 0x7EC: 84, + 0x7ED: 84, + 0x7EE: 84, + 0x7EF: 84, + 0x7F0: 84, + 0x7F1: 84, + 0x7F2: 84, + 0x7F3: 84, + 0x7FA: 67, + 0x7FD: 84, + 0x816: 84, + 0x817: 84, + 0x818: 84, + 0x819: 84, + 0x81B: 84, + 0x81C: 84, + 0x81D: 84, + 0x81E: 84, + 0x81F: 84, + 0x820: 84, + 0x821: 84, + 0x822: 84, + 0x823: 84, + 0x825: 84, + 0x826: 84, + 0x827: 84, + 0x829: 84, + 0x82A: 84, + 0x82B: 84, + 0x82C: 84, + 0x82D: 84, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84A: 68, + 0x84B: 68, + 0x84C: 68, + 0x84D: 68, + 0x84E: 68, + 0x84F: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 82, + 0x857: 82, + 0x858: 82, + 0x859: 84, + 0x85A: 84, + 0x85B: 84, + 0x860: 68, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86A: 82, + 0x870: 82, + 0x871: 82, + 0x872: 82, + 0x873: 82, + 0x874: 82, + 0x875: 82, + 0x876: 82, + 0x877: 82, + 0x878: 82, + 0x879: 82, + 0x87A: 82, + 0x87B: 82, + 0x87C: 82, + 0x87D: 82, + 0x87E: 82, + 0x87F: 82, + 0x880: 82, + 0x881: 82, + 0x882: 82, + 0x883: 67, + 0x884: 67, + 0x885: 67, + 0x886: 68, + 0x889: 68, + 0x88A: 68, + 0x88B: 68, + 0x88C: 68, + 0x88D: 68, + 0x88E: 82, + 0x898: 84, + 0x899: 84, + 0x89A: 84, + 0x89B: 84, + 0x89C: 84, + 0x89D: 84, + 0x89E: 84, + 0x89F: 84, + 0x8A0: 68, + 0x8A1: 68, + 0x8A2: 68, + 0x8A3: 68, + 0x8A4: 68, + 0x8A5: 68, + 0x8A6: 68, + 0x8A7: 68, + 0x8A8: 68, + 0x8A9: 68, + 0x8AA: 82, + 0x8AB: 82, + 0x8AC: 82, + 0x8AE: 82, + 0x8AF: 68, + 0x8B0: 68, + 0x8B1: 82, + 0x8B2: 82, + 0x8B3: 68, + 0x8B4: 68, + 0x8B5: 68, + 0x8B6: 68, + 0x8B7: 68, + 0x8B8: 68, + 0x8B9: 82, + 0x8BA: 68, + 0x8BB: 68, + 0x8BC: 68, + 0x8BD: 68, + 0x8BE: 68, + 0x8BF: 68, + 0x8C0: 68, + 0x8C1: 68, + 0x8C2: 68, + 0x8C3: 68, + 0x8C4: 68, + 0x8C5: 68, + 0x8C6: 68, + 0x8C7: 68, + 0x8C8: 68, + 0x8CA: 84, + 0x8CB: 84, + 0x8CC: 84, + 0x8CD: 84, + 0x8CE: 84, + 0x8CF: 84, + 0x8D0: 84, + 0x8D1: 84, + 0x8D2: 84, + 0x8D3: 84, + 0x8D4: 84, + 0x8D5: 84, + 0x8D6: 84, + 0x8D7: 84, + 0x8D8: 84, + 0x8D9: 84, + 0x8DA: 84, + 0x8DB: 84, + 0x8DC: 84, + 0x8DD: 84, + 0x8DE: 84, + 0x8DF: 84, + 0x8E0: 84, + 0x8E1: 84, + 0x8E3: 84, + 0x8E4: 84, + 0x8E5: 84, + 0x8E6: 84, + 0x8E7: 84, + 0x8E8: 84, + 0x8E9: 84, + 0x8EA: 84, + 0x8EB: 84, + 0x8EC: 84, + 0x8ED: 84, + 0x8EE: 84, + 0x8EF: 84, + 0x8F0: 84, + 0x8F1: 84, + 0x8F2: 84, + 0x8F3: 84, + 0x8F4: 84, + 0x8F5: 84, + 0x8F6: 84, + 0x8F7: 84, + 0x8F8: 84, + 0x8F9: 84, + 0x8FA: 84, + 0x8FB: 84, + 0x8FC: 84, + 0x8FD: 84, + 0x8FE: 84, + 0x8FF: 84, + 0x900: 84, + 0x901: 84, + 0x902: 84, + 0x93A: 84, + 0x93C: 84, + 0x941: 84, + 0x942: 84, + 0x943: 84, + 0x944: 84, + 0x945: 84, + 0x946: 84, + 0x947: 84, + 0x948: 84, + 0x94D: 84, + 0x951: 84, + 0x952: 84, + 0x953: 84, + 0x954: 84, + 0x955: 84, + 0x956: 84, + 0x957: 84, + 0x962: 84, + 0x963: 84, + 0x981: 84, + 0x9BC: 84, + 0x9C1: 84, + 0x9C2: 84, + 0x9C3: 84, + 0x9C4: 84, + 0x9CD: 84, + 0x9E2: 84, + 0x9E3: 84, + 0x9FE: 84, + 0xA01: 84, + 0xA02: 84, + 0xA3C: 84, + 0xA41: 84, + 0xA42: 84, + 0xA47: 84, + 0xA48: 84, + 0xA4B: 84, + 0xA4C: 84, + 0xA4D: 84, + 0xA51: 84, + 0xA70: 84, + 0xA71: 84, + 0xA75: 84, + 0xA81: 84, + 0xA82: 84, + 0xABC: 84, + 0xAC1: 84, + 0xAC2: 84, + 0xAC3: 84, + 0xAC4: 84, + 0xAC5: 84, + 0xAC7: 84, + 0xAC8: 84, + 0xACD: 84, + 0xAE2: 84, + 0xAE3: 84, + 0xAFA: 84, + 0xAFB: 84, + 0xAFC: 84, + 0xAFD: 84, + 0xAFE: 84, + 0xAFF: 84, + 0xB01: 84, + 0xB3C: 84, + 0xB3F: 84, + 0xB41: 84, + 0xB42: 84, + 0xB43: 84, + 0xB44: 84, + 0xB4D: 84, + 0xB55: 84, + 0xB56: 84, + 0xB62: 84, + 0xB63: 84, + 0xB82: 84, + 0xBC0: 84, + 0xBCD: 84, + 0xC00: 84, + 0xC04: 84, + 0xC3C: 84, + 0xC3E: 84, + 0xC3F: 84, + 0xC40: 84, + 0xC46: 84, + 0xC47: 84, + 0xC48: 84, + 0xC4A: 84, + 0xC4B: 84, + 0xC4C: 84, + 0xC4D: 84, + 0xC55: 84, + 0xC56: 84, + 0xC62: 84, + 0xC63: 84, + 0xC81: 84, + 0xCBC: 84, + 0xCBF: 84, + 0xCC6: 84, + 0xCCC: 84, + 0xCCD: 84, + 0xCE2: 84, + 0xCE3: 84, + 0xD00: 84, + 0xD01: 84, + 0xD3B: 84, + 0xD3C: 84, + 0xD41: 84, + 0xD42: 84, + 0xD43: 84, + 0xD44: 84, + 0xD4D: 84, + 0xD62: 84, + 0xD63: 84, + 0xD81: 84, + 0xDCA: 84, + 0xDD2: 84, + 0xDD3: 84, + 0xDD4: 84, + 0xDD6: 84, + 0xE31: 84, + 0xE34: 84, + 0xE35: 84, + 0xE36: 84, + 0xE37: 84, + 0xE38: 84, + 0xE39: 84, + 0xE3A: 84, + 0xE47: 84, + 0xE48: 84, + 0xE49: 84, + 0xE4A: 84, + 0xE4B: 84, + 0xE4C: 84, + 0xE4D: 84, + 0xE4E: 84, + 0xEB1: 84, + 0xEB4: 84, + 0xEB5: 84, + 0xEB6: 84, + 0xEB7: 84, + 0xEB8: 84, + 0xEB9: 84, + 0xEBA: 84, + 0xEBB: 84, + 0xEBC: 84, + 0xEC8: 84, + 0xEC9: 84, + 0xECA: 84, + 0xECB: 84, + 0xECC: 84, + 0xECD: 84, + 0xECE: 84, + 0xF18: 84, + 0xF19: 84, + 0xF35: 84, + 0xF37: 84, + 0xF39: 84, + 0xF71: 84, + 0xF72: 84, + 0xF73: 84, + 0xF74: 84, + 0xF75: 84, + 0xF76: 84, + 0xF77: 84, + 0xF78: 84, + 0xF79: 84, + 0xF7A: 84, + 0xF7B: 84, + 0xF7C: 84, + 0xF7D: 84, + 0xF7E: 84, + 0xF80: 84, + 0xF81: 84, + 0xF82: 84, + 0xF83: 84, + 0xF84: 84, + 0xF86: 84, + 0xF87: 84, + 0xF8D: 84, + 0xF8E: 84, + 0xF8F: 84, + 0xF90: 84, + 0xF91: 84, + 0xF92: 84, + 0xF93: 84, + 0xF94: 84, + 0xF95: 84, + 0xF96: 84, + 0xF97: 84, + 0xF99: 84, + 0xF9A: 84, + 0xF9B: 84, + 0xF9C: 84, + 0xF9D: 84, + 0xF9E: 84, + 0xF9F: 84, + 0xFA0: 84, + 0xFA1: 84, + 0xFA2: 84, + 0xFA3: 84, + 0xFA4: 84, + 0xFA5: 84, + 0xFA6: 84, + 0xFA7: 84, + 0xFA8: 84, + 0xFA9: 84, + 0xFAA: 84, + 0xFAB: 84, + 0xFAC: 84, + 0xFAD: 84, + 0xFAE: 84, + 0xFAF: 84, + 0xFB0: 84, + 0xFB1: 84, + 0xFB2: 84, + 0xFB3: 84, + 0xFB4: 84, + 0xFB5: 84, + 0xFB6: 84, + 0xFB7: 84, + 0xFB8: 84, + 0xFB9: 84, + 0xFBA: 84, + 0xFBB: 84, + 0xFBC: 84, + 0xFC6: 84, + 0x102D: 84, + 0x102E: 84, + 0x102F: 84, + 0x1030: 84, + 0x1032: 84, + 0x1033: 84, + 0x1034: 84, + 0x1035: 84, + 0x1036: 84, + 0x1037: 84, + 0x1039: 84, + 0x103A: 84, + 0x103D: 84, + 0x103E: 84, + 0x1058: 84, + 0x1059: 84, + 0x105E: 84, + 0x105F: 84, + 0x1060: 84, + 0x1071: 84, + 0x1072: 84, + 0x1073: 84, + 0x1074: 84, + 0x1082: 84, + 0x1085: 84, + 0x1086: 84, + 0x108D: 84, + 0x109D: 84, + 0x135D: 84, + 0x135E: 84, + 0x135F: 84, + 0x1712: 84, + 0x1713: 84, + 0x1714: 84, + 0x1732: 84, + 0x1733: 84, + 0x1752: 84, + 0x1753: 84, + 0x1772: 84, + 0x1773: 84, + 0x17B4: 84, + 0x17B5: 84, + 0x17B7: 84, + 0x17B8: 84, + 0x17B9: 84, + 0x17BA: 84, + 0x17BB: 84, + 0x17BC: 84, + 0x17BD: 84, + 0x17C6: 84, + 0x17C9: 84, + 0x17CA: 84, + 0x17CB: 84, + 0x17CC: 84, + 0x17CD: 84, + 0x17CE: 84, + 0x17CF: 84, + 0x17D0: 84, + 0x17D1: 84, + 0x17D2: 84, + 0x17D3: 84, + 0x17DD: 84, + 0x1807: 68, + 0x180A: 67, + 0x180B: 84, + 0x180C: 84, + 0x180D: 84, + 0x180F: 84, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182A: 68, + 0x182B: 68, + 0x182C: 68, + 0x182D: 68, + 0x182E: 68, + 0x182F: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183A: 68, + 0x183B: 68, + 0x183C: 68, + 0x183D: 68, + 0x183E: 68, + 0x183F: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184A: 68, + 0x184B: 68, + 0x184C: 68, + 0x184D: 68, + 0x184E: 68, + 0x184F: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185A: 68, + 0x185B: 68, + 0x185C: 68, + 0x185D: 68, + 0x185E: 68, + 0x185F: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186A: 68, + 0x186B: 68, + 0x186C: 68, + 0x186D: 68, + 0x186E: 68, + 0x186F: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188A: 68, + 0x188B: 68, + 0x188C: 68, + 0x188D: 68, + 0x188E: 68, + 0x188F: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189A: 68, + 0x189B: 68, + 0x189C: 68, + 0x189D: 68, + 0x189E: 68, + 0x189F: 68, + 0x18A0: 68, + 0x18A1: 68, + 0x18A2: 68, + 0x18A3: 68, + 0x18A4: 68, + 0x18A5: 68, + 0x18A6: 68, + 0x18A7: 68, + 0x18A8: 68, + 0x18A9: 84, + 0x18AA: 68, + 0x1920: 84, + 0x1921: 84, + 0x1922: 84, + 0x1927: 84, + 0x1928: 84, + 0x1932: 84, + 0x1939: 84, + 0x193A: 84, + 0x193B: 84, + 0x1A17: 84, + 0x1A18: 84, + 0x1A1B: 84, + 0x1A56: 84, + 0x1A58: 84, + 0x1A59: 84, + 0x1A5A: 84, + 0x1A5B: 84, + 0x1A5C: 84, + 0x1A5D: 84, + 0x1A5E: 84, + 0x1A60: 84, + 0x1A62: 84, + 0x1A65: 84, + 0x1A66: 84, + 0x1A67: 84, + 0x1A68: 84, + 0x1A69: 84, + 0x1A6A: 84, + 0x1A6B: 84, + 0x1A6C: 84, + 0x1A73: 84, + 0x1A74: 84, + 0x1A75: 84, + 0x1A76: 84, + 0x1A77: 84, + 0x1A78: 84, + 0x1A79: 84, + 0x1A7A: 84, + 0x1A7B: 84, + 0x1A7C: 84, + 0x1A7F: 84, + 0x1AB0: 84, + 0x1AB1: 84, + 0x1AB2: 84, + 0x1AB3: 84, + 0x1AB4: 84, + 0x1AB5: 84, + 0x1AB6: 84, + 0x1AB7: 84, + 0x1AB8: 84, + 0x1AB9: 84, + 0x1ABA: 84, + 0x1ABB: 84, + 0x1ABC: 84, + 0x1ABD: 84, + 0x1ABE: 84, + 0x1ABF: 84, + 0x1AC0: 84, + 0x1AC1: 84, + 0x1AC2: 84, + 0x1AC3: 84, + 0x1AC4: 84, + 0x1AC5: 84, + 0x1AC6: 84, + 0x1AC7: 84, + 0x1AC8: 84, + 0x1AC9: 84, + 0x1ACA: 84, + 0x1ACB: 84, + 0x1ACC: 84, + 0x1ACD: 84, + 0x1ACE: 84, + 0x1B00: 84, + 0x1B01: 84, + 0x1B02: 84, + 0x1B03: 84, + 0x1B34: 84, + 0x1B36: 84, + 0x1B37: 84, + 0x1B38: 84, + 0x1B39: 84, + 0x1B3A: 84, + 0x1B3C: 84, + 0x1B42: 84, + 0x1B6B: 84, + 0x1B6C: 84, + 0x1B6D: 84, + 0x1B6E: 84, + 0x1B6F: 84, + 0x1B70: 84, + 0x1B71: 84, + 0x1B72: 84, + 0x1B73: 84, + 0x1B80: 84, + 0x1B81: 84, + 0x1BA2: 84, + 0x1BA3: 84, + 0x1BA4: 84, + 0x1BA5: 84, + 0x1BA8: 84, + 0x1BA9: 84, + 0x1BAB: 84, + 0x1BAC: 84, + 0x1BAD: 84, + 0x1BE6: 84, + 0x1BE8: 84, + 0x1BE9: 84, + 0x1BED: 84, + 0x1BEF: 84, + 0x1BF0: 84, + 0x1BF1: 84, + 0x1C2C: 84, + 0x1C2D: 84, + 0x1C2E: 84, + 0x1C2F: 84, + 0x1C30: 84, + 0x1C31: 84, + 0x1C32: 84, + 0x1C33: 84, + 0x1C36: 84, + 0x1C37: 84, + 0x1CD0: 84, + 0x1CD1: 84, + 0x1CD2: 84, + 0x1CD4: 84, + 0x1CD5: 84, + 0x1CD6: 84, + 0x1CD7: 84, + 0x1CD8: 84, + 0x1CD9: 84, + 0x1CDA: 84, + 0x1CDB: 84, + 0x1CDC: 84, + 0x1CDD: 84, + 0x1CDE: 84, + 0x1CDF: 84, + 0x1CE0: 84, + 0x1CE2: 84, + 0x1CE3: 84, + 0x1CE4: 84, + 0x1CE5: 84, + 0x1CE6: 84, + 0x1CE7: 84, + 0x1CE8: 84, + 0x1CED: 84, + 0x1CF4: 84, + 0x1CF8: 84, + 0x1CF9: 84, + 0x1DC0: 84, + 0x1DC1: 84, + 0x1DC2: 84, + 0x1DC3: 84, + 0x1DC4: 84, + 0x1DC5: 84, + 0x1DC6: 84, + 0x1DC7: 84, + 0x1DC8: 84, + 0x1DC9: 84, + 0x1DCA: 84, + 0x1DCB: 84, + 0x1DCC: 84, + 0x1DCD: 84, + 0x1DCE: 84, + 0x1DCF: 84, + 0x1DD0: 84, + 0x1DD1: 84, + 0x1DD2: 84, + 0x1DD3: 84, + 0x1DD4: 84, + 0x1DD5: 84, + 0x1DD6: 84, + 0x1DD7: 84, + 0x1DD8: 84, + 0x1DD9: 84, + 0x1DDA: 84, + 0x1DDB: 84, + 0x1DDC: 84, + 0x1DDD: 84, + 0x1DDE: 84, + 0x1DDF: 84, + 0x1DE0: 84, + 0x1DE1: 84, + 0x1DE2: 84, + 0x1DE3: 84, + 0x1DE4: 84, + 0x1DE5: 84, + 0x1DE6: 84, + 0x1DE7: 84, + 0x1DE8: 84, + 0x1DE9: 84, + 0x1DEA: 84, + 0x1DEB: 84, + 0x1DEC: 84, + 0x1DED: 84, + 0x1DEE: 84, + 0x1DEF: 84, + 0x1DF0: 84, + 0x1DF1: 84, + 0x1DF2: 84, + 0x1DF3: 84, + 0x1DF4: 84, + 0x1DF5: 84, + 0x1DF6: 84, + 0x1DF7: 84, + 0x1DF8: 84, + 0x1DF9: 84, + 0x1DFA: 84, + 0x1DFB: 84, + 0x1DFC: 84, + 0x1DFD: 84, + 0x1DFE: 84, + 0x1DFF: 84, + 0x200B: 84, + 0x200D: 67, + 0x200E: 84, + 0x200F: 84, + 0x202A: 84, + 0x202B: 84, + 0x202C: 84, + 0x202D: 84, + 0x202E: 84, + 0x2060: 84, + 0x2061: 84, + 0x2062: 84, + 0x2063: 84, + 0x2064: 84, + 0x206A: 84, + 0x206B: 84, + 0x206C: 84, + 0x206D: 84, + 0x206E: 84, + 0x206F: 84, + 0x20D0: 84, + 0x20D1: 84, + 0x20D2: 84, + 0x20D3: 84, + 0x20D4: 84, + 0x20D5: 84, + 0x20D6: 84, + 0x20D7: 84, + 0x20D8: 84, + 0x20D9: 84, + 0x20DA: 84, + 0x20DB: 84, + 0x20DC: 84, + 0x20DD: 84, + 0x20DE: 84, + 0x20DF: 84, + 0x20E0: 84, + 0x20E1: 84, + 0x20E2: 84, + 0x20E3: 84, + 0x20E4: 84, + 0x20E5: 84, + 0x20E6: 84, + 0x20E7: 84, + 0x20E8: 84, + 0x20E9: 84, + 0x20EA: 84, + 0x20EB: 84, + 0x20EC: 84, + 0x20ED: 84, + 0x20EE: 84, + 0x20EF: 84, + 0x20F0: 84, + 0x2CEF: 84, + 0x2CF0: 84, + 0x2CF1: 84, + 0x2D7F: 84, + 0x2DE0: 84, + 0x2DE1: 84, + 0x2DE2: 84, + 0x2DE3: 84, + 0x2DE4: 84, + 0x2DE5: 84, + 0x2DE6: 84, + 0x2DE7: 84, + 0x2DE8: 84, + 0x2DE9: 84, + 0x2DEA: 84, + 0x2DEB: 84, + 0x2DEC: 84, + 0x2DED: 84, + 0x2DEE: 84, + 0x2DEF: 84, + 0x2DF0: 84, + 0x2DF1: 84, + 0x2DF2: 84, + 0x2DF3: 84, + 0x2DF4: 84, + 0x2DF5: 84, + 0x2DF6: 84, + 0x2DF7: 84, + 0x2DF8: 84, + 0x2DF9: 84, + 0x2DFA: 84, + 0x2DFB: 84, + 0x2DFC: 84, + 0x2DFD: 84, + 0x2DFE: 84, + 0x2DFF: 84, + 0x302A: 84, + 0x302B: 84, + 0x302C: 84, + 0x302D: 84, + 0x3099: 84, + 0x309A: 84, + 0xA66F: 84, + 0xA670: 84, + 0xA671: 84, + 0xA672: 84, + 0xA674: 84, + 0xA675: 84, + 0xA676: 84, + 0xA677: 84, + 0xA678: 84, + 0xA679: 84, + 0xA67A: 84, + 0xA67B: 84, + 0xA67C: 84, + 0xA67D: 84, + 0xA69E: 84, + 0xA69F: 84, + 0xA6F0: 84, + 0xA6F1: 84, + 0xA802: 84, + 0xA806: 84, + 0xA80B: 84, + 0xA825: 84, + 0xA826: 84, + 0xA82C: 84, + 0xA840: 68, + 0xA841: 68, + 0xA842: 68, + 0xA843: 68, + 0xA844: 68, + 0xA845: 68, + 0xA846: 68, + 0xA847: 68, + 0xA848: 68, + 0xA849: 68, + 0xA84A: 68, + 0xA84B: 68, + 0xA84C: 68, + 0xA84D: 68, + 0xA84E: 68, + 0xA84F: 68, + 0xA850: 68, + 0xA851: 68, + 0xA852: 68, + 0xA853: 68, + 0xA854: 68, + 0xA855: 68, + 0xA856: 68, + 0xA857: 68, + 0xA858: 68, + 0xA859: 68, + 0xA85A: 68, + 0xA85B: 68, + 0xA85C: 68, + 0xA85D: 68, + 0xA85E: 68, + 0xA85F: 68, + 0xA860: 68, + 0xA861: 68, + 0xA862: 68, + 0xA863: 68, + 0xA864: 68, + 0xA865: 68, + 0xA866: 68, + 0xA867: 68, + 0xA868: 68, + 0xA869: 68, + 0xA86A: 68, + 0xA86B: 68, + 0xA86C: 68, + 0xA86D: 68, + 0xA86E: 68, + 0xA86F: 68, + 0xA870: 68, + 0xA871: 68, + 0xA872: 76, + 0xA8C4: 84, + 0xA8C5: 84, + 0xA8E0: 84, + 0xA8E1: 84, + 0xA8E2: 84, + 0xA8E3: 84, + 0xA8E4: 84, + 0xA8E5: 84, + 0xA8E6: 84, + 0xA8E7: 84, + 0xA8E8: 84, + 0xA8E9: 84, + 0xA8EA: 84, + 0xA8EB: 84, + 0xA8EC: 84, + 0xA8ED: 84, + 0xA8EE: 84, + 0xA8EF: 84, + 0xA8F0: 84, + 0xA8F1: 84, + 0xA8FF: 84, + 0xA926: 84, + 0xA927: 84, + 0xA928: 84, + 0xA929: 84, + 0xA92A: 84, + 0xA92B: 84, + 0xA92C: 84, + 0xA92D: 84, + 0xA947: 84, + 0xA948: 84, + 0xA949: 84, + 0xA94A: 84, + 0xA94B: 84, + 0xA94C: 84, + 0xA94D: 84, + 0xA94E: 84, + 0xA94F: 84, + 0xA950: 84, + 0xA951: 84, + 0xA980: 84, + 0xA981: 84, + 0xA982: 84, + 0xA9B3: 84, + 0xA9B6: 84, + 0xA9B7: 84, + 0xA9B8: 84, + 0xA9B9: 84, + 0xA9BC: 84, + 0xA9BD: 84, + 0xA9E5: 84, + 0xAA29: 84, + 0xAA2A: 84, + 0xAA2B: 84, + 0xAA2C: 84, + 0xAA2D: 84, + 0xAA2E: 84, + 0xAA31: 84, + 0xAA32: 84, + 0xAA35: 84, + 0xAA36: 84, + 0xAA43: 84, + 0xAA4C: 84, + 0xAA7C: 84, + 0xAAB0: 84, + 0xAAB2: 84, + 0xAAB3: 84, + 0xAAB4: 84, + 0xAAB7: 84, + 0xAAB8: 84, + 0xAABE: 84, + 0xAABF: 84, + 0xAAC1: 84, + 0xAAEC: 84, + 0xAAED: 84, + 0xAAF6: 84, + 0xABE5: 84, + 0xABE8: 84, + 0xABED: 84, + 0xFB1E: 84, + 0xFE00: 84, + 0xFE01: 84, + 0xFE02: 84, + 0xFE03: 84, + 0xFE04: 84, + 0xFE05: 84, + 0xFE06: 84, + 0xFE07: 84, + 0xFE08: 84, + 0xFE09: 84, + 0xFE0A: 84, + 0xFE0B: 84, + 0xFE0C: 84, + 0xFE0D: 84, + 0xFE0E: 84, + 0xFE0F: 84, + 0xFE20: 84, + 0xFE21: 84, + 0xFE22: 84, + 0xFE23: 84, + 0xFE24: 84, + 0xFE25: 84, + 0xFE26: 84, + 0xFE27: 84, + 0xFE28: 84, + 0xFE29: 84, + 0xFE2A: 84, + 0xFE2B: 84, + 0xFE2C: 84, + 0xFE2D: 84, + 0xFE2E: 84, + 0xFE2F: 84, + 0xFEFF: 84, + 0xFFF9: 84, + 0xFFFA: 84, + 0xFFFB: 84, + 0x101FD: 84, + 0x102E0: 84, + 0x10376: 84, + 0x10377: 84, + 0x10378: 84, + 0x10379: 84, + 0x1037A: 84, + 0x10A01: 84, + 0x10A02: 84, + 0x10A03: 84, + 0x10A05: 84, + 0x10A06: 84, + 0x10A0C: 84, + 0x10A0D: 84, + 0x10A0E: 84, + 0x10A0F: 84, + 0x10A38: 84, + 0x10A39: 84, + 0x10A3A: 84, + 0x10A3F: 84, + 0x10AC0: 68, + 0x10AC1: 68, + 0x10AC2: 68, + 0x10AC3: 68, + 0x10AC4: 68, + 0x10AC5: 82, + 0x10AC7: 82, + 0x10AC9: 82, + 0x10ACA: 82, + 0x10ACD: 76, + 0x10ACE: 82, + 0x10ACF: 82, + 0x10AD0: 82, + 0x10AD1: 82, + 0x10AD2: 82, + 0x10AD3: 68, + 0x10AD4: 68, + 0x10AD5: 68, + 0x10AD6: 68, + 0x10AD7: 76, + 0x10AD8: 68, + 0x10AD9: 68, + 0x10ADA: 68, + 0x10ADB: 68, + 0x10ADC: 68, + 0x10ADD: 82, + 0x10ADE: 68, + 0x10ADF: 68, + 0x10AE0: 68, + 0x10AE1: 82, + 0x10AE4: 82, + 0x10AE5: 84, + 0x10AE6: 84, + 0x10AEB: 68, + 0x10AEC: 68, + 0x10AED: 68, + 0x10AEE: 68, + 0x10AEF: 82, + 0x10B80: 68, + 0x10B81: 82, + 0x10B82: 68, + 0x10B83: 82, + 0x10B84: 82, + 0x10B85: 82, + 0x10B86: 68, + 0x10B87: 68, + 0x10B88: 68, + 0x10B89: 82, + 0x10B8A: 68, + 0x10B8B: 68, + 0x10B8C: 82, + 0x10B8D: 68, + 0x10B8E: 82, + 0x10B8F: 82, + 0x10B90: 68, + 0x10B91: 82, + 0x10BA9: 82, + 0x10BAA: 82, + 0x10BAB: 82, + 0x10BAC: 82, + 0x10BAD: 68, + 0x10BAE: 68, + 0x10D00: 76, + 0x10D01: 68, + 0x10D02: 68, + 0x10D03: 68, + 0x10D04: 68, + 0x10D05: 68, + 0x10D06: 68, + 0x10D07: 68, + 0x10D08: 68, + 0x10D09: 68, + 0x10D0A: 68, + 0x10D0B: 68, + 0x10D0C: 68, + 0x10D0D: 68, + 0x10D0E: 68, + 0x10D0F: 68, + 0x10D10: 68, + 0x10D11: 68, + 0x10D12: 68, + 0x10D13: 68, + 0x10D14: 68, + 0x10D15: 68, + 0x10D16: 68, + 0x10D17: 68, + 0x10D18: 68, + 0x10D19: 68, + 0x10D1A: 68, + 0x10D1B: 68, + 0x10D1C: 68, + 0x10D1D: 68, + 0x10D1E: 68, + 0x10D1F: 68, + 0x10D20: 68, + 0x10D21: 68, + 0x10D22: 82, + 0x10D23: 68, + 0x10D24: 84, + 0x10D25: 84, + 0x10D26: 84, + 0x10D27: 84, + 0x10EAB: 84, + 0x10EAC: 84, + 0x10EFD: 84, + 0x10EFE: 84, + 0x10EFF: 84, + 0x10F30: 68, + 0x10F31: 68, + 0x10F32: 68, + 0x10F33: 82, + 0x10F34: 68, + 0x10F35: 68, + 0x10F36: 68, + 0x10F37: 68, + 0x10F38: 68, + 0x10F39: 68, + 0x10F3A: 68, + 0x10F3B: 68, + 0x10F3C: 68, + 0x10F3D: 68, + 0x10F3E: 68, + 0x10F3F: 68, + 0x10F40: 68, + 0x10F41: 68, + 0x10F42: 68, + 0x10F43: 68, + 0x10F44: 68, + 0x10F46: 84, + 0x10F47: 84, + 0x10F48: 84, + 0x10F49: 84, + 0x10F4A: 84, + 0x10F4B: 84, + 0x10F4C: 84, + 0x10F4D: 84, + 0x10F4E: 84, + 0x10F4F: 84, + 0x10F50: 84, + 0x10F51: 68, + 0x10F52: 68, + 0x10F53: 68, + 0x10F54: 82, + 0x10F70: 68, + 0x10F71: 68, + 0x10F72: 68, + 0x10F73: 68, + 0x10F74: 82, + 0x10F75: 82, + 0x10F76: 68, + 0x10F77: 68, + 0x10F78: 68, + 0x10F79: 68, + 0x10F7A: 68, + 0x10F7B: 68, + 0x10F7C: 68, + 0x10F7D: 68, + 0x10F7E: 68, + 0x10F7F: 68, + 0x10F80: 68, + 0x10F81: 68, + 0x10F82: 84, + 0x10F83: 84, + 0x10F84: 84, + 0x10F85: 84, + 0x10FB0: 68, + 0x10FB2: 68, + 0x10FB3: 68, + 0x10FB4: 82, + 0x10FB5: 82, + 0x10FB6: 82, + 0x10FB8: 68, + 0x10FB9: 82, + 0x10FBA: 82, + 0x10FBB: 68, + 0x10FBC: 68, + 0x10FBD: 82, + 0x10FBE: 68, + 0x10FBF: 68, + 0x10FC1: 68, + 0x10FC2: 82, + 0x10FC3: 82, + 0x10FC4: 68, + 0x10FC9: 82, + 0x10FCA: 68, + 0x10FCB: 76, + 0x11001: 84, + 0x11038: 84, + 0x11039: 84, + 0x1103A: 84, + 0x1103B: 84, + 0x1103C: 84, + 0x1103D: 84, + 0x1103E: 84, + 0x1103F: 84, + 0x11040: 84, + 0x11041: 84, + 0x11042: 84, + 0x11043: 84, + 0x11044: 84, + 0x11045: 84, + 0x11046: 84, + 0x11070: 84, + 0x11073: 84, + 0x11074: 84, + 0x1107F: 84, + 0x11080: 84, + 0x11081: 84, + 0x110B3: 84, + 0x110B4: 84, + 0x110B5: 84, + 0x110B6: 84, + 0x110B9: 84, + 0x110BA: 84, + 0x110C2: 84, + 0x11100: 84, + 0x11101: 84, + 0x11102: 84, + 0x11127: 84, + 0x11128: 84, + 0x11129: 84, + 0x1112A: 84, + 0x1112B: 84, + 0x1112D: 84, + 0x1112E: 84, + 0x1112F: 84, + 0x11130: 84, + 0x11131: 84, + 0x11132: 84, + 0x11133: 84, + 0x11134: 84, + 0x11173: 84, + 0x11180: 84, + 0x11181: 84, + 0x111B6: 84, + 0x111B7: 84, + 0x111B8: 84, + 0x111B9: 84, + 0x111BA: 84, + 0x111BB: 84, + 0x111BC: 84, + 0x111BD: 84, + 0x111BE: 84, + 0x111C9: 84, + 0x111CA: 84, + 0x111CB: 84, + 0x111CC: 84, + 0x111CF: 84, + 0x1122F: 84, + 0x11230: 84, + 0x11231: 84, + 0x11234: 84, + 0x11236: 84, + 0x11237: 84, + 0x1123E: 84, + 0x11241: 84, + 0x112DF: 84, + 0x112E3: 84, + 0x112E4: 84, + 0x112E5: 84, + 0x112E6: 84, + 0x112E7: 84, + 0x112E8: 84, + 0x112E9: 84, + 0x112EA: 84, + 0x11300: 84, + 0x11301: 84, + 0x1133B: 84, + 0x1133C: 84, + 0x11340: 84, + 0x11366: 84, + 0x11367: 84, + 0x11368: 84, + 0x11369: 84, + 0x1136A: 84, + 0x1136B: 84, + 0x1136C: 84, + 0x11370: 84, + 0x11371: 84, + 0x11372: 84, + 0x11373: 84, + 0x11374: 84, + 0x11438: 84, + 0x11439: 84, + 0x1143A: 84, + 0x1143B: 84, + 0x1143C: 84, + 0x1143D: 84, + 0x1143E: 84, + 0x1143F: 84, + 0x11442: 84, + 0x11443: 84, + 0x11444: 84, + 0x11446: 84, + 0x1145E: 84, + 0x114B3: 84, + 0x114B4: 84, + 0x114B5: 84, + 0x114B6: 84, + 0x114B7: 84, + 0x114B8: 84, + 0x114BA: 84, + 0x114BF: 84, + 0x114C0: 84, + 0x114C2: 84, + 0x114C3: 84, + 0x115B2: 84, + 0x115B3: 84, + 0x115B4: 84, + 0x115B5: 84, + 0x115BC: 84, + 0x115BD: 84, + 0x115BF: 84, + 0x115C0: 84, + 0x115DC: 84, + 0x115DD: 84, + 0x11633: 84, + 0x11634: 84, + 0x11635: 84, + 0x11636: 84, + 0x11637: 84, + 0x11638: 84, + 0x11639: 84, + 0x1163A: 84, + 0x1163D: 84, + 0x1163F: 84, + 0x11640: 84, + 0x116AB: 84, + 0x116AD: 84, + 0x116B0: 84, + 0x116B1: 84, + 0x116B2: 84, + 0x116B3: 84, + 0x116B4: 84, + 0x116B5: 84, + 0x116B7: 84, + 0x1171D: 84, + 0x1171E: 84, + 0x1171F: 84, + 0x11722: 84, + 0x11723: 84, + 0x11724: 84, + 0x11725: 84, + 0x11727: 84, + 0x11728: 84, + 0x11729: 84, + 0x1172A: 84, + 0x1172B: 84, + 0x1182F: 84, + 0x11830: 84, + 0x11831: 84, + 0x11832: 84, + 0x11833: 84, + 0x11834: 84, + 0x11835: 84, + 0x11836: 84, + 0x11837: 84, + 0x11839: 84, + 0x1183A: 84, + 0x1193B: 84, + 0x1193C: 84, + 0x1193E: 84, + 0x11943: 84, + 0x119D4: 84, + 0x119D5: 84, + 0x119D6: 84, + 0x119D7: 84, + 0x119DA: 84, + 0x119DB: 84, + 0x119E0: 84, + 0x11A01: 84, + 0x11A02: 84, + 0x11A03: 84, + 0x11A04: 84, + 0x11A05: 84, + 0x11A06: 84, + 0x11A07: 84, + 0x11A08: 84, + 0x11A09: 84, + 0x11A0A: 84, + 0x11A33: 84, + 0x11A34: 84, + 0x11A35: 84, + 0x11A36: 84, + 0x11A37: 84, + 0x11A38: 84, + 0x11A3B: 84, + 0x11A3C: 84, + 0x11A3D: 84, + 0x11A3E: 84, + 0x11A47: 84, + 0x11A51: 84, + 0x11A52: 84, + 0x11A53: 84, + 0x11A54: 84, + 0x11A55: 84, + 0x11A56: 84, + 0x11A59: 84, + 0x11A5A: 84, + 0x11A5B: 84, + 0x11A8A: 84, + 0x11A8B: 84, + 0x11A8C: 84, + 0x11A8D: 84, + 0x11A8E: 84, + 0x11A8F: 84, + 0x11A90: 84, + 0x11A91: 84, + 0x11A92: 84, + 0x11A93: 84, + 0x11A94: 84, + 0x11A95: 84, + 0x11A96: 84, + 0x11A98: 84, + 0x11A99: 84, + 0x11C30: 84, + 0x11C31: 84, + 0x11C32: 84, + 0x11C33: 84, + 0x11C34: 84, + 0x11C35: 84, + 0x11C36: 84, + 0x11C38: 84, + 0x11C39: 84, + 0x11C3A: 84, + 0x11C3B: 84, + 0x11C3C: 84, + 0x11C3D: 84, + 0x11C3F: 84, + 0x11C92: 84, + 0x11C93: 84, + 0x11C94: 84, + 0x11C95: 84, + 0x11C96: 84, + 0x11C97: 84, + 0x11C98: 84, + 0x11C99: 84, + 0x11C9A: 84, + 0x11C9B: 84, + 0x11C9C: 84, + 0x11C9D: 84, + 0x11C9E: 84, + 0x11C9F: 84, + 0x11CA0: 84, + 0x11CA1: 84, + 0x11CA2: 84, + 0x11CA3: 84, + 0x11CA4: 84, + 0x11CA5: 84, + 0x11CA6: 84, + 0x11CA7: 84, + 0x11CAA: 84, + 0x11CAB: 84, + 0x11CAC: 84, + 0x11CAD: 84, + 0x11CAE: 84, + 0x11CAF: 84, + 0x11CB0: 84, + 0x11CB2: 84, + 0x11CB3: 84, + 0x11CB5: 84, + 0x11CB6: 84, + 0x11D31: 84, + 0x11D32: 84, + 0x11D33: 84, + 0x11D34: 84, + 0x11D35: 84, + 0x11D36: 84, + 0x11D3A: 84, + 0x11D3C: 84, + 0x11D3D: 84, + 0x11D3F: 84, + 0x11D40: 84, + 0x11D41: 84, + 0x11D42: 84, + 0x11D43: 84, + 0x11D44: 84, + 0x11D45: 84, + 0x11D47: 84, + 0x11D90: 84, + 0x11D91: 84, + 0x11D95: 84, + 0x11D97: 84, + 0x11EF3: 84, + 0x11EF4: 84, + 0x11F00: 84, + 0x11F01: 84, + 0x11F36: 84, + 0x11F37: 84, + 0x11F38: 84, + 0x11F39: 84, + 0x11F3A: 84, + 0x11F40: 84, + 0x11F42: 84, + 0x13430: 84, + 0x13431: 84, + 0x13432: 84, + 0x13433: 84, + 0x13434: 84, + 0x13435: 84, + 0x13436: 84, + 0x13437: 84, + 0x13438: 84, + 0x13439: 84, + 0x1343A: 84, + 0x1343B: 84, + 0x1343C: 84, + 0x1343D: 84, + 0x1343E: 84, + 0x1343F: 84, + 0x13440: 84, + 0x13447: 84, + 0x13448: 84, + 0x13449: 84, + 0x1344A: 84, + 0x1344B: 84, + 0x1344C: 84, + 0x1344D: 84, + 0x1344E: 84, + 0x1344F: 84, + 0x13450: 84, + 0x13451: 84, + 0x13452: 84, + 0x13453: 84, + 0x13454: 84, + 0x13455: 84, + 0x16AF0: 84, + 0x16AF1: 84, + 0x16AF2: 84, + 0x16AF3: 84, + 0x16AF4: 84, + 0x16B30: 84, + 0x16B31: 84, + 0x16B32: 84, + 0x16B33: 84, + 0x16B34: 84, + 0x16B35: 84, + 0x16B36: 84, + 0x16F4F: 84, + 0x16F8F: 84, + 0x16F90: 84, + 0x16F91: 84, + 0x16F92: 84, + 0x16FE4: 84, + 0x1BC9D: 84, + 0x1BC9E: 84, + 0x1BCA0: 84, + 0x1BCA1: 84, + 0x1BCA2: 84, + 0x1BCA3: 84, + 0x1CF00: 84, + 0x1CF01: 84, + 0x1CF02: 84, + 0x1CF03: 84, + 0x1CF04: 84, + 0x1CF05: 84, + 0x1CF06: 84, + 0x1CF07: 84, + 0x1CF08: 84, + 0x1CF09: 84, + 0x1CF0A: 84, + 0x1CF0B: 84, + 0x1CF0C: 84, + 0x1CF0D: 84, + 0x1CF0E: 84, + 0x1CF0F: 84, + 0x1CF10: 84, + 0x1CF11: 84, + 0x1CF12: 84, + 0x1CF13: 84, + 0x1CF14: 84, + 0x1CF15: 84, + 0x1CF16: 84, + 0x1CF17: 84, + 0x1CF18: 84, + 0x1CF19: 84, + 0x1CF1A: 84, + 0x1CF1B: 84, + 0x1CF1C: 84, + 0x1CF1D: 84, + 0x1CF1E: 84, + 0x1CF1F: 84, + 0x1CF20: 84, + 0x1CF21: 84, + 0x1CF22: 84, + 0x1CF23: 84, + 0x1CF24: 84, + 0x1CF25: 84, + 0x1CF26: 84, + 0x1CF27: 84, + 0x1CF28: 84, + 0x1CF29: 84, + 0x1CF2A: 84, + 0x1CF2B: 84, + 0x1CF2C: 84, + 0x1CF2D: 84, + 0x1CF30: 84, + 0x1CF31: 84, + 0x1CF32: 84, + 0x1CF33: 84, + 0x1CF34: 84, + 0x1CF35: 84, + 0x1CF36: 84, + 0x1CF37: 84, + 0x1CF38: 84, + 0x1CF39: 84, + 0x1CF3A: 84, + 0x1CF3B: 84, + 0x1CF3C: 84, + 0x1CF3D: 84, + 0x1CF3E: 84, + 0x1CF3F: 84, + 0x1CF40: 84, + 0x1CF41: 84, + 0x1CF42: 84, + 0x1CF43: 84, + 0x1CF44: 84, + 0x1CF45: 84, + 0x1CF46: 84, + 0x1D167: 84, + 0x1D168: 84, + 0x1D169: 84, + 0x1D173: 84, + 0x1D174: 84, + 0x1D175: 84, + 0x1D176: 84, + 0x1D177: 84, + 0x1D178: 84, + 0x1D179: 84, + 0x1D17A: 84, + 0x1D17B: 84, + 0x1D17C: 84, + 0x1D17D: 84, + 0x1D17E: 84, + 0x1D17F: 84, + 0x1D180: 84, + 0x1D181: 84, + 0x1D182: 84, + 0x1D185: 84, + 0x1D186: 84, + 0x1D187: 84, + 0x1D188: 84, + 0x1D189: 84, + 0x1D18A: 84, + 0x1D18B: 84, + 0x1D1AA: 84, + 0x1D1AB: 84, + 0x1D1AC: 84, + 0x1D1AD: 84, + 0x1D242: 84, + 0x1D243: 84, + 0x1D244: 84, + 0x1DA00: 84, + 0x1DA01: 84, + 0x1DA02: 84, + 0x1DA03: 84, + 0x1DA04: 84, + 0x1DA05: 84, + 0x1DA06: 84, + 0x1DA07: 84, + 0x1DA08: 84, + 0x1DA09: 84, + 0x1DA0A: 84, + 0x1DA0B: 84, + 0x1DA0C: 84, + 0x1DA0D: 84, + 0x1DA0E: 84, + 0x1DA0F: 84, + 0x1DA10: 84, + 0x1DA11: 84, + 0x1DA12: 84, + 0x1DA13: 84, + 0x1DA14: 84, + 0x1DA15: 84, + 0x1DA16: 84, + 0x1DA17: 84, + 0x1DA18: 84, + 0x1DA19: 84, + 0x1DA1A: 84, + 0x1DA1B: 84, + 0x1DA1C: 84, + 0x1DA1D: 84, + 0x1DA1E: 84, + 0x1DA1F: 84, + 0x1DA20: 84, + 0x1DA21: 84, + 0x1DA22: 84, + 0x1DA23: 84, + 0x1DA24: 84, + 0x1DA25: 84, + 0x1DA26: 84, + 0x1DA27: 84, + 0x1DA28: 84, + 0x1DA29: 84, + 0x1DA2A: 84, + 0x1DA2B: 84, + 0x1DA2C: 84, + 0x1DA2D: 84, + 0x1DA2E: 84, + 0x1DA2F: 84, + 0x1DA30: 84, + 0x1DA31: 84, + 0x1DA32: 84, + 0x1DA33: 84, + 0x1DA34: 84, + 0x1DA35: 84, + 0x1DA36: 84, + 0x1DA3B: 84, + 0x1DA3C: 84, + 0x1DA3D: 84, + 0x1DA3E: 84, + 0x1DA3F: 84, + 0x1DA40: 84, + 0x1DA41: 84, + 0x1DA42: 84, + 0x1DA43: 84, + 0x1DA44: 84, + 0x1DA45: 84, + 0x1DA46: 84, + 0x1DA47: 84, + 0x1DA48: 84, + 0x1DA49: 84, + 0x1DA4A: 84, + 0x1DA4B: 84, + 0x1DA4C: 84, + 0x1DA4D: 84, + 0x1DA4E: 84, + 0x1DA4F: 84, + 0x1DA50: 84, + 0x1DA51: 84, + 0x1DA52: 84, + 0x1DA53: 84, + 0x1DA54: 84, + 0x1DA55: 84, + 0x1DA56: 84, + 0x1DA57: 84, + 0x1DA58: 84, + 0x1DA59: 84, + 0x1DA5A: 84, + 0x1DA5B: 84, + 0x1DA5C: 84, + 0x1DA5D: 84, + 0x1DA5E: 84, + 0x1DA5F: 84, + 0x1DA60: 84, + 0x1DA61: 84, + 0x1DA62: 84, + 0x1DA63: 84, + 0x1DA64: 84, + 0x1DA65: 84, + 0x1DA66: 84, + 0x1DA67: 84, + 0x1DA68: 84, + 0x1DA69: 84, + 0x1DA6A: 84, + 0x1DA6B: 84, + 0x1DA6C: 84, + 0x1DA75: 84, + 0x1DA84: 84, + 0x1DA9B: 84, + 0x1DA9C: 84, + 0x1DA9D: 84, + 0x1DA9E: 84, + 0x1DA9F: 84, + 0x1DAA1: 84, + 0x1DAA2: 84, + 0x1DAA3: 84, + 0x1DAA4: 84, + 0x1DAA5: 84, + 0x1DAA6: 84, + 0x1DAA7: 84, + 0x1DAA8: 84, + 0x1DAA9: 84, + 0x1DAAA: 84, + 0x1DAAB: 84, + 0x1DAAC: 84, + 0x1DAAD: 84, + 0x1DAAE: 84, + 0x1DAAF: 84, + 0x1E000: 84, + 0x1E001: 84, + 0x1E002: 84, + 0x1E003: 84, + 0x1E004: 84, + 0x1E005: 84, + 0x1E006: 84, + 0x1E008: 84, + 0x1E009: 84, + 0x1E00A: 84, + 0x1E00B: 84, + 0x1E00C: 84, + 0x1E00D: 84, + 0x1E00E: 84, + 0x1E00F: 84, + 0x1E010: 84, + 0x1E011: 84, + 0x1E012: 84, + 0x1E013: 84, + 0x1E014: 84, + 0x1E015: 84, + 0x1E016: 84, + 0x1E017: 84, + 0x1E018: 84, + 0x1E01B: 84, + 0x1E01C: 84, + 0x1E01D: 84, + 0x1E01E: 84, + 0x1E01F: 84, + 0x1E020: 84, + 0x1E021: 84, + 0x1E023: 84, + 0x1E024: 84, + 0x1E026: 84, + 0x1E027: 84, + 0x1E028: 84, + 0x1E029: 84, + 0x1E02A: 84, + 0x1E08F: 84, + 0x1E130: 84, + 0x1E131: 84, + 0x1E132: 84, + 0x1E133: 84, + 0x1E134: 84, + 0x1E135: 84, + 0x1E136: 84, + 0x1E2AE: 84, + 0x1E2EC: 84, + 0x1E2ED: 84, + 0x1E2EE: 84, + 0x1E2EF: 84, + 0x1E4EC: 84, + 0x1E4ED: 84, + 0x1E4EE: 84, + 0x1E4EF: 84, + 0x1E8D0: 84, + 0x1E8D1: 84, + 0x1E8D2: 84, + 0x1E8D3: 84, + 0x1E8D4: 84, + 0x1E8D5: 84, + 0x1E8D6: 84, + 0x1E900: 68, + 0x1E901: 68, + 0x1E902: 68, + 0x1E903: 68, + 0x1E904: 68, + 0x1E905: 68, + 0x1E906: 68, + 0x1E907: 68, + 0x1E908: 68, + 0x1E909: 68, + 0x1E90A: 68, + 0x1E90B: 68, + 0x1E90C: 68, + 0x1E90D: 68, + 0x1E90E: 68, + 0x1E90F: 68, + 0x1E910: 68, + 0x1E911: 68, + 0x1E912: 68, + 0x1E913: 68, + 0x1E914: 68, + 0x1E915: 68, + 0x1E916: 68, + 0x1E917: 68, + 0x1E918: 68, + 0x1E919: 68, + 0x1E91A: 68, + 0x1E91B: 68, + 0x1E91C: 68, + 0x1E91D: 68, + 0x1E91E: 68, + 0x1E91F: 68, + 0x1E920: 68, + 0x1E921: 68, + 0x1E922: 68, + 0x1E923: 68, + 0x1E924: 68, + 0x1E925: 68, + 0x1E926: 68, + 0x1E927: 68, + 0x1E928: 68, + 0x1E929: 68, + 0x1E92A: 68, + 0x1E92B: 68, + 0x1E92C: 68, + 0x1E92D: 68, + 0x1E92E: 68, + 0x1E92F: 68, + 0x1E930: 68, + 0x1E931: 68, + 0x1E932: 68, + 0x1E933: 68, + 0x1E934: 68, + 0x1E935: 68, + 0x1E936: 68, + 0x1E937: 68, + 0x1E938: 68, + 0x1E939: 68, + 0x1E93A: 68, + 0x1E93B: 68, + 0x1E93C: 68, + 0x1E93D: 68, + 0x1E93E: 68, + 0x1E93F: 68, + 0x1E940: 68, + 0x1E941: 68, + 0x1E942: 68, + 0x1E943: 68, + 0x1E944: 84, + 0x1E945: 84, + 0x1E946: 84, + 0x1E947: 84, + 0x1E948: 84, + 0x1E949: 84, + 0x1E94A: 84, + 0x1E94B: 84, + 0xE0001: 84, + 0xE0020: 84, + 0xE0021: 84, + 0xE0022: 84, + 0xE0023: 84, + 0xE0024: 84, + 0xE0025: 84, + 0xE0026: 84, + 0xE0027: 84, + 0xE0028: 84, + 0xE0029: 84, + 0xE002A: 84, + 0xE002B: 84, + 0xE002C: 84, + 0xE002D: 84, + 0xE002E: 84, + 0xE002F: 84, + 0xE0030: 84, + 0xE0031: 84, + 0xE0032: 84, + 0xE0033: 84, + 0xE0034: 84, + 0xE0035: 84, + 0xE0036: 84, + 0xE0037: 84, + 0xE0038: 84, + 0xE0039: 84, + 0xE003A: 84, + 0xE003B: 84, + 0xE003C: 84, + 0xE003D: 84, + 0xE003E: 84, + 0xE003F: 84, + 0xE0040: 84, + 0xE0041: 84, + 0xE0042: 84, + 0xE0043: 84, + 0xE0044: 84, + 0xE0045: 84, + 0xE0046: 84, + 0xE0047: 84, + 0xE0048: 84, + 0xE0049: 84, + 0xE004A: 84, + 0xE004B: 84, + 0xE004C: 84, + 0xE004D: 84, + 0xE004E: 84, + 0xE004F: 84, + 0xE0050: 84, + 0xE0051: 84, + 0xE0052: 84, + 0xE0053: 84, + 0xE0054: 84, + 0xE0055: 84, + 0xE0056: 84, + 0xE0057: 84, + 0xE0058: 84, + 0xE0059: 84, + 0xE005A: 84, + 0xE005B: 84, + 0xE005C: 84, + 0xE005D: 84, + 0xE005E: 84, + 0xE005F: 84, + 0xE0060: 84, + 0xE0061: 84, + 0xE0062: 84, + 0xE0063: 84, + 0xE0064: 84, + 0xE0065: 84, + 0xE0066: 84, + 0xE0067: 84, + 0xE0068: 84, + 0xE0069: 84, + 0xE006A: 84, + 0xE006B: 84, + 0xE006C: 84, + 0xE006D: 84, + 0xE006E: 84, + 0xE006F: 84, + 0xE0070: 84, + 0xE0071: 84, + 0xE0072: 84, + 0xE0073: 84, + 0xE0074: 84, + 0xE0075: 84, + 0xE0076: 84, + 0xE0077: 84, + 0xE0078: 84, + 0xE0079: 84, + 0xE007A: 84, + 0xE007B: 84, + 0xE007C: 84, + 0xE007D: 84, + 0xE007E: 84, + 0xE007F: 84, + 0xE0100: 84, + 0xE0101: 84, + 0xE0102: 84, + 0xE0103: 84, + 0xE0104: 84, + 0xE0105: 84, + 0xE0106: 84, + 0xE0107: 84, + 0xE0108: 84, + 0xE0109: 84, + 0xE010A: 84, + 0xE010B: 84, + 0xE010C: 84, + 0xE010D: 84, + 0xE010E: 84, + 0xE010F: 84, + 0xE0110: 84, + 0xE0111: 84, + 0xE0112: 84, + 0xE0113: 84, + 0xE0114: 84, + 0xE0115: 84, + 0xE0116: 84, + 0xE0117: 84, + 0xE0118: 84, + 0xE0119: 84, + 0xE011A: 84, + 0xE011B: 84, + 0xE011C: 84, + 0xE011D: 84, + 0xE011E: 84, + 0xE011F: 84, + 0xE0120: 84, + 0xE0121: 84, + 0xE0122: 84, + 0xE0123: 84, + 0xE0124: 84, + 0xE0125: 84, + 0xE0126: 84, + 0xE0127: 84, + 0xE0128: 84, + 0xE0129: 84, + 0xE012A: 84, + 0xE012B: 84, + 0xE012C: 84, + 0xE012D: 84, + 0xE012E: 84, + 0xE012F: 84, + 0xE0130: 84, + 0xE0131: 84, + 0xE0132: 84, + 0xE0133: 84, + 0xE0134: 84, + 0xE0135: 84, + 0xE0136: 84, + 0xE0137: 84, + 0xE0138: 84, + 0xE0139: 84, + 0xE013A: 84, + 0xE013B: 84, + 0xE013C: 84, + 0xE013D: 84, + 0xE013E: 84, + 0xE013F: 84, + 0xE0140: 84, + 0xE0141: 84, + 0xE0142: 84, + 0xE0143: 84, + 0xE0144: 84, + 0xE0145: 84, + 0xE0146: 84, + 0xE0147: 84, + 0xE0148: 84, + 0xE0149: 84, + 0xE014A: 84, + 0xE014B: 84, + 0xE014C: 84, + 0xE014D: 84, + 0xE014E: 84, + 0xE014F: 84, + 0xE0150: 84, + 0xE0151: 84, + 0xE0152: 84, + 0xE0153: 84, + 0xE0154: 84, + 0xE0155: 84, + 0xE0156: 84, + 0xE0157: 84, + 0xE0158: 84, + 0xE0159: 84, + 0xE015A: 84, + 0xE015B: 84, + 0xE015C: 84, + 0xE015D: 84, + 0xE015E: 84, + 0xE015F: 84, + 0xE0160: 84, + 0xE0161: 84, + 0xE0162: 84, + 0xE0163: 84, + 0xE0164: 84, + 0xE0165: 84, + 0xE0166: 84, + 0xE0167: 84, + 0xE0168: 84, + 0xE0169: 84, + 0xE016A: 84, + 0xE016B: 84, + 0xE016C: 84, + 0xE016D: 84, + 0xE016E: 84, + 0xE016F: 84, + 0xE0170: 84, + 0xE0171: 84, + 0xE0172: 84, + 0xE0173: 84, + 0xE0174: 84, + 0xE0175: 84, + 0xE0176: 84, + 0xE0177: 84, + 0xE0178: 84, + 0xE0179: 84, + 0xE017A: 84, + 0xE017B: 84, + 0xE017C: 84, + 0xE017D: 84, + 0xE017E: 84, + 0xE017F: 84, + 0xE0180: 84, + 0xE0181: 84, + 0xE0182: 84, + 0xE0183: 84, + 0xE0184: 84, + 0xE0185: 84, + 0xE0186: 84, + 0xE0187: 84, + 0xE0188: 84, + 0xE0189: 84, + 0xE018A: 84, + 0xE018B: 84, + 0xE018C: 84, + 0xE018D: 84, + 0xE018E: 84, + 0xE018F: 84, + 0xE0190: 84, + 0xE0191: 84, + 0xE0192: 84, + 0xE0193: 84, + 0xE0194: 84, + 0xE0195: 84, + 0xE0196: 84, + 0xE0197: 84, + 0xE0198: 84, + 0xE0199: 84, + 0xE019A: 84, + 0xE019B: 84, + 0xE019C: 84, + 0xE019D: 84, + 0xE019E: 84, + 0xE019F: 84, + 0xE01A0: 84, + 0xE01A1: 84, + 0xE01A2: 84, + 0xE01A3: 84, + 0xE01A4: 84, + 0xE01A5: 84, + 0xE01A6: 84, + 0xE01A7: 84, + 0xE01A8: 84, + 0xE01A9: 84, + 0xE01AA: 84, + 0xE01AB: 84, + 0xE01AC: 84, + 0xE01AD: 84, + 0xE01AE: 84, + 0xE01AF: 84, + 0xE01B0: 84, + 0xE01B1: 84, + 0xE01B2: 84, + 0xE01B3: 84, + 0xE01B4: 84, + 0xE01B5: 84, + 0xE01B6: 84, + 0xE01B7: 84, + 0xE01B8: 84, + 0xE01B9: 84, + 0xE01BA: 84, + 0xE01BB: 84, + 0xE01BC: 84, + 0xE01BD: 84, + 0xE01BE: 84, + 0xE01BF: 84, + 0xE01C0: 84, + 0xE01C1: 84, + 0xE01C2: 84, + 0xE01C3: 84, + 0xE01C4: 84, + 0xE01C5: 84, + 0xE01C6: 84, + 0xE01C7: 84, + 0xE01C8: 84, + 0xE01C9: 84, + 0xE01CA: 84, + 0xE01CB: 84, + 0xE01CC: 84, + 0xE01CD: 84, + 0xE01CE: 84, + 0xE01CF: 84, + 0xE01D0: 84, + 0xE01D1: 84, + 0xE01D2: 84, + 0xE01D3: 84, + 0xE01D4: 84, + 0xE01D5: 84, + 0xE01D6: 84, + 0xE01D7: 84, + 0xE01D8: 84, + 0xE01D9: 84, + 0xE01DA: 84, + 0xE01DB: 84, + 0xE01DC: 84, + 0xE01DD: 84, + 0xE01DE: 84, + 0xE01DF: 84, + 0xE01E0: 84, + 0xE01E1: 84, + 0xE01E2: 84, + 0xE01E3: 84, + 0xE01E4: 84, + 0xE01E5: 84, + 0xE01E6: 84, + 0xE01E7: 84, + 0xE01E8: 84, + 0xE01E9: 84, + 0xE01EA: 84, + 0xE01EB: 84, + 0xE01EC: 84, + 0xE01ED: 84, + 0xE01EE: 84, + 0xE01EF: 84, +} +codepoint_classes = { + "PVALID": ( + 0x2D0000002E, + 0x300000003A, + 0x610000007B, + 0xDF000000F7, + 0xF800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010A, + 0x10B0000010C, + 0x10D0000010E, + 0x10F00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011A, + 0x11B0000011C, + 0x11D0000011E, + 0x11F00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012A, + 0x12B0000012C, + 0x12D0000012E, + 0x12F00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13A0000013B, + 0x13C0000013D, + 0x13E0000013F, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14B0000014C, + 0x14D0000014E, + 0x14F00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015A, + 0x15B0000015C, + 0x15D0000015E, + 0x15F00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016A, + 0x16B0000016C, + 0x16D0000016E, + 0x16F00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17A0000017B, + 0x17C0000017D, + 0x17E0000017F, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18C0000018E, + 0x19200000193, + 0x19500000196, + 0x1990000019C, + 0x19E0000019F, + 0x1A1000001A2, + 0x1A3000001A4, + 0x1A5000001A6, + 0x1A8000001A9, + 0x1AA000001AC, + 0x1AD000001AE, + 0x1B0000001B1, + 0x1B4000001B5, + 0x1B6000001B7, + 0x1B9000001BC, + 0x1BD000001C4, + 0x1CE000001CF, + 0x1D0000001D1, + 0x1D2000001D3, + 0x1D4000001D5, + 0x1D6000001D7, + 0x1D8000001D9, + 0x1DA000001DB, + 0x1DC000001DE, + 0x1DF000001E0, + 0x1E1000001E2, + 0x1E3000001E4, + 0x1E5000001E6, + 0x1E7000001E8, + 0x1E9000001EA, + 0x1EB000001EC, + 0x1ED000001EE, + 0x1EF000001F1, + 0x1F5000001F6, + 0x1F9000001FA, + 0x1FB000001FC, + 0x1FD000001FE, + 0x1FF00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020A, + 0x20B0000020C, + 0x20D0000020E, + 0x20F00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021A, + 0x21B0000021C, + 0x21D0000021E, + 0x21F00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022A, + 0x22B0000022C, + 0x22D0000022E, + 0x22F00000230, + 0x23100000232, + 0x2330000023A, + 0x23C0000023D, + 0x23F00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024A, + 0x24B0000024C, + 0x24D0000024E, + 0x24F000002B0, + 0x2B9000002C2, + 0x2C6000002D2, + 0x2EC000002ED, + 0x2EE000002EF, + 0x30000000340, + 0x34200000343, + 0x3460000034F, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37B0000037E, + 0x39000000391, + 0x3AC000003CF, + 0x3D7000003D8, + 0x3D9000003DA, + 0x3DB000003DC, + 0x3DD000003DE, + 0x3DF000003E0, + 0x3E1000003E2, + 0x3E3000003E4, + 0x3E5000003E6, + 0x3E7000003E8, + 0x3E9000003EA, + 0x3EB000003EC, + 0x3ED000003EE, + 0x3EF000003F0, + 0x3F3000003F4, + 0x3F8000003F9, + 0x3FB000003FD, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046A, + 0x46B0000046C, + 0x46D0000046E, + 0x46F00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047A, + 0x47B0000047C, + 0x47D0000047E, + 0x47F00000480, + 0x48100000482, + 0x48300000488, + 0x48B0000048C, + 0x48D0000048E, + 0x48F00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049A, + 0x49B0000049C, + 0x49D0000049E, + 0x49F000004A0, + 0x4A1000004A2, + 0x4A3000004A4, + 0x4A5000004A6, + 0x4A7000004A8, + 0x4A9000004AA, + 0x4AB000004AC, + 0x4AD000004AE, + 0x4AF000004B0, + 0x4B1000004B2, + 0x4B3000004B4, + 0x4B5000004B6, + 0x4B7000004B8, + 0x4B9000004BA, + 0x4BB000004BC, + 0x4BD000004BE, + 0x4BF000004C0, + 0x4C2000004C3, + 0x4C4000004C5, + 0x4C6000004C7, + 0x4C8000004C9, + 0x4CA000004CB, + 0x4CC000004CD, + 0x4CE000004D0, + 0x4D1000004D2, + 0x4D3000004D4, + 0x4D5000004D6, + 0x4D7000004D8, + 0x4D9000004DA, + 0x4DB000004DC, + 0x4DD000004DE, + 0x4DF000004E0, + 0x4E1000004E2, + 0x4E3000004E4, + 0x4E5000004E6, + 0x4E7000004E8, + 0x4E9000004EA, + 0x4EB000004EC, + 0x4ED000004EE, + 0x4EF000004F0, + 0x4F1000004F2, + 0x4F3000004F4, + 0x4F5000004F6, + 0x4F7000004F8, + 0x4F9000004FA, + 0x4FB000004FC, + 0x4FD000004FE, + 0x4FF00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050A, + 0x50B0000050C, + 0x50D0000050E, + 0x50F00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051A, + 0x51B0000051C, + 0x51D0000051E, + 0x51F00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052A, + 0x52B0000052C, + 0x52D0000052E, + 0x52F00000530, + 0x5590000055A, + 0x56000000587, + 0x58800000589, + 0x591000005BE, + 0x5BF000005C0, + 0x5C1000005C3, + 0x5C4000005C6, + 0x5C7000005C8, + 0x5D0000005EB, + 0x5EF000005F3, + 0x6100000061B, + 0x62000000640, + 0x64100000660, + 0x66E00000675, + 0x679000006D4, + 0x6D5000006DD, + 0x6DF000006E9, + 0x6EA000006F0, + 0x6FA00000700, + 0x7100000074B, + 0x74D000007B2, + 0x7C0000007F6, + 0x7FD000007FE, + 0x8000000082E, + 0x8400000085C, + 0x8600000086B, + 0x87000000888, + 0x8890000088F, + 0x898000008E2, + 0x8E300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098D, + 0x98F00000991, + 0x993000009A9, + 0x9AA000009B1, + 0x9B2000009B3, + 0x9B6000009BA, + 0x9BC000009C5, + 0x9C7000009C9, + 0x9CB000009CF, + 0x9D7000009D8, + 0x9E0000009E4, + 0x9E6000009F2, + 0x9FC000009FD, + 0x9FE000009FF, + 0xA0100000A04, + 0xA0500000A0B, + 0xA0F00000A11, + 0xA1300000A29, + 0xA2A00000A31, + 0xA3200000A33, + 0xA3500000A36, + 0xA3800000A3A, + 0xA3C00000A3D, + 0xA3E00000A43, + 0xA4700000A49, + 0xA4B00000A4E, + 0xA5100000A52, + 0xA5C00000A5D, + 0xA6600000A76, + 0xA8100000A84, + 0xA8500000A8E, + 0xA8F00000A92, + 0xA9300000AA9, + 0xAAA00000AB1, + 0xAB200000AB4, + 0xAB500000ABA, + 0xABC00000AC6, + 0xAC700000ACA, + 0xACB00000ACE, + 0xAD000000AD1, + 0xAE000000AE4, + 0xAE600000AF0, + 0xAF900000B00, + 0xB0100000B04, + 0xB0500000B0D, + 0xB0F00000B11, + 0xB1300000B29, + 0xB2A00000B31, + 0xB3200000B34, + 0xB3500000B3A, + 0xB3C00000B45, + 0xB4700000B49, + 0xB4B00000B4E, + 0xB5500000B58, + 0xB5F00000B64, + 0xB6600000B70, + 0xB7100000B72, + 0xB8200000B84, + 0xB8500000B8B, + 0xB8E00000B91, + 0xB9200000B96, + 0xB9900000B9B, + 0xB9C00000B9D, + 0xB9E00000BA0, + 0xBA300000BA5, + 0xBA800000BAB, + 0xBAE00000BBA, + 0xBBE00000BC3, + 0xBC600000BC9, + 0xBCA00000BCE, + 0xBD000000BD1, + 0xBD700000BD8, + 0xBE600000BF0, + 0xC0000000C0D, + 0xC0E00000C11, + 0xC1200000C29, + 0xC2A00000C3A, + 0xC3C00000C45, + 0xC4600000C49, + 0xC4A00000C4E, + 0xC5500000C57, + 0xC5800000C5B, + 0xC5D00000C5E, + 0xC6000000C64, + 0xC6600000C70, + 0xC8000000C84, + 0xC8500000C8D, + 0xC8E00000C91, + 0xC9200000CA9, + 0xCAA00000CB4, + 0xCB500000CBA, + 0xCBC00000CC5, + 0xCC600000CC9, + 0xCCA00000CCE, + 0xCD500000CD7, + 0xCDD00000CDF, + 0xCE000000CE4, + 0xCE600000CF0, + 0xCF100000CF4, + 0xD0000000D0D, + 0xD0E00000D11, + 0xD1200000D45, + 0xD4600000D49, + 0xD4A00000D4F, + 0xD5400000D58, + 0xD5F00000D64, + 0xD6600000D70, + 0xD7A00000D80, + 0xD8100000D84, + 0xD8500000D97, + 0xD9A00000DB2, + 0xDB300000DBC, + 0xDBD00000DBE, + 0xDC000000DC7, + 0xDCA00000DCB, + 0xDCF00000DD5, + 0xDD600000DD7, + 0xDD800000DE0, + 0xDE600000DF0, + 0xDF200000DF4, + 0xE0100000E33, + 0xE3400000E3B, + 0xE4000000E4F, + 0xE5000000E5A, + 0xE8100000E83, + 0xE8400000E85, + 0xE8600000E8B, + 0xE8C00000EA4, + 0xEA500000EA6, + 0xEA700000EB3, + 0xEB400000EBE, + 0xEC000000EC5, + 0xEC600000EC7, + 0xEC800000ECF, + 0xED000000EDA, + 0xEDE00000EE0, + 0xF0000000F01, + 0xF0B00000F0C, + 0xF1800000F1A, + 0xF2000000F2A, + 0xF3500000F36, + 0xF3700000F38, + 0xF3900000F3A, + 0xF3E00000F43, + 0xF4400000F48, + 0xF4900000F4D, + 0xF4E00000F52, + 0xF5300000F57, + 0xF5800000F5C, + 0xF5D00000F69, + 0xF6A00000F6D, + 0xF7100000F73, + 0xF7400000F75, + 0xF7A00000F81, + 0xF8200000F85, + 0xF8600000F93, + 0xF9400000F98, + 0xF9900000F9D, + 0xF9E00000FA2, + 0xFA300000FA7, + 0xFA800000FAC, + 0xFAD00000FB9, + 0xFBA00000FBD, + 0xFC600000FC7, + 0x10000000104A, + 0x10500000109E, + 0x10D0000010FB, + 0x10FD00001100, + 0x120000001249, + 0x124A0000124E, + 0x125000001257, + 0x125800001259, + 0x125A0000125E, + 0x126000001289, + 0x128A0000128E, + 0x1290000012B1, + 0x12B2000012B6, + 0x12B8000012BF, + 0x12C0000012C1, + 0x12C2000012C6, + 0x12C8000012D7, + 0x12D800001311, + 0x131200001316, + 0x13180000135B, + 0x135D00001360, + 0x138000001390, + 0x13A0000013F6, + 0x14010000166D, + 0x166F00001680, + 0x16810000169B, + 0x16A0000016EB, + 0x16F1000016F9, + 0x170000001716, + 0x171F00001735, + 0x174000001754, + 0x17600000176D, + 0x176E00001771, + 0x177200001774, + 0x1780000017B4, + 0x17B6000017D4, + 0x17D7000017D8, + 0x17DC000017DE, + 0x17E0000017EA, + 0x18100000181A, + 0x182000001879, + 0x1880000018AB, + 0x18B0000018F6, + 0x19000000191F, + 0x19200000192C, + 0x19300000193C, + 0x19460000196E, + 0x197000001975, + 0x1980000019AC, + 0x19B0000019CA, + 0x19D0000019DA, + 0x1A0000001A1C, + 0x1A2000001A5F, + 0x1A6000001A7D, + 0x1A7F00001A8A, + 0x1A9000001A9A, + 0x1AA700001AA8, + 0x1AB000001ABE, + 0x1ABF00001ACF, + 0x1B0000001B4D, + 0x1B5000001B5A, + 0x1B6B00001B74, + 0x1B8000001BF4, + 0x1C0000001C38, + 0x1C4000001C4A, + 0x1C4D00001C7E, + 0x1CD000001CD3, + 0x1CD400001CFB, + 0x1D0000001D2C, + 0x1D2F00001D30, + 0x1D3B00001D3C, + 0x1D4E00001D4F, + 0x1D6B00001D78, + 0x1D7900001D9B, + 0x1DC000001E00, + 0x1E0100001E02, + 0x1E0300001E04, + 0x1E0500001E06, + 0x1E0700001E08, + 0x1E0900001E0A, + 0x1E0B00001E0C, + 0x1E0D00001E0E, + 0x1E0F00001E10, + 0x1E1100001E12, + 0x1E1300001E14, + 0x1E1500001E16, + 0x1E1700001E18, + 0x1E1900001E1A, + 0x1E1B00001E1C, + 0x1E1D00001E1E, + 0x1E1F00001E20, + 0x1E2100001E22, + 0x1E2300001E24, + 0x1E2500001E26, + 0x1E2700001E28, + 0x1E2900001E2A, + 0x1E2B00001E2C, + 0x1E2D00001E2E, + 0x1E2F00001E30, + 0x1E3100001E32, + 0x1E3300001E34, + 0x1E3500001E36, + 0x1E3700001E38, + 0x1E3900001E3A, + 0x1E3B00001E3C, + 0x1E3D00001E3E, + 0x1E3F00001E40, + 0x1E4100001E42, + 0x1E4300001E44, + 0x1E4500001E46, + 0x1E4700001E48, + 0x1E4900001E4A, + 0x1E4B00001E4C, + 0x1E4D00001E4E, + 0x1E4F00001E50, + 0x1E5100001E52, + 0x1E5300001E54, + 0x1E5500001E56, + 0x1E5700001E58, + 0x1E5900001E5A, + 0x1E5B00001E5C, + 0x1E5D00001E5E, + 0x1E5F00001E60, + 0x1E6100001E62, + 0x1E6300001E64, + 0x1E6500001E66, + 0x1E6700001E68, + 0x1E6900001E6A, + 0x1E6B00001E6C, + 0x1E6D00001E6E, + 0x1E6F00001E70, + 0x1E7100001E72, + 0x1E7300001E74, + 0x1E7500001E76, + 0x1E7700001E78, + 0x1E7900001E7A, + 0x1E7B00001E7C, + 0x1E7D00001E7E, + 0x1E7F00001E80, + 0x1E8100001E82, + 0x1E8300001E84, + 0x1E8500001E86, + 0x1E8700001E88, + 0x1E8900001E8A, + 0x1E8B00001E8C, + 0x1E8D00001E8E, + 0x1E8F00001E90, + 0x1E9100001E92, + 0x1E9300001E94, + 0x1E9500001E9A, + 0x1E9C00001E9E, + 0x1E9F00001EA0, + 0x1EA100001EA2, + 0x1EA300001EA4, + 0x1EA500001EA6, + 0x1EA700001EA8, + 0x1EA900001EAA, + 0x1EAB00001EAC, + 0x1EAD00001EAE, + 0x1EAF00001EB0, + 0x1EB100001EB2, + 0x1EB300001EB4, + 0x1EB500001EB6, + 0x1EB700001EB8, + 0x1EB900001EBA, + 0x1EBB00001EBC, + 0x1EBD00001EBE, + 0x1EBF00001EC0, + 0x1EC100001EC2, + 0x1EC300001EC4, + 0x1EC500001EC6, + 0x1EC700001EC8, + 0x1EC900001ECA, + 0x1ECB00001ECC, + 0x1ECD00001ECE, + 0x1ECF00001ED0, + 0x1ED100001ED2, + 0x1ED300001ED4, + 0x1ED500001ED6, + 0x1ED700001ED8, + 0x1ED900001EDA, + 0x1EDB00001EDC, + 0x1EDD00001EDE, + 0x1EDF00001EE0, + 0x1EE100001EE2, + 0x1EE300001EE4, + 0x1EE500001EE6, + 0x1EE700001EE8, + 0x1EE900001EEA, + 0x1EEB00001EEC, + 0x1EED00001EEE, + 0x1EEF00001EF0, + 0x1EF100001EF2, + 0x1EF300001EF4, + 0x1EF500001EF6, + 0x1EF700001EF8, + 0x1EF900001EFA, + 0x1EFB00001EFC, + 0x1EFD00001EFE, + 0x1EFF00001F08, + 0x1F1000001F16, + 0x1F2000001F28, + 0x1F3000001F38, + 0x1F4000001F46, + 0x1F5000001F58, + 0x1F6000001F68, + 0x1F7000001F71, + 0x1F7200001F73, + 0x1F7400001F75, + 0x1F7600001F77, + 0x1F7800001F79, + 0x1F7A00001F7B, + 0x1F7C00001F7D, + 0x1FB000001FB2, + 0x1FB600001FB7, + 0x1FC600001FC7, + 0x1FD000001FD3, + 0x1FD600001FD8, + 0x1FE000001FE3, + 0x1FE400001FE8, + 0x1FF600001FF7, + 0x214E0000214F, + 0x218400002185, + 0x2C3000002C60, + 0x2C6100002C62, + 0x2C6500002C67, + 0x2C6800002C69, + 0x2C6A00002C6B, + 0x2C6C00002C6D, + 0x2C7100002C72, + 0x2C7300002C75, + 0x2C7600002C7C, + 0x2C8100002C82, + 0x2C8300002C84, + 0x2C8500002C86, + 0x2C8700002C88, + 0x2C8900002C8A, + 0x2C8B00002C8C, + 0x2C8D00002C8E, + 0x2C8F00002C90, + 0x2C9100002C92, + 0x2C9300002C94, + 0x2C9500002C96, + 0x2C9700002C98, + 0x2C9900002C9A, + 0x2C9B00002C9C, + 0x2C9D00002C9E, + 0x2C9F00002CA0, + 0x2CA100002CA2, + 0x2CA300002CA4, + 0x2CA500002CA6, + 0x2CA700002CA8, + 0x2CA900002CAA, + 0x2CAB00002CAC, + 0x2CAD00002CAE, + 0x2CAF00002CB0, + 0x2CB100002CB2, + 0x2CB300002CB4, + 0x2CB500002CB6, + 0x2CB700002CB8, + 0x2CB900002CBA, + 0x2CBB00002CBC, + 0x2CBD00002CBE, + 0x2CBF00002CC0, + 0x2CC100002CC2, + 0x2CC300002CC4, + 0x2CC500002CC6, + 0x2CC700002CC8, + 0x2CC900002CCA, + 0x2CCB00002CCC, + 0x2CCD00002CCE, + 0x2CCF00002CD0, + 0x2CD100002CD2, + 0x2CD300002CD4, + 0x2CD500002CD6, + 0x2CD700002CD8, + 0x2CD900002CDA, + 0x2CDB00002CDC, + 0x2CDD00002CDE, + 0x2CDF00002CE0, + 0x2CE100002CE2, + 0x2CE300002CE5, + 0x2CEC00002CED, + 0x2CEE00002CF2, + 0x2CF300002CF4, + 0x2D0000002D26, + 0x2D2700002D28, + 0x2D2D00002D2E, + 0x2D3000002D68, + 0x2D7F00002D97, + 0x2DA000002DA7, + 0x2DA800002DAF, + 0x2DB000002DB7, + 0x2DB800002DBF, + 0x2DC000002DC7, + 0x2DC800002DCF, + 0x2DD000002DD7, + 0x2DD800002DDF, + 0x2DE000002E00, + 0x2E2F00002E30, + 0x300500003008, + 0x302A0000302E, + 0x303C0000303D, + 0x304100003097, + 0x30990000309B, + 0x309D0000309F, + 0x30A1000030FB, + 0x30FC000030FF, + 0x310500003130, + 0x31A0000031C0, + 0x31F000003200, + 0x340000004DC0, + 0x4E000000A48D, + 0xA4D00000A4FE, + 0xA5000000A60D, + 0xA6100000A62C, + 0xA6410000A642, + 0xA6430000A644, + 0xA6450000A646, + 0xA6470000A648, + 0xA6490000A64A, + 0xA64B0000A64C, + 0xA64D0000A64E, + 0xA64F0000A650, + 0xA6510000A652, + 0xA6530000A654, + 0xA6550000A656, + 0xA6570000A658, + 0xA6590000A65A, + 0xA65B0000A65C, + 0xA65D0000A65E, + 0xA65F0000A660, + 0xA6610000A662, + 0xA6630000A664, + 0xA6650000A666, + 0xA6670000A668, + 0xA6690000A66A, + 0xA66B0000A66C, + 0xA66D0000A670, + 0xA6740000A67E, + 0xA67F0000A680, + 0xA6810000A682, + 0xA6830000A684, + 0xA6850000A686, + 0xA6870000A688, + 0xA6890000A68A, + 0xA68B0000A68C, + 0xA68D0000A68E, + 0xA68F0000A690, + 0xA6910000A692, + 0xA6930000A694, + 0xA6950000A696, + 0xA6970000A698, + 0xA6990000A69A, + 0xA69B0000A69C, + 0xA69E0000A6E6, + 0xA6F00000A6F2, + 0xA7170000A720, + 0xA7230000A724, + 0xA7250000A726, + 0xA7270000A728, + 0xA7290000A72A, + 0xA72B0000A72C, + 0xA72D0000A72E, + 0xA72F0000A732, + 0xA7330000A734, + 0xA7350000A736, + 0xA7370000A738, + 0xA7390000A73A, + 0xA73B0000A73C, + 0xA73D0000A73E, + 0xA73F0000A740, + 0xA7410000A742, + 0xA7430000A744, + 0xA7450000A746, + 0xA7470000A748, + 0xA7490000A74A, + 0xA74B0000A74C, + 0xA74D0000A74E, + 0xA74F0000A750, + 0xA7510000A752, + 0xA7530000A754, + 0xA7550000A756, + 0xA7570000A758, + 0xA7590000A75A, + 0xA75B0000A75C, + 0xA75D0000A75E, + 0xA75F0000A760, + 0xA7610000A762, + 0xA7630000A764, + 0xA7650000A766, + 0xA7670000A768, + 0xA7690000A76A, + 0xA76B0000A76C, + 0xA76D0000A76E, + 0xA76F0000A770, + 0xA7710000A779, + 0xA77A0000A77B, + 0xA77C0000A77D, + 0xA77F0000A780, + 0xA7810000A782, + 0xA7830000A784, + 0xA7850000A786, + 0xA7870000A789, + 0xA78C0000A78D, + 0xA78E0000A790, + 0xA7910000A792, + 0xA7930000A796, + 0xA7970000A798, + 0xA7990000A79A, + 0xA79B0000A79C, + 0xA79D0000A79E, + 0xA79F0000A7A0, + 0xA7A10000A7A2, + 0xA7A30000A7A4, + 0xA7A50000A7A6, + 0xA7A70000A7A8, + 0xA7A90000A7AA, + 0xA7AF0000A7B0, + 0xA7B50000A7B6, + 0xA7B70000A7B8, + 0xA7B90000A7BA, + 0xA7BB0000A7BC, + 0xA7BD0000A7BE, + 0xA7BF0000A7C0, + 0xA7C10000A7C2, + 0xA7C30000A7C4, + 0xA7C80000A7C9, + 0xA7CA0000A7CB, + 0xA7D10000A7D2, + 0xA7D30000A7D4, + 0xA7D50000A7D6, + 0xA7D70000A7D8, + 0xA7D90000A7DA, + 0xA7F60000A7F8, + 0xA7FA0000A828, + 0xA82C0000A82D, + 0xA8400000A874, + 0xA8800000A8C6, + 0xA8D00000A8DA, + 0xA8E00000A8F8, + 0xA8FB0000A8FC, + 0xA8FD0000A92E, + 0xA9300000A954, + 0xA9800000A9C1, + 0xA9CF0000A9DA, + 0xA9E00000A9FF, + 0xAA000000AA37, + 0xAA400000AA4E, + 0xAA500000AA5A, + 0xAA600000AA77, + 0xAA7A0000AAC3, + 0xAADB0000AADE, + 0xAAE00000AAF0, + 0xAAF20000AAF7, + 0xAB010000AB07, + 0xAB090000AB0F, + 0xAB110000AB17, + 0xAB200000AB27, + 0xAB280000AB2F, + 0xAB300000AB5B, + 0xAB600000AB69, + 0xABC00000ABEB, + 0xABEC0000ABEE, + 0xABF00000ABFA, + 0xAC000000D7A4, + 0xFA0E0000FA10, + 0xFA110000FA12, + 0xFA130000FA15, + 0xFA1F0000FA20, + 0xFA210000FA22, + 0xFA230000FA25, + 0xFA270000FA2A, + 0xFB1E0000FB1F, + 0xFE200000FE30, + 0xFE730000FE74, + 0x100000001000C, + 0x1000D00010027, + 0x100280001003B, + 0x1003C0001003E, + 0x1003F0001004E, + 0x100500001005E, + 0x10080000100FB, + 0x101FD000101FE, + 0x102800001029D, + 0x102A0000102D1, + 0x102E0000102E1, + 0x1030000010320, + 0x1032D00010341, + 0x103420001034A, + 0x103500001037B, + 0x103800001039E, + 0x103A0000103C4, + 0x103C8000103D0, + 0x104280001049E, + 0x104A0000104AA, + 0x104D8000104FC, + 0x1050000010528, + 0x1053000010564, + 0x10597000105A2, + 0x105A3000105B2, + 0x105B3000105BA, + 0x105BB000105BD, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1078000010781, + 0x1080000010806, + 0x1080800010809, + 0x1080A00010836, + 0x1083700010839, + 0x1083C0001083D, + 0x1083F00010856, + 0x1086000010877, + 0x108800001089F, + 0x108E0000108F3, + 0x108F4000108F6, + 0x1090000010916, + 0x109200001093A, + 0x10980000109B8, + 0x109BE000109C0, + 0x10A0000010A04, + 0x10A0500010A07, + 0x10A0C00010A14, + 0x10A1500010A18, + 0x10A1900010A36, + 0x10A3800010A3B, + 0x10A3F00010A40, + 0x10A6000010A7D, + 0x10A8000010A9D, + 0x10AC000010AC8, + 0x10AC900010AE7, + 0x10B0000010B36, + 0x10B4000010B56, + 0x10B6000010B73, + 0x10B8000010B92, + 0x10C0000010C49, + 0x10CC000010CF3, + 0x10D0000010D28, + 0x10D3000010D3A, + 0x10E8000010EAA, + 0x10EAB00010EAD, + 0x10EB000010EB2, + 0x10EFD00010F1D, + 0x10F2700010F28, + 0x10F3000010F51, + 0x10F7000010F86, + 0x10FB000010FC5, + 0x10FE000010FF7, + 0x1100000011047, + 0x1106600011076, + 0x1107F000110BB, + 0x110C2000110C3, + 0x110D0000110E9, + 0x110F0000110FA, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111C5, + 0x111C9000111CD, + 0x111CE000111DB, + 0x111DC000111DD, + 0x1120000011212, + 0x1121300011238, + 0x1123E00011242, + 0x1128000011287, + 0x1128800011289, + 0x1128A0001128E, + 0x1128F0001129E, + 0x1129F000112A9, + 0x112B0000112EB, + 0x112F0000112FA, + 0x1130000011304, + 0x113050001130D, + 0x1130F00011311, + 0x1131300011329, + 0x1132A00011331, + 0x1133200011334, + 0x113350001133A, + 0x1133B00011345, + 0x1134700011349, + 0x1134B0001134E, + 0x1135000011351, + 0x1135700011358, + 0x1135D00011364, + 0x113660001136D, + 0x1137000011375, + 0x114000001144B, + 0x114500001145A, + 0x1145E00011462, + 0x11480000114C6, + 0x114C7000114C8, + 0x114D0000114DA, + 0x11580000115B6, + 0x115B8000115C1, + 0x115D8000115DE, + 0x1160000011641, + 0x1164400011645, + 0x116500001165A, + 0x11680000116B9, + 0x116C0000116CA, + 0x117000001171B, + 0x1171D0001172C, + 0x117300001173A, + 0x1174000011747, + 0x118000001183B, + 0x118C0000118EA, + 0x118FF00011907, + 0x119090001190A, + 0x1190C00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193B00011944, + 0x119500001195A, + 0x119A0000119A8, + 0x119AA000119D8, + 0x119DA000119E2, + 0x119E3000119E5, + 0x11A0000011A3F, + 0x11A4700011A48, + 0x11A5000011A9A, + 0x11A9D00011A9E, + 0x11AB000011AF9, + 0x11C0000011C09, + 0x11C0A00011C37, + 0x11C3800011C41, + 0x11C5000011C5A, + 0x11C7200011C90, + 0x11C9200011CA8, + 0x11CA900011CB7, + 0x11D0000011D07, + 0x11D0800011D0A, + 0x11D0B00011D37, + 0x11D3A00011D3B, + 0x11D3C00011D3E, + 0x11D3F00011D48, + 0x11D5000011D5A, + 0x11D6000011D66, + 0x11D6700011D69, + 0x11D6A00011D8F, + 0x11D9000011D92, + 0x11D9300011D99, + 0x11DA000011DAA, + 0x11EE000011EF7, + 0x11F0000011F11, + 0x11F1200011F3B, + 0x11F3E00011F43, + 0x11F5000011F5A, + 0x11FB000011FB1, + 0x120000001239A, + 0x1248000012544, + 0x12F9000012FF1, + 0x1300000013430, + 0x1344000013456, + 0x1440000014647, + 0x1680000016A39, + 0x16A4000016A5F, + 0x16A6000016A6A, + 0x16A7000016ABF, + 0x16AC000016ACA, + 0x16AD000016AEE, + 0x16AF000016AF5, + 0x16B0000016B37, + 0x16B4000016B44, + 0x16B5000016B5A, + 0x16B6300016B78, + 0x16B7D00016B90, + 0x16E6000016E80, + 0x16F0000016F4B, + 0x16F4F00016F88, + 0x16F8F00016FA0, + 0x16FE000016FE2, + 0x16FE300016FE5, + 0x16FF000016FF2, + 0x17000000187F8, + 0x1880000018CD6, + 0x18D0000018D09, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B123, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1B1550001B156, + 0x1B1640001B168, + 0x1B1700001B2FC, + 0x1BC000001BC6B, + 0x1BC700001BC7D, + 0x1BC800001BC89, + 0x1BC900001BC9A, + 0x1BC9D0001BC9F, + 0x1CF000001CF2E, + 0x1CF300001CF47, + 0x1DA000001DA37, + 0x1DA3B0001DA6D, + 0x1DA750001DA76, + 0x1DA840001DA85, + 0x1DA9B0001DAA0, + 0x1DAA10001DAB0, + 0x1DF000001DF1F, + 0x1DF250001DF2B, + 0x1E0000001E007, + 0x1E0080001E019, + 0x1E01B0001E022, + 0x1E0230001E025, + 0x1E0260001E02B, + 0x1E08F0001E090, + 0x1E1000001E12D, + 0x1E1300001E13E, + 0x1E1400001E14A, + 0x1E14E0001E14F, + 0x1E2900001E2AF, + 0x1E2C00001E2FA, + 0x1E4D00001E4FA, + 0x1E7E00001E7E7, + 0x1E7E80001E7EC, + 0x1E7ED0001E7EF, + 0x1E7F00001E7FF, + 0x1E8000001E8C5, + 0x1E8D00001E8D7, + 0x1E9220001E94C, + 0x1E9500001E95A, + 0x200000002A6E0, + 0x2A7000002B73A, + 0x2B7400002B81E, + 0x2B8200002CEA2, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x300000003134B, + 0x31350000323B0, + ), + "CONTEXTJ": (0x200C0000200E,), + "CONTEXTO": ( + 0xB7000000B8, + 0x37500000376, + 0x5F3000005F5, + 0x6600000066A, + 0x6F0000006FA, + 0x30FB000030FC, + ), +} diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/intranges.py b/venv/lib/python3.8/site-packages/pip/_vendor/idna/intranges.py new file mode 100644 index 000000000..7bfaa8d80 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/idna/intranges.py @@ -0,0 +1,57 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect +from typing import List, Tuple + + +def intranges_from_list(list_: List[int]) -> Tuple[int, ...]: + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i + 1 < len(sorted_list): + if sorted_list[i] == sorted_list[i + 1] - 1: + continue + current_range = sorted_list[last_write + 1 : i + 1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + + +def _encode_range(start: int, end: int) -> int: + return (start << 32) | end + + +def _decode_range(r: int) -> Tuple[int, int]: + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool: + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos - 1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/package_data.py b/venv/lib/python3.8/site-packages/pip/_vendor/idna/package_data.py new file mode 100644 index 000000000..514ff7e2e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/idna/package_data.py @@ -0,0 +1 @@ +__version__ = "3.10" diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/py.typed b/venv/lib/python3.8/site-packages/pip/_vendor/idna/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/idna/uts46data.py b/venv/lib/python3.8/site-packages/pip/_vendor/idna/uts46data.py new file mode 100644 index 000000000..eb8943274 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/idna/uts46data.py @@ -0,0 +1,8681 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +from typing import List, Tuple, Union + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "15.1.0" + + +def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x0, "3"), + (0x1, "3"), + (0x2, "3"), + (0x3, "3"), + (0x4, "3"), + (0x5, "3"), + (0x6, "3"), + (0x7, "3"), + (0x8, "3"), + (0x9, "3"), + (0xA, "3"), + (0xB, "3"), + (0xC, "3"), + (0xD, "3"), + (0xE, "3"), + (0xF, "3"), + (0x10, "3"), + (0x11, "3"), + (0x12, "3"), + (0x13, "3"), + (0x14, "3"), + (0x15, "3"), + (0x16, "3"), + (0x17, "3"), + (0x18, "3"), + (0x19, "3"), + (0x1A, "3"), + (0x1B, "3"), + (0x1C, "3"), + (0x1D, "3"), + (0x1E, "3"), + (0x1F, "3"), + (0x20, "3"), + (0x21, "3"), + (0x22, "3"), + (0x23, "3"), + (0x24, "3"), + (0x25, "3"), + (0x26, "3"), + (0x27, "3"), + (0x28, "3"), + (0x29, "3"), + (0x2A, "3"), + (0x2B, "3"), + (0x2C, "3"), + (0x2D, "V"), + (0x2E, "V"), + (0x2F, "3"), + (0x30, "V"), + (0x31, "V"), + (0x32, "V"), + (0x33, "V"), + (0x34, "V"), + (0x35, "V"), + (0x36, "V"), + (0x37, "V"), + (0x38, "V"), + (0x39, "V"), + (0x3A, "3"), + (0x3B, "3"), + (0x3C, "3"), + (0x3D, "3"), + (0x3E, "3"), + (0x3F, "3"), + (0x40, "3"), + (0x41, "M", "a"), + (0x42, "M", "b"), + (0x43, "M", "c"), + (0x44, "M", "d"), + (0x45, "M", "e"), + (0x46, "M", "f"), + (0x47, "M", "g"), + (0x48, "M", "h"), + (0x49, "M", "i"), + (0x4A, "M", "j"), + (0x4B, "M", "k"), + (0x4C, "M", "l"), + (0x4D, "M", "m"), + (0x4E, "M", "n"), + (0x4F, "M", "o"), + (0x50, "M", "p"), + (0x51, "M", "q"), + (0x52, "M", "r"), + (0x53, "M", "s"), + (0x54, "M", "t"), + (0x55, "M", "u"), + (0x56, "M", "v"), + (0x57, "M", "w"), + (0x58, "M", "x"), + (0x59, "M", "y"), + (0x5A, "M", "z"), + (0x5B, "3"), + (0x5C, "3"), + (0x5D, "3"), + (0x5E, "3"), + (0x5F, "3"), + (0x60, "3"), + (0x61, "V"), + (0x62, "V"), + (0x63, "V"), + ] + + +def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x64, "V"), + (0x65, "V"), + (0x66, "V"), + (0x67, "V"), + (0x68, "V"), + (0x69, "V"), + (0x6A, "V"), + (0x6B, "V"), + (0x6C, "V"), + (0x6D, "V"), + (0x6E, "V"), + (0x6F, "V"), + (0x70, "V"), + (0x71, "V"), + (0x72, "V"), + (0x73, "V"), + (0x74, "V"), + (0x75, "V"), + (0x76, "V"), + (0x77, "V"), + (0x78, "V"), + (0x79, "V"), + (0x7A, "V"), + (0x7B, "3"), + (0x7C, "3"), + (0x7D, "3"), + (0x7E, "3"), + (0x7F, "3"), + (0x80, "X"), + (0x81, "X"), + (0x82, "X"), + (0x83, "X"), + (0x84, "X"), + (0x85, "X"), + (0x86, "X"), + (0x87, "X"), + (0x88, "X"), + (0x89, "X"), + (0x8A, "X"), + (0x8B, "X"), + (0x8C, "X"), + (0x8D, "X"), + (0x8E, "X"), + (0x8F, "X"), + (0x90, "X"), + (0x91, "X"), + (0x92, "X"), + (0x93, "X"), + (0x94, "X"), + (0x95, "X"), + (0x96, "X"), + (0x97, "X"), + (0x98, "X"), + (0x99, "X"), + (0x9A, "X"), + (0x9B, "X"), + (0x9C, "X"), + (0x9D, "X"), + (0x9E, "X"), + (0x9F, "X"), + (0xA0, "3", " "), + (0xA1, "V"), + (0xA2, "V"), + (0xA3, "V"), + (0xA4, "V"), + (0xA5, "V"), + (0xA6, "V"), + (0xA7, "V"), + (0xA8, "3", " ̈"), + (0xA9, "V"), + (0xAA, "M", "a"), + (0xAB, "V"), + (0xAC, "V"), + (0xAD, "I"), + (0xAE, "V"), + (0xAF, "3", " ̄"), + (0xB0, "V"), + (0xB1, "V"), + (0xB2, "M", "2"), + (0xB3, "M", "3"), + (0xB4, "3", " ́"), + (0xB5, "M", "μ"), + (0xB6, "V"), + (0xB7, "V"), + (0xB8, "3", " ̧"), + (0xB9, "M", "1"), + (0xBA, "M", "o"), + (0xBB, "V"), + (0xBC, "M", "1⁄4"), + (0xBD, "M", "1⁄2"), + (0xBE, "M", "3⁄4"), + (0xBF, "V"), + (0xC0, "M", "à"), + (0xC1, "M", "á"), + (0xC2, "M", "â"), + (0xC3, "M", "ã"), + (0xC4, "M", "ä"), + (0xC5, "M", "å"), + (0xC6, "M", "æ"), + (0xC7, "M", "ç"), + ] + + +def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC8, "M", "è"), + (0xC9, "M", "é"), + (0xCA, "M", "ê"), + (0xCB, "M", "ë"), + (0xCC, "M", "ì"), + (0xCD, "M", "í"), + (0xCE, "M", "î"), + (0xCF, "M", "ï"), + (0xD0, "M", "ð"), + (0xD1, "M", "ñ"), + (0xD2, "M", "ò"), + (0xD3, "M", "ó"), + (0xD4, "M", "ô"), + (0xD5, "M", "õ"), + (0xD6, "M", "ö"), + (0xD7, "V"), + (0xD8, "M", "ø"), + (0xD9, "M", "ù"), + (0xDA, "M", "ú"), + (0xDB, "M", "û"), + (0xDC, "M", "ü"), + (0xDD, "M", "ý"), + (0xDE, "M", "þ"), + (0xDF, "D", "ss"), + (0xE0, "V"), + (0xE1, "V"), + (0xE2, "V"), + (0xE3, "V"), + (0xE4, "V"), + (0xE5, "V"), + (0xE6, "V"), + (0xE7, "V"), + (0xE8, "V"), + (0xE9, "V"), + (0xEA, "V"), + (0xEB, "V"), + (0xEC, "V"), + (0xED, "V"), + (0xEE, "V"), + (0xEF, "V"), + (0xF0, "V"), + (0xF1, "V"), + (0xF2, "V"), + (0xF3, "V"), + (0xF4, "V"), + (0xF5, "V"), + (0xF6, "V"), + (0xF7, "V"), + (0xF8, "V"), + (0xF9, "V"), + (0xFA, "V"), + (0xFB, "V"), + (0xFC, "V"), + (0xFD, "V"), + (0xFE, "V"), + (0xFF, "V"), + (0x100, "M", "ā"), + (0x101, "V"), + (0x102, "M", "ă"), + (0x103, "V"), + (0x104, "M", "ą"), + (0x105, "V"), + (0x106, "M", "ć"), + (0x107, "V"), + (0x108, "M", "ĉ"), + (0x109, "V"), + (0x10A, "M", "ċ"), + (0x10B, "V"), + (0x10C, "M", "č"), + (0x10D, "V"), + (0x10E, "M", "ď"), + (0x10F, "V"), + (0x110, "M", "đ"), + (0x111, "V"), + (0x112, "M", "ē"), + (0x113, "V"), + (0x114, "M", "ĕ"), + (0x115, "V"), + (0x116, "M", "ė"), + (0x117, "V"), + (0x118, "M", "ę"), + (0x119, "V"), + (0x11A, "M", "ě"), + (0x11B, "V"), + (0x11C, "M", "ĝ"), + (0x11D, "V"), + (0x11E, "M", "ğ"), + (0x11F, "V"), + (0x120, "M", "ġ"), + (0x121, "V"), + (0x122, "M", "ģ"), + (0x123, "V"), + (0x124, "M", "ĥ"), + (0x125, "V"), + (0x126, "M", "ħ"), + (0x127, "V"), + (0x128, "M", "ĩ"), + (0x129, "V"), + (0x12A, "M", "ī"), + (0x12B, "V"), + ] + + +def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x12C, "M", "ĭ"), + (0x12D, "V"), + (0x12E, "M", "į"), + (0x12F, "V"), + (0x130, "M", "i̇"), + (0x131, "V"), + (0x132, "M", "ij"), + (0x134, "M", "ĵ"), + (0x135, "V"), + (0x136, "M", "ķ"), + (0x137, "V"), + (0x139, "M", "ĺ"), + (0x13A, "V"), + (0x13B, "M", "ļ"), + (0x13C, "V"), + (0x13D, "M", "ľ"), + (0x13E, "V"), + (0x13F, "M", "l·"), + (0x141, "M", "ł"), + (0x142, "V"), + (0x143, "M", "ń"), + (0x144, "V"), + (0x145, "M", "ņ"), + (0x146, "V"), + (0x147, "M", "ň"), + (0x148, "V"), + (0x149, "M", "ʼn"), + (0x14A, "M", "ŋ"), + (0x14B, "V"), + (0x14C, "M", "ō"), + (0x14D, "V"), + (0x14E, "M", "ŏ"), + (0x14F, "V"), + (0x150, "M", "ő"), + (0x151, "V"), + (0x152, "M", "œ"), + (0x153, "V"), + (0x154, "M", "ŕ"), + (0x155, "V"), + (0x156, "M", "ŗ"), + (0x157, "V"), + (0x158, "M", "ř"), + (0x159, "V"), + (0x15A, "M", "ś"), + (0x15B, "V"), + (0x15C, "M", "ŝ"), + (0x15D, "V"), + (0x15E, "M", "ş"), + (0x15F, "V"), + (0x160, "M", "š"), + (0x161, "V"), + (0x162, "M", "ţ"), + (0x163, "V"), + (0x164, "M", "ť"), + (0x165, "V"), + (0x166, "M", "ŧ"), + (0x167, "V"), + (0x168, "M", "ũ"), + (0x169, "V"), + (0x16A, "M", "ū"), + (0x16B, "V"), + (0x16C, "M", "ŭ"), + (0x16D, "V"), + (0x16E, "M", "ů"), + (0x16F, "V"), + (0x170, "M", "ű"), + (0x171, "V"), + (0x172, "M", "ų"), + (0x173, "V"), + (0x174, "M", "ŵ"), + (0x175, "V"), + (0x176, "M", "ŷ"), + (0x177, "V"), + (0x178, "M", "ÿ"), + (0x179, "M", "ź"), + (0x17A, "V"), + (0x17B, "M", "ż"), + (0x17C, "V"), + (0x17D, "M", "ž"), + (0x17E, "V"), + (0x17F, "M", "s"), + (0x180, "V"), + (0x181, "M", "ɓ"), + (0x182, "M", "ƃ"), + (0x183, "V"), + (0x184, "M", "ƅ"), + (0x185, "V"), + (0x186, "M", "ɔ"), + (0x187, "M", "ƈ"), + (0x188, "V"), + (0x189, "M", "ɖ"), + (0x18A, "M", "ɗ"), + (0x18B, "M", "ƌ"), + (0x18C, "V"), + (0x18E, "M", "ǝ"), + (0x18F, "M", "ə"), + (0x190, "M", "ɛ"), + (0x191, "M", "ƒ"), + (0x192, "V"), + (0x193, "M", "ɠ"), + ] + + +def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x194, "M", "ɣ"), + (0x195, "V"), + (0x196, "M", "ɩ"), + (0x197, "M", "ɨ"), + (0x198, "M", "ƙ"), + (0x199, "V"), + (0x19C, "M", "ɯ"), + (0x19D, "M", "ɲ"), + (0x19E, "V"), + (0x19F, "M", "ɵ"), + (0x1A0, "M", "ơ"), + (0x1A1, "V"), + (0x1A2, "M", "ƣ"), + (0x1A3, "V"), + (0x1A4, "M", "ƥ"), + (0x1A5, "V"), + (0x1A6, "M", "ʀ"), + (0x1A7, "M", "ƨ"), + (0x1A8, "V"), + (0x1A9, "M", "ʃ"), + (0x1AA, "V"), + (0x1AC, "M", "ƭ"), + (0x1AD, "V"), + (0x1AE, "M", "ʈ"), + (0x1AF, "M", "ư"), + (0x1B0, "V"), + (0x1B1, "M", "ʊ"), + (0x1B2, "M", "ʋ"), + (0x1B3, "M", "ƴ"), + (0x1B4, "V"), + (0x1B5, "M", "ƶ"), + (0x1B6, "V"), + (0x1B7, "M", "ʒ"), + (0x1B8, "M", "ƹ"), + (0x1B9, "V"), + (0x1BC, "M", "ƽ"), + (0x1BD, "V"), + (0x1C4, "M", "dž"), + (0x1C7, "M", "lj"), + (0x1CA, "M", "nj"), + (0x1CD, "M", "ǎ"), + (0x1CE, "V"), + (0x1CF, "M", "ǐ"), + (0x1D0, "V"), + (0x1D1, "M", "ǒ"), + (0x1D2, "V"), + (0x1D3, "M", "ǔ"), + (0x1D4, "V"), + (0x1D5, "M", "ǖ"), + (0x1D6, "V"), + (0x1D7, "M", "ǘ"), + (0x1D8, "V"), + (0x1D9, "M", "ǚ"), + (0x1DA, "V"), + (0x1DB, "M", "ǜ"), + (0x1DC, "V"), + (0x1DE, "M", "ǟ"), + (0x1DF, "V"), + (0x1E0, "M", "ǡ"), + (0x1E1, "V"), + (0x1E2, "M", "ǣ"), + (0x1E3, "V"), + (0x1E4, "M", "ǥ"), + (0x1E5, "V"), + (0x1E6, "M", "ǧ"), + (0x1E7, "V"), + (0x1E8, "M", "ǩ"), + (0x1E9, "V"), + (0x1EA, "M", "ǫ"), + (0x1EB, "V"), + (0x1EC, "M", "ǭ"), + (0x1ED, "V"), + (0x1EE, "M", "ǯ"), + (0x1EF, "V"), + (0x1F1, "M", "dz"), + (0x1F4, "M", "ǵ"), + (0x1F5, "V"), + (0x1F6, "M", "ƕ"), + (0x1F7, "M", "ƿ"), + (0x1F8, "M", "ǹ"), + (0x1F9, "V"), + (0x1FA, "M", "ǻ"), + (0x1FB, "V"), + (0x1FC, "M", "ǽ"), + (0x1FD, "V"), + (0x1FE, "M", "ǿ"), + (0x1FF, "V"), + (0x200, "M", "ȁ"), + (0x201, "V"), + (0x202, "M", "ȃ"), + (0x203, "V"), + (0x204, "M", "ȅ"), + (0x205, "V"), + (0x206, "M", "ȇ"), + (0x207, "V"), + (0x208, "M", "ȉ"), + (0x209, "V"), + (0x20A, "M", "ȋ"), + (0x20B, "V"), + (0x20C, "M", "ȍ"), + ] + + +def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x20D, "V"), + (0x20E, "M", "ȏ"), + (0x20F, "V"), + (0x210, "M", "ȑ"), + (0x211, "V"), + (0x212, "M", "ȓ"), + (0x213, "V"), + (0x214, "M", "ȕ"), + (0x215, "V"), + (0x216, "M", "ȗ"), + (0x217, "V"), + (0x218, "M", "ș"), + (0x219, "V"), + (0x21A, "M", "ț"), + (0x21B, "V"), + (0x21C, "M", "ȝ"), + (0x21D, "V"), + (0x21E, "M", "ȟ"), + (0x21F, "V"), + (0x220, "M", "ƞ"), + (0x221, "V"), + (0x222, "M", "ȣ"), + (0x223, "V"), + (0x224, "M", "ȥ"), + (0x225, "V"), + (0x226, "M", "ȧ"), + (0x227, "V"), + (0x228, "M", "ȩ"), + (0x229, "V"), + (0x22A, "M", "ȫ"), + (0x22B, "V"), + (0x22C, "M", "ȭ"), + (0x22D, "V"), + (0x22E, "M", "ȯ"), + (0x22F, "V"), + (0x230, "M", "ȱ"), + (0x231, "V"), + (0x232, "M", "ȳ"), + (0x233, "V"), + (0x23A, "M", "ⱥ"), + (0x23B, "M", "ȼ"), + (0x23C, "V"), + (0x23D, "M", "ƚ"), + (0x23E, "M", "ⱦ"), + (0x23F, "V"), + (0x241, "M", "ɂ"), + (0x242, "V"), + (0x243, "M", "ƀ"), + (0x244, "M", "ʉ"), + (0x245, "M", "ʌ"), + (0x246, "M", "ɇ"), + (0x247, "V"), + (0x248, "M", "ɉ"), + (0x249, "V"), + (0x24A, "M", "ɋ"), + (0x24B, "V"), + (0x24C, "M", "ɍ"), + (0x24D, "V"), + (0x24E, "M", "ɏ"), + (0x24F, "V"), + (0x2B0, "M", "h"), + (0x2B1, "M", "ɦ"), + (0x2B2, "M", "j"), + (0x2B3, "M", "r"), + (0x2B4, "M", "ɹ"), + (0x2B5, "M", "ɻ"), + (0x2B6, "M", "ʁ"), + (0x2B7, "M", "w"), + (0x2B8, "M", "y"), + (0x2B9, "V"), + (0x2D8, "3", " ̆"), + (0x2D9, "3", " ̇"), + (0x2DA, "3", " ̊"), + (0x2DB, "3", " ̨"), + (0x2DC, "3", " ̃"), + (0x2DD, "3", " ̋"), + (0x2DE, "V"), + (0x2E0, "M", "ɣ"), + (0x2E1, "M", "l"), + (0x2E2, "M", "s"), + (0x2E3, "M", "x"), + (0x2E4, "M", "ʕ"), + (0x2E5, "V"), + (0x340, "M", "̀"), + (0x341, "M", "́"), + (0x342, "V"), + (0x343, "M", "̓"), + (0x344, "M", "̈́"), + (0x345, "M", "ι"), + (0x346, "V"), + (0x34F, "I"), + (0x350, "V"), + (0x370, "M", "ͱ"), + (0x371, "V"), + (0x372, "M", "ͳ"), + (0x373, "V"), + (0x374, "M", "ʹ"), + (0x375, "V"), + (0x376, "M", "ͷ"), + (0x377, "V"), + ] + + +def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x378, "X"), + (0x37A, "3", " ι"), + (0x37B, "V"), + (0x37E, "3", ";"), + (0x37F, "M", "ϳ"), + (0x380, "X"), + (0x384, "3", " ́"), + (0x385, "3", " ̈́"), + (0x386, "M", "ά"), + (0x387, "M", "·"), + (0x388, "M", "έ"), + (0x389, "M", "ή"), + (0x38A, "M", "ί"), + (0x38B, "X"), + (0x38C, "M", "ό"), + (0x38D, "X"), + (0x38E, "M", "ύ"), + (0x38F, "M", "ώ"), + (0x390, "V"), + (0x391, "M", "α"), + (0x392, "M", "β"), + (0x393, "M", "γ"), + (0x394, "M", "δ"), + (0x395, "M", "ε"), + (0x396, "M", "ζ"), + (0x397, "M", "η"), + (0x398, "M", "θ"), + (0x399, "M", "ι"), + (0x39A, "M", "κ"), + (0x39B, "M", "λ"), + (0x39C, "M", "μ"), + (0x39D, "M", "ν"), + (0x39E, "M", "ξ"), + (0x39F, "M", "ο"), + (0x3A0, "M", "π"), + (0x3A1, "M", "ρ"), + (0x3A2, "X"), + (0x3A3, "M", "σ"), + (0x3A4, "M", "τ"), + (0x3A5, "M", "υ"), + (0x3A6, "M", "φ"), + (0x3A7, "M", "χ"), + (0x3A8, "M", "ψ"), + (0x3A9, "M", "ω"), + (0x3AA, "M", "ϊ"), + (0x3AB, "M", "ϋ"), + (0x3AC, "V"), + (0x3C2, "D", "σ"), + (0x3C3, "V"), + (0x3CF, "M", "ϗ"), + (0x3D0, "M", "β"), + (0x3D1, "M", "θ"), + (0x3D2, "M", "υ"), + (0x3D3, "M", "ύ"), + (0x3D4, "M", "ϋ"), + (0x3D5, "M", "φ"), + (0x3D6, "M", "π"), + (0x3D7, "V"), + (0x3D8, "M", "ϙ"), + (0x3D9, "V"), + (0x3DA, "M", "ϛ"), + (0x3DB, "V"), + (0x3DC, "M", "ϝ"), + (0x3DD, "V"), + (0x3DE, "M", "ϟ"), + (0x3DF, "V"), + (0x3E0, "M", "ϡ"), + (0x3E1, "V"), + (0x3E2, "M", "ϣ"), + (0x3E3, "V"), + (0x3E4, "M", "ϥ"), + (0x3E5, "V"), + (0x3E6, "M", "ϧ"), + (0x3E7, "V"), + (0x3E8, "M", "ϩ"), + (0x3E9, "V"), + (0x3EA, "M", "ϫ"), + (0x3EB, "V"), + (0x3EC, "M", "ϭ"), + (0x3ED, "V"), + (0x3EE, "M", "ϯ"), + (0x3EF, "V"), + (0x3F0, "M", "κ"), + (0x3F1, "M", "ρ"), + (0x3F2, "M", "σ"), + (0x3F3, "V"), + (0x3F4, "M", "θ"), + (0x3F5, "M", "ε"), + (0x3F6, "V"), + (0x3F7, "M", "ϸ"), + (0x3F8, "V"), + (0x3F9, "M", "σ"), + (0x3FA, "M", "ϻ"), + (0x3FB, "V"), + (0x3FD, "M", "ͻ"), + (0x3FE, "M", "ͼ"), + (0x3FF, "M", "ͽ"), + (0x400, "M", "ѐ"), + (0x401, "M", "ё"), + (0x402, "M", "ђ"), + ] + + +def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x403, "M", "ѓ"), + (0x404, "M", "є"), + (0x405, "M", "ѕ"), + (0x406, "M", "і"), + (0x407, "M", "ї"), + (0x408, "M", "ј"), + (0x409, "M", "љ"), + (0x40A, "M", "њ"), + (0x40B, "M", "ћ"), + (0x40C, "M", "ќ"), + (0x40D, "M", "ѝ"), + (0x40E, "M", "ў"), + (0x40F, "M", "џ"), + (0x410, "M", "а"), + (0x411, "M", "б"), + (0x412, "M", "в"), + (0x413, "M", "г"), + (0x414, "M", "д"), + (0x415, "M", "е"), + (0x416, "M", "ж"), + (0x417, "M", "з"), + (0x418, "M", "и"), + (0x419, "M", "й"), + (0x41A, "M", "к"), + (0x41B, "M", "л"), + (0x41C, "M", "м"), + (0x41D, "M", "н"), + (0x41E, "M", "о"), + (0x41F, "M", "п"), + (0x420, "M", "р"), + (0x421, "M", "с"), + (0x422, "M", "т"), + (0x423, "M", "у"), + (0x424, "M", "ф"), + (0x425, "M", "х"), + (0x426, "M", "ц"), + (0x427, "M", "ч"), + (0x428, "M", "ш"), + (0x429, "M", "щ"), + (0x42A, "M", "ъ"), + (0x42B, "M", "ы"), + (0x42C, "M", "ь"), + (0x42D, "M", "э"), + (0x42E, "M", "ю"), + (0x42F, "M", "я"), + (0x430, "V"), + (0x460, "M", "ѡ"), + (0x461, "V"), + (0x462, "M", "ѣ"), + (0x463, "V"), + (0x464, "M", "ѥ"), + (0x465, "V"), + (0x466, "M", "ѧ"), + (0x467, "V"), + (0x468, "M", "ѩ"), + (0x469, "V"), + (0x46A, "M", "ѫ"), + (0x46B, "V"), + (0x46C, "M", "ѭ"), + (0x46D, "V"), + (0x46E, "M", "ѯ"), + (0x46F, "V"), + (0x470, "M", "ѱ"), + (0x471, "V"), + (0x472, "M", "ѳ"), + (0x473, "V"), + (0x474, "M", "ѵ"), + (0x475, "V"), + (0x476, "M", "ѷ"), + (0x477, "V"), + (0x478, "M", "ѹ"), + (0x479, "V"), + (0x47A, "M", "ѻ"), + (0x47B, "V"), + (0x47C, "M", "ѽ"), + (0x47D, "V"), + (0x47E, "M", "ѿ"), + (0x47F, "V"), + (0x480, "M", "ҁ"), + (0x481, "V"), + (0x48A, "M", "ҋ"), + (0x48B, "V"), + (0x48C, "M", "ҍ"), + (0x48D, "V"), + (0x48E, "M", "ҏ"), + (0x48F, "V"), + (0x490, "M", "ґ"), + (0x491, "V"), + (0x492, "M", "ғ"), + (0x493, "V"), + (0x494, "M", "ҕ"), + (0x495, "V"), + (0x496, "M", "җ"), + (0x497, "V"), + (0x498, "M", "ҙ"), + (0x499, "V"), + (0x49A, "M", "қ"), + (0x49B, "V"), + (0x49C, "M", "ҝ"), + (0x49D, "V"), + ] + + +def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x49E, "M", "ҟ"), + (0x49F, "V"), + (0x4A0, "M", "ҡ"), + (0x4A1, "V"), + (0x4A2, "M", "ң"), + (0x4A3, "V"), + (0x4A4, "M", "ҥ"), + (0x4A5, "V"), + (0x4A6, "M", "ҧ"), + (0x4A7, "V"), + (0x4A8, "M", "ҩ"), + (0x4A9, "V"), + (0x4AA, "M", "ҫ"), + (0x4AB, "V"), + (0x4AC, "M", "ҭ"), + (0x4AD, "V"), + (0x4AE, "M", "ү"), + (0x4AF, "V"), + (0x4B0, "M", "ұ"), + (0x4B1, "V"), + (0x4B2, "M", "ҳ"), + (0x4B3, "V"), + (0x4B4, "M", "ҵ"), + (0x4B5, "V"), + (0x4B6, "M", "ҷ"), + (0x4B7, "V"), + (0x4B8, "M", "ҹ"), + (0x4B9, "V"), + (0x4BA, "M", "һ"), + (0x4BB, "V"), + (0x4BC, "M", "ҽ"), + (0x4BD, "V"), + (0x4BE, "M", "ҿ"), + (0x4BF, "V"), + (0x4C0, "X"), + (0x4C1, "M", "ӂ"), + (0x4C2, "V"), + (0x4C3, "M", "ӄ"), + (0x4C4, "V"), + (0x4C5, "M", "ӆ"), + (0x4C6, "V"), + (0x4C7, "M", "ӈ"), + (0x4C8, "V"), + (0x4C9, "M", "ӊ"), + (0x4CA, "V"), + (0x4CB, "M", "ӌ"), + (0x4CC, "V"), + (0x4CD, "M", "ӎ"), + (0x4CE, "V"), + (0x4D0, "M", "ӑ"), + (0x4D1, "V"), + (0x4D2, "M", "ӓ"), + (0x4D3, "V"), + (0x4D4, "M", "ӕ"), + (0x4D5, "V"), + (0x4D6, "M", "ӗ"), + (0x4D7, "V"), + (0x4D8, "M", "ә"), + (0x4D9, "V"), + (0x4DA, "M", "ӛ"), + (0x4DB, "V"), + (0x4DC, "M", "ӝ"), + (0x4DD, "V"), + (0x4DE, "M", "ӟ"), + (0x4DF, "V"), + (0x4E0, "M", "ӡ"), + (0x4E1, "V"), + (0x4E2, "M", "ӣ"), + (0x4E3, "V"), + (0x4E4, "M", "ӥ"), + (0x4E5, "V"), + (0x4E6, "M", "ӧ"), + (0x4E7, "V"), + (0x4E8, "M", "ө"), + (0x4E9, "V"), + (0x4EA, "M", "ӫ"), + (0x4EB, "V"), + (0x4EC, "M", "ӭ"), + (0x4ED, "V"), + (0x4EE, "M", "ӯ"), + (0x4EF, "V"), + (0x4F0, "M", "ӱ"), + (0x4F1, "V"), + (0x4F2, "M", "ӳ"), + (0x4F3, "V"), + (0x4F4, "M", "ӵ"), + (0x4F5, "V"), + (0x4F6, "M", "ӷ"), + (0x4F7, "V"), + (0x4F8, "M", "ӹ"), + (0x4F9, "V"), + (0x4FA, "M", "ӻ"), + (0x4FB, "V"), + (0x4FC, "M", "ӽ"), + (0x4FD, "V"), + (0x4FE, "M", "ӿ"), + (0x4FF, "V"), + (0x500, "M", "ԁ"), + (0x501, "V"), + (0x502, "M", "ԃ"), + ] + + +def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x503, "V"), + (0x504, "M", "ԅ"), + (0x505, "V"), + (0x506, "M", "ԇ"), + (0x507, "V"), + (0x508, "M", "ԉ"), + (0x509, "V"), + (0x50A, "M", "ԋ"), + (0x50B, "V"), + (0x50C, "M", "ԍ"), + (0x50D, "V"), + (0x50E, "M", "ԏ"), + (0x50F, "V"), + (0x510, "M", "ԑ"), + (0x511, "V"), + (0x512, "M", "ԓ"), + (0x513, "V"), + (0x514, "M", "ԕ"), + (0x515, "V"), + (0x516, "M", "ԗ"), + (0x517, "V"), + (0x518, "M", "ԙ"), + (0x519, "V"), + (0x51A, "M", "ԛ"), + (0x51B, "V"), + (0x51C, "M", "ԝ"), + (0x51D, "V"), + (0x51E, "M", "ԟ"), + (0x51F, "V"), + (0x520, "M", "ԡ"), + (0x521, "V"), + (0x522, "M", "ԣ"), + (0x523, "V"), + (0x524, "M", "ԥ"), + (0x525, "V"), + (0x526, "M", "ԧ"), + (0x527, "V"), + (0x528, "M", "ԩ"), + (0x529, "V"), + (0x52A, "M", "ԫ"), + (0x52B, "V"), + (0x52C, "M", "ԭ"), + (0x52D, "V"), + (0x52E, "M", "ԯ"), + (0x52F, "V"), + (0x530, "X"), + (0x531, "M", "ա"), + (0x532, "M", "բ"), + (0x533, "M", "գ"), + (0x534, "M", "դ"), + (0x535, "M", "ե"), + (0x536, "M", "զ"), + (0x537, "M", "է"), + (0x538, "M", "ը"), + (0x539, "M", "թ"), + (0x53A, "M", "ժ"), + (0x53B, "M", "ի"), + (0x53C, "M", "լ"), + (0x53D, "M", "խ"), + (0x53E, "M", "ծ"), + (0x53F, "M", "կ"), + (0x540, "M", "հ"), + (0x541, "M", "ձ"), + (0x542, "M", "ղ"), + (0x543, "M", "ճ"), + (0x544, "M", "մ"), + (0x545, "M", "յ"), + (0x546, "M", "ն"), + (0x547, "M", "շ"), + (0x548, "M", "ո"), + (0x549, "M", "չ"), + (0x54A, "M", "պ"), + (0x54B, "M", "ջ"), + (0x54C, "M", "ռ"), + (0x54D, "M", "ս"), + (0x54E, "M", "վ"), + (0x54F, "M", "տ"), + (0x550, "M", "ր"), + (0x551, "M", "ց"), + (0x552, "M", "ւ"), + (0x553, "M", "փ"), + (0x554, "M", "ք"), + (0x555, "M", "օ"), + (0x556, "M", "ֆ"), + (0x557, "X"), + (0x559, "V"), + (0x587, "M", "եւ"), + (0x588, "V"), + (0x58B, "X"), + (0x58D, "V"), + (0x590, "X"), + (0x591, "V"), + (0x5C8, "X"), + (0x5D0, "V"), + (0x5EB, "X"), + (0x5EF, "V"), + (0x5F5, "X"), + (0x606, "V"), + (0x61C, "X"), + (0x61D, "V"), + ] + + +def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x675, "M", "اٴ"), + (0x676, "M", "وٴ"), + (0x677, "M", "ۇٴ"), + (0x678, "M", "يٴ"), + (0x679, "V"), + (0x6DD, "X"), + (0x6DE, "V"), + (0x70E, "X"), + (0x710, "V"), + (0x74B, "X"), + (0x74D, "V"), + (0x7B2, "X"), + (0x7C0, "V"), + (0x7FB, "X"), + (0x7FD, "V"), + (0x82E, "X"), + (0x830, "V"), + (0x83F, "X"), + (0x840, "V"), + (0x85C, "X"), + (0x85E, "V"), + (0x85F, "X"), + (0x860, "V"), + (0x86B, "X"), + (0x870, "V"), + (0x88F, "X"), + (0x898, "V"), + (0x8E2, "X"), + (0x8E3, "V"), + (0x958, "M", "क़"), + (0x959, "M", "ख़"), + (0x95A, "M", "ग़"), + (0x95B, "M", "ज़"), + (0x95C, "M", "ड़"), + (0x95D, "M", "ढ़"), + (0x95E, "M", "फ़"), + (0x95F, "M", "य़"), + (0x960, "V"), + (0x984, "X"), + (0x985, "V"), + (0x98D, "X"), + (0x98F, "V"), + (0x991, "X"), + (0x993, "V"), + (0x9A9, "X"), + (0x9AA, "V"), + (0x9B1, "X"), + (0x9B2, "V"), + (0x9B3, "X"), + (0x9B6, "V"), + (0x9BA, "X"), + (0x9BC, "V"), + (0x9C5, "X"), + (0x9C7, "V"), + (0x9C9, "X"), + (0x9CB, "V"), + (0x9CF, "X"), + (0x9D7, "V"), + (0x9D8, "X"), + (0x9DC, "M", "ড়"), + (0x9DD, "M", "ঢ়"), + (0x9DE, "X"), + (0x9DF, "M", "য়"), + (0x9E0, "V"), + (0x9E4, "X"), + (0x9E6, "V"), + (0x9FF, "X"), + (0xA01, "V"), + (0xA04, "X"), + (0xA05, "V"), + (0xA0B, "X"), + (0xA0F, "V"), + (0xA11, "X"), + (0xA13, "V"), + (0xA29, "X"), + (0xA2A, "V"), + (0xA31, "X"), + (0xA32, "V"), + (0xA33, "M", "ਲ਼"), + (0xA34, "X"), + (0xA35, "V"), + (0xA36, "M", "ਸ਼"), + (0xA37, "X"), + (0xA38, "V"), + (0xA3A, "X"), + (0xA3C, "V"), + (0xA3D, "X"), + (0xA3E, "V"), + (0xA43, "X"), + (0xA47, "V"), + (0xA49, "X"), + (0xA4B, "V"), + (0xA4E, "X"), + (0xA51, "V"), + (0xA52, "X"), + (0xA59, "M", "ਖ਼"), + (0xA5A, "M", "ਗ਼"), + (0xA5B, "M", "ਜ਼"), + (0xA5C, "V"), + (0xA5D, "X"), + ] + + +def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA5E, "M", "ਫ਼"), + (0xA5F, "X"), + (0xA66, "V"), + (0xA77, "X"), + (0xA81, "V"), + (0xA84, "X"), + (0xA85, "V"), + (0xA8E, "X"), + (0xA8F, "V"), + (0xA92, "X"), + (0xA93, "V"), + (0xAA9, "X"), + (0xAAA, "V"), + (0xAB1, "X"), + (0xAB2, "V"), + (0xAB4, "X"), + (0xAB5, "V"), + (0xABA, "X"), + (0xABC, "V"), + (0xAC6, "X"), + (0xAC7, "V"), + (0xACA, "X"), + (0xACB, "V"), + (0xACE, "X"), + (0xAD0, "V"), + (0xAD1, "X"), + (0xAE0, "V"), + (0xAE4, "X"), + (0xAE6, "V"), + (0xAF2, "X"), + (0xAF9, "V"), + (0xB00, "X"), + (0xB01, "V"), + (0xB04, "X"), + (0xB05, "V"), + (0xB0D, "X"), + (0xB0F, "V"), + (0xB11, "X"), + (0xB13, "V"), + (0xB29, "X"), + (0xB2A, "V"), + (0xB31, "X"), + (0xB32, "V"), + (0xB34, "X"), + (0xB35, "V"), + (0xB3A, "X"), + (0xB3C, "V"), + (0xB45, "X"), + (0xB47, "V"), + (0xB49, "X"), + (0xB4B, "V"), + (0xB4E, "X"), + (0xB55, "V"), + (0xB58, "X"), + (0xB5C, "M", "ଡ଼"), + (0xB5D, "M", "ଢ଼"), + (0xB5E, "X"), + (0xB5F, "V"), + (0xB64, "X"), + (0xB66, "V"), + (0xB78, "X"), + (0xB82, "V"), + (0xB84, "X"), + (0xB85, "V"), + (0xB8B, "X"), + (0xB8E, "V"), + (0xB91, "X"), + (0xB92, "V"), + (0xB96, "X"), + (0xB99, "V"), + (0xB9B, "X"), + (0xB9C, "V"), + (0xB9D, "X"), + (0xB9E, "V"), + (0xBA0, "X"), + (0xBA3, "V"), + (0xBA5, "X"), + (0xBA8, "V"), + (0xBAB, "X"), + (0xBAE, "V"), + (0xBBA, "X"), + (0xBBE, "V"), + (0xBC3, "X"), + (0xBC6, "V"), + (0xBC9, "X"), + (0xBCA, "V"), + (0xBCE, "X"), + (0xBD0, "V"), + (0xBD1, "X"), + (0xBD7, "V"), + (0xBD8, "X"), + (0xBE6, "V"), + (0xBFB, "X"), + (0xC00, "V"), + (0xC0D, "X"), + (0xC0E, "V"), + (0xC11, "X"), + (0xC12, "V"), + (0xC29, "X"), + (0xC2A, "V"), + ] + + +def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC3A, "X"), + (0xC3C, "V"), + (0xC45, "X"), + (0xC46, "V"), + (0xC49, "X"), + (0xC4A, "V"), + (0xC4E, "X"), + (0xC55, "V"), + (0xC57, "X"), + (0xC58, "V"), + (0xC5B, "X"), + (0xC5D, "V"), + (0xC5E, "X"), + (0xC60, "V"), + (0xC64, "X"), + (0xC66, "V"), + (0xC70, "X"), + (0xC77, "V"), + (0xC8D, "X"), + (0xC8E, "V"), + (0xC91, "X"), + (0xC92, "V"), + (0xCA9, "X"), + (0xCAA, "V"), + (0xCB4, "X"), + (0xCB5, "V"), + (0xCBA, "X"), + (0xCBC, "V"), + (0xCC5, "X"), + (0xCC6, "V"), + (0xCC9, "X"), + (0xCCA, "V"), + (0xCCE, "X"), + (0xCD5, "V"), + (0xCD7, "X"), + (0xCDD, "V"), + (0xCDF, "X"), + (0xCE0, "V"), + (0xCE4, "X"), + (0xCE6, "V"), + (0xCF0, "X"), + (0xCF1, "V"), + (0xCF4, "X"), + (0xD00, "V"), + (0xD0D, "X"), + (0xD0E, "V"), + (0xD11, "X"), + (0xD12, "V"), + (0xD45, "X"), + (0xD46, "V"), + (0xD49, "X"), + (0xD4A, "V"), + (0xD50, "X"), + (0xD54, "V"), + (0xD64, "X"), + (0xD66, "V"), + (0xD80, "X"), + (0xD81, "V"), + (0xD84, "X"), + (0xD85, "V"), + (0xD97, "X"), + (0xD9A, "V"), + (0xDB2, "X"), + (0xDB3, "V"), + (0xDBC, "X"), + (0xDBD, "V"), + (0xDBE, "X"), + (0xDC0, "V"), + (0xDC7, "X"), + (0xDCA, "V"), + (0xDCB, "X"), + (0xDCF, "V"), + (0xDD5, "X"), + (0xDD6, "V"), + (0xDD7, "X"), + (0xDD8, "V"), + (0xDE0, "X"), + (0xDE6, "V"), + (0xDF0, "X"), + (0xDF2, "V"), + (0xDF5, "X"), + (0xE01, "V"), + (0xE33, "M", "ํา"), + (0xE34, "V"), + (0xE3B, "X"), + (0xE3F, "V"), + (0xE5C, "X"), + (0xE81, "V"), + (0xE83, "X"), + (0xE84, "V"), + (0xE85, "X"), + (0xE86, "V"), + (0xE8B, "X"), + (0xE8C, "V"), + (0xEA4, "X"), + (0xEA5, "V"), + (0xEA6, "X"), + (0xEA7, "V"), + (0xEB3, "M", "ໍາ"), + (0xEB4, "V"), + ] + + +def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xEBE, "X"), + (0xEC0, "V"), + (0xEC5, "X"), + (0xEC6, "V"), + (0xEC7, "X"), + (0xEC8, "V"), + (0xECF, "X"), + (0xED0, "V"), + (0xEDA, "X"), + (0xEDC, "M", "ຫນ"), + (0xEDD, "M", "ຫມ"), + (0xEDE, "V"), + (0xEE0, "X"), + (0xF00, "V"), + (0xF0C, "M", "་"), + (0xF0D, "V"), + (0xF43, "M", "གྷ"), + (0xF44, "V"), + (0xF48, "X"), + (0xF49, "V"), + (0xF4D, "M", "ཌྷ"), + (0xF4E, "V"), + (0xF52, "M", "དྷ"), + (0xF53, "V"), + (0xF57, "M", "བྷ"), + (0xF58, "V"), + (0xF5C, "M", "ཛྷ"), + (0xF5D, "V"), + (0xF69, "M", "ཀྵ"), + (0xF6A, "V"), + (0xF6D, "X"), + (0xF71, "V"), + (0xF73, "M", "ཱི"), + (0xF74, "V"), + (0xF75, "M", "ཱུ"), + (0xF76, "M", "ྲྀ"), + (0xF77, "M", "ྲཱྀ"), + (0xF78, "M", "ླྀ"), + (0xF79, "M", "ླཱྀ"), + (0xF7A, "V"), + (0xF81, "M", "ཱྀ"), + (0xF82, "V"), + (0xF93, "M", "ྒྷ"), + (0xF94, "V"), + (0xF98, "X"), + (0xF99, "V"), + (0xF9D, "M", "ྜྷ"), + (0xF9E, "V"), + (0xFA2, "M", "ྡྷ"), + (0xFA3, "V"), + (0xFA7, "M", "ྦྷ"), + (0xFA8, "V"), + (0xFAC, "M", "ྫྷ"), + (0xFAD, "V"), + (0xFB9, "M", "ྐྵ"), + (0xFBA, "V"), + (0xFBD, "X"), + (0xFBE, "V"), + (0xFCD, "X"), + (0xFCE, "V"), + (0xFDB, "X"), + (0x1000, "V"), + (0x10A0, "X"), + (0x10C7, "M", "ⴧ"), + (0x10C8, "X"), + (0x10CD, "M", "ⴭ"), + (0x10CE, "X"), + (0x10D0, "V"), + (0x10FC, "M", "ნ"), + (0x10FD, "V"), + (0x115F, "X"), + (0x1161, "V"), + (0x1249, "X"), + (0x124A, "V"), + (0x124E, "X"), + (0x1250, "V"), + (0x1257, "X"), + (0x1258, "V"), + (0x1259, "X"), + (0x125A, "V"), + (0x125E, "X"), + (0x1260, "V"), + (0x1289, "X"), + (0x128A, "V"), + (0x128E, "X"), + (0x1290, "V"), + (0x12B1, "X"), + (0x12B2, "V"), + (0x12B6, "X"), + (0x12B8, "V"), + (0x12BF, "X"), + (0x12C0, "V"), + (0x12C1, "X"), + (0x12C2, "V"), + (0x12C6, "X"), + (0x12C8, "V"), + (0x12D7, "X"), + (0x12D8, "V"), + (0x1311, "X"), + (0x1312, "V"), + ] + + +def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1316, "X"), + (0x1318, "V"), + (0x135B, "X"), + (0x135D, "V"), + (0x137D, "X"), + (0x1380, "V"), + (0x139A, "X"), + (0x13A0, "V"), + (0x13F6, "X"), + (0x13F8, "M", "Ᏸ"), + (0x13F9, "M", "Ᏹ"), + (0x13FA, "M", "Ᏺ"), + (0x13FB, "M", "Ᏻ"), + (0x13FC, "M", "Ᏼ"), + (0x13FD, "M", "Ᏽ"), + (0x13FE, "X"), + (0x1400, "V"), + (0x1680, "X"), + (0x1681, "V"), + (0x169D, "X"), + (0x16A0, "V"), + (0x16F9, "X"), + (0x1700, "V"), + (0x1716, "X"), + (0x171F, "V"), + (0x1737, "X"), + (0x1740, "V"), + (0x1754, "X"), + (0x1760, "V"), + (0x176D, "X"), + (0x176E, "V"), + (0x1771, "X"), + (0x1772, "V"), + (0x1774, "X"), + (0x1780, "V"), + (0x17B4, "X"), + (0x17B6, "V"), + (0x17DE, "X"), + (0x17E0, "V"), + (0x17EA, "X"), + (0x17F0, "V"), + (0x17FA, "X"), + (0x1800, "V"), + (0x1806, "X"), + (0x1807, "V"), + (0x180B, "I"), + (0x180E, "X"), + (0x180F, "I"), + (0x1810, "V"), + (0x181A, "X"), + (0x1820, "V"), + (0x1879, "X"), + (0x1880, "V"), + (0x18AB, "X"), + (0x18B0, "V"), + (0x18F6, "X"), + (0x1900, "V"), + (0x191F, "X"), + (0x1920, "V"), + (0x192C, "X"), + (0x1930, "V"), + (0x193C, "X"), + (0x1940, "V"), + (0x1941, "X"), + (0x1944, "V"), + (0x196E, "X"), + (0x1970, "V"), + (0x1975, "X"), + (0x1980, "V"), + (0x19AC, "X"), + (0x19B0, "V"), + (0x19CA, "X"), + (0x19D0, "V"), + (0x19DB, "X"), + (0x19DE, "V"), + (0x1A1C, "X"), + (0x1A1E, "V"), + (0x1A5F, "X"), + (0x1A60, "V"), + (0x1A7D, "X"), + (0x1A7F, "V"), + (0x1A8A, "X"), + (0x1A90, "V"), + (0x1A9A, "X"), + (0x1AA0, "V"), + (0x1AAE, "X"), + (0x1AB0, "V"), + (0x1ACF, "X"), + (0x1B00, "V"), + (0x1B4D, "X"), + (0x1B50, "V"), + (0x1B7F, "X"), + (0x1B80, "V"), + (0x1BF4, "X"), + (0x1BFC, "V"), + (0x1C38, "X"), + (0x1C3B, "V"), + (0x1C4A, "X"), + (0x1C4D, "V"), + (0x1C80, "M", "в"), + ] + + +def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1C81, "M", "д"), + (0x1C82, "M", "о"), + (0x1C83, "M", "с"), + (0x1C84, "M", "т"), + (0x1C86, "M", "ъ"), + (0x1C87, "M", "ѣ"), + (0x1C88, "M", "ꙋ"), + (0x1C89, "X"), + (0x1C90, "M", "ა"), + (0x1C91, "M", "ბ"), + (0x1C92, "M", "გ"), + (0x1C93, "M", "დ"), + (0x1C94, "M", "ე"), + (0x1C95, "M", "ვ"), + (0x1C96, "M", "ზ"), + (0x1C97, "M", "თ"), + (0x1C98, "M", "ი"), + (0x1C99, "M", "კ"), + (0x1C9A, "M", "ლ"), + (0x1C9B, "M", "მ"), + (0x1C9C, "M", "ნ"), + (0x1C9D, "M", "ო"), + (0x1C9E, "M", "პ"), + (0x1C9F, "M", "ჟ"), + (0x1CA0, "M", "რ"), + (0x1CA1, "M", "ს"), + (0x1CA2, "M", "ტ"), + (0x1CA3, "M", "უ"), + (0x1CA4, "M", "ფ"), + (0x1CA5, "M", "ქ"), + (0x1CA6, "M", "ღ"), + (0x1CA7, "M", "ყ"), + (0x1CA8, "M", "შ"), + (0x1CA9, "M", "ჩ"), + (0x1CAA, "M", "ც"), + (0x1CAB, "M", "ძ"), + (0x1CAC, "M", "წ"), + (0x1CAD, "M", "ჭ"), + (0x1CAE, "M", "ხ"), + (0x1CAF, "M", "ჯ"), + (0x1CB0, "M", "ჰ"), + (0x1CB1, "M", "ჱ"), + (0x1CB2, "M", "ჲ"), + (0x1CB3, "M", "ჳ"), + (0x1CB4, "M", "ჴ"), + (0x1CB5, "M", "ჵ"), + (0x1CB6, "M", "ჶ"), + (0x1CB7, "M", "ჷ"), + (0x1CB8, "M", "ჸ"), + (0x1CB9, "M", "ჹ"), + (0x1CBA, "M", "ჺ"), + (0x1CBB, "X"), + (0x1CBD, "M", "ჽ"), + (0x1CBE, "M", "ჾ"), + (0x1CBF, "M", "ჿ"), + (0x1CC0, "V"), + (0x1CC8, "X"), + (0x1CD0, "V"), + (0x1CFB, "X"), + (0x1D00, "V"), + (0x1D2C, "M", "a"), + (0x1D2D, "M", "æ"), + (0x1D2E, "M", "b"), + (0x1D2F, "V"), + (0x1D30, "M", "d"), + (0x1D31, "M", "e"), + (0x1D32, "M", "ǝ"), + (0x1D33, "M", "g"), + (0x1D34, "M", "h"), + (0x1D35, "M", "i"), + (0x1D36, "M", "j"), + (0x1D37, "M", "k"), + (0x1D38, "M", "l"), + (0x1D39, "M", "m"), + (0x1D3A, "M", "n"), + (0x1D3B, "V"), + (0x1D3C, "M", "o"), + (0x1D3D, "M", "ȣ"), + (0x1D3E, "M", "p"), + (0x1D3F, "M", "r"), + (0x1D40, "M", "t"), + (0x1D41, "M", "u"), + (0x1D42, "M", "w"), + (0x1D43, "M", "a"), + (0x1D44, "M", "ɐ"), + (0x1D45, "M", "ɑ"), + (0x1D46, "M", "ᴂ"), + (0x1D47, "M", "b"), + (0x1D48, "M", "d"), + (0x1D49, "M", "e"), + (0x1D4A, "M", "ə"), + (0x1D4B, "M", "ɛ"), + (0x1D4C, "M", "ɜ"), + (0x1D4D, "M", "g"), + (0x1D4E, "V"), + (0x1D4F, "M", "k"), + (0x1D50, "M", "m"), + (0x1D51, "M", "ŋ"), + (0x1D52, "M", "o"), + (0x1D53, "M", "ɔ"), + ] + + +def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D54, "M", "ᴖ"), + (0x1D55, "M", "ᴗ"), + (0x1D56, "M", "p"), + (0x1D57, "M", "t"), + (0x1D58, "M", "u"), + (0x1D59, "M", "ᴝ"), + (0x1D5A, "M", "ɯ"), + (0x1D5B, "M", "v"), + (0x1D5C, "M", "ᴥ"), + (0x1D5D, "M", "β"), + (0x1D5E, "M", "γ"), + (0x1D5F, "M", "δ"), + (0x1D60, "M", "φ"), + (0x1D61, "M", "χ"), + (0x1D62, "M", "i"), + (0x1D63, "M", "r"), + (0x1D64, "M", "u"), + (0x1D65, "M", "v"), + (0x1D66, "M", "β"), + (0x1D67, "M", "γ"), + (0x1D68, "M", "ρ"), + (0x1D69, "M", "φ"), + (0x1D6A, "M", "χ"), + (0x1D6B, "V"), + (0x1D78, "M", "н"), + (0x1D79, "V"), + (0x1D9B, "M", "ɒ"), + (0x1D9C, "M", "c"), + (0x1D9D, "M", "ɕ"), + (0x1D9E, "M", "ð"), + (0x1D9F, "M", "ɜ"), + (0x1DA0, "M", "f"), + (0x1DA1, "M", "ɟ"), + (0x1DA2, "M", "ɡ"), + (0x1DA3, "M", "ɥ"), + (0x1DA4, "M", "ɨ"), + (0x1DA5, "M", "ɩ"), + (0x1DA6, "M", "ɪ"), + (0x1DA7, "M", "ᵻ"), + (0x1DA8, "M", "ʝ"), + (0x1DA9, "M", "ɭ"), + (0x1DAA, "M", "ᶅ"), + (0x1DAB, "M", "ʟ"), + (0x1DAC, "M", "ɱ"), + (0x1DAD, "M", "ɰ"), + (0x1DAE, "M", "ɲ"), + (0x1DAF, "M", "ɳ"), + (0x1DB0, "M", "ɴ"), + (0x1DB1, "M", "ɵ"), + (0x1DB2, "M", "ɸ"), + (0x1DB3, "M", "ʂ"), + (0x1DB4, "M", "ʃ"), + (0x1DB5, "M", "ƫ"), + (0x1DB6, "M", "ʉ"), + (0x1DB7, "M", "ʊ"), + (0x1DB8, "M", "ᴜ"), + (0x1DB9, "M", "ʋ"), + (0x1DBA, "M", "ʌ"), + (0x1DBB, "M", "z"), + (0x1DBC, "M", "ʐ"), + (0x1DBD, "M", "ʑ"), + (0x1DBE, "M", "ʒ"), + (0x1DBF, "M", "θ"), + (0x1DC0, "V"), + (0x1E00, "M", "ḁ"), + (0x1E01, "V"), + (0x1E02, "M", "ḃ"), + (0x1E03, "V"), + (0x1E04, "M", "ḅ"), + (0x1E05, "V"), + (0x1E06, "M", "ḇ"), + (0x1E07, "V"), + (0x1E08, "M", "ḉ"), + (0x1E09, "V"), + (0x1E0A, "M", "ḋ"), + (0x1E0B, "V"), + (0x1E0C, "M", "ḍ"), + (0x1E0D, "V"), + (0x1E0E, "M", "ḏ"), + (0x1E0F, "V"), + (0x1E10, "M", "ḑ"), + (0x1E11, "V"), + (0x1E12, "M", "ḓ"), + (0x1E13, "V"), + (0x1E14, "M", "ḕ"), + (0x1E15, "V"), + (0x1E16, "M", "ḗ"), + (0x1E17, "V"), + (0x1E18, "M", "ḙ"), + (0x1E19, "V"), + (0x1E1A, "M", "ḛ"), + (0x1E1B, "V"), + (0x1E1C, "M", "ḝ"), + (0x1E1D, "V"), + (0x1E1E, "M", "ḟ"), + (0x1E1F, "V"), + (0x1E20, "M", "ḡ"), + (0x1E21, "V"), + (0x1E22, "M", "ḣ"), + (0x1E23, "V"), + ] + + +def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E24, "M", "ḥ"), + (0x1E25, "V"), + (0x1E26, "M", "ḧ"), + (0x1E27, "V"), + (0x1E28, "M", "ḩ"), + (0x1E29, "V"), + (0x1E2A, "M", "ḫ"), + (0x1E2B, "V"), + (0x1E2C, "M", "ḭ"), + (0x1E2D, "V"), + (0x1E2E, "M", "ḯ"), + (0x1E2F, "V"), + (0x1E30, "M", "ḱ"), + (0x1E31, "V"), + (0x1E32, "M", "ḳ"), + (0x1E33, "V"), + (0x1E34, "M", "ḵ"), + (0x1E35, "V"), + (0x1E36, "M", "ḷ"), + (0x1E37, "V"), + (0x1E38, "M", "ḹ"), + (0x1E39, "V"), + (0x1E3A, "M", "ḻ"), + (0x1E3B, "V"), + (0x1E3C, "M", "ḽ"), + (0x1E3D, "V"), + (0x1E3E, "M", "ḿ"), + (0x1E3F, "V"), + (0x1E40, "M", "ṁ"), + (0x1E41, "V"), + (0x1E42, "M", "ṃ"), + (0x1E43, "V"), + (0x1E44, "M", "ṅ"), + (0x1E45, "V"), + (0x1E46, "M", "ṇ"), + (0x1E47, "V"), + (0x1E48, "M", "ṉ"), + (0x1E49, "V"), + (0x1E4A, "M", "ṋ"), + (0x1E4B, "V"), + (0x1E4C, "M", "ṍ"), + (0x1E4D, "V"), + (0x1E4E, "M", "ṏ"), + (0x1E4F, "V"), + (0x1E50, "M", "ṑ"), + (0x1E51, "V"), + (0x1E52, "M", "ṓ"), + (0x1E53, "V"), + (0x1E54, "M", "ṕ"), + (0x1E55, "V"), + (0x1E56, "M", "ṗ"), + (0x1E57, "V"), + (0x1E58, "M", "ṙ"), + (0x1E59, "V"), + (0x1E5A, "M", "ṛ"), + (0x1E5B, "V"), + (0x1E5C, "M", "ṝ"), + (0x1E5D, "V"), + (0x1E5E, "M", "ṟ"), + (0x1E5F, "V"), + (0x1E60, "M", "ṡ"), + (0x1E61, "V"), + (0x1E62, "M", "ṣ"), + (0x1E63, "V"), + (0x1E64, "M", "ṥ"), + (0x1E65, "V"), + (0x1E66, "M", "ṧ"), + (0x1E67, "V"), + (0x1E68, "M", "ṩ"), + (0x1E69, "V"), + (0x1E6A, "M", "ṫ"), + (0x1E6B, "V"), + (0x1E6C, "M", "ṭ"), + (0x1E6D, "V"), + (0x1E6E, "M", "ṯ"), + (0x1E6F, "V"), + (0x1E70, "M", "ṱ"), + (0x1E71, "V"), + (0x1E72, "M", "ṳ"), + (0x1E73, "V"), + (0x1E74, "M", "ṵ"), + (0x1E75, "V"), + (0x1E76, "M", "ṷ"), + (0x1E77, "V"), + (0x1E78, "M", "ṹ"), + (0x1E79, "V"), + (0x1E7A, "M", "ṻ"), + (0x1E7B, "V"), + (0x1E7C, "M", "ṽ"), + (0x1E7D, "V"), + (0x1E7E, "M", "ṿ"), + (0x1E7F, "V"), + (0x1E80, "M", "ẁ"), + (0x1E81, "V"), + (0x1E82, "M", "ẃ"), + (0x1E83, "V"), + (0x1E84, "M", "ẅ"), + (0x1E85, "V"), + (0x1E86, "M", "ẇ"), + (0x1E87, "V"), + ] + + +def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E88, "M", "ẉ"), + (0x1E89, "V"), + (0x1E8A, "M", "ẋ"), + (0x1E8B, "V"), + (0x1E8C, "M", "ẍ"), + (0x1E8D, "V"), + (0x1E8E, "M", "ẏ"), + (0x1E8F, "V"), + (0x1E90, "M", "ẑ"), + (0x1E91, "V"), + (0x1E92, "M", "ẓ"), + (0x1E93, "V"), + (0x1E94, "M", "ẕ"), + (0x1E95, "V"), + (0x1E9A, "M", "aʾ"), + (0x1E9B, "M", "ṡ"), + (0x1E9C, "V"), + (0x1E9E, "M", "ß"), + (0x1E9F, "V"), + (0x1EA0, "M", "ạ"), + (0x1EA1, "V"), + (0x1EA2, "M", "ả"), + (0x1EA3, "V"), + (0x1EA4, "M", "ấ"), + (0x1EA5, "V"), + (0x1EA6, "M", "ầ"), + (0x1EA7, "V"), + (0x1EA8, "M", "ẩ"), + (0x1EA9, "V"), + (0x1EAA, "M", "ẫ"), + (0x1EAB, "V"), + (0x1EAC, "M", "ậ"), + (0x1EAD, "V"), + (0x1EAE, "M", "ắ"), + (0x1EAF, "V"), + (0x1EB0, "M", "ằ"), + (0x1EB1, "V"), + (0x1EB2, "M", "ẳ"), + (0x1EB3, "V"), + (0x1EB4, "M", "ẵ"), + (0x1EB5, "V"), + (0x1EB6, "M", "ặ"), + (0x1EB7, "V"), + (0x1EB8, "M", "ẹ"), + (0x1EB9, "V"), + (0x1EBA, "M", "ẻ"), + (0x1EBB, "V"), + (0x1EBC, "M", "ẽ"), + (0x1EBD, "V"), + (0x1EBE, "M", "ế"), + (0x1EBF, "V"), + (0x1EC0, "M", "ề"), + (0x1EC1, "V"), + (0x1EC2, "M", "ể"), + (0x1EC3, "V"), + (0x1EC4, "M", "ễ"), + (0x1EC5, "V"), + (0x1EC6, "M", "ệ"), + (0x1EC7, "V"), + (0x1EC8, "M", "ỉ"), + (0x1EC9, "V"), + (0x1ECA, "M", "ị"), + (0x1ECB, "V"), + (0x1ECC, "M", "ọ"), + (0x1ECD, "V"), + (0x1ECE, "M", "ỏ"), + (0x1ECF, "V"), + (0x1ED0, "M", "ố"), + (0x1ED1, "V"), + (0x1ED2, "M", "ồ"), + (0x1ED3, "V"), + (0x1ED4, "M", "ổ"), + (0x1ED5, "V"), + (0x1ED6, "M", "ỗ"), + (0x1ED7, "V"), + (0x1ED8, "M", "ộ"), + (0x1ED9, "V"), + (0x1EDA, "M", "ớ"), + (0x1EDB, "V"), + (0x1EDC, "M", "ờ"), + (0x1EDD, "V"), + (0x1EDE, "M", "ở"), + (0x1EDF, "V"), + (0x1EE0, "M", "ỡ"), + (0x1EE1, "V"), + (0x1EE2, "M", "ợ"), + (0x1EE3, "V"), + (0x1EE4, "M", "ụ"), + (0x1EE5, "V"), + (0x1EE6, "M", "ủ"), + (0x1EE7, "V"), + (0x1EE8, "M", "ứ"), + (0x1EE9, "V"), + (0x1EEA, "M", "ừ"), + (0x1EEB, "V"), + (0x1EEC, "M", "ử"), + (0x1EED, "V"), + (0x1EEE, "M", "ữ"), + (0x1EEF, "V"), + (0x1EF0, "M", "ự"), + ] + + +def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EF1, "V"), + (0x1EF2, "M", "ỳ"), + (0x1EF3, "V"), + (0x1EF4, "M", "ỵ"), + (0x1EF5, "V"), + (0x1EF6, "M", "ỷ"), + (0x1EF7, "V"), + (0x1EF8, "M", "ỹ"), + (0x1EF9, "V"), + (0x1EFA, "M", "ỻ"), + (0x1EFB, "V"), + (0x1EFC, "M", "ỽ"), + (0x1EFD, "V"), + (0x1EFE, "M", "ỿ"), + (0x1EFF, "V"), + (0x1F08, "M", "ἀ"), + (0x1F09, "M", "ἁ"), + (0x1F0A, "M", "ἂ"), + (0x1F0B, "M", "ἃ"), + (0x1F0C, "M", "ἄ"), + (0x1F0D, "M", "ἅ"), + (0x1F0E, "M", "ἆ"), + (0x1F0F, "M", "ἇ"), + (0x1F10, "V"), + (0x1F16, "X"), + (0x1F18, "M", "ἐ"), + (0x1F19, "M", "ἑ"), + (0x1F1A, "M", "ἒ"), + (0x1F1B, "M", "ἓ"), + (0x1F1C, "M", "ἔ"), + (0x1F1D, "M", "ἕ"), + (0x1F1E, "X"), + (0x1F20, "V"), + (0x1F28, "M", "ἠ"), + (0x1F29, "M", "ἡ"), + (0x1F2A, "M", "ἢ"), + (0x1F2B, "M", "ἣ"), + (0x1F2C, "M", "ἤ"), + (0x1F2D, "M", "ἥ"), + (0x1F2E, "M", "ἦ"), + (0x1F2F, "M", "ἧ"), + (0x1F30, "V"), + (0x1F38, "M", "ἰ"), + (0x1F39, "M", "ἱ"), + (0x1F3A, "M", "ἲ"), + (0x1F3B, "M", "ἳ"), + (0x1F3C, "M", "ἴ"), + (0x1F3D, "M", "ἵ"), + (0x1F3E, "M", "ἶ"), + (0x1F3F, "M", "ἷ"), + (0x1F40, "V"), + (0x1F46, "X"), + (0x1F48, "M", "ὀ"), + (0x1F49, "M", "ὁ"), + (0x1F4A, "M", "ὂ"), + (0x1F4B, "M", "ὃ"), + (0x1F4C, "M", "ὄ"), + (0x1F4D, "M", "ὅ"), + (0x1F4E, "X"), + (0x1F50, "V"), + (0x1F58, "X"), + (0x1F59, "M", "ὑ"), + (0x1F5A, "X"), + (0x1F5B, "M", "ὓ"), + (0x1F5C, "X"), + (0x1F5D, "M", "ὕ"), + (0x1F5E, "X"), + (0x1F5F, "M", "ὗ"), + (0x1F60, "V"), + (0x1F68, "M", "ὠ"), + (0x1F69, "M", "ὡ"), + (0x1F6A, "M", "ὢ"), + (0x1F6B, "M", "ὣ"), + (0x1F6C, "M", "ὤ"), + (0x1F6D, "M", "ὥ"), + (0x1F6E, "M", "ὦ"), + (0x1F6F, "M", "ὧ"), + (0x1F70, "V"), + (0x1F71, "M", "ά"), + (0x1F72, "V"), + (0x1F73, "M", "έ"), + (0x1F74, "V"), + (0x1F75, "M", "ή"), + (0x1F76, "V"), + (0x1F77, "M", "ί"), + (0x1F78, "V"), + (0x1F79, "M", "ό"), + (0x1F7A, "V"), + (0x1F7B, "M", "ύ"), + (0x1F7C, "V"), + (0x1F7D, "M", "ώ"), + (0x1F7E, "X"), + (0x1F80, "M", "ἀι"), + (0x1F81, "M", "ἁι"), + (0x1F82, "M", "ἂι"), + (0x1F83, "M", "ἃι"), + (0x1F84, "M", "ἄι"), + (0x1F85, "M", "ἅι"), + (0x1F86, "M", "ἆι"), + (0x1F87, "M", "ἇι"), + ] + + +def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F88, "M", "ἀι"), + (0x1F89, "M", "ἁι"), + (0x1F8A, "M", "ἂι"), + (0x1F8B, "M", "ἃι"), + (0x1F8C, "M", "ἄι"), + (0x1F8D, "M", "ἅι"), + (0x1F8E, "M", "ἆι"), + (0x1F8F, "M", "ἇι"), + (0x1F90, "M", "ἠι"), + (0x1F91, "M", "ἡι"), + (0x1F92, "M", "ἢι"), + (0x1F93, "M", "ἣι"), + (0x1F94, "M", "ἤι"), + (0x1F95, "M", "ἥι"), + (0x1F96, "M", "ἦι"), + (0x1F97, "M", "ἧι"), + (0x1F98, "M", "ἠι"), + (0x1F99, "M", "ἡι"), + (0x1F9A, "M", "ἢι"), + (0x1F9B, "M", "ἣι"), + (0x1F9C, "M", "ἤι"), + (0x1F9D, "M", "ἥι"), + (0x1F9E, "M", "ἦι"), + (0x1F9F, "M", "ἧι"), + (0x1FA0, "M", "ὠι"), + (0x1FA1, "M", "ὡι"), + (0x1FA2, "M", "ὢι"), + (0x1FA3, "M", "ὣι"), + (0x1FA4, "M", "ὤι"), + (0x1FA5, "M", "ὥι"), + (0x1FA6, "M", "ὦι"), + (0x1FA7, "M", "ὧι"), + (0x1FA8, "M", "ὠι"), + (0x1FA9, "M", "ὡι"), + (0x1FAA, "M", "ὢι"), + (0x1FAB, "M", "ὣι"), + (0x1FAC, "M", "ὤι"), + (0x1FAD, "M", "ὥι"), + (0x1FAE, "M", "ὦι"), + (0x1FAF, "M", "ὧι"), + (0x1FB0, "V"), + (0x1FB2, "M", "ὰι"), + (0x1FB3, "M", "αι"), + (0x1FB4, "M", "άι"), + (0x1FB5, "X"), + (0x1FB6, "V"), + (0x1FB7, "M", "ᾶι"), + (0x1FB8, "M", "ᾰ"), + (0x1FB9, "M", "ᾱ"), + (0x1FBA, "M", "ὰ"), + (0x1FBB, "M", "ά"), + (0x1FBC, "M", "αι"), + (0x1FBD, "3", " ̓"), + (0x1FBE, "M", "ι"), + (0x1FBF, "3", " ̓"), + (0x1FC0, "3", " ͂"), + (0x1FC1, "3", " ̈͂"), + (0x1FC2, "M", "ὴι"), + (0x1FC3, "M", "ηι"), + (0x1FC4, "M", "ήι"), + (0x1FC5, "X"), + (0x1FC6, "V"), + (0x1FC7, "M", "ῆι"), + (0x1FC8, "M", "ὲ"), + (0x1FC9, "M", "έ"), + (0x1FCA, "M", "ὴ"), + (0x1FCB, "M", "ή"), + (0x1FCC, "M", "ηι"), + (0x1FCD, "3", " ̓̀"), + (0x1FCE, "3", " ̓́"), + (0x1FCF, "3", " ̓͂"), + (0x1FD0, "V"), + (0x1FD3, "M", "ΐ"), + (0x1FD4, "X"), + (0x1FD6, "V"), + (0x1FD8, "M", "ῐ"), + (0x1FD9, "M", "ῑ"), + (0x1FDA, "M", "ὶ"), + (0x1FDB, "M", "ί"), + (0x1FDC, "X"), + (0x1FDD, "3", " ̔̀"), + (0x1FDE, "3", " ̔́"), + (0x1FDF, "3", " ̔͂"), + (0x1FE0, "V"), + (0x1FE3, "M", "ΰ"), + (0x1FE4, "V"), + (0x1FE8, "M", "ῠ"), + (0x1FE9, "M", "ῡ"), + (0x1FEA, "M", "ὺ"), + (0x1FEB, "M", "ύ"), + (0x1FEC, "M", "ῥ"), + (0x1FED, "3", " ̈̀"), + (0x1FEE, "3", " ̈́"), + (0x1FEF, "3", "`"), + (0x1FF0, "X"), + (0x1FF2, "M", "ὼι"), + (0x1FF3, "M", "ωι"), + (0x1FF4, "M", "ώι"), + (0x1FF5, "X"), + (0x1FF6, "V"), + ] + + +def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FF7, "M", "ῶι"), + (0x1FF8, "M", "ὸ"), + (0x1FF9, "M", "ό"), + (0x1FFA, "M", "ὼ"), + (0x1FFB, "M", "ώ"), + (0x1FFC, "M", "ωι"), + (0x1FFD, "3", " ́"), + (0x1FFE, "3", " ̔"), + (0x1FFF, "X"), + (0x2000, "3", " "), + (0x200B, "I"), + (0x200C, "D", ""), + (0x200E, "X"), + (0x2010, "V"), + (0x2011, "M", "‐"), + (0x2012, "V"), + (0x2017, "3", " ̳"), + (0x2018, "V"), + (0x2024, "X"), + (0x2027, "V"), + (0x2028, "X"), + (0x202F, "3", " "), + (0x2030, "V"), + (0x2033, "M", "′′"), + (0x2034, "M", "′′′"), + (0x2035, "V"), + (0x2036, "M", "‵‵"), + (0x2037, "M", "‵‵‵"), + (0x2038, "V"), + (0x203C, "3", "!!"), + (0x203D, "V"), + (0x203E, "3", " ̅"), + (0x203F, "V"), + (0x2047, "3", "??"), + (0x2048, "3", "?!"), + (0x2049, "3", "!?"), + (0x204A, "V"), + (0x2057, "M", "′′′′"), + (0x2058, "V"), + (0x205F, "3", " "), + (0x2060, "I"), + (0x2061, "X"), + (0x2064, "I"), + (0x2065, "X"), + (0x2070, "M", "0"), + (0x2071, "M", "i"), + (0x2072, "X"), + (0x2074, "M", "4"), + (0x2075, "M", "5"), + (0x2076, "M", "6"), + (0x2077, "M", "7"), + (0x2078, "M", "8"), + (0x2079, "M", "9"), + (0x207A, "3", "+"), + (0x207B, "M", "−"), + (0x207C, "3", "="), + (0x207D, "3", "("), + (0x207E, "3", ")"), + (0x207F, "M", "n"), + (0x2080, "M", "0"), + (0x2081, "M", "1"), + (0x2082, "M", "2"), + (0x2083, "M", "3"), + (0x2084, "M", "4"), + (0x2085, "M", "5"), + (0x2086, "M", "6"), + (0x2087, "M", "7"), + (0x2088, "M", "8"), + (0x2089, "M", "9"), + (0x208A, "3", "+"), + (0x208B, "M", "−"), + (0x208C, "3", "="), + (0x208D, "3", "("), + (0x208E, "3", ")"), + (0x208F, "X"), + (0x2090, "M", "a"), + (0x2091, "M", "e"), + (0x2092, "M", "o"), + (0x2093, "M", "x"), + (0x2094, "M", "ə"), + (0x2095, "M", "h"), + (0x2096, "M", "k"), + (0x2097, "M", "l"), + (0x2098, "M", "m"), + (0x2099, "M", "n"), + (0x209A, "M", "p"), + (0x209B, "M", "s"), + (0x209C, "M", "t"), + (0x209D, "X"), + (0x20A0, "V"), + (0x20A8, "M", "rs"), + (0x20A9, "V"), + (0x20C1, "X"), + (0x20D0, "V"), + (0x20F1, "X"), + (0x2100, "3", "a/c"), + (0x2101, "3", "a/s"), + (0x2102, "M", "c"), + (0x2103, "M", "°c"), + (0x2104, "V"), + ] + + +def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2105, "3", "c/o"), + (0x2106, "3", "c/u"), + (0x2107, "M", "ɛ"), + (0x2108, "V"), + (0x2109, "M", "°f"), + (0x210A, "M", "g"), + (0x210B, "M", "h"), + (0x210F, "M", "ħ"), + (0x2110, "M", "i"), + (0x2112, "M", "l"), + (0x2114, "V"), + (0x2115, "M", "n"), + (0x2116, "M", "no"), + (0x2117, "V"), + (0x2119, "M", "p"), + (0x211A, "M", "q"), + (0x211B, "M", "r"), + (0x211E, "V"), + (0x2120, "M", "sm"), + (0x2121, "M", "tel"), + (0x2122, "M", "tm"), + (0x2123, "V"), + (0x2124, "M", "z"), + (0x2125, "V"), + (0x2126, "M", "ω"), + (0x2127, "V"), + (0x2128, "M", "z"), + (0x2129, "V"), + (0x212A, "M", "k"), + (0x212B, "M", "å"), + (0x212C, "M", "b"), + (0x212D, "M", "c"), + (0x212E, "V"), + (0x212F, "M", "e"), + (0x2131, "M", "f"), + (0x2132, "X"), + (0x2133, "M", "m"), + (0x2134, "M", "o"), + (0x2135, "M", "א"), + (0x2136, "M", "ב"), + (0x2137, "M", "ג"), + (0x2138, "M", "ד"), + (0x2139, "M", "i"), + (0x213A, "V"), + (0x213B, "M", "fax"), + (0x213C, "M", "π"), + (0x213D, "M", "γ"), + (0x213F, "M", "π"), + (0x2140, "M", "∑"), + (0x2141, "V"), + (0x2145, "M", "d"), + (0x2147, "M", "e"), + (0x2148, "M", "i"), + (0x2149, "M", "j"), + (0x214A, "V"), + (0x2150, "M", "1⁄7"), + (0x2151, "M", "1⁄9"), + (0x2152, "M", "1⁄10"), + (0x2153, "M", "1⁄3"), + (0x2154, "M", "2⁄3"), + (0x2155, "M", "1⁄5"), + (0x2156, "M", "2⁄5"), + (0x2157, "M", "3⁄5"), + (0x2158, "M", "4⁄5"), + (0x2159, "M", "1⁄6"), + (0x215A, "M", "5⁄6"), + (0x215B, "M", "1⁄8"), + (0x215C, "M", "3⁄8"), + (0x215D, "M", "5⁄8"), + (0x215E, "M", "7⁄8"), + (0x215F, "M", "1⁄"), + (0x2160, "M", "i"), + (0x2161, "M", "ii"), + (0x2162, "M", "iii"), + (0x2163, "M", "iv"), + (0x2164, "M", "v"), + (0x2165, "M", "vi"), + (0x2166, "M", "vii"), + (0x2167, "M", "viii"), + (0x2168, "M", "ix"), + (0x2169, "M", "x"), + (0x216A, "M", "xi"), + (0x216B, "M", "xii"), + (0x216C, "M", "l"), + (0x216D, "M", "c"), + (0x216E, "M", "d"), + (0x216F, "M", "m"), + (0x2170, "M", "i"), + (0x2171, "M", "ii"), + (0x2172, "M", "iii"), + (0x2173, "M", "iv"), + (0x2174, "M", "v"), + (0x2175, "M", "vi"), + (0x2176, "M", "vii"), + (0x2177, "M", "viii"), + (0x2178, "M", "ix"), + (0x2179, "M", "x"), + (0x217A, "M", "xi"), + (0x217B, "M", "xii"), + (0x217C, "M", "l"), + ] + + +def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x217D, "M", "c"), + (0x217E, "M", "d"), + (0x217F, "M", "m"), + (0x2180, "V"), + (0x2183, "X"), + (0x2184, "V"), + (0x2189, "M", "0⁄3"), + (0x218A, "V"), + (0x218C, "X"), + (0x2190, "V"), + (0x222C, "M", "∫∫"), + (0x222D, "M", "∫∫∫"), + (0x222E, "V"), + (0x222F, "M", "∮∮"), + (0x2230, "M", "∮∮∮"), + (0x2231, "V"), + (0x2329, "M", "〈"), + (0x232A, "M", "〉"), + (0x232B, "V"), + (0x2427, "X"), + (0x2440, "V"), + (0x244B, "X"), + (0x2460, "M", "1"), + (0x2461, "M", "2"), + (0x2462, "M", "3"), + (0x2463, "M", "4"), + (0x2464, "M", "5"), + (0x2465, "M", "6"), + (0x2466, "M", "7"), + (0x2467, "M", "8"), + (0x2468, "M", "9"), + (0x2469, "M", "10"), + (0x246A, "M", "11"), + (0x246B, "M", "12"), + (0x246C, "M", "13"), + (0x246D, "M", "14"), + (0x246E, "M", "15"), + (0x246F, "M", "16"), + (0x2470, "M", "17"), + (0x2471, "M", "18"), + (0x2472, "M", "19"), + (0x2473, "M", "20"), + (0x2474, "3", "(1)"), + (0x2475, "3", "(2)"), + (0x2476, "3", "(3)"), + (0x2477, "3", "(4)"), + (0x2478, "3", "(5)"), + (0x2479, "3", "(6)"), + (0x247A, "3", "(7)"), + (0x247B, "3", "(8)"), + (0x247C, "3", "(9)"), + (0x247D, "3", "(10)"), + (0x247E, "3", "(11)"), + (0x247F, "3", "(12)"), + (0x2480, "3", "(13)"), + (0x2481, "3", "(14)"), + (0x2482, "3", "(15)"), + (0x2483, "3", "(16)"), + (0x2484, "3", "(17)"), + (0x2485, "3", "(18)"), + (0x2486, "3", "(19)"), + (0x2487, "3", "(20)"), + (0x2488, "X"), + (0x249C, "3", "(a)"), + (0x249D, "3", "(b)"), + (0x249E, "3", "(c)"), + (0x249F, "3", "(d)"), + (0x24A0, "3", "(e)"), + (0x24A1, "3", "(f)"), + (0x24A2, "3", "(g)"), + (0x24A3, "3", "(h)"), + (0x24A4, "3", "(i)"), + (0x24A5, "3", "(j)"), + (0x24A6, "3", "(k)"), + (0x24A7, "3", "(l)"), + (0x24A8, "3", "(m)"), + (0x24A9, "3", "(n)"), + (0x24AA, "3", "(o)"), + (0x24AB, "3", "(p)"), + (0x24AC, "3", "(q)"), + (0x24AD, "3", "(r)"), + (0x24AE, "3", "(s)"), + (0x24AF, "3", "(t)"), + (0x24B0, "3", "(u)"), + (0x24B1, "3", "(v)"), + (0x24B2, "3", "(w)"), + (0x24B3, "3", "(x)"), + (0x24B4, "3", "(y)"), + (0x24B5, "3", "(z)"), + (0x24B6, "M", "a"), + (0x24B7, "M", "b"), + (0x24B8, "M", "c"), + (0x24B9, "M", "d"), + (0x24BA, "M", "e"), + (0x24BB, "M", "f"), + (0x24BC, "M", "g"), + (0x24BD, "M", "h"), + (0x24BE, "M", "i"), + (0x24BF, "M", "j"), + (0x24C0, "M", "k"), + ] + + +def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x24C1, "M", "l"), + (0x24C2, "M", "m"), + (0x24C3, "M", "n"), + (0x24C4, "M", "o"), + (0x24C5, "M", "p"), + (0x24C6, "M", "q"), + (0x24C7, "M", "r"), + (0x24C8, "M", "s"), + (0x24C9, "M", "t"), + (0x24CA, "M", "u"), + (0x24CB, "M", "v"), + (0x24CC, "M", "w"), + (0x24CD, "M", "x"), + (0x24CE, "M", "y"), + (0x24CF, "M", "z"), + (0x24D0, "M", "a"), + (0x24D1, "M", "b"), + (0x24D2, "M", "c"), + (0x24D3, "M", "d"), + (0x24D4, "M", "e"), + (0x24D5, "M", "f"), + (0x24D6, "M", "g"), + (0x24D7, "M", "h"), + (0x24D8, "M", "i"), + (0x24D9, "M", "j"), + (0x24DA, "M", "k"), + (0x24DB, "M", "l"), + (0x24DC, "M", "m"), + (0x24DD, "M", "n"), + (0x24DE, "M", "o"), + (0x24DF, "M", "p"), + (0x24E0, "M", "q"), + (0x24E1, "M", "r"), + (0x24E2, "M", "s"), + (0x24E3, "M", "t"), + (0x24E4, "M", "u"), + (0x24E5, "M", "v"), + (0x24E6, "M", "w"), + (0x24E7, "M", "x"), + (0x24E8, "M", "y"), + (0x24E9, "M", "z"), + (0x24EA, "M", "0"), + (0x24EB, "V"), + (0x2A0C, "M", "∫∫∫∫"), + (0x2A0D, "V"), + (0x2A74, "3", "::="), + (0x2A75, "3", "=="), + (0x2A76, "3", "==="), + (0x2A77, "V"), + (0x2ADC, "M", "⫝̸"), + (0x2ADD, "V"), + (0x2B74, "X"), + (0x2B76, "V"), + (0x2B96, "X"), + (0x2B97, "V"), + (0x2C00, "M", "ⰰ"), + (0x2C01, "M", "ⰱ"), + (0x2C02, "M", "ⰲ"), + (0x2C03, "M", "ⰳ"), + (0x2C04, "M", "ⰴ"), + (0x2C05, "M", "ⰵ"), + (0x2C06, "M", "ⰶ"), + (0x2C07, "M", "ⰷ"), + (0x2C08, "M", "ⰸ"), + (0x2C09, "M", "ⰹ"), + (0x2C0A, "M", "ⰺ"), + (0x2C0B, "M", "ⰻ"), + (0x2C0C, "M", "ⰼ"), + (0x2C0D, "M", "ⰽ"), + (0x2C0E, "M", "ⰾ"), + (0x2C0F, "M", "ⰿ"), + (0x2C10, "M", "ⱀ"), + (0x2C11, "M", "ⱁ"), + (0x2C12, "M", "ⱂ"), + (0x2C13, "M", "ⱃ"), + (0x2C14, "M", "ⱄ"), + (0x2C15, "M", "ⱅ"), + (0x2C16, "M", "ⱆ"), + (0x2C17, "M", "ⱇ"), + (0x2C18, "M", "ⱈ"), + (0x2C19, "M", "ⱉ"), + (0x2C1A, "M", "ⱊ"), + (0x2C1B, "M", "ⱋ"), + (0x2C1C, "M", "ⱌ"), + (0x2C1D, "M", "ⱍ"), + (0x2C1E, "M", "ⱎ"), + (0x2C1F, "M", "ⱏ"), + (0x2C20, "M", "ⱐ"), + (0x2C21, "M", "ⱑ"), + (0x2C22, "M", "ⱒ"), + (0x2C23, "M", "ⱓ"), + (0x2C24, "M", "ⱔ"), + (0x2C25, "M", "ⱕ"), + (0x2C26, "M", "ⱖ"), + (0x2C27, "M", "ⱗ"), + (0x2C28, "M", "ⱘ"), + (0x2C29, "M", "ⱙ"), + (0x2C2A, "M", "ⱚ"), + (0x2C2B, "M", "ⱛ"), + (0x2C2C, "M", "ⱜ"), + ] + + +def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2C2D, "M", "ⱝ"), + (0x2C2E, "M", "ⱞ"), + (0x2C2F, "M", "ⱟ"), + (0x2C30, "V"), + (0x2C60, "M", "ⱡ"), + (0x2C61, "V"), + (0x2C62, "M", "ɫ"), + (0x2C63, "M", "ᵽ"), + (0x2C64, "M", "ɽ"), + (0x2C65, "V"), + (0x2C67, "M", "ⱨ"), + (0x2C68, "V"), + (0x2C69, "M", "ⱪ"), + (0x2C6A, "V"), + (0x2C6B, "M", "ⱬ"), + (0x2C6C, "V"), + (0x2C6D, "M", "ɑ"), + (0x2C6E, "M", "ɱ"), + (0x2C6F, "M", "ɐ"), + (0x2C70, "M", "ɒ"), + (0x2C71, "V"), + (0x2C72, "M", "ⱳ"), + (0x2C73, "V"), + (0x2C75, "M", "ⱶ"), + (0x2C76, "V"), + (0x2C7C, "M", "j"), + (0x2C7D, "M", "v"), + (0x2C7E, "M", "ȿ"), + (0x2C7F, "M", "ɀ"), + (0x2C80, "M", "ⲁ"), + (0x2C81, "V"), + (0x2C82, "M", "ⲃ"), + (0x2C83, "V"), + (0x2C84, "M", "ⲅ"), + (0x2C85, "V"), + (0x2C86, "M", "ⲇ"), + (0x2C87, "V"), + (0x2C88, "M", "ⲉ"), + (0x2C89, "V"), + (0x2C8A, "M", "ⲋ"), + (0x2C8B, "V"), + (0x2C8C, "M", "ⲍ"), + (0x2C8D, "V"), + (0x2C8E, "M", "ⲏ"), + (0x2C8F, "V"), + (0x2C90, "M", "ⲑ"), + (0x2C91, "V"), + (0x2C92, "M", "ⲓ"), + (0x2C93, "V"), + (0x2C94, "M", "ⲕ"), + (0x2C95, "V"), + (0x2C96, "M", "ⲗ"), + (0x2C97, "V"), + (0x2C98, "M", "ⲙ"), + (0x2C99, "V"), + (0x2C9A, "M", "ⲛ"), + (0x2C9B, "V"), + (0x2C9C, "M", "ⲝ"), + (0x2C9D, "V"), + (0x2C9E, "M", "ⲟ"), + (0x2C9F, "V"), + (0x2CA0, "M", "ⲡ"), + (0x2CA1, "V"), + (0x2CA2, "M", "ⲣ"), + (0x2CA3, "V"), + (0x2CA4, "M", "ⲥ"), + (0x2CA5, "V"), + (0x2CA6, "M", "ⲧ"), + (0x2CA7, "V"), + (0x2CA8, "M", "ⲩ"), + (0x2CA9, "V"), + (0x2CAA, "M", "ⲫ"), + (0x2CAB, "V"), + (0x2CAC, "M", "ⲭ"), + (0x2CAD, "V"), + (0x2CAE, "M", "ⲯ"), + (0x2CAF, "V"), + (0x2CB0, "M", "ⲱ"), + (0x2CB1, "V"), + (0x2CB2, "M", "ⲳ"), + (0x2CB3, "V"), + (0x2CB4, "M", "ⲵ"), + (0x2CB5, "V"), + (0x2CB6, "M", "ⲷ"), + (0x2CB7, "V"), + (0x2CB8, "M", "ⲹ"), + (0x2CB9, "V"), + (0x2CBA, "M", "ⲻ"), + (0x2CBB, "V"), + (0x2CBC, "M", "ⲽ"), + (0x2CBD, "V"), + (0x2CBE, "M", "ⲿ"), + (0x2CBF, "V"), + (0x2CC0, "M", "ⳁ"), + (0x2CC1, "V"), + (0x2CC2, "M", "ⳃ"), + (0x2CC3, "V"), + (0x2CC4, "M", "ⳅ"), + (0x2CC5, "V"), + (0x2CC6, "M", "ⳇ"), + ] + + +def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2CC7, "V"), + (0x2CC8, "M", "ⳉ"), + (0x2CC9, "V"), + (0x2CCA, "M", "ⳋ"), + (0x2CCB, "V"), + (0x2CCC, "M", "ⳍ"), + (0x2CCD, "V"), + (0x2CCE, "M", "ⳏ"), + (0x2CCF, "V"), + (0x2CD0, "M", "ⳑ"), + (0x2CD1, "V"), + (0x2CD2, "M", "ⳓ"), + (0x2CD3, "V"), + (0x2CD4, "M", "ⳕ"), + (0x2CD5, "V"), + (0x2CD6, "M", "ⳗ"), + (0x2CD7, "V"), + (0x2CD8, "M", "ⳙ"), + (0x2CD9, "V"), + (0x2CDA, "M", "ⳛ"), + (0x2CDB, "V"), + (0x2CDC, "M", "ⳝ"), + (0x2CDD, "V"), + (0x2CDE, "M", "ⳟ"), + (0x2CDF, "V"), + (0x2CE0, "M", "ⳡ"), + (0x2CE1, "V"), + (0x2CE2, "M", "ⳣ"), + (0x2CE3, "V"), + (0x2CEB, "M", "ⳬ"), + (0x2CEC, "V"), + (0x2CED, "M", "ⳮ"), + (0x2CEE, "V"), + (0x2CF2, "M", "ⳳ"), + (0x2CF3, "V"), + (0x2CF4, "X"), + (0x2CF9, "V"), + (0x2D26, "X"), + (0x2D27, "V"), + (0x2D28, "X"), + (0x2D2D, "V"), + (0x2D2E, "X"), + (0x2D30, "V"), + (0x2D68, "X"), + (0x2D6F, "M", "ⵡ"), + (0x2D70, "V"), + (0x2D71, "X"), + (0x2D7F, "V"), + (0x2D97, "X"), + (0x2DA0, "V"), + (0x2DA7, "X"), + (0x2DA8, "V"), + (0x2DAF, "X"), + (0x2DB0, "V"), + (0x2DB7, "X"), + (0x2DB8, "V"), + (0x2DBF, "X"), + (0x2DC0, "V"), + (0x2DC7, "X"), + (0x2DC8, "V"), + (0x2DCF, "X"), + (0x2DD0, "V"), + (0x2DD7, "X"), + (0x2DD8, "V"), + (0x2DDF, "X"), + (0x2DE0, "V"), + (0x2E5E, "X"), + (0x2E80, "V"), + (0x2E9A, "X"), + (0x2E9B, "V"), + (0x2E9F, "M", "母"), + (0x2EA0, "V"), + (0x2EF3, "M", "龟"), + (0x2EF4, "X"), + (0x2F00, "M", "一"), + (0x2F01, "M", "丨"), + (0x2F02, "M", "丶"), + (0x2F03, "M", "丿"), + (0x2F04, "M", "乙"), + (0x2F05, "M", "亅"), + (0x2F06, "M", "二"), + (0x2F07, "M", "亠"), + (0x2F08, "M", "人"), + (0x2F09, "M", "儿"), + (0x2F0A, "M", "入"), + (0x2F0B, "M", "八"), + (0x2F0C, "M", "冂"), + (0x2F0D, "M", "冖"), + (0x2F0E, "M", "冫"), + (0x2F0F, "M", "几"), + (0x2F10, "M", "凵"), + (0x2F11, "M", "刀"), + (0x2F12, "M", "力"), + (0x2F13, "M", "勹"), + (0x2F14, "M", "匕"), + (0x2F15, "M", "匚"), + (0x2F16, "M", "匸"), + (0x2F17, "M", "十"), + (0x2F18, "M", "卜"), + (0x2F19, "M", "卩"), + ] + + +def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F1A, "M", "厂"), + (0x2F1B, "M", "厶"), + (0x2F1C, "M", "又"), + (0x2F1D, "M", "口"), + (0x2F1E, "M", "囗"), + (0x2F1F, "M", "土"), + (0x2F20, "M", "士"), + (0x2F21, "M", "夂"), + (0x2F22, "M", "夊"), + (0x2F23, "M", "夕"), + (0x2F24, "M", "大"), + (0x2F25, "M", "女"), + (0x2F26, "M", "子"), + (0x2F27, "M", "宀"), + (0x2F28, "M", "寸"), + (0x2F29, "M", "小"), + (0x2F2A, "M", "尢"), + (0x2F2B, "M", "尸"), + (0x2F2C, "M", "屮"), + (0x2F2D, "M", "山"), + (0x2F2E, "M", "巛"), + (0x2F2F, "M", "工"), + (0x2F30, "M", "己"), + (0x2F31, "M", "巾"), + (0x2F32, "M", "干"), + (0x2F33, "M", "幺"), + (0x2F34, "M", "广"), + (0x2F35, "M", "廴"), + (0x2F36, "M", "廾"), + (0x2F37, "M", "弋"), + (0x2F38, "M", "弓"), + (0x2F39, "M", "彐"), + (0x2F3A, "M", "彡"), + (0x2F3B, "M", "彳"), + (0x2F3C, "M", "心"), + (0x2F3D, "M", "戈"), + (0x2F3E, "M", "戶"), + (0x2F3F, "M", "手"), + (0x2F40, "M", "支"), + (0x2F41, "M", "攴"), + (0x2F42, "M", "文"), + (0x2F43, "M", "斗"), + (0x2F44, "M", "斤"), + (0x2F45, "M", "方"), + (0x2F46, "M", "无"), + (0x2F47, "M", "日"), + (0x2F48, "M", "曰"), + (0x2F49, "M", "月"), + (0x2F4A, "M", "木"), + (0x2F4B, "M", "欠"), + (0x2F4C, "M", "止"), + (0x2F4D, "M", "歹"), + (0x2F4E, "M", "殳"), + (0x2F4F, "M", "毋"), + (0x2F50, "M", "比"), + (0x2F51, "M", "毛"), + (0x2F52, "M", "氏"), + (0x2F53, "M", "气"), + (0x2F54, "M", "水"), + (0x2F55, "M", "火"), + (0x2F56, "M", "爪"), + (0x2F57, "M", "父"), + (0x2F58, "M", "爻"), + (0x2F59, "M", "爿"), + (0x2F5A, "M", "片"), + (0x2F5B, "M", "牙"), + (0x2F5C, "M", "牛"), + (0x2F5D, "M", "犬"), + (0x2F5E, "M", "玄"), + (0x2F5F, "M", "玉"), + (0x2F60, "M", "瓜"), + (0x2F61, "M", "瓦"), + (0x2F62, "M", "甘"), + (0x2F63, "M", "生"), + (0x2F64, "M", "用"), + (0x2F65, "M", "田"), + (0x2F66, "M", "疋"), + (0x2F67, "M", "疒"), + (0x2F68, "M", "癶"), + (0x2F69, "M", "白"), + (0x2F6A, "M", "皮"), + (0x2F6B, "M", "皿"), + (0x2F6C, "M", "目"), + (0x2F6D, "M", "矛"), + (0x2F6E, "M", "矢"), + (0x2F6F, "M", "石"), + (0x2F70, "M", "示"), + (0x2F71, "M", "禸"), + (0x2F72, "M", "禾"), + (0x2F73, "M", "穴"), + (0x2F74, "M", "立"), + (0x2F75, "M", "竹"), + (0x2F76, "M", "米"), + (0x2F77, "M", "糸"), + (0x2F78, "M", "缶"), + (0x2F79, "M", "网"), + (0x2F7A, "M", "羊"), + (0x2F7B, "M", "羽"), + (0x2F7C, "M", "老"), + (0x2F7D, "M", "而"), + ] + + +def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F7E, "M", "耒"), + (0x2F7F, "M", "耳"), + (0x2F80, "M", "聿"), + (0x2F81, "M", "肉"), + (0x2F82, "M", "臣"), + (0x2F83, "M", "自"), + (0x2F84, "M", "至"), + (0x2F85, "M", "臼"), + (0x2F86, "M", "舌"), + (0x2F87, "M", "舛"), + (0x2F88, "M", "舟"), + (0x2F89, "M", "艮"), + (0x2F8A, "M", "色"), + (0x2F8B, "M", "艸"), + (0x2F8C, "M", "虍"), + (0x2F8D, "M", "虫"), + (0x2F8E, "M", "血"), + (0x2F8F, "M", "行"), + (0x2F90, "M", "衣"), + (0x2F91, "M", "襾"), + (0x2F92, "M", "見"), + (0x2F93, "M", "角"), + (0x2F94, "M", "言"), + (0x2F95, "M", "谷"), + (0x2F96, "M", "豆"), + (0x2F97, "M", "豕"), + (0x2F98, "M", "豸"), + (0x2F99, "M", "貝"), + (0x2F9A, "M", "赤"), + (0x2F9B, "M", "走"), + (0x2F9C, "M", "足"), + (0x2F9D, "M", "身"), + (0x2F9E, "M", "車"), + (0x2F9F, "M", "辛"), + (0x2FA0, "M", "辰"), + (0x2FA1, "M", "辵"), + (0x2FA2, "M", "邑"), + (0x2FA3, "M", "酉"), + (0x2FA4, "M", "釆"), + (0x2FA5, "M", "里"), + (0x2FA6, "M", "金"), + (0x2FA7, "M", "長"), + (0x2FA8, "M", "門"), + (0x2FA9, "M", "阜"), + (0x2FAA, "M", "隶"), + (0x2FAB, "M", "隹"), + (0x2FAC, "M", "雨"), + (0x2FAD, "M", "靑"), + (0x2FAE, "M", "非"), + (0x2FAF, "M", "面"), + (0x2FB0, "M", "革"), + (0x2FB1, "M", "韋"), + (0x2FB2, "M", "韭"), + (0x2FB3, "M", "音"), + (0x2FB4, "M", "頁"), + (0x2FB5, "M", "風"), + (0x2FB6, "M", "飛"), + (0x2FB7, "M", "食"), + (0x2FB8, "M", "首"), + (0x2FB9, "M", "香"), + (0x2FBA, "M", "馬"), + (0x2FBB, "M", "骨"), + (0x2FBC, "M", "高"), + (0x2FBD, "M", "髟"), + (0x2FBE, "M", "鬥"), + (0x2FBF, "M", "鬯"), + (0x2FC0, "M", "鬲"), + (0x2FC1, "M", "鬼"), + (0x2FC2, "M", "魚"), + (0x2FC3, "M", "鳥"), + (0x2FC4, "M", "鹵"), + (0x2FC5, "M", "鹿"), + (0x2FC6, "M", "麥"), + (0x2FC7, "M", "麻"), + (0x2FC8, "M", "黃"), + (0x2FC9, "M", "黍"), + (0x2FCA, "M", "黑"), + (0x2FCB, "M", "黹"), + (0x2FCC, "M", "黽"), + (0x2FCD, "M", "鼎"), + (0x2FCE, "M", "鼓"), + (0x2FCF, "M", "鼠"), + (0x2FD0, "M", "鼻"), + (0x2FD1, "M", "齊"), + (0x2FD2, "M", "齒"), + (0x2FD3, "M", "龍"), + (0x2FD4, "M", "龜"), + (0x2FD5, "M", "龠"), + (0x2FD6, "X"), + (0x3000, "3", " "), + (0x3001, "V"), + (0x3002, "M", "."), + (0x3003, "V"), + (0x3036, "M", "〒"), + (0x3037, "V"), + (0x3038, "M", "十"), + (0x3039, "M", "卄"), + (0x303A, "M", "卅"), + (0x303B, "V"), + (0x3040, "X"), + ] + + +def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3041, "V"), + (0x3097, "X"), + (0x3099, "V"), + (0x309B, "3", " ゙"), + (0x309C, "3", " ゚"), + (0x309D, "V"), + (0x309F, "M", "より"), + (0x30A0, "V"), + (0x30FF, "M", "コト"), + (0x3100, "X"), + (0x3105, "V"), + (0x3130, "X"), + (0x3131, "M", "ᄀ"), + (0x3132, "M", "ᄁ"), + (0x3133, "M", "ᆪ"), + (0x3134, "M", "ᄂ"), + (0x3135, "M", "ᆬ"), + (0x3136, "M", "ᆭ"), + (0x3137, "M", "ᄃ"), + (0x3138, "M", "ᄄ"), + (0x3139, "M", "ᄅ"), + (0x313A, "M", "ᆰ"), + (0x313B, "M", "ᆱ"), + (0x313C, "M", "ᆲ"), + (0x313D, "M", "ᆳ"), + (0x313E, "M", "ᆴ"), + (0x313F, "M", "ᆵ"), + (0x3140, "M", "ᄚ"), + (0x3141, "M", "ᄆ"), + (0x3142, "M", "ᄇ"), + (0x3143, "M", "ᄈ"), + (0x3144, "M", "ᄡ"), + (0x3145, "M", "ᄉ"), + (0x3146, "M", "ᄊ"), + (0x3147, "M", "ᄋ"), + (0x3148, "M", "ᄌ"), + (0x3149, "M", "ᄍ"), + (0x314A, "M", "ᄎ"), + (0x314B, "M", "ᄏ"), + (0x314C, "M", "ᄐ"), + (0x314D, "M", "ᄑ"), + (0x314E, "M", "ᄒ"), + (0x314F, "M", "ᅡ"), + (0x3150, "M", "ᅢ"), + (0x3151, "M", "ᅣ"), + (0x3152, "M", "ᅤ"), + (0x3153, "M", "ᅥ"), + (0x3154, "M", "ᅦ"), + (0x3155, "M", "ᅧ"), + (0x3156, "M", "ᅨ"), + (0x3157, "M", "ᅩ"), + (0x3158, "M", "ᅪ"), + (0x3159, "M", "ᅫ"), + (0x315A, "M", "ᅬ"), + (0x315B, "M", "ᅭ"), + (0x315C, "M", "ᅮ"), + (0x315D, "M", "ᅯ"), + (0x315E, "M", "ᅰ"), + (0x315F, "M", "ᅱ"), + (0x3160, "M", "ᅲ"), + (0x3161, "M", "ᅳ"), + (0x3162, "M", "ᅴ"), + (0x3163, "M", "ᅵ"), + (0x3164, "X"), + (0x3165, "M", "ᄔ"), + (0x3166, "M", "ᄕ"), + (0x3167, "M", "ᇇ"), + (0x3168, "M", "ᇈ"), + (0x3169, "M", "ᇌ"), + (0x316A, "M", "ᇎ"), + (0x316B, "M", "ᇓ"), + (0x316C, "M", "ᇗ"), + (0x316D, "M", "ᇙ"), + (0x316E, "M", "ᄜ"), + (0x316F, "M", "ᇝ"), + (0x3170, "M", "ᇟ"), + (0x3171, "M", "ᄝ"), + (0x3172, "M", "ᄞ"), + (0x3173, "M", "ᄠ"), + (0x3174, "M", "ᄢ"), + (0x3175, "M", "ᄣ"), + (0x3176, "M", "ᄧ"), + (0x3177, "M", "ᄩ"), + (0x3178, "M", "ᄫ"), + (0x3179, "M", "ᄬ"), + (0x317A, "M", "ᄭ"), + (0x317B, "M", "ᄮ"), + (0x317C, "M", "ᄯ"), + (0x317D, "M", "ᄲ"), + (0x317E, "M", "ᄶ"), + (0x317F, "M", "ᅀ"), + (0x3180, "M", "ᅇ"), + (0x3181, "M", "ᅌ"), + (0x3182, "M", "ᇱ"), + (0x3183, "M", "ᇲ"), + (0x3184, "M", "ᅗ"), + (0x3185, "M", "ᅘ"), + (0x3186, "M", "ᅙ"), + (0x3187, "M", "ᆄ"), + (0x3188, "M", "ᆅ"), + ] + + +def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3189, "M", "ᆈ"), + (0x318A, "M", "ᆑ"), + (0x318B, "M", "ᆒ"), + (0x318C, "M", "ᆔ"), + (0x318D, "M", "ᆞ"), + (0x318E, "M", "ᆡ"), + (0x318F, "X"), + (0x3190, "V"), + (0x3192, "M", "一"), + (0x3193, "M", "二"), + (0x3194, "M", "三"), + (0x3195, "M", "四"), + (0x3196, "M", "上"), + (0x3197, "M", "中"), + (0x3198, "M", "下"), + (0x3199, "M", "甲"), + (0x319A, "M", "乙"), + (0x319B, "M", "丙"), + (0x319C, "M", "丁"), + (0x319D, "M", "天"), + (0x319E, "M", "地"), + (0x319F, "M", "人"), + (0x31A0, "V"), + (0x31E4, "X"), + (0x31F0, "V"), + (0x3200, "3", "(ᄀ)"), + (0x3201, "3", "(ᄂ)"), + (0x3202, "3", "(ᄃ)"), + (0x3203, "3", "(ᄅ)"), + (0x3204, "3", "(ᄆ)"), + (0x3205, "3", "(ᄇ)"), + (0x3206, "3", "(ᄉ)"), + (0x3207, "3", "(ᄋ)"), + (0x3208, "3", "(ᄌ)"), + (0x3209, "3", "(ᄎ)"), + (0x320A, "3", "(ᄏ)"), + (0x320B, "3", "(ᄐ)"), + (0x320C, "3", "(ᄑ)"), + (0x320D, "3", "(ᄒ)"), + (0x320E, "3", "(가)"), + (0x320F, "3", "(나)"), + (0x3210, "3", "(다)"), + (0x3211, "3", "(라)"), + (0x3212, "3", "(마)"), + (0x3213, "3", "(바)"), + (0x3214, "3", "(사)"), + (0x3215, "3", "(아)"), + (0x3216, "3", "(자)"), + (0x3217, "3", "(차)"), + (0x3218, "3", "(카)"), + (0x3219, "3", "(타)"), + (0x321A, "3", "(파)"), + (0x321B, "3", "(하)"), + (0x321C, "3", "(주)"), + (0x321D, "3", "(오전)"), + (0x321E, "3", "(오후)"), + (0x321F, "X"), + (0x3220, "3", "(一)"), + (0x3221, "3", "(二)"), + (0x3222, "3", "(三)"), + (0x3223, "3", "(四)"), + (0x3224, "3", "(五)"), + (0x3225, "3", "(六)"), + (0x3226, "3", "(七)"), + (0x3227, "3", "(八)"), + (0x3228, "3", "(九)"), + (0x3229, "3", "(十)"), + (0x322A, "3", "(月)"), + (0x322B, "3", "(火)"), + (0x322C, "3", "(水)"), + (0x322D, "3", "(木)"), + (0x322E, "3", "(金)"), + (0x322F, "3", "(土)"), + (0x3230, "3", "(日)"), + (0x3231, "3", "(株)"), + (0x3232, "3", "(有)"), + (0x3233, "3", "(社)"), + (0x3234, "3", "(名)"), + (0x3235, "3", "(特)"), + (0x3236, "3", "(財)"), + (0x3237, "3", "(祝)"), + (0x3238, "3", "(労)"), + (0x3239, "3", "(代)"), + (0x323A, "3", "(呼)"), + (0x323B, "3", "(学)"), + (0x323C, "3", "(監)"), + (0x323D, "3", "(企)"), + (0x323E, "3", "(資)"), + (0x323F, "3", "(協)"), + (0x3240, "3", "(祭)"), + (0x3241, "3", "(休)"), + (0x3242, "3", "(自)"), + (0x3243, "3", "(至)"), + (0x3244, "M", "問"), + (0x3245, "M", "幼"), + (0x3246, "M", "文"), + (0x3247, "M", "箏"), + (0x3248, "V"), + (0x3250, "M", "pte"), + (0x3251, "M", "21"), + ] + + +def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3252, "M", "22"), + (0x3253, "M", "23"), + (0x3254, "M", "24"), + (0x3255, "M", "25"), + (0x3256, "M", "26"), + (0x3257, "M", "27"), + (0x3258, "M", "28"), + (0x3259, "M", "29"), + (0x325A, "M", "30"), + (0x325B, "M", "31"), + (0x325C, "M", "32"), + (0x325D, "M", "33"), + (0x325E, "M", "34"), + (0x325F, "M", "35"), + (0x3260, "M", "ᄀ"), + (0x3261, "M", "ᄂ"), + (0x3262, "M", "ᄃ"), + (0x3263, "M", "ᄅ"), + (0x3264, "M", "ᄆ"), + (0x3265, "M", "ᄇ"), + (0x3266, "M", "ᄉ"), + (0x3267, "M", "ᄋ"), + (0x3268, "M", "ᄌ"), + (0x3269, "M", "ᄎ"), + (0x326A, "M", "ᄏ"), + (0x326B, "M", "ᄐ"), + (0x326C, "M", "ᄑ"), + (0x326D, "M", "ᄒ"), + (0x326E, "M", "가"), + (0x326F, "M", "나"), + (0x3270, "M", "다"), + (0x3271, "M", "라"), + (0x3272, "M", "마"), + (0x3273, "M", "바"), + (0x3274, "M", "사"), + (0x3275, "M", "아"), + (0x3276, "M", "자"), + (0x3277, "M", "차"), + (0x3278, "M", "카"), + (0x3279, "M", "타"), + (0x327A, "M", "파"), + (0x327B, "M", "하"), + (0x327C, "M", "참고"), + (0x327D, "M", "주의"), + (0x327E, "M", "우"), + (0x327F, "V"), + (0x3280, "M", "一"), + (0x3281, "M", "二"), + (0x3282, "M", "三"), + (0x3283, "M", "四"), + (0x3284, "M", "五"), + (0x3285, "M", "六"), + (0x3286, "M", "七"), + (0x3287, "M", "八"), + (0x3288, "M", "九"), + (0x3289, "M", "十"), + (0x328A, "M", "月"), + (0x328B, "M", "火"), + (0x328C, "M", "水"), + (0x328D, "M", "木"), + (0x328E, "M", "金"), + (0x328F, "M", "土"), + (0x3290, "M", "日"), + (0x3291, "M", "株"), + (0x3292, "M", "有"), + (0x3293, "M", "社"), + (0x3294, "M", "名"), + (0x3295, "M", "特"), + (0x3296, "M", "財"), + (0x3297, "M", "祝"), + (0x3298, "M", "労"), + (0x3299, "M", "秘"), + (0x329A, "M", "男"), + (0x329B, "M", "女"), + (0x329C, "M", "適"), + (0x329D, "M", "優"), + (0x329E, "M", "印"), + (0x329F, "M", "注"), + (0x32A0, "M", "項"), + (0x32A1, "M", "休"), + (0x32A2, "M", "写"), + (0x32A3, "M", "正"), + (0x32A4, "M", "上"), + (0x32A5, "M", "中"), + (0x32A6, "M", "下"), + (0x32A7, "M", "左"), + (0x32A8, "M", "右"), + (0x32A9, "M", "医"), + (0x32AA, "M", "宗"), + (0x32AB, "M", "学"), + (0x32AC, "M", "監"), + (0x32AD, "M", "企"), + (0x32AE, "M", "資"), + (0x32AF, "M", "協"), + (0x32B0, "M", "夜"), + (0x32B1, "M", "36"), + (0x32B2, "M", "37"), + (0x32B3, "M", "38"), + (0x32B4, "M", "39"), + (0x32B5, "M", "40"), + ] + + +def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x32B6, "M", "41"), + (0x32B7, "M", "42"), + (0x32B8, "M", "43"), + (0x32B9, "M", "44"), + (0x32BA, "M", "45"), + (0x32BB, "M", "46"), + (0x32BC, "M", "47"), + (0x32BD, "M", "48"), + (0x32BE, "M", "49"), + (0x32BF, "M", "50"), + (0x32C0, "M", "1月"), + (0x32C1, "M", "2月"), + (0x32C2, "M", "3月"), + (0x32C3, "M", "4月"), + (0x32C4, "M", "5月"), + (0x32C5, "M", "6月"), + (0x32C6, "M", "7月"), + (0x32C7, "M", "8月"), + (0x32C8, "M", "9月"), + (0x32C9, "M", "10月"), + (0x32CA, "M", "11月"), + (0x32CB, "M", "12月"), + (0x32CC, "M", "hg"), + (0x32CD, "M", "erg"), + (0x32CE, "M", "ev"), + (0x32CF, "M", "ltd"), + (0x32D0, "M", "ア"), + (0x32D1, "M", "イ"), + (0x32D2, "M", "ウ"), + (0x32D3, "M", "エ"), + (0x32D4, "M", "オ"), + (0x32D5, "M", "カ"), + (0x32D6, "M", "キ"), + (0x32D7, "M", "ク"), + (0x32D8, "M", "ケ"), + (0x32D9, "M", "コ"), + (0x32DA, "M", "サ"), + (0x32DB, "M", "シ"), + (0x32DC, "M", "ス"), + (0x32DD, "M", "セ"), + (0x32DE, "M", "ソ"), + (0x32DF, "M", "タ"), + (0x32E0, "M", "チ"), + (0x32E1, "M", "ツ"), + (0x32E2, "M", "テ"), + (0x32E3, "M", "ト"), + (0x32E4, "M", "ナ"), + (0x32E5, "M", "ニ"), + (0x32E6, "M", "ヌ"), + (0x32E7, "M", "ネ"), + (0x32E8, "M", "ノ"), + (0x32E9, "M", "ハ"), + (0x32EA, "M", "ヒ"), + (0x32EB, "M", "フ"), + (0x32EC, "M", "ヘ"), + (0x32ED, "M", "ホ"), + (0x32EE, "M", "マ"), + (0x32EF, "M", "ミ"), + (0x32F0, "M", "ム"), + (0x32F1, "M", "メ"), + (0x32F2, "M", "モ"), + (0x32F3, "M", "ヤ"), + (0x32F4, "M", "ユ"), + (0x32F5, "M", "ヨ"), + (0x32F6, "M", "ラ"), + (0x32F7, "M", "リ"), + (0x32F8, "M", "ル"), + (0x32F9, "M", "レ"), + (0x32FA, "M", "ロ"), + (0x32FB, "M", "ワ"), + (0x32FC, "M", "ヰ"), + (0x32FD, "M", "ヱ"), + (0x32FE, "M", "ヲ"), + (0x32FF, "M", "令和"), + (0x3300, "M", "アパート"), + (0x3301, "M", "アルファ"), + (0x3302, "M", "アンペア"), + (0x3303, "M", "アール"), + (0x3304, "M", "イニング"), + (0x3305, "M", "インチ"), + (0x3306, "M", "ウォン"), + (0x3307, "M", "エスクード"), + (0x3308, "M", "エーカー"), + (0x3309, "M", "オンス"), + (0x330A, "M", "オーム"), + (0x330B, "M", "カイリ"), + (0x330C, "M", "カラット"), + (0x330D, "M", "カロリー"), + (0x330E, "M", "ガロン"), + (0x330F, "M", "ガンマ"), + (0x3310, "M", "ギガ"), + (0x3311, "M", "ギニー"), + (0x3312, "M", "キュリー"), + (0x3313, "M", "ギルダー"), + (0x3314, "M", "キロ"), + (0x3315, "M", "キログラム"), + (0x3316, "M", "キロメートル"), + (0x3317, "M", "キロワット"), + (0x3318, "M", "グラム"), + (0x3319, "M", "グラムトン"), + ] + + +def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x331A, "M", "クルゼイロ"), + (0x331B, "M", "クローネ"), + (0x331C, "M", "ケース"), + (0x331D, "M", "コルナ"), + (0x331E, "M", "コーポ"), + (0x331F, "M", "サイクル"), + (0x3320, "M", "サンチーム"), + (0x3321, "M", "シリング"), + (0x3322, "M", "センチ"), + (0x3323, "M", "セント"), + (0x3324, "M", "ダース"), + (0x3325, "M", "デシ"), + (0x3326, "M", "ドル"), + (0x3327, "M", "トン"), + (0x3328, "M", "ナノ"), + (0x3329, "M", "ノット"), + (0x332A, "M", "ハイツ"), + (0x332B, "M", "パーセント"), + (0x332C, "M", "パーツ"), + (0x332D, "M", "バーレル"), + (0x332E, "M", "ピアストル"), + (0x332F, "M", "ピクル"), + (0x3330, "M", "ピコ"), + (0x3331, "M", "ビル"), + (0x3332, "M", "ファラッド"), + (0x3333, "M", "フィート"), + (0x3334, "M", "ブッシェル"), + (0x3335, "M", "フラン"), + (0x3336, "M", "ヘクタール"), + (0x3337, "M", "ペソ"), + (0x3338, "M", "ペニヒ"), + (0x3339, "M", "ヘルツ"), + (0x333A, "M", "ペンス"), + (0x333B, "M", "ページ"), + (0x333C, "M", "ベータ"), + (0x333D, "M", "ポイント"), + (0x333E, "M", "ボルト"), + (0x333F, "M", "ホン"), + (0x3340, "M", "ポンド"), + (0x3341, "M", "ホール"), + (0x3342, "M", "ホーン"), + (0x3343, "M", "マイクロ"), + (0x3344, "M", "マイル"), + (0x3345, "M", "マッハ"), + (0x3346, "M", "マルク"), + (0x3347, "M", "マンション"), + (0x3348, "M", "ミクロン"), + (0x3349, "M", "ミリ"), + (0x334A, "M", "ミリバール"), + (0x334B, "M", "メガ"), + (0x334C, "M", "メガトン"), + (0x334D, "M", "メートル"), + (0x334E, "M", "ヤード"), + (0x334F, "M", "ヤール"), + (0x3350, "M", "ユアン"), + (0x3351, "M", "リットル"), + (0x3352, "M", "リラ"), + (0x3353, "M", "ルピー"), + (0x3354, "M", "ルーブル"), + (0x3355, "M", "レム"), + (0x3356, "M", "レントゲン"), + (0x3357, "M", "ワット"), + (0x3358, "M", "0点"), + (0x3359, "M", "1点"), + (0x335A, "M", "2点"), + (0x335B, "M", "3点"), + (0x335C, "M", "4点"), + (0x335D, "M", "5点"), + (0x335E, "M", "6点"), + (0x335F, "M", "7点"), + (0x3360, "M", "8点"), + (0x3361, "M", "9点"), + (0x3362, "M", "10点"), + (0x3363, "M", "11点"), + (0x3364, "M", "12点"), + (0x3365, "M", "13点"), + (0x3366, "M", "14点"), + (0x3367, "M", "15点"), + (0x3368, "M", "16点"), + (0x3369, "M", "17点"), + (0x336A, "M", "18点"), + (0x336B, "M", "19点"), + (0x336C, "M", "20点"), + (0x336D, "M", "21点"), + (0x336E, "M", "22点"), + (0x336F, "M", "23点"), + (0x3370, "M", "24点"), + (0x3371, "M", "hpa"), + (0x3372, "M", "da"), + (0x3373, "M", "au"), + (0x3374, "M", "bar"), + (0x3375, "M", "ov"), + (0x3376, "M", "pc"), + (0x3377, "M", "dm"), + (0x3378, "M", "dm2"), + (0x3379, "M", "dm3"), + (0x337A, "M", "iu"), + (0x337B, "M", "平成"), + (0x337C, "M", "昭和"), + (0x337D, "M", "大正"), + ] + + +def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x337E, "M", "明治"), + (0x337F, "M", "株式会社"), + (0x3380, "M", "pa"), + (0x3381, "M", "na"), + (0x3382, "M", "μa"), + (0x3383, "M", "ma"), + (0x3384, "M", "ka"), + (0x3385, "M", "kb"), + (0x3386, "M", "mb"), + (0x3387, "M", "gb"), + (0x3388, "M", "cal"), + (0x3389, "M", "kcal"), + (0x338A, "M", "pf"), + (0x338B, "M", "nf"), + (0x338C, "M", "μf"), + (0x338D, "M", "μg"), + (0x338E, "M", "mg"), + (0x338F, "M", "kg"), + (0x3390, "M", "hz"), + (0x3391, "M", "khz"), + (0x3392, "M", "mhz"), + (0x3393, "M", "ghz"), + (0x3394, "M", "thz"), + (0x3395, "M", "μl"), + (0x3396, "M", "ml"), + (0x3397, "M", "dl"), + (0x3398, "M", "kl"), + (0x3399, "M", "fm"), + (0x339A, "M", "nm"), + (0x339B, "M", "μm"), + (0x339C, "M", "mm"), + (0x339D, "M", "cm"), + (0x339E, "M", "km"), + (0x339F, "M", "mm2"), + (0x33A0, "M", "cm2"), + (0x33A1, "M", "m2"), + (0x33A2, "M", "km2"), + (0x33A3, "M", "mm3"), + (0x33A4, "M", "cm3"), + (0x33A5, "M", "m3"), + (0x33A6, "M", "km3"), + (0x33A7, "M", "m∕s"), + (0x33A8, "M", "m∕s2"), + (0x33A9, "M", "pa"), + (0x33AA, "M", "kpa"), + (0x33AB, "M", "mpa"), + (0x33AC, "M", "gpa"), + (0x33AD, "M", "rad"), + (0x33AE, "M", "rad∕s"), + (0x33AF, "M", "rad∕s2"), + (0x33B0, "M", "ps"), + (0x33B1, "M", "ns"), + (0x33B2, "M", "μs"), + (0x33B3, "M", "ms"), + (0x33B4, "M", "pv"), + (0x33B5, "M", "nv"), + (0x33B6, "M", "μv"), + (0x33B7, "M", "mv"), + (0x33B8, "M", "kv"), + (0x33B9, "M", "mv"), + (0x33BA, "M", "pw"), + (0x33BB, "M", "nw"), + (0x33BC, "M", "μw"), + (0x33BD, "M", "mw"), + (0x33BE, "M", "kw"), + (0x33BF, "M", "mw"), + (0x33C0, "M", "kω"), + (0x33C1, "M", "mω"), + (0x33C2, "X"), + (0x33C3, "M", "bq"), + (0x33C4, "M", "cc"), + (0x33C5, "M", "cd"), + (0x33C6, "M", "c∕kg"), + (0x33C7, "X"), + (0x33C8, "M", "db"), + (0x33C9, "M", "gy"), + (0x33CA, "M", "ha"), + (0x33CB, "M", "hp"), + (0x33CC, "M", "in"), + (0x33CD, "M", "kk"), + (0x33CE, "M", "km"), + (0x33CF, "M", "kt"), + (0x33D0, "M", "lm"), + (0x33D1, "M", "ln"), + (0x33D2, "M", "log"), + (0x33D3, "M", "lx"), + (0x33D4, "M", "mb"), + (0x33D5, "M", "mil"), + (0x33D6, "M", "mol"), + (0x33D7, "M", "ph"), + (0x33D8, "X"), + (0x33D9, "M", "ppm"), + (0x33DA, "M", "pr"), + (0x33DB, "M", "sr"), + (0x33DC, "M", "sv"), + (0x33DD, "M", "wb"), + (0x33DE, "M", "v∕m"), + (0x33DF, "M", "a∕m"), + (0x33E0, "M", "1日"), + (0x33E1, "M", "2日"), + ] + + +def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x33E2, "M", "3日"), + (0x33E3, "M", "4日"), + (0x33E4, "M", "5日"), + (0x33E5, "M", "6日"), + (0x33E6, "M", "7日"), + (0x33E7, "M", "8日"), + (0x33E8, "M", "9日"), + (0x33E9, "M", "10日"), + (0x33EA, "M", "11日"), + (0x33EB, "M", "12日"), + (0x33EC, "M", "13日"), + (0x33ED, "M", "14日"), + (0x33EE, "M", "15日"), + (0x33EF, "M", "16日"), + (0x33F0, "M", "17日"), + (0x33F1, "M", "18日"), + (0x33F2, "M", "19日"), + (0x33F3, "M", "20日"), + (0x33F4, "M", "21日"), + (0x33F5, "M", "22日"), + (0x33F6, "M", "23日"), + (0x33F7, "M", "24日"), + (0x33F8, "M", "25日"), + (0x33F9, "M", "26日"), + (0x33FA, "M", "27日"), + (0x33FB, "M", "28日"), + (0x33FC, "M", "29日"), + (0x33FD, "M", "30日"), + (0x33FE, "M", "31日"), + (0x33FF, "M", "gal"), + (0x3400, "V"), + (0xA48D, "X"), + (0xA490, "V"), + (0xA4C7, "X"), + (0xA4D0, "V"), + (0xA62C, "X"), + (0xA640, "M", "ꙁ"), + (0xA641, "V"), + (0xA642, "M", "ꙃ"), + (0xA643, "V"), + (0xA644, "M", "ꙅ"), + (0xA645, "V"), + (0xA646, "M", "ꙇ"), + (0xA647, "V"), + (0xA648, "M", "ꙉ"), + (0xA649, "V"), + (0xA64A, "M", "ꙋ"), + (0xA64B, "V"), + (0xA64C, "M", "ꙍ"), + (0xA64D, "V"), + (0xA64E, "M", "ꙏ"), + (0xA64F, "V"), + (0xA650, "M", "ꙑ"), + (0xA651, "V"), + (0xA652, "M", "ꙓ"), + (0xA653, "V"), + (0xA654, "M", "ꙕ"), + (0xA655, "V"), + (0xA656, "M", "ꙗ"), + (0xA657, "V"), + (0xA658, "M", "ꙙ"), + (0xA659, "V"), + (0xA65A, "M", "ꙛ"), + (0xA65B, "V"), + (0xA65C, "M", "ꙝ"), + (0xA65D, "V"), + (0xA65E, "M", "ꙟ"), + (0xA65F, "V"), + (0xA660, "M", "ꙡ"), + (0xA661, "V"), + (0xA662, "M", "ꙣ"), + (0xA663, "V"), + (0xA664, "M", "ꙥ"), + (0xA665, "V"), + (0xA666, "M", "ꙧ"), + (0xA667, "V"), + (0xA668, "M", "ꙩ"), + (0xA669, "V"), + (0xA66A, "M", "ꙫ"), + (0xA66B, "V"), + (0xA66C, "M", "ꙭ"), + (0xA66D, "V"), + (0xA680, "M", "ꚁ"), + (0xA681, "V"), + (0xA682, "M", "ꚃ"), + (0xA683, "V"), + (0xA684, "M", "ꚅ"), + (0xA685, "V"), + (0xA686, "M", "ꚇ"), + (0xA687, "V"), + (0xA688, "M", "ꚉ"), + (0xA689, "V"), + (0xA68A, "M", "ꚋ"), + (0xA68B, "V"), + (0xA68C, "M", "ꚍ"), + (0xA68D, "V"), + (0xA68E, "M", "ꚏ"), + (0xA68F, "V"), + (0xA690, "M", "ꚑ"), + (0xA691, "V"), + ] + + +def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA692, "M", "ꚓ"), + (0xA693, "V"), + (0xA694, "M", "ꚕ"), + (0xA695, "V"), + (0xA696, "M", "ꚗ"), + (0xA697, "V"), + (0xA698, "M", "ꚙ"), + (0xA699, "V"), + (0xA69A, "M", "ꚛ"), + (0xA69B, "V"), + (0xA69C, "M", "ъ"), + (0xA69D, "M", "ь"), + (0xA69E, "V"), + (0xA6F8, "X"), + (0xA700, "V"), + (0xA722, "M", "ꜣ"), + (0xA723, "V"), + (0xA724, "M", "ꜥ"), + (0xA725, "V"), + (0xA726, "M", "ꜧ"), + (0xA727, "V"), + (0xA728, "M", "ꜩ"), + (0xA729, "V"), + (0xA72A, "M", "ꜫ"), + (0xA72B, "V"), + (0xA72C, "M", "ꜭ"), + (0xA72D, "V"), + (0xA72E, "M", "ꜯ"), + (0xA72F, "V"), + (0xA732, "M", "ꜳ"), + (0xA733, "V"), + (0xA734, "M", "ꜵ"), + (0xA735, "V"), + (0xA736, "M", "ꜷ"), + (0xA737, "V"), + (0xA738, "M", "ꜹ"), + (0xA739, "V"), + (0xA73A, "M", "ꜻ"), + (0xA73B, "V"), + (0xA73C, "M", "ꜽ"), + (0xA73D, "V"), + (0xA73E, "M", "ꜿ"), + (0xA73F, "V"), + (0xA740, "M", "ꝁ"), + (0xA741, "V"), + (0xA742, "M", "ꝃ"), + (0xA743, "V"), + (0xA744, "M", "ꝅ"), + (0xA745, "V"), + (0xA746, "M", "ꝇ"), + (0xA747, "V"), + (0xA748, "M", "ꝉ"), + (0xA749, "V"), + (0xA74A, "M", "ꝋ"), + (0xA74B, "V"), + (0xA74C, "M", "ꝍ"), + (0xA74D, "V"), + (0xA74E, "M", "ꝏ"), + (0xA74F, "V"), + (0xA750, "M", "ꝑ"), + (0xA751, "V"), + (0xA752, "M", "ꝓ"), + (0xA753, "V"), + (0xA754, "M", "ꝕ"), + (0xA755, "V"), + (0xA756, "M", "ꝗ"), + (0xA757, "V"), + (0xA758, "M", "ꝙ"), + (0xA759, "V"), + (0xA75A, "M", "ꝛ"), + (0xA75B, "V"), + (0xA75C, "M", "ꝝ"), + (0xA75D, "V"), + (0xA75E, "M", "ꝟ"), + (0xA75F, "V"), + (0xA760, "M", "ꝡ"), + (0xA761, "V"), + (0xA762, "M", "ꝣ"), + (0xA763, "V"), + (0xA764, "M", "ꝥ"), + (0xA765, "V"), + (0xA766, "M", "ꝧ"), + (0xA767, "V"), + (0xA768, "M", "ꝩ"), + (0xA769, "V"), + (0xA76A, "M", "ꝫ"), + (0xA76B, "V"), + (0xA76C, "M", "ꝭ"), + (0xA76D, "V"), + (0xA76E, "M", "ꝯ"), + (0xA76F, "V"), + (0xA770, "M", "ꝯ"), + (0xA771, "V"), + (0xA779, "M", "ꝺ"), + (0xA77A, "V"), + (0xA77B, "M", "ꝼ"), + (0xA77C, "V"), + (0xA77D, "M", "ᵹ"), + (0xA77E, "M", "ꝿ"), + (0xA77F, "V"), + ] + + +def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA780, "M", "ꞁ"), + (0xA781, "V"), + (0xA782, "M", "ꞃ"), + (0xA783, "V"), + (0xA784, "M", "ꞅ"), + (0xA785, "V"), + (0xA786, "M", "ꞇ"), + (0xA787, "V"), + (0xA78B, "M", "ꞌ"), + (0xA78C, "V"), + (0xA78D, "M", "ɥ"), + (0xA78E, "V"), + (0xA790, "M", "ꞑ"), + (0xA791, "V"), + (0xA792, "M", "ꞓ"), + (0xA793, "V"), + (0xA796, "M", "ꞗ"), + (0xA797, "V"), + (0xA798, "M", "ꞙ"), + (0xA799, "V"), + (0xA79A, "M", "ꞛ"), + (0xA79B, "V"), + (0xA79C, "M", "ꞝ"), + (0xA79D, "V"), + (0xA79E, "M", "ꞟ"), + (0xA79F, "V"), + (0xA7A0, "M", "ꞡ"), + (0xA7A1, "V"), + (0xA7A2, "M", "ꞣ"), + (0xA7A3, "V"), + (0xA7A4, "M", "ꞥ"), + (0xA7A5, "V"), + (0xA7A6, "M", "ꞧ"), + (0xA7A7, "V"), + (0xA7A8, "M", "ꞩ"), + (0xA7A9, "V"), + (0xA7AA, "M", "ɦ"), + (0xA7AB, "M", "ɜ"), + (0xA7AC, "M", "ɡ"), + (0xA7AD, "M", "ɬ"), + (0xA7AE, "M", "ɪ"), + (0xA7AF, "V"), + (0xA7B0, "M", "ʞ"), + (0xA7B1, "M", "ʇ"), + (0xA7B2, "M", "ʝ"), + (0xA7B3, "M", "ꭓ"), + (0xA7B4, "M", "ꞵ"), + (0xA7B5, "V"), + (0xA7B6, "M", "ꞷ"), + (0xA7B7, "V"), + (0xA7B8, "M", "ꞹ"), + (0xA7B9, "V"), + (0xA7BA, "M", "ꞻ"), + (0xA7BB, "V"), + (0xA7BC, "M", "ꞽ"), + (0xA7BD, "V"), + (0xA7BE, "M", "ꞿ"), + (0xA7BF, "V"), + (0xA7C0, "M", "ꟁ"), + (0xA7C1, "V"), + (0xA7C2, "M", "ꟃ"), + (0xA7C3, "V"), + (0xA7C4, "M", "ꞔ"), + (0xA7C5, "M", "ʂ"), + (0xA7C6, "M", "ᶎ"), + (0xA7C7, "M", "ꟈ"), + (0xA7C8, "V"), + (0xA7C9, "M", "ꟊ"), + (0xA7CA, "V"), + (0xA7CB, "X"), + (0xA7D0, "M", "ꟑ"), + (0xA7D1, "V"), + (0xA7D2, "X"), + (0xA7D3, "V"), + (0xA7D4, "X"), + (0xA7D5, "V"), + (0xA7D6, "M", "ꟗ"), + (0xA7D7, "V"), + (0xA7D8, "M", "ꟙ"), + (0xA7D9, "V"), + (0xA7DA, "X"), + (0xA7F2, "M", "c"), + (0xA7F3, "M", "f"), + (0xA7F4, "M", "q"), + (0xA7F5, "M", "ꟶ"), + (0xA7F6, "V"), + (0xA7F8, "M", "ħ"), + (0xA7F9, "M", "œ"), + (0xA7FA, "V"), + (0xA82D, "X"), + (0xA830, "V"), + (0xA83A, "X"), + (0xA840, "V"), + (0xA878, "X"), + (0xA880, "V"), + (0xA8C6, "X"), + (0xA8CE, "V"), + (0xA8DA, "X"), + (0xA8E0, "V"), + (0xA954, "X"), + ] + + +def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA95F, "V"), + (0xA97D, "X"), + (0xA980, "V"), + (0xA9CE, "X"), + (0xA9CF, "V"), + (0xA9DA, "X"), + (0xA9DE, "V"), + (0xA9FF, "X"), + (0xAA00, "V"), + (0xAA37, "X"), + (0xAA40, "V"), + (0xAA4E, "X"), + (0xAA50, "V"), + (0xAA5A, "X"), + (0xAA5C, "V"), + (0xAAC3, "X"), + (0xAADB, "V"), + (0xAAF7, "X"), + (0xAB01, "V"), + (0xAB07, "X"), + (0xAB09, "V"), + (0xAB0F, "X"), + (0xAB11, "V"), + (0xAB17, "X"), + (0xAB20, "V"), + (0xAB27, "X"), + (0xAB28, "V"), + (0xAB2F, "X"), + (0xAB30, "V"), + (0xAB5C, "M", "ꜧ"), + (0xAB5D, "M", "ꬷ"), + (0xAB5E, "M", "ɫ"), + (0xAB5F, "M", "ꭒ"), + (0xAB60, "V"), + (0xAB69, "M", "ʍ"), + (0xAB6A, "V"), + (0xAB6C, "X"), + (0xAB70, "M", "Ꭰ"), + (0xAB71, "M", "Ꭱ"), + (0xAB72, "M", "Ꭲ"), + (0xAB73, "M", "Ꭳ"), + (0xAB74, "M", "Ꭴ"), + (0xAB75, "M", "Ꭵ"), + (0xAB76, "M", "Ꭶ"), + (0xAB77, "M", "Ꭷ"), + (0xAB78, "M", "Ꭸ"), + (0xAB79, "M", "Ꭹ"), + (0xAB7A, "M", "Ꭺ"), + (0xAB7B, "M", "Ꭻ"), + (0xAB7C, "M", "Ꭼ"), + (0xAB7D, "M", "Ꭽ"), + (0xAB7E, "M", "Ꭾ"), + (0xAB7F, "M", "Ꭿ"), + (0xAB80, "M", "Ꮀ"), + (0xAB81, "M", "Ꮁ"), + (0xAB82, "M", "Ꮂ"), + (0xAB83, "M", "Ꮃ"), + (0xAB84, "M", "Ꮄ"), + (0xAB85, "M", "Ꮅ"), + (0xAB86, "M", "Ꮆ"), + (0xAB87, "M", "Ꮇ"), + (0xAB88, "M", "Ꮈ"), + (0xAB89, "M", "Ꮉ"), + (0xAB8A, "M", "Ꮊ"), + (0xAB8B, "M", "Ꮋ"), + (0xAB8C, "M", "Ꮌ"), + (0xAB8D, "M", "Ꮍ"), + (0xAB8E, "M", "Ꮎ"), + (0xAB8F, "M", "Ꮏ"), + (0xAB90, "M", "Ꮐ"), + (0xAB91, "M", "Ꮑ"), + (0xAB92, "M", "Ꮒ"), + (0xAB93, "M", "Ꮓ"), + (0xAB94, "M", "Ꮔ"), + (0xAB95, "M", "Ꮕ"), + (0xAB96, "M", "Ꮖ"), + (0xAB97, "M", "Ꮗ"), + (0xAB98, "M", "Ꮘ"), + (0xAB99, "M", "Ꮙ"), + (0xAB9A, "M", "Ꮚ"), + (0xAB9B, "M", "Ꮛ"), + (0xAB9C, "M", "Ꮜ"), + (0xAB9D, "M", "Ꮝ"), + (0xAB9E, "M", "Ꮞ"), + (0xAB9F, "M", "Ꮟ"), + (0xABA0, "M", "Ꮠ"), + (0xABA1, "M", "Ꮡ"), + (0xABA2, "M", "Ꮢ"), + (0xABA3, "M", "Ꮣ"), + (0xABA4, "M", "Ꮤ"), + (0xABA5, "M", "Ꮥ"), + (0xABA6, "M", "Ꮦ"), + (0xABA7, "M", "Ꮧ"), + (0xABA8, "M", "Ꮨ"), + (0xABA9, "M", "Ꮩ"), + (0xABAA, "M", "Ꮪ"), + (0xABAB, "M", "Ꮫ"), + (0xABAC, "M", "Ꮬ"), + (0xABAD, "M", "Ꮭ"), + (0xABAE, "M", "Ꮮ"), + ] + + +def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xABAF, "M", "Ꮯ"), + (0xABB0, "M", "Ꮰ"), + (0xABB1, "M", "Ꮱ"), + (0xABB2, "M", "Ꮲ"), + (0xABB3, "M", "Ꮳ"), + (0xABB4, "M", "Ꮴ"), + (0xABB5, "M", "Ꮵ"), + (0xABB6, "M", "Ꮶ"), + (0xABB7, "M", "Ꮷ"), + (0xABB8, "M", "Ꮸ"), + (0xABB9, "M", "Ꮹ"), + (0xABBA, "M", "Ꮺ"), + (0xABBB, "M", "Ꮻ"), + (0xABBC, "M", "Ꮼ"), + (0xABBD, "M", "Ꮽ"), + (0xABBE, "M", "Ꮾ"), + (0xABBF, "M", "Ꮿ"), + (0xABC0, "V"), + (0xABEE, "X"), + (0xABF0, "V"), + (0xABFA, "X"), + (0xAC00, "V"), + (0xD7A4, "X"), + (0xD7B0, "V"), + (0xD7C7, "X"), + (0xD7CB, "V"), + (0xD7FC, "X"), + (0xF900, "M", "豈"), + (0xF901, "M", "更"), + (0xF902, "M", "車"), + (0xF903, "M", "賈"), + (0xF904, "M", "滑"), + (0xF905, "M", "串"), + (0xF906, "M", "句"), + (0xF907, "M", "龜"), + (0xF909, "M", "契"), + (0xF90A, "M", "金"), + (0xF90B, "M", "喇"), + (0xF90C, "M", "奈"), + (0xF90D, "M", "懶"), + (0xF90E, "M", "癩"), + (0xF90F, "M", "羅"), + (0xF910, "M", "蘿"), + (0xF911, "M", "螺"), + (0xF912, "M", "裸"), + (0xF913, "M", "邏"), + (0xF914, "M", "樂"), + (0xF915, "M", "洛"), + (0xF916, "M", "烙"), + (0xF917, "M", "珞"), + (0xF918, "M", "落"), + (0xF919, "M", "酪"), + (0xF91A, "M", "駱"), + (0xF91B, "M", "亂"), + (0xF91C, "M", "卵"), + (0xF91D, "M", "欄"), + (0xF91E, "M", "爛"), + (0xF91F, "M", "蘭"), + (0xF920, "M", "鸞"), + (0xF921, "M", "嵐"), + (0xF922, "M", "濫"), + (0xF923, "M", "藍"), + (0xF924, "M", "襤"), + (0xF925, "M", "拉"), + (0xF926, "M", "臘"), + (0xF927, "M", "蠟"), + (0xF928, "M", "廊"), + (0xF929, "M", "朗"), + (0xF92A, "M", "浪"), + (0xF92B, "M", "狼"), + (0xF92C, "M", "郎"), + (0xF92D, "M", "來"), + (0xF92E, "M", "冷"), + (0xF92F, "M", "勞"), + (0xF930, "M", "擄"), + (0xF931, "M", "櫓"), + (0xF932, "M", "爐"), + (0xF933, "M", "盧"), + (0xF934, "M", "老"), + (0xF935, "M", "蘆"), + (0xF936, "M", "虜"), + (0xF937, "M", "路"), + (0xF938, "M", "露"), + (0xF939, "M", "魯"), + (0xF93A, "M", "鷺"), + (0xF93B, "M", "碌"), + (0xF93C, "M", "祿"), + (0xF93D, "M", "綠"), + (0xF93E, "M", "菉"), + (0xF93F, "M", "錄"), + (0xF940, "M", "鹿"), + (0xF941, "M", "論"), + (0xF942, "M", "壟"), + (0xF943, "M", "弄"), + (0xF944, "M", "籠"), + (0xF945, "M", "聾"), + (0xF946, "M", "牢"), + (0xF947, "M", "磊"), + (0xF948, "M", "賂"), + (0xF949, "M", "雷"), + ] + + +def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF94A, "M", "壘"), + (0xF94B, "M", "屢"), + (0xF94C, "M", "樓"), + (0xF94D, "M", "淚"), + (0xF94E, "M", "漏"), + (0xF94F, "M", "累"), + (0xF950, "M", "縷"), + (0xF951, "M", "陋"), + (0xF952, "M", "勒"), + (0xF953, "M", "肋"), + (0xF954, "M", "凜"), + (0xF955, "M", "凌"), + (0xF956, "M", "稜"), + (0xF957, "M", "綾"), + (0xF958, "M", "菱"), + (0xF959, "M", "陵"), + (0xF95A, "M", "讀"), + (0xF95B, "M", "拏"), + (0xF95C, "M", "樂"), + (0xF95D, "M", "諾"), + (0xF95E, "M", "丹"), + (0xF95F, "M", "寧"), + (0xF960, "M", "怒"), + (0xF961, "M", "率"), + (0xF962, "M", "異"), + (0xF963, "M", "北"), + (0xF964, "M", "磻"), + (0xF965, "M", "便"), + (0xF966, "M", "復"), + (0xF967, "M", "不"), + (0xF968, "M", "泌"), + (0xF969, "M", "數"), + (0xF96A, "M", "索"), + (0xF96B, "M", "參"), + (0xF96C, "M", "塞"), + (0xF96D, "M", "省"), + (0xF96E, "M", "葉"), + (0xF96F, "M", "說"), + (0xF970, "M", "殺"), + (0xF971, "M", "辰"), + (0xF972, "M", "沈"), + (0xF973, "M", "拾"), + (0xF974, "M", "若"), + (0xF975, "M", "掠"), + (0xF976, "M", "略"), + (0xF977, "M", "亮"), + (0xF978, "M", "兩"), + (0xF979, "M", "凉"), + (0xF97A, "M", "梁"), + (0xF97B, "M", "糧"), + (0xF97C, "M", "良"), + (0xF97D, "M", "諒"), + (0xF97E, "M", "量"), + (0xF97F, "M", "勵"), + (0xF980, "M", "呂"), + (0xF981, "M", "女"), + (0xF982, "M", "廬"), + (0xF983, "M", "旅"), + (0xF984, "M", "濾"), + (0xF985, "M", "礪"), + (0xF986, "M", "閭"), + (0xF987, "M", "驪"), + (0xF988, "M", "麗"), + (0xF989, "M", "黎"), + (0xF98A, "M", "力"), + (0xF98B, "M", "曆"), + (0xF98C, "M", "歷"), + (0xF98D, "M", "轢"), + (0xF98E, "M", "年"), + (0xF98F, "M", "憐"), + (0xF990, "M", "戀"), + (0xF991, "M", "撚"), + (0xF992, "M", "漣"), + (0xF993, "M", "煉"), + (0xF994, "M", "璉"), + (0xF995, "M", "秊"), + (0xF996, "M", "練"), + (0xF997, "M", "聯"), + (0xF998, "M", "輦"), + (0xF999, "M", "蓮"), + (0xF99A, "M", "連"), + (0xF99B, "M", "鍊"), + (0xF99C, "M", "列"), + (0xF99D, "M", "劣"), + (0xF99E, "M", "咽"), + (0xF99F, "M", "烈"), + (0xF9A0, "M", "裂"), + (0xF9A1, "M", "說"), + (0xF9A2, "M", "廉"), + (0xF9A3, "M", "念"), + (0xF9A4, "M", "捻"), + (0xF9A5, "M", "殮"), + (0xF9A6, "M", "簾"), + (0xF9A7, "M", "獵"), + (0xF9A8, "M", "令"), + (0xF9A9, "M", "囹"), + (0xF9AA, "M", "寧"), + (0xF9AB, "M", "嶺"), + (0xF9AC, "M", "怜"), + (0xF9AD, "M", "玲"), + ] + + +def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF9AE, "M", "瑩"), + (0xF9AF, "M", "羚"), + (0xF9B0, "M", "聆"), + (0xF9B1, "M", "鈴"), + (0xF9B2, "M", "零"), + (0xF9B3, "M", "靈"), + (0xF9B4, "M", "領"), + (0xF9B5, "M", "例"), + (0xF9B6, "M", "禮"), + (0xF9B7, "M", "醴"), + (0xF9B8, "M", "隸"), + (0xF9B9, "M", "惡"), + (0xF9BA, "M", "了"), + (0xF9BB, "M", "僚"), + (0xF9BC, "M", "寮"), + (0xF9BD, "M", "尿"), + (0xF9BE, "M", "料"), + (0xF9BF, "M", "樂"), + (0xF9C0, "M", "燎"), + (0xF9C1, "M", "療"), + (0xF9C2, "M", "蓼"), + (0xF9C3, "M", "遼"), + (0xF9C4, "M", "龍"), + (0xF9C5, "M", "暈"), + (0xF9C6, "M", "阮"), + (0xF9C7, "M", "劉"), + (0xF9C8, "M", "杻"), + (0xF9C9, "M", "柳"), + (0xF9CA, "M", "流"), + (0xF9CB, "M", "溜"), + (0xF9CC, "M", "琉"), + (0xF9CD, "M", "留"), + (0xF9CE, "M", "硫"), + (0xF9CF, "M", "紐"), + (0xF9D0, "M", "類"), + (0xF9D1, "M", "六"), + (0xF9D2, "M", "戮"), + (0xF9D3, "M", "陸"), + (0xF9D4, "M", "倫"), + (0xF9D5, "M", "崙"), + (0xF9D6, "M", "淪"), + (0xF9D7, "M", "輪"), + (0xF9D8, "M", "律"), + (0xF9D9, "M", "慄"), + (0xF9DA, "M", "栗"), + (0xF9DB, "M", "率"), + (0xF9DC, "M", "隆"), + (0xF9DD, "M", "利"), + (0xF9DE, "M", "吏"), + (0xF9DF, "M", "履"), + (0xF9E0, "M", "易"), + (0xF9E1, "M", "李"), + (0xF9E2, "M", "梨"), + (0xF9E3, "M", "泥"), + (0xF9E4, "M", "理"), + (0xF9E5, "M", "痢"), + (0xF9E6, "M", "罹"), + (0xF9E7, "M", "裏"), + (0xF9E8, "M", "裡"), + (0xF9E9, "M", "里"), + (0xF9EA, "M", "離"), + (0xF9EB, "M", "匿"), + (0xF9EC, "M", "溺"), + (0xF9ED, "M", "吝"), + (0xF9EE, "M", "燐"), + (0xF9EF, "M", "璘"), + (0xF9F0, "M", "藺"), + (0xF9F1, "M", "隣"), + (0xF9F2, "M", "鱗"), + (0xF9F3, "M", "麟"), + (0xF9F4, "M", "林"), + (0xF9F5, "M", "淋"), + (0xF9F6, "M", "臨"), + (0xF9F7, "M", "立"), + (0xF9F8, "M", "笠"), + (0xF9F9, "M", "粒"), + (0xF9FA, "M", "狀"), + (0xF9FB, "M", "炙"), + (0xF9FC, "M", "識"), + (0xF9FD, "M", "什"), + (0xF9FE, "M", "茶"), + (0xF9FF, "M", "刺"), + (0xFA00, "M", "切"), + (0xFA01, "M", "度"), + (0xFA02, "M", "拓"), + (0xFA03, "M", "糖"), + (0xFA04, "M", "宅"), + (0xFA05, "M", "洞"), + (0xFA06, "M", "暴"), + (0xFA07, "M", "輻"), + (0xFA08, "M", "行"), + (0xFA09, "M", "降"), + (0xFA0A, "M", "見"), + (0xFA0B, "M", "廓"), + (0xFA0C, "M", "兀"), + (0xFA0D, "M", "嗀"), + (0xFA0E, "V"), + (0xFA10, "M", "塚"), + (0xFA11, "V"), + (0xFA12, "M", "晴"), + ] + + +def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA13, "V"), + (0xFA15, "M", "凞"), + (0xFA16, "M", "猪"), + (0xFA17, "M", "益"), + (0xFA18, "M", "礼"), + (0xFA19, "M", "神"), + (0xFA1A, "M", "祥"), + (0xFA1B, "M", "福"), + (0xFA1C, "M", "靖"), + (0xFA1D, "M", "精"), + (0xFA1E, "M", "羽"), + (0xFA1F, "V"), + (0xFA20, "M", "蘒"), + (0xFA21, "V"), + (0xFA22, "M", "諸"), + (0xFA23, "V"), + (0xFA25, "M", "逸"), + (0xFA26, "M", "都"), + (0xFA27, "V"), + (0xFA2A, "M", "飯"), + (0xFA2B, "M", "飼"), + (0xFA2C, "M", "館"), + (0xFA2D, "M", "鶴"), + (0xFA2E, "M", "郞"), + (0xFA2F, "M", "隷"), + (0xFA30, "M", "侮"), + (0xFA31, "M", "僧"), + (0xFA32, "M", "免"), + (0xFA33, "M", "勉"), + (0xFA34, "M", "勤"), + (0xFA35, "M", "卑"), + (0xFA36, "M", "喝"), + (0xFA37, "M", "嘆"), + (0xFA38, "M", "器"), + (0xFA39, "M", "塀"), + (0xFA3A, "M", "墨"), + (0xFA3B, "M", "層"), + (0xFA3C, "M", "屮"), + (0xFA3D, "M", "悔"), + (0xFA3E, "M", "慨"), + (0xFA3F, "M", "憎"), + (0xFA40, "M", "懲"), + (0xFA41, "M", "敏"), + (0xFA42, "M", "既"), + (0xFA43, "M", "暑"), + (0xFA44, "M", "梅"), + (0xFA45, "M", "海"), + (0xFA46, "M", "渚"), + (0xFA47, "M", "漢"), + (0xFA48, "M", "煮"), + (0xFA49, "M", "爫"), + (0xFA4A, "M", "琢"), + (0xFA4B, "M", "碑"), + (0xFA4C, "M", "社"), + (0xFA4D, "M", "祉"), + (0xFA4E, "M", "祈"), + (0xFA4F, "M", "祐"), + (0xFA50, "M", "祖"), + (0xFA51, "M", "祝"), + (0xFA52, "M", "禍"), + (0xFA53, "M", "禎"), + (0xFA54, "M", "穀"), + (0xFA55, "M", "突"), + (0xFA56, "M", "節"), + (0xFA57, "M", "練"), + (0xFA58, "M", "縉"), + (0xFA59, "M", "繁"), + (0xFA5A, "M", "署"), + (0xFA5B, "M", "者"), + (0xFA5C, "M", "臭"), + (0xFA5D, "M", "艹"), + (0xFA5F, "M", "著"), + (0xFA60, "M", "褐"), + (0xFA61, "M", "視"), + (0xFA62, "M", "謁"), + (0xFA63, "M", "謹"), + (0xFA64, "M", "賓"), + (0xFA65, "M", "贈"), + (0xFA66, "M", "辶"), + (0xFA67, "M", "逸"), + (0xFA68, "M", "難"), + (0xFA69, "M", "響"), + (0xFA6A, "M", "頻"), + (0xFA6B, "M", "恵"), + (0xFA6C, "M", "𤋮"), + (0xFA6D, "M", "舘"), + (0xFA6E, "X"), + (0xFA70, "M", "並"), + (0xFA71, "M", "况"), + (0xFA72, "M", "全"), + (0xFA73, "M", "侀"), + (0xFA74, "M", "充"), + (0xFA75, "M", "冀"), + (0xFA76, "M", "勇"), + (0xFA77, "M", "勺"), + (0xFA78, "M", "喝"), + (0xFA79, "M", "啕"), + (0xFA7A, "M", "喙"), + (0xFA7B, "M", "嗢"), + (0xFA7C, "M", "塚"), + ] + + +def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA7D, "M", "墳"), + (0xFA7E, "M", "奄"), + (0xFA7F, "M", "奔"), + (0xFA80, "M", "婢"), + (0xFA81, "M", "嬨"), + (0xFA82, "M", "廒"), + (0xFA83, "M", "廙"), + (0xFA84, "M", "彩"), + (0xFA85, "M", "徭"), + (0xFA86, "M", "惘"), + (0xFA87, "M", "慎"), + (0xFA88, "M", "愈"), + (0xFA89, "M", "憎"), + (0xFA8A, "M", "慠"), + (0xFA8B, "M", "懲"), + (0xFA8C, "M", "戴"), + (0xFA8D, "M", "揄"), + (0xFA8E, "M", "搜"), + (0xFA8F, "M", "摒"), + (0xFA90, "M", "敖"), + (0xFA91, "M", "晴"), + (0xFA92, "M", "朗"), + (0xFA93, "M", "望"), + (0xFA94, "M", "杖"), + (0xFA95, "M", "歹"), + (0xFA96, "M", "殺"), + (0xFA97, "M", "流"), + (0xFA98, "M", "滛"), + (0xFA99, "M", "滋"), + (0xFA9A, "M", "漢"), + (0xFA9B, "M", "瀞"), + (0xFA9C, "M", "煮"), + (0xFA9D, "M", "瞧"), + (0xFA9E, "M", "爵"), + (0xFA9F, "M", "犯"), + (0xFAA0, "M", "猪"), + (0xFAA1, "M", "瑱"), + (0xFAA2, "M", "甆"), + (0xFAA3, "M", "画"), + (0xFAA4, "M", "瘝"), + (0xFAA5, "M", "瘟"), + (0xFAA6, "M", "益"), + (0xFAA7, "M", "盛"), + (0xFAA8, "M", "直"), + (0xFAA9, "M", "睊"), + (0xFAAA, "M", "着"), + (0xFAAB, "M", "磌"), + (0xFAAC, "M", "窱"), + (0xFAAD, "M", "節"), + (0xFAAE, "M", "类"), + (0xFAAF, "M", "絛"), + (0xFAB0, "M", "練"), + (0xFAB1, "M", "缾"), + (0xFAB2, "M", "者"), + (0xFAB3, "M", "荒"), + (0xFAB4, "M", "華"), + (0xFAB5, "M", "蝹"), + (0xFAB6, "M", "襁"), + (0xFAB7, "M", "覆"), + (0xFAB8, "M", "視"), + (0xFAB9, "M", "調"), + (0xFABA, "M", "諸"), + (0xFABB, "M", "請"), + (0xFABC, "M", "謁"), + (0xFABD, "M", "諾"), + (0xFABE, "M", "諭"), + (0xFABF, "M", "謹"), + (0xFAC0, "M", "變"), + (0xFAC1, "M", "贈"), + (0xFAC2, "M", "輸"), + (0xFAC3, "M", "遲"), + (0xFAC4, "M", "醙"), + (0xFAC5, "M", "鉶"), + (0xFAC6, "M", "陼"), + (0xFAC7, "M", "難"), + (0xFAC8, "M", "靖"), + (0xFAC9, "M", "韛"), + (0xFACA, "M", "響"), + (0xFACB, "M", "頋"), + (0xFACC, "M", "頻"), + (0xFACD, "M", "鬒"), + (0xFACE, "M", "龜"), + (0xFACF, "M", "𢡊"), + (0xFAD0, "M", "𢡄"), + (0xFAD1, "M", "𣏕"), + (0xFAD2, "M", "㮝"), + (0xFAD3, "M", "䀘"), + (0xFAD4, "M", "䀹"), + (0xFAD5, "M", "𥉉"), + (0xFAD6, "M", "𥳐"), + (0xFAD7, "M", "𧻓"), + (0xFAD8, "M", "齃"), + (0xFAD9, "M", "龎"), + (0xFADA, "X"), + (0xFB00, "M", "ff"), + (0xFB01, "M", "fi"), + (0xFB02, "M", "fl"), + (0xFB03, "M", "ffi"), + (0xFB04, "M", "ffl"), + (0xFB05, "M", "st"), + ] + + +def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFB07, "X"), + (0xFB13, "M", "մն"), + (0xFB14, "M", "մե"), + (0xFB15, "M", "մի"), + (0xFB16, "M", "վն"), + (0xFB17, "M", "մխ"), + (0xFB18, "X"), + (0xFB1D, "M", "יִ"), + (0xFB1E, "V"), + (0xFB1F, "M", "ײַ"), + (0xFB20, "M", "ע"), + (0xFB21, "M", "א"), + (0xFB22, "M", "ד"), + (0xFB23, "M", "ה"), + (0xFB24, "M", "כ"), + (0xFB25, "M", "ל"), + (0xFB26, "M", "ם"), + (0xFB27, "M", "ר"), + (0xFB28, "M", "ת"), + (0xFB29, "3", "+"), + (0xFB2A, "M", "שׁ"), + (0xFB2B, "M", "שׂ"), + (0xFB2C, "M", "שּׁ"), + (0xFB2D, "M", "שּׂ"), + (0xFB2E, "M", "אַ"), + (0xFB2F, "M", "אָ"), + (0xFB30, "M", "אּ"), + (0xFB31, "M", "בּ"), + (0xFB32, "M", "גּ"), + (0xFB33, "M", "דּ"), + (0xFB34, "M", "הּ"), + (0xFB35, "M", "וּ"), + (0xFB36, "M", "זּ"), + (0xFB37, "X"), + (0xFB38, "M", "טּ"), + (0xFB39, "M", "יּ"), + (0xFB3A, "M", "ךּ"), + (0xFB3B, "M", "כּ"), + (0xFB3C, "M", "לּ"), + (0xFB3D, "X"), + (0xFB3E, "M", "מּ"), + (0xFB3F, "X"), + (0xFB40, "M", "נּ"), + (0xFB41, "M", "סּ"), + (0xFB42, "X"), + (0xFB43, "M", "ףּ"), + (0xFB44, "M", "פּ"), + (0xFB45, "X"), + (0xFB46, "M", "צּ"), + (0xFB47, "M", "קּ"), + (0xFB48, "M", "רּ"), + (0xFB49, "M", "שּ"), + (0xFB4A, "M", "תּ"), + (0xFB4B, "M", "וֹ"), + (0xFB4C, "M", "בֿ"), + (0xFB4D, "M", "כֿ"), + (0xFB4E, "M", "פֿ"), + (0xFB4F, "M", "אל"), + (0xFB50, "M", "ٱ"), + (0xFB52, "M", "ٻ"), + (0xFB56, "M", "پ"), + (0xFB5A, "M", "ڀ"), + (0xFB5E, "M", "ٺ"), + (0xFB62, "M", "ٿ"), + (0xFB66, "M", "ٹ"), + (0xFB6A, "M", "ڤ"), + (0xFB6E, "M", "ڦ"), + (0xFB72, "M", "ڄ"), + (0xFB76, "M", "ڃ"), + (0xFB7A, "M", "چ"), + (0xFB7E, "M", "ڇ"), + (0xFB82, "M", "ڍ"), + (0xFB84, "M", "ڌ"), + (0xFB86, "M", "ڎ"), + (0xFB88, "M", "ڈ"), + (0xFB8A, "M", "ژ"), + (0xFB8C, "M", "ڑ"), + (0xFB8E, "M", "ک"), + (0xFB92, "M", "گ"), + (0xFB96, "M", "ڳ"), + (0xFB9A, "M", "ڱ"), + (0xFB9E, "M", "ں"), + (0xFBA0, "M", "ڻ"), + (0xFBA4, "M", "ۀ"), + (0xFBA6, "M", "ہ"), + (0xFBAA, "M", "ھ"), + (0xFBAE, "M", "ے"), + (0xFBB0, "M", "ۓ"), + (0xFBB2, "V"), + (0xFBC3, "X"), + (0xFBD3, "M", "ڭ"), + (0xFBD7, "M", "ۇ"), + (0xFBD9, "M", "ۆ"), + (0xFBDB, "M", "ۈ"), + (0xFBDD, "M", "ۇٴ"), + (0xFBDE, "M", "ۋ"), + (0xFBE0, "M", "ۅ"), + (0xFBE2, "M", "ۉ"), + (0xFBE4, "M", "ې"), + (0xFBE8, "M", "ى"), + ] + + +def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFBEA, "M", "ئا"), + (0xFBEC, "M", "ئە"), + (0xFBEE, "M", "ئو"), + (0xFBF0, "M", "ئۇ"), + (0xFBF2, "M", "ئۆ"), + (0xFBF4, "M", "ئۈ"), + (0xFBF6, "M", "ئې"), + (0xFBF9, "M", "ئى"), + (0xFBFC, "M", "ی"), + (0xFC00, "M", "ئج"), + (0xFC01, "M", "ئح"), + (0xFC02, "M", "ئم"), + (0xFC03, "M", "ئى"), + (0xFC04, "M", "ئي"), + (0xFC05, "M", "بج"), + (0xFC06, "M", "بح"), + (0xFC07, "M", "بخ"), + (0xFC08, "M", "بم"), + (0xFC09, "M", "بى"), + (0xFC0A, "M", "بي"), + (0xFC0B, "M", "تج"), + (0xFC0C, "M", "تح"), + (0xFC0D, "M", "تخ"), + (0xFC0E, "M", "تم"), + (0xFC0F, "M", "تى"), + (0xFC10, "M", "تي"), + (0xFC11, "M", "ثج"), + (0xFC12, "M", "ثم"), + (0xFC13, "M", "ثى"), + (0xFC14, "M", "ثي"), + (0xFC15, "M", "جح"), + (0xFC16, "M", "جم"), + (0xFC17, "M", "حج"), + (0xFC18, "M", "حم"), + (0xFC19, "M", "خج"), + (0xFC1A, "M", "خح"), + (0xFC1B, "M", "خم"), + (0xFC1C, "M", "سج"), + (0xFC1D, "M", "سح"), + (0xFC1E, "M", "سخ"), + (0xFC1F, "M", "سم"), + (0xFC20, "M", "صح"), + (0xFC21, "M", "صم"), + (0xFC22, "M", "ضج"), + (0xFC23, "M", "ضح"), + (0xFC24, "M", "ضخ"), + (0xFC25, "M", "ضم"), + (0xFC26, "M", "طح"), + (0xFC27, "M", "طم"), + (0xFC28, "M", "ظم"), + (0xFC29, "M", "عج"), + (0xFC2A, "M", "عم"), + (0xFC2B, "M", "غج"), + (0xFC2C, "M", "غم"), + (0xFC2D, "M", "فج"), + (0xFC2E, "M", "فح"), + (0xFC2F, "M", "فخ"), + (0xFC30, "M", "فم"), + (0xFC31, "M", "فى"), + (0xFC32, "M", "في"), + (0xFC33, "M", "قح"), + (0xFC34, "M", "قم"), + (0xFC35, "M", "قى"), + (0xFC36, "M", "قي"), + (0xFC37, "M", "كا"), + (0xFC38, "M", "كج"), + (0xFC39, "M", "كح"), + (0xFC3A, "M", "كخ"), + (0xFC3B, "M", "كل"), + (0xFC3C, "M", "كم"), + (0xFC3D, "M", "كى"), + (0xFC3E, "M", "كي"), + (0xFC3F, "M", "لج"), + (0xFC40, "M", "لح"), + (0xFC41, "M", "لخ"), + (0xFC42, "M", "لم"), + (0xFC43, "M", "لى"), + (0xFC44, "M", "لي"), + (0xFC45, "M", "مج"), + (0xFC46, "M", "مح"), + (0xFC47, "M", "مخ"), + (0xFC48, "M", "مم"), + (0xFC49, "M", "مى"), + (0xFC4A, "M", "مي"), + (0xFC4B, "M", "نج"), + (0xFC4C, "M", "نح"), + (0xFC4D, "M", "نخ"), + (0xFC4E, "M", "نم"), + (0xFC4F, "M", "نى"), + (0xFC50, "M", "ني"), + (0xFC51, "M", "هج"), + (0xFC52, "M", "هم"), + (0xFC53, "M", "هى"), + (0xFC54, "M", "هي"), + (0xFC55, "M", "يج"), + (0xFC56, "M", "يح"), + (0xFC57, "M", "يخ"), + (0xFC58, "M", "يم"), + (0xFC59, "M", "يى"), + (0xFC5A, "M", "يي"), + ] + + +def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFC5B, "M", "ذٰ"), + (0xFC5C, "M", "رٰ"), + (0xFC5D, "M", "ىٰ"), + (0xFC5E, "3", " ٌّ"), + (0xFC5F, "3", " ٍّ"), + (0xFC60, "3", " َّ"), + (0xFC61, "3", " ُّ"), + (0xFC62, "3", " ِّ"), + (0xFC63, "3", " ّٰ"), + (0xFC64, "M", "ئر"), + (0xFC65, "M", "ئز"), + (0xFC66, "M", "ئم"), + (0xFC67, "M", "ئن"), + (0xFC68, "M", "ئى"), + (0xFC69, "M", "ئي"), + (0xFC6A, "M", "بر"), + (0xFC6B, "M", "بز"), + (0xFC6C, "M", "بم"), + (0xFC6D, "M", "بن"), + (0xFC6E, "M", "بى"), + (0xFC6F, "M", "بي"), + (0xFC70, "M", "تر"), + (0xFC71, "M", "تز"), + (0xFC72, "M", "تم"), + (0xFC73, "M", "تن"), + (0xFC74, "M", "تى"), + (0xFC75, "M", "تي"), + (0xFC76, "M", "ثر"), + (0xFC77, "M", "ثز"), + (0xFC78, "M", "ثم"), + (0xFC79, "M", "ثن"), + (0xFC7A, "M", "ثى"), + (0xFC7B, "M", "ثي"), + (0xFC7C, "M", "فى"), + (0xFC7D, "M", "في"), + (0xFC7E, "M", "قى"), + (0xFC7F, "M", "قي"), + (0xFC80, "M", "كا"), + (0xFC81, "M", "كل"), + (0xFC82, "M", "كم"), + (0xFC83, "M", "كى"), + (0xFC84, "M", "كي"), + (0xFC85, "M", "لم"), + (0xFC86, "M", "لى"), + (0xFC87, "M", "لي"), + (0xFC88, "M", "ما"), + (0xFC89, "M", "مم"), + (0xFC8A, "M", "نر"), + (0xFC8B, "M", "نز"), + (0xFC8C, "M", "نم"), + (0xFC8D, "M", "نن"), + (0xFC8E, "M", "نى"), + (0xFC8F, "M", "ني"), + (0xFC90, "M", "ىٰ"), + (0xFC91, "M", "ير"), + (0xFC92, "M", "يز"), + (0xFC93, "M", "يم"), + (0xFC94, "M", "ين"), + (0xFC95, "M", "يى"), + (0xFC96, "M", "يي"), + (0xFC97, "M", "ئج"), + (0xFC98, "M", "ئح"), + (0xFC99, "M", "ئخ"), + (0xFC9A, "M", "ئم"), + (0xFC9B, "M", "ئه"), + (0xFC9C, "M", "بج"), + (0xFC9D, "M", "بح"), + (0xFC9E, "M", "بخ"), + (0xFC9F, "M", "بم"), + (0xFCA0, "M", "به"), + (0xFCA1, "M", "تج"), + (0xFCA2, "M", "تح"), + (0xFCA3, "M", "تخ"), + (0xFCA4, "M", "تم"), + (0xFCA5, "M", "ته"), + (0xFCA6, "M", "ثم"), + (0xFCA7, "M", "جح"), + (0xFCA8, "M", "جم"), + (0xFCA9, "M", "حج"), + (0xFCAA, "M", "حم"), + (0xFCAB, "M", "خج"), + (0xFCAC, "M", "خم"), + (0xFCAD, "M", "سج"), + (0xFCAE, "M", "سح"), + (0xFCAF, "M", "سخ"), + (0xFCB0, "M", "سم"), + (0xFCB1, "M", "صح"), + (0xFCB2, "M", "صخ"), + (0xFCB3, "M", "صم"), + (0xFCB4, "M", "ضج"), + (0xFCB5, "M", "ضح"), + (0xFCB6, "M", "ضخ"), + (0xFCB7, "M", "ضم"), + (0xFCB8, "M", "طح"), + (0xFCB9, "M", "ظم"), + (0xFCBA, "M", "عج"), + (0xFCBB, "M", "عم"), + (0xFCBC, "M", "غج"), + (0xFCBD, "M", "غم"), + (0xFCBE, "M", "فج"), + ] + + +def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFCBF, "M", "فح"), + (0xFCC0, "M", "فخ"), + (0xFCC1, "M", "فم"), + (0xFCC2, "M", "قح"), + (0xFCC3, "M", "قم"), + (0xFCC4, "M", "كج"), + (0xFCC5, "M", "كح"), + (0xFCC6, "M", "كخ"), + (0xFCC7, "M", "كل"), + (0xFCC8, "M", "كم"), + (0xFCC9, "M", "لج"), + (0xFCCA, "M", "لح"), + (0xFCCB, "M", "لخ"), + (0xFCCC, "M", "لم"), + (0xFCCD, "M", "له"), + (0xFCCE, "M", "مج"), + (0xFCCF, "M", "مح"), + (0xFCD0, "M", "مخ"), + (0xFCD1, "M", "مم"), + (0xFCD2, "M", "نج"), + (0xFCD3, "M", "نح"), + (0xFCD4, "M", "نخ"), + (0xFCD5, "M", "نم"), + (0xFCD6, "M", "نه"), + (0xFCD7, "M", "هج"), + (0xFCD8, "M", "هم"), + (0xFCD9, "M", "هٰ"), + (0xFCDA, "M", "يج"), + (0xFCDB, "M", "يح"), + (0xFCDC, "M", "يخ"), + (0xFCDD, "M", "يم"), + (0xFCDE, "M", "يه"), + (0xFCDF, "M", "ئم"), + (0xFCE0, "M", "ئه"), + (0xFCE1, "M", "بم"), + (0xFCE2, "M", "به"), + (0xFCE3, "M", "تم"), + (0xFCE4, "M", "ته"), + (0xFCE5, "M", "ثم"), + (0xFCE6, "M", "ثه"), + (0xFCE7, "M", "سم"), + (0xFCE8, "M", "سه"), + (0xFCE9, "M", "شم"), + (0xFCEA, "M", "شه"), + (0xFCEB, "M", "كل"), + (0xFCEC, "M", "كم"), + (0xFCED, "M", "لم"), + (0xFCEE, "M", "نم"), + (0xFCEF, "M", "نه"), + (0xFCF0, "M", "يم"), + (0xFCF1, "M", "يه"), + (0xFCF2, "M", "ـَّ"), + (0xFCF3, "M", "ـُّ"), + (0xFCF4, "M", "ـِّ"), + (0xFCF5, "M", "طى"), + (0xFCF6, "M", "طي"), + (0xFCF7, "M", "عى"), + (0xFCF8, "M", "عي"), + (0xFCF9, "M", "غى"), + (0xFCFA, "M", "غي"), + (0xFCFB, "M", "سى"), + (0xFCFC, "M", "سي"), + (0xFCFD, "M", "شى"), + (0xFCFE, "M", "شي"), + (0xFCFF, "M", "حى"), + (0xFD00, "M", "حي"), + (0xFD01, "M", "جى"), + (0xFD02, "M", "جي"), + (0xFD03, "M", "خى"), + (0xFD04, "M", "خي"), + (0xFD05, "M", "صى"), + (0xFD06, "M", "صي"), + (0xFD07, "M", "ضى"), + (0xFD08, "M", "ضي"), + (0xFD09, "M", "شج"), + (0xFD0A, "M", "شح"), + (0xFD0B, "M", "شخ"), + (0xFD0C, "M", "شم"), + (0xFD0D, "M", "شر"), + (0xFD0E, "M", "سر"), + (0xFD0F, "M", "صر"), + (0xFD10, "M", "ضر"), + (0xFD11, "M", "طى"), + (0xFD12, "M", "طي"), + (0xFD13, "M", "عى"), + (0xFD14, "M", "عي"), + (0xFD15, "M", "غى"), + (0xFD16, "M", "غي"), + (0xFD17, "M", "سى"), + (0xFD18, "M", "سي"), + (0xFD19, "M", "شى"), + (0xFD1A, "M", "شي"), + (0xFD1B, "M", "حى"), + (0xFD1C, "M", "حي"), + (0xFD1D, "M", "جى"), + (0xFD1E, "M", "جي"), + (0xFD1F, "M", "خى"), + (0xFD20, "M", "خي"), + (0xFD21, "M", "صى"), + (0xFD22, "M", "صي"), + ] + + +def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFD23, "M", "ضى"), + (0xFD24, "M", "ضي"), + (0xFD25, "M", "شج"), + (0xFD26, "M", "شح"), + (0xFD27, "M", "شخ"), + (0xFD28, "M", "شم"), + (0xFD29, "M", "شر"), + (0xFD2A, "M", "سر"), + (0xFD2B, "M", "صر"), + (0xFD2C, "M", "ضر"), + (0xFD2D, "M", "شج"), + (0xFD2E, "M", "شح"), + (0xFD2F, "M", "شخ"), + (0xFD30, "M", "شم"), + (0xFD31, "M", "سه"), + (0xFD32, "M", "شه"), + (0xFD33, "M", "طم"), + (0xFD34, "M", "سج"), + (0xFD35, "M", "سح"), + (0xFD36, "M", "سخ"), + (0xFD37, "M", "شج"), + (0xFD38, "M", "شح"), + (0xFD39, "M", "شخ"), + (0xFD3A, "M", "طم"), + (0xFD3B, "M", "ظم"), + (0xFD3C, "M", "اً"), + (0xFD3E, "V"), + (0xFD50, "M", "تجم"), + (0xFD51, "M", "تحج"), + (0xFD53, "M", "تحم"), + (0xFD54, "M", "تخم"), + (0xFD55, "M", "تمج"), + (0xFD56, "M", "تمح"), + (0xFD57, "M", "تمخ"), + (0xFD58, "M", "جمح"), + (0xFD5A, "M", "حمي"), + (0xFD5B, "M", "حمى"), + (0xFD5C, "M", "سحج"), + (0xFD5D, "M", "سجح"), + (0xFD5E, "M", "سجى"), + (0xFD5F, "M", "سمح"), + (0xFD61, "M", "سمج"), + (0xFD62, "M", "سمم"), + (0xFD64, "M", "صحح"), + (0xFD66, "M", "صمم"), + (0xFD67, "M", "شحم"), + (0xFD69, "M", "شجي"), + (0xFD6A, "M", "شمخ"), + (0xFD6C, "M", "شمم"), + (0xFD6E, "M", "ضحى"), + (0xFD6F, "M", "ضخم"), + (0xFD71, "M", "طمح"), + (0xFD73, "M", "طمم"), + (0xFD74, "M", "طمي"), + (0xFD75, "M", "عجم"), + (0xFD76, "M", "عمم"), + (0xFD78, "M", "عمى"), + (0xFD79, "M", "غمم"), + (0xFD7A, "M", "غمي"), + (0xFD7B, "M", "غمى"), + (0xFD7C, "M", "فخم"), + (0xFD7E, "M", "قمح"), + (0xFD7F, "M", "قمم"), + (0xFD80, "M", "لحم"), + (0xFD81, "M", "لحي"), + (0xFD82, "M", "لحى"), + (0xFD83, "M", "لجج"), + (0xFD85, "M", "لخم"), + (0xFD87, "M", "لمح"), + (0xFD89, "M", "محج"), + (0xFD8A, "M", "محم"), + (0xFD8B, "M", "محي"), + (0xFD8C, "M", "مجح"), + (0xFD8D, "M", "مجم"), + (0xFD8E, "M", "مخج"), + (0xFD8F, "M", "مخم"), + (0xFD90, "X"), + (0xFD92, "M", "مجخ"), + (0xFD93, "M", "همج"), + (0xFD94, "M", "همم"), + (0xFD95, "M", "نحم"), + (0xFD96, "M", "نحى"), + (0xFD97, "M", "نجم"), + (0xFD99, "M", "نجى"), + (0xFD9A, "M", "نمي"), + (0xFD9B, "M", "نمى"), + (0xFD9C, "M", "يمم"), + (0xFD9E, "M", "بخي"), + (0xFD9F, "M", "تجي"), + (0xFDA0, "M", "تجى"), + (0xFDA1, "M", "تخي"), + (0xFDA2, "M", "تخى"), + (0xFDA3, "M", "تمي"), + (0xFDA4, "M", "تمى"), + (0xFDA5, "M", "جمي"), + (0xFDA6, "M", "جحى"), + (0xFDA7, "M", "جمى"), + (0xFDA8, "M", "سخى"), + (0xFDA9, "M", "صحي"), + (0xFDAA, "M", "شحي"), + ] + + +def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFDAB, "M", "ضحي"), + (0xFDAC, "M", "لجي"), + (0xFDAD, "M", "لمي"), + (0xFDAE, "M", "يحي"), + (0xFDAF, "M", "يجي"), + (0xFDB0, "M", "يمي"), + (0xFDB1, "M", "ممي"), + (0xFDB2, "M", "قمي"), + (0xFDB3, "M", "نحي"), + (0xFDB4, "M", "قمح"), + (0xFDB5, "M", "لحم"), + (0xFDB6, "M", "عمي"), + (0xFDB7, "M", "كمي"), + (0xFDB8, "M", "نجح"), + (0xFDB9, "M", "مخي"), + (0xFDBA, "M", "لجم"), + (0xFDBB, "M", "كمم"), + (0xFDBC, "M", "لجم"), + (0xFDBD, "M", "نجح"), + (0xFDBE, "M", "جحي"), + (0xFDBF, "M", "حجي"), + (0xFDC0, "M", "مجي"), + (0xFDC1, "M", "فمي"), + (0xFDC2, "M", "بحي"), + (0xFDC3, "M", "كمم"), + (0xFDC4, "M", "عجم"), + (0xFDC5, "M", "صمم"), + (0xFDC6, "M", "سخي"), + (0xFDC7, "M", "نجي"), + (0xFDC8, "X"), + (0xFDCF, "V"), + (0xFDD0, "X"), + (0xFDF0, "M", "صلے"), + (0xFDF1, "M", "قلے"), + (0xFDF2, "M", "الله"), + (0xFDF3, "M", "اكبر"), + (0xFDF4, "M", "محمد"), + (0xFDF5, "M", "صلعم"), + (0xFDF6, "M", "رسول"), + (0xFDF7, "M", "عليه"), + (0xFDF8, "M", "وسلم"), + (0xFDF9, "M", "صلى"), + (0xFDFA, "3", "صلى الله عليه وسلم"), + (0xFDFB, "3", "جل جلاله"), + (0xFDFC, "M", "ریال"), + (0xFDFD, "V"), + (0xFE00, "I"), + (0xFE10, "3", ","), + (0xFE11, "M", "、"), + (0xFE12, "X"), + (0xFE13, "3", ":"), + (0xFE14, "3", ";"), + (0xFE15, "3", "!"), + (0xFE16, "3", "?"), + (0xFE17, "M", "〖"), + (0xFE18, "M", "〗"), + (0xFE19, "X"), + (0xFE20, "V"), + (0xFE30, "X"), + (0xFE31, "M", "—"), + (0xFE32, "M", "–"), + (0xFE33, "3", "_"), + (0xFE35, "3", "("), + (0xFE36, "3", ")"), + (0xFE37, "3", "{"), + (0xFE38, "3", "}"), + (0xFE39, "M", "〔"), + (0xFE3A, "M", "〕"), + (0xFE3B, "M", "【"), + (0xFE3C, "M", "】"), + (0xFE3D, "M", "《"), + (0xFE3E, "M", "》"), + (0xFE3F, "M", "〈"), + (0xFE40, "M", "〉"), + (0xFE41, "M", "「"), + (0xFE42, "M", "」"), + (0xFE43, "M", "『"), + (0xFE44, "M", "』"), + (0xFE45, "V"), + (0xFE47, "3", "["), + (0xFE48, "3", "]"), + (0xFE49, "3", " ̅"), + (0xFE4D, "3", "_"), + (0xFE50, "3", ","), + (0xFE51, "M", "、"), + (0xFE52, "X"), + (0xFE54, "3", ";"), + (0xFE55, "3", ":"), + (0xFE56, "3", "?"), + (0xFE57, "3", "!"), + (0xFE58, "M", "—"), + (0xFE59, "3", "("), + (0xFE5A, "3", ")"), + (0xFE5B, "3", "{"), + (0xFE5C, "3", "}"), + (0xFE5D, "M", "〔"), + (0xFE5E, "M", "〕"), + (0xFE5F, "3", "#"), + (0xFE60, "3", "&"), + (0xFE61, "3", "*"), + ] + + +def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFE62, "3", "+"), + (0xFE63, "M", "-"), + (0xFE64, "3", "<"), + (0xFE65, "3", ">"), + (0xFE66, "3", "="), + (0xFE67, "X"), + (0xFE68, "3", "\\"), + (0xFE69, "3", "$"), + (0xFE6A, "3", "%"), + (0xFE6B, "3", "@"), + (0xFE6C, "X"), + (0xFE70, "3", " ً"), + (0xFE71, "M", "ـً"), + (0xFE72, "3", " ٌ"), + (0xFE73, "V"), + (0xFE74, "3", " ٍ"), + (0xFE75, "X"), + (0xFE76, "3", " َ"), + (0xFE77, "M", "ـَ"), + (0xFE78, "3", " ُ"), + (0xFE79, "M", "ـُ"), + (0xFE7A, "3", " ِ"), + (0xFE7B, "M", "ـِ"), + (0xFE7C, "3", " ّ"), + (0xFE7D, "M", "ـّ"), + (0xFE7E, "3", " ْ"), + (0xFE7F, "M", "ـْ"), + (0xFE80, "M", "ء"), + (0xFE81, "M", "آ"), + (0xFE83, "M", "أ"), + (0xFE85, "M", "ؤ"), + (0xFE87, "M", "إ"), + (0xFE89, "M", "ئ"), + (0xFE8D, "M", "ا"), + (0xFE8F, "M", "ب"), + (0xFE93, "M", "ة"), + (0xFE95, "M", "ت"), + (0xFE99, "M", "ث"), + (0xFE9D, "M", "ج"), + (0xFEA1, "M", "ح"), + (0xFEA5, "M", "خ"), + (0xFEA9, "M", "د"), + (0xFEAB, "M", "ذ"), + (0xFEAD, "M", "ر"), + (0xFEAF, "M", "ز"), + (0xFEB1, "M", "س"), + (0xFEB5, "M", "ش"), + (0xFEB9, "M", "ص"), + (0xFEBD, "M", "ض"), + (0xFEC1, "M", "ط"), + (0xFEC5, "M", "ظ"), + (0xFEC9, "M", "ع"), + (0xFECD, "M", "غ"), + (0xFED1, "M", "ف"), + (0xFED5, "M", "ق"), + (0xFED9, "M", "ك"), + (0xFEDD, "M", "ل"), + (0xFEE1, "M", "م"), + (0xFEE5, "M", "ن"), + (0xFEE9, "M", "ه"), + (0xFEED, "M", "و"), + (0xFEEF, "M", "ى"), + (0xFEF1, "M", "ي"), + (0xFEF5, "M", "لآ"), + (0xFEF7, "M", "لأ"), + (0xFEF9, "M", "لإ"), + (0xFEFB, "M", "لا"), + (0xFEFD, "X"), + (0xFEFF, "I"), + (0xFF00, "X"), + (0xFF01, "3", "!"), + (0xFF02, "3", '"'), + (0xFF03, "3", "#"), + (0xFF04, "3", "$"), + (0xFF05, "3", "%"), + (0xFF06, "3", "&"), + (0xFF07, "3", "'"), + (0xFF08, "3", "("), + (0xFF09, "3", ")"), + (0xFF0A, "3", "*"), + (0xFF0B, "3", "+"), + (0xFF0C, "3", ","), + (0xFF0D, "M", "-"), + (0xFF0E, "M", "."), + (0xFF0F, "3", "/"), + (0xFF10, "M", "0"), + (0xFF11, "M", "1"), + (0xFF12, "M", "2"), + (0xFF13, "M", "3"), + (0xFF14, "M", "4"), + (0xFF15, "M", "5"), + (0xFF16, "M", "6"), + (0xFF17, "M", "7"), + (0xFF18, "M", "8"), + (0xFF19, "M", "9"), + (0xFF1A, "3", ":"), + (0xFF1B, "3", ";"), + (0xFF1C, "3", "<"), + (0xFF1D, "3", "="), + (0xFF1E, "3", ">"), + ] + + +def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF1F, "3", "?"), + (0xFF20, "3", "@"), + (0xFF21, "M", "a"), + (0xFF22, "M", "b"), + (0xFF23, "M", "c"), + (0xFF24, "M", "d"), + (0xFF25, "M", "e"), + (0xFF26, "M", "f"), + (0xFF27, "M", "g"), + (0xFF28, "M", "h"), + (0xFF29, "M", "i"), + (0xFF2A, "M", "j"), + (0xFF2B, "M", "k"), + (0xFF2C, "M", "l"), + (0xFF2D, "M", "m"), + (0xFF2E, "M", "n"), + (0xFF2F, "M", "o"), + (0xFF30, "M", "p"), + (0xFF31, "M", "q"), + (0xFF32, "M", "r"), + (0xFF33, "M", "s"), + (0xFF34, "M", "t"), + (0xFF35, "M", "u"), + (0xFF36, "M", "v"), + (0xFF37, "M", "w"), + (0xFF38, "M", "x"), + (0xFF39, "M", "y"), + (0xFF3A, "M", "z"), + (0xFF3B, "3", "["), + (0xFF3C, "3", "\\"), + (0xFF3D, "3", "]"), + (0xFF3E, "3", "^"), + (0xFF3F, "3", "_"), + (0xFF40, "3", "`"), + (0xFF41, "M", "a"), + (0xFF42, "M", "b"), + (0xFF43, "M", "c"), + (0xFF44, "M", "d"), + (0xFF45, "M", "e"), + (0xFF46, "M", "f"), + (0xFF47, "M", "g"), + (0xFF48, "M", "h"), + (0xFF49, "M", "i"), + (0xFF4A, "M", "j"), + (0xFF4B, "M", "k"), + (0xFF4C, "M", "l"), + (0xFF4D, "M", "m"), + (0xFF4E, "M", "n"), + (0xFF4F, "M", "o"), + (0xFF50, "M", "p"), + (0xFF51, "M", "q"), + (0xFF52, "M", "r"), + (0xFF53, "M", "s"), + (0xFF54, "M", "t"), + (0xFF55, "M", "u"), + (0xFF56, "M", "v"), + (0xFF57, "M", "w"), + (0xFF58, "M", "x"), + (0xFF59, "M", "y"), + (0xFF5A, "M", "z"), + (0xFF5B, "3", "{"), + (0xFF5C, "3", "|"), + (0xFF5D, "3", "}"), + (0xFF5E, "3", "~"), + (0xFF5F, "M", "⦅"), + (0xFF60, "M", "⦆"), + (0xFF61, "M", "."), + (0xFF62, "M", "「"), + (0xFF63, "M", "」"), + (0xFF64, "M", "、"), + (0xFF65, "M", "・"), + (0xFF66, "M", "ヲ"), + (0xFF67, "M", "ァ"), + (0xFF68, "M", "ィ"), + (0xFF69, "M", "ゥ"), + (0xFF6A, "M", "ェ"), + (0xFF6B, "M", "ォ"), + (0xFF6C, "M", "ャ"), + (0xFF6D, "M", "ュ"), + (0xFF6E, "M", "ョ"), + (0xFF6F, "M", "ッ"), + (0xFF70, "M", "ー"), + (0xFF71, "M", "ア"), + (0xFF72, "M", "イ"), + (0xFF73, "M", "ウ"), + (0xFF74, "M", "エ"), + (0xFF75, "M", "オ"), + (0xFF76, "M", "カ"), + (0xFF77, "M", "キ"), + (0xFF78, "M", "ク"), + (0xFF79, "M", "ケ"), + (0xFF7A, "M", "コ"), + (0xFF7B, "M", "サ"), + (0xFF7C, "M", "シ"), + (0xFF7D, "M", "ス"), + (0xFF7E, "M", "セ"), + (0xFF7F, "M", "ソ"), + (0xFF80, "M", "タ"), + (0xFF81, "M", "チ"), + (0xFF82, "M", "ツ"), + ] + + +def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF83, "M", "テ"), + (0xFF84, "M", "ト"), + (0xFF85, "M", "ナ"), + (0xFF86, "M", "ニ"), + (0xFF87, "M", "ヌ"), + (0xFF88, "M", "ネ"), + (0xFF89, "M", "ノ"), + (0xFF8A, "M", "ハ"), + (0xFF8B, "M", "ヒ"), + (0xFF8C, "M", "フ"), + (0xFF8D, "M", "ヘ"), + (0xFF8E, "M", "ホ"), + (0xFF8F, "M", "マ"), + (0xFF90, "M", "ミ"), + (0xFF91, "M", "ム"), + (0xFF92, "M", "メ"), + (0xFF93, "M", "モ"), + (0xFF94, "M", "ヤ"), + (0xFF95, "M", "ユ"), + (0xFF96, "M", "ヨ"), + (0xFF97, "M", "ラ"), + (0xFF98, "M", "リ"), + (0xFF99, "M", "ル"), + (0xFF9A, "M", "レ"), + (0xFF9B, "M", "ロ"), + (0xFF9C, "M", "ワ"), + (0xFF9D, "M", "ン"), + (0xFF9E, "M", "゙"), + (0xFF9F, "M", "゚"), + (0xFFA0, "X"), + (0xFFA1, "M", "ᄀ"), + (0xFFA2, "M", "ᄁ"), + (0xFFA3, "M", "ᆪ"), + (0xFFA4, "M", "ᄂ"), + (0xFFA5, "M", "ᆬ"), + (0xFFA6, "M", "ᆭ"), + (0xFFA7, "M", "ᄃ"), + (0xFFA8, "M", "ᄄ"), + (0xFFA9, "M", "ᄅ"), + (0xFFAA, "M", "ᆰ"), + (0xFFAB, "M", "ᆱ"), + (0xFFAC, "M", "ᆲ"), + (0xFFAD, "M", "ᆳ"), + (0xFFAE, "M", "ᆴ"), + (0xFFAF, "M", "ᆵ"), + (0xFFB0, "M", "ᄚ"), + (0xFFB1, "M", "ᄆ"), + (0xFFB2, "M", "ᄇ"), + (0xFFB3, "M", "ᄈ"), + (0xFFB4, "M", "ᄡ"), + (0xFFB5, "M", "ᄉ"), + (0xFFB6, "M", "ᄊ"), + (0xFFB7, "M", "ᄋ"), + (0xFFB8, "M", "ᄌ"), + (0xFFB9, "M", "ᄍ"), + (0xFFBA, "M", "ᄎ"), + (0xFFBB, "M", "ᄏ"), + (0xFFBC, "M", "ᄐ"), + (0xFFBD, "M", "ᄑ"), + (0xFFBE, "M", "ᄒ"), + (0xFFBF, "X"), + (0xFFC2, "M", "ᅡ"), + (0xFFC3, "M", "ᅢ"), + (0xFFC4, "M", "ᅣ"), + (0xFFC5, "M", "ᅤ"), + (0xFFC6, "M", "ᅥ"), + (0xFFC7, "M", "ᅦ"), + (0xFFC8, "X"), + (0xFFCA, "M", "ᅧ"), + (0xFFCB, "M", "ᅨ"), + (0xFFCC, "M", "ᅩ"), + (0xFFCD, "M", "ᅪ"), + (0xFFCE, "M", "ᅫ"), + (0xFFCF, "M", "ᅬ"), + (0xFFD0, "X"), + (0xFFD2, "M", "ᅭ"), + (0xFFD3, "M", "ᅮ"), + (0xFFD4, "M", "ᅯ"), + (0xFFD5, "M", "ᅰ"), + (0xFFD6, "M", "ᅱ"), + (0xFFD7, "M", "ᅲ"), + (0xFFD8, "X"), + (0xFFDA, "M", "ᅳ"), + (0xFFDB, "M", "ᅴ"), + (0xFFDC, "M", "ᅵ"), + (0xFFDD, "X"), + (0xFFE0, "M", "¢"), + (0xFFE1, "M", "£"), + (0xFFE2, "M", "¬"), + (0xFFE3, "3", " ̄"), + (0xFFE4, "M", "¦"), + (0xFFE5, "M", "¥"), + (0xFFE6, "M", "₩"), + (0xFFE7, "X"), + (0xFFE8, "M", "│"), + (0xFFE9, "M", "←"), + (0xFFEA, "M", "↑"), + (0xFFEB, "M", "→"), + (0xFFEC, "M", "↓"), + (0xFFED, "M", "■"), + ] + + +def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFFEE, "M", "○"), + (0xFFEF, "X"), + (0x10000, "V"), + (0x1000C, "X"), + (0x1000D, "V"), + (0x10027, "X"), + (0x10028, "V"), + (0x1003B, "X"), + (0x1003C, "V"), + (0x1003E, "X"), + (0x1003F, "V"), + (0x1004E, "X"), + (0x10050, "V"), + (0x1005E, "X"), + (0x10080, "V"), + (0x100FB, "X"), + (0x10100, "V"), + (0x10103, "X"), + (0x10107, "V"), + (0x10134, "X"), + (0x10137, "V"), + (0x1018F, "X"), + (0x10190, "V"), + (0x1019D, "X"), + (0x101A0, "V"), + (0x101A1, "X"), + (0x101D0, "V"), + (0x101FE, "X"), + (0x10280, "V"), + (0x1029D, "X"), + (0x102A0, "V"), + (0x102D1, "X"), + (0x102E0, "V"), + (0x102FC, "X"), + (0x10300, "V"), + (0x10324, "X"), + (0x1032D, "V"), + (0x1034B, "X"), + (0x10350, "V"), + (0x1037B, "X"), + (0x10380, "V"), + (0x1039E, "X"), + (0x1039F, "V"), + (0x103C4, "X"), + (0x103C8, "V"), + (0x103D6, "X"), + (0x10400, "M", "𐐨"), + (0x10401, "M", "𐐩"), + (0x10402, "M", "𐐪"), + (0x10403, "M", "𐐫"), + (0x10404, "M", "𐐬"), + (0x10405, "M", "𐐭"), + (0x10406, "M", "𐐮"), + (0x10407, "M", "𐐯"), + (0x10408, "M", "𐐰"), + (0x10409, "M", "𐐱"), + (0x1040A, "M", "𐐲"), + (0x1040B, "M", "𐐳"), + (0x1040C, "M", "𐐴"), + (0x1040D, "M", "𐐵"), + (0x1040E, "M", "𐐶"), + (0x1040F, "M", "𐐷"), + (0x10410, "M", "𐐸"), + (0x10411, "M", "𐐹"), + (0x10412, "M", "𐐺"), + (0x10413, "M", "𐐻"), + (0x10414, "M", "𐐼"), + (0x10415, "M", "𐐽"), + (0x10416, "M", "𐐾"), + (0x10417, "M", "𐐿"), + (0x10418, "M", "𐑀"), + (0x10419, "M", "𐑁"), + (0x1041A, "M", "𐑂"), + (0x1041B, "M", "𐑃"), + (0x1041C, "M", "𐑄"), + (0x1041D, "M", "𐑅"), + (0x1041E, "M", "𐑆"), + (0x1041F, "M", "𐑇"), + (0x10420, "M", "𐑈"), + (0x10421, "M", "𐑉"), + (0x10422, "M", "𐑊"), + (0x10423, "M", "𐑋"), + (0x10424, "M", "𐑌"), + (0x10425, "M", "𐑍"), + (0x10426, "M", "𐑎"), + (0x10427, "M", "𐑏"), + (0x10428, "V"), + (0x1049E, "X"), + (0x104A0, "V"), + (0x104AA, "X"), + (0x104B0, "M", "𐓘"), + (0x104B1, "M", "𐓙"), + (0x104B2, "M", "𐓚"), + (0x104B3, "M", "𐓛"), + (0x104B4, "M", "𐓜"), + (0x104B5, "M", "𐓝"), + (0x104B6, "M", "𐓞"), + (0x104B7, "M", "𐓟"), + (0x104B8, "M", "𐓠"), + (0x104B9, "M", "𐓡"), + ] + + +def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x104BA, "M", "𐓢"), + (0x104BB, "M", "𐓣"), + (0x104BC, "M", "𐓤"), + (0x104BD, "M", "𐓥"), + (0x104BE, "M", "𐓦"), + (0x104BF, "M", "𐓧"), + (0x104C0, "M", "𐓨"), + (0x104C1, "M", "𐓩"), + (0x104C2, "M", "𐓪"), + (0x104C3, "M", "𐓫"), + (0x104C4, "M", "𐓬"), + (0x104C5, "M", "𐓭"), + (0x104C6, "M", "𐓮"), + (0x104C7, "M", "𐓯"), + (0x104C8, "M", "𐓰"), + (0x104C9, "M", "𐓱"), + (0x104CA, "M", "𐓲"), + (0x104CB, "M", "𐓳"), + (0x104CC, "M", "𐓴"), + (0x104CD, "M", "𐓵"), + (0x104CE, "M", "𐓶"), + (0x104CF, "M", "𐓷"), + (0x104D0, "M", "𐓸"), + (0x104D1, "M", "𐓹"), + (0x104D2, "M", "𐓺"), + (0x104D3, "M", "𐓻"), + (0x104D4, "X"), + (0x104D8, "V"), + (0x104FC, "X"), + (0x10500, "V"), + (0x10528, "X"), + (0x10530, "V"), + (0x10564, "X"), + (0x1056F, "V"), + (0x10570, "M", "𐖗"), + (0x10571, "M", "𐖘"), + (0x10572, "M", "𐖙"), + (0x10573, "M", "𐖚"), + (0x10574, "M", "𐖛"), + (0x10575, "M", "𐖜"), + (0x10576, "M", "𐖝"), + (0x10577, "M", "𐖞"), + (0x10578, "M", "𐖟"), + (0x10579, "M", "𐖠"), + (0x1057A, "M", "𐖡"), + (0x1057B, "X"), + (0x1057C, "M", "𐖣"), + (0x1057D, "M", "𐖤"), + (0x1057E, "M", "𐖥"), + (0x1057F, "M", "𐖦"), + (0x10580, "M", "𐖧"), + (0x10581, "M", "𐖨"), + (0x10582, "M", "𐖩"), + (0x10583, "M", "𐖪"), + (0x10584, "M", "𐖫"), + (0x10585, "M", "𐖬"), + (0x10586, "M", "𐖭"), + (0x10587, "M", "𐖮"), + (0x10588, "M", "𐖯"), + (0x10589, "M", "𐖰"), + (0x1058A, "M", "𐖱"), + (0x1058B, "X"), + (0x1058C, "M", "𐖳"), + (0x1058D, "M", "𐖴"), + (0x1058E, "M", "𐖵"), + (0x1058F, "M", "𐖶"), + (0x10590, "M", "𐖷"), + (0x10591, "M", "𐖸"), + (0x10592, "M", "𐖹"), + (0x10593, "X"), + (0x10594, "M", "𐖻"), + (0x10595, "M", "𐖼"), + (0x10596, "X"), + (0x10597, "V"), + (0x105A2, "X"), + (0x105A3, "V"), + (0x105B2, "X"), + (0x105B3, "V"), + (0x105BA, "X"), + (0x105BB, "V"), + (0x105BD, "X"), + (0x10600, "V"), + (0x10737, "X"), + (0x10740, "V"), + (0x10756, "X"), + (0x10760, "V"), + (0x10768, "X"), + (0x10780, "V"), + (0x10781, "M", "ː"), + (0x10782, "M", "ˑ"), + (0x10783, "M", "æ"), + (0x10784, "M", "ʙ"), + (0x10785, "M", "ɓ"), + (0x10786, "X"), + (0x10787, "M", "ʣ"), + (0x10788, "M", "ꭦ"), + (0x10789, "M", "ʥ"), + (0x1078A, "M", "ʤ"), + (0x1078B, "M", "ɖ"), + (0x1078C, "M", "ɗ"), + ] + + +def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1078D, "M", "ᶑ"), + (0x1078E, "M", "ɘ"), + (0x1078F, "M", "ɞ"), + (0x10790, "M", "ʩ"), + (0x10791, "M", "ɤ"), + (0x10792, "M", "ɢ"), + (0x10793, "M", "ɠ"), + (0x10794, "M", "ʛ"), + (0x10795, "M", "ħ"), + (0x10796, "M", "ʜ"), + (0x10797, "M", "ɧ"), + (0x10798, "M", "ʄ"), + (0x10799, "M", "ʪ"), + (0x1079A, "M", "ʫ"), + (0x1079B, "M", "ɬ"), + (0x1079C, "M", "𝼄"), + (0x1079D, "M", "ꞎ"), + (0x1079E, "M", "ɮ"), + (0x1079F, "M", "𝼅"), + (0x107A0, "M", "ʎ"), + (0x107A1, "M", "𝼆"), + (0x107A2, "M", "ø"), + (0x107A3, "M", "ɶ"), + (0x107A4, "M", "ɷ"), + (0x107A5, "M", "q"), + (0x107A6, "M", "ɺ"), + (0x107A7, "M", "𝼈"), + (0x107A8, "M", "ɽ"), + (0x107A9, "M", "ɾ"), + (0x107AA, "M", "ʀ"), + (0x107AB, "M", "ʨ"), + (0x107AC, "M", "ʦ"), + (0x107AD, "M", "ꭧ"), + (0x107AE, "M", "ʧ"), + (0x107AF, "M", "ʈ"), + (0x107B0, "M", "ⱱ"), + (0x107B1, "X"), + (0x107B2, "M", "ʏ"), + (0x107B3, "M", "ʡ"), + (0x107B4, "M", "ʢ"), + (0x107B5, "M", "ʘ"), + (0x107B6, "M", "ǀ"), + (0x107B7, "M", "ǁ"), + (0x107B8, "M", "ǂ"), + (0x107B9, "M", "𝼊"), + (0x107BA, "M", "𝼞"), + (0x107BB, "X"), + (0x10800, "V"), + (0x10806, "X"), + (0x10808, "V"), + (0x10809, "X"), + (0x1080A, "V"), + (0x10836, "X"), + (0x10837, "V"), + (0x10839, "X"), + (0x1083C, "V"), + (0x1083D, "X"), + (0x1083F, "V"), + (0x10856, "X"), + (0x10857, "V"), + (0x1089F, "X"), + (0x108A7, "V"), + (0x108B0, "X"), + (0x108E0, "V"), + (0x108F3, "X"), + (0x108F4, "V"), + (0x108F6, "X"), + (0x108FB, "V"), + (0x1091C, "X"), + (0x1091F, "V"), + (0x1093A, "X"), + (0x1093F, "V"), + (0x10940, "X"), + (0x10980, "V"), + (0x109B8, "X"), + (0x109BC, "V"), + (0x109D0, "X"), + (0x109D2, "V"), + (0x10A04, "X"), + (0x10A05, "V"), + (0x10A07, "X"), + (0x10A0C, "V"), + (0x10A14, "X"), + (0x10A15, "V"), + (0x10A18, "X"), + (0x10A19, "V"), + (0x10A36, "X"), + (0x10A38, "V"), + (0x10A3B, "X"), + (0x10A3F, "V"), + (0x10A49, "X"), + (0x10A50, "V"), + (0x10A59, "X"), + (0x10A60, "V"), + (0x10AA0, "X"), + (0x10AC0, "V"), + (0x10AE7, "X"), + (0x10AEB, "V"), + (0x10AF7, "X"), + (0x10B00, "V"), + ] + + +def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x10B36, "X"), + (0x10B39, "V"), + (0x10B56, "X"), + (0x10B58, "V"), + (0x10B73, "X"), + (0x10B78, "V"), + (0x10B92, "X"), + (0x10B99, "V"), + (0x10B9D, "X"), + (0x10BA9, "V"), + (0x10BB0, "X"), + (0x10C00, "V"), + (0x10C49, "X"), + (0x10C80, "M", "𐳀"), + (0x10C81, "M", "𐳁"), + (0x10C82, "M", "𐳂"), + (0x10C83, "M", "𐳃"), + (0x10C84, "M", "𐳄"), + (0x10C85, "M", "𐳅"), + (0x10C86, "M", "𐳆"), + (0x10C87, "M", "𐳇"), + (0x10C88, "M", "𐳈"), + (0x10C89, "M", "𐳉"), + (0x10C8A, "M", "𐳊"), + (0x10C8B, "M", "𐳋"), + (0x10C8C, "M", "𐳌"), + (0x10C8D, "M", "𐳍"), + (0x10C8E, "M", "𐳎"), + (0x10C8F, "M", "𐳏"), + (0x10C90, "M", "𐳐"), + (0x10C91, "M", "𐳑"), + (0x10C92, "M", "𐳒"), + (0x10C93, "M", "𐳓"), + (0x10C94, "M", "𐳔"), + (0x10C95, "M", "𐳕"), + (0x10C96, "M", "𐳖"), + (0x10C97, "M", "𐳗"), + (0x10C98, "M", "𐳘"), + (0x10C99, "M", "𐳙"), + (0x10C9A, "M", "𐳚"), + (0x10C9B, "M", "𐳛"), + (0x10C9C, "M", "𐳜"), + (0x10C9D, "M", "𐳝"), + (0x10C9E, "M", "𐳞"), + (0x10C9F, "M", "𐳟"), + (0x10CA0, "M", "𐳠"), + (0x10CA1, "M", "𐳡"), + (0x10CA2, "M", "𐳢"), + (0x10CA3, "M", "𐳣"), + (0x10CA4, "M", "𐳤"), + (0x10CA5, "M", "𐳥"), + (0x10CA6, "M", "𐳦"), + (0x10CA7, "M", "𐳧"), + (0x10CA8, "M", "𐳨"), + (0x10CA9, "M", "𐳩"), + (0x10CAA, "M", "𐳪"), + (0x10CAB, "M", "𐳫"), + (0x10CAC, "M", "𐳬"), + (0x10CAD, "M", "𐳭"), + (0x10CAE, "M", "𐳮"), + (0x10CAF, "M", "𐳯"), + (0x10CB0, "M", "𐳰"), + (0x10CB1, "M", "𐳱"), + (0x10CB2, "M", "𐳲"), + (0x10CB3, "X"), + (0x10CC0, "V"), + (0x10CF3, "X"), + (0x10CFA, "V"), + (0x10D28, "X"), + (0x10D30, "V"), + (0x10D3A, "X"), + (0x10E60, "V"), + (0x10E7F, "X"), + (0x10E80, "V"), + (0x10EAA, "X"), + (0x10EAB, "V"), + (0x10EAE, "X"), + (0x10EB0, "V"), + (0x10EB2, "X"), + (0x10EFD, "V"), + (0x10F28, "X"), + (0x10F30, "V"), + (0x10F5A, "X"), + (0x10F70, "V"), + (0x10F8A, "X"), + (0x10FB0, "V"), + (0x10FCC, "X"), + (0x10FE0, "V"), + (0x10FF7, "X"), + (0x11000, "V"), + (0x1104E, "X"), + (0x11052, "V"), + (0x11076, "X"), + (0x1107F, "V"), + (0x110BD, "X"), + (0x110BE, "V"), + (0x110C3, "X"), + (0x110D0, "V"), + (0x110E9, "X"), + (0x110F0, "V"), + ] + + +def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x110FA, "X"), + (0x11100, "V"), + (0x11135, "X"), + (0x11136, "V"), + (0x11148, "X"), + (0x11150, "V"), + (0x11177, "X"), + (0x11180, "V"), + (0x111E0, "X"), + (0x111E1, "V"), + (0x111F5, "X"), + (0x11200, "V"), + (0x11212, "X"), + (0x11213, "V"), + (0x11242, "X"), + (0x11280, "V"), + (0x11287, "X"), + (0x11288, "V"), + (0x11289, "X"), + (0x1128A, "V"), + (0x1128E, "X"), + (0x1128F, "V"), + (0x1129E, "X"), + (0x1129F, "V"), + (0x112AA, "X"), + (0x112B0, "V"), + (0x112EB, "X"), + (0x112F0, "V"), + (0x112FA, "X"), + (0x11300, "V"), + (0x11304, "X"), + (0x11305, "V"), + (0x1130D, "X"), + (0x1130F, "V"), + (0x11311, "X"), + (0x11313, "V"), + (0x11329, "X"), + (0x1132A, "V"), + (0x11331, "X"), + (0x11332, "V"), + (0x11334, "X"), + (0x11335, "V"), + (0x1133A, "X"), + (0x1133B, "V"), + (0x11345, "X"), + (0x11347, "V"), + (0x11349, "X"), + (0x1134B, "V"), + (0x1134E, "X"), + (0x11350, "V"), + (0x11351, "X"), + (0x11357, "V"), + (0x11358, "X"), + (0x1135D, "V"), + (0x11364, "X"), + (0x11366, "V"), + (0x1136D, "X"), + (0x11370, "V"), + (0x11375, "X"), + (0x11400, "V"), + (0x1145C, "X"), + (0x1145D, "V"), + (0x11462, "X"), + (0x11480, "V"), + (0x114C8, "X"), + (0x114D0, "V"), + (0x114DA, "X"), + (0x11580, "V"), + (0x115B6, "X"), + (0x115B8, "V"), + (0x115DE, "X"), + (0x11600, "V"), + (0x11645, "X"), + (0x11650, "V"), + (0x1165A, "X"), + (0x11660, "V"), + (0x1166D, "X"), + (0x11680, "V"), + (0x116BA, "X"), + (0x116C0, "V"), + (0x116CA, "X"), + (0x11700, "V"), + (0x1171B, "X"), + (0x1171D, "V"), + (0x1172C, "X"), + (0x11730, "V"), + (0x11747, "X"), + (0x11800, "V"), + (0x1183C, "X"), + (0x118A0, "M", "𑣀"), + (0x118A1, "M", "𑣁"), + (0x118A2, "M", "𑣂"), + (0x118A3, "M", "𑣃"), + (0x118A4, "M", "𑣄"), + (0x118A5, "M", "𑣅"), + (0x118A6, "M", "𑣆"), + (0x118A7, "M", "𑣇"), + (0x118A8, "M", "𑣈"), + (0x118A9, "M", "𑣉"), + (0x118AA, "M", "𑣊"), + ] + + +def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x118AB, "M", "𑣋"), + (0x118AC, "M", "𑣌"), + (0x118AD, "M", "𑣍"), + (0x118AE, "M", "𑣎"), + (0x118AF, "M", "𑣏"), + (0x118B0, "M", "𑣐"), + (0x118B1, "M", "𑣑"), + (0x118B2, "M", "𑣒"), + (0x118B3, "M", "𑣓"), + (0x118B4, "M", "𑣔"), + (0x118B5, "M", "𑣕"), + (0x118B6, "M", "𑣖"), + (0x118B7, "M", "𑣗"), + (0x118B8, "M", "𑣘"), + (0x118B9, "M", "𑣙"), + (0x118BA, "M", "𑣚"), + (0x118BB, "M", "𑣛"), + (0x118BC, "M", "𑣜"), + (0x118BD, "M", "𑣝"), + (0x118BE, "M", "𑣞"), + (0x118BF, "M", "𑣟"), + (0x118C0, "V"), + (0x118F3, "X"), + (0x118FF, "V"), + (0x11907, "X"), + (0x11909, "V"), + (0x1190A, "X"), + (0x1190C, "V"), + (0x11914, "X"), + (0x11915, "V"), + (0x11917, "X"), + (0x11918, "V"), + (0x11936, "X"), + (0x11937, "V"), + (0x11939, "X"), + (0x1193B, "V"), + (0x11947, "X"), + (0x11950, "V"), + (0x1195A, "X"), + (0x119A0, "V"), + (0x119A8, "X"), + (0x119AA, "V"), + (0x119D8, "X"), + (0x119DA, "V"), + (0x119E5, "X"), + (0x11A00, "V"), + (0x11A48, "X"), + (0x11A50, "V"), + (0x11AA3, "X"), + (0x11AB0, "V"), + (0x11AF9, "X"), + (0x11B00, "V"), + (0x11B0A, "X"), + (0x11C00, "V"), + (0x11C09, "X"), + (0x11C0A, "V"), + (0x11C37, "X"), + (0x11C38, "V"), + (0x11C46, "X"), + (0x11C50, "V"), + (0x11C6D, "X"), + (0x11C70, "V"), + (0x11C90, "X"), + (0x11C92, "V"), + (0x11CA8, "X"), + (0x11CA9, "V"), + (0x11CB7, "X"), + (0x11D00, "V"), + (0x11D07, "X"), + (0x11D08, "V"), + (0x11D0A, "X"), + (0x11D0B, "V"), + (0x11D37, "X"), + (0x11D3A, "V"), + (0x11D3B, "X"), + (0x11D3C, "V"), + (0x11D3E, "X"), + (0x11D3F, "V"), + (0x11D48, "X"), + (0x11D50, "V"), + (0x11D5A, "X"), + (0x11D60, "V"), + (0x11D66, "X"), + (0x11D67, "V"), + (0x11D69, "X"), + (0x11D6A, "V"), + (0x11D8F, "X"), + (0x11D90, "V"), + (0x11D92, "X"), + (0x11D93, "V"), + (0x11D99, "X"), + (0x11DA0, "V"), + (0x11DAA, "X"), + (0x11EE0, "V"), + (0x11EF9, "X"), + (0x11F00, "V"), + (0x11F11, "X"), + (0x11F12, "V"), + (0x11F3B, "X"), + (0x11F3E, "V"), + ] + + +def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x11F5A, "X"), + (0x11FB0, "V"), + (0x11FB1, "X"), + (0x11FC0, "V"), + (0x11FF2, "X"), + (0x11FFF, "V"), + (0x1239A, "X"), + (0x12400, "V"), + (0x1246F, "X"), + (0x12470, "V"), + (0x12475, "X"), + (0x12480, "V"), + (0x12544, "X"), + (0x12F90, "V"), + (0x12FF3, "X"), + (0x13000, "V"), + (0x13430, "X"), + (0x13440, "V"), + (0x13456, "X"), + (0x14400, "V"), + (0x14647, "X"), + (0x16800, "V"), + (0x16A39, "X"), + (0x16A40, "V"), + (0x16A5F, "X"), + (0x16A60, "V"), + (0x16A6A, "X"), + (0x16A6E, "V"), + (0x16ABF, "X"), + (0x16AC0, "V"), + (0x16ACA, "X"), + (0x16AD0, "V"), + (0x16AEE, "X"), + (0x16AF0, "V"), + (0x16AF6, "X"), + (0x16B00, "V"), + (0x16B46, "X"), + (0x16B50, "V"), + (0x16B5A, "X"), + (0x16B5B, "V"), + (0x16B62, "X"), + (0x16B63, "V"), + (0x16B78, "X"), + (0x16B7D, "V"), + (0x16B90, "X"), + (0x16E40, "M", "𖹠"), + (0x16E41, "M", "𖹡"), + (0x16E42, "M", "𖹢"), + (0x16E43, "M", "𖹣"), + (0x16E44, "M", "𖹤"), + (0x16E45, "M", "𖹥"), + (0x16E46, "M", "𖹦"), + (0x16E47, "M", "𖹧"), + (0x16E48, "M", "𖹨"), + (0x16E49, "M", "𖹩"), + (0x16E4A, "M", "𖹪"), + (0x16E4B, "M", "𖹫"), + (0x16E4C, "M", "𖹬"), + (0x16E4D, "M", "𖹭"), + (0x16E4E, "M", "𖹮"), + (0x16E4F, "M", "𖹯"), + (0x16E50, "M", "𖹰"), + (0x16E51, "M", "𖹱"), + (0x16E52, "M", "𖹲"), + (0x16E53, "M", "𖹳"), + (0x16E54, "M", "𖹴"), + (0x16E55, "M", "𖹵"), + (0x16E56, "M", "𖹶"), + (0x16E57, "M", "𖹷"), + (0x16E58, "M", "𖹸"), + (0x16E59, "M", "𖹹"), + (0x16E5A, "M", "𖹺"), + (0x16E5B, "M", "𖹻"), + (0x16E5C, "M", "𖹼"), + (0x16E5D, "M", "𖹽"), + (0x16E5E, "M", "𖹾"), + (0x16E5F, "M", "𖹿"), + (0x16E60, "V"), + (0x16E9B, "X"), + (0x16F00, "V"), + (0x16F4B, "X"), + (0x16F4F, "V"), + (0x16F88, "X"), + (0x16F8F, "V"), + (0x16FA0, "X"), + (0x16FE0, "V"), + (0x16FE5, "X"), + (0x16FF0, "V"), + (0x16FF2, "X"), + (0x17000, "V"), + (0x187F8, "X"), + (0x18800, "V"), + (0x18CD6, "X"), + (0x18D00, "V"), + (0x18D09, "X"), + (0x1AFF0, "V"), + (0x1AFF4, "X"), + (0x1AFF5, "V"), + (0x1AFFC, "X"), + (0x1AFFD, "V"), + ] + + +def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1AFFF, "X"), + (0x1B000, "V"), + (0x1B123, "X"), + (0x1B132, "V"), + (0x1B133, "X"), + (0x1B150, "V"), + (0x1B153, "X"), + (0x1B155, "V"), + (0x1B156, "X"), + (0x1B164, "V"), + (0x1B168, "X"), + (0x1B170, "V"), + (0x1B2FC, "X"), + (0x1BC00, "V"), + (0x1BC6B, "X"), + (0x1BC70, "V"), + (0x1BC7D, "X"), + (0x1BC80, "V"), + (0x1BC89, "X"), + (0x1BC90, "V"), + (0x1BC9A, "X"), + (0x1BC9C, "V"), + (0x1BCA0, "I"), + (0x1BCA4, "X"), + (0x1CF00, "V"), + (0x1CF2E, "X"), + (0x1CF30, "V"), + (0x1CF47, "X"), + (0x1CF50, "V"), + (0x1CFC4, "X"), + (0x1D000, "V"), + (0x1D0F6, "X"), + (0x1D100, "V"), + (0x1D127, "X"), + (0x1D129, "V"), + (0x1D15E, "M", "𝅗𝅥"), + (0x1D15F, "M", "𝅘𝅥"), + (0x1D160, "M", "𝅘𝅥𝅮"), + (0x1D161, "M", "𝅘𝅥𝅯"), + (0x1D162, "M", "𝅘𝅥𝅰"), + (0x1D163, "M", "𝅘𝅥𝅱"), + (0x1D164, "M", "𝅘𝅥𝅲"), + (0x1D165, "V"), + (0x1D173, "X"), + (0x1D17B, "V"), + (0x1D1BB, "M", "𝆹𝅥"), + (0x1D1BC, "M", "𝆺𝅥"), + (0x1D1BD, "M", "𝆹𝅥𝅮"), + (0x1D1BE, "M", "𝆺𝅥𝅮"), + (0x1D1BF, "M", "𝆹𝅥𝅯"), + (0x1D1C0, "M", "𝆺𝅥𝅯"), + (0x1D1C1, "V"), + (0x1D1EB, "X"), + (0x1D200, "V"), + (0x1D246, "X"), + (0x1D2C0, "V"), + (0x1D2D4, "X"), + (0x1D2E0, "V"), + (0x1D2F4, "X"), + (0x1D300, "V"), + (0x1D357, "X"), + (0x1D360, "V"), + (0x1D379, "X"), + (0x1D400, "M", "a"), + (0x1D401, "M", "b"), + (0x1D402, "M", "c"), + (0x1D403, "M", "d"), + (0x1D404, "M", "e"), + (0x1D405, "M", "f"), + (0x1D406, "M", "g"), + (0x1D407, "M", "h"), + (0x1D408, "M", "i"), + (0x1D409, "M", "j"), + (0x1D40A, "M", "k"), + (0x1D40B, "M", "l"), + (0x1D40C, "M", "m"), + (0x1D40D, "M", "n"), + (0x1D40E, "M", "o"), + (0x1D40F, "M", "p"), + (0x1D410, "M", "q"), + (0x1D411, "M", "r"), + (0x1D412, "M", "s"), + (0x1D413, "M", "t"), + (0x1D414, "M", "u"), + (0x1D415, "M", "v"), + (0x1D416, "M", "w"), + (0x1D417, "M", "x"), + (0x1D418, "M", "y"), + (0x1D419, "M", "z"), + (0x1D41A, "M", "a"), + (0x1D41B, "M", "b"), + (0x1D41C, "M", "c"), + (0x1D41D, "M", "d"), + (0x1D41E, "M", "e"), + (0x1D41F, "M", "f"), + (0x1D420, "M", "g"), + (0x1D421, "M", "h"), + (0x1D422, "M", "i"), + (0x1D423, "M", "j"), + (0x1D424, "M", "k"), + ] + + +def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D425, "M", "l"), + (0x1D426, "M", "m"), + (0x1D427, "M", "n"), + (0x1D428, "M", "o"), + (0x1D429, "M", "p"), + (0x1D42A, "M", "q"), + (0x1D42B, "M", "r"), + (0x1D42C, "M", "s"), + (0x1D42D, "M", "t"), + (0x1D42E, "M", "u"), + (0x1D42F, "M", "v"), + (0x1D430, "M", "w"), + (0x1D431, "M", "x"), + (0x1D432, "M", "y"), + (0x1D433, "M", "z"), + (0x1D434, "M", "a"), + (0x1D435, "M", "b"), + (0x1D436, "M", "c"), + (0x1D437, "M", "d"), + (0x1D438, "M", "e"), + (0x1D439, "M", "f"), + (0x1D43A, "M", "g"), + (0x1D43B, "M", "h"), + (0x1D43C, "M", "i"), + (0x1D43D, "M", "j"), + (0x1D43E, "M", "k"), + (0x1D43F, "M", "l"), + (0x1D440, "M", "m"), + (0x1D441, "M", "n"), + (0x1D442, "M", "o"), + (0x1D443, "M", "p"), + (0x1D444, "M", "q"), + (0x1D445, "M", "r"), + (0x1D446, "M", "s"), + (0x1D447, "M", "t"), + (0x1D448, "M", "u"), + (0x1D449, "M", "v"), + (0x1D44A, "M", "w"), + (0x1D44B, "M", "x"), + (0x1D44C, "M", "y"), + (0x1D44D, "M", "z"), + (0x1D44E, "M", "a"), + (0x1D44F, "M", "b"), + (0x1D450, "M", "c"), + (0x1D451, "M", "d"), + (0x1D452, "M", "e"), + (0x1D453, "M", "f"), + (0x1D454, "M", "g"), + (0x1D455, "X"), + (0x1D456, "M", "i"), + (0x1D457, "M", "j"), + (0x1D458, "M", "k"), + (0x1D459, "M", "l"), + (0x1D45A, "M", "m"), + (0x1D45B, "M", "n"), + (0x1D45C, "M", "o"), + (0x1D45D, "M", "p"), + (0x1D45E, "M", "q"), + (0x1D45F, "M", "r"), + (0x1D460, "M", "s"), + (0x1D461, "M", "t"), + (0x1D462, "M", "u"), + (0x1D463, "M", "v"), + (0x1D464, "M", "w"), + (0x1D465, "M", "x"), + (0x1D466, "M", "y"), + (0x1D467, "M", "z"), + (0x1D468, "M", "a"), + (0x1D469, "M", "b"), + (0x1D46A, "M", "c"), + (0x1D46B, "M", "d"), + (0x1D46C, "M", "e"), + (0x1D46D, "M", "f"), + (0x1D46E, "M", "g"), + (0x1D46F, "M", "h"), + (0x1D470, "M", "i"), + (0x1D471, "M", "j"), + (0x1D472, "M", "k"), + (0x1D473, "M", "l"), + (0x1D474, "M", "m"), + (0x1D475, "M", "n"), + (0x1D476, "M", "o"), + (0x1D477, "M", "p"), + (0x1D478, "M", "q"), + (0x1D479, "M", "r"), + (0x1D47A, "M", "s"), + (0x1D47B, "M", "t"), + (0x1D47C, "M", "u"), + (0x1D47D, "M", "v"), + (0x1D47E, "M", "w"), + (0x1D47F, "M", "x"), + (0x1D480, "M", "y"), + (0x1D481, "M", "z"), + (0x1D482, "M", "a"), + (0x1D483, "M", "b"), + (0x1D484, "M", "c"), + (0x1D485, "M", "d"), + (0x1D486, "M", "e"), + (0x1D487, "M", "f"), + (0x1D488, "M", "g"), + ] + + +def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D489, "M", "h"), + (0x1D48A, "M", "i"), + (0x1D48B, "M", "j"), + (0x1D48C, "M", "k"), + (0x1D48D, "M", "l"), + (0x1D48E, "M", "m"), + (0x1D48F, "M", "n"), + (0x1D490, "M", "o"), + (0x1D491, "M", "p"), + (0x1D492, "M", "q"), + (0x1D493, "M", "r"), + (0x1D494, "M", "s"), + (0x1D495, "M", "t"), + (0x1D496, "M", "u"), + (0x1D497, "M", "v"), + (0x1D498, "M", "w"), + (0x1D499, "M", "x"), + (0x1D49A, "M", "y"), + (0x1D49B, "M", "z"), + (0x1D49C, "M", "a"), + (0x1D49D, "X"), + (0x1D49E, "M", "c"), + (0x1D49F, "M", "d"), + (0x1D4A0, "X"), + (0x1D4A2, "M", "g"), + (0x1D4A3, "X"), + (0x1D4A5, "M", "j"), + (0x1D4A6, "M", "k"), + (0x1D4A7, "X"), + (0x1D4A9, "M", "n"), + (0x1D4AA, "M", "o"), + (0x1D4AB, "M", "p"), + (0x1D4AC, "M", "q"), + (0x1D4AD, "X"), + (0x1D4AE, "M", "s"), + (0x1D4AF, "M", "t"), + (0x1D4B0, "M", "u"), + (0x1D4B1, "M", "v"), + (0x1D4B2, "M", "w"), + (0x1D4B3, "M", "x"), + (0x1D4B4, "M", "y"), + (0x1D4B5, "M", "z"), + (0x1D4B6, "M", "a"), + (0x1D4B7, "M", "b"), + (0x1D4B8, "M", "c"), + (0x1D4B9, "M", "d"), + (0x1D4BA, "X"), + (0x1D4BB, "M", "f"), + (0x1D4BC, "X"), + (0x1D4BD, "M", "h"), + (0x1D4BE, "M", "i"), + (0x1D4BF, "M", "j"), + (0x1D4C0, "M", "k"), + (0x1D4C1, "M", "l"), + (0x1D4C2, "M", "m"), + (0x1D4C3, "M", "n"), + (0x1D4C4, "X"), + (0x1D4C5, "M", "p"), + (0x1D4C6, "M", "q"), + (0x1D4C7, "M", "r"), + (0x1D4C8, "M", "s"), + (0x1D4C9, "M", "t"), + (0x1D4CA, "M", "u"), + (0x1D4CB, "M", "v"), + (0x1D4CC, "M", "w"), + (0x1D4CD, "M", "x"), + (0x1D4CE, "M", "y"), + (0x1D4CF, "M", "z"), + (0x1D4D0, "M", "a"), + (0x1D4D1, "M", "b"), + (0x1D4D2, "M", "c"), + (0x1D4D3, "M", "d"), + (0x1D4D4, "M", "e"), + (0x1D4D5, "M", "f"), + (0x1D4D6, "M", "g"), + (0x1D4D7, "M", "h"), + (0x1D4D8, "M", "i"), + (0x1D4D9, "M", "j"), + (0x1D4DA, "M", "k"), + (0x1D4DB, "M", "l"), + (0x1D4DC, "M", "m"), + (0x1D4DD, "M", "n"), + (0x1D4DE, "M", "o"), + (0x1D4DF, "M", "p"), + (0x1D4E0, "M", "q"), + (0x1D4E1, "M", "r"), + (0x1D4E2, "M", "s"), + (0x1D4E3, "M", "t"), + (0x1D4E4, "M", "u"), + (0x1D4E5, "M", "v"), + (0x1D4E6, "M", "w"), + (0x1D4E7, "M", "x"), + (0x1D4E8, "M", "y"), + (0x1D4E9, "M", "z"), + (0x1D4EA, "M", "a"), + (0x1D4EB, "M", "b"), + (0x1D4EC, "M", "c"), + (0x1D4ED, "M", "d"), + (0x1D4EE, "M", "e"), + (0x1D4EF, "M", "f"), + ] + + +def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D4F0, "M", "g"), + (0x1D4F1, "M", "h"), + (0x1D4F2, "M", "i"), + (0x1D4F3, "M", "j"), + (0x1D4F4, "M", "k"), + (0x1D4F5, "M", "l"), + (0x1D4F6, "M", "m"), + (0x1D4F7, "M", "n"), + (0x1D4F8, "M", "o"), + (0x1D4F9, "M", "p"), + (0x1D4FA, "M", "q"), + (0x1D4FB, "M", "r"), + (0x1D4FC, "M", "s"), + (0x1D4FD, "M", "t"), + (0x1D4FE, "M", "u"), + (0x1D4FF, "M", "v"), + (0x1D500, "M", "w"), + (0x1D501, "M", "x"), + (0x1D502, "M", "y"), + (0x1D503, "M", "z"), + (0x1D504, "M", "a"), + (0x1D505, "M", "b"), + (0x1D506, "X"), + (0x1D507, "M", "d"), + (0x1D508, "M", "e"), + (0x1D509, "M", "f"), + (0x1D50A, "M", "g"), + (0x1D50B, "X"), + (0x1D50D, "M", "j"), + (0x1D50E, "M", "k"), + (0x1D50F, "M", "l"), + (0x1D510, "M", "m"), + (0x1D511, "M", "n"), + (0x1D512, "M", "o"), + (0x1D513, "M", "p"), + (0x1D514, "M", "q"), + (0x1D515, "X"), + (0x1D516, "M", "s"), + (0x1D517, "M", "t"), + (0x1D518, "M", "u"), + (0x1D519, "M", "v"), + (0x1D51A, "M", "w"), + (0x1D51B, "M", "x"), + (0x1D51C, "M", "y"), + (0x1D51D, "X"), + (0x1D51E, "M", "a"), + (0x1D51F, "M", "b"), + (0x1D520, "M", "c"), + (0x1D521, "M", "d"), + (0x1D522, "M", "e"), + (0x1D523, "M", "f"), + (0x1D524, "M", "g"), + (0x1D525, "M", "h"), + (0x1D526, "M", "i"), + (0x1D527, "M", "j"), + (0x1D528, "M", "k"), + (0x1D529, "M", "l"), + (0x1D52A, "M", "m"), + (0x1D52B, "M", "n"), + (0x1D52C, "M", "o"), + (0x1D52D, "M", "p"), + (0x1D52E, "M", "q"), + (0x1D52F, "M", "r"), + (0x1D530, "M", "s"), + (0x1D531, "M", "t"), + (0x1D532, "M", "u"), + (0x1D533, "M", "v"), + (0x1D534, "M", "w"), + (0x1D535, "M", "x"), + (0x1D536, "M", "y"), + (0x1D537, "M", "z"), + (0x1D538, "M", "a"), + (0x1D539, "M", "b"), + (0x1D53A, "X"), + (0x1D53B, "M", "d"), + (0x1D53C, "M", "e"), + (0x1D53D, "M", "f"), + (0x1D53E, "M", "g"), + (0x1D53F, "X"), + (0x1D540, "M", "i"), + (0x1D541, "M", "j"), + (0x1D542, "M", "k"), + (0x1D543, "M", "l"), + (0x1D544, "M", "m"), + (0x1D545, "X"), + (0x1D546, "M", "o"), + (0x1D547, "X"), + (0x1D54A, "M", "s"), + (0x1D54B, "M", "t"), + (0x1D54C, "M", "u"), + (0x1D54D, "M", "v"), + (0x1D54E, "M", "w"), + (0x1D54F, "M", "x"), + (0x1D550, "M", "y"), + (0x1D551, "X"), + (0x1D552, "M", "a"), + (0x1D553, "M", "b"), + (0x1D554, "M", "c"), + (0x1D555, "M", "d"), + (0x1D556, "M", "e"), + ] + + +def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D557, "M", "f"), + (0x1D558, "M", "g"), + (0x1D559, "M", "h"), + (0x1D55A, "M", "i"), + (0x1D55B, "M", "j"), + (0x1D55C, "M", "k"), + (0x1D55D, "M", "l"), + (0x1D55E, "M", "m"), + (0x1D55F, "M", "n"), + (0x1D560, "M", "o"), + (0x1D561, "M", "p"), + (0x1D562, "M", "q"), + (0x1D563, "M", "r"), + (0x1D564, "M", "s"), + (0x1D565, "M", "t"), + (0x1D566, "M", "u"), + (0x1D567, "M", "v"), + (0x1D568, "M", "w"), + (0x1D569, "M", "x"), + (0x1D56A, "M", "y"), + (0x1D56B, "M", "z"), + (0x1D56C, "M", "a"), + (0x1D56D, "M", "b"), + (0x1D56E, "M", "c"), + (0x1D56F, "M", "d"), + (0x1D570, "M", "e"), + (0x1D571, "M", "f"), + (0x1D572, "M", "g"), + (0x1D573, "M", "h"), + (0x1D574, "M", "i"), + (0x1D575, "M", "j"), + (0x1D576, "M", "k"), + (0x1D577, "M", "l"), + (0x1D578, "M", "m"), + (0x1D579, "M", "n"), + (0x1D57A, "M", "o"), + (0x1D57B, "M", "p"), + (0x1D57C, "M", "q"), + (0x1D57D, "M", "r"), + (0x1D57E, "M", "s"), + (0x1D57F, "M", "t"), + (0x1D580, "M", "u"), + (0x1D581, "M", "v"), + (0x1D582, "M", "w"), + (0x1D583, "M", "x"), + (0x1D584, "M", "y"), + (0x1D585, "M", "z"), + (0x1D586, "M", "a"), + (0x1D587, "M", "b"), + (0x1D588, "M", "c"), + (0x1D589, "M", "d"), + (0x1D58A, "M", "e"), + (0x1D58B, "M", "f"), + (0x1D58C, "M", "g"), + (0x1D58D, "M", "h"), + (0x1D58E, "M", "i"), + (0x1D58F, "M", "j"), + (0x1D590, "M", "k"), + (0x1D591, "M", "l"), + (0x1D592, "M", "m"), + (0x1D593, "M", "n"), + (0x1D594, "M", "o"), + (0x1D595, "M", "p"), + (0x1D596, "M", "q"), + (0x1D597, "M", "r"), + (0x1D598, "M", "s"), + (0x1D599, "M", "t"), + (0x1D59A, "M", "u"), + (0x1D59B, "M", "v"), + (0x1D59C, "M", "w"), + (0x1D59D, "M", "x"), + (0x1D59E, "M", "y"), + (0x1D59F, "M", "z"), + (0x1D5A0, "M", "a"), + (0x1D5A1, "M", "b"), + (0x1D5A2, "M", "c"), + (0x1D5A3, "M", "d"), + (0x1D5A4, "M", "e"), + (0x1D5A5, "M", "f"), + (0x1D5A6, "M", "g"), + (0x1D5A7, "M", "h"), + (0x1D5A8, "M", "i"), + (0x1D5A9, "M", "j"), + (0x1D5AA, "M", "k"), + (0x1D5AB, "M", "l"), + (0x1D5AC, "M", "m"), + (0x1D5AD, "M", "n"), + (0x1D5AE, "M", "o"), + (0x1D5AF, "M", "p"), + (0x1D5B0, "M", "q"), + (0x1D5B1, "M", "r"), + (0x1D5B2, "M", "s"), + (0x1D5B3, "M", "t"), + (0x1D5B4, "M", "u"), + (0x1D5B5, "M", "v"), + (0x1D5B6, "M", "w"), + (0x1D5B7, "M", "x"), + (0x1D5B8, "M", "y"), + (0x1D5B9, "M", "z"), + (0x1D5BA, "M", "a"), + ] + + +def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D5BB, "M", "b"), + (0x1D5BC, "M", "c"), + (0x1D5BD, "M", "d"), + (0x1D5BE, "M", "e"), + (0x1D5BF, "M", "f"), + (0x1D5C0, "M", "g"), + (0x1D5C1, "M", "h"), + (0x1D5C2, "M", "i"), + (0x1D5C3, "M", "j"), + (0x1D5C4, "M", "k"), + (0x1D5C5, "M", "l"), + (0x1D5C6, "M", "m"), + (0x1D5C7, "M", "n"), + (0x1D5C8, "M", "o"), + (0x1D5C9, "M", "p"), + (0x1D5CA, "M", "q"), + (0x1D5CB, "M", "r"), + (0x1D5CC, "M", "s"), + (0x1D5CD, "M", "t"), + (0x1D5CE, "M", "u"), + (0x1D5CF, "M", "v"), + (0x1D5D0, "M", "w"), + (0x1D5D1, "M", "x"), + (0x1D5D2, "M", "y"), + (0x1D5D3, "M", "z"), + (0x1D5D4, "M", "a"), + (0x1D5D5, "M", "b"), + (0x1D5D6, "M", "c"), + (0x1D5D7, "M", "d"), + (0x1D5D8, "M", "e"), + (0x1D5D9, "M", "f"), + (0x1D5DA, "M", "g"), + (0x1D5DB, "M", "h"), + (0x1D5DC, "M", "i"), + (0x1D5DD, "M", "j"), + (0x1D5DE, "M", "k"), + (0x1D5DF, "M", "l"), + (0x1D5E0, "M", "m"), + (0x1D5E1, "M", "n"), + (0x1D5E2, "M", "o"), + (0x1D5E3, "M", "p"), + (0x1D5E4, "M", "q"), + (0x1D5E5, "M", "r"), + (0x1D5E6, "M", "s"), + (0x1D5E7, "M", "t"), + (0x1D5E8, "M", "u"), + (0x1D5E9, "M", "v"), + (0x1D5EA, "M", "w"), + (0x1D5EB, "M", "x"), + (0x1D5EC, "M", "y"), + (0x1D5ED, "M", "z"), + (0x1D5EE, "M", "a"), + (0x1D5EF, "M", "b"), + (0x1D5F0, "M", "c"), + (0x1D5F1, "M", "d"), + (0x1D5F2, "M", "e"), + (0x1D5F3, "M", "f"), + (0x1D5F4, "M", "g"), + (0x1D5F5, "M", "h"), + (0x1D5F6, "M", "i"), + (0x1D5F7, "M", "j"), + (0x1D5F8, "M", "k"), + (0x1D5F9, "M", "l"), + (0x1D5FA, "M", "m"), + (0x1D5FB, "M", "n"), + (0x1D5FC, "M", "o"), + (0x1D5FD, "M", "p"), + (0x1D5FE, "M", "q"), + (0x1D5FF, "M", "r"), + (0x1D600, "M", "s"), + (0x1D601, "M", "t"), + (0x1D602, "M", "u"), + (0x1D603, "M", "v"), + (0x1D604, "M", "w"), + (0x1D605, "M", "x"), + (0x1D606, "M", "y"), + (0x1D607, "M", "z"), + (0x1D608, "M", "a"), + (0x1D609, "M", "b"), + (0x1D60A, "M", "c"), + (0x1D60B, "M", "d"), + (0x1D60C, "M", "e"), + (0x1D60D, "M", "f"), + (0x1D60E, "M", "g"), + (0x1D60F, "M", "h"), + (0x1D610, "M", "i"), + (0x1D611, "M", "j"), + (0x1D612, "M", "k"), + (0x1D613, "M", "l"), + (0x1D614, "M", "m"), + (0x1D615, "M", "n"), + (0x1D616, "M", "o"), + (0x1D617, "M", "p"), + (0x1D618, "M", "q"), + (0x1D619, "M", "r"), + (0x1D61A, "M", "s"), + (0x1D61B, "M", "t"), + (0x1D61C, "M", "u"), + (0x1D61D, "M", "v"), + (0x1D61E, "M", "w"), + ] + + +def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D61F, "M", "x"), + (0x1D620, "M", "y"), + (0x1D621, "M", "z"), + (0x1D622, "M", "a"), + (0x1D623, "M", "b"), + (0x1D624, "M", "c"), + (0x1D625, "M", "d"), + (0x1D626, "M", "e"), + (0x1D627, "M", "f"), + (0x1D628, "M", "g"), + (0x1D629, "M", "h"), + (0x1D62A, "M", "i"), + (0x1D62B, "M", "j"), + (0x1D62C, "M", "k"), + (0x1D62D, "M", "l"), + (0x1D62E, "M", "m"), + (0x1D62F, "M", "n"), + (0x1D630, "M", "o"), + (0x1D631, "M", "p"), + (0x1D632, "M", "q"), + (0x1D633, "M", "r"), + (0x1D634, "M", "s"), + (0x1D635, "M", "t"), + (0x1D636, "M", "u"), + (0x1D637, "M", "v"), + (0x1D638, "M", "w"), + (0x1D639, "M", "x"), + (0x1D63A, "M", "y"), + (0x1D63B, "M", "z"), + (0x1D63C, "M", "a"), + (0x1D63D, "M", "b"), + (0x1D63E, "M", "c"), + (0x1D63F, "M", "d"), + (0x1D640, "M", "e"), + (0x1D641, "M", "f"), + (0x1D642, "M", "g"), + (0x1D643, "M", "h"), + (0x1D644, "M", "i"), + (0x1D645, "M", "j"), + (0x1D646, "M", "k"), + (0x1D647, "M", "l"), + (0x1D648, "M", "m"), + (0x1D649, "M", "n"), + (0x1D64A, "M", "o"), + (0x1D64B, "M", "p"), + (0x1D64C, "M", "q"), + (0x1D64D, "M", "r"), + (0x1D64E, "M", "s"), + (0x1D64F, "M", "t"), + (0x1D650, "M", "u"), + (0x1D651, "M", "v"), + (0x1D652, "M", "w"), + (0x1D653, "M", "x"), + (0x1D654, "M", "y"), + (0x1D655, "M", "z"), + (0x1D656, "M", "a"), + (0x1D657, "M", "b"), + (0x1D658, "M", "c"), + (0x1D659, "M", "d"), + (0x1D65A, "M", "e"), + (0x1D65B, "M", "f"), + (0x1D65C, "M", "g"), + (0x1D65D, "M", "h"), + (0x1D65E, "M", "i"), + (0x1D65F, "M", "j"), + (0x1D660, "M", "k"), + (0x1D661, "M", "l"), + (0x1D662, "M", "m"), + (0x1D663, "M", "n"), + (0x1D664, "M", "o"), + (0x1D665, "M", "p"), + (0x1D666, "M", "q"), + (0x1D667, "M", "r"), + (0x1D668, "M", "s"), + (0x1D669, "M", "t"), + (0x1D66A, "M", "u"), + (0x1D66B, "M", "v"), + (0x1D66C, "M", "w"), + (0x1D66D, "M", "x"), + (0x1D66E, "M", "y"), + (0x1D66F, "M", "z"), + (0x1D670, "M", "a"), + (0x1D671, "M", "b"), + (0x1D672, "M", "c"), + (0x1D673, "M", "d"), + (0x1D674, "M", "e"), + (0x1D675, "M", "f"), + (0x1D676, "M", "g"), + (0x1D677, "M", "h"), + (0x1D678, "M", "i"), + (0x1D679, "M", "j"), + (0x1D67A, "M", "k"), + (0x1D67B, "M", "l"), + (0x1D67C, "M", "m"), + (0x1D67D, "M", "n"), + (0x1D67E, "M", "o"), + (0x1D67F, "M", "p"), + (0x1D680, "M", "q"), + (0x1D681, "M", "r"), + (0x1D682, "M", "s"), + ] + + +def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D683, "M", "t"), + (0x1D684, "M", "u"), + (0x1D685, "M", "v"), + (0x1D686, "M", "w"), + (0x1D687, "M", "x"), + (0x1D688, "M", "y"), + (0x1D689, "M", "z"), + (0x1D68A, "M", "a"), + (0x1D68B, "M", "b"), + (0x1D68C, "M", "c"), + (0x1D68D, "M", "d"), + (0x1D68E, "M", "e"), + (0x1D68F, "M", "f"), + (0x1D690, "M", "g"), + (0x1D691, "M", "h"), + (0x1D692, "M", "i"), + (0x1D693, "M", "j"), + (0x1D694, "M", "k"), + (0x1D695, "M", "l"), + (0x1D696, "M", "m"), + (0x1D697, "M", "n"), + (0x1D698, "M", "o"), + (0x1D699, "M", "p"), + (0x1D69A, "M", "q"), + (0x1D69B, "M", "r"), + (0x1D69C, "M", "s"), + (0x1D69D, "M", "t"), + (0x1D69E, "M", "u"), + (0x1D69F, "M", "v"), + (0x1D6A0, "M", "w"), + (0x1D6A1, "M", "x"), + (0x1D6A2, "M", "y"), + (0x1D6A3, "M", "z"), + (0x1D6A4, "M", "ı"), + (0x1D6A5, "M", "ȷ"), + (0x1D6A6, "X"), + (0x1D6A8, "M", "α"), + (0x1D6A9, "M", "β"), + (0x1D6AA, "M", "γ"), + (0x1D6AB, "M", "δ"), + (0x1D6AC, "M", "ε"), + (0x1D6AD, "M", "ζ"), + (0x1D6AE, "M", "η"), + (0x1D6AF, "M", "θ"), + (0x1D6B0, "M", "ι"), + (0x1D6B1, "M", "κ"), + (0x1D6B2, "M", "λ"), + (0x1D6B3, "M", "μ"), + (0x1D6B4, "M", "ν"), + (0x1D6B5, "M", "ξ"), + (0x1D6B6, "M", "ο"), + (0x1D6B7, "M", "π"), + (0x1D6B8, "M", "ρ"), + (0x1D6B9, "M", "θ"), + (0x1D6BA, "M", "σ"), + (0x1D6BB, "M", "τ"), + (0x1D6BC, "M", "υ"), + (0x1D6BD, "M", "φ"), + (0x1D6BE, "M", "χ"), + (0x1D6BF, "M", "ψ"), + (0x1D6C0, "M", "ω"), + (0x1D6C1, "M", "∇"), + (0x1D6C2, "M", "α"), + (0x1D6C3, "M", "β"), + (0x1D6C4, "M", "γ"), + (0x1D6C5, "M", "δ"), + (0x1D6C6, "M", "ε"), + (0x1D6C7, "M", "ζ"), + (0x1D6C8, "M", "η"), + (0x1D6C9, "M", "θ"), + (0x1D6CA, "M", "ι"), + (0x1D6CB, "M", "κ"), + (0x1D6CC, "M", "λ"), + (0x1D6CD, "M", "μ"), + (0x1D6CE, "M", "ν"), + (0x1D6CF, "M", "ξ"), + (0x1D6D0, "M", "ο"), + (0x1D6D1, "M", "π"), + (0x1D6D2, "M", "ρ"), + (0x1D6D3, "M", "σ"), + (0x1D6D5, "M", "τ"), + (0x1D6D6, "M", "υ"), + (0x1D6D7, "M", "φ"), + (0x1D6D8, "M", "χ"), + (0x1D6D9, "M", "ψ"), + (0x1D6DA, "M", "ω"), + (0x1D6DB, "M", "∂"), + (0x1D6DC, "M", "ε"), + (0x1D6DD, "M", "θ"), + (0x1D6DE, "M", "κ"), + (0x1D6DF, "M", "φ"), + (0x1D6E0, "M", "ρ"), + (0x1D6E1, "M", "π"), + (0x1D6E2, "M", "α"), + (0x1D6E3, "M", "β"), + (0x1D6E4, "M", "γ"), + (0x1D6E5, "M", "δ"), + (0x1D6E6, "M", "ε"), + (0x1D6E7, "M", "ζ"), + (0x1D6E8, "M", "η"), + ] + + +def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D6E9, "M", "θ"), + (0x1D6EA, "M", "ι"), + (0x1D6EB, "M", "κ"), + (0x1D6EC, "M", "λ"), + (0x1D6ED, "M", "μ"), + (0x1D6EE, "M", "ν"), + (0x1D6EF, "M", "ξ"), + (0x1D6F0, "M", "ο"), + (0x1D6F1, "M", "π"), + (0x1D6F2, "M", "ρ"), + (0x1D6F3, "M", "θ"), + (0x1D6F4, "M", "σ"), + (0x1D6F5, "M", "τ"), + (0x1D6F6, "M", "υ"), + (0x1D6F7, "M", "φ"), + (0x1D6F8, "M", "χ"), + (0x1D6F9, "M", "ψ"), + (0x1D6FA, "M", "ω"), + (0x1D6FB, "M", "∇"), + (0x1D6FC, "M", "α"), + (0x1D6FD, "M", "β"), + (0x1D6FE, "M", "γ"), + (0x1D6FF, "M", "δ"), + (0x1D700, "M", "ε"), + (0x1D701, "M", "ζ"), + (0x1D702, "M", "η"), + (0x1D703, "M", "θ"), + (0x1D704, "M", "ι"), + (0x1D705, "M", "κ"), + (0x1D706, "M", "λ"), + (0x1D707, "M", "μ"), + (0x1D708, "M", "ν"), + (0x1D709, "M", "ξ"), + (0x1D70A, "M", "ο"), + (0x1D70B, "M", "π"), + (0x1D70C, "M", "ρ"), + (0x1D70D, "M", "σ"), + (0x1D70F, "M", "τ"), + (0x1D710, "M", "υ"), + (0x1D711, "M", "φ"), + (0x1D712, "M", "χ"), + (0x1D713, "M", "ψ"), + (0x1D714, "M", "ω"), + (0x1D715, "M", "∂"), + (0x1D716, "M", "ε"), + (0x1D717, "M", "θ"), + (0x1D718, "M", "κ"), + (0x1D719, "M", "φ"), + (0x1D71A, "M", "ρ"), + (0x1D71B, "M", "π"), + (0x1D71C, "M", "α"), + (0x1D71D, "M", "β"), + (0x1D71E, "M", "γ"), + (0x1D71F, "M", "δ"), + (0x1D720, "M", "ε"), + (0x1D721, "M", "ζ"), + (0x1D722, "M", "η"), + (0x1D723, "M", "θ"), + (0x1D724, "M", "ι"), + (0x1D725, "M", "κ"), + (0x1D726, "M", "λ"), + (0x1D727, "M", "μ"), + (0x1D728, "M", "ν"), + (0x1D729, "M", "ξ"), + (0x1D72A, "M", "ο"), + (0x1D72B, "M", "π"), + (0x1D72C, "M", "ρ"), + (0x1D72D, "M", "θ"), + (0x1D72E, "M", "σ"), + (0x1D72F, "M", "τ"), + (0x1D730, "M", "υ"), + (0x1D731, "M", "φ"), + (0x1D732, "M", "χ"), + (0x1D733, "M", "ψ"), + (0x1D734, "M", "ω"), + (0x1D735, "M", "∇"), + (0x1D736, "M", "α"), + (0x1D737, "M", "β"), + (0x1D738, "M", "γ"), + (0x1D739, "M", "δ"), + (0x1D73A, "M", "ε"), + (0x1D73B, "M", "ζ"), + (0x1D73C, "M", "η"), + (0x1D73D, "M", "θ"), + (0x1D73E, "M", "ι"), + (0x1D73F, "M", "κ"), + (0x1D740, "M", "λ"), + (0x1D741, "M", "μ"), + (0x1D742, "M", "ν"), + (0x1D743, "M", "ξ"), + (0x1D744, "M", "ο"), + (0x1D745, "M", "π"), + (0x1D746, "M", "ρ"), + (0x1D747, "M", "σ"), + (0x1D749, "M", "τ"), + (0x1D74A, "M", "υ"), + (0x1D74B, "M", "φ"), + (0x1D74C, "M", "χ"), + (0x1D74D, "M", "ψ"), + (0x1D74E, "M", "ω"), + ] + + +def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D74F, "M", "∂"), + (0x1D750, "M", "ε"), + (0x1D751, "M", "θ"), + (0x1D752, "M", "κ"), + (0x1D753, "M", "φ"), + (0x1D754, "M", "ρ"), + (0x1D755, "M", "π"), + (0x1D756, "M", "α"), + (0x1D757, "M", "β"), + (0x1D758, "M", "γ"), + (0x1D759, "M", "δ"), + (0x1D75A, "M", "ε"), + (0x1D75B, "M", "ζ"), + (0x1D75C, "M", "η"), + (0x1D75D, "M", "θ"), + (0x1D75E, "M", "ι"), + (0x1D75F, "M", "κ"), + (0x1D760, "M", "λ"), + (0x1D761, "M", "μ"), + (0x1D762, "M", "ν"), + (0x1D763, "M", "ξ"), + (0x1D764, "M", "ο"), + (0x1D765, "M", "π"), + (0x1D766, "M", "ρ"), + (0x1D767, "M", "θ"), + (0x1D768, "M", "σ"), + (0x1D769, "M", "τ"), + (0x1D76A, "M", "υ"), + (0x1D76B, "M", "φ"), + (0x1D76C, "M", "χ"), + (0x1D76D, "M", "ψ"), + (0x1D76E, "M", "ω"), + (0x1D76F, "M", "∇"), + (0x1D770, "M", "α"), + (0x1D771, "M", "β"), + (0x1D772, "M", "γ"), + (0x1D773, "M", "δ"), + (0x1D774, "M", "ε"), + (0x1D775, "M", "ζ"), + (0x1D776, "M", "η"), + (0x1D777, "M", "θ"), + (0x1D778, "M", "ι"), + (0x1D779, "M", "κ"), + (0x1D77A, "M", "λ"), + (0x1D77B, "M", "μ"), + (0x1D77C, "M", "ν"), + (0x1D77D, "M", "ξ"), + (0x1D77E, "M", "ο"), + (0x1D77F, "M", "π"), + (0x1D780, "M", "ρ"), + (0x1D781, "M", "σ"), + (0x1D783, "M", "τ"), + (0x1D784, "M", "υ"), + (0x1D785, "M", "φ"), + (0x1D786, "M", "χ"), + (0x1D787, "M", "ψ"), + (0x1D788, "M", "ω"), + (0x1D789, "M", "∂"), + (0x1D78A, "M", "ε"), + (0x1D78B, "M", "θ"), + (0x1D78C, "M", "κ"), + (0x1D78D, "M", "φ"), + (0x1D78E, "M", "ρ"), + (0x1D78F, "M", "π"), + (0x1D790, "M", "α"), + (0x1D791, "M", "β"), + (0x1D792, "M", "γ"), + (0x1D793, "M", "δ"), + (0x1D794, "M", "ε"), + (0x1D795, "M", "ζ"), + (0x1D796, "M", "η"), + (0x1D797, "M", "θ"), + (0x1D798, "M", "ι"), + (0x1D799, "M", "κ"), + (0x1D79A, "M", "λ"), + (0x1D79B, "M", "μ"), + (0x1D79C, "M", "ν"), + (0x1D79D, "M", "ξ"), + (0x1D79E, "M", "ο"), + (0x1D79F, "M", "π"), + (0x1D7A0, "M", "ρ"), + (0x1D7A1, "M", "θ"), + (0x1D7A2, "M", "σ"), + (0x1D7A3, "M", "τ"), + (0x1D7A4, "M", "υ"), + (0x1D7A5, "M", "φ"), + (0x1D7A6, "M", "χ"), + (0x1D7A7, "M", "ψ"), + (0x1D7A8, "M", "ω"), + (0x1D7A9, "M", "∇"), + (0x1D7AA, "M", "α"), + (0x1D7AB, "M", "β"), + (0x1D7AC, "M", "γ"), + (0x1D7AD, "M", "δ"), + (0x1D7AE, "M", "ε"), + (0x1D7AF, "M", "ζ"), + (0x1D7B0, "M", "η"), + (0x1D7B1, "M", "θ"), + (0x1D7B2, "M", "ι"), + (0x1D7B3, "M", "κ"), + ] + + +def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D7B4, "M", "λ"), + (0x1D7B5, "M", "μ"), + (0x1D7B6, "M", "ν"), + (0x1D7B7, "M", "ξ"), + (0x1D7B8, "M", "ο"), + (0x1D7B9, "M", "π"), + (0x1D7BA, "M", "ρ"), + (0x1D7BB, "M", "σ"), + (0x1D7BD, "M", "τ"), + (0x1D7BE, "M", "υ"), + (0x1D7BF, "M", "φ"), + (0x1D7C0, "M", "χ"), + (0x1D7C1, "M", "ψ"), + (0x1D7C2, "M", "ω"), + (0x1D7C3, "M", "∂"), + (0x1D7C4, "M", "ε"), + (0x1D7C5, "M", "θ"), + (0x1D7C6, "M", "κ"), + (0x1D7C7, "M", "φ"), + (0x1D7C8, "M", "ρ"), + (0x1D7C9, "M", "π"), + (0x1D7CA, "M", "ϝ"), + (0x1D7CC, "X"), + (0x1D7CE, "M", "0"), + (0x1D7CF, "M", "1"), + (0x1D7D0, "M", "2"), + (0x1D7D1, "M", "3"), + (0x1D7D2, "M", "4"), + (0x1D7D3, "M", "5"), + (0x1D7D4, "M", "6"), + (0x1D7D5, "M", "7"), + (0x1D7D6, "M", "8"), + (0x1D7D7, "M", "9"), + (0x1D7D8, "M", "0"), + (0x1D7D9, "M", "1"), + (0x1D7DA, "M", "2"), + (0x1D7DB, "M", "3"), + (0x1D7DC, "M", "4"), + (0x1D7DD, "M", "5"), + (0x1D7DE, "M", "6"), + (0x1D7DF, "M", "7"), + (0x1D7E0, "M", "8"), + (0x1D7E1, "M", "9"), + (0x1D7E2, "M", "0"), + (0x1D7E3, "M", "1"), + (0x1D7E4, "M", "2"), + (0x1D7E5, "M", "3"), + (0x1D7E6, "M", "4"), + (0x1D7E7, "M", "5"), + (0x1D7E8, "M", "6"), + (0x1D7E9, "M", "7"), + (0x1D7EA, "M", "8"), + (0x1D7EB, "M", "9"), + (0x1D7EC, "M", "0"), + (0x1D7ED, "M", "1"), + (0x1D7EE, "M", "2"), + (0x1D7EF, "M", "3"), + (0x1D7F0, "M", "4"), + (0x1D7F1, "M", "5"), + (0x1D7F2, "M", "6"), + (0x1D7F3, "M", "7"), + (0x1D7F4, "M", "8"), + (0x1D7F5, "M", "9"), + (0x1D7F6, "M", "0"), + (0x1D7F7, "M", "1"), + (0x1D7F8, "M", "2"), + (0x1D7F9, "M", "3"), + (0x1D7FA, "M", "4"), + (0x1D7FB, "M", "5"), + (0x1D7FC, "M", "6"), + (0x1D7FD, "M", "7"), + (0x1D7FE, "M", "8"), + (0x1D7FF, "M", "9"), + (0x1D800, "V"), + (0x1DA8C, "X"), + (0x1DA9B, "V"), + (0x1DAA0, "X"), + (0x1DAA1, "V"), + (0x1DAB0, "X"), + (0x1DF00, "V"), + (0x1DF1F, "X"), + (0x1DF25, "V"), + (0x1DF2B, "X"), + (0x1E000, "V"), + (0x1E007, "X"), + (0x1E008, "V"), + (0x1E019, "X"), + (0x1E01B, "V"), + (0x1E022, "X"), + (0x1E023, "V"), + (0x1E025, "X"), + (0x1E026, "V"), + (0x1E02B, "X"), + (0x1E030, "M", "а"), + (0x1E031, "M", "б"), + (0x1E032, "M", "в"), + (0x1E033, "M", "г"), + (0x1E034, "M", "д"), + (0x1E035, "M", "е"), + (0x1E036, "M", "ж"), + ] + + +def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E037, "M", "з"), + (0x1E038, "M", "и"), + (0x1E039, "M", "к"), + (0x1E03A, "M", "л"), + (0x1E03B, "M", "м"), + (0x1E03C, "M", "о"), + (0x1E03D, "M", "п"), + (0x1E03E, "M", "р"), + (0x1E03F, "M", "с"), + (0x1E040, "M", "т"), + (0x1E041, "M", "у"), + (0x1E042, "M", "ф"), + (0x1E043, "M", "х"), + (0x1E044, "M", "ц"), + (0x1E045, "M", "ч"), + (0x1E046, "M", "ш"), + (0x1E047, "M", "ы"), + (0x1E048, "M", "э"), + (0x1E049, "M", "ю"), + (0x1E04A, "M", "ꚉ"), + (0x1E04B, "M", "ә"), + (0x1E04C, "M", "і"), + (0x1E04D, "M", "ј"), + (0x1E04E, "M", "ө"), + (0x1E04F, "M", "ү"), + (0x1E050, "M", "ӏ"), + (0x1E051, "M", "а"), + (0x1E052, "M", "б"), + (0x1E053, "M", "в"), + (0x1E054, "M", "г"), + (0x1E055, "M", "д"), + (0x1E056, "M", "е"), + (0x1E057, "M", "ж"), + (0x1E058, "M", "з"), + (0x1E059, "M", "и"), + (0x1E05A, "M", "к"), + (0x1E05B, "M", "л"), + (0x1E05C, "M", "о"), + (0x1E05D, "M", "п"), + (0x1E05E, "M", "с"), + (0x1E05F, "M", "у"), + (0x1E060, "M", "ф"), + (0x1E061, "M", "х"), + (0x1E062, "M", "ц"), + (0x1E063, "M", "ч"), + (0x1E064, "M", "ш"), + (0x1E065, "M", "ъ"), + (0x1E066, "M", "ы"), + (0x1E067, "M", "ґ"), + (0x1E068, "M", "і"), + (0x1E069, "M", "ѕ"), + (0x1E06A, "M", "џ"), + (0x1E06B, "M", "ҫ"), + (0x1E06C, "M", "ꙑ"), + (0x1E06D, "M", "ұ"), + (0x1E06E, "X"), + (0x1E08F, "V"), + (0x1E090, "X"), + (0x1E100, "V"), + (0x1E12D, "X"), + (0x1E130, "V"), + (0x1E13E, "X"), + (0x1E140, "V"), + (0x1E14A, "X"), + (0x1E14E, "V"), + (0x1E150, "X"), + (0x1E290, "V"), + (0x1E2AF, "X"), + (0x1E2C0, "V"), + (0x1E2FA, "X"), + (0x1E2FF, "V"), + (0x1E300, "X"), + (0x1E4D0, "V"), + (0x1E4FA, "X"), + (0x1E7E0, "V"), + (0x1E7E7, "X"), + (0x1E7E8, "V"), + (0x1E7EC, "X"), + (0x1E7ED, "V"), + (0x1E7EF, "X"), + (0x1E7F0, "V"), + (0x1E7FF, "X"), + (0x1E800, "V"), + (0x1E8C5, "X"), + (0x1E8C7, "V"), + (0x1E8D7, "X"), + (0x1E900, "M", "𞤢"), + (0x1E901, "M", "𞤣"), + (0x1E902, "M", "𞤤"), + (0x1E903, "M", "𞤥"), + (0x1E904, "M", "𞤦"), + (0x1E905, "M", "𞤧"), + (0x1E906, "M", "𞤨"), + (0x1E907, "M", "𞤩"), + (0x1E908, "M", "𞤪"), + (0x1E909, "M", "𞤫"), + (0x1E90A, "M", "𞤬"), + (0x1E90B, "M", "𞤭"), + (0x1E90C, "M", "𞤮"), + (0x1E90D, "M", "𞤯"), + ] + + +def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E90E, "M", "𞤰"), + (0x1E90F, "M", "𞤱"), + (0x1E910, "M", "𞤲"), + (0x1E911, "M", "𞤳"), + (0x1E912, "M", "𞤴"), + (0x1E913, "M", "𞤵"), + (0x1E914, "M", "𞤶"), + (0x1E915, "M", "𞤷"), + (0x1E916, "M", "𞤸"), + (0x1E917, "M", "𞤹"), + (0x1E918, "M", "𞤺"), + (0x1E919, "M", "𞤻"), + (0x1E91A, "M", "𞤼"), + (0x1E91B, "M", "𞤽"), + (0x1E91C, "M", "𞤾"), + (0x1E91D, "M", "𞤿"), + (0x1E91E, "M", "𞥀"), + (0x1E91F, "M", "𞥁"), + (0x1E920, "M", "𞥂"), + (0x1E921, "M", "𞥃"), + (0x1E922, "V"), + (0x1E94C, "X"), + (0x1E950, "V"), + (0x1E95A, "X"), + (0x1E95E, "V"), + (0x1E960, "X"), + (0x1EC71, "V"), + (0x1ECB5, "X"), + (0x1ED01, "V"), + (0x1ED3E, "X"), + (0x1EE00, "M", "ا"), + (0x1EE01, "M", "ب"), + (0x1EE02, "M", "ج"), + (0x1EE03, "M", "د"), + (0x1EE04, "X"), + (0x1EE05, "M", "و"), + (0x1EE06, "M", "ز"), + (0x1EE07, "M", "ح"), + (0x1EE08, "M", "ط"), + (0x1EE09, "M", "ي"), + (0x1EE0A, "M", "ك"), + (0x1EE0B, "M", "ل"), + (0x1EE0C, "M", "م"), + (0x1EE0D, "M", "ن"), + (0x1EE0E, "M", "س"), + (0x1EE0F, "M", "ع"), + (0x1EE10, "M", "ف"), + (0x1EE11, "M", "ص"), + (0x1EE12, "M", "ق"), + (0x1EE13, "M", "ر"), + (0x1EE14, "M", "ش"), + (0x1EE15, "M", "ت"), + (0x1EE16, "M", "ث"), + (0x1EE17, "M", "خ"), + (0x1EE18, "M", "ذ"), + (0x1EE19, "M", "ض"), + (0x1EE1A, "M", "ظ"), + (0x1EE1B, "M", "غ"), + (0x1EE1C, "M", "ٮ"), + (0x1EE1D, "M", "ں"), + (0x1EE1E, "M", "ڡ"), + (0x1EE1F, "M", "ٯ"), + (0x1EE20, "X"), + (0x1EE21, "M", "ب"), + (0x1EE22, "M", "ج"), + (0x1EE23, "X"), + (0x1EE24, "M", "ه"), + (0x1EE25, "X"), + (0x1EE27, "M", "ح"), + (0x1EE28, "X"), + (0x1EE29, "M", "ي"), + (0x1EE2A, "M", "ك"), + (0x1EE2B, "M", "ل"), + (0x1EE2C, "M", "م"), + (0x1EE2D, "M", "ن"), + (0x1EE2E, "M", "س"), + (0x1EE2F, "M", "ع"), + (0x1EE30, "M", "ف"), + (0x1EE31, "M", "ص"), + (0x1EE32, "M", "ق"), + (0x1EE33, "X"), + (0x1EE34, "M", "ش"), + (0x1EE35, "M", "ت"), + (0x1EE36, "M", "ث"), + (0x1EE37, "M", "خ"), + (0x1EE38, "X"), + (0x1EE39, "M", "ض"), + (0x1EE3A, "X"), + (0x1EE3B, "M", "غ"), + (0x1EE3C, "X"), + (0x1EE42, "M", "ج"), + (0x1EE43, "X"), + (0x1EE47, "M", "ح"), + (0x1EE48, "X"), + (0x1EE49, "M", "ي"), + (0x1EE4A, "X"), + (0x1EE4B, "M", "ل"), + (0x1EE4C, "X"), + (0x1EE4D, "M", "ن"), + (0x1EE4E, "M", "س"), + ] + + +def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EE4F, "M", "ع"), + (0x1EE50, "X"), + (0x1EE51, "M", "ص"), + (0x1EE52, "M", "ق"), + (0x1EE53, "X"), + (0x1EE54, "M", "ش"), + (0x1EE55, "X"), + (0x1EE57, "M", "خ"), + (0x1EE58, "X"), + (0x1EE59, "M", "ض"), + (0x1EE5A, "X"), + (0x1EE5B, "M", "غ"), + (0x1EE5C, "X"), + (0x1EE5D, "M", "ں"), + (0x1EE5E, "X"), + (0x1EE5F, "M", "ٯ"), + (0x1EE60, "X"), + (0x1EE61, "M", "ب"), + (0x1EE62, "M", "ج"), + (0x1EE63, "X"), + (0x1EE64, "M", "ه"), + (0x1EE65, "X"), + (0x1EE67, "M", "ح"), + (0x1EE68, "M", "ط"), + (0x1EE69, "M", "ي"), + (0x1EE6A, "M", "ك"), + (0x1EE6B, "X"), + (0x1EE6C, "M", "م"), + (0x1EE6D, "M", "ن"), + (0x1EE6E, "M", "س"), + (0x1EE6F, "M", "ع"), + (0x1EE70, "M", "ف"), + (0x1EE71, "M", "ص"), + (0x1EE72, "M", "ق"), + (0x1EE73, "X"), + (0x1EE74, "M", "ش"), + (0x1EE75, "M", "ت"), + (0x1EE76, "M", "ث"), + (0x1EE77, "M", "خ"), + (0x1EE78, "X"), + (0x1EE79, "M", "ض"), + (0x1EE7A, "M", "ظ"), + (0x1EE7B, "M", "غ"), + (0x1EE7C, "M", "ٮ"), + (0x1EE7D, "X"), + (0x1EE7E, "M", "ڡ"), + (0x1EE7F, "X"), + (0x1EE80, "M", "ا"), + (0x1EE81, "M", "ب"), + (0x1EE82, "M", "ج"), + (0x1EE83, "M", "د"), + (0x1EE84, "M", "ه"), + (0x1EE85, "M", "و"), + (0x1EE86, "M", "ز"), + (0x1EE87, "M", "ح"), + (0x1EE88, "M", "ط"), + (0x1EE89, "M", "ي"), + (0x1EE8A, "X"), + (0x1EE8B, "M", "ل"), + (0x1EE8C, "M", "م"), + (0x1EE8D, "M", "ن"), + (0x1EE8E, "M", "س"), + (0x1EE8F, "M", "ع"), + (0x1EE90, "M", "ف"), + (0x1EE91, "M", "ص"), + (0x1EE92, "M", "ق"), + (0x1EE93, "M", "ر"), + (0x1EE94, "M", "ش"), + (0x1EE95, "M", "ت"), + (0x1EE96, "M", "ث"), + (0x1EE97, "M", "خ"), + (0x1EE98, "M", "ذ"), + (0x1EE99, "M", "ض"), + (0x1EE9A, "M", "ظ"), + (0x1EE9B, "M", "غ"), + (0x1EE9C, "X"), + (0x1EEA1, "M", "ب"), + (0x1EEA2, "M", "ج"), + (0x1EEA3, "M", "د"), + (0x1EEA4, "X"), + (0x1EEA5, "M", "و"), + (0x1EEA6, "M", "ز"), + (0x1EEA7, "M", "ح"), + (0x1EEA8, "M", "ط"), + (0x1EEA9, "M", "ي"), + (0x1EEAA, "X"), + (0x1EEAB, "M", "ل"), + (0x1EEAC, "M", "م"), + (0x1EEAD, "M", "ن"), + (0x1EEAE, "M", "س"), + (0x1EEAF, "M", "ع"), + (0x1EEB0, "M", "ف"), + (0x1EEB1, "M", "ص"), + (0x1EEB2, "M", "ق"), + (0x1EEB3, "M", "ر"), + (0x1EEB4, "M", "ش"), + (0x1EEB5, "M", "ت"), + (0x1EEB6, "M", "ث"), + (0x1EEB7, "M", "خ"), + (0x1EEB8, "M", "ذ"), + ] + + +def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EEB9, "M", "ض"), + (0x1EEBA, "M", "ظ"), + (0x1EEBB, "M", "غ"), + (0x1EEBC, "X"), + (0x1EEF0, "V"), + (0x1EEF2, "X"), + (0x1F000, "V"), + (0x1F02C, "X"), + (0x1F030, "V"), + (0x1F094, "X"), + (0x1F0A0, "V"), + (0x1F0AF, "X"), + (0x1F0B1, "V"), + (0x1F0C0, "X"), + (0x1F0C1, "V"), + (0x1F0D0, "X"), + (0x1F0D1, "V"), + (0x1F0F6, "X"), + (0x1F101, "3", "0,"), + (0x1F102, "3", "1,"), + (0x1F103, "3", "2,"), + (0x1F104, "3", "3,"), + (0x1F105, "3", "4,"), + (0x1F106, "3", "5,"), + (0x1F107, "3", "6,"), + (0x1F108, "3", "7,"), + (0x1F109, "3", "8,"), + (0x1F10A, "3", "9,"), + (0x1F10B, "V"), + (0x1F110, "3", "(a)"), + (0x1F111, "3", "(b)"), + (0x1F112, "3", "(c)"), + (0x1F113, "3", "(d)"), + (0x1F114, "3", "(e)"), + (0x1F115, "3", "(f)"), + (0x1F116, "3", "(g)"), + (0x1F117, "3", "(h)"), + (0x1F118, "3", "(i)"), + (0x1F119, "3", "(j)"), + (0x1F11A, "3", "(k)"), + (0x1F11B, "3", "(l)"), + (0x1F11C, "3", "(m)"), + (0x1F11D, "3", "(n)"), + (0x1F11E, "3", "(o)"), + (0x1F11F, "3", "(p)"), + (0x1F120, "3", "(q)"), + (0x1F121, "3", "(r)"), + (0x1F122, "3", "(s)"), + (0x1F123, "3", "(t)"), + (0x1F124, "3", "(u)"), + (0x1F125, "3", "(v)"), + (0x1F126, "3", "(w)"), + (0x1F127, "3", "(x)"), + (0x1F128, "3", "(y)"), + (0x1F129, "3", "(z)"), + (0x1F12A, "M", "〔s〕"), + (0x1F12B, "M", "c"), + (0x1F12C, "M", "r"), + (0x1F12D, "M", "cd"), + (0x1F12E, "M", "wz"), + (0x1F12F, "V"), + (0x1F130, "M", "a"), + (0x1F131, "M", "b"), + (0x1F132, "M", "c"), + (0x1F133, "M", "d"), + (0x1F134, "M", "e"), + (0x1F135, "M", "f"), + (0x1F136, "M", "g"), + (0x1F137, "M", "h"), + (0x1F138, "M", "i"), + (0x1F139, "M", "j"), + (0x1F13A, "M", "k"), + (0x1F13B, "M", "l"), + (0x1F13C, "M", "m"), + (0x1F13D, "M", "n"), + (0x1F13E, "M", "o"), + (0x1F13F, "M", "p"), + (0x1F140, "M", "q"), + (0x1F141, "M", "r"), + (0x1F142, "M", "s"), + (0x1F143, "M", "t"), + (0x1F144, "M", "u"), + (0x1F145, "M", "v"), + (0x1F146, "M", "w"), + (0x1F147, "M", "x"), + (0x1F148, "M", "y"), + (0x1F149, "M", "z"), + (0x1F14A, "M", "hv"), + (0x1F14B, "M", "mv"), + (0x1F14C, "M", "sd"), + (0x1F14D, "M", "ss"), + (0x1F14E, "M", "ppv"), + (0x1F14F, "M", "wc"), + (0x1F150, "V"), + (0x1F16A, "M", "mc"), + (0x1F16B, "M", "md"), + (0x1F16C, "M", "mr"), + (0x1F16D, "V"), + (0x1F190, "M", "dj"), + (0x1F191, "V"), + ] + + +def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F1AE, "X"), + (0x1F1E6, "V"), + (0x1F200, "M", "ほか"), + (0x1F201, "M", "ココ"), + (0x1F202, "M", "サ"), + (0x1F203, "X"), + (0x1F210, "M", "手"), + (0x1F211, "M", "字"), + (0x1F212, "M", "双"), + (0x1F213, "M", "デ"), + (0x1F214, "M", "二"), + (0x1F215, "M", "多"), + (0x1F216, "M", "解"), + (0x1F217, "M", "天"), + (0x1F218, "M", "交"), + (0x1F219, "M", "映"), + (0x1F21A, "M", "無"), + (0x1F21B, "M", "料"), + (0x1F21C, "M", "前"), + (0x1F21D, "M", "後"), + (0x1F21E, "M", "再"), + (0x1F21F, "M", "新"), + (0x1F220, "M", "初"), + (0x1F221, "M", "終"), + (0x1F222, "M", "生"), + (0x1F223, "M", "販"), + (0x1F224, "M", "声"), + (0x1F225, "M", "吹"), + (0x1F226, "M", "演"), + (0x1F227, "M", "投"), + (0x1F228, "M", "捕"), + (0x1F229, "M", "一"), + (0x1F22A, "M", "三"), + (0x1F22B, "M", "遊"), + (0x1F22C, "M", "左"), + (0x1F22D, "M", "中"), + (0x1F22E, "M", "右"), + (0x1F22F, "M", "指"), + (0x1F230, "M", "走"), + (0x1F231, "M", "打"), + (0x1F232, "M", "禁"), + (0x1F233, "M", "空"), + (0x1F234, "M", "合"), + (0x1F235, "M", "満"), + (0x1F236, "M", "有"), + (0x1F237, "M", "月"), + (0x1F238, "M", "申"), + (0x1F239, "M", "割"), + (0x1F23A, "M", "営"), + (0x1F23B, "M", "配"), + (0x1F23C, "X"), + (0x1F240, "M", "〔本〕"), + (0x1F241, "M", "〔三〕"), + (0x1F242, "M", "〔二〕"), + (0x1F243, "M", "〔安〕"), + (0x1F244, "M", "〔点〕"), + (0x1F245, "M", "〔打〕"), + (0x1F246, "M", "〔盗〕"), + (0x1F247, "M", "〔勝〕"), + (0x1F248, "M", "〔敗〕"), + (0x1F249, "X"), + (0x1F250, "M", "得"), + (0x1F251, "M", "可"), + (0x1F252, "X"), + (0x1F260, "V"), + (0x1F266, "X"), + (0x1F300, "V"), + (0x1F6D8, "X"), + (0x1F6DC, "V"), + (0x1F6ED, "X"), + (0x1F6F0, "V"), + (0x1F6FD, "X"), + (0x1F700, "V"), + (0x1F777, "X"), + (0x1F77B, "V"), + (0x1F7DA, "X"), + (0x1F7E0, "V"), + (0x1F7EC, "X"), + (0x1F7F0, "V"), + (0x1F7F1, "X"), + (0x1F800, "V"), + (0x1F80C, "X"), + (0x1F810, "V"), + (0x1F848, "X"), + (0x1F850, "V"), + (0x1F85A, "X"), + (0x1F860, "V"), + (0x1F888, "X"), + (0x1F890, "V"), + (0x1F8AE, "X"), + (0x1F8B0, "V"), + (0x1F8B2, "X"), + (0x1F900, "V"), + (0x1FA54, "X"), + (0x1FA60, "V"), + (0x1FA6E, "X"), + (0x1FA70, "V"), + (0x1FA7D, "X"), + (0x1FA80, "V"), + (0x1FA89, "X"), + ] + + +def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FA90, "V"), + (0x1FABE, "X"), + (0x1FABF, "V"), + (0x1FAC6, "X"), + (0x1FACE, "V"), + (0x1FADC, "X"), + (0x1FAE0, "V"), + (0x1FAE9, "X"), + (0x1FAF0, "V"), + (0x1FAF9, "X"), + (0x1FB00, "V"), + (0x1FB93, "X"), + (0x1FB94, "V"), + (0x1FBCB, "X"), + (0x1FBF0, "M", "0"), + (0x1FBF1, "M", "1"), + (0x1FBF2, "M", "2"), + (0x1FBF3, "M", "3"), + (0x1FBF4, "M", "4"), + (0x1FBF5, "M", "5"), + (0x1FBF6, "M", "6"), + (0x1FBF7, "M", "7"), + (0x1FBF8, "M", "8"), + (0x1FBF9, "M", "9"), + (0x1FBFA, "X"), + (0x20000, "V"), + (0x2A6E0, "X"), + (0x2A700, "V"), + (0x2B73A, "X"), + (0x2B740, "V"), + (0x2B81E, "X"), + (0x2B820, "V"), + (0x2CEA2, "X"), + (0x2CEB0, "V"), + (0x2EBE1, "X"), + (0x2EBF0, "V"), + (0x2EE5E, "X"), + (0x2F800, "M", "丽"), + (0x2F801, "M", "丸"), + (0x2F802, "M", "乁"), + (0x2F803, "M", "𠄢"), + (0x2F804, "M", "你"), + (0x2F805, "M", "侮"), + (0x2F806, "M", "侻"), + (0x2F807, "M", "倂"), + (0x2F808, "M", "偺"), + (0x2F809, "M", "備"), + (0x2F80A, "M", "僧"), + (0x2F80B, "M", "像"), + (0x2F80C, "M", "㒞"), + (0x2F80D, "M", "𠘺"), + (0x2F80E, "M", "免"), + (0x2F80F, "M", "兔"), + (0x2F810, "M", "兤"), + (0x2F811, "M", "具"), + (0x2F812, "M", "𠔜"), + (0x2F813, "M", "㒹"), + (0x2F814, "M", "內"), + (0x2F815, "M", "再"), + (0x2F816, "M", "𠕋"), + (0x2F817, "M", "冗"), + (0x2F818, "M", "冤"), + (0x2F819, "M", "仌"), + (0x2F81A, "M", "冬"), + (0x2F81B, "M", "况"), + (0x2F81C, "M", "𩇟"), + (0x2F81D, "M", "凵"), + (0x2F81E, "M", "刃"), + (0x2F81F, "M", "㓟"), + (0x2F820, "M", "刻"), + (0x2F821, "M", "剆"), + (0x2F822, "M", "割"), + (0x2F823, "M", "剷"), + (0x2F824, "M", "㔕"), + (0x2F825, "M", "勇"), + (0x2F826, "M", "勉"), + (0x2F827, "M", "勤"), + (0x2F828, "M", "勺"), + (0x2F829, "M", "包"), + (0x2F82A, "M", "匆"), + (0x2F82B, "M", "北"), + (0x2F82C, "M", "卉"), + (0x2F82D, "M", "卑"), + (0x2F82E, "M", "博"), + (0x2F82F, "M", "即"), + (0x2F830, "M", "卽"), + (0x2F831, "M", "卿"), + (0x2F834, "M", "𠨬"), + (0x2F835, "M", "灰"), + (0x2F836, "M", "及"), + (0x2F837, "M", "叟"), + (0x2F838, "M", "𠭣"), + (0x2F839, "M", "叫"), + (0x2F83A, "M", "叱"), + (0x2F83B, "M", "吆"), + (0x2F83C, "M", "咞"), + (0x2F83D, "M", "吸"), + (0x2F83E, "M", "呈"), + (0x2F83F, "M", "周"), + (0x2F840, "M", "咢"), + ] + + +def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F841, "M", "哶"), + (0x2F842, "M", "唐"), + (0x2F843, "M", "啓"), + (0x2F844, "M", "啣"), + (0x2F845, "M", "善"), + (0x2F847, "M", "喙"), + (0x2F848, "M", "喫"), + (0x2F849, "M", "喳"), + (0x2F84A, "M", "嗂"), + (0x2F84B, "M", "圖"), + (0x2F84C, "M", "嘆"), + (0x2F84D, "M", "圗"), + (0x2F84E, "M", "噑"), + (0x2F84F, "M", "噴"), + (0x2F850, "M", "切"), + (0x2F851, "M", "壮"), + (0x2F852, "M", "城"), + (0x2F853, "M", "埴"), + (0x2F854, "M", "堍"), + (0x2F855, "M", "型"), + (0x2F856, "M", "堲"), + (0x2F857, "M", "報"), + (0x2F858, "M", "墬"), + (0x2F859, "M", "𡓤"), + (0x2F85A, "M", "売"), + (0x2F85B, "M", "壷"), + (0x2F85C, "M", "夆"), + (0x2F85D, "M", "多"), + (0x2F85E, "M", "夢"), + (0x2F85F, "M", "奢"), + (0x2F860, "M", "𡚨"), + (0x2F861, "M", "𡛪"), + (0x2F862, "M", "姬"), + (0x2F863, "M", "娛"), + (0x2F864, "M", "娧"), + (0x2F865, "M", "姘"), + (0x2F866, "M", "婦"), + (0x2F867, "M", "㛮"), + (0x2F868, "X"), + (0x2F869, "M", "嬈"), + (0x2F86A, "M", "嬾"), + (0x2F86C, "M", "𡧈"), + (0x2F86D, "M", "寃"), + (0x2F86E, "M", "寘"), + (0x2F86F, "M", "寧"), + (0x2F870, "M", "寳"), + (0x2F871, "M", "𡬘"), + (0x2F872, "M", "寿"), + (0x2F873, "M", "将"), + (0x2F874, "X"), + (0x2F875, "M", "尢"), + (0x2F876, "M", "㞁"), + (0x2F877, "M", "屠"), + (0x2F878, "M", "屮"), + (0x2F879, "M", "峀"), + (0x2F87A, "M", "岍"), + (0x2F87B, "M", "𡷤"), + (0x2F87C, "M", "嵃"), + (0x2F87D, "M", "𡷦"), + (0x2F87E, "M", "嵮"), + (0x2F87F, "M", "嵫"), + (0x2F880, "M", "嵼"), + (0x2F881, "M", "巡"), + (0x2F882, "M", "巢"), + (0x2F883, "M", "㠯"), + (0x2F884, "M", "巽"), + (0x2F885, "M", "帨"), + (0x2F886, "M", "帽"), + (0x2F887, "M", "幩"), + (0x2F888, "M", "㡢"), + (0x2F889, "M", "𢆃"), + (0x2F88A, "M", "㡼"), + (0x2F88B, "M", "庰"), + (0x2F88C, "M", "庳"), + (0x2F88D, "M", "庶"), + (0x2F88E, "M", "廊"), + (0x2F88F, "M", "𪎒"), + (0x2F890, "M", "廾"), + (0x2F891, "M", "𢌱"), + (0x2F893, "M", "舁"), + (0x2F894, "M", "弢"), + (0x2F896, "M", "㣇"), + (0x2F897, "M", "𣊸"), + (0x2F898, "M", "𦇚"), + (0x2F899, "M", "形"), + (0x2F89A, "M", "彫"), + (0x2F89B, "M", "㣣"), + (0x2F89C, "M", "徚"), + (0x2F89D, "M", "忍"), + (0x2F89E, "M", "志"), + (0x2F89F, "M", "忹"), + (0x2F8A0, "M", "悁"), + (0x2F8A1, "M", "㤺"), + (0x2F8A2, "M", "㤜"), + (0x2F8A3, "M", "悔"), + (0x2F8A4, "M", "𢛔"), + (0x2F8A5, "M", "惇"), + (0x2F8A6, "M", "慈"), + (0x2F8A7, "M", "慌"), + (0x2F8A8, "M", "慎"), + ] + + +def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F8A9, "M", "慌"), + (0x2F8AA, "M", "慺"), + (0x2F8AB, "M", "憎"), + (0x2F8AC, "M", "憲"), + (0x2F8AD, "M", "憤"), + (0x2F8AE, "M", "憯"), + (0x2F8AF, "M", "懞"), + (0x2F8B0, "M", "懲"), + (0x2F8B1, "M", "懶"), + (0x2F8B2, "M", "成"), + (0x2F8B3, "M", "戛"), + (0x2F8B4, "M", "扝"), + (0x2F8B5, "M", "抱"), + (0x2F8B6, "M", "拔"), + (0x2F8B7, "M", "捐"), + (0x2F8B8, "M", "𢬌"), + (0x2F8B9, "M", "挽"), + (0x2F8BA, "M", "拼"), + (0x2F8BB, "M", "捨"), + (0x2F8BC, "M", "掃"), + (0x2F8BD, "M", "揤"), + (0x2F8BE, "M", "𢯱"), + (0x2F8BF, "M", "搢"), + (0x2F8C0, "M", "揅"), + (0x2F8C1, "M", "掩"), + (0x2F8C2, "M", "㨮"), + (0x2F8C3, "M", "摩"), + (0x2F8C4, "M", "摾"), + (0x2F8C5, "M", "撝"), + (0x2F8C6, "M", "摷"), + (0x2F8C7, "M", "㩬"), + (0x2F8C8, "M", "敏"), + (0x2F8C9, "M", "敬"), + (0x2F8CA, "M", "𣀊"), + (0x2F8CB, "M", "旣"), + (0x2F8CC, "M", "書"), + (0x2F8CD, "M", "晉"), + (0x2F8CE, "M", "㬙"), + (0x2F8CF, "M", "暑"), + (0x2F8D0, "M", "㬈"), + (0x2F8D1, "M", "㫤"), + (0x2F8D2, "M", "冒"), + (0x2F8D3, "M", "冕"), + (0x2F8D4, "M", "最"), + (0x2F8D5, "M", "暜"), + (0x2F8D6, "M", "肭"), + (0x2F8D7, "M", "䏙"), + (0x2F8D8, "M", "朗"), + (0x2F8D9, "M", "望"), + (0x2F8DA, "M", "朡"), + (0x2F8DB, "M", "杞"), + (0x2F8DC, "M", "杓"), + (0x2F8DD, "M", "𣏃"), + (0x2F8DE, "M", "㭉"), + (0x2F8DF, "M", "柺"), + (0x2F8E0, "M", "枅"), + (0x2F8E1, "M", "桒"), + (0x2F8E2, "M", "梅"), + (0x2F8E3, "M", "𣑭"), + (0x2F8E4, "M", "梎"), + (0x2F8E5, "M", "栟"), + (0x2F8E6, "M", "椔"), + (0x2F8E7, "M", "㮝"), + (0x2F8E8, "M", "楂"), + (0x2F8E9, "M", "榣"), + (0x2F8EA, "M", "槪"), + (0x2F8EB, "M", "檨"), + (0x2F8EC, "M", "𣚣"), + (0x2F8ED, "M", "櫛"), + (0x2F8EE, "M", "㰘"), + (0x2F8EF, "M", "次"), + (0x2F8F0, "M", "𣢧"), + (0x2F8F1, "M", "歔"), + (0x2F8F2, "M", "㱎"), + (0x2F8F3, "M", "歲"), + (0x2F8F4, "M", "殟"), + (0x2F8F5, "M", "殺"), + (0x2F8F6, "M", "殻"), + (0x2F8F7, "M", "𣪍"), + (0x2F8F8, "M", "𡴋"), + (0x2F8F9, "M", "𣫺"), + (0x2F8FA, "M", "汎"), + (0x2F8FB, "M", "𣲼"), + (0x2F8FC, "M", "沿"), + (0x2F8FD, "M", "泍"), + (0x2F8FE, "M", "汧"), + (0x2F8FF, "M", "洖"), + (0x2F900, "M", "派"), + (0x2F901, "M", "海"), + (0x2F902, "M", "流"), + (0x2F903, "M", "浩"), + (0x2F904, "M", "浸"), + (0x2F905, "M", "涅"), + (0x2F906, "M", "𣴞"), + (0x2F907, "M", "洴"), + (0x2F908, "M", "港"), + (0x2F909, "M", "湮"), + (0x2F90A, "M", "㴳"), + (0x2F90B, "M", "滋"), + (0x2F90C, "M", "滇"), + ] + + +def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F90D, "M", "𣻑"), + (0x2F90E, "M", "淹"), + (0x2F90F, "M", "潮"), + (0x2F910, "M", "𣽞"), + (0x2F911, "M", "𣾎"), + (0x2F912, "M", "濆"), + (0x2F913, "M", "瀹"), + (0x2F914, "M", "瀞"), + (0x2F915, "M", "瀛"), + (0x2F916, "M", "㶖"), + (0x2F917, "M", "灊"), + (0x2F918, "M", "災"), + (0x2F919, "M", "灷"), + (0x2F91A, "M", "炭"), + (0x2F91B, "M", "𠔥"), + (0x2F91C, "M", "煅"), + (0x2F91D, "M", "𤉣"), + (0x2F91E, "M", "熜"), + (0x2F91F, "X"), + (0x2F920, "M", "爨"), + (0x2F921, "M", "爵"), + (0x2F922, "M", "牐"), + (0x2F923, "M", "𤘈"), + (0x2F924, "M", "犀"), + (0x2F925, "M", "犕"), + (0x2F926, "M", "𤜵"), + (0x2F927, "M", "𤠔"), + (0x2F928, "M", "獺"), + (0x2F929, "M", "王"), + (0x2F92A, "M", "㺬"), + (0x2F92B, "M", "玥"), + (0x2F92C, "M", "㺸"), + (0x2F92E, "M", "瑇"), + (0x2F92F, "M", "瑜"), + (0x2F930, "M", "瑱"), + (0x2F931, "M", "璅"), + (0x2F932, "M", "瓊"), + (0x2F933, "M", "㼛"), + (0x2F934, "M", "甤"), + (0x2F935, "M", "𤰶"), + (0x2F936, "M", "甾"), + (0x2F937, "M", "𤲒"), + (0x2F938, "M", "異"), + (0x2F939, "M", "𢆟"), + (0x2F93A, "M", "瘐"), + (0x2F93B, "M", "𤾡"), + (0x2F93C, "M", "𤾸"), + (0x2F93D, "M", "𥁄"), + (0x2F93E, "M", "㿼"), + (0x2F93F, "M", "䀈"), + (0x2F940, "M", "直"), + (0x2F941, "M", "𥃳"), + (0x2F942, "M", "𥃲"), + (0x2F943, "M", "𥄙"), + (0x2F944, "M", "𥄳"), + (0x2F945, "M", "眞"), + (0x2F946, "M", "真"), + (0x2F948, "M", "睊"), + (0x2F949, "M", "䀹"), + (0x2F94A, "M", "瞋"), + (0x2F94B, "M", "䁆"), + (0x2F94C, "M", "䂖"), + (0x2F94D, "M", "𥐝"), + (0x2F94E, "M", "硎"), + (0x2F94F, "M", "碌"), + (0x2F950, "M", "磌"), + (0x2F951, "M", "䃣"), + (0x2F952, "M", "𥘦"), + (0x2F953, "M", "祖"), + (0x2F954, "M", "𥚚"), + (0x2F955, "M", "𥛅"), + (0x2F956, "M", "福"), + (0x2F957, "M", "秫"), + (0x2F958, "M", "䄯"), + (0x2F959, "M", "穀"), + (0x2F95A, "M", "穊"), + (0x2F95B, "M", "穏"), + (0x2F95C, "M", "𥥼"), + (0x2F95D, "M", "𥪧"), + (0x2F95F, "X"), + (0x2F960, "M", "䈂"), + (0x2F961, "M", "𥮫"), + (0x2F962, "M", "篆"), + (0x2F963, "M", "築"), + (0x2F964, "M", "䈧"), + (0x2F965, "M", "𥲀"), + (0x2F966, "M", "糒"), + (0x2F967, "M", "䊠"), + (0x2F968, "M", "糨"), + (0x2F969, "M", "糣"), + (0x2F96A, "M", "紀"), + (0x2F96B, "M", "𥾆"), + (0x2F96C, "M", "絣"), + (0x2F96D, "M", "䌁"), + (0x2F96E, "M", "緇"), + (0x2F96F, "M", "縂"), + (0x2F970, "M", "繅"), + (0x2F971, "M", "䌴"), + (0x2F972, "M", "𦈨"), + (0x2F973, "M", "𦉇"), + ] + + +def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F974, "M", "䍙"), + (0x2F975, "M", "𦋙"), + (0x2F976, "M", "罺"), + (0x2F977, "M", "𦌾"), + (0x2F978, "M", "羕"), + (0x2F979, "M", "翺"), + (0x2F97A, "M", "者"), + (0x2F97B, "M", "𦓚"), + (0x2F97C, "M", "𦔣"), + (0x2F97D, "M", "聠"), + (0x2F97E, "M", "𦖨"), + (0x2F97F, "M", "聰"), + (0x2F980, "M", "𣍟"), + (0x2F981, "M", "䏕"), + (0x2F982, "M", "育"), + (0x2F983, "M", "脃"), + (0x2F984, "M", "䐋"), + (0x2F985, "M", "脾"), + (0x2F986, "M", "媵"), + (0x2F987, "M", "𦞧"), + (0x2F988, "M", "𦞵"), + (0x2F989, "M", "𣎓"), + (0x2F98A, "M", "𣎜"), + (0x2F98B, "M", "舁"), + (0x2F98C, "M", "舄"), + (0x2F98D, "M", "辞"), + (0x2F98E, "M", "䑫"), + (0x2F98F, "M", "芑"), + (0x2F990, "M", "芋"), + (0x2F991, "M", "芝"), + (0x2F992, "M", "劳"), + (0x2F993, "M", "花"), + (0x2F994, "M", "芳"), + (0x2F995, "M", "芽"), + (0x2F996, "M", "苦"), + (0x2F997, "M", "𦬼"), + (0x2F998, "M", "若"), + (0x2F999, "M", "茝"), + (0x2F99A, "M", "荣"), + (0x2F99B, "M", "莭"), + (0x2F99C, "M", "茣"), + (0x2F99D, "M", "莽"), + (0x2F99E, "M", "菧"), + (0x2F99F, "M", "著"), + (0x2F9A0, "M", "荓"), + (0x2F9A1, "M", "菊"), + (0x2F9A2, "M", "菌"), + (0x2F9A3, "M", "菜"), + (0x2F9A4, "M", "𦰶"), + (0x2F9A5, "M", "𦵫"), + (0x2F9A6, "M", "𦳕"), + (0x2F9A7, "M", "䔫"), + (0x2F9A8, "M", "蓱"), + (0x2F9A9, "M", "蓳"), + (0x2F9AA, "M", "蔖"), + (0x2F9AB, "M", "𧏊"), + (0x2F9AC, "M", "蕤"), + (0x2F9AD, "M", "𦼬"), + (0x2F9AE, "M", "䕝"), + (0x2F9AF, "M", "䕡"), + (0x2F9B0, "M", "𦾱"), + (0x2F9B1, "M", "𧃒"), + (0x2F9B2, "M", "䕫"), + (0x2F9B3, "M", "虐"), + (0x2F9B4, "M", "虜"), + (0x2F9B5, "M", "虧"), + (0x2F9B6, "M", "虩"), + (0x2F9B7, "M", "蚩"), + (0x2F9B8, "M", "蚈"), + (0x2F9B9, "M", "蜎"), + (0x2F9BA, "M", "蛢"), + (0x2F9BB, "M", "蝹"), + (0x2F9BC, "M", "蜨"), + (0x2F9BD, "M", "蝫"), + (0x2F9BE, "M", "螆"), + (0x2F9BF, "X"), + (0x2F9C0, "M", "蟡"), + (0x2F9C1, "M", "蠁"), + (0x2F9C2, "M", "䗹"), + (0x2F9C3, "M", "衠"), + (0x2F9C4, "M", "衣"), + (0x2F9C5, "M", "𧙧"), + (0x2F9C6, "M", "裗"), + (0x2F9C7, "M", "裞"), + (0x2F9C8, "M", "䘵"), + (0x2F9C9, "M", "裺"), + (0x2F9CA, "M", "㒻"), + (0x2F9CB, "M", "𧢮"), + (0x2F9CC, "M", "𧥦"), + (0x2F9CD, "M", "䚾"), + (0x2F9CE, "M", "䛇"), + (0x2F9CF, "M", "誠"), + (0x2F9D0, "M", "諭"), + (0x2F9D1, "M", "變"), + (0x2F9D2, "M", "豕"), + (0x2F9D3, "M", "𧲨"), + (0x2F9D4, "M", "貫"), + (0x2F9D5, "M", "賁"), + (0x2F9D6, "M", "贛"), + (0x2F9D7, "M", "起"), + ] + + +def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F9D8, "M", "𧼯"), + (0x2F9D9, "M", "𠠄"), + (0x2F9DA, "M", "跋"), + (0x2F9DB, "M", "趼"), + (0x2F9DC, "M", "跰"), + (0x2F9DD, "M", "𠣞"), + (0x2F9DE, "M", "軔"), + (0x2F9DF, "M", "輸"), + (0x2F9E0, "M", "𨗒"), + (0x2F9E1, "M", "𨗭"), + (0x2F9E2, "M", "邔"), + (0x2F9E3, "M", "郱"), + (0x2F9E4, "M", "鄑"), + (0x2F9E5, "M", "𨜮"), + (0x2F9E6, "M", "鄛"), + (0x2F9E7, "M", "鈸"), + (0x2F9E8, "M", "鋗"), + (0x2F9E9, "M", "鋘"), + (0x2F9EA, "M", "鉼"), + (0x2F9EB, "M", "鏹"), + (0x2F9EC, "M", "鐕"), + (0x2F9ED, "M", "𨯺"), + (0x2F9EE, "M", "開"), + (0x2F9EF, "M", "䦕"), + (0x2F9F0, "M", "閷"), + (0x2F9F1, "M", "𨵷"), + (0x2F9F2, "M", "䧦"), + (0x2F9F3, "M", "雃"), + (0x2F9F4, "M", "嶲"), + (0x2F9F5, "M", "霣"), + (0x2F9F6, "M", "𩅅"), + (0x2F9F7, "M", "𩈚"), + (0x2F9F8, "M", "䩮"), + (0x2F9F9, "M", "䩶"), + (0x2F9FA, "M", "韠"), + (0x2F9FB, "M", "𩐊"), + (0x2F9FC, "M", "䪲"), + (0x2F9FD, "M", "𩒖"), + (0x2F9FE, "M", "頋"), + (0x2FA00, "M", "頩"), + (0x2FA01, "M", "𩖶"), + (0x2FA02, "M", "飢"), + (0x2FA03, "M", "䬳"), + (0x2FA04, "M", "餩"), + (0x2FA05, "M", "馧"), + (0x2FA06, "M", "駂"), + (0x2FA07, "M", "駾"), + (0x2FA08, "M", "䯎"), + (0x2FA09, "M", "𩬰"), + (0x2FA0A, "M", "鬒"), + (0x2FA0B, "M", "鱀"), + (0x2FA0C, "M", "鳽"), + (0x2FA0D, "M", "䳎"), + (0x2FA0E, "M", "䳭"), + (0x2FA0F, "M", "鵧"), + (0x2FA10, "M", "𪃎"), + (0x2FA11, "M", "䳸"), + (0x2FA12, "M", "𪄅"), + (0x2FA13, "M", "𪈎"), + (0x2FA14, "M", "𪊑"), + (0x2FA15, "M", "麻"), + (0x2FA16, "M", "䵖"), + (0x2FA17, "M", "黹"), + (0x2FA18, "M", "黾"), + (0x2FA19, "M", "鼅"), + (0x2FA1A, "M", "鼏"), + (0x2FA1B, "M", "鼖"), + (0x2FA1C, "M", "鼻"), + (0x2FA1D, "M", "𪘀"), + (0x2FA1E, "X"), + (0x30000, "V"), + (0x3134B, "X"), + (0x31350, "V"), + (0x323B0, "X"), + (0xE0100, "I"), + (0xE01F0, "X"), + ] + + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() + + _seg_78() + + _seg_79() + + _seg_80() + + _seg_81() +) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...] diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__init__.py new file mode 100644 index 000000000..b61510544 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__init__.py @@ -0,0 +1,55 @@ +# ruff: noqa: F401 +import os + +from .exceptions import * # noqa: F403 +from .ext import ExtType, Timestamp + +version = (1, 1, 0) +__version__ = "1.1.0" + + +if os.environ.get("MSGPACK_PUREPYTHON"): + from .fallback import Packer, Unpacker, unpackb +else: + try: + from ._cmsgpack import Packer, Unpacker, unpackb + except ImportError: + from .fallback import Packer, Unpacker, unpackb + + +def pack(o, stream, **kwargs): + """ + Pack object `o` and write it to `stream` + + See :class:`Packer` for options. + """ + packer = Packer(**kwargs) + stream.write(packer.pack(o)) + + +def packb(o, **kwargs): + """ + Pack object `o` and return packed bytes + + See :class:`Packer` for options. + """ + return Packer(**kwargs).pack(o) + + +def unpack(stream, **kwargs): + """ + Unpack an object from `stream`. + + Raises `ExtraData` when `stream` contains extra bytes. + See :class:`Unpacker` for options. + """ + data = stream.read() + return unpackb(data, **kwargs) + + +# alias for compatibility to simplejson/marshal/pickle. +load = unpack +loads = unpackb + +dump = pack +dumps = packb diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea58824eb1fad8fd0bd94965b509bd6772da209e GIT binary patch literal 1395 zcmb7E&2HQ_5GEz9R;%6hPnrZl4+YUny9K;zkV_F1MUuwoPukRoZ6wh}t6k38iX~AX zX*;{v+b5eWH2=DRuXOgb-J|Ez^ z!H0Ye*Go`BU)o)mz~?=+vCsEYd`LOvvdFJN7>H)lkJ`7d*A-_c;uUKC!ZaGG|_(Dw7c%H z+f8RS&BZ`kNeAa6oa!Sqn#{?{dre>YD|$v({+YKT=k%N+TwAQSd=@htHaip37ckRo zA|@=aIXjhw23BaM1)C@>A+IK}i{1rbcV}g;)ZIy2*M!Z5WTMf9sMXj7VbwCk3QWf( zGJ&mo6c{Q@*k&`~(P=IZlnGl%zY)2P2mW3phoXW6mT*)+nLLCOC`D62t&<<}sY;rK zJ{0vTArzP9utwMZ&mKK)NpCFo;WGY6Qn z=|Y40ADoUnN2x0c_kH}j99m;}YmKeQbZq{q?Z0yB2<0a3-+^HvT*9!BSLBSGlUJUm zD{oF$2*^SC)j?@hhqAa3$y|!+QWfJC!jrsEpx6Y(F7t=E&L`~j5bDb?HWRhZi&`RqP_}=UjG2JiRO7(PLb)hDP4Qm7MAw1 z*;{d${&!U)q~4ZNNY^&k30H&(N|AHZu~s2~O}+e;+uZ}MH*Yw literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c64eae58da0d488b4aee80d9057e9722a8c0e7f GIT binary patch literal 1873 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 xN9ZdwUq|_l{{s+D8X*7x literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..755d122f8915c64408351fc4e562dbef0ae91caf GIT binary patch literal 24003 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 V@oWBz(%PQiOxxX08T@> 34 + elif len(b) == 12: + nanoseconds, seconds = struct.unpack("!Iq", b) + else: + raise ValueError( + "Timestamp type can only be created from 32, 64, or 96-bit byte objects" + ) + return Timestamp(seconds, nanoseconds) + + def to_bytes(self): + """Pack this Timestamp object into bytes. + + Used for pure-Python msgpack packing. + + :returns data: Payload for EXT message with code -1 (timestamp type) + :rtype: bytes + """ + if (self.seconds >> 34) == 0: # seconds is non-negative and fits in 34 bits + data64 = self.nanoseconds << 34 | self.seconds + if data64 & 0xFFFFFFFF00000000 == 0: + # nanoseconds is zero and seconds < 2**32, so timestamp 32 + data = struct.pack("!L", data64) + else: + # timestamp 64 + data = struct.pack("!Q", data64) + else: + # timestamp 96 + data = struct.pack("!Iq", self.nanoseconds, self.seconds) + return data + + @staticmethod + def from_unix(unix_sec): + """Create a Timestamp from posix timestamp in seconds. + + :param unix_float: Posix timestamp in seconds. + :type unix_float: int or float + """ + seconds = int(unix_sec // 1) + nanoseconds = int((unix_sec % 1) * 10**9) + return Timestamp(seconds, nanoseconds) + + def to_unix(self): + """Get the timestamp as a floating-point value. + + :returns: posix timestamp + :rtype: float + """ + return self.seconds + self.nanoseconds / 1e9 + + @staticmethod + def from_unix_nano(unix_ns): + """Create a Timestamp from posix timestamp in nanoseconds. + + :param int unix_ns: Posix timestamp in nanoseconds. + :rtype: Timestamp + """ + return Timestamp(*divmod(unix_ns, 10**9)) + + def to_unix_nano(self): + """Get the timestamp as a unixtime in nanoseconds. + + :returns: posix timestamp in nanoseconds + :rtype: int + """ + return self.seconds * 10**9 + self.nanoseconds + + def to_datetime(self): + """Get the timestamp as a UTC datetime. + + :rtype: `datetime.datetime` + """ + utc = datetime.timezone.utc + return datetime.datetime.fromtimestamp(0, utc) + datetime.timedelta( + seconds=self.seconds, microseconds=self.nanoseconds // 1000 + ) + + @staticmethod + def from_datetime(dt): + """Create a Timestamp from datetime with tzinfo. + + :rtype: Timestamp + """ + return Timestamp(seconds=int(dt.timestamp()), nanoseconds=dt.microsecond * 1000) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/fallback.py b/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/fallback.py new file mode 100644 index 000000000..b02e47cfb --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/msgpack/fallback.py @@ -0,0 +1,929 @@ +"""Fallback pure Python implementation of msgpack""" + +import struct +import sys +from datetime import datetime as _DateTime + +if hasattr(sys, "pypy_version_info"): + from __pypy__ import newlist_hint + from __pypy__.builders import BytesBuilder + + _USING_STRINGBUILDER = True + + class BytesIO: + def __init__(self, s=b""): + if s: + self.builder = BytesBuilder(len(s)) + self.builder.append(s) + else: + self.builder = BytesBuilder() + + def write(self, s): + if isinstance(s, memoryview): + s = s.tobytes() + elif isinstance(s, bytearray): + s = bytes(s) + self.builder.append(s) + + def getvalue(self): + return self.builder.build() + +else: + from io import BytesIO + + _USING_STRINGBUILDER = False + + def newlist_hint(size): + return [] + + +from .exceptions import BufferFull, ExtraData, FormatError, OutOfData, StackError +from .ext import ExtType, Timestamp + +EX_SKIP = 0 +EX_CONSTRUCT = 1 +EX_READ_ARRAY_HEADER = 2 +EX_READ_MAP_HEADER = 3 + +TYPE_IMMEDIATE = 0 +TYPE_ARRAY = 1 +TYPE_MAP = 2 +TYPE_RAW = 3 +TYPE_BIN = 4 +TYPE_EXT = 5 + +DEFAULT_RECURSE_LIMIT = 511 + + +def _check_type_strict(obj, t, type=type, tuple=tuple): + if type(t) is tuple: + return type(obj) in t + else: + return type(obj) is t + + +def _get_data_from_buffer(obj): + view = memoryview(obj) + if view.itemsize != 1: + raise ValueError("cannot unpack from multi-byte object") + return view + + +def unpackb(packed, **kwargs): + """ + Unpack an object from `packed`. + + Raises ``ExtraData`` when *packed* contains extra bytes. + Raises ``ValueError`` when *packed* is incomplete. + Raises ``FormatError`` when *packed* is not valid msgpack. + Raises ``StackError`` when *packed* contains too nested. + Other exceptions can be raised during unpacking. + + See :class:`Unpacker` for options. + """ + unpacker = Unpacker(None, max_buffer_size=len(packed), **kwargs) + unpacker.feed(packed) + try: + ret = unpacker._unpack() + except OutOfData: + raise ValueError("Unpack failed: incomplete input") + except RecursionError: + raise StackError + if unpacker._got_extradata(): + raise ExtraData(ret, unpacker._get_extradata()) + return ret + + +_NO_FORMAT_USED = "" +_MSGPACK_HEADERS = { + 0xC4: (1, _NO_FORMAT_USED, TYPE_BIN), + 0xC5: (2, ">H", TYPE_BIN), + 0xC6: (4, ">I", TYPE_BIN), + 0xC7: (2, "Bb", TYPE_EXT), + 0xC8: (3, ">Hb", TYPE_EXT), + 0xC9: (5, ">Ib", TYPE_EXT), + 0xCA: (4, ">f"), + 0xCB: (8, ">d"), + 0xCC: (1, _NO_FORMAT_USED), + 0xCD: (2, ">H"), + 0xCE: (4, ">I"), + 0xCF: (8, ">Q"), + 0xD0: (1, "b"), + 0xD1: (2, ">h"), + 0xD2: (4, ">i"), + 0xD3: (8, ">q"), + 0xD4: (1, "b1s", TYPE_EXT), + 0xD5: (2, "b2s", TYPE_EXT), + 0xD6: (4, "b4s", TYPE_EXT), + 0xD7: (8, "b8s", TYPE_EXT), + 0xD8: (16, "b16s", TYPE_EXT), + 0xD9: (1, _NO_FORMAT_USED, TYPE_RAW), + 0xDA: (2, ">H", TYPE_RAW), + 0xDB: (4, ">I", TYPE_RAW), + 0xDC: (2, ">H", TYPE_ARRAY), + 0xDD: (4, ">I", TYPE_ARRAY), + 0xDE: (2, ">H", TYPE_MAP), + 0xDF: (4, ">I", TYPE_MAP), +} + + +class Unpacker: + """Streaming unpacker. + + Arguments: + + :param file_like: + File-like object having `.read(n)` method. + If specified, unpacker reads serialized data from it and `.feed()` is not usable. + + :param int read_size: + Used as `file_like.read(read_size)`. (default: `min(16*1024, max_buffer_size)`) + + :param bool use_list: + If true, unpack msgpack array to Python list. + Otherwise, unpack to Python tuple. (default: True) + + :param bool raw: + If true, unpack msgpack raw to Python bytes. + Otherwise, unpack to Python str by decoding with UTF-8 encoding (default). + + :param int timestamp: + Control how timestamp type is unpacked: + + 0 - Timestamp + 1 - float (Seconds from the EPOCH) + 2 - int (Nanoseconds from the EPOCH) + 3 - datetime.datetime (UTC). + + :param bool strict_map_key: + If true (default), only str or bytes are accepted for map (dict) keys. + + :param object_hook: + When specified, it should be callable. + Unpacker calls it with a dict argument after unpacking msgpack map. + (See also simplejson) + + :param object_pairs_hook: + When specified, it should be callable. + Unpacker calls it with a list of key-value pairs after unpacking msgpack map. + (See also simplejson) + + :param str unicode_errors: + The error handler for decoding unicode. (default: 'strict') + This option should be used only when you have msgpack data which + contains invalid UTF-8 string. + + :param int max_buffer_size: + Limits size of data waiting unpacked. 0 means 2**32-1. + The default value is 100*1024*1024 (100MiB). + Raises `BufferFull` exception when it is insufficient. + You should set this parameter when unpacking data from untrusted source. + + :param int max_str_len: + Deprecated, use *max_buffer_size* instead. + Limits max length of str. (default: max_buffer_size) + + :param int max_bin_len: + Deprecated, use *max_buffer_size* instead. + Limits max length of bin. (default: max_buffer_size) + + :param int max_array_len: + Limits max length of array. + (default: max_buffer_size) + + :param int max_map_len: + Limits max length of map. + (default: max_buffer_size//2) + + :param int max_ext_len: + Deprecated, use *max_buffer_size* instead. + Limits max size of ext type. (default: max_buffer_size) + + Example of streaming deserialize from file-like object:: + + unpacker = Unpacker(file_like) + for o in unpacker: + process(o) + + Example of streaming deserialize from socket:: + + unpacker = Unpacker() + while True: + buf = sock.recv(1024**2) + if not buf: + break + unpacker.feed(buf) + for o in unpacker: + process(o) + + Raises ``ExtraData`` when *packed* contains extra bytes. + Raises ``OutOfData`` when *packed* is incomplete. + Raises ``FormatError`` when *packed* is not valid msgpack. + Raises ``StackError`` when *packed* contains too nested. + Other exceptions can be raised during unpacking. + """ + + def __init__( + self, + file_like=None, + *, + read_size=0, + use_list=True, + raw=False, + timestamp=0, + strict_map_key=True, + object_hook=None, + object_pairs_hook=None, + list_hook=None, + unicode_errors=None, + max_buffer_size=100 * 1024 * 1024, + ext_hook=ExtType, + max_str_len=-1, + max_bin_len=-1, + max_array_len=-1, + max_map_len=-1, + max_ext_len=-1, + ): + if unicode_errors is None: + unicode_errors = "strict" + + if file_like is None: + self._feeding = True + else: + if not callable(file_like.read): + raise TypeError("`file_like.read` must be callable") + self.file_like = file_like + self._feeding = False + + #: array of bytes fed. + self._buffer = bytearray() + #: Which position we currently reads + self._buff_i = 0 + + # When Unpacker is used as an iterable, between the calls to next(), + # the buffer is not "consumed" completely, for efficiency sake. + # Instead, it is done sloppily. To make sure we raise BufferFull at + # the correct moments, we have to keep track of how sloppy we were. + # Furthermore, when the buffer is incomplete (that is: in the case + # we raise an OutOfData) we need to rollback the buffer to the correct + # state, which _buf_checkpoint records. + self._buf_checkpoint = 0 + + if not max_buffer_size: + max_buffer_size = 2**31 - 1 + if max_str_len == -1: + max_str_len = max_buffer_size + if max_bin_len == -1: + max_bin_len = max_buffer_size + if max_array_len == -1: + max_array_len = max_buffer_size + if max_map_len == -1: + max_map_len = max_buffer_size // 2 + if max_ext_len == -1: + max_ext_len = max_buffer_size + + self._max_buffer_size = max_buffer_size + if read_size > self._max_buffer_size: + raise ValueError("read_size must be smaller than max_buffer_size") + self._read_size = read_size or min(self._max_buffer_size, 16 * 1024) + self._raw = bool(raw) + self._strict_map_key = bool(strict_map_key) + self._unicode_errors = unicode_errors + self._use_list = use_list + if not (0 <= timestamp <= 3): + raise ValueError("timestamp must be 0..3") + self._timestamp = timestamp + self._list_hook = list_hook + self._object_hook = object_hook + self._object_pairs_hook = object_pairs_hook + self._ext_hook = ext_hook + self._max_str_len = max_str_len + self._max_bin_len = max_bin_len + self._max_array_len = max_array_len + self._max_map_len = max_map_len + self._max_ext_len = max_ext_len + self._stream_offset = 0 + + if list_hook is not None and not callable(list_hook): + raise TypeError("`list_hook` is not callable") + if object_hook is not None and not callable(object_hook): + raise TypeError("`object_hook` is not callable") + if object_pairs_hook is not None and not callable(object_pairs_hook): + raise TypeError("`object_pairs_hook` is not callable") + if object_hook is not None and object_pairs_hook is not None: + raise TypeError("object_pairs_hook and object_hook are mutually exclusive") + if not callable(ext_hook): + raise TypeError("`ext_hook` is not callable") + + def feed(self, next_bytes): + assert self._feeding + view = _get_data_from_buffer(next_bytes) + if len(self._buffer) - self._buff_i + len(view) > self._max_buffer_size: + raise BufferFull + + # Strip buffer before checkpoint before reading file. + if self._buf_checkpoint > 0: + del self._buffer[: self._buf_checkpoint] + self._buff_i -= self._buf_checkpoint + self._buf_checkpoint = 0 + + # Use extend here: INPLACE_ADD += doesn't reliably typecast memoryview in jython + self._buffer.extend(view) + view.release() + + def _consume(self): + """Gets rid of the used parts of the buffer.""" + self._stream_offset += self._buff_i - self._buf_checkpoint + self._buf_checkpoint = self._buff_i + + def _got_extradata(self): + return self._buff_i < len(self._buffer) + + def _get_extradata(self): + return self._buffer[self._buff_i :] + + def read_bytes(self, n): + ret = self._read(n, raise_outofdata=False) + self._consume() + return ret + + def _read(self, n, raise_outofdata=True): + # (int) -> bytearray + self._reserve(n, raise_outofdata=raise_outofdata) + i = self._buff_i + ret = self._buffer[i : i + n] + self._buff_i = i + len(ret) + return ret + + def _reserve(self, n, raise_outofdata=True): + remain_bytes = len(self._buffer) - self._buff_i - n + + # Fast path: buffer has n bytes already + if remain_bytes >= 0: + return + + if self._feeding: + self._buff_i = self._buf_checkpoint + raise OutOfData + + # Strip buffer before checkpoint before reading file. + if self._buf_checkpoint > 0: + del self._buffer[: self._buf_checkpoint] + self._buff_i -= self._buf_checkpoint + self._buf_checkpoint = 0 + + # Read from file + remain_bytes = -remain_bytes + if remain_bytes + len(self._buffer) > self._max_buffer_size: + raise BufferFull + while remain_bytes > 0: + to_read_bytes = max(self._read_size, remain_bytes) + read_data = self.file_like.read(to_read_bytes) + if not read_data: + break + assert isinstance(read_data, bytes) + self._buffer += read_data + remain_bytes -= len(read_data) + + if len(self._buffer) < n + self._buff_i and raise_outofdata: + self._buff_i = 0 # rollback + raise OutOfData + + def _read_header(self): + typ = TYPE_IMMEDIATE + n = 0 + obj = None + self._reserve(1) + b = self._buffer[self._buff_i] + self._buff_i += 1 + if b & 0b10000000 == 0: + obj = b + elif b & 0b11100000 == 0b11100000: + obj = -1 - (b ^ 0xFF) + elif b & 0b11100000 == 0b10100000: + n = b & 0b00011111 + typ = TYPE_RAW + if n > self._max_str_len: + raise ValueError(f"{n} exceeds max_str_len({self._max_str_len})") + obj = self._read(n) + elif b & 0b11110000 == 0b10010000: + n = b & 0b00001111 + typ = TYPE_ARRAY + if n > self._max_array_len: + raise ValueError(f"{n} exceeds max_array_len({self._max_array_len})") + elif b & 0b11110000 == 0b10000000: + n = b & 0b00001111 + typ = TYPE_MAP + if n > self._max_map_len: + raise ValueError(f"{n} exceeds max_map_len({self._max_map_len})") + elif b == 0xC0: + obj = None + elif b == 0xC2: + obj = False + elif b == 0xC3: + obj = True + elif 0xC4 <= b <= 0xC6: + size, fmt, typ = _MSGPACK_HEADERS[b] + self._reserve(size) + if len(fmt) > 0: + n = struct.unpack_from(fmt, self._buffer, self._buff_i)[0] + else: + n = self._buffer[self._buff_i] + self._buff_i += size + if n > self._max_bin_len: + raise ValueError(f"{n} exceeds max_bin_len({self._max_bin_len})") + obj = self._read(n) + elif 0xC7 <= b <= 0xC9: + size, fmt, typ = _MSGPACK_HEADERS[b] + self._reserve(size) + L, n = struct.unpack_from(fmt, self._buffer, self._buff_i) + self._buff_i += size + if L > self._max_ext_len: + raise ValueError(f"{L} exceeds max_ext_len({self._max_ext_len})") + obj = self._read(L) + elif 0xCA <= b <= 0xD3: + size, fmt = _MSGPACK_HEADERS[b] + self._reserve(size) + if len(fmt) > 0: + obj = struct.unpack_from(fmt, self._buffer, self._buff_i)[0] + else: + obj = self._buffer[self._buff_i] + self._buff_i += size + elif 0xD4 <= b <= 0xD8: + size, fmt, typ = _MSGPACK_HEADERS[b] + if self._max_ext_len < size: + raise ValueError(f"{size} exceeds max_ext_len({self._max_ext_len})") + self._reserve(size + 1) + n, obj = struct.unpack_from(fmt, self._buffer, self._buff_i) + self._buff_i += size + 1 + elif 0xD9 <= b <= 0xDB: + size, fmt, typ = _MSGPACK_HEADERS[b] + self._reserve(size) + if len(fmt) > 0: + (n,) = struct.unpack_from(fmt, self._buffer, self._buff_i) + else: + n = self._buffer[self._buff_i] + self._buff_i += size + if n > self._max_str_len: + raise ValueError(f"{n} exceeds max_str_len({self._max_str_len})") + obj = self._read(n) + elif 0xDC <= b <= 0xDD: + size, fmt, typ = _MSGPACK_HEADERS[b] + self._reserve(size) + (n,) = struct.unpack_from(fmt, self._buffer, self._buff_i) + self._buff_i += size + if n > self._max_array_len: + raise ValueError(f"{n} exceeds max_array_len({self._max_array_len})") + elif 0xDE <= b <= 0xDF: + size, fmt, typ = _MSGPACK_HEADERS[b] + self._reserve(size) + (n,) = struct.unpack_from(fmt, self._buffer, self._buff_i) + self._buff_i += size + if n > self._max_map_len: + raise ValueError(f"{n} exceeds max_map_len({self._max_map_len})") + else: + raise FormatError("Unknown header: 0x%x" % b) + return typ, n, obj + + def _unpack(self, execute=EX_CONSTRUCT): + typ, n, obj = self._read_header() + + if execute == EX_READ_ARRAY_HEADER: + if typ != TYPE_ARRAY: + raise ValueError("Expected array") + return n + if execute == EX_READ_MAP_HEADER: + if typ != TYPE_MAP: + raise ValueError("Expected map") + return n + # TODO should we eliminate the recursion? + if typ == TYPE_ARRAY: + if execute == EX_SKIP: + for i in range(n): + # TODO check whether we need to call `list_hook` + self._unpack(EX_SKIP) + return + ret = newlist_hint(n) + for i in range(n): + ret.append(self._unpack(EX_CONSTRUCT)) + if self._list_hook is not None: + ret = self._list_hook(ret) + # TODO is the interaction between `list_hook` and `use_list` ok? + return ret if self._use_list else tuple(ret) + if typ == TYPE_MAP: + if execute == EX_SKIP: + for i in range(n): + # TODO check whether we need to call hooks + self._unpack(EX_SKIP) + self._unpack(EX_SKIP) + return + if self._object_pairs_hook is not None: + ret = self._object_pairs_hook( + (self._unpack(EX_CONSTRUCT), self._unpack(EX_CONSTRUCT)) for _ in range(n) + ) + else: + ret = {} + for _ in range(n): + key = self._unpack(EX_CONSTRUCT) + if self._strict_map_key and type(key) not in (str, bytes): + raise ValueError("%s is not allowed for map key" % str(type(key))) + if isinstance(key, str): + key = sys.intern(key) + ret[key] = self._unpack(EX_CONSTRUCT) + if self._object_hook is not None: + ret = self._object_hook(ret) + return ret + if execute == EX_SKIP: + return + if typ == TYPE_RAW: + if self._raw: + obj = bytes(obj) + else: + obj = obj.decode("utf_8", self._unicode_errors) + return obj + if typ == TYPE_BIN: + return bytes(obj) + if typ == TYPE_EXT: + if n == -1: # timestamp + ts = Timestamp.from_bytes(bytes(obj)) + if self._timestamp == 1: + return ts.to_unix() + elif self._timestamp == 2: + return ts.to_unix_nano() + elif self._timestamp == 3: + return ts.to_datetime() + else: + return ts + else: + return self._ext_hook(n, bytes(obj)) + assert typ == TYPE_IMMEDIATE + return obj + + def __iter__(self): + return self + + def __next__(self): + try: + ret = self._unpack(EX_CONSTRUCT) + self._consume() + return ret + except OutOfData: + self._consume() + raise StopIteration + except RecursionError: + raise StackError + + next = __next__ + + def skip(self): + self._unpack(EX_SKIP) + self._consume() + + def unpack(self): + try: + ret = self._unpack(EX_CONSTRUCT) + except RecursionError: + raise StackError + self._consume() + return ret + + def read_array_header(self): + ret = self._unpack(EX_READ_ARRAY_HEADER) + self._consume() + return ret + + def read_map_header(self): + ret = self._unpack(EX_READ_MAP_HEADER) + self._consume() + return ret + + def tell(self): + return self._stream_offset + + +class Packer: + """ + MessagePack Packer + + Usage:: + + packer = Packer() + astream.write(packer.pack(a)) + astream.write(packer.pack(b)) + + Packer's constructor has some keyword arguments: + + :param default: + When specified, it should be callable. + Convert user type to builtin type that Packer supports. + See also simplejson's document. + + :param bool use_single_float: + Use single precision float type for float. (default: False) + + :param bool autoreset: + Reset buffer after each pack and return its content as `bytes`. (default: True). + If set this to false, use `bytes()` to get content and `.reset()` to clear buffer. + + :param bool use_bin_type: + Use bin type introduced in msgpack spec 2.0 for bytes. + It also enables str8 type for unicode. (default: True) + + :param bool strict_types: + If set to true, types will be checked to be exact. Derived classes + from serializable types will not be serialized and will be + treated as unsupported type and forwarded to default. + Additionally tuples will not be serialized as lists. + This is useful when trying to implement accurate serialization + for python types. + + :param bool datetime: + If set to true, datetime with tzinfo is packed into Timestamp type. + Note that the tzinfo is stripped in the timestamp. + You can get UTC datetime with `timestamp=3` option of the Unpacker. + + :param str unicode_errors: + The error handler for encoding unicode. (default: 'strict') + DO NOT USE THIS!! This option is kept for very specific usage. + + :param int buf_size: + Internal buffer size. This option is used only for C implementation. + """ + + def __init__( + self, + *, + default=None, + use_single_float=False, + autoreset=True, + use_bin_type=True, + strict_types=False, + datetime=False, + unicode_errors=None, + buf_size=None, + ): + self._strict_types = strict_types + self._use_float = use_single_float + self._autoreset = autoreset + self._use_bin_type = use_bin_type + self._buffer = BytesIO() + self._datetime = bool(datetime) + self._unicode_errors = unicode_errors or "strict" + if default is not None and not callable(default): + raise TypeError("default must be callable") + self._default = default + + def _pack( + self, + obj, + nest_limit=DEFAULT_RECURSE_LIMIT, + check=isinstance, + check_type_strict=_check_type_strict, + ): + default_used = False + if self._strict_types: + check = check_type_strict + list_types = list + else: + list_types = (list, tuple) + while True: + if nest_limit < 0: + raise ValueError("recursion limit exceeded") + if obj is None: + return self._buffer.write(b"\xc0") + if check(obj, bool): + if obj: + return self._buffer.write(b"\xc3") + return self._buffer.write(b"\xc2") + if check(obj, int): + if 0 <= obj < 0x80: + return self._buffer.write(struct.pack("B", obj)) + if -0x20 <= obj < 0: + return self._buffer.write(struct.pack("b", obj)) + if 0x80 <= obj <= 0xFF: + return self._buffer.write(struct.pack("BB", 0xCC, obj)) + if -0x80 <= obj < 0: + return self._buffer.write(struct.pack(">Bb", 0xD0, obj)) + if 0xFF < obj <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xCD, obj)) + if -0x8000 <= obj < -0x80: + return self._buffer.write(struct.pack(">Bh", 0xD1, obj)) + if 0xFFFF < obj <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xCE, obj)) + if -0x80000000 <= obj < -0x8000: + return self._buffer.write(struct.pack(">Bi", 0xD2, obj)) + if 0xFFFFFFFF < obj <= 0xFFFFFFFFFFFFFFFF: + return self._buffer.write(struct.pack(">BQ", 0xCF, obj)) + if -0x8000000000000000 <= obj < -0x80000000: + return self._buffer.write(struct.pack(">Bq", 0xD3, obj)) + if not default_used and self._default is not None: + obj = self._default(obj) + default_used = True + continue + raise OverflowError("Integer value out of range") + if check(obj, (bytes, bytearray)): + n = len(obj) + if n >= 2**32: + raise ValueError("%s is too large" % type(obj).__name__) + self._pack_bin_header(n) + return self._buffer.write(obj) + if check(obj, str): + obj = obj.encode("utf-8", self._unicode_errors) + n = len(obj) + if n >= 2**32: + raise ValueError("String is too large") + self._pack_raw_header(n) + return self._buffer.write(obj) + if check(obj, memoryview): + n = obj.nbytes + if n >= 2**32: + raise ValueError("Memoryview is too large") + self._pack_bin_header(n) + return self._buffer.write(obj) + if check(obj, float): + if self._use_float: + return self._buffer.write(struct.pack(">Bf", 0xCA, obj)) + return self._buffer.write(struct.pack(">Bd", 0xCB, obj)) + if check(obj, (ExtType, Timestamp)): + if check(obj, Timestamp): + code = -1 + data = obj.to_bytes() + else: + code = obj.code + data = obj.data + assert isinstance(code, int) + assert isinstance(data, bytes) + L = len(data) + if L == 1: + self._buffer.write(b"\xd4") + elif L == 2: + self._buffer.write(b"\xd5") + elif L == 4: + self._buffer.write(b"\xd6") + elif L == 8: + self._buffer.write(b"\xd7") + elif L == 16: + self._buffer.write(b"\xd8") + elif L <= 0xFF: + self._buffer.write(struct.pack(">BB", 0xC7, L)) + elif L <= 0xFFFF: + self._buffer.write(struct.pack(">BH", 0xC8, L)) + else: + self._buffer.write(struct.pack(">BI", 0xC9, L)) + self._buffer.write(struct.pack("b", code)) + self._buffer.write(data) + return + if check(obj, list_types): + n = len(obj) + self._pack_array_header(n) + for i in range(n): + self._pack(obj[i], nest_limit - 1) + return + if check(obj, dict): + return self._pack_map_pairs(len(obj), obj.items(), nest_limit - 1) + + if self._datetime and check(obj, _DateTime) and obj.tzinfo is not None: + obj = Timestamp.from_datetime(obj) + default_used = 1 + continue + + if not default_used and self._default is not None: + obj = self._default(obj) + default_used = 1 + continue + + if self._datetime and check(obj, _DateTime): + raise ValueError(f"Cannot serialize {obj!r} where tzinfo=None") + + raise TypeError(f"Cannot serialize {obj!r}") + + def pack(self, obj): + try: + self._pack(obj) + except: + self._buffer = BytesIO() # force reset + raise + if self._autoreset: + ret = self._buffer.getvalue() + self._buffer = BytesIO() + return ret + + def pack_map_pairs(self, pairs): + self._pack_map_pairs(len(pairs), pairs) + if self._autoreset: + ret = self._buffer.getvalue() + self._buffer = BytesIO() + return ret + + def pack_array_header(self, n): + if n >= 2**32: + raise ValueError + self._pack_array_header(n) + if self._autoreset: + ret = self._buffer.getvalue() + self._buffer = BytesIO() + return ret + + def pack_map_header(self, n): + if n >= 2**32: + raise ValueError + self._pack_map_header(n) + if self._autoreset: + ret = self._buffer.getvalue() + self._buffer = BytesIO() + return ret + + def pack_ext_type(self, typecode, data): + if not isinstance(typecode, int): + raise TypeError("typecode must have int type.") + if not 0 <= typecode <= 127: + raise ValueError("typecode should be 0-127") + if not isinstance(data, bytes): + raise TypeError("data must have bytes type") + L = len(data) + if L > 0xFFFFFFFF: + raise ValueError("Too large data") + if L == 1: + self._buffer.write(b"\xd4") + elif L == 2: + self._buffer.write(b"\xd5") + elif L == 4: + self._buffer.write(b"\xd6") + elif L == 8: + self._buffer.write(b"\xd7") + elif L == 16: + self._buffer.write(b"\xd8") + elif L <= 0xFF: + self._buffer.write(b"\xc7" + struct.pack("B", L)) + elif L <= 0xFFFF: + self._buffer.write(b"\xc8" + struct.pack(">H", L)) + else: + self._buffer.write(b"\xc9" + struct.pack(">I", L)) + self._buffer.write(struct.pack("B", typecode)) + self._buffer.write(data) + + def _pack_array_header(self, n): + if n <= 0x0F: + return self._buffer.write(struct.pack("B", 0x90 + n)) + if n <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xDC, n)) + if n <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xDD, n)) + raise ValueError("Array is too large") + + def _pack_map_header(self, n): + if n <= 0x0F: + return self._buffer.write(struct.pack("B", 0x80 + n)) + if n <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xDE, n)) + if n <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xDF, n)) + raise ValueError("Dict is too large") + + def _pack_map_pairs(self, n, pairs, nest_limit=DEFAULT_RECURSE_LIMIT): + self._pack_map_header(n) + for k, v in pairs: + self._pack(k, nest_limit - 1) + self._pack(v, nest_limit - 1) + + def _pack_raw_header(self, n): + if n <= 0x1F: + self._buffer.write(struct.pack("B", 0xA0 + n)) + elif self._use_bin_type and n <= 0xFF: + self._buffer.write(struct.pack(">BB", 0xD9, n)) + elif n <= 0xFFFF: + self._buffer.write(struct.pack(">BH", 0xDA, n)) + elif n <= 0xFFFFFFFF: + self._buffer.write(struct.pack(">BI", 0xDB, n)) + else: + raise ValueError("Raw is too large") + + def _pack_bin_header(self, n): + if not self._use_bin_type: + return self._pack_raw_header(n) + elif n <= 0xFF: + return self._buffer.write(struct.pack(">BB", 0xC4, n)) + elif n <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xC5, n)) + elif n <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xC6, n)) + else: + raise ValueError("Bin is too large") + + def bytes(self): + """Return internal buffer contents as bytes object""" + return self._buffer.getvalue() + + def reset(self): + """Reset internal buffer. + + This method is useful only when autoreset=False. + """ + self._buffer = BytesIO() + + def getbuffer(self): + """Return view of internal buffer.""" + if _USING_STRINGBUILDER: + return memoryview(self.bytes()) + else: + return self._buffer.getbuffer() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__init__.py new file mode 100644 index 000000000..d79f73c57 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__init__.py @@ -0,0 +1,15 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "24.2" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = f"2014 {__author__}" diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f327050aa9bd139ece90e48c15fac52214ecbfe9 GIT binary patch literal 544 zcmYjOL5|Zf6iqVev>jB$nw4Q!q{KsJ76^^dz;uZaQpF}4%ZY!RuW@Y4b|NJl1#tkb zz?xHZ%bo+U!f8Rn|MHWc_5Aex{Asnih(dk;LGf1{ML#FSveEu<++$c`X&0BlykAzM(gEaX?T6l_o(*c^E+*UbRH#o1V?t-l8=LL zv?3=Fxba!zy>YK8-J@^1Dz9}*&0sivut|&SycpLTt+*6q>$}~~6Rre7C9p@)aY<^e zyv3^X+Pd+HI7)BbQJSM3pT65}vLd^aymLUp7;l4p16h$@j wE9%fg_AgUv&`=gsLfeNJDPyQGOp%-6u()Zp=p?+EO@I72KpKZmLQfd~0V_|flK=n! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58bdf98b0f738d7b8833463bef6740d57de8ea9c GIT binary patch literal 3435 zcmbVOOLN@D5ys%L3xdn#MrPTe<8qKgT-vg@Ynu-}qEbZi zfz<%Z5;x1F%2aMSCdq z>;A>ejHBWC^IzK0-)oxoSL&R6Omu##X)OCa8eHpWoN+y7J-wqdY8$c9G1TAenCfqJ zEd0&5(yMl=O#7L}Ena!7@rtkyjLsDLRc@ni3uDItXBwO-?ttTrIW=&mc@3P}m@@;; z46lPzKhQgMZrs+Iv;I$*ra7zEKXt<>7H;2{nUF2Vxzmj@w=dJ(h>Ohi-5~a}%-!6C zHC76^&y(spj6D7Hjp&tl0Pobj|^-CQs_eK)$ zL6dN$@Hr+;&K!ez~Zpyik}JYrpX1jo9F%PLc6^kWqE5m%DaQDR*?4E@m`kk zl()m>kC)G$_r*DWela+=vV7KGUS3)9MHqY%hFgKJHrvH=LLMuVBFy?C$Xaof3?7B) zAmM%z1TUVb8fO?+E0=+S97ur^!4={0S%pe z;OR095F?teG7>Wmxh}+l1u87khDPk@3-(}I*83w z|IM49gV6Ej5%)S`o?_7~s;lc)Db5bbQ-`E&qiIPCe_0_?1?d=97e4`6SvuZ+*_t8| zx`^-K%g893nM`s1FKa5R>(~6;m(!$|j_h^DT+g;T#%I~qYmJ$~r1$V08$+Bo8{=a1 zRo4It%ua<{ohq-uP1V9){mc)#Q6dfvOgMa-$Y~;%i2Mm;qiMhFHgkRP+)Bs1y?Fkk zj&WJ`I@aBDD=SN#%H_*4c!M+RQ1v5x8F`hQS-z6^uk7Lo1pc9ZOVrjn6z`yv}DIn+FX0Y+#??LpYRRGi*67p3g;JiVPvGZkd~gufk3# zIGkF)@#WTDF0%WDxsfIU_lt!^MT&eNlYruC@=!$4=B^StuOaM4Y@~VGus+hBGR~f{ zef?Kj(lWJN-!UFI@(R=T4TkaD{8DC+18JNCr{KmMeeW4}#T`jjpI@h@IJAGa| zVEg9C%?2I6Oo98(GiTJ!ck8^{**9qbPjf z&0?*OCcFOwdtv{xZ_>^+9A9IjFg{+nGJJoKsDuFISINaiLP_!{i~@CxhZHvIe*6}v zJ)Dh740L<`b`)H259#tZjp6jgwY7D6*4EaBQx{beYrk}+Ski45NvBvJ(F zb`lMd$^su?8s&TL>EUeCO{FW6AmxBo_w;kt9JXI}Td%zGhN*)r~KG# z6uK7{mPd)SsARbu1bJZzDO0Hcxv&X9|Ufuqc@o$+uo=mDP047ouPDixMG?1G8HQU?ly)I!WVftQ5a zl<=Y?$~~`zUUm~iV-;Ozb#{uq#~RFLb=_f(u3I`&@4rpU`PZpR1HHNb>W2QGIZLnE z|DZ8bNf0eR^PgsIjtv`QIB89mDh#-3IGYbpXncv<^;z-tpQw6m@`JccX9XaohcyAO{UTNh4#=XQEsGTuEW{9Hm`9V_!d5N zz=1EuBS5!#r8S>^O4y`7of(gq<;XMkw9bJ+I`rN~ZA-~*xTdh4@cs_U!oHt(Q5y!S z{|`uR=Np_9unuF8(4m&h=Sy&q@r=M1TFwy>n1YzujY#>#Wa zgm@sE)c=qOIbZ&mh)aZWp!@)&(9^82!;Hfs)Q@M0W#J^Ee$!A#iHJ|QcvP4uAN`k( zw3t@)Es_|jTn|$-x+9uZXWvJtHt;$KcAdS=K48PQCdYkZ&Z}9ysCphx1J9F-(4@>R zY*g)iA@eP^eqjn+Z+Q{T zabV|reZZBZP%E5Y`td+0%qY>Ox}M~#G=Z|Kq_Rg<4Qh$k*;LpU%QE*8g>gofm`+W% Paq(?dGaBY83$Oh@XYv@% literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9031ae89e169b5ac656dbe6bc93fb5849c435dc GIT binary patch literal 6488 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 J==WXP{$IDIY_b3V literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a4c7ae4d622c34b16780346b9a61277d69eca79 GIT binary patch literal 3416 zcmaJ@TXWRN6;?}{n>+?%o9n`+ca!Wg37$#VK#0plu@%@Ys)*wN%aYlpWLe!amPaGW z-7T=E#xF6+8x``rNtOM$e<6>lRDOhB^Tc1^CHcBFV+^69sZrmL&gJXxoUdkQJqyp@ z|JjNDgxrX$-L;ACJF(MsOzU=C(^k3_(^k7xv~FA* z*1Pp#quUsIT@Pm}tQyY@o82a{zO-14)t_6e&Kvtqca}An$7cR&vnHE;?siXbXX~Vy zJAt{AZ0@Bq18RjPam9NXAg<| zhdRs7?UO$cevX~zr}v>ddKdT{uWp^+CzJ69>>|7LLa&|T?!g82A^QO&e84EH9NO%o z^VXrWPi|XJ=DQd9Iew9ST<(5JwyVr}V6}cYdbxIIjb6WYoyt7RQlZ+O_i!_kbeOU{ z<}?~+F(2|oNgAj9D5NT-j4K{08uh5!?YBNn}B)vFF2At8L zA4G{%bePNd3JnrQpKS)^kUr%?MrJq7C1-8#m5ybL6f;4Rq$*HYl&dYLsNYpw1S%DU zw;BvNdzeGBqW*wy=R67dD+F5~tKGTx#TQY`LoMd`<6v;hzycj^bqSzhe+58rhuJG- z>!5YvmP*m|e*2z9lrm)CFbyb^LoA4*-VGL!$*7&@wjuFKjm?nX>WJ#1sk%H8AmQYv1U|Nb#i%%R!&xXq0t)>|&|tl#_@|$JY@+Q1YF2wkzsT zWFCL%;^mP#F*AiDl^87?x1WybYMSttT~yOtWw|Pdhq2VgRxR6dtGn#ThCwWEqBA}BUcIE&>kNcJs}ou)?W#u>v9@h(Zi}n}*A+x1%EW1GdsnZP*@=-|HPVuNf<8cZs5Mn6(;GXsDAd2WirEnw?w>zegD z>%R5a`r1k^x|VVjP}Xw>p14I23S#}Q-%R_X*lM zb6Mquv*)PB*xB;-2?AIR*5TgUC2bz@<V-Pon! zCJzUiV>R>2jE7M#0>QpMK$mP@D#g3r+0? z#WVujmlvJl7!9UM8BrRCNxx0+mh?Y4t1|}7xMCEWoPCz&F{2w$6fCDUwc2qs;LFRU zZQ4F#I(RIkmv?Sl_pkq=Wxk=8ruh4L`)d2@(d_Msacd&&i(p9a8P&})fVZ(KE+NPd zHB(6in?&I%zN1<*g_~wP5jCt9GhlNrGEf*7MeVl_?g#;1Z#>-1c-i$H1#v#@%X}jf zX~-qujUW~7+TFD~27KZ&q_te}i55zFg&niP)qCGX^(nv4)mz-~5spaBy;jpEO>&Mb zf_^ow7m2ur+0pDYuYCi5KN-zVlb;#Fyhm_JaL)m($yVh%QaT!dalVhfRN zxNIN6;344Oiv(&4eKF)_5XTy5=4){(ITj`#4{$Vy%~z&!Oa(I84ILn(cO-bKqqc9lQb z+50>NBaSqK!uqxtJ(k`$dFsqEA zgs22|Wr^;J2$DWGlyAr%sJ{+y6?N<|z%E=JsdB?nfm5c<*{M%`jgK2RCYyL!GrHt( z@t-H+rV)lJ3WcKYkE*4WqXsHK&13n_sG_ATk1ASGG+Na1YeJD39aK3Z_nY?86Y(pE zc}yX;>C&ySU>YtdGn~#uMI#ou9|mA(<2+-M(1sQbI4Mk%Ueqs|nh2);09n8$ zBv1tq%4d<9X literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f30d09f0679da4a3470e2a25408f4ffec8fab2e GIT binary patch literal 9061 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 R84&SPO^_@qYgwzZ_6s%g5*+{l literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ca82b865db35b1fba9ef70ec7468341371556cc GIT binary patch literal 5704 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed7000aa4ac352199e5c28f1a34c971663e0581e GIT binary patch literal 7793 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 W&C*Y6Cv2;X|39HsluKR2zDGDMr`S z#(mcBd`pq_dD)7yEVr>u@~r5K(t9^ME*p2OHnGjkx4d%Nn#HEj(&&ft#OSHcELLva zrAc%DIJ5B=?-`RDtPlPinoUvDG)|L+mih~8V)-ae=eoeU_WhY2CkF|ZYPZig z&hji7SI${H)nkfn{h=^!6LMV$nvJ+(Bcb@nQvzr4%HEv}+b8PYMSzPxg%O^2vnZ)- z(OUG;=W!7o>h>F8}o+|hpXcO5yy8GVU=;n>L_uMAKzF#(^q|l@+Ln)`Z znx`bUrF=M#)0L$zrOL-rzF=kxfx6-JZ-c{pst0v2#op}Lv=PcCzJbdf8e}+CN=QMe%yF_ClA0 zGs}YnCtkNlg*7-g@{GJCwun%yw%Cb^`S>tGo+&xd&Wdiw?e@XF_jjT=Q;{pqVlTQE zCxtGemkTM%GsLqWtMqvH05z?tO3wShZB_+P)k^6>EB#@fX|zLc%~e)zWni?On`}(! zID3d1h&+BrDR=^5(YEY_FZpBf5zB7)%<`kq+~Vx9$JwH1h4oInCG02O-{El+6!O^n z()*bGm1W{%b|NPJ36nm4YqOzZkZU%8R-b+PfMFGrb=!d6baK;qgwR99j+*9c+j`cv zt>@$y+6|f~zey-Nirr@~_^`iO_9Sq=ObVus$X$JzZAhh0(3jraq(DX6GN`~dbkeeU zo{=(`I?^x=nnx35`g18wleC7cYz11-FLZifUZv^4{kJN)zUVa5s}{&4ORSVXhgQ6X zfpNjxyvrleaKCLH@DS2sd#&>J_qjh}+RAj~3#$QNM0}f{03gk|$Xi6uGflnV(|H<;DD% zq@Q8vdxl_gM=ZNk-LZvh#n{DFXj>BOV<_#m_!X9|&<#WYbUPT^_;!FU%`C28Lib{2 zpI2ZKr=L9}*YI`aPiX{zfbtm?op=riaa1uZDc(0>HM=xWUS9C(0OxdD6#;4p$&(WM zRkX+W;@YKnD*O0q5R_FH1d*n~-y>_{sYfrF;i7YEZHr&NvGCsLzf)l}fcI6j@WL>9 zh9Lg17Q~g3s~k!X)b&y*!C5GjH;@6F@QFA;NY6Z1IyEY-uf-DB7%KCAQ2K{)aacB` z9H((n;Qu!;Ux9`DNQIdBKe5tMDeu6optGZCMDV>3o$ti=R~w~rHH1~^@8@|6qO0Zh zP@9+Agh=ij)O%2f)+}F&qGR#y292?azvTGk^m`=oyO^m#tF7b!amP|Cl+ zR1p0z@Q3eh^AG@q-w7}LukgeFEP89@0Y0H6!+uyLp+no!TiDPQ#{7Z?SH(9-qHZ!b zY4{Bd7k}?0Y6y3tS0EI$Q-k~M?q;{w@$hLl+pxb)H zYz208%xMgjmwBD0LTU3Oo#Cct9D$eh73DfUu4p(sC+nhO@7mlZY2hF(!w>0d=Wc)% f`4jp-bcnKXtGYS8L$SEaTw8QSdqvA_uN(XicMH5f literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aaf081f27755bfdaea6f98991eb7d49a562d2984 GIT binary patch literal 31282 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

        jDp}N*9@xGQQ&^X8tUMld66UeaBnQ0u5 zmqM~MBvYQ9`)}zGaAoW2n_TS6xZlw@f?m>jfeLIO5=_ctt~Sn3a6 z{X0I=Z0G-bUj8F5)Ku@YWr4Q9zYjSwID#8&Stw!S-=9xCjZFv>+?X)FYkUlw5$uWm zJ_Cibi0Z-mlTWB4?xt2j!ESz)n#Z39*?AD}{X z4KEbK{30)qaS-Od*{(MNbChpWqlDxA7vAc!_s1i9h7sE?mN)S6)5!c?9>% z-{O+^82`I``L}tw!pnQSZ1TdON@f2{=2Gq`bB3i8>TjK%WG4ekp!uOu&LjRkj!<~a gB>W8%xryS$=+q}Be#KD#rbcoTyC=3`!^W=v4G6Ex5dZ)H literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec932f1243d39ee8ebad69a2156a1654b2cb571d GIT binary patch literal 14964 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

        a%{;q%4+6~Ol44wqF! z35llNFZq`gyiNgqmHcZ2@L_PmZp4!R7OyVQcoCLpIY=v|0CjfG1<(un(P)v}P);5( z+KHfDBQOU8yGB4Ac??ce-rH|Ml0tLE+Ht7TdzO_??HkKPGspii;=th~Ege*CdKz4x8w*kN858i@11fOZl$Q7i653=+84-w~{VHytV1^G=sL%qO7iL7c~h4%q9VIt3(Sqd5VGl0)XCV;anM)xhZryq=Bi^u@Psq zFjednHAMEkw4T^s{d@bSY>!7X{*Nrmq@>k<;2d%-b9l>6?PT?&RqZ)e2>Yom=|8fl z+_8zqb@cfvPtjT6rrm7A9S){OdYezV!4`8{$ywb&3>fd#%}Bxpa_ybron;6O$-CMk zB`wPH9(xqk8n}P^-A9n5gyb+XO(wInV$Hs zDP&|W$RSt4vHJM$AIk+CtUp(&j1($Xr7~O`8O18owf|>ijDI}#LiyRgr>80v g^>p~?;o|VqXynnz4Gupwa;R*T%jJQ?rHb|c0g-Tt>;M1& literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a14dde0403891c6ec7cd9f76af02a7ff0b291a74 GIT binary patch literal 4628 zcmbVP&2!tv6~_V~2vQV9*|IGqjtwVDB+4SCINwg{IF0Sp(^kpE&PQp>7)ZO6V1WR; z04)nc<`B7^>7g^dC!LXwx%Qa$*gv3WTzm4rkm*$Yz2%2wHExoUgT>q3w{PFReZTh} zJ{cb`YWRKo_a*<^lbZHV>Ky%9=v>t_7T;r<#x>3Yt;HIQ5v>P$LsxI3Vc=~9W-Hgo zwXBBK$~W>YyJ3URvi>?5;r zUg-B7{v1CC-t*u+#U1co;Lks@c3IX~ud+sF!SPsYMcyp5`nm2ZX*yujz53KCO;l1RmtFM_3O;(-XFc1wiG z(yzR=c&WXUY(`7>TfQ^ej;jpT?Ll$P18r@=EbJi{|k|NlP2Epmy`8SWiFLrl{@ z;S4|EH%~v*864u=kwZ~Gt=^lz^Iom7TzhM^J0u40x?5Xa7>-qEQ|nI6t*>58m$1aE zrTrxXVmMM;oqzX2tyZlr3|X+SFn^a{tj^aLsy6_5TLINeL}mafnYk840Z?Vq4NEe5 zW=WBBWVlX1q#wNvJSLv^37D^g=rVaW(e_wZ+h_6F1OV-7U+I6=ciERLG4@QZV@BW0 z?Q1zm4fvy<++7e=AK zt^&t^!H7F+SuTjSg_M*$WEoA>kk1mWj*^4OjyU3DmUGqjaMwu*n0TFWD~=6=K>mgg4LnU7t|&u(}@ zERLLSd9HuHL2NxdnkNPF5@AkorxSWXBBbCysA)5boHbA4Nr(C#9a5UZg=feIN@ze= ziB?W=%>AZDw|eA`E`z)8fM?_a=n)2n5rGh+FmMS(TI#z`Pyb8{r%f$kTSYm`5Iz{( z%F=$L-O@hS61}T;8E1c_XrQmiIjjU^$MBL=9;%HnFw!)$RH4C-gP6Iu!5*uUQrB4t zuq`sVh_Up|gi4n?Pu3ZW&HyEg&d)o3z){02I!&NQgt18Opu4&X)Y`*r_9Sc|RwEs0 zJ9Xk;zy@7y4^=AS-*mz#-VNhlmnv4{cc?3S#G`X_pY<3{o!iRGYv8qx^6nYj#9V$F z@v;Z&a2x9kUg#QIR{StA_j0`0H4^i_wU5eo%a*@~gt0>jh^@vh>zQdKv9Y=|1L*d0 zxPwc$g97g0A5806OiQP*ZhW||Yki4(dJUTPtS_w3aXX>@Il=%hZ|Q%69U%!-axb40 z_j27l^tZb2M~yJk1nEp&@b6Mk@VB{l2Yn?d@@5F5uTi+S-d!97t;*Xkf$uL)<47R*&IJCFZ$ zaBv`>!^Kqf#}w8c=g_3fPZPB*BNTjLr?rMSgXjhKN@9{jVNsH6XS(aAWB2ad-M-7~ z?&`v9dU`0VEu;HlJyDhAWk=>|r-k9Riumz~r^N_3Q; zKgN*`s+G(}nHO*S$)+lavfMhVt0c?C?Z8hmT?&;^viv72H{bv8+I5w*4xyA)T(tq! z=z|1ASu(SS{+}5>N!yT7r!5PZs*cGTaai1oH$|M~lx?yCWh0m9e(EA@W=v$ozId9* z#`?5M*;!6?G7}re$8kFac6>J|Te(B9-GjvV8XCG~CkFcBnB0s`zu4^?Zs^(YHd_D1GeIo^|Qoo&E9eU`o zV)uJt)n7U1L=qUoYQmU;S33RuV&J(W2pr$wzadb~%U5CH%)m#5q#8IYO7b9|zSxd= zaMkYnV}bm{jT6z5@2a)2XlJDXtd2RRyb6w|IGxgNhKfm*B0wM4yAr4$?I~2PFlKtkCR)>j?_!SiV7hE7@tI43P#pl0uTCkkqeH z$;%}yo}Sq(jY%)VxK_EHzX~X5|>_1p=QwZB=70(4dX78{a}GrYVam6sZ#* zF{Y{-RK04y)BhsALMMyqMoYR4FixX*p3P2JPu9&8-A571f K?3qI4;(q|d;hr-9 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82e5833731a9ac49984020a06834f293eded9cf0 GIT binary patch literal 15149 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_elffile.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_elffile.py new file mode 100644 index 000000000..25f4282cc --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_elffile.py @@ -0,0 +1,110 @@ +""" +ELF file parser. + +This provides a class ``ELFFile`` that parses an ELF executable in a similar +interface to ``ZipFile``. Only the read interface is implemented. + +Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca +ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html +""" + +from __future__ import annotations + +import enum +import os +import struct +from typing import IO + + +class ELFInvalid(ValueError): + pass + + +class EIClass(enum.IntEnum): + C32 = 1 + C64 = 2 + + +class EIData(enum.IntEnum): + Lsb = 1 + Msb = 2 + + +class EMachine(enum.IntEnum): + I386 = 3 + S390 = 22 + Arm = 40 + X8664 = 62 + AArc64 = 183 + + +class ELFFile: + """ + Representation of an ELF executable. + """ + + def __init__(self, f: IO[bytes]) -> None: + self._f = f + + try: + ident = self._read("16B") + except struct.error as e: + raise ELFInvalid("unable to parse identification") from e + magic = bytes(ident[:4]) + if magic != b"\x7fELF": + raise ELFInvalid(f"invalid magic: {magic!r}") + + self.capacity = ident[4] # Format for program header (bitness). + self.encoding = ident[5] # Data structure encoding (endianness). + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, self._p_fmt, self._p_idx = { + (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. + (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. + }[(self.capacity, self.encoding)] + except KeyError as e: + raise ELFInvalid( + f"unrecognized capacity ({self.capacity}) or " + f"encoding ({self.encoding})" + ) from e + + try: + ( + _, + self.machine, # Architecture type. + _, + _, + self._e_phoff, # Offset of program header. + _, + self.flags, # Processor-specific flags. + _, + self._e_phentsize, # Size of section. + self._e_phnum, # Number of sections. + ) = self._read(e_fmt) + except struct.error as e: + raise ELFInvalid("unable to parse machine and section information") from e + + def _read(self, fmt: str) -> tuple[int, ...]: + return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) + + @property + def interpreter(self) -> str | None: + """ + The path recorded in the ``PT_INTERP`` section header. + """ + for index in range(self._e_phnum): + self._f.seek(self._e_phoff + self._e_phentsize * index) + try: + data = self._read(self._p_fmt) + except struct.error: + continue + if data[self._p_idx[0]] != 3: # Not PT_INTERP. + continue + self._f.seek(data[self._p_idx[1]]) + return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") + return None diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_manylinux.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_manylinux.py new file mode 100644 index 000000000..61339a6fc --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_manylinux.py @@ -0,0 +1,263 @@ +from __future__ import annotations + +import collections +import contextlib +import functools +import os +import re +import sys +import warnings +from typing import Generator, Iterator, NamedTuple, Sequence + +from ._elffile import EIClass, EIData, ELFFile, EMachine + +EF_ARM_ABIMASK = 0xFF000000 +EF_ARM_ABI_VER5 = 0x05000000 +EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + +# `os.PathLike` not a generic type until Python 3.9, so sticking with `str` +# as the type for `path` until then. +@contextlib.contextmanager +def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]: + try: + with open(path, "rb") as f: + yield ELFFile(f) + except (OSError, TypeError, ValueError): + yield None + + +def _is_linux_armhf(executable: str) -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + with _parse_elf(executable) as f: + return ( + f is not None + and f.capacity == EIClass.C32 + and f.encoding == EIData.Lsb + and f.machine == EMachine.Arm + and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5 + and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD + ) + + +def _is_linux_i686(executable: str) -> bool: + with _parse_elf(executable) as f: + return ( + f is not None + and f.capacity == EIClass.C32 + and f.encoding == EIData.Lsb + and f.machine == EMachine.I386 + ) + + +def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool: + if "armv7l" in archs: + return _is_linux_armhf(executable) + if "i686" in archs: + return _is_linux_i686(executable) + allowed_archs = { + "x86_64", + "aarch64", + "ppc64", + "ppc64le", + "s390x", + "loongarch64", + "riscv64", + } + return any(arch in allowed_archs for arch in archs) + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> str | None: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # Should be a string like "glibc 2.17". + version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.rsplit() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> str | None: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> str | None: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + f"Expected glibc version with 2 components major.minor," + f" got: {version_str}", + RuntimeWarning, + stacklevel=2, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache +def _get_glibc_version() -> tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(archs: Sequence[str]) -> Iterator[str]: + """Generate manylinux tags compatible to the current platform. + + :param archs: Sequence of compatible architectures. + The first one shall be the closest to the actual architecture and be the part of + platform tag after the ``linux_`` prefix, e.g. ``x86_64``. + The ``linux_`` prefix is assumed as a prerequisite for the current platform to + be manylinux-compatible. + + :returns: An iterator of compatible manylinux tags. + """ + if not _have_compatible_abi(sys.executable, archs): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if set(archs) & {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for arch in archs: + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(arch, glibc_version): + yield f"{tag}_{arch}" + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(arch, glibc_version): + yield f"{legacy_tag}_{arch}" diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_musllinux.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_musllinux.py new file mode 100644 index 000000000..d2bf30b56 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_musllinux.py @@ -0,0 +1,85 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +from __future__ import annotations + +import functools +import re +import subprocess +import sys +from typing import Iterator, NamedTuple, Sequence + +from ._elffile import ELFFile + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> _MuslVersion | None: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache +def _get_musl_version(executable: str) -> _MuslVersion | None: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + try: + with open(executable, "rb") as f: + ld = ELFFile(f).interpreter + except (OSError, TypeError, ValueError): + return None + if ld is None or "musl" not in ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(archs: Sequence[str]) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param archs: Sequence of compatible architectures. + The first one shall be the closest to the actual architecture and be the part of + platform tag after the ``linux_`` prefix, e.g. ``x86_64``. + The ``linux_`` prefix is assumed as a prerequisite for the current platform to + be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for arch in archs: + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_parser.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_parser.py new file mode 100644 index 000000000..c1238c06e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_parser.py @@ -0,0 +1,354 @@ +"""Handwritten parser of dependency specifiers. + +The docstring for each __parse_* function contains EBNF-inspired grammar representing +the implementation. +""" + +from __future__ import annotations + +import ast +from typing import NamedTuple, Sequence, Tuple, Union + +from ._tokenizer import DEFAULT_RULES, Tokenizer + + +class Node: + def __init__(self, value: str) -> None: + self.value = value + + def __str__(self) -> str: + return self.value + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" + + def serialize(self) -> str: + raise NotImplementedError + + +class Variable(Node): + def serialize(self) -> str: + return str(self) + + +class Value(Node): + def serialize(self) -> str: + return f'"{self}"' + + +class Op(Node): + def serialize(self) -> str: + return str(self) + + +MarkerVar = Union[Variable, Value] +MarkerItem = Tuple[MarkerVar, Op, MarkerVar] +MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]] +MarkerList = Sequence[Union["MarkerList", MarkerAtom, str]] + + +class ParsedRequirement(NamedTuple): + name: str + url: str + extras: list[str] + specifier: str + marker: MarkerList | None + + +# -------------------------------------------------------------------------------------- +# Recursive descent parser for dependency specifier +# -------------------------------------------------------------------------------------- +def parse_requirement(source: str) -> ParsedRequirement: + return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES)) + + +def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement: + """ + requirement = WS? IDENTIFIER WS? extras WS? requirement_details + """ + tokenizer.consume("WS") + + name_token = tokenizer.expect( + "IDENTIFIER", expected="package name at the start of dependency specifier" + ) + name = name_token.text + tokenizer.consume("WS") + + extras = _parse_extras(tokenizer) + tokenizer.consume("WS") + + url, specifier, marker = _parse_requirement_details(tokenizer) + tokenizer.expect("END", expected="end of dependency specifier") + + return ParsedRequirement(name, url, extras, specifier, marker) + + +def _parse_requirement_details( + tokenizer: Tokenizer, +) -> tuple[str, str, MarkerList | None]: + """ + requirement_details = AT URL (WS requirement_marker?)? + | specifier WS? (requirement_marker)? + """ + + specifier = "" + url = "" + marker = None + + if tokenizer.check("AT"): + tokenizer.read() + tokenizer.consume("WS") + + url_start = tokenizer.position + url = tokenizer.expect("URL", expected="URL after @").text + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + tokenizer.expect("WS", expected="whitespace after URL") + + # The input might end after whitespace. + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + marker = _parse_requirement_marker( + tokenizer, span_start=url_start, after="URL and whitespace" + ) + else: + specifier_start = tokenizer.position + specifier = _parse_specifier(tokenizer) + tokenizer.consume("WS") + + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + marker = _parse_requirement_marker( + tokenizer, + span_start=specifier_start, + after=( + "version specifier" + if specifier + else "name and no valid version specifier" + ), + ) + + return (url, specifier, marker) + + +def _parse_requirement_marker( + tokenizer: Tokenizer, *, span_start: int, after: str +) -> MarkerList: + """ + requirement_marker = SEMICOLON marker WS? + """ + + if not tokenizer.check("SEMICOLON"): + tokenizer.raise_syntax_error( + f"Expected end or semicolon (after {after})", + span_start=span_start, + ) + tokenizer.read() + + marker = _parse_marker(tokenizer) + tokenizer.consume("WS") + + return marker + + +def _parse_extras(tokenizer: Tokenizer) -> list[str]: + """ + extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)? + """ + if not tokenizer.check("LEFT_BRACKET", peek=True): + return [] + + with tokenizer.enclosing_tokens( + "LEFT_BRACKET", + "RIGHT_BRACKET", + around="extras", + ): + tokenizer.consume("WS") + extras = _parse_extras_list(tokenizer) + tokenizer.consume("WS") + + return extras + + +def _parse_extras_list(tokenizer: Tokenizer) -> list[str]: + """ + extras_list = identifier (wsp* ',' wsp* identifier)* + """ + extras: list[str] = [] + + if not tokenizer.check("IDENTIFIER"): + return extras + + extras.append(tokenizer.read().text) + + while True: + tokenizer.consume("WS") + if tokenizer.check("IDENTIFIER", peek=True): + tokenizer.raise_syntax_error("Expected comma between extra names") + elif not tokenizer.check("COMMA"): + break + + tokenizer.read() + tokenizer.consume("WS") + + extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma") + extras.append(extra_token.text) + + return extras + + +def _parse_specifier(tokenizer: Tokenizer) -> str: + """ + specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS + | WS? version_many WS? + """ + with tokenizer.enclosing_tokens( + "LEFT_PARENTHESIS", + "RIGHT_PARENTHESIS", + around="version specifier", + ): + tokenizer.consume("WS") + parsed_specifiers = _parse_version_many(tokenizer) + tokenizer.consume("WS") + + return parsed_specifiers + + +def _parse_version_many(tokenizer: Tokenizer) -> str: + """ + version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)? + """ + parsed_specifiers = "" + while tokenizer.check("SPECIFIER"): + span_start = tokenizer.position + parsed_specifiers += tokenizer.read().text + if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True): + tokenizer.raise_syntax_error( + ".* suffix can only be used with `==` or `!=` operators", + span_start=span_start, + span_end=tokenizer.position + 1, + ) + if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True): + tokenizer.raise_syntax_error( + "Local version label can only be used with `==` or `!=` operators", + span_start=span_start, + span_end=tokenizer.position, + ) + tokenizer.consume("WS") + if not tokenizer.check("COMMA"): + break + parsed_specifiers += tokenizer.read().text + tokenizer.consume("WS") + + return parsed_specifiers + + +# -------------------------------------------------------------------------------------- +# Recursive descent parser for marker expression +# -------------------------------------------------------------------------------------- +def parse_marker(source: str) -> MarkerList: + return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES)) + + +def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList: + retval = _parse_marker(tokenizer) + tokenizer.expect("END", expected="end of marker expression") + return retval + + +def _parse_marker(tokenizer: Tokenizer) -> MarkerList: + """ + marker = marker_atom (BOOLOP marker_atom)+ + """ + expression = [_parse_marker_atom(tokenizer)] + while tokenizer.check("BOOLOP"): + token = tokenizer.read() + expr_right = _parse_marker_atom(tokenizer) + expression.extend((token.text, expr_right)) + return expression + + +def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom: + """ + marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS? + | WS? marker_item WS? + """ + + tokenizer.consume("WS") + if tokenizer.check("LEFT_PARENTHESIS", peek=True): + with tokenizer.enclosing_tokens( + "LEFT_PARENTHESIS", + "RIGHT_PARENTHESIS", + around="marker expression", + ): + tokenizer.consume("WS") + marker: MarkerAtom = _parse_marker(tokenizer) + tokenizer.consume("WS") + else: + marker = _parse_marker_item(tokenizer) + tokenizer.consume("WS") + return marker + + +def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem: + """ + marker_item = WS? marker_var WS? marker_op WS? marker_var WS? + """ + tokenizer.consume("WS") + marker_var_left = _parse_marker_var(tokenizer) + tokenizer.consume("WS") + marker_op = _parse_marker_op(tokenizer) + tokenizer.consume("WS") + marker_var_right = _parse_marker_var(tokenizer) + tokenizer.consume("WS") + return (marker_var_left, marker_op, marker_var_right) + + +def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: + """ + marker_var = VARIABLE | QUOTED_STRING + """ + if tokenizer.check("VARIABLE"): + return process_env_var(tokenizer.read().text.replace(".", "_")) + elif tokenizer.check("QUOTED_STRING"): + return process_python_str(tokenizer.read().text) + else: + tokenizer.raise_syntax_error( + message="Expected a marker variable or quoted string" + ) + + +def process_env_var(env_var: str) -> Variable: + if env_var in ("platform_python_implementation", "python_implementation"): + return Variable("platform_python_implementation") + else: + return Variable(env_var) + + +def process_python_str(python_str: str) -> Value: + value = ast.literal_eval(python_str) + return Value(str(value)) + + +def _parse_marker_op(tokenizer: Tokenizer) -> Op: + """ + marker_op = IN | NOT IN | OP + """ + if tokenizer.check("IN"): + tokenizer.read() + return Op("in") + elif tokenizer.check("NOT"): + tokenizer.read() + tokenizer.expect("WS", expected="whitespace after 'not'") + tokenizer.expect("IN", expected="'in' after 'not'") + return Op("not in") + elif tokenizer.check("OP"): + return Op(tokenizer.read().text) + else: + return tokenizer.raise_syntax_error( + "Expected marker operator, one of " + "<=, <, !=, ==, >=, >, ~=, ===, in, not in" + ) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_structures.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_structures.py new file mode 100644 index 000000000..90a6465f9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_structures.py @@ -0,0 +1,61 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + + +class InfinityType: + def __repr__(self) -> str: + return "Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return False + + def __le__(self, other: object) -> bool: + return False + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return True + + def __ge__(self, other: object) -> bool: + return True + + def __neg__(self: object) -> "NegativeInfinityType": + return NegativeInfinity + + +Infinity = InfinityType() + + +class NegativeInfinityType: + def __repr__(self) -> str: + return "-Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return True + + def __le__(self, other: object) -> bool: + return True + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return False + + def __ge__(self, other: object) -> bool: + return False + + def __neg__(self: object) -> InfinityType: + return Infinity + + +NegativeInfinity = NegativeInfinityType() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_tokenizer.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_tokenizer.py new file mode 100644 index 000000000..89d041605 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/_tokenizer.py @@ -0,0 +1,194 @@ +from __future__ import annotations + +import contextlib +import re +from dataclasses import dataclass +from typing import Iterator, NoReturn + +from .specifiers import Specifier + + +@dataclass +class Token: + name: str + text: str + position: int + + +class ParserSyntaxError(Exception): + """The provided source text could not be parsed correctly.""" + + def __init__( + self, + message: str, + *, + source: str, + span: tuple[int, int], + ) -> None: + self.span = span + self.message = message + self.source = source + + super().__init__() + + def __str__(self) -> str: + marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^" + return "\n ".join([self.message, self.source, marker]) + + +DEFAULT_RULES: dict[str, str | re.Pattern[str]] = { + "LEFT_PARENTHESIS": r"\(", + "RIGHT_PARENTHESIS": r"\)", + "LEFT_BRACKET": r"\[", + "RIGHT_BRACKET": r"\]", + "SEMICOLON": r";", + "COMMA": r",", + "QUOTED_STRING": re.compile( + r""" + ( + ('[^']*') + | + ("[^"]*") + ) + """, + re.VERBOSE, + ), + "OP": r"(===|==|~=|!=|<=|>=|<|>)", + "BOOLOP": r"\b(or|and)\b", + "IN": r"\bin\b", + "NOT": r"\bnot\b", + "VARIABLE": re.compile( + r""" + \b( + python_version + |python_full_version + |os[._]name + |sys[._]platform + |platform_(release|system) + |platform[._](version|machine|python_implementation) + |python_implementation + |implementation_(name|version) + |extra + )\b + """, + re.VERBOSE, + ), + "SPECIFIER": re.compile( + Specifier._operator_regex_str + Specifier._version_regex_str, + re.VERBOSE | re.IGNORECASE, + ), + "AT": r"\@", + "URL": r"[^ \t]+", + "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b", + "VERSION_PREFIX_TRAIL": r"\.\*", + "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*", + "WS": r"[ \t]+", + "END": r"$", +} + + +class Tokenizer: + """Context-sensitive token parsing. + + Provides methods to examine the input stream to check whether the next token + matches. + """ + + def __init__( + self, + source: str, + *, + rules: dict[str, str | re.Pattern[str]], + ) -> None: + self.source = source + self.rules: dict[str, re.Pattern[str]] = { + name: re.compile(pattern) for name, pattern in rules.items() + } + self.next_token: Token | None = None + self.position = 0 + + def consume(self, name: str) -> None: + """Move beyond provided token name, if at current position.""" + if self.check(name): + self.read() + + def check(self, name: str, *, peek: bool = False) -> bool: + """Check whether the next token has the provided name. + + By default, if the check succeeds, the token *must* be read before + another check. If `peek` is set to `True`, the token is not loaded and + would need to be checked again. + """ + assert ( + self.next_token is None + ), f"Cannot check for {name!r}, already have {self.next_token!r}" + assert name in self.rules, f"Unknown token name: {name!r}" + + expression = self.rules[name] + + match = expression.match(self.source, self.position) + if match is None: + return False + if not peek: + self.next_token = Token(name, match[0], self.position) + return True + + def expect(self, name: str, *, expected: str) -> Token: + """Expect a certain token name next, failing with a syntax error otherwise. + + The token is *not* read. + """ + if not self.check(name): + raise self.raise_syntax_error(f"Expected {expected}") + return self.read() + + def read(self) -> Token: + """Consume the next token and return it.""" + token = self.next_token + assert token is not None + + self.position += len(token.text) + self.next_token = None + + return token + + def raise_syntax_error( + self, + message: str, + *, + span_start: int | None = None, + span_end: int | None = None, + ) -> NoReturn: + """Raise ParserSyntaxError at the given position.""" + span = ( + self.position if span_start is None else span_start, + self.position if span_end is None else span_end, + ) + raise ParserSyntaxError( + message, + source=self.source, + span=span, + ) + + @contextlib.contextmanager + def enclosing_tokens( + self, open_token: str, close_token: str, *, around: str + ) -> Iterator[None]: + if self.check(open_token): + open_position = self.position + self.read() + else: + open_position = None + + yield + + if open_position is None: + return + + if not self.check(close_token): + self.raise_syntax_error( + f"Expected matching {close_token} for {open_token}, after {around}", + span_start=open_position, + ) + + self.read() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__init__.py new file mode 100644 index 000000000..71a1a7794 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__init__.py @@ -0,0 +1,145 @@ +####################################################################################### +# +# Adapted from: +# https://github.com/pypa/hatch/blob/5352e44/backend/src/hatchling/licenses/parse.py +# +# MIT License +# +# Copyright (c) 2017-present Ofek Lev +# +# 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. +# +# +# With additional allowance of arbitrary `LicenseRef-` identifiers, not just +# `LicenseRef-Public-Domain` and `LicenseRef-Proprietary`. +# +####################################################################################### +from __future__ import annotations + +import re +from typing import NewType, cast + +from pip._vendor.packaging.licenses._spdx import EXCEPTIONS, LICENSES + +__all__ = [ + "NormalizedLicenseExpression", + "InvalidLicenseExpression", + "canonicalize_license_expression", +] + +license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$") + +NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str) + + +class InvalidLicenseExpression(ValueError): + """Raised when a license-expression string is invalid + + >>> canonicalize_license_expression("invalid") + Traceback (most recent call last): + ... + packaging.licenses.InvalidLicenseExpression: Invalid license expression: 'invalid' + """ + + +def canonicalize_license_expression( + raw_license_expression: str, +) -> NormalizedLicenseExpression: + if not raw_license_expression: + message = f"Invalid license expression: {raw_license_expression!r}" + raise InvalidLicenseExpression(message) + + # Pad any parentheses so tokenization can be achieved by merely splitting on + # whitespace. + license_expression = raw_license_expression.replace("(", " ( ").replace(")", " ) ") + licenseref_prefix = "LicenseRef-" + license_refs = { + ref.lower(): "LicenseRef-" + ref[len(licenseref_prefix) :] + for ref in license_expression.split() + if ref.lower().startswith(licenseref_prefix.lower()) + } + + # Normalize to lower case so we can look up licenses/exceptions + # and so boolean operators are Python-compatible. + license_expression = license_expression.lower() + + tokens = license_expression.split() + + # Rather than implementing boolean logic, we create an expression that Python can + # parse. Everything that is not involved with the grammar itself is treated as + # `False` and the expression should evaluate as such. + python_tokens = [] + for token in tokens: + if token not in {"or", "and", "with", "(", ")"}: + python_tokens.append("False") + elif token == "with": + python_tokens.append("or") + elif token == "(" and python_tokens and python_tokens[-1] not in {"or", "and"}: + message = f"Invalid license expression: {raw_license_expression!r}" + raise InvalidLicenseExpression(message) + else: + python_tokens.append(token) + + python_expression = " ".join(python_tokens) + try: + invalid = eval(python_expression, globals(), locals()) + except Exception: + invalid = True + + if invalid is not False: + message = f"Invalid license expression: {raw_license_expression!r}" + raise InvalidLicenseExpression(message) from None + + # Take a final pass to check for unknown licenses/exceptions. + normalized_tokens = [] + for token in tokens: + if token in {"or", "and", "with", "(", ")"}: + normalized_tokens.append(token.upper()) + continue + + if normalized_tokens and normalized_tokens[-1] == "WITH": + if token not in EXCEPTIONS: + message = f"Unknown license exception: {token!r}" + raise InvalidLicenseExpression(message) + + normalized_tokens.append(EXCEPTIONS[token]["id"]) + else: + if token.endswith("+"): + final_token = token[:-1] + suffix = "+" + else: + final_token = token + suffix = "" + + if final_token.startswith("licenseref-"): + if not license_ref_allowed.match(final_token): + message = f"Invalid licenseref: {final_token!r}" + raise InvalidLicenseExpression(message) + normalized_tokens.append(license_refs[final_token] + suffix) + else: + if final_token not in LICENSES: + message = f"Unknown license: {final_token!r}" + raise InvalidLicenseExpression(message) + normalized_tokens.append(LICENSES[final_token]["id"] + suffix) + + normalized_expression = " ".join(normalized_tokens) + + return cast( + NormalizedLicenseExpression, + normalized_expression.replace("( ", "(").replace(" )", ")"), + ) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e411ac3394cac83bd8d635945290a940aed187d GIT binary patch literal 2638 zcmai0Pj3@P6rY)0@2=N&;(!B$P*@=(;#RgJxX=h$R1jz+lvX4Zx-DI8JmX~R^{$!S z{Ik{{R@)gnlgd?JYwNb& zHf#g<4ArQd`X)67*v>JPW>|iY*o9k&W~ufVQH@RPDfT2aK|l8cp?Ru5R_!7s zE2vy}AtNedk#mC}jNRA^gJ`)-gucwSS9d#1s0}xYL0~T4zrOhK>e5HcE5f+FbbWDo zWpPE!E{DAB`d*jO+g^hO5nJ5pa27@2N1R#;w&2?vzMN~gK^SXmJT~K?H0fI z?1PH!R=V?*_p6l$?_OxgIFAkt%r#lPA`lGOm?Eecwv{w5g&QF5{(i^xB1Wz44Hj6g zHCm`LUdW1K?gi_X7g^pgSkts%)a!NYEiulWA6c9)rzWf1ZLl@B(X!^+VH8`OK|Nyd z;`^2_Yq*fAN$=HaHKjYC?XJW6)lpUBw>w{Sg0GWfIb&-ctHh9ai}BhP3$|*$w>DC_T)k3@yqHynSu!y7IyDEh zXvk}ase?s|o2ceEUf{)!Q|;`gyLSmSYr3270j8(-MtlTRM-EOkbtu0AuvkF+EaSQJrtZ$QJig= z{11%ej~08zrzjxzQDV@kR`x08uVqisBLW#@(+vI`WuSp(J;};-`{n@2wV&dLXA|k+ zQIbvLJ=P*Ar^UIJ4pcyoC)z7@1rKp9(IC6hOzI{04m{@R^Z1Zw#dN59ZhiTU~(iISKzD9N21u5l?b<08aFIH5Cd z(vOQ$U&eG`BeP3un}you&>E8`fI*67 z%}%0k$wL)lB77Rmm&=OC_~AC=!i-{<$I-SIZwSR_0h}6Aofisc&1cx28l5)a9Jr^O z-p=soPl6L+UZq|mZiMYl{TA?%JUtpA_}EJgbuYi=I;>vxL%8drdUfojo{?@z$b~B7 z;gBxkB`H(ffQrmb&}aOtl)eM67k|U3%i5$YKL^x4wpMT2>b<4a54*E>gH{l32S={J z2J6U{wO|S2(S7`$K;7Ip#Y(sI%6>u03s!gP6-OW>*!pdM{EZftN(7C`1aaQ2EijHG%<{y*MN(8`667ycgEA;Z^B?8s;a~s& literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f0f9ae959d809a84c64a17701b1b1aae1c0a060 GIT binary patch literal 29649 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/_spdx.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/_spdx.py new file mode 100644 index 000000000..eac22276a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/licenses/_spdx.py @@ -0,0 +1,759 @@ + +from __future__ import annotations + +from typing import TypedDict + +class SPDXLicense(TypedDict): + id: str + deprecated: bool + +class SPDXException(TypedDict): + id: str + deprecated: bool + + +VERSION = '3.25.0' + +LICENSES: dict[str, SPDXLicense] = { + '0bsd': {'id': '0BSD', 'deprecated': False}, + '3d-slicer-1.0': {'id': '3D-Slicer-1.0', 'deprecated': False}, + 'aal': {'id': 'AAL', 'deprecated': False}, + 'abstyles': {'id': 'Abstyles', 'deprecated': False}, + 'adacore-doc': {'id': 'AdaCore-doc', 'deprecated': False}, + 'adobe-2006': {'id': 'Adobe-2006', 'deprecated': False}, + 'adobe-display-postscript': {'id': 'Adobe-Display-PostScript', 'deprecated': False}, + 'adobe-glyph': {'id': 'Adobe-Glyph', 'deprecated': False}, + 'adobe-utopia': {'id': 'Adobe-Utopia', 'deprecated': False}, + 'adsl': {'id': 'ADSL', 'deprecated': False}, + 'afl-1.1': {'id': 'AFL-1.1', 'deprecated': False}, + 'afl-1.2': {'id': 'AFL-1.2', 'deprecated': False}, + 'afl-2.0': {'id': 'AFL-2.0', 'deprecated': False}, + 'afl-2.1': {'id': 'AFL-2.1', 'deprecated': False}, + 'afl-3.0': {'id': 'AFL-3.0', 'deprecated': False}, + 'afmparse': {'id': 'Afmparse', 'deprecated': False}, + 'agpl-1.0': {'id': 'AGPL-1.0', 'deprecated': True}, + 'agpl-1.0-only': {'id': 'AGPL-1.0-only', 'deprecated': False}, + 'agpl-1.0-or-later': {'id': 'AGPL-1.0-or-later', 'deprecated': False}, + 'agpl-3.0': {'id': 'AGPL-3.0', 'deprecated': True}, + 'agpl-3.0-only': {'id': 'AGPL-3.0-only', 'deprecated': False}, + 'agpl-3.0-or-later': {'id': 'AGPL-3.0-or-later', 'deprecated': False}, + 'aladdin': {'id': 'Aladdin', 'deprecated': False}, + 'amd-newlib': {'id': 'AMD-newlib', 'deprecated': False}, + 'amdplpa': {'id': 'AMDPLPA', 'deprecated': False}, + 'aml': {'id': 'AML', 'deprecated': False}, + 'aml-glslang': {'id': 'AML-glslang', 'deprecated': False}, + 'ampas': {'id': 'AMPAS', 'deprecated': False}, + 'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False}, + 'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False}, + 'any-osi': {'id': 'any-OSI', 'deprecated': False}, + 'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False}, + 'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False}, + 'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False}, + 'apafml': {'id': 'APAFML', 'deprecated': False}, + 'apl-1.0': {'id': 'APL-1.0', 'deprecated': False}, + 'app-s2p': {'id': 'App-s2p', 'deprecated': False}, + 'apsl-1.0': {'id': 'APSL-1.0', 'deprecated': False}, + 'apsl-1.1': {'id': 'APSL-1.1', 'deprecated': False}, + 'apsl-1.2': {'id': 'APSL-1.2', 'deprecated': False}, + 'apsl-2.0': {'id': 'APSL-2.0', 'deprecated': False}, + 'arphic-1999': {'id': 'Arphic-1999', 'deprecated': False}, + 'artistic-1.0': {'id': 'Artistic-1.0', 'deprecated': False}, + 'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False}, + 'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False}, + 'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False}, + 'aswf-digital-assets-1.0': {'id': 'ASWF-Digital-Assets-1.0', 'deprecated': False}, + 'aswf-digital-assets-1.1': {'id': 'ASWF-Digital-Assets-1.1', 'deprecated': False}, + 'baekmuk': {'id': 'Baekmuk', 'deprecated': False}, + 'bahyph': {'id': 'Bahyph', 'deprecated': False}, + 'barr': {'id': 'Barr', 'deprecated': False}, + 'bcrypt-solar-designer': {'id': 'bcrypt-Solar-Designer', 'deprecated': False}, + 'beerware': {'id': 'Beerware', 'deprecated': False}, + 'bitstream-charter': {'id': 'Bitstream-Charter', 'deprecated': False}, + 'bitstream-vera': {'id': 'Bitstream-Vera', 'deprecated': False}, + 'bittorrent-1.0': {'id': 'BitTorrent-1.0', 'deprecated': False}, + 'bittorrent-1.1': {'id': 'BitTorrent-1.1', 'deprecated': False}, + 'blessing': {'id': 'blessing', 'deprecated': False}, + 'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False}, + 'boehm-gc': {'id': 'Boehm-GC', 'deprecated': False}, + 'borceux': {'id': 'Borceux', 'deprecated': False}, + 'brian-gladman-2-clause': {'id': 'Brian-Gladman-2-Clause', 'deprecated': False}, + 'brian-gladman-3-clause': {'id': 'Brian-Gladman-3-Clause', 'deprecated': False}, + 'bsd-1-clause': {'id': 'BSD-1-Clause', 'deprecated': False}, + 'bsd-2-clause': {'id': 'BSD-2-Clause', 'deprecated': False}, + 'bsd-2-clause-darwin': {'id': 'BSD-2-Clause-Darwin', 'deprecated': False}, + 'bsd-2-clause-first-lines': {'id': 'BSD-2-Clause-first-lines', 'deprecated': False}, + 'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True}, + 'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True}, + 'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False}, + 'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False}, + 'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False}, + 'bsd-3-clause-acpica': {'id': 'BSD-3-Clause-acpica', 'deprecated': False}, + 'bsd-3-clause-attribution': {'id': 'BSD-3-Clause-Attribution', 'deprecated': False}, + 'bsd-3-clause-clear': {'id': 'BSD-3-Clause-Clear', 'deprecated': False}, + 'bsd-3-clause-flex': {'id': 'BSD-3-Clause-flex', 'deprecated': False}, + 'bsd-3-clause-hp': {'id': 'BSD-3-Clause-HP', 'deprecated': False}, + 'bsd-3-clause-lbnl': {'id': 'BSD-3-Clause-LBNL', 'deprecated': False}, + 'bsd-3-clause-modification': {'id': 'BSD-3-Clause-Modification', 'deprecated': False}, + 'bsd-3-clause-no-military-license': {'id': 'BSD-3-Clause-No-Military-License', 'deprecated': False}, + 'bsd-3-clause-no-nuclear-license': {'id': 'BSD-3-Clause-No-Nuclear-License', 'deprecated': False}, + 'bsd-3-clause-no-nuclear-license-2014': {'id': 'BSD-3-Clause-No-Nuclear-License-2014', 'deprecated': False}, + 'bsd-3-clause-no-nuclear-warranty': {'id': 'BSD-3-Clause-No-Nuclear-Warranty', 'deprecated': False}, + 'bsd-3-clause-open-mpi': {'id': 'BSD-3-Clause-Open-MPI', 'deprecated': False}, + 'bsd-3-clause-sun': {'id': 'BSD-3-Clause-Sun', 'deprecated': False}, + 'bsd-4-clause': {'id': 'BSD-4-Clause', 'deprecated': False}, + 'bsd-4-clause-shortened': {'id': 'BSD-4-Clause-Shortened', 'deprecated': False}, + 'bsd-4-clause-uc': {'id': 'BSD-4-Clause-UC', 'deprecated': False}, + 'bsd-4.3reno': {'id': 'BSD-4.3RENO', 'deprecated': False}, + 'bsd-4.3tahoe': {'id': 'BSD-4.3TAHOE', 'deprecated': False}, + 'bsd-advertising-acknowledgement': {'id': 'BSD-Advertising-Acknowledgement', 'deprecated': False}, + 'bsd-attribution-hpnd-disclaimer': {'id': 'BSD-Attribution-HPND-disclaimer', 'deprecated': False}, + 'bsd-inferno-nettverk': {'id': 'BSD-Inferno-Nettverk', 'deprecated': False}, + 'bsd-protection': {'id': 'BSD-Protection', 'deprecated': False}, + 'bsd-source-beginning-file': {'id': 'BSD-Source-beginning-file', 'deprecated': False}, + 'bsd-source-code': {'id': 'BSD-Source-Code', 'deprecated': False}, + 'bsd-systemics': {'id': 'BSD-Systemics', 'deprecated': False}, + 'bsd-systemics-w3works': {'id': 'BSD-Systemics-W3Works', 'deprecated': False}, + 'bsl-1.0': {'id': 'BSL-1.0', 'deprecated': False}, + 'busl-1.1': {'id': 'BUSL-1.1', 'deprecated': False}, + 'bzip2-1.0.5': {'id': 'bzip2-1.0.5', 'deprecated': True}, + 'bzip2-1.0.6': {'id': 'bzip2-1.0.6', 'deprecated': False}, + 'c-uda-1.0': {'id': 'C-UDA-1.0', 'deprecated': False}, + 'cal-1.0': {'id': 'CAL-1.0', 'deprecated': False}, + 'cal-1.0-combined-work-exception': {'id': 'CAL-1.0-Combined-Work-Exception', 'deprecated': False}, + 'caldera': {'id': 'Caldera', 'deprecated': False}, + 'caldera-no-preamble': {'id': 'Caldera-no-preamble', 'deprecated': False}, + 'catharon': {'id': 'Catharon', 'deprecated': False}, + 'catosl-1.1': {'id': 'CATOSL-1.1', 'deprecated': False}, + 'cc-by-1.0': {'id': 'CC-BY-1.0', 'deprecated': False}, + 'cc-by-2.0': {'id': 'CC-BY-2.0', 'deprecated': False}, + 'cc-by-2.5': {'id': 'CC-BY-2.5', 'deprecated': False}, + 'cc-by-2.5-au': {'id': 'CC-BY-2.5-AU', 'deprecated': False}, + 'cc-by-3.0': {'id': 'CC-BY-3.0', 'deprecated': False}, + 'cc-by-3.0-at': {'id': 'CC-BY-3.0-AT', 'deprecated': False}, + 'cc-by-3.0-au': {'id': 'CC-BY-3.0-AU', 'deprecated': False}, + 'cc-by-3.0-de': {'id': 'CC-BY-3.0-DE', 'deprecated': False}, + 'cc-by-3.0-igo': {'id': 'CC-BY-3.0-IGO', 'deprecated': False}, + 'cc-by-3.0-nl': {'id': 'CC-BY-3.0-NL', 'deprecated': False}, + 'cc-by-3.0-us': {'id': 'CC-BY-3.0-US', 'deprecated': False}, + 'cc-by-4.0': {'id': 'CC-BY-4.0', 'deprecated': False}, + 'cc-by-nc-1.0': {'id': 'CC-BY-NC-1.0', 'deprecated': False}, + 'cc-by-nc-2.0': {'id': 'CC-BY-NC-2.0', 'deprecated': False}, + 'cc-by-nc-2.5': {'id': 'CC-BY-NC-2.5', 'deprecated': False}, + 'cc-by-nc-3.0': {'id': 'CC-BY-NC-3.0', 'deprecated': False}, + 'cc-by-nc-3.0-de': {'id': 'CC-BY-NC-3.0-DE', 'deprecated': False}, + 'cc-by-nc-4.0': {'id': 'CC-BY-NC-4.0', 'deprecated': False}, + 'cc-by-nc-nd-1.0': {'id': 'CC-BY-NC-ND-1.0', 'deprecated': False}, + 'cc-by-nc-nd-2.0': {'id': 'CC-BY-NC-ND-2.0', 'deprecated': False}, + 'cc-by-nc-nd-2.5': {'id': 'CC-BY-NC-ND-2.5', 'deprecated': False}, + 'cc-by-nc-nd-3.0': {'id': 'CC-BY-NC-ND-3.0', 'deprecated': False}, + 'cc-by-nc-nd-3.0-de': {'id': 'CC-BY-NC-ND-3.0-DE', 'deprecated': False}, + 'cc-by-nc-nd-3.0-igo': {'id': 'CC-BY-NC-ND-3.0-IGO', 'deprecated': False}, + 'cc-by-nc-nd-4.0': {'id': 'CC-BY-NC-ND-4.0', 'deprecated': False}, + 'cc-by-nc-sa-1.0': {'id': 'CC-BY-NC-SA-1.0', 'deprecated': False}, + 'cc-by-nc-sa-2.0': {'id': 'CC-BY-NC-SA-2.0', 'deprecated': False}, + 'cc-by-nc-sa-2.0-de': {'id': 'CC-BY-NC-SA-2.0-DE', 'deprecated': False}, + 'cc-by-nc-sa-2.0-fr': {'id': 'CC-BY-NC-SA-2.0-FR', 'deprecated': False}, + 'cc-by-nc-sa-2.0-uk': {'id': 'CC-BY-NC-SA-2.0-UK', 'deprecated': False}, + 'cc-by-nc-sa-2.5': {'id': 'CC-BY-NC-SA-2.5', 'deprecated': False}, + 'cc-by-nc-sa-3.0': {'id': 'CC-BY-NC-SA-3.0', 'deprecated': False}, + 'cc-by-nc-sa-3.0-de': {'id': 'CC-BY-NC-SA-3.0-DE', 'deprecated': False}, + 'cc-by-nc-sa-3.0-igo': {'id': 'CC-BY-NC-SA-3.0-IGO', 'deprecated': False}, + 'cc-by-nc-sa-4.0': {'id': 'CC-BY-NC-SA-4.0', 'deprecated': False}, + 'cc-by-nd-1.0': {'id': 'CC-BY-ND-1.0', 'deprecated': False}, + 'cc-by-nd-2.0': {'id': 'CC-BY-ND-2.0', 'deprecated': False}, + 'cc-by-nd-2.5': {'id': 'CC-BY-ND-2.5', 'deprecated': False}, + 'cc-by-nd-3.0': {'id': 'CC-BY-ND-3.0', 'deprecated': False}, + 'cc-by-nd-3.0-de': {'id': 'CC-BY-ND-3.0-DE', 'deprecated': False}, + 'cc-by-nd-4.0': {'id': 'CC-BY-ND-4.0', 'deprecated': False}, + 'cc-by-sa-1.0': {'id': 'CC-BY-SA-1.0', 'deprecated': False}, + 'cc-by-sa-2.0': {'id': 'CC-BY-SA-2.0', 'deprecated': False}, + 'cc-by-sa-2.0-uk': {'id': 'CC-BY-SA-2.0-UK', 'deprecated': False}, + 'cc-by-sa-2.1-jp': {'id': 'CC-BY-SA-2.1-JP', 'deprecated': False}, + 'cc-by-sa-2.5': {'id': 'CC-BY-SA-2.5', 'deprecated': False}, + 'cc-by-sa-3.0': {'id': 'CC-BY-SA-3.0', 'deprecated': False}, + 'cc-by-sa-3.0-at': {'id': 'CC-BY-SA-3.0-AT', 'deprecated': False}, + 'cc-by-sa-3.0-de': {'id': 'CC-BY-SA-3.0-DE', 'deprecated': False}, + 'cc-by-sa-3.0-igo': {'id': 'CC-BY-SA-3.0-IGO', 'deprecated': False}, + 'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False}, + 'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False}, + 'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False}, + 'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False}, + 'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False}, + 'cdl-1.0': {'id': 'CDL-1.0', 'deprecated': False}, + 'cdla-permissive-1.0': {'id': 'CDLA-Permissive-1.0', 'deprecated': False}, + 'cdla-permissive-2.0': {'id': 'CDLA-Permissive-2.0', 'deprecated': False}, + 'cdla-sharing-1.0': {'id': 'CDLA-Sharing-1.0', 'deprecated': False}, + 'cecill-1.0': {'id': 'CECILL-1.0', 'deprecated': False}, + 'cecill-1.1': {'id': 'CECILL-1.1', 'deprecated': False}, + 'cecill-2.0': {'id': 'CECILL-2.0', 'deprecated': False}, + 'cecill-2.1': {'id': 'CECILL-2.1', 'deprecated': False}, + 'cecill-b': {'id': 'CECILL-B', 'deprecated': False}, + 'cecill-c': {'id': 'CECILL-C', 'deprecated': False}, + 'cern-ohl-1.1': {'id': 'CERN-OHL-1.1', 'deprecated': False}, + 'cern-ohl-1.2': {'id': 'CERN-OHL-1.2', 'deprecated': False}, + 'cern-ohl-p-2.0': {'id': 'CERN-OHL-P-2.0', 'deprecated': False}, + 'cern-ohl-s-2.0': {'id': 'CERN-OHL-S-2.0', 'deprecated': False}, + 'cern-ohl-w-2.0': {'id': 'CERN-OHL-W-2.0', 'deprecated': False}, + 'cfitsio': {'id': 'CFITSIO', 'deprecated': False}, + 'check-cvs': {'id': 'check-cvs', 'deprecated': False}, + 'checkmk': {'id': 'checkmk', 'deprecated': False}, + 'clartistic': {'id': 'ClArtistic', 'deprecated': False}, + 'clips': {'id': 'Clips', 'deprecated': False}, + 'cmu-mach': {'id': 'CMU-Mach', 'deprecated': False}, + 'cmu-mach-nodoc': {'id': 'CMU-Mach-nodoc', 'deprecated': False}, + 'cnri-jython': {'id': 'CNRI-Jython', 'deprecated': False}, + 'cnri-python': {'id': 'CNRI-Python', 'deprecated': False}, + 'cnri-python-gpl-compatible': {'id': 'CNRI-Python-GPL-Compatible', 'deprecated': False}, + 'coil-1.0': {'id': 'COIL-1.0', 'deprecated': False}, + 'community-spec-1.0': {'id': 'Community-Spec-1.0', 'deprecated': False}, + 'condor-1.1': {'id': 'Condor-1.1', 'deprecated': False}, + 'copyleft-next-0.3.0': {'id': 'copyleft-next-0.3.0', 'deprecated': False}, + 'copyleft-next-0.3.1': {'id': 'copyleft-next-0.3.1', 'deprecated': False}, + 'cornell-lossless-jpeg': {'id': 'Cornell-Lossless-JPEG', 'deprecated': False}, + 'cpal-1.0': {'id': 'CPAL-1.0', 'deprecated': False}, + 'cpl-1.0': {'id': 'CPL-1.0', 'deprecated': False}, + 'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False}, + 'cronyx': {'id': 'Cronyx', 'deprecated': False}, + 'crossword': {'id': 'Crossword', 'deprecated': False}, + 'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False}, + 'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False}, + 'cube': {'id': 'Cube', 'deprecated': False}, + 'curl': {'id': 'curl', 'deprecated': False}, + 'cve-tou': {'id': 'cve-tou', 'deprecated': False}, + 'd-fsl-1.0': {'id': 'D-FSL-1.0', 'deprecated': False}, + 'dec-3-clause': {'id': 'DEC-3-Clause', 'deprecated': False}, + 'diffmark': {'id': 'diffmark', 'deprecated': False}, + 'dl-de-by-2.0': {'id': 'DL-DE-BY-2.0', 'deprecated': False}, + 'dl-de-zero-2.0': {'id': 'DL-DE-ZERO-2.0', 'deprecated': False}, + 'doc': {'id': 'DOC', 'deprecated': False}, + 'docbook-schema': {'id': 'DocBook-Schema', 'deprecated': False}, + 'docbook-xml': {'id': 'DocBook-XML', 'deprecated': False}, + 'dotseqn': {'id': 'Dotseqn', 'deprecated': False}, + 'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False}, + 'drl-1.1': {'id': 'DRL-1.1', 'deprecated': False}, + 'dsdp': {'id': 'DSDP', 'deprecated': False}, + 'dtoa': {'id': 'dtoa', 'deprecated': False}, + 'dvipdfm': {'id': 'dvipdfm', 'deprecated': False}, + 'ecl-1.0': {'id': 'ECL-1.0', 'deprecated': False}, + 'ecl-2.0': {'id': 'ECL-2.0', 'deprecated': False}, + 'ecos-2.0': {'id': 'eCos-2.0', 'deprecated': True}, + 'efl-1.0': {'id': 'EFL-1.0', 'deprecated': False}, + 'efl-2.0': {'id': 'EFL-2.0', 'deprecated': False}, + 'egenix': {'id': 'eGenix', 'deprecated': False}, + 'elastic-2.0': {'id': 'Elastic-2.0', 'deprecated': False}, + 'entessa': {'id': 'Entessa', 'deprecated': False}, + 'epics': {'id': 'EPICS', 'deprecated': False}, + 'epl-1.0': {'id': 'EPL-1.0', 'deprecated': False}, + 'epl-2.0': {'id': 'EPL-2.0', 'deprecated': False}, + 'erlpl-1.1': {'id': 'ErlPL-1.1', 'deprecated': False}, + 'etalab-2.0': {'id': 'etalab-2.0', 'deprecated': False}, + 'eudatagrid': {'id': 'EUDatagrid', 'deprecated': False}, + 'eupl-1.0': {'id': 'EUPL-1.0', 'deprecated': False}, + 'eupl-1.1': {'id': 'EUPL-1.1', 'deprecated': False}, + 'eupl-1.2': {'id': 'EUPL-1.2', 'deprecated': False}, + 'eurosym': {'id': 'Eurosym', 'deprecated': False}, + 'fair': {'id': 'Fair', 'deprecated': False}, + 'fbm': {'id': 'FBM', 'deprecated': False}, + 'fdk-aac': {'id': 'FDK-AAC', 'deprecated': False}, + 'ferguson-twofish': {'id': 'Ferguson-Twofish', 'deprecated': False}, + 'frameworx-1.0': {'id': 'Frameworx-1.0', 'deprecated': False}, + 'freebsd-doc': {'id': 'FreeBSD-DOC', 'deprecated': False}, + 'freeimage': {'id': 'FreeImage', 'deprecated': False}, + 'fsfap': {'id': 'FSFAP', 'deprecated': False}, + 'fsfap-no-warranty-disclaimer': {'id': 'FSFAP-no-warranty-disclaimer', 'deprecated': False}, + 'fsful': {'id': 'FSFUL', 'deprecated': False}, + 'fsfullr': {'id': 'FSFULLR', 'deprecated': False}, + 'fsfullrwd': {'id': 'FSFULLRWD', 'deprecated': False}, + 'ftl': {'id': 'FTL', 'deprecated': False}, + 'furuseth': {'id': 'Furuseth', 'deprecated': False}, + 'fwlw': {'id': 'fwlw', 'deprecated': False}, + 'gcr-docs': {'id': 'GCR-docs', 'deprecated': False}, + 'gd': {'id': 'GD', 'deprecated': False}, + 'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True}, + 'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False}, + 'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False}, + 'gfdl-1.1-no-invariants-only': {'id': 'GFDL-1.1-no-invariants-only', 'deprecated': False}, + 'gfdl-1.1-no-invariants-or-later': {'id': 'GFDL-1.1-no-invariants-or-later', 'deprecated': False}, + 'gfdl-1.1-only': {'id': 'GFDL-1.1-only', 'deprecated': False}, + 'gfdl-1.1-or-later': {'id': 'GFDL-1.1-or-later', 'deprecated': False}, + 'gfdl-1.2': {'id': 'GFDL-1.2', 'deprecated': True}, + 'gfdl-1.2-invariants-only': {'id': 'GFDL-1.2-invariants-only', 'deprecated': False}, + 'gfdl-1.2-invariants-or-later': {'id': 'GFDL-1.2-invariants-or-later', 'deprecated': False}, + 'gfdl-1.2-no-invariants-only': {'id': 'GFDL-1.2-no-invariants-only', 'deprecated': False}, + 'gfdl-1.2-no-invariants-or-later': {'id': 'GFDL-1.2-no-invariants-or-later', 'deprecated': False}, + 'gfdl-1.2-only': {'id': 'GFDL-1.2-only', 'deprecated': False}, + 'gfdl-1.2-or-later': {'id': 'GFDL-1.2-or-later', 'deprecated': False}, + 'gfdl-1.3': {'id': 'GFDL-1.3', 'deprecated': True}, + 'gfdl-1.3-invariants-only': {'id': 'GFDL-1.3-invariants-only', 'deprecated': False}, + 'gfdl-1.3-invariants-or-later': {'id': 'GFDL-1.3-invariants-or-later', 'deprecated': False}, + 'gfdl-1.3-no-invariants-only': {'id': 'GFDL-1.3-no-invariants-only', 'deprecated': False}, + 'gfdl-1.3-no-invariants-or-later': {'id': 'GFDL-1.3-no-invariants-or-later', 'deprecated': False}, + 'gfdl-1.3-only': {'id': 'GFDL-1.3-only', 'deprecated': False}, + 'gfdl-1.3-or-later': {'id': 'GFDL-1.3-or-later', 'deprecated': False}, + 'giftware': {'id': 'Giftware', 'deprecated': False}, + 'gl2ps': {'id': 'GL2PS', 'deprecated': False}, + 'glide': {'id': 'Glide', 'deprecated': False}, + 'glulxe': {'id': 'Glulxe', 'deprecated': False}, + 'glwtpl': {'id': 'GLWTPL', 'deprecated': False}, + 'gnuplot': {'id': 'gnuplot', 'deprecated': False}, + 'gpl-1.0': {'id': 'GPL-1.0', 'deprecated': True}, + 'gpl-1.0+': {'id': 'GPL-1.0+', 'deprecated': True}, + 'gpl-1.0-only': {'id': 'GPL-1.0-only', 'deprecated': False}, + 'gpl-1.0-or-later': {'id': 'GPL-1.0-or-later', 'deprecated': False}, + 'gpl-2.0': {'id': 'GPL-2.0', 'deprecated': True}, + 'gpl-2.0+': {'id': 'GPL-2.0+', 'deprecated': True}, + 'gpl-2.0-only': {'id': 'GPL-2.0-only', 'deprecated': False}, + 'gpl-2.0-or-later': {'id': 'GPL-2.0-or-later', 'deprecated': False}, + 'gpl-2.0-with-autoconf-exception': {'id': 'GPL-2.0-with-autoconf-exception', 'deprecated': True}, + 'gpl-2.0-with-bison-exception': {'id': 'GPL-2.0-with-bison-exception', 'deprecated': True}, + 'gpl-2.0-with-classpath-exception': {'id': 'GPL-2.0-with-classpath-exception', 'deprecated': True}, + 'gpl-2.0-with-font-exception': {'id': 'GPL-2.0-with-font-exception', 'deprecated': True}, + 'gpl-2.0-with-gcc-exception': {'id': 'GPL-2.0-with-GCC-exception', 'deprecated': True}, + 'gpl-3.0': {'id': 'GPL-3.0', 'deprecated': True}, + 'gpl-3.0+': {'id': 'GPL-3.0+', 'deprecated': True}, + 'gpl-3.0-only': {'id': 'GPL-3.0-only', 'deprecated': False}, + 'gpl-3.0-or-later': {'id': 'GPL-3.0-or-later', 'deprecated': False}, + 'gpl-3.0-with-autoconf-exception': {'id': 'GPL-3.0-with-autoconf-exception', 'deprecated': True}, + 'gpl-3.0-with-gcc-exception': {'id': 'GPL-3.0-with-GCC-exception', 'deprecated': True}, + 'graphics-gems': {'id': 'Graphics-Gems', 'deprecated': False}, + 'gsoap-1.3b': {'id': 'gSOAP-1.3b', 'deprecated': False}, + 'gtkbook': {'id': 'gtkbook', 'deprecated': False}, + 'gutmann': {'id': 'Gutmann', 'deprecated': False}, + 'haskellreport': {'id': 'HaskellReport', 'deprecated': False}, + 'hdparm': {'id': 'hdparm', 'deprecated': False}, + 'hidapi': {'id': 'HIDAPI', 'deprecated': False}, + 'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False}, + 'hp-1986': {'id': 'HP-1986', 'deprecated': False}, + 'hp-1989': {'id': 'HP-1989', 'deprecated': False}, + 'hpnd': {'id': 'HPND', 'deprecated': False}, + 'hpnd-dec': {'id': 'HPND-DEC', 'deprecated': False}, + 'hpnd-doc': {'id': 'HPND-doc', 'deprecated': False}, + 'hpnd-doc-sell': {'id': 'HPND-doc-sell', 'deprecated': False}, + 'hpnd-export-us': {'id': 'HPND-export-US', 'deprecated': False}, + 'hpnd-export-us-acknowledgement': {'id': 'HPND-export-US-acknowledgement', 'deprecated': False}, + 'hpnd-export-us-modify': {'id': 'HPND-export-US-modify', 'deprecated': False}, + 'hpnd-export2-us': {'id': 'HPND-export2-US', 'deprecated': False}, + 'hpnd-fenneberg-livingston': {'id': 'HPND-Fenneberg-Livingston', 'deprecated': False}, + 'hpnd-inria-imag': {'id': 'HPND-INRIA-IMAG', 'deprecated': False}, + 'hpnd-intel': {'id': 'HPND-Intel', 'deprecated': False}, + 'hpnd-kevlin-henney': {'id': 'HPND-Kevlin-Henney', 'deprecated': False}, + 'hpnd-markus-kuhn': {'id': 'HPND-Markus-Kuhn', 'deprecated': False}, + 'hpnd-merchantability-variant': {'id': 'HPND-merchantability-variant', 'deprecated': False}, + 'hpnd-mit-disclaimer': {'id': 'HPND-MIT-disclaimer', 'deprecated': False}, + 'hpnd-netrek': {'id': 'HPND-Netrek', 'deprecated': False}, + 'hpnd-pbmplus': {'id': 'HPND-Pbmplus', 'deprecated': False}, + 'hpnd-sell-mit-disclaimer-xserver': {'id': 'HPND-sell-MIT-disclaimer-xserver', 'deprecated': False}, + 'hpnd-sell-regexpr': {'id': 'HPND-sell-regexpr', 'deprecated': False}, + 'hpnd-sell-variant': {'id': 'HPND-sell-variant', 'deprecated': False}, + 'hpnd-sell-variant-mit-disclaimer': {'id': 'HPND-sell-variant-MIT-disclaimer', 'deprecated': False}, + 'hpnd-sell-variant-mit-disclaimer-rev': {'id': 'HPND-sell-variant-MIT-disclaimer-rev', 'deprecated': False}, + 'hpnd-uc': {'id': 'HPND-UC', 'deprecated': False}, + 'hpnd-uc-export-us': {'id': 'HPND-UC-export-US', 'deprecated': False}, + 'htmltidy': {'id': 'HTMLTIDY', 'deprecated': False}, + 'ibm-pibs': {'id': 'IBM-pibs', 'deprecated': False}, + 'icu': {'id': 'ICU', 'deprecated': False}, + 'iec-code-components-eula': {'id': 'IEC-Code-Components-EULA', 'deprecated': False}, + 'ijg': {'id': 'IJG', 'deprecated': False}, + 'ijg-short': {'id': 'IJG-short', 'deprecated': False}, + 'imagemagick': {'id': 'ImageMagick', 'deprecated': False}, + 'imatix': {'id': 'iMatix', 'deprecated': False}, + 'imlib2': {'id': 'Imlib2', 'deprecated': False}, + 'info-zip': {'id': 'Info-ZIP', 'deprecated': False}, + 'inner-net-2.0': {'id': 'Inner-Net-2.0', 'deprecated': False}, + 'intel': {'id': 'Intel', 'deprecated': False}, + 'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False}, + 'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False}, + 'ipa': {'id': 'IPA', 'deprecated': False}, + 'ipl-1.0': {'id': 'IPL-1.0', 'deprecated': False}, + 'isc': {'id': 'ISC', 'deprecated': False}, + 'isc-veillard': {'id': 'ISC-Veillard', 'deprecated': False}, + 'jam': {'id': 'Jam', 'deprecated': False}, + 'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False}, + 'jpl-image': {'id': 'JPL-image', 'deprecated': False}, + 'jpnic': {'id': 'JPNIC', 'deprecated': False}, + 'json': {'id': 'JSON', 'deprecated': False}, + 'kastrup': {'id': 'Kastrup', 'deprecated': False}, + 'kazlib': {'id': 'Kazlib', 'deprecated': False}, + 'knuth-ctan': {'id': 'Knuth-CTAN', 'deprecated': False}, + 'lal-1.2': {'id': 'LAL-1.2', 'deprecated': False}, + 'lal-1.3': {'id': 'LAL-1.3', 'deprecated': False}, + 'latex2e': {'id': 'Latex2e', 'deprecated': False}, + 'latex2e-translated-notice': {'id': 'Latex2e-translated-notice', 'deprecated': False}, + 'leptonica': {'id': 'Leptonica', 'deprecated': False}, + 'lgpl-2.0': {'id': 'LGPL-2.0', 'deprecated': True}, + 'lgpl-2.0+': {'id': 'LGPL-2.0+', 'deprecated': True}, + 'lgpl-2.0-only': {'id': 'LGPL-2.0-only', 'deprecated': False}, + 'lgpl-2.0-or-later': {'id': 'LGPL-2.0-or-later', 'deprecated': False}, + 'lgpl-2.1': {'id': 'LGPL-2.1', 'deprecated': True}, + 'lgpl-2.1+': {'id': 'LGPL-2.1+', 'deprecated': True}, + 'lgpl-2.1-only': {'id': 'LGPL-2.1-only', 'deprecated': False}, + 'lgpl-2.1-or-later': {'id': 'LGPL-2.1-or-later', 'deprecated': False}, + 'lgpl-3.0': {'id': 'LGPL-3.0', 'deprecated': True}, + 'lgpl-3.0+': {'id': 'LGPL-3.0+', 'deprecated': True}, + 'lgpl-3.0-only': {'id': 'LGPL-3.0-only', 'deprecated': False}, + 'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False}, + 'lgpllr': {'id': 'LGPLLR', 'deprecated': False}, + 'libpng': {'id': 'Libpng', 'deprecated': False}, + 'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False}, + 'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False}, + 'libtiff': {'id': 'libtiff', 'deprecated': False}, + 'libutil-david-nugent': {'id': 'libutil-David-Nugent', 'deprecated': False}, + 'liliq-p-1.1': {'id': 'LiLiQ-P-1.1', 'deprecated': False}, + 'liliq-r-1.1': {'id': 'LiLiQ-R-1.1', 'deprecated': False}, + 'liliq-rplus-1.1': {'id': 'LiLiQ-Rplus-1.1', 'deprecated': False}, + 'linux-man-pages-1-para': {'id': 'Linux-man-pages-1-para', 'deprecated': False}, + 'linux-man-pages-copyleft': {'id': 'Linux-man-pages-copyleft', 'deprecated': False}, + 'linux-man-pages-copyleft-2-para': {'id': 'Linux-man-pages-copyleft-2-para', 'deprecated': False}, + 'linux-man-pages-copyleft-var': {'id': 'Linux-man-pages-copyleft-var', 'deprecated': False}, + 'linux-openib': {'id': 'Linux-OpenIB', 'deprecated': False}, + 'loop': {'id': 'LOOP', 'deprecated': False}, + 'lpd-document': {'id': 'LPD-document', 'deprecated': False}, + 'lpl-1.0': {'id': 'LPL-1.0', 'deprecated': False}, + 'lpl-1.02': {'id': 'LPL-1.02', 'deprecated': False}, + 'lppl-1.0': {'id': 'LPPL-1.0', 'deprecated': False}, + 'lppl-1.1': {'id': 'LPPL-1.1', 'deprecated': False}, + 'lppl-1.2': {'id': 'LPPL-1.2', 'deprecated': False}, + 'lppl-1.3a': {'id': 'LPPL-1.3a', 'deprecated': False}, + 'lppl-1.3c': {'id': 'LPPL-1.3c', 'deprecated': False}, + 'lsof': {'id': 'lsof', 'deprecated': False}, + 'lucida-bitmap-fonts': {'id': 'Lucida-Bitmap-Fonts', 'deprecated': False}, + 'lzma-sdk-9.11-to-9.20': {'id': 'LZMA-SDK-9.11-to-9.20', 'deprecated': False}, + 'lzma-sdk-9.22': {'id': 'LZMA-SDK-9.22', 'deprecated': False}, + 'mackerras-3-clause': {'id': 'Mackerras-3-Clause', 'deprecated': False}, + 'mackerras-3-clause-acknowledgment': {'id': 'Mackerras-3-Clause-acknowledgment', 'deprecated': False}, + 'magaz': {'id': 'magaz', 'deprecated': False}, + 'mailprio': {'id': 'mailprio', 'deprecated': False}, + 'makeindex': {'id': 'MakeIndex', 'deprecated': False}, + 'martin-birgmeier': {'id': 'Martin-Birgmeier', 'deprecated': False}, + 'mcphee-slideshow': {'id': 'McPhee-slideshow', 'deprecated': False}, + 'metamail': {'id': 'metamail', 'deprecated': False}, + 'minpack': {'id': 'Minpack', 'deprecated': False}, + 'miros': {'id': 'MirOS', 'deprecated': False}, + 'mit': {'id': 'MIT', 'deprecated': False}, + 'mit-0': {'id': 'MIT-0', 'deprecated': False}, + 'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False}, + 'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False}, + 'mit-enna': {'id': 'MIT-enna', 'deprecated': False}, + 'mit-feh': {'id': 'MIT-feh', 'deprecated': False}, + 'mit-festival': {'id': 'MIT-Festival', 'deprecated': False}, + 'mit-khronos-old': {'id': 'MIT-Khronos-old', 'deprecated': False}, + 'mit-modern-variant': {'id': 'MIT-Modern-Variant', 'deprecated': False}, + 'mit-open-group': {'id': 'MIT-open-group', 'deprecated': False}, + 'mit-testregex': {'id': 'MIT-testregex', 'deprecated': False}, + 'mit-wu': {'id': 'MIT-Wu', 'deprecated': False}, + 'mitnfa': {'id': 'MITNFA', 'deprecated': False}, + 'mmixware': {'id': 'MMIXware', 'deprecated': False}, + 'motosoto': {'id': 'Motosoto', 'deprecated': False}, + 'mpeg-ssg': {'id': 'MPEG-SSG', 'deprecated': False}, + 'mpi-permissive': {'id': 'mpi-permissive', 'deprecated': False}, + 'mpich2': {'id': 'mpich2', 'deprecated': False}, + 'mpl-1.0': {'id': 'MPL-1.0', 'deprecated': False}, + 'mpl-1.1': {'id': 'MPL-1.1', 'deprecated': False}, + 'mpl-2.0': {'id': 'MPL-2.0', 'deprecated': False}, + 'mpl-2.0-no-copyleft-exception': {'id': 'MPL-2.0-no-copyleft-exception', 'deprecated': False}, + 'mplus': {'id': 'mplus', 'deprecated': False}, + 'ms-lpl': {'id': 'MS-LPL', 'deprecated': False}, + 'ms-pl': {'id': 'MS-PL', 'deprecated': False}, + 'ms-rl': {'id': 'MS-RL', 'deprecated': False}, + 'mtll': {'id': 'MTLL', 'deprecated': False}, + 'mulanpsl-1.0': {'id': 'MulanPSL-1.0', 'deprecated': False}, + 'mulanpsl-2.0': {'id': 'MulanPSL-2.0', 'deprecated': False}, + 'multics': {'id': 'Multics', 'deprecated': False}, + 'mup': {'id': 'Mup', 'deprecated': False}, + 'naist-2003': {'id': 'NAIST-2003', 'deprecated': False}, + 'nasa-1.3': {'id': 'NASA-1.3', 'deprecated': False}, + 'naumen': {'id': 'Naumen', 'deprecated': False}, + 'nbpl-1.0': {'id': 'NBPL-1.0', 'deprecated': False}, + 'ncbi-pd': {'id': 'NCBI-PD', 'deprecated': False}, + 'ncgl-uk-2.0': {'id': 'NCGL-UK-2.0', 'deprecated': False}, + 'ncl': {'id': 'NCL', 'deprecated': False}, + 'ncsa': {'id': 'NCSA', 'deprecated': False}, + 'net-snmp': {'id': 'Net-SNMP', 'deprecated': True}, + 'netcdf': {'id': 'NetCDF', 'deprecated': False}, + 'newsletr': {'id': 'Newsletr', 'deprecated': False}, + 'ngpl': {'id': 'NGPL', 'deprecated': False}, + 'nicta-1.0': {'id': 'NICTA-1.0', 'deprecated': False}, + 'nist-pd': {'id': 'NIST-PD', 'deprecated': False}, + 'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False}, + 'nist-software': {'id': 'NIST-Software', 'deprecated': False}, + 'nlod-1.0': {'id': 'NLOD-1.0', 'deprecated': False}, + 'nlod-2.0': {'id': 'NLOD-2.0', 'deprecated': False}, + 'nlpl': {'id': 'NLPL', 'deprecated': False}, + 'nokia': {'id': 'Nokia', 'deprecated': False}, + 'nosl': {'id': 'NOSL', 'deprecated': False}, + 'noweb': {'id': 'Noweb', 'deprecated': False}, + 'npl-1.0': {'id': 'NPL-1.0', 'deprecated': False}, + 'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False}, + 'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False}, + 'nrl': {'id': 'NRL', 'deprecated': False}, + 'ntp': {'id': 'NTP', 'deprecated': False}, + 'ntp-0': {'id': 'NTP-0', 'deprecated': False}, + 'nunit': {'id': 'Nunit', 'deprecated': True}, + 'o-uda-1.0': {'id': 'O-UDA-1.0', 'deprecated': False}, + 'oar': {'id': 'OAR', 'deprecated': False}, + 'occt-pl': {'id': 'OCCT-PL', 'deprecated': False}, + 'oclc-2.0': {'id': 'OCLC-2.0', 'deprecated': False}, + 'odbl-1.0': {'id': 'ODbL-1.0', 'deprecated': False}, + 'odc-by-1.0': {'id': 'ODC-By-1.0', 'deprecated': False}, + 'offis': {'id': 'OFFIS', 'deprecated': False}, + 'ofl-1.0': {'id': 'OFL-1.0', 'deprecated': False}, + 'ofl-1.0-no-rfn': {'id': 'OFL-1.0-no-RFN', 'deprecated': False}, + 'ofl-1.0-rfn': {'id': 'OFL-1.0-RFN', 'deprecated': False}, + 'ofl-1.1': {'id': 'OFL-1.1', 'deprecated': False}, + 'ofl-1.1-no-rfn': {'id': 'OFL-1.1-no-RFN', 'deprecated': False}, + 'ofl-1.1-rfn': {'id': 'OFL-1.1-RFN', 'deprecated': False}, + 'ogc-1.0': {'id': 'OGC-1.0', 'deprecated': False}, + 'ogdl-taiwan-1.0': {'id': 'OGDL-Taiwan-1.0', 'deprecated': False}, + 'ogl-canada-2.0': {'id': 'OGL-Canada-2.0', 'deprecated': False}, + 'ogl-uk-1.0': {'id': 'OGL-UK-1.0', 'deprecated': False}, + 'ogl-uk-2.0': {'id': 'OGL-UK-2.0', 'deprecated': False}, + 'ogl-uk-3.0': {'id': 'OGL-UK-3.0', 'deprecated': False}, + 'ogtsl': {'id': 'OGTSL', 'deprecated': False}, + 'oldap-1.1': {'id': 'OLDAP-1.1', 'deprecated': False}, + 'oldap-1.2': {'id': 'OLDAP-1.2', 'deprecated': False}, + 'oldap-1.3': {'id': 'OLDAP-1.3', 'deprecated': False}, + 'oldap-1.4': {'id': 'OLDAP-1.4', 'deprecated': False}, + 'oldap-2.0': {'id': 'OLDAP-2.0', 'deprecated': False}, + 'oldap-2.0.1': {'id': 'OLDAP-2.0.1', 'deprecated': False}, + 'oldap-2.1': {'id': 'OLDAP-2.1', 'deprecated': False}, + 'oldap-2.2': {'id': 'OLDAP-2.2', 'deprecated': False}, + 'oldap-2.2.1': {'id': 'OLDAP-2.2.1', 'deprecated': False}, + 'oldap-2.2.2': {'id': 'OLDAP-2.2.2', 'deprecated': False}, + 'oldap-2.3': {'id': 'OLDAP-2.3', 'deprecated': False}, + 'oldap-2.4': {'id': 'OLDAP-2.4', 'deprecated': False}, + 'oldap-2.5': {'id': 'OLDAP-2.5', 'deprecated': False}, + 'oldap-2.6': {'id': 'OLDAP-2.6', 'deprecated': False}, + 'oldap-2.7': {'id': 'OLDAP-2.7', 'deprecated': False}, + 'oldap-2.8': {'id': 'OLDAP-2.8', 'deprecated': False}, + 'olfl-1.3': {'id': 'OLFL-1.3', 'deprecated': False}, + 'oml': {'id': 'OML', 'deprecated': False}, + 'openpbs-2.3': {'id': 'OpenPBS-2.3', 'deprecated': False}, + 'openssl': {'id': 'OpenSSL', 'deprecated': False}, + 'openssl-standalone': {'id': 'OpenSSL-standalone', 'deprecated': False}, + 'openvision': {'id': 'OpenVision', 'deprecated': False}, + 'opl-1.0': {'id': 'OPL-1.0', 'deprecated': False}, + 'opl-uk-3.0': {'id': 'OPL-UK-3.0', 'deprecated': False}, + 'opubl-1.0': {'id': 'OPUBL-1.0', 'deprecated': False}, + 'oset-pl-2.1': {'id': 'OSET-PL-2.1', 'deprecated': False}, + 'osl-1.0': {'id': 'OSL-1.0', 'deprecated': False}, + 'osl-1.1': {'id': 'OSL-1.1', 'deprecated': False}, + 'osl-2.0': {'id': 'OSL-2.0', 'deprecated': False}, + 'osl-2.1': {'id': 'OSL-2.1', 'deprecated': False}, + 'osl-3.0': {'id': 'OSL-3.0', 'deprecated': False}, + 'padl': {'id': 'PADL', 'deprecated': False}, + 'parity-6.0.0': {'id': 'Parity-6.0.0', 'deprecated': False}, + 'parity-7.0.0': {'id': 'Parity-7.0.0', 'deprecated': False}, + 'pddl-1.0': {'id': 'PDDL-1.0', 'deprecated': False}, + 'php-3.0': {'id': 'PHP-3.0', 'deprecated': False}, + 'php-3.01': {'id': 'PHP-3.01', 'deprecated': False}, + 'pixar': {'id': 'Pixar', 'deprecated': False}, + 'pkgconf': {'id': 'pkgconf', 'deprecated': False}, + 'plexus': {'id': 'Plexus', 'deprecated': False}, + 'pnmstitch': {'id': 'pnmstitch', 'deprecated': False}, + 'polyform-noncommercial-1.0.0': {'id': 'PolyForm-Noncommercial-1.0.0', 'deprecated': False}, + 'polyform-small-business-1.0.0': {'id': 'PolyForm-Small-Business-1.0.0', 'deprecated': False}, + 'postgresql': {'id': 'PostgreSQL', 'deprecated': False}, + 'ppl': {'id': 'PPL', 'deprecated': False}, + 'psf-2.0': {'id': 'PSF-2.0', 'deprecated': False}, + 'psfrag': {'id': 'psfrag', 'deprecated': False}, + 'psutils': {'id': 'psutils', 'deprecated': False}, + 'python-2.0': {'id': 'Python-2.0', 'deprecated': False}, + 'python-2.0.1': {'id': 'Python-2.0.1', 'deprecated': False}, + 'python-ldap': {'id': 'python-ldap', 'deprecated': False}, + 'qhull': {'id': 'Qhull', 'deprecated': False}, + 'qpl-1.0': {'id': 'QPL-1.0', 'deprecated': False}, + 'qpl-1.0-inria-2004': {'id': 'QPL-1.0-INRIA-2004', 'deprecated': False}, + 'radvd': {'id': 'radvd', 'deprecated': False}, + 'rdisc': {'id': 'Rdisc', 'deprecated': False}, + 'rhecos-1.1': {'id': 'RHeCos-1.1', 'deprecated': False}, + 'rpl-1.1': {'id': 'RPL-1.1', 'deprecated': False}, + 'rpl-1.5': {'id': 'RPL-1.5', 'deprecated': False}, + 'rpsl-1.0': {'id': 'RPSL-1.0', 'deprecated': False}, + 'rsa-md': {'id': 'RSA-MD', 'deprecated': False}, + 'rscpl': {'id': 'RSCPL', 'deprecated': False}, + 'ruby': {'id': 'Ruby', 'deprecated': False}, + 'ruby-pty': {'id': 'Ruby-pty', 'deprecated': False}, + 'sax-pd': {'id': 'SAX-PD', 'deprecated': False}, + 'sax-pd-2.0': {'id': 'SAX-PD-2.0', 'deprecated': False}, + 'saxpath': {'id': 'Saxpath', 'deprecated': False}, + 'scea': {'id': 'SCEA', 'deprecated': False}, + 'schemereport': {'id': 'SchemeReport', 'deprecated': False}, + 'sendmail': {'id': 'Sendmail', 'deprecated': False}, + 'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False}, + 'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False}, + 'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False}, + 'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False}, + 'sgi-opengl': {'id': 'SGI-OpenGL', 'deprecated': False}, + 'sgp4': {'id': 'SGP4', 'deprecated': False}, + 'shl-0.5': {'id': 'SHL-0.5', 'deprecated': False}, + 'shl-0.51': {'id': 'SHL-0.51', 'deprecated': False}, + 'simpl-2.0': {'id': 'SimPL-2.0', 'deprecated': False}, + 'sissl': {'id': 'SISSL', 'deprecated': False}, + 'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False}, + 'sl': {'id': 'SL', 'deprecated': False}, + 'sleepycat': {'id': 'Sleepycat', 'deprecated': False}, + 'smlnj': {'id': 'SMLNJ', 'deprecated': False}, + 'smppl': {'id': 'SMPPL', 'deprecated': False}, + 'snia': {'id': 'SNIA', 'deprecated': False}, + 'snprintf': {'id': 'snprintf', 'deprecated': False}, + 'softsurfer': {'id': 'softSurfer', 'deprecated': False}, + 'soundex': {'id': 'Soundex', 'deprecated': False}, + 'spencer-86': {'id': 'Spencer-86', 'deprecated': False}, + 'spencer-94': {'id': 'Spencer-94', 'deprecated': False}, + 'spencer-99': {'id': 'Spencer-99', 'deprecated': False}, + 'spl-1.0': {'id': 'SPL-1.0', 'deprecated': False}, + 'ssh-keyscan': {'id': 'ssh-keyscan', 'deprecated': False}, + 'ssh-openssh': {'id': 'SSH-OpenSSH', 'deprecated': False}, + 'ssh-short': {'id': 'SSH-short', 'deprecated': False}, + 'ssleay-standalone': {'id': 'SSLeay-standalone', 'deprecated': False}, + 'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False}, + 'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True}, + 'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False}, + 'sun-ppp': {'id': 'Sun-PPP', 'deprecated': False}, + 'sun-ppp-2000': {'id': 'Sun-PPP-2000', 'deprecated': False}, + 'sunpro': {'id': 'SunPro', 'deprecated': False}, + 'swl': {'id': 'SWL', 'deprecated': False}, + 'swrule': {'id': 'swrule', 'deprecated': False}, + 'symlinks': {'id': 'Symlinks', 'deprecated': False}, + 'tapr-ohl-1.0': {'id': 'TAPR-OHL-1.0', 'deprecated': False}, + 'tcl': {'id': 'TCL', 'deprecated': False}, + 'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False}, + 'termreadkey': {'id': 'TermReadKey', 'deprecated': False}, + 'tgppl-1.0': {'id': 'TGPPL-1.0', 'deprecated': False}, + 'threeparttable': {'id': 'threeparttable', 'deprecated': False}, + 'tmate': {'id': 'TMate', 'deprecated': False}, + 'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False}, + 'tosl': {'id': 'TOSL', 'deprecated': False}, + 'tpdl': {'id': 'TPDL', 'deprecated': False}, + 'tpl-1.0': {'id': 'TPL-1.0', 'deprecated': False}, + 'ttwl': {'id': 'TTWL', 'deprecated': False}, + 'ttyp0': {'id': 'TTYP0', 'deprecated': False}, + 'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False}, + 'tu-berlin-2.0': {'id': 'TU-Berlin-2.0', 'deprecated': False}, + 'ubuntu-font-1.0': {'id': 'Ubuntu-font-1.0', 'deprecated': False}, + 'ucar': {'id': 'UCAR', 'deprecated': False}, + 'ucl-1.0': {'id': 'UCL-1.0', 'deprecated': False}, + 'ulem': {'id': 'ulem', 'deprecated': False}, + 'umich-merit': {'id': 'UMich-Merit', 'deprecated': False}, + 'unicode-3.0': {'id': 'Unicode-3.0', 'deprecated': False}, + 'unicode-dfs-2015': {'id': 'Unicode-DFS-2015', 'deprecated': False}, + 'unicode-dfs-2016': {'id': 'Unicode-DFS-2016', 'deprecated': False}, + 'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False}, + 'unixcrypt': {'id': 'UnixCrypt', 'deprecated': False}, + 'unlicense': {'id': 'Unlicense', 'deprecated': False}, + 'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False}, + 'urt-rle': {'id': 'URT-RLE', 'deprecated': False}, + 'vim': {'id': 'Vim', 'deprecated': False}, + 'vostrom': {'id': 'VOSTROM', 'deprecated': False}, + 'vsl-1.0': {'id': 'VSL-1.0', 'deprecated': False}, + 'w3c': {'id': 'W3C', 'deprecated': False}, + 'w3c-19980720': {'id': 'W3C-19980720', 'deprecated': False}, + 'w3c-20150513': {'id': 'W3C-20150513', 'deprecated': False}, + 'w3m': {'id': 'w3m', 'deprecated': False}, + 'watcom-1.0': {'id': 'Watcom-1.0', 'deprecated': False}, + 'widget-workshop': {'id': 'Widget-Workshop', 'deprecated': False}, + 'wsuipa': {'id': 'Wsuipa', 'deprecated': False}, + 'wtfpl': {'id': 'WTFPL', 'deprecated': False}, + 'wxwindows': {'id': 'wxWindows', 'deprecated': True}, + 'x11': {'id': 'X11', 'deprecated': False}, + 'x11-distribute-modifications-variant': {'id': 'X11-distribute-modifications-variant', 'deprecated': False}, + 'x11-swapped': {'id': 'X11-swapped', 'deprecated': False}, + 'xdebug-1.03': {'id': 'Xdebug-1.03', 'deprecated': False}, + 'xerox': {'id': 'Xerox', 'deprecated': False}, + 'xfig': {'id': 'Xfig', 'deprecated': False}, + 'xfree86-1.1': {'id': 'XFree86-1.1', 'deprecated': False}, + 'xinetd': {'id': 'xinetd', 'deprecated': False}, + 'xkeyboard-config-zinoviev': {'id': 'xkeyboard-config-Zinoviev', 'deprecated': False}, + 'xlock': {'id': 'xlock', 'deprecated': False}, + 'xnet': {'id': 'Xnet', 'deprecated': False}, + 'xpp': {'id': 'xpp', 'deprecated': False}, + 'xskat': {'id': 'XSkat', 'deprecated': False}, + 'xzoom': {'id': 'xzoom', 'deprecated': False}, + 'ypl-1.0': {'id': 'YPL-1.0', 'deprecated': False}, + 'ypl-1.1': {'id': 'YPL-1.1', 'deprecated': False}, + 'zed': {'id': 'Zed', 'deprecated': False}, + 'zeeff': {'id': 'Zeeff', 'deprecated': False}, + 'zend-2.0': {'id': 'Zend-2.0', 'deprecated': False}, + 'zimbra-1.3': {'id': 'Zimbra-1.3', 'deprecated': False}, + 'zimbra-1.4': {'id': 'Zimbra-1.4', 'deprecated': False}, + 'zlib': {'id': 'Zlib', 'deprecated': False}, + 'zlib-acknowledgement': {'id': 'zlib-acknowledgement', 'deprecated': False}, + 'zpl-1.1': {'id': 'ZPL-1.1', 'deprecated': False}, + 'zpl-2.0': {'id': 'ZPL-2.0', 'deprecated': False}, + 'zpl-2.1': {'id': 'ZPL-2.1', 'deprecated': False}, +} + +EXCEPTIONS: dict[str, SPDXException] = { + '389-exception': {'id': '389-exception', 'deprecated': False}, + 'asterisk-exception': {'id': 'Asterisk-exception', 'deprecated': False}, + 'asterisk-linking-protocols-exception': {'id': 'Asterisk-linking-protocols-exception', 'deprecated': False}, + 'autoconf-exception-2.0': {'id': 'Autoconf-exception-2.0', 'deprecated': False}, + 'autoconf-exception-3.0': {'id': 'Autoconf-exception-3.0', 'deprecated': False}, + 'autoconf-exception-generic': {'id': 'Autoconf-exception-generic', 'deprecated': False}, + 'autoconf-exception-generic-3.0': {'id': 'Autoconf-exception-generic-3.0', 'deprecated': False}, + 'autoconf-exception-macro': {'id': 'Autoconf-exception-macro', 'deprecated': False}, + 'bison-exception-1.24': {'id': 'Bison-exception-1.24', 'deprecated': False}, + 'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False}, + 'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False}, + 'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False}, + 'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False}, + 'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception', 'deprecated': False}, + 'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False}, + 'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False}, + 'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception', 'deprecated': False}, + 'fawkes-runtime-exception': {'id': 'Fawkes-Runtime-exception', 'deprecated': False}, + 'fltk-exception': {'id': 'FLTK-exception', 'deprecated': False}, + 'fmt-exception': {'id': 'fmt-exception', 'deprecated': False}, + 'font-exception-2.0': {'id': 'Font-exception-2.0', 'deprecated': False}, + 'freertos-exception-2.0': {'id': 'freertos-exception-2.0', 'deprecated': False}, + 'gcc-exception-2.0': {'id': 'GCC-exception-2.0', 'deprecated': False}, + 'gcc-exception-2.0-note': {'id': 'GCC-exception-2.0-note', 'deprecated': False}, + 'gcc-exception-3.1': {'id': 'GCC-exception-3.1', 'deprecated': False}, + 'gmsh-exception': {'id': 'Gmsh-exception', 'deprecated': False}, + 'gnat-exception': {'id': 'GNAT-exception', 'deprecated': False}, + 'gnome-examples-exception': {'id': 'GNOME-examples-exception', 'deprecated': False}, + 'gnu-compiler-exception': {'id': 'GNU-compiler-exception', 'deprecated': False}, + 'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False}, + 'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception', 'deprecated': False}, + 'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False}, + 'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False}, + 'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False}, + 'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005', 'deprecated': False}, + 'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008', 'deprecated': False}, + 'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False}, + 'kicad-libraries-exception': {'id': 'KiCad-libraries-exception', 'deprecated': False}, + 'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False}, + 'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception', 'deprecated': False}, + 'libtool-exception': {'id': 'Libtool-exception', 'deprecated': False}, + 'linux-syscall-note': {'id': 'Linux-syscall-note', 'deprecated': False}, + 'llgpl': {'id': 'LLGPL', 'deprecated': False}, + 'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False}, + 'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False}, + 'mif-exception': {'id': 'mif-exception', 'deprecated': False}, + 'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True}, + 'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False}, + 'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False}, + 'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False}, + 'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False}, + 'pcre2-exception': {'id': 'PCRE2-exception', 'deprecated': False}, + 'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False}, + 'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception', 'deprecated': False}, + 'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False}, + 'qt-lgpl-exception-1.1': {'id': 'Qt-LGPL-exception-1.1', 'deprecated': False}, + 'qwt-exception-1.0': {'id': 'Qwt-exception-1.0', 'deprecated': False}, + 'romic-exception': {'id': 'romic-exception', 'deprecated': False}, + 'rrdtool-floss-exception-2.0': {'id': 'RRDtool-FLOSS-exception-2.0', 'deprecated': False}, + 'sane-exception': {'id': 'SANE-exception', 'deprecated': False}, + 'shl-2.0': {'id': 'SHL-2.0', 'deprecated': False}, + 'shl-2.1': {'id': 'SHL-2.1', 'deprecated': False}, + 'stunnel-exception': {'id': 'stunnel-exception', 'deprecated': False}, + 'swi-exception': {'id': 'SWI-exception', 'deprecated': False}, + 'swift-exception': {'id': 'Swift-exception', 'deprecated': False}, + 'texinfo-exception': {'id': 'Texinfo-exception', 'deprecated': False}, + 'u-boot-exception-2.0': {'id': 'u-boot-exception-2.0', 'deprecated': False}, + 'ubdl-exception': {'id': 'UBDL-exception', 'deprecated': False}, + 'universal-foss-exception-1.0': {'id': 'Universal-FOSS-exception-1.0', 'deprecated': False}, + 'vsftpd-openssl-exception': {'id': 'vsftpd-openssl-exception', 'deprecated': False}, + 'wxwindows-exception-3.1': {'id': 'WxWindows-exception-3.1', 'deprecated': False}, + 'x11vnc-openssl-exception': {'id': 'x11vnc-openssl-exception', 'deprecated': False}, +} diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/markers.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/markers.py new file mode 100644 index 000000000..fb7f49cf8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/markers.py @@ -0,0 +1,331 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import operator +import os +import platform +import sys +from typing import Any, Callable, TypedDict, cast + +from ._parser import MarkerAtom, MarkerList, Op, Value, Variable +from ._parser import parse_marker as _parse_marker +from ._tokenizer import ParserSyntaxError +from .specifiers import InvalidSpecifier, Specifier +from .utils import canonicalize_name + +__all__ = [ + "InvalidMarker", + "Marker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "default_environment", +] + +Operator = Callable[[str, str], bool] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Environment(TypedDict): + implementation_name: str + """The implementation's identifier, e.g. ``'cpython'``.""" + + implementation_version: str + """ + The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or + ``'7.3.13'`` for PyPy3.10 v7.3.13. + """ + + os_name: str + """ + The value of :py:data:`os.name`. The name of the operating system dependent module + imported, e.g. ``'posix'``. + """ + + platform_machine: str + """ + Returns the machine type, e.g. ``'i386'``. + + An empty string if the value cannot be determined. + """ + + platform_release: str + """ + The system's release, e.g. ``'2.2.0'`` or ``'NT'``. + + An empty string if the value cannot be determined. + """ + + platform_system: str + """ + The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``. + + An empty string if the value cannot be determined. + """ + + platform_version: str + """ + The system's release version, e.g. ``'#3 on degas'``. + + An empty string if the value cannot be determined. + """ + + python_full_version: str + """ + The Python version as string ``'major.minor.patchlevel'``. + + Note that unlike the Python :py:data:`sys.version`, this value will always include + the patchlevel (it defaults to 0). + """ + + platform_python_implementation: str + """ + A string identifying the Python implementation, e.g. ``'CPython'``. + """ + + python_version: str + """The Python version as string ``'major.minor'``.""" + + sys_platform: str + """ + This string contains a platform identifier that can be used to append + platform-specific components to :py:data:`sys.path`, for instance. + + For Unix systems, except on Linux and AIX, this is the lowercased OS name as + returned by ``uname -s`` with the first part of the version as returned by + ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python + was built. + """ + + +def _normalize_extra_values(results: Any) -> Any: + """ + Normalize extra values. + """ + if isinstance(results[0], tuple): + lhs, op, rhs = results[0] + if isinstance(lhs, Variable) and lhs.value == "extra": + normalized_extra = canonicalize_name(rhs.value) + rhs = Value(normalized_extra) + elif isinstance(rhs, Variable) and rhs.value == "extra": + normalized_extra = canonicalize_name(lhs.value) + lhs = Value(normalized_extra) + results[0] = lhs, op, rhs + return results + + +def _format_marker( + marker: list[str] | MarkerAtom | str, first: bool | None = True +) -> str: + assert isinstance(marker, (list, tuple, str)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators: dict[str, Operator] = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs, prereleases=True) + + oper: Operator | None = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") + + return oper(lhs, rhs) + + +def _normalize(*values: str, key: str) -> tuple[str, ...]: + # PEP 685 – Comparison of extra names for optional distribution dependencies + # https://peps.python.org/pep-0685/ + # > When comparing extra names, tools MUST normalize the names being + # > compared using the semantics outlined in PEP 503 for names + if key == "extra": + return tuple(canonicalize_name(v) for v in values) + + # other environment markers don't have such standards + return values + + +def _evaluate_markers(markers: MarkerList, environment: dict[str, str]) -> bool: + groups: list[list[bool]] = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, str)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + environment_key = lhs.value + lhs_value = environment[environment_key] + rhs_value = rhs.value + else: + lhs_value = lhs.value + environment_key = rhs.value + rhs_value = environment[environment_key] + + lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key) + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info: sys._version_info) -> str: + version = f"{info.major}.{info.minor}.{info.micro}" + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment() -> Environment: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker: + def __init__(self, marker: str) -> None: + # Note: We create a Marker object without calling this constructor in + # packaging.requirements.Requirement. If any additional logic is + # added here, make sure to mirror/adapt Requirement. + try: + self._markers = _normalize_extra_values(_parse_marker(marker)) + # The attribute `_markers` can be described in terms of a recursive type: + # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]] + # + # For example, the following expression: + # python_version > "3.6" or (python_version == "3.6" and os_name == "unix") + # + # is parsed into: + # [ + # (, ')>, ), + # 'and', + # [ + # (, , ), + # 'or', + # (, , ) + # ] + # ] + except ParserSyntaxError as e: + raise InvalidMarker(str(e)) from e + + def __str__(self) -> str: + return _format_marker(self._markers) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash((self.__class__.__name__, str(self))) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Marker): + return NotImplemented + + return str(self) == str(other) + + def evaluate(self, environment: dict[str, str] | None = None) -> bool: + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = cast("dict[str, str]", default_environment()) + current_environment["extra"] = "" + if environment is not None: + current_environment.update(environment) + # The API used to allow setting extra to None. We need to handle this + # case for backwards compatibility. + if current_environment["extra"] is None: + current_environment["extra"] = "" + + return _evaluate_markers( + self._markers, _repair_python_full_version(current_environment) + ) + + +def _repair_python_full_version(env: dict[str, str]) -> dict[str, str]: + """ + Work around platform.python_version() returning something that is not PEP 440 + compliant for non-tagged Python builds. + """ + if env["python_full_version"].endswith("+"): + env["python_full_version"] += "local" + return env diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/metadata.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/metadata.py new file mode 100644 index 000000000..721f411cf --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/metadata.py @@ -0,0 +1,863 @@ +from __future__ import annotations + +import email.feedparser +import email.header +import email.message +import email.parser +import email.policy +import pathlib +import sys +import typing +from typing import ( + Any, + Callable, + Generic, + Literal, + TypedDict, + cast, +) + +from . import licenses, requirements, specifiers, utils +from . import version as version_module +from .licenses import NormalizedLicenseExpression + +T = typing.TypeVar("T") + + +if sys.version_info >= (3, 11): # pragma: no cover + ExceptionGroup = ExceptionGroup +else: # pragma: no cover + + class ExceptionGroup(Exception): + """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11. + + If :external:exc:`ExceptionGroup` is already defined by Python itself, + that version is used instead. + """ + + message: str + exceptions: list[Exception] + + def __init__(self, message: str, exceptions: list[Exception]) -> None: + self.message = message + self.exceptions = exceptions + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})" + + +class InvalidMetadata(ValueError): + """A metadata field contains invalid data.""" + + field: str + """The name of the field that contains invalid data.""" + + def __init__(self, field: str, message: str) -> None: + self.field = field + super().__init__(message) + + +# The RawMetadata class attempts to make as few assumptions about the underlying +# serialization formats as possible. The idea is that as long as a serialization +# formats offer some very basic primitives in *some* way then we can support +# serializing to and from that format. +class RawMetadata(TypedDict, total=False): + """A dictionary of raw core metadata. + + Each field in core metadata maps to a key of this dictionary (when data is + provided). The key is lower-case and underscores are used instead of dashes + compared to the equivalent core metadata field. Any core metadata field that + can be specified multiple times or can hold multiple values in a single + field have a key with a plural name. See :class:`Metadata` whose attributes + match the keys of this dictionary. + + Core metadata fields that can be specified multiple times are stored as a + list or dict depending on which is appropriate for the field. Any fields + which hold multiple values in a single field are stored as a list. + + """ + + # Metadata 1.0 - PEP 241 + metadata_version: str + name: str + version: str + platforms: list[str] + summary: str + description: str + keywords: list[str] + home_page: str + author: str + author_email: str + license: str + + # Metadata 1.1 - PEP 314 + supported_platforms: list[str] + download_url: str + classifiers: list[str] + requires: list[str] + provides: list[str] + obsoletes: list[str] + + # Metadata 1.2 - PEP 345 + maintainer: str + maintainer_email: str + requires_dist: list[str] + provides_dist: list[str] + obsoletes_dist: list[str] + requires_python: str + requires_external: list[str] + project_urls: dict[str, str] + + # Metadata 2.0 + # PEP 426 attempted to completely revamp the metadata format + # but got stuck without ever being able to build consensus on + # it and ultimately ended up withdrawn. + # + # However, a number of tools had started emitting METADATA with + # `2.0` Metadata-Version, so for historical reasons, this version + # was skipped. + + # Metadata 2.1 - PEP 566 + description_content_type: str + provides_extra: list[str] + + # Metadata 2.2 - PEP 643 + dynamic: list[str] + + # Metadata 2.3 - PEP 685 + # No new fields were added in PEP 685, just some edge case were + # tightened up to provide better interoptability. + + # Metadata 2.4 - PEP 639 + license_expression: str + license_files: list[str] + + +_STRING_FIELDS = { + "author", + "author_email", + "description", + "description_content_type", + "download_url", + "home_page", + "license", + "license_expression", + "maintainer", + "maintainer_email", + "metadata_version", + "name", + "requires_python", + "summary", + "version", +} + +_LIST_FIELDS = { + "classifiers", + "dynamic", + "license_files", + "obsoletes", + "obsoletes_dist", + "platforms", + "provides", + "provides_dist", + "provides_extra", + "requires", + "requires_dist", + "requires_external", + "supported_platforms", +} + +_DICT_FIELDS = { + "project_urls", +} + + +def _parse_keywords(data: str) -> list[str]: + """Split a string of comma-separated keywords into a list of keywords.""" + return [k.strip() for k in data.split(",")] + + +def _parse_project_urls(data: list[str]) -> dict[str, str]: + """Parse a list of label/URL string pairings separated by a comma.""" + urls = {} + for pair in data: + # Our logic is slightly tricky here as we want to try and do + # *something* reasonable with malformed data. + # + # The main thing that we have to worry about, is data that does + # not have a ',' at all to split the label from the Value. There + # isn't a singular right answer here, and we will fail validation + # later on (if the caller is validating) so it doesn't *really* + # matter, but since the missing value has to be an empty str + # and our return value is dict[str, str], if we let the key + # be the missing value, then they'd have multiple '' values that + # overwrite each other in a accumulating dict. + # + # The other potentional issue is that it's possible to have the + # same label multiple times in the metadata, with no solid "right" + # answer with what to do in that case. As such, we'll do the only + # thing we can, which is treat the field as unparseable and add it + # to our list of unparsed fields. + parts = [p.strip() for p in pair.split(",", 1)] + parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items + + # TODO: The spec doesn't say anything about if the keys should be + # considered case sensitive or not... logically they should + # be case-preserving and case-insensitive, but doing that + # would open up more cases where we might have duplicate + # entries. + label, url = parts + if label in urls: + # The label already exists in our set of urls, so this field + # is unparseable, and we can just add the whole thing to our + # unparseable data and stop processing it. + raise KeyError("duplicate labels in project urls") + urls[label] = url + + return urls + + +def _get_payload(msg: email.message.Message, source: bytes | str) -> str: + """Get the body of the message.""" + # If our source is a str, then our caller has managed encodings for us, + # and we don't need to deal with it. + if isinstance(source, str): + payload = msg.get_payload() + assert isinstance(payload, str) + return payload + # If our source is a bytes, then we're managing the encoding and we need + # to deal with it. + else: + bpayload = msg.get_payload(decode=True) + assert isinstance(bpayload, bytes) + try: + return bpayload.decode("utf8", "strict") + except UnicodeDecodeError as exc: + raise ValueError("payload in an invalid encoding") from exc + + +# The various parse_FORMAT functions here are intended to be as lenient as +# possible in their parsing, while still returning a correctly typed +# RawMetadata. +# +# To aid in this, we also generally want to do as little touching of the +# data as possible, except where there are possibly some historic holdovers +# that make valid data awkward to work with. +# +# While this is a lower level, intermediate format than our ``Metadata`` +# class, some light touch ups can make a massive difference in usability. + +# Map METADATA fields to RawMetadata. +_EMAIL_TO_RAW_MAPPING = { + "author": "author", + "author-email": "author_email", + "classifier": "classifiers", + "description": "description", + "description-content-type": "description_content_type", + "download-url": "download_url", + "dynamic": "dynamic", + "home-page": "home_page", + "keywords": "keywords", + "license": "license", + "license-expression": "license_expression", + "license-file": "license_files", + "maintainer": "maintainer", + "maintainer-email": "maintainer_email", + "metadata-version": "metadata_version", + "name": "name", + "obsoletes": "obsoletes", + "obsoletes-dist": "obsoletes_dist", + "platform": "platforms", + "project-url": "project_urls", + "provides": "provides", + "provides-dist": "provides_dist", + "provides-extra": "provides_extra", + "requires": "requires", + "requires-dist": "requires_dist", + "requires-external": "requires_external", + "requires-python": "requires_python", + "summary": "summary", + "supported-platform": "supported_platforms", + "version": "version", +} +_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()} + + +def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]: + """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``). + + This function returns a two-item tuple of dicts. The first dict is of + recognized fields from the core metadata specification. Fields that can be + parsed and translated into Python's built-in types are converted + appropriately. All other fields are left as-is. Fields that are allowed to + appear multiple times are stored as lists. + + The second dict contains all other fields from the metadata. This includes + any unrecognized fields. It also includes any fields which are expected to + be parsed into a built-in type but were not formatted appropriately. Finally, + any fields that are expected to appear only once but are repeated are + included in this dict. + + """ + raw: dict[str, str | list[str] | dict[str, str]] = {} + unparsed: dict[str, list[str]] = {} + + if isinstance(data, str): + parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data) + else: + parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data) + + # We have to wrap parsed.keys() in a set, because in the case of multiple + # values for a key (a list), the key will appear multiple times in the + # list of keys, but we're avoiding that by using get_all(). + for name in frozenset(parsed.keys()): + # Header names in RFC are case insensitive, so we'll normalize to all + # lower case to make comparisons easier. + name = name.lower() + + # We use get_all() here, even for fields that aren't multiple use, + # because otherwise someone could have e.g. two Name fields, and we + # would just silently ignore it rather than doing something about it. + headers = parsed.get_all(name) or [] + + # The way the email module works when parsing bytes is that it + # unconditionally decodes the bytes as ascii using the surrogateescape + # handler. When you pull that data back out (such as with get_all() ), + # it looks to see if the str has any surrogate escapes, and if it does + # it wraps it in a Header object instead of returning the string. + # + # As such, we'll look for those Header objects, and fix up the encoding. + value = [] + # Flag if we have run into any issues processing the headers, thus + # signalling that the data belongs in 'unparsed'. + valid_encoding = True + for h in headers: + # It's unclear if this can return more types than just a Header or + # a str, so we'll just assert here to make sure. + assert isinstance(h, (email.header.Header, str)) + + # If it's a header object, we need to do our little dance to get + # the real data out of it. In cases where there is invalid data + # we're going to end up with mojibake, but there's no obvious, good + # way around that without reimplementing parts of the Header object + # ourselves. + # + # That should be fine since, if mojibacked happens, this key is + # going into the unparsed dict anyways. + if isinstance(h, email.header.Header): + # The Header object stores it's data as chunks, and each chunk + # can be independently encoded, so we'll need to check each + # of them. + chunks: list[tuple[bytes, str | None]] = [] + for bin, encoding in email.header.decode_header(h): + try: + bin.decode("utf8", "strict") + except UnicodeDecodeError: + # Enable mojibake. + encoding = "latin1" + valid_encoding = False + else: + encoding = "utf8" + chunks.append((bin, encoding)) + + # Turn our chunks back into a Header object, then let that + # Header object do the right thing to turn them into a + # string for us. + value.append(str(email.header.make_header(chunks))) + # This is already a string, so just add it. + else: + value.append(h) + + # We've processed all of our values to get them into a list of str, + # but we may have mojibake data, in which case this is an unparsed + # field. + if not valid_encoding: + unparsed[name] = value + continue + + raw_name = _EMAIL_TO_RAW_MAPPING.get(name) + if raw_name is None: + # This is a bit of a weird situation, we've encountered a key that + # we don't know what it means, so we don't know whether it's meant + # to be a list or not. + # + # Since we can't really tell one way or another, we'll just leave it + # as a list, even though it may be a single item list, because that's + # what makes the most sense for email headers. + unparsed[name] = value + continue + + # If this is one of our string fields, then we'll check to see if our + # value is a list of a single item. If it is then we'll assume that + # it was emitted as a single string, and unwrap the str from inside + # the list. + # + # If it's any other kind of data, then we haven't the faintest clue + # what we should parse it as, and we have to just add it to our list + # of unparsed stuff. + if raw_name in _STRING_FIELDS and len(value) == 1: + raw[raw_name] = value[0] + # If this is one of our list of string fields, then we can just assign + # the value, since email *only* has strings, and our get_all() call + # above ensures that this is a list. + elif raw_name in _LIST_FIELDS: + raw[raw_name] = value + # Special Case: Keywords + # The keywords field is implemented in the metadata spec as a str, + # but it conceptually is a list of strings, and is serialized using + # ", ".join(keywords), so we'll do some light data massaging to turn + # this into what it logically is. + elif raw_name == "keywords" and len(value) == 1: + raw[raw_name] = _parse_keywords(value[0]) + # Special Case: Project-URL + # The project urls is implemented in the metadata spec as a list of + # specially-formatted strings that represent a key and a value, which + # is fundamentally a mapping, however the email format doesn't support + # mappings in a sane way, so it was crammed into a list of strings + # instead. + # + # We will do a little light data massaging to turn this into a map as + # it logically should be. + elif raw_name == "project_urls": + try: + raw[raw_name] = _parse_project_urls(value) + except KeyError: + unparsed[name] = value + # Nothing that we've done has managed to parse this, so it'll just + # throw it in our unparseable data and move on. + else: + unparsed[name] = value + + # We need to support getting the Description from the message payload in + # addition to getting it from the the headers. This does mean, though, there + # is the possibility of it being set both ways, in which case we put both + # in 'unparsed' since we don't know which is right. + try: + payload = _get_payload(parsed, data) + except ValueError: + unparsed.setdefault("description", []).append( + parsed.get_payload(decode=isinstance(data, bytes)) # type: ignore[call-overload] + ) + else: + if payload: + # Check to see if we've already got a description, if so then both + # it, and this body move to unparseable. + if "description" in raw: + description_header = cast(str, raw.pop("description")) + unparsed.setdefault("description", []).extend( + [description_header, payload] + ) + elif "description" in unparsed: + unparsed["description"].append(payload) + else: + raw["description"] = payload + + # We need to cast our `raw` to a metadata, because a TypedDict only support + # literal key names, but we're computing our key names on purpose, but the + # way this function is implemented, our `TypedDict` can only have valid key + # names. + return cast(RawMetadata, raw), unparsed + + +_NOT_FOUND = object() + + +# Keep the two values in sync. +_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"] +_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"] + +_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"]) + + +class _Validator(Generic[T]): + """Validate a metadata field. + + All _process_*() methods correspond to a core metadata field. The method is + called with the field's raw value. If the raw value is valid it is returned + in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field). + If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause + as appropriate). + """ + + name: str + raw_name: str + added: _MetadataVersion + + def __init__( + self, + *, + added: _MetadataVersion = "1.0", + ) -> None: + self.added = added + + def __set_name__(self, _owner: Metadata, name: str) -> None: + self.name = name + self.raw_name = _RAW_TO_EMAIL_MAPPING[name] + + def __get__(self, instance: Metadata, _owner: type[Metadata]) -> T: + # With Python 3.8, the caching can be replaced with functools.cached_property(). + # No need to check the cache as attribute lookup will resolve into the + # instance's __dict__ before __get__ is called. + cache = instance.__dict__ + value = instance._raw.get(self.name) + + # To make the _process_* methods easier, we'll check if the value is None + # and if this field is NOT a required attribute, and if both of those + # things are true, we'll skip the the converter. This will mean that the + # converters never have to deal with the None union. + if self.name in _REQUIRED_ATTRS or value is not None: + try: + converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}") + except AttributeError: + pass + else: + value = converter(value) + + cache[self.name] = value + try: + del instance._raw[self.name] # type: ignore[misc] + except KeyError: + pass + + return cast(T, value) + + def _invalid_metadata( + self, msg: str, cause: Exception | None = None + ) -> InvalidMetadata: + exc = InvalidMetadata( + self.raw_name, msg.format_map({"field": repr(self.raw_name)}) + ) + exc.__cause__ = cause + return exc + + def _process_metadata_version(self, value: str) -> _MetadataVersion: + # Implicitly makes Metadata-Version required. + if value not in _VALID_METADATA_VERSIONS: + raise self._invalid_metadata(f"{value!r} is not a valid metadata version") + return cast(_MetadataVersion, value) + + def _process_name(self, value: str) -> str: + if not value: + raise self._invalid_metadata("{field} is a required field") + # Validate the name as a side-effect. + try: + utils.canonicalize_name(value, validate=True) + except utils.InvalidName as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + else: + return value + + def _process_version(self, value: str) -> version_module.Version: + if not value: + raise self._invalid_metadata("{field} is a required field") + try: + return version_module.parse(value) + except version_module.InvalidVersion as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + + def _process_summary(self, value: str) -> str: + """Check the field contains no newlines.""" + if "\n" in value: + raise self._invalid_metadata("{field} must be a single line") + return value + + def _process_description_content_type(self, value: str) -> str: + content_types = {"text/plain", "text/x-rst", "text/markdown"} + message = email.message.EmailMessage() + message["content-type"] = value + + content_type, parameters = ( + # Defaults to `text/plain` if parsing failed. + message.get_content_type().lower(), + message["content-type"].params, + ) + # Check if content-type is valid or defaulted to `text/plain` and thus was + # not parseable. + if content_type not in content_types or content_type not in value.lower(): + raise self._invalid_metadata( + f"{{field}} must be one of {list(content_types)}, not {value!r}" + ) + + charset = parameters.get("charset", "UTF-8") + if charset != "UTF-8": + raise self._invalid_metadata( + f"{{field}} can only specify the UTF-8 charset, not {list(charset)}" + ) + + markdown_variants = {"GFM", "CommonMark"} + variant = parameters.get("variant", "GFM") # Use an acceptable default. + if content_type == "text/markdown" and variant not in markdown_variants: + raise self._invalid_metadata( + f"valid Markdown variants for {{field}} are {list(markdown_variants)}, " + f"not {variant!r}", + ) + return value + + def _process_dynamic(self, value: list[str]) -> list[str]: + for dynamic_field in map(str.lower, value): + if dynamic_field in {"name", "version", "metadata-version"}: + raise self._invalid_metadata( + f"{dynamic_field!r} is not allowed as a dynamic field" + ) + elif dynamic_field not in _EMAIL_TO_RAW_MAPPING: + raise self._invalid_metadata( + f"{dynamic_field!r} is not a valid dynamic field" + ) + return list(map(str.lower, value)) + + def _process_provides_extra( + self, + value: list[str], + ) -> list[utils.NormalizedName]: + normalized_names = [] + try: + for name in value: + normalized_names.append(utils.canonicalize_name(name, validate=True)) + except utils.InvalidName as exc: + raise self._invalid_metadata( + f"{name!r} is invalid for {{field}}", cause=exc + ) from exc + else: + return normalized_names + + def _process_requires_python(self, value: str) -> specifiers.SpecifierSet: + try: + return specifiers.SpecifierSet(value) + except specifiers.InvalidSpecifier as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + + def _process_requires_dist( + self, + value: list[str], + ) -> list[requirements.Requirement]: + reqs = [] + try: + for req in value: + reqs.append(requirements.Requirement(req)) + except requirements.InvalidRequirement as exc: + raise self._invalid_metadata( + f"{req!r} is invalid for {{field}}", cause=exc + ) from exc + else: + return reqs + + def _process_license_expression( + self, value: str + ) -> NormalizedLicenseExpression | None: + try: + return licenses.canonicalize_license_expression(value) + except ValueError as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + + def _process_license_files(self, value: list[str]) -> list[str]: + paths = [] + for path in value: + if ".." in path: + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, " + "parent directory indicators are not allowed" + ) + if "*" in path: + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, paths must be resolved" + ) + if ( + pathlib.PurePosixPath(path).is_absolute() + or pathlib.PureWindowsPath(path).is_absolute() + ): + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, paths must be relative" + ) + if pathlib.PureWindowsPath(path).as_posix() != path: + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, " + "paths must use '/' delimiter" + ) + paths.append(path) + return paths + + +class Metadata: + """Representation of distribution metadata. + + Compared to :class:`RawMetadata`, this class provides objects representing + metadata fields instead of only using built-in types. Any invalid metadata + will cause :exc:`InvalidMetadata` to be raised (with a + :py:attr:`~BaseException.__cause__` attribute as appropriate). + """ + + _raw: RawMetadata + + @classmethod + def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> Metadata: + """Create an instance from :class:`RawMetadata`. + + If *validate* is true, all metadata will be validated. All exceptions + related to validation will be gathered and raised as an :class:`ExceptionGroup`. + """ + ins = cls() + ins._raw = data.copy() # Mutations occur due to caching enriched values. + + if validate: + exceptions: list[Exception] = [] + try: + metadata_version = ins.metadata_version + metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version) + except InvalidMetadata as metadata_version_exc: + exceptions.append(metadata_version_exc) + metadata_version = None + + # Make sure to check for the fields that are present, the required + # fields (so their absence can be reported). + fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS + # Remove fields that have already been checked. + fields_to_check -= {"metadata_version"} + + for key in fields_to_check: + try: + if metadata_version: + # Can't use getattr() as that triggers descriptor protocol which + # will fail due to no value for the instance argument. + try: + field_metadata_version = cls.__dict__[key].added + except KeyError: + exc = InvalidMetadata(key, f"unrecognized field: {key!r}") + exceptions.append(exc) + continue + field_age = _VALID_METADATA_VERSIONS.index( + field_metadata_version + ) + if field_age > metadata_age: + field = _RAW_TO_EMAIL_MAPPING[key] + exc = InvalidMetadata( + field, + f"{field} introduced in metadata version " + f"{field_metadata_version}, not {metadata_version}", + ) + exceptions.append(exc) + continue + getattr(ins, key) + except InvalidMetadata as exc: + exceptions.append(exc) + + if exceptions: + raise ExceptionGroup("invalid metadata", exceptions) + + return ins + + @classmethod + def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata: + """Parse metadata from email headers. + + If *validate* is true, the metadata will be validated. All exceptions + related to validation will be gathered and raised as an :class:`ExceptionGroup`. + """ + raw, unparsed = parse_email(data) + + if validate: + exceptions: list[Exception] = [] + for unparsed_key in unparsed: + if unparsed_key in _EMAIL_TO_RAW_MAPPING: + message = f"{unparsed_key!r} has invalid data" + else: + message = f"unrecognized field: {unparsed_key!r}" + exceptions.append(InvalidMetadata(unparsed_key, message)) + + if exceptions: + raise ExceptionGroup("unparsed", exceptions) + + try: + return cls.from_raw(raw, validate=validate) + except ExceptionGroup as exc_group: + raise ExceptionGroup( + "invalid or unparsed metadata", exc_group.exceptions + ) from None + + metadata_version: _Validator[_MetadataVersion] = _Validator() + """:external:ref:`core-metadata-metadata-version` + (required; validated to be a valid metadata version)""" + # `name` is not normalized/typed to NormalizedName so as to provide access to + # the original/raw name. + name: _Validator[str] = _Validator() + """:external:ref:`core-metadata-name` + (required; validated using :func:`~packaging.utils.canonicalize_name` and its + *validate* parameter)""" + version: _Validator[version_module.Version] = _Validator() + """:external:ref:`core-metadata-version` (required)""" + dynamic: _Validator[list[str] | None] = _Validator( + added="2.2", + ) + """:external:ref:`core-metadata-dynamic` + (validated against core metadata field names and lowercased)""" + platforms: _Validator[list[str] | None] = _Validator() + """:external:ref:`core-metadata-platform`""" + supported_platforms: _Validator[list[str] | None] = _Validator(added="1.1") + """:external:ref:`core-metadata-supported-platform`""" + summary: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-summary` (validated to contain no newlines)""" + description: _Validator[str | None] = _Validator() # TODO 2.1: can be in body + """:external:ref:`core-metadata-description`""" + description_content_type: _Validator[str | None] = _Validator(added="2.1") + """:external:ref:`core-metadata-description-content-type` (validated)""" + keywords: _Validator[list[str] | None] = _Validator() + """:external:ref:`core-metadata-keywords`""" + home_page: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-home-page`""" + download_url: _Validator[str | None] = _Validator(added="1.1") + """:external:ref:`core-metadata-download-url`""" + author: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-author`""" + author_email: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-author-email`""" + maintainer: _Validator[str | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-maintainer`""" + maintainer_email: _Validator[str | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-maintainer-email`""" + license: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-license`""" + license_expression: _Validator[NormalizedLicenseExpression | None] = _Validator( + added="2.4" + ) + """:external:ref:`core-metadata-license-expression`""" + license_files: _Validator[list[str] | None] = _Validator(added="2.4") + """:external:ref:`core-metadata-license-file`""" + classifiers: _Validator[list[str] | None] = _Validator(added="1.1") + """:external:ref:`core-metadata-classifier`""" + requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator( + added="1.2" + ) + """:external:ref:`core-metadata-requires-dist`""" + requires_python: _Validator[specifiers.SpecifierSet | None] = _Validator( + added="1.2" + ) + """:external:ref:`core-metadata-requires-python`""" + # Because `Requires-External` allows for non-PEP 440 version specifiers, we + # don't do any processing on the values. + requires_external: _Validator[list[str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-requires-external`""" + project_urls: _Validator[dict[str, str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-project-url`""" + # PEP 685 lets us raise an error if an extra doesn't pass `Name` validation + # regardless of metadata version. + provides_extra: _Validator[list[utils.NormalizedName] | None] = _Validator( + added="2.1", + ) + """:external:ref:`core-metadata-provides-extra`""" + provides_dist: _Validator[list[str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-provides-dist`""" + obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-obsoletes-dist`""" + requires: _Validator[list[str] | None] = _Validator(added="1.1") + """``Requires`` (deprecated)""" + provides: _Validator[list[str] | None] = _Validator(added="1.1") + """``Provides`` (deprecated)""" + obsoletes: _Validator[list[str] | None] = _Validator(added="1.1") + """``Obsoletes`` (deprecated)""" diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/py.typed b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/requirements.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/requirements.py new file mode 100644 index 000000000..4e068c956 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/requirements.py @@ -0,0 +1,91 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import annotations + +from typing import Any, Iterator + +from ._parser import parse_requirement as _parse_requirement +from ._tokenizer import ParserSyntaxError +from .markers import Marker, _normalize_extra_values +from .specifiers import SpecifierSet +from .utils import canonicalize_name + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +class Requirement: + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string: str) -> None: + try: + parsed = _parse_requirement(requirement_string) + except ParserSyntaxError as e: + raise InvalidRequirement(str(e)) from e + + self.name: str = parsed.name + self.url: str | None = parsed.url or None + self.extras: set[str] = set(parsed.extras or []) + self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) + self.marker: Marker | None = None + if parsed.marker is not None: + self.marker = Marker.__new__(Marker) + self.marker._markers = _normalize_extra_values(parsed.marker) + + def _iter_parts(self, name: str) -> Iterator[str]: + yield name + + if self.extras: + formatted_extras = ",".join(sorted(self.extras)) + yield f"[{formatted_extras}]" + + if self.specifier: + yield str(self.specifier) + + if self.url: + yield f"@ {self.url}" + if self.marker: + yield " " + + if self.marker: + yield f"; {self.marker}" + + def __str__(self) -> str: + return "".join(self._iter_parts(self.name)) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash( + ( + self.__class__.__name__, + *self._iter_parts(canonicalize_name(self.name)), + ) + ) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Requirement): + return NotImplemented + + return ( + canonicalize_name(self.name) == canonicalize_name(other.name) + and self.extras == other.extras + and self.specifier == other.specifier + and self.url == other.url + and self.marker == other.marker + ) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/specifiers.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/specifiers.py new file mode 100644 index 000000000..f18016e16 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/specifiers.py @@ -0,0 +1,1020 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +""" +.. testsetup:: + + from pip._vendor.packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier + from pip._vendor.packaging.version import Version +""" + +from __future__ import annotations + +import abc +import itertools +import re +from typing import Callable, Iterable, Iterator, TypeVar, Union + +from .utils import canonicalize_version +from .version import Version + +UnparsedVersion = Union[Version, str] +UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) +CallableOperator = Callable[[Version, str], bool] + + +def _coerce_version(version: UnparsedVersion) -> Version: + if not isinstance(version, Version): + version = Version(version) + return version + + +class InvalidSpecifier(ValueError): + """ + Raised when attempting to create a :class:`Specifier` with a specifier + string that is invalid. + + >>> Specifier("lolwat") + Traceback (most recent call last): + ... + packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat' + """ + + +class BaseSpecifier(metaclass=abc.ABCMeta): + @abc.abstractmethod + def __str__(self) -> str: + """ + Returns the str representation of this Specifier-like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self) -> int: + """ + Returns a hash value for this Specifier-like object. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Returns a boolean representing whether or not the two Specifier-like + objects are equal. + + :param other: The other object to check against. + """ + + @property + @abc.abstractmethod + def prereleases(self) -> bool | None: + """Whether or not pre-releases as a whole are allowed. + + This can be set to either ``True`` or ``False`` to explicitly enable or disable + prereleases or it can be set to ``None`` (the default) to use default semantics. + """ + + @prereleases.setter + def prereleases(self, value: bool) -> None: + """Setter for :attr:`prereleases`. + + :param value: The value to set. + """ + + @abc.abstractmethod + def contains(self, item: str, prereleases: bool | None = None) -> bool: + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None + ) -> Iterator[UnparsedVersionVar]: + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class Specifier(BaseSpecifier): + """This class abstracts handling of version specifiers. + + .. tip:: + + It is generally not required to instantiate this manually. You should instead + prefer to work with :class:`SpecifierSet` instead, which can parse + comma-separated version specifiers (which is what package metadata contains). + """ + + _operator_regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + """ + _version_regex_str = r""" + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s;)]* # The arbitrary version can be just about anything, + # we match everything except for whitespace, a + # semi-colon for marker support, and a closing paren + # since versions can be enclosed in them. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + + # You cannot use a wild card and a pre-release, post-release, a dev or + # local version together so group them with a | and make them optional. + (?: + \.\* # Wild card syntax of .* + | + (?: # pre release + [-_\.]? + (alpha|beta|preview|pre|a|b|c|rc) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (alpha|beta|preview|pre|a|b|c|rc) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + def __init__(self, spec: str = "", prereleases: bool | None = None) -> None: + """Initialize a Specifier instance. + + :param spec: + The string representation of a specifier which will be parsed and + normalized before use. + :param prereleases: + This tells the specifier if it should accept prerelease versions if + applicable or not. The default of ``None`` will autodetect it from the + given specifiers. + :raises InvalidSpecifier: + If the given specifier is invalid (i.e. bad syntax). + """ + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier(f"Invalid specifier: {spec!r}") + + self._spec: tuple[str, str] = ( + match.group("operator").strip(), + match.group("version").strip(), + ) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515 + @property # type: ignore[override] + def prereleases(self) -> bool: + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "===", ">", "<"]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if Version(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + @property + def operator(self) -> str: + """The operator of this specifier. + + >>> Specifier("==1.2.3").operator + '==' + """ + return self._spec[0] + + @property + def version(self) -> str: + """The version of this specifier. + + >>> Specifier("==1.2.3").version + '1.2.3' + """ + return self._spec[1] + + def __repr__(self) -> str: + """A representation of the Specifier that shows all internal state. + + >>> Specifier('>=1.0.0') + =1.0.0')> + >>> Specifier('>=1.0.0', prereleases=False) + =1.0.0', prereleases=False)> + >>> Specifier('>=1.0.0', prereleases=True) + =1.0.0', prereleases=True)> + """ + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return f"<{self.__class__.__name__}({str(self)!r}{pre})>" + + def __str__(self) -> str: + """A string representation of the Specifier that can be round-tripped. + + >>> str(Specifier('>=1.0.0')) + '>=1.0.0' + >>> str(Specifier('>=1.0.0', prereleases=False)) + '>=1.0.0' + """ + return "{}{}".format(*self._spec) + + @property + def _canonical_spec(self) -> tuple[str, str]: + canonical_version = canonicalize_version( + self._spec[1], + strip_trailing_zero=(self._spec[0] != "~="), + ) + return self._spec[0], canonical_version + + def __hash__(self) -> int: + return hash(self._canonical_spec) + + def __eq__(self, other: object) -> bool: + """Whether or not the two Specifier-like objects are equal. + + :param other: The other object to check against. + + The value of :attr:`prereleases` is ignored. + + >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0") + True + >>> (Specifier("==1.2.3", prereleases=False) == + ... Specifier("==1.2.3", prereleases=True)) + True + >>> Specifier("==1.2.3") == "==1.2.3" + True + >>> Specifier("==1.2.3") == Specifier("==1.2.4") + False + >>> Specifier("==1.2.3") == Specifier("~=1.2.3") + False + """ + if isinstance(other, str): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._canonical_spec == other._canonical_spec + + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) + return operator_callable + + def _compare_compatible(self, prospective: Version, spec: str) -> bool: + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore suffix segments. + prefix = _version_join( + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + def _compare_equal(self, prospective: Version, spec: str) -> bool: + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + normalized_prospective = canonicalize_version( + prospective.public, strip_trailing_zero=False + ) + # Get the normalized version string ignoring the trailing .* + normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False) + # Split the spec out by bangs and dots, and pretend that there is + # an implicit dot in between a release segment and a pre-release segment. + split_spec = _version_split(normalized_spec) + + # Split the prospective version out by bangs and dots, and pretend + # that there is an implicit dot in between a release segment and + # a pre-release segment. + split_prospective = _version_split(normalized_prospective) + + # 0-pad the prospective version before shortening it to get the correct + # shortened version. + padded_prospective, _ = _pad_version(split_prospective, split_spec) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + shortened_prospective = padded_prospective[: len(split_spec)] + + return shortened_prospective == split_spec + else: + # Convert our spec string into a Version + spec_version = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec_version.local: + prospective = Version(prospective.public) + + return prospective == spec_version + + def _compare_not_equal(self, prospective: Version, spec: str) -> bool: + return not self._compare_equal(prospective, spec) + + def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) + + def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) + + def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: + return str(prospective).lower() == str(spec).lower() + + def __contains__(self, item: str | Version) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: The item to check for. + + This is used for the ``in`` operator and behaves the same as + :meth:`contains` with no ``prereleases`` argument passed. + + >>> "1.2.3" in Specifier(">=1.2.3") + True + >>> Version("1.2.3") in Specifier(">=1.2.3") + True + >>> "1.0.0" in Specifier(">=1.2.3") + False + >>> "1.3.0a1" in Specifier(">=1.2.3") + False + >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True) + True + """ + return self.contains(item) + + def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: + The item to check for, which can be a version string or a + :class:`Version` instance. + :param prereleases: + Whether or not to match prereleases with this Specifier. If set to + ``None`` (the default), it uses :attr:`prereleases` to determine + whether or not prereleases are allowed. + + >>> Specifier(">=1.2.3").contains("1.2.3") + True + >>> Specifier(">=1.2.3").contains(Version("1.2.3")) + True + >>> Specifier(">=1.2.3").contains("1.0.0") + False + >>> Specifier(">=1.2.3").contains("1.3.0a1") + False + >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1") + True + >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True) + True + """ + + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version, this allows us to have a shortcut for + # "2.0" in Specifier(">=2") + normalized_item = _coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if normalized_item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + operator_callable: CallableOperator = self._get_operator(self.operator) + return operator_callable(normalized_item, self.version) + + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None + ) -> Iterator[UnparsedVersionVar]: + """Filter items in the given iterable, that match the specifier. + + :param iterable: + An iterable that can contain version strings and :class:`Version` instances. + The items in the iterable will be filtered according to the specifier. + :param prereleases: + Whether or not to allow prereleases in the returned iterator. If set to + ``None`` (the default), it will be intelligently decide whether to allow + prereleases or not (based on the :attr:`prereleases` attribute, and + whether the only versions matching are prereleases). + + This method is smarter than just ``filter(Specifier().contains, [...])`` + because it implements the rule from :pep:`440` that a prerelease item + SHOULD be accepted if no other versions match the given specifier. + + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) + ['1.3'] + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")])) + ['1.2.3', '1.3', ] + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"])) + ['1.5a1'] + >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + """ + + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = _coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later in case nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version: str) -> list[str]: + """Split version into components. + + The split components are intended for version comparison. The logic does + not attempt to retain the original version string, so joining the + components back with :func:`_version_join` may not produce the original + version string. + """ + result: list[str] = [] + + epoch, _, rest = version.rpartition("!") + result.append(epoch or "0") + + for item in rest.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _version_join(components: list[str]) -> str: + """Join split version components into a version string. + + This function assumes the input came from :func:`_version_split`, where the + first component must be the epoch (either empty or numeric), and all other + components numeric. + """ + epoch, *rest = components + return f"{epoch}!{'.'.join(rest)}" + + +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]: + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return ( + list(itertools.chain.from_iterable(left_split)), + list(itertools.chain.from_iterable(right_split)), + ) + + +class SpecifierSet(BaseSpecifier): + """This class abstracts handling of a set of version specifiers. + + It can be passed a single specifier (``>=3.0``), a comma-separated list of + specifiers (``>=3.0,!=3.1``), or no specifier at all. + """ + + def __init__( + self, + specifiers: str | Iterable[Specifier] = "", + prereleases: bool | None = None, + ) -> None: + """Initialize a SpecifierSet instance. + + :param specifiers: + The string representation of a specifier or a comma-separated list of + specifiers which will be parsed and normalized before use. + May also be an iterable of ``Specifier`` instances, which will be used + as is. + :param prereleases: + This tells the SpecifierSet if it should accept prerelease versions if + applicable or not. The default of ``None`` will autodetect it from the + given specifiers. + + :raises InvalidSpecifier: + If the given ``specifiers`` are not parseable than this exception will be + raised. + """ + + if isinstance(specifiers, str): + # Split on `,` to break each individual specifier into its own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Make each individual specifier a Specifier and save in a frozen set + # for later. + self._specs = frozenset(map(Specifier, split_specifiers)) + else: + # Save the supplied specifiers in a frozen set. + self._specs = frozenset(specifiers) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + @property + def prereleases(self) -> bool | None: + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + def __repr__(self) -> str: + """A representation of the specifier set that shows all internal state. + + Note that the ordering of the individual specifiers within the set may not + match the input string. + + >>> SpecifierSet('>=1.0.0,!=2.0.0') + =1.0.0')> + >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False) + =1.0.0', prereleases=False)> + >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True) + =1.0.0', prereleases=True)> + """ + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return f"" + + def __str__(self) -> str: + """A string representation of the specifier set that can be round-tripped. + + Note that the ordering of the individual specifiers within the set may not + match the input string. + + >>> str(SpecifierSet(">=1.0.0,!=1.0.1")) + '!=1.0.1,>=1.0.0' + >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False)) + '!=1.0.1,>=1.0.0' + """ + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self) -> int: + return hash(self._specs) + + def __and__(self, other: SpecifierSet | str) -> SpecifierSet: + """Return a SpecifierSet which is a combination of the two sets. + + :param other: The other object to combine with. + + >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1' + =1.0.0')> + >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1') + =1.0.0')> + """ + if isinstance(other, str): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other: object) -> bool: + """Whether or not the two SpecifierSet-like objects are equal. + + :param other: The other object to check against. + + The value of :attr:`prereleases` is ignored. + + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) == + ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)) + True + >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1" + True + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2") + False + """ + if isinstance(other, (str, Specifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __len__(self) -> int: + """Returns the number of specifiers in this specifier set.""" + return len(self._specs) + + def __iter__(self) -> Iterator[Specifier]: + """ + Returns an iterator over all the underlying :class:`Specifier` instances + in this specifier set. + + >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str) + [, =1.0.0')>] + """ + return iter(self._specs) + + def __contains__(self, item: UnparsedVersion) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: The item to check for. + + This is used for the ``in`` operator and behaves the same as + :meth:`contains` with no ``prereleases`` argument passed. + + >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1") + False + >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1") + False + >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True) + True + """ + return self.contains(item) + + def contains( + self, + item: UnparsedVersion, + prereleases: bool | None = None, + installed: bool | None = None, + ) -> bool: + """Return whether or not the item is contained in this SpecifierSet. + + :param item: + The item to check for, which can be a version string or a + :class:`Version` instance. + :param prereleases: + Whether or not to match prereleases with this SpecifierSet. If set to + ``None`` (the default), it uses :attr:`prereleases` to determine + whether or not prereleases are allowed. + + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3") + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3")) + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1") + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True) + True + """ + # Ensure that our item is a Version instance. + if not isinstance(item, Version): + item = Version(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + if installed and item.is_prerelease: + item = Version(item.base_version) + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None + ) -> Iterator[UnparsedVersionVar]: + """Filter items in the given iterable, that match the specifiers in this set. + + :param iterable: + An iterable that can contain version strings and :class:`Version` instances. + The items in the iterable will be filtered according to the specifier. + :param prereleases: + Whether or not to allow prereleases in the returned iterator. If set to + ``None`` (the default), it will be intelligently decide whether to allow + prereleases or not (based on the :attr:`prereleases` attribute, and + whether the only versions matching are prereleases). + + This method is smarter than just ``filter(SpecifierSet(...).contains, [...])`` + because it implements the rule from :pep:`440` that a prerelease item + SHOULD be accepted if no other versions match the given specifier. + + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) + ['1.3'] + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")])) + ['1.3', ] + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"])) + [] + >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + + An "empty" SpecifierSet will filter items based on the presence of prerelease + versions in the set. + + >>> list(SpecifierSet("").filter(["1.3", "1.5a1"])) + ['1.3'] + >>> list(SpecifierSet("").filter(["1.5a1"])) + ['1.5a1'] + >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + """ + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iter(iterable) + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases. + else: + filtered: list[UnparsedVersionVar] = [] + found_prereleases: list[UnparsedVersionVar] = [] + + for item in iterable: + parsed_version = _coerce_version(item) + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return iter(found_prereleases) + + return iter(filtered) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/tags.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/tags.py new file mode 100644 index 000000000..f5903402a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/tags.py @@ -0,0 +1,617 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import logging +import platform +import re +import struct +import subprocess +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Iterable, + Iterator, + Sequence, + Tuple, + cast, +) + +from . import _manylinux, _musllinux + +logger = logging.getLogger(__name__) + +PythonVersion = Sequence[int] +AppleVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: dict[str, str] = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = struct.calcsize("P") == 4 + + +class Tag: + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_abi", "_hash", "_interpreter", "_platform"] + + def __init__(self, interpreter: str, abi: str, platform: str) -> None: + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self) -> str: + return self._interpreter + + @property + def abi(self) -> str: + return self._abi + + @property + def platform(self) -> str: + return self._platform + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) + ) + + def __hash__(self) -> int: + return self._hash + + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" + + def __repr__(self) -> str: + return f"<{self} @ {id(self)}>" + + +def parse_tag(tag: str) -> frozenset[Tag]: + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _get_config_var(name: str, warn: bool = False) -> int | str | None: + value: int | str | None = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string: str) -> str: + return string.replace(".", "_").replace("-", "_").replace(" ", "_") + + +def _is_threaded_cpython(abis: list[str]) -> bool: + """ + Determine if the ABI corresponds to a threaded (`--disable-gil`) build. + + The threaded builds are indicated by a "t" in the abiflags. + """ + if len(abis) == 0: + return False + # expect e.g., cp313 + m = re.match(r"cp\d+(.*)", abis[0]) + if not m: + return False + abiflags = m.group(1) + return "t" in abiflags + + +def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool: + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`) + builds do not support abi3. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading + + +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]: + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + threading = debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn): + threading = "t" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append(f"cp{version}{threading}") + abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}") + return abis + + +def cpython_tags( + python_version: PythonVersion | None = None, + abis: Iterable[str] | None = None, + platforms: Iterable[str] | None = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + if not python_version: + python_version = sys.version_info[:2] + + interpreter = f"cp{_version_nodot(python_version[:2])}" + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + threading = _is_threaded_cpython(abis) + use_abi3 = _abi3_applies(python_version, threading) + if use_abi3: + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) + + if use_abi3: + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + version = _version_nodot((python_version[0], minor_version)) + interpreter = f"cp{version}" + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi() -> list[str]: + """ + Return the ABI tag based on EXT_SUFFIX. + """ + # The following are examples of `EXT_SUFFIX`. + # We want to keep the parts which are related to the ABI and remove the + # parts which are related to the platform: + # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310 + # - mac: '.cpython-310-darwin.so' => cp310 + # - win: '.cp310-win_amd64.pyd' => cp310 + # - win: '.pyd' => cp37 (uses _cpython_abis()) + # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73 + # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib' + # => graalpy_38_native + + ext_suffix = _get_config_var("EXT_SUFFIX", warn=True) + if not isinstance(ext_suffix, str) or ext_suffix[0] != ".": + raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')") + parts = ext_suffix.split(".") + if len(parts) < 3: + # CPython3.7 and earlier uses ".pyd" on Windows. + return _cpython_abis(sys.version_info[:2]) + soabi = parts[1] + if soabi.startswith("cpython"): + # non-windows + abi = "cp" + soabi.split("-")[1] + elif soabi.startswith("cp"): + # windows + abi = soabi.split("-")[0] + elif soabi.startswith("pypy"): + abi = "-".join(soabi.split("-")[:2]) + elif soabi.startswith("graalpy"): + abi = "-".join(soabi.split("-")[:3]) + elif soabi: + # pyston, ironpython, others? + abi = soabi + else: + return [] + return [_normalize_string(abi)] + + +def generic_tags( + interpreter: str | None = None, + abis: Iterable[str] | None = None, + platforms: Iterable[str] | None = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - -- + + The "none" ABI will be added if it was not explicitly provided. + """ + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + else: + abis = list(abis) + platforms = list(platforms or platform_tags()) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield f"py{_version_nodot(py_version[:2])}" + yield f"py{py_version[0]}" + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield f"py{_version_nodot((py_version[0], minor))}" + + +def compatible_tags( + python_version: PythonVersion | None = None, + interpreter: str | None = None, + platforms: Iterable[str] | None = None, +) -> Iterator[Tag]: + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]: + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms( + version: AppleVersion | None = None, arch: str | None = None +) -> Iterator[str]: + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2]))) + if version == (10, 16): + # When built against an older macOS SDK, Python will report macOS 10.16 + # instead of the real version. + version_str = subprocess.run( + [ + sys.executable, + "-sS", + "-c", + "import platform; print(platform.mac_ver()[0])", + ], + check=True, + env={"SYSTEM_VERSION_COMPAT": "0"}, + stdout=subprocess.PIPE, + text=True, + ).stdout + version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + major_version = 10 + for minor_version in range(version[1], -1, -1): + compat_version = major_version, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + minor_version = 0 + for major_version in range(version[0], 10, -1): + compat_version = major_version, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + + if version >= (11, 0): + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + # + # However, the "universal2" binary format can have a + # macOS version earlier than 11.0 when the x86_64 part of the binary supports + # that version of macOS. + major_version = 10 + if arch == "x86_64": + for minor_version in range(16, 3, -1): + compat_version = major_version, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + else: + for minor_version in range(16, 3, -1): + compat_version = major_version, minor_version + binary_format = "universal2" + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + + +def ios_platforms( + version: AppleVersion | None = None, multiarch: str | None = None +) -> Iterator[str]: + """ + Yields the platform tags for an iOS system. + + :param version: A two-item tuple specifying the iOS version to generate + platform tags for. Defaults to the current iOS version. + :param multiarch: The CPU architecture+ABI to generate platform tags for - + (the value used by `sys.implementation._multiarch` e.g., + `arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current + multiarch value. + """ + if version is None: + # if iOS is the current platform, ios_ver *must* be defined. However, + # it won't exist for CPython versions before 3.13, which causes a mypy + # error. + _, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore] + version = cast("AppleVersion", tuple(map(int, release.split(".")[:2]))) + + if multiarch is None: + multiarch = sys.implementation._multiarch + multiarch = multiarch.replace("-", "_") + + ios_platform_template = "ios_{major}_{minor}_{multiarch}" + + # Consider any iOS major.minor version from the version requested, down to + # 12.0. 12.0 is the first iOS version that is known to have enough features + # to support CPython. Consider every possible minor release up to X.9. There + # highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra + # candidates that won't ever match doesn't really hurt, and it saves us from + # having to keep an explicit list of known iOS versions in the code. Return + # the results descending order of version number. + + # If the requested major version is less than 12, there won't be any matches. + if version[0] < 12: + return + + # Consider the actual X.Y version that was requested. + yield ios_platform_template.format( + major=version[0], minor=version[1], multiarch=multiarch + ) + + # Consider every minor version from X.0 to the minor version prior to the + # version requested by the platform. + for minor in range(version[1] - 1, -1, -1): + yield ios_platform_template.format( + major=version[0], minor=minor, multiarch=multiarch + ) + + for major in range(version[0] - 1, 11, -1): + for minor in range(9, -1, -1): + yield ios_platform_template.format( + major=major, minor=minor, multiarch=multiarch + ) + + +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) + if not linux.startswith("linux_"): + # we should never be here, just yield the sysconfig one and return + yield linux + return + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv8l" + _, arch = linux.split("_", 1) + archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch]) + yield from _manylinux.platform_tags(archs) + yield from _musllinux.platform_tags(archs) + for arch in archs: + yield f"linux_{arch}" + + +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) + + +def platform_tags() -> Iterator[str]: + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "iOS": + return ios_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name() -> str: + """ + Returns the name of the running interpreter. + + Some implementations have a reserved, two-letter abbreviation which will + be returned when appropriate. + """ + name = sys.implementation.name + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(*, warn: bool = False) -> str: + """ + Returns the version of the running interpreter. + """ + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version: PythonVersion) -> str: + return "".join(map(str, version)) + + +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + + interp_name = interpreter_name() + if interp_name == "cp": + yield from cpython_tags(warn=warn) + else: + yield from generic_tags() + + if interp_name == "pp": + interp = "pp3" + elif interp_name == "cp": + interp = "cp" + interpreter_version(warn=warn) + else: + interp = None + yield from compatible_tags(interpreter=interp) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/utils.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/utils.py new file mode 100644 index 000000000..23450953d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/utils.py @@ -0,0 +1,163 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import functools +import re +from typing import NewType, Tuple, Union, cast + +from .tags import Tag, parse_tag +from .version import InvalidVersion, Version, _TrimmedRelease + +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) + + +class InvalidName(ValueError): + """ + An invalid distribution name; users should refer to the packaging user guide. + """ + + +class InvalidWheelFilename(ValueError): + """ + An invalid wheel filename was found, users should refer to PEP 427. + """ + + +class InvalidSdistFilename(ValueError): + """ + An invalid sdist filename was found, users should refer to the packaging user guide. + """ + + +# Core metadata spec for `Name` +_validate_regex = re.compile( + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE +) +_canonicalize_regex = re.compile(r"[-_.]+") +_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$") +# PEP 427: The build number must start with a digit. +_build_tag_regex = re.compile(r"(\d+)(.*)") + + +def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName: + if validate and not _validate_regex.match(name): + raise InvalidName(f"name is invalid: {name!r}") + # This is taken from PEP 503. + value = _canonicalize_regex.sub("-", name).lower() + return cast(NormalizedName, value) + + +def is_normalized_name(name: str) -> bool: + return _normalized_regex.match(name) is not None + + +@functools.singledispatch +def canonicalize_version( + version: Version | str, *, strip_trailing_zero: bool = True +) -> str: + """ + Return a canonical form of a version as a string. + + >>> canonicalize_version('1.0.1') + '1.0.1' + + Per PEP 625, versions may have multiple canonical forms, differing + only by trailing zeros. + + >>> canonicalize_version('1.0.0') + '1' + >>> canonicalize_version('1.0.0', strip_trailing_zero=False) + '1.0.0' + + Invalid versions are returned unaltered. + + >>> canonicalize_version('foo bar baz') + 'foo bar baz' + """ + return str(_TrimmedRelease(str(version)) if strip_trailing_zero else version) + + +@canonicalize_version.register +def _(version: str, *, strip_trailing_zero: bool = True) -> str: + try: + parsed = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + return canonicalize_version(parsed, strip_trailing_zero=strip_trailing_zero) + + +def parse_wheel_filename( + filename: str, +) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]: + if not filename.endswith(".whl"): + raise InvalidWheelFilename( + f"Invalid wheel filename (extension must be '.whl'): {filename!r}" + ) + + filename = filename[:-4] + dashes = filename.count("-") + if dashes not in (4, 5): + raise InvalidWheelFilename( + f"Invalid wheel filename (wrong number of parts): {filename!r}" + ) + + parts = filename.split("-", dashes - 2) + name_part = parts[0] + # See PEP 427 for the rules on escaping the project name. + if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: + raise InvalidWheelFilename(f"Invalid project name: {filename!r}") + name = canonicalize_name(name_part) + + try: + version = Version(parts[1]) + except InvalidVersion as e: + raise InvalidWheelFilename( + f"Invalid wheel filename (invalid version): {filename!r}" + ) from e + + if dashes == 5: + build_part = parts[2] + build_match = _build_tag_regex.match(build_part) + if build_match is None: + raise InvalidWheelFilename( + f"Invalid build number: {build_part} in {filename!r}" + ) + build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) + else: + build = () + tags = parse_tag(parts[-1]) + return (name, version, build, tags) + + +def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: + raise InvalidSdistFilename( + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename!r}" + ) + + # We are requiring a PEP 440 version, which cannot contain dashes, + # so we split on the last dash. + name_part, sep, version_part = file_stem.rpartition("-") + if not sep: + raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}") + + name = canonicalize_name(name_part) + + try: + version = Version(version_part) + except InvalidVersion as e: + raise InvalidSdistFilename( + f"Invalid sdist filename (invalid version): {filename!r}" + ) from e + + return (name, version) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/packaging/version.py b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/version.py new file mode 100644 index 000000000..21f44ca09 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/packaging/version.py @@ -0,0 +1,582 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +""" +.. testsetup:: + + from pip._vendor.packaging.version import parse, Version +""" + +from __future__ import annotations + +import itertools +import re +from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union + +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType + +__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"] + +LocalType = Tuple[Union[int, str], ...] + +CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]] +CmpLocalType = Union[ + NegativeInfinityType, + Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...], +] +CmpKey = Tuple[ + int, + Tuple[int, ...], + CmpPrePostDevType, + CmpPrePostDevType, + CmpPrePostDevType, + CmpLocalType, +] +VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool] + + +class _Version(NamedTuple): + epoch: int + release: tuple[int, ...] + dev: tuple[str, int] | None + pre: tuple[str, int] | None + post: tuple[str, int] | None + local: LocalType | None + + +def parse(version: str) -> Version: + """Parse the given version string. + + >>> parse('1.0.dev1') + + + :param version: The version string to parse. + :raises InvalidVersion: When the version string is not a valid version. + """ + return Version(version) + + +class InvalidVersion(ValueError): + """Raised when a version string is not a valid version. + + >>> Version("invalid") + Traceback (most recent call last): + ... + packaging.version.InvalidVersion: Invalid version: 'invalid' + """ + + +class _BaseVersion: + _key: tuple[Any, ...] + + def __hash__(self) -> int: + return hash(self._key) + + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key < other._key + + def __le__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key <= other._key + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key == other._key + + def __ge__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key >= other._key + + def __gt__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key > other._key + + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key != other._key + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +_VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P

                                                  # pre-release
        +            [-_\.]?
        +            (?Palpha|a|beta|b|preview|pre|c|rc)
        +            [-_\.]?
        +            (?P[0-9]+)?
        +        )?
        +        (?P                                         # post release
        +            (?:-(?P[0-9]+))
        +            |
        +            (?:
        +                [-_\.]?
        +                (?Ppost|rev|r)
        +                [-_\.]?
        +                (?P[0-9]+)?
        +            )
        +        )?
        +        (?P                                          # dev release
        +            [-_\.]?
        +            (?Pdev)
        +            [-_\.]?
        +            (?P[0-9]+)?
        +        )?
        +    )
        +    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
        +"""
        +
        +VERSION_PATTERN = _VERSION_PATTERN
        +"""
        +A string containing the regular expression used to match a valid version.
        +
        +The pattern is not anchored at either end, and is intended for embedding in larger
        +expressions (for example, matching a version number as part of a file name). The
        +regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
        +flags set.
        +
        +:meta hide-value:
        +"""
        +
        +
        +class Version(_BaseVersion):
        +    """This class abstracts handling of a project's versions.
        +
        +    A :class:`Version` instance is comparison aware and can be compared and
        +    sorted using the standard Python interfaces.
        +
        +    >>> v1 = Version("1.0a5")
        +    >>> v2 = Version("1.0")
        +    >>> v1
        +    
        +    >>> v2
        +    
        +    >>> v1 < v2
        +    True
        +    >>> v1 == v2
        +    False
        +    >>> v1 > v2
        +    False
        +    >>> v1 >= v2
        +    False
        +    >>> v1 <= v2
        +    True
        +    """
        +
        +    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
        +    _key: CmpKey
        +
        +    def __init__(self, version: str) -> None:
        +        """Initialize a Version object.
        +
        +        :param version:
        +            The string representation of a version which will be parsed and normalized
        +            before use.
        +        :raises InvalidVersion:
        +            If the ``version`` does not conform to PEP 440 in any way then this
        +            exception will be raised.
        +        """
        +
        +        # Validate the version and parse it into pieces
        +        match = self._regex.search(version)
        +        if not match:
        +            raise InvalidVersion(f"Invalid version: {version!r}")
        +
        +        # Store the parsed out pieces of the version
        +        self._version = _Version(
        +            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
        +            release=tuple(int(i) for i in match.group("release").split(".")),
        +            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
        +            post=_parse_letter_version(
        +                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
        +            ),
        +            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
        +            local=_parse_local_version(match.group("local")),
        +        )
        +
        +        # Generate a key which will be used for sorting
        +        self._key = _cmpkey(
        +            self._version.epoch,
        +            self._version.release,
        +            self._version.pre,
        +            self._version.post,
        +            self._version.dev,
        +            self._version.local,
        +        )
        +
        +    def __repr__(self) -> str:
        +        """A representation of the Version that shows all internal state.
        +
        +        >>> Version('1.0.0')
        +        
        +        """
        +        return f""
        +
        +    def __str__(self) -> str:
        +        """A string representation of the version that can be round-tripped.
        +
        +        >>> str(Version("1.0a5"))
        +        '1.0a5'
        +        """
        +        parts = []
        +
        +        # Epoch
        +        if self.epoch != 0:
        +            parts.append(f"{self.epoch}!")
        +
        +        # Release segment
        +        parts.append(".".join(str(x) for x in self.release))
        +
        +        # Pre-release
        +        if self.pre is not None:
        +            parts.append("".join(str(x) for x in self.pre))
        +
        +        # Post-release
        +        if self.post is not None:
        +            parts.append(f".post{self.post}")
        +
        +        # Development release
        +        if self.dev is not None:
        +            parts.append(f".dev{self.dev}")
        +
        +        # Local version segment
        +        if self.local is not None:
        +            parts.append(f"+{self.local}")
        +
        +        return "".join(parts)
        +
        +    @property
        +    def epoch(self) -> int:
        +        """The epoch of the version.
        +
        +        >>> Version("2.0.0").epoch
        +        0
        +        >>> Version("1!2.0.0").epoch
        +        1
        +        """
        +        return self._version.epoch
        +
        +    @property
        +    def release(self) -> tuple[int, ...]:
        +        """The components of the "release" segment of the version.
        +
        +        >>> Version("1.2.3").release
        +        (1, 2, 3)
        +        >>> Version("2.0.0").release
        +        (2, 0, 0)
        +        >>> Version("1!2.0.0.post0").release
        +        (2, 0, 0)
        +
        +        Includes trailing zeroes but not the epoch or any pre-release / development /
        +        post-release suffixes.
        +        """
        +        return self._version.release
        +
        +    @property
        +    def pre(self) -> tuple[str, int] | None:
        +        """The pre-release segment of the version.
        +
        +        >>> print(Version("1.2.3").pre)
        +        None
        +        >>> Version("1.2.3a1").pre
        +        ('a', 1)
        +        >>> Version("1.2.3b1").pre
        +        ('b', 1)
        +        >>> Version("1.2.3rc1").pre
        +        ('rc', 1)
        +        """
        +        return self._version.pre
        +
        +    @property
        +    def post(self) -> int | None:
        +        """The post-release number of the version.
        +
        +        >>> print(Version("1.2.3").post)
        +        None
        +        >>> Version("1.2.3.post1").post
        +        1
        +        """
        +        return self._version.post[1] if self._version.post else None
        +
        +    @property
        +    def dev(self) -> int | None:
        +        """The development number of the version.
        +
        +        >>> print(Version("1.2.3").dev)
        +        None
        +        >>> Version("1.2.3.dev1").dev
        +        1
        +        """
        +        return self._version.dev[1] if self._version.dev else None
        +
        +    @property
        +    def local(self) -> str | None:
        +        """The local version segment of the version.
        +
        +        >>> print(Version("1.2.3").local)
        +        None
        +        >>> Version("1.2.3+abc").local
        +        'abc'
        +        """
        +        if self._version.local:
        +            return ".".join(str(x) for x in self._version.local)
        +        else:
        +            return None
        +
        +    @property
        +    def public(self) -> str:
        +        """The public portion of the version.
        +
        +        >>> Version("1.2.3").public
        +        '1.2.3'
        +        >>> Version("1.2.3+abc").public
        +        '1.2.3'
        +        >>> Version("1!1.2.3dev1+abc").public
        +        '1!1.2.3.dev1'
        +        """
        +        return str(self).split("+", 1)[0]
        +
        +    @property
        +    def base_version(self) -> str:
        +        """The "base version" of the version.
        +
        +        >>> Version("1.2.3").base_version
        +        '1.2.3'
        +        >>> Version("1.2.3+abc").base_version
        +        '1.2.3'
        +        >>> Version("1!1.2.3dev1+abc").base_version
        +        '1!1.2.3'
        +
        +        The "base version" is the public version of the project without any pre or post
        +        release markers.
        +        """
        +        parts = []
        +
        +        # Epoch
        +        if self.epoch != 0:
        +            parts.append(f"{self.epoch}!")
        +
        +        # Release segment
        +        parts.append(".".join(str(x) for x in self.release))
        +
        +        return "".join(parts)
        +
        +    @property
        +    def is_prerelease(self) -> bool:
        +        """Whether this version is a pre-release.
        +
        +        >>> Version("1.2.3").is_prerelease
        +        False
        +        >>> Version("1.2.3a1").is_prerelease
        +        True
        +        >>> Version("1.2.3b1").is_prerelease
        +        True
        +        >>> Version("1.2.3rc1").is_prerelease
        +        True
        +        >>> Version("1.2.3dev1").is_prerelease
        +        True
        +        """
        +        return self.dev is not None or self.pre is not None
        +
        +    @property
        +    def is_postrelease(self) -> bool:
        +        """Whether this version is a post-release.
        +
        +        >>> Version("1.2.3").is_postrelease
        +        False
        +        >>> Version("1.2.3.post1").is_postrelease
        +        True
        +        """
        +        return self.post is not None
        +
        +    @property
        +    def is_devrelease(self) -> bool:
        +        """Whether this version is a development release.
        +
        +        >>> Version("1.2.3").is_devrelease
        +        False
        +        >>> Version("1.2.3.dev1").is_devrelease
        +        True
        +        """
        +        return self.dev is not None
        +
        +    @property
        +    def major(self) -> int:
        +        """The first item of :attr:`release` or ``0`` if unavailable.
        +
        +        >>> Version("1.2.3").major
        +        1
        +        """
        +        return self.release[0] if len(self.release) >= 1 else 0
        +
        +    @property
        +    def minor(self) -> int:
        +        """The second item of :attr:`release` or ``0`` if unavailable.
        +
        +        >>> Version("1.2.3").minor
        +        2
        +        >>> Version("1").minor
        +        0
        +        """
        +        return self.release[1] if len(self.release) >= 2 else 0
        +
        +    @property
        +    def micro(self) -> int:
        +        """The third item of :attr:`release` or ``0`` if unavailable.
        +
        +        >>> Version("1.2.3").micro
        +        3
        +        >>> Version("1").micro
        +        0
        +        """
        +        return self.release[2] if len(self.release) >= 3 else 0
        +
        +
        +class _TrimmedRelease(Version):
        +    @property
        +    def release(self) -> tuple[int, ...]:
        +        """
        +        Release segment without any trailing zeros.
        +
        +        >>> _TrimmedRelease('1.0.0').release
        +        (1,)
        +        >>> _TrimmedRelease('0.0').release
        +        (0,)
        +        """
        +        rel = super().release
        +        nonzeros = (index for index, val in enumerate(rel) if val)
        +        last_nonzero = max(nonzeros, default=0)
        +        return rel[: last_nonzero + 1]
        +
        +
        +def _parse_letter_version(
        +    letter: str | None, number: str | bytes | SupportsInt | None
        +) -> tuple[str, int] | None:
        +    if letter:
        +        # We consider there to be an implicit 0 in a pre-release if there is
        +        # not a numeral associated with it.
        +        if number is None:
        +            number = 0
        +
        +        # We normalize any letters to their lower case form
        +        letter = letter.lower()
        +
        +        # We consider some words to be alternate spellings of other words and
        +        # in those cases we want to normalize the spellings to our preferred
        +        # spelling.
        +        if letter == "alpha":
        +            letter = "a"
        +        elif letter == "beta":
        +            letter = "b"
        +        elif letter in ["c", "pre", "preview"]:
        +            letter = "rc"
        +        elif letter in ["rev", "r"]:
        +            letter = "post"
        +
        +        return letter, int(number)
        +
        +    assert not letter
        +    if number:
        +        # We assume if we are given a number, but we are not given a letter
        +        # then this is using the implicit post release syntax (e.g. 1.0-1)
        +        letter = "post"
        +
        +        return letter, int(number)
        +
        +    return None
        +
        +
        +_local_version_separators = re.compile(r"[\._-]")
        +
        +
        +def _parse_local_version(local: str | None) -> LocalType | None:
        +    """
        +    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
        +    """
        +    if local is not None:
        +        return tuple(
        +            part.lower() if not part.isdigit() else int(part)
        +            for part in _local_version_separators.split(local)
        +        )
        +    return None
        +
        +
        +def _cmpkey(
        +    epoch: int,
        +    release: tuple[int, ...],
        +    pre: tuple[str, int] | None,
        +    post: tuple[str, int] | None,
        +    dev: tuple[str, int] | None,
        +    local: LocalType | None,
        +) -> CmpKey:
        +    # When we compare a release version, we want to compare it with all of the
        +    # trailing zeros removed. So we'll use a reverse the list, drop all the now
        +    # leading zeros until we come to something non zero, then take the rest
        +    # re-reverse it back into the correct order and make it a tuple and use
        +    # that for our sorting key.
        +    _release = tuple(
        +        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
        +    )
        +
        +    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
        +    # We'll do this by abusing the pre segment, but we _only_ want to do this
        +    # if there is not a pre or a post segment. If we have one of those then
        +    # the normal sorting rules will handle this case correctly.
        +    if pre is None and post is None and dev is not None:
        +        _pre: CmpPrePostDevType = NegativeInfinity
        +    # Versions without a pre-release (except as noted above) should sort after
        +    # those with one.
        +    elif pre is None:
        +        _pre = Infinity
        +    else:
        +        _pre = pre
        +
        +    # Versions without a post segment should sort before those with one.
        +    if post is None:
        +        _post: CmpPrePostDevType = NegativeInfinity
        +
        +    else:
        +        _post = post
        +
        +    # Versions without a development segment should sort after those with one.
        +    if dev is None:
        +        _dev: CmpPrePostDevType = Infinity
        +
        +    else:
        +        _dev = dev
        +
        +    if local is None:
        +        # Versions without a local segment should sort before those with one.
        +        _local: CmpLocalType = NegativeInfinity
        +    else:
        +        # Versions with a local segment need that segment parsed to implement
        +        # the sorting rules in PEP440.
        +        # - Alpha numeric segments sort before numeric segments
        +        # - Alpha numeric segments sort lexicographically
        +        # - Numeric segments sort numerically
        +        # - Shorter versions sort before longer versions when the prefixes
        +        #   match exactly
        +        _local = tuple(
        +            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
        +        )
        +
        +    return epoch, _release, _pre, _post, _dev, _local
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pkg_resources/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/pkg_resources/__init__.py
        new file mode 100644
        index 000000000..57ce7f100
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pkg_resources/__init__.py
        @@ -0,0 +1,3676 @@
        +# TODO: Add Generic type annotations to initialized collections.
        +# For now we'd simply use implicit Any/Unknown which would add redundant annotations
        +# mypy: disable-error-code="var-annotated"
        +"""
        +Package resource API
        +--------------------
        +
        +A resource is a logical file contained within a package, or a logical
        +subdirectory thereof.  The package resource API expects resource names
        +to have their path parts separated with ``/``, *not* whatever the local
        +path separator is.  Do not use os.path operations to manipulate resource
        +names being passed into the API.
        +
        +The package resource API is designed to work with normal filesystem packages,
        +.egg files, and unpacked .egg files.  It can also work in a limited way with
        +.zip files and with custom PEP 302 loaders that support the ``get_data()``
        +method.
        +
        +This module is deprecated. Users are directed to :mod:`importlib.resources`,
        +:mod:`importlib.metadata` and :pypi:`packaging` instead.
        +"""
        +
        +from __future__ import annotations
        +
        +import sys
        +
        +if sys.version_info < (3, 8):  # noqa: UP036 # Check for unsupported versions
        +    raise RuntimeError("Python 3.8 or later is required")
        +
        +import os
        +import io
        +import time
        +import re
        +import types
        +from typing import (
        +    Any,
        +    Literal,
        +    Dict,
        +    Iterator,
        +    Mapping,
        +    MutableSequence,
        +    NamedTuple,
        +    NoReturn,
        +    Tuple,
        +    Union,
        +    TYPE_CHECKING,
        +    Protocol,
        +    Callable,
        +    Iterable,
        +    TypeVar,
        +    overload,
        +)
        +import zipfile
        +import zipimport
        +import warnings
        +import stat
        +import functools
        +import pkgutil
        +import operator
        +import platform
        +import collections
        +import plistlib
        +import email.parser
        +import errno
        +import tempfile
        +import textwrap
        +import inspect
        +import ntpath
        +import posixpath
        +import importlib
        +import importlib.abc
        +import importlib.machinery
        +from pkgutil import get_importer
        +
        +import _imp
        +
        +# capture these to bypass sandboxing
        +from os import utime
        +from os import open as os_open
        +from os.path import isdir, split
        +
        +try:
        +    from os import mkdir, rename, unlink
        +
        +    WRITE_SUPPORT = True
        +except ImportError:
        +    # no write support, probably under GAE
        +    WRITE_SUPPORT = False
        +
        +from pip._internal.utils._jaraco_text import (
        +    yield_lines,
        +    drop_comment,
        +    join_continuation,
        +)
        +from pip._vendor.packaging import markers as _packaging_markers
        +from pip._vendor.packaging import requirements as _packaging_requirements
        +from pip._vendor.packaging import utils as _packaging_utils
        +from pip._vendor.packaging import version as _packaging_version
        +from pip._vendor.platformdirs import user_cache_dir as _user_cache_dir
        +
        +if TYPE_CHECKING:
        +    from _typeshed import BytesPath, StrPath, StrOrBytesPath
        +    from pip._vendor.typing_extensions import Self
        +
        +
        +# Patch: Remove deprecation warning from vendored pkg_resources.
        +# Setting PYTHONWARNINGS=error to verify builds produce no warnings
        +# causes immediate exceptions.
        +# See https://github.com/pypa/pip/issues/12243
        +
        +
        +_T = TypeVar("_T")
        +_DistributionT = TypeVar("_DistributionT", bound="Distribution")
        +# Type aliases
        +_NestedStr = Union[str, Iterable[Union[str, Iterable["_NestedStr"]]]]
        +_InstallerTypeT = Callable[["Requirement"], "_DistributionT"]
        +_InstallerType = Callable[["Requirement"], Union["Distribution", None]]
        +_PkgReqType = Union[str, "Requirement"]
        +_EPDistType = Union["Distribution", _PkgReqType]
        +_MetadataType = Union["IResourceProvider", None]
        +_ResolvedEntryPoint = Any  # Can be any attribute in the module
        +_ResourceStream = Any  # TODO / Incomplete: A readable file-like object
        +# Any object works, but let's indicate we expect something like a module (optionally has __loader__ or __file__)
        +_ModuleLike = Union[object, types.ModuleType]
        +# Any: Should be _ModuleLike but we end up with issues where _ModuleLike doesn't have _ZipLoaderModule's __loader__
        +_ProviderFactoryType = Callable[[Any], "IResourceProvider"]
        +_DistFinderType = Callable[[_T, str, bool], Iterable["Distribution"]]
        +_NSHandlerType = Callable[[_T, str, str, types.ModuleType], Union[str, None]]
        +_AdapterT = TypeVar(
        +    "_AdapterT", _DistFinderType[Any], _ProviderFactoryType, _NSHandlerType[Any]
        +)
        +
        +
        +# Use _typeshed.importlib.LoaderProtocol once available https://github.com/python/typeshed/pull/11890
        +class _LoaderProtocol(Protocol):
        +    def load_module(self, fullname: str, /) -> types.ModuleType: ...
        +
        +
        +class _ZipLoaderModule(Protocol):
        +    __loader__: zipimport.zipimporter
        +
        +
        +_PEP440_FALLBACK = re.compile(r"^v?(?P(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)
        +
        +
        +class PEP440Warning(RuntimeWarning):
        +    """
        +    Used when there is an issue with a version or specifier not complying with
        +    PEP 440.
        +    """
        +
        +
        +parse_version = _packaging_version.Version
        +
        +
        +_state_vars: dict[str, str] = {}
        +
        +
        +def _declare_state(vartype: str, varname: str, initial_value: _T) -> _T:
        +    _state_vars[varname] = vartype
        +    return initial_value
        +
        +
        +def __getstate__() -> dict[str, Any]:
        +    state = {}
        +    g = globals()
        +    for k, v in _state_vars.items():
        +        state[k] = g['_sget_' + v](g[k])
        +    return state
        +
        +
        +def __setstate__(state: dict[str, Any]) -> dict[str, Any]:
        +    g = globals()
        +    for k, v in state.items():
        +        g['_sset_' + _state_vars[k]](k, g[k], v)
        +    return state
        +
        +
        +def _sget_dict(val):
        +    return val.copy()
        +
        +
        +def _sset_dict(key, ob, state):
        +    ob.clear()
        +    ob.update(state)
        +
        +
        +def _sget_object(val):
        +    return val.__getstate__()
        +
        +
        +def _sset_object(key, ob, state):
        +    ob.__setstate__(state)
        +
        +
        +_sget_none = _sset_none = lambda *args: None
        +
        +
        +def get_supported_platform():
        +    """Return this platform's maximum compatible version.
        +
        +    distutils.util.get_platform() normally reports the minimum version
        +    of macOS that would be required to *use* extensions produced by
        +    distutils.  But what we want when checking compatibility is to know the
        +    version of macOS that we are *running*.  To allow usage of packages that
        +    explicitly require a newer version of macOS, we must also know the
        +    current version of the OS.
        +
        +    If this condition occurs for any other platform with a version in its
        +    platform strings, this function should be extended accordingly.
        +    """
        +    plat = get_build_platform()
        +    m = macosVersionString.match(plat)
        +    if m is not None and sys.platform == "darwin":
        +        try:
        +            plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))
        +        except ValueError:
        +            # not macOS
        +            pass
        +    return plat
        +
        +
        +__all__ = [
        +    # Basic resource access and distribution/entry point discovery
        +    'require',
        +    'run_script',
        +    'get_provider',
        +    'get_distribution',
        +    'load_entry_point',
        +    'get_entry_map',
        +    'get_entry_info',
        +    'iter_entry_points',
        +    'resource_string',
        +    'resource_stream',
        +    'resource_filename',
        +    'resource_listdir',
        +    'resource_exists',
        +    'resource_isdir',
        +    # Environmental control
        +    'declare_namespace',
        +    'working_set',
        +    'add_activation_listener',
        +    'find_distributions',
        +    'set_extraction_path',
        +    'cleanup_resources',
        +    'get_default_cache',
        +    # Primary implementation classes
        +    'Environment',
        +    'WorkingSet',
        +    'ResourceManager',
        +    'Distribution',
        +    'Requirement',
        +    'EntryPoint',
        +    # Exceptions
        +    'ResolutionError',
        +    'VersionConflict',
        +    'DistributionNotFound',
        +    'UnknownExtra',
        +    'ExtractionError',
        +    # Warnings
        +    'PEP440Warning',
        +    # Parsing functions and string utilities
        +    'parse_requirements',
        +    'parse_version',
        +    'safe_name',
        +    'safe_version',
        +    'get_platform',
        +    'compatible_platforms',
        +    'yield_lines',
        +    'split_sections',
        +    'safe_extra',
        +    'to_filename',
        +    'invalid_marker',
        +    'evaluate_marker',
        +    # filesystem utilities
        +    'ensure_directory',
        +    'normalize_path',
        +    # Distribution "precedence" constants
        +    'EGG_DIST',
        +    'BINARY_DIST',
        +    'SOURCE_DIST',
        +    'CHECKOUT_DIST',
        +    'DEVELOP_DIST',
        +    # "Provider" interfaces, implementations, and registration/lookup APIs
        +    'IMetadataProvider',
        +    'IResourceProvider',
        +    'FileMetadata',
        +    'PathMetadata',
        +    'EggMetadata',
        +    'EmptyProvider',
        +    'empty_provider',
        +    'NullProvider',
        +    'EggProvider',
        +    'DefaultProvider',
        +    'ZipProvider',
        +    'register_finder',
        +    'register_namespace_handler',
        +    'register_loader_type',
        +    'fixup_namespace_packages',
        +    'get_importer',
        +    # Warnings
        +    'PkgResourcesDeprecationWarning',
        +    # Deprecated/backward compatibility only
        +    'run_main',
        +    'AvailableDistributions',
        +]
        +
        +
        +class ResolutionError(Exception):
        +    """Abstract base for dependency resolution errors"""
        +
        +    def __repr__(self):
        +        return self.__class__.__name__ + repr(self.args)
        +
        +
        +class VersionConflict(ResolutionError):
        +    """
        +    An already-installed version conflicts with the requested version.
        +
        +    Should be initialized with the installed Distribution and the requested
        +    Requirement.
        +    """
        +
        +    _template = "{self.dist} is installed but {self.req} is required"
        +
        +    @property
        +    def dist(self) -> Distribution:
        +        return self.args[0]
        +
        +    @property
        +    def req(self) -> Requirement:
        +        return self.args[1]
        +
        +    def report(self):
        +        return self._template.format(**locals())
        +
        +    def with_context(self, required_by: set[Distribution | str]):
        +        """
        +        If required_by is non-empty, return a version of self that is a
        +        ContextualVersionConflict.
        +        """
        +        if not required_by:
        +            return self
        +        args = self.args + (required_by,)
        +        return ContextualVersionConflict(*args)
        +
        +
        +class ContextualVersionConflict(VersionConflict):
        +    """
        +    A VersionConflict that accepts a third parameter, the set of the
        +    requirements that required the installed Distribution.
        +    """
        +
        +    _template = VersionConflict._template + ' by {self.required_by}'
        +
        +    @property
        +    def required_by(self) -> set[str]:
        +        return self.args[2]
        +
        +
        +class DistributionNotFound(ResolutionError):
        +    """A requested distribution was not found"""
        +
        +    _template = (
        +        "The '{self.req}' distribution was not found "
        +        "and is required by {self.requirers_str}"
        +    )
        +
        +    @property
        +    def req(self) -> Requirement:
        +        return self.args[0]
        +
        +    @property
        +    def requirers(self) -> set[str] | None:
        +        return self.args[1]
        +
        +    @property
        +    def requirers_str(self):
        +        if not self.requirers:
        +            return 'the application'
        +        return ', '.join(self.requirers)
        +
        +    def report(self):
        +        return self._template.format(**locals())
        +
        +    def __str__(self):
        +        return self.report()
        +
        +
        +class UnknownExtra(ResolutionError):
        +    """Distribution doesn't have an "extra feature" of the given name"""
        +
        +
        +_provider_factories: dict[type[_ModuleLike], _ProviderFactoryType] = {}
        +
        +PY_MAJOR = '{}.{}'.format(*sys.version_info)
        +EGG_DIST = 3
        +BINARY_DIST = 2
        +SOURCE_DIST = 1
        +CHECKOUT_DIST = 0
        +DEVELOP_DIST = -1
        +
        +
        +def register_loader_type(
        +    loader_type: type[_ModuleLike], provider_factory: _ProviderFactoryType
        +):
        +    """Register `provider_factory` to make providers for `loader_type`
        +
        +    `loader_type` is the type or class of a PEP 302 ``module.__loader__``,
        +    and `provider_factory` is a function that, passed a *module* object,
        +    returns an ``IResourceProvider`` for that module.
        +    """
        +    _provider_factories[loader_type] = provider_factory
        +
        +
        +@overload
        +def get_provider(moduleOrReq: str) -> IResourceProvider: ...
        +@overload
        +def get_provider(moduleOrReq: Requirement) -> Distribution: ...
        +def get_provider(moduleOrReq: str | Requirement) -> IResourceProvider | Distribution:
        +    """Return an IResourceProvider for the named module or requirement"""
        +    if isinstance(moduleOrReq, Requirement):
        +        return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]
        +    try:
        +        module = sys.modules[moduleOrReq]
        +    except KeyError:
        +        __import__(moduleOrReq)
        +        module = sys.modules[moduleOrReq]
        +    loader = getattr(module, '__loader__', None)
        +    return _find_adapter(_provider_factories, loader)(module)
        +
        +
        +@functools.lru_cache(maxsize=None)
        +def _macos_vers():
        +    version = platform.mac_ver()[0]
        +    # fallback for MacPorts
        +    if version == '':
        +        plist = '/System/Library/CoreServices/SystemVersion.plist'
        +        if os.path.exists(plist):
        +            with open(plist, 'rb') as fh:
        +                plist_content = plistlib.load(fh)
        +            if 'ProductVersion' in plist_content:
        +                version = plist_content['ProductVersion']
        +    return version.split('.')
        +
        +
        +def _macos_arch(machine):
        +    return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)
        +
        +
        +def get_build_platform():
        +    """Return this platform's string for platform-specific distributions
        +
        +    XXX Currently this is the same as ``distutils.util.get_platform()``, but it
        +    needs some hacks for Linux and macOS.
        +    """
        +    from sysconfig import get_platform
        +
        +    plat = get_platform()
        +    if sys.platform == "darwin" and not plat.startswith('macosx-'):
        +        try:
        +            version = _macos_vers()
        +            machine = os.uname()[4].replace(" ", "_")
        +            return "macosx-%d.%d-%s" % (
        +                int(version[0]),
        +                int(version[1]),
        +                _macos_arch(machine),
        +            )
        +        except ValueError:
        +            # if someone is running a non-Mac darwin system, this will fall
        +            # through to the default implementation
        +            pass
        +    return plat
        +
        +
        +macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")
        +darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")
        +# XXX backward compat
        +get_platform = get_build_platform
        +
        +
        +def compatible_platforms(provided: str | None, required: str | None):
        +    """Can code for the `provided` platform run on the `required` platform?
        +
        +    Returns true if either platform is ``None``, or the platforms are equal.
        +
        +    XXX Needs compatibility checks for Linux and other unixy OSes.
        +    """
        +    if provided is None or required is None or provided == required:
        +        # easy case
        +        return True
        +
        +    # macOS special cases
        +    reqMac = macosVersionString.match(required)
        +    if reqMac:
        +        provMac = macosVersionString.match(provided)
        +
        +        # is this a Mac package?
        +        if not provMac:
        +            # this is backwards compatibility for packages built before
        +            # setuptools 0.6. All packages built after this point will
        +            # use the new macOS designation.
        +            provDarwin = darwinVersionString.match(provided)
        +            if provDarwin:
        +                dversion = int(provDarwin.group(1))
        +                macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2))
        +                if (
        +                    dversion == 7
        +                    and macosversion >= "10.3"
        +                    or dversion == 8
        +                    and macosversion >= "10.4"
        +                ):
        +                    return True
        +            # egg isn't macOS or legacy darwin
        +            return False
        +
        +        # are they the same major version and machine type?
        +        if provMac.group(1) != reqMac.group(1) or provMac.group(3) != reqMac.group(3):
        +            return False
        +
        +        # is the required OS major update >= the provided one?
        +        if int(provMac.group(2)) > int(reqMac.group(2)):
        +            return False
        +
        +        return True
        +
        +    # XXX Linux and other platforms' special cases should go here
        +    return False
        +
        +
        +@overload
        +def get_distribution(dist: _DistributionT) -> _DistributionT: ...
        +@overload
        +def get_distribution(dist: _PkgReqType) -> Distribution: ...
        +def get_distribution(dist: Distribution | _PkgReqType) -> Distribution:
        +    """Return a current distribution object for a Requirement or string"""
        +    if isinstance(dist, str):
        +        dist = Requirement.parse(dist)
        +    if isinstance(dist, Requirement):
        +        # Bad type narrowing, dist has to be a Requirement here, so get_provider has to return Distribution
        +        dist = get_provider(dist)  # type: ignore[assignment]
        +    if not isinstance(dist, Distribution):
        +        raise TypeError("Expected str, Requirement, or Distribution", dist)
        +    return dist
        +
        +
        +def load_entry_point(dist: _EPDistType, group: str, name: str) -> _ResolvedEntryPoint:
        +    """Return `name` entry point of `group` for `dist` or raise ImportError"""
        +    return get_distribution(dist).load_entry_point(group, name)
        +
        +
        +@overload
        +def get_entry_map(
        +    dist: _EPDistType, group: None = None
        +) -> dict[str, dict[str, EntryPoint]]: ...
        +@overload
        +def get_entry_map(dist: _EPDistType, group: str) -> dict[str, EntryPoint]: ...
        +def get_entry_map(dist: _EPDistType, group: str | None = None):
        +    """Return the entry point map for `group`, or the full entry map"""
        +    return get_distribution(dist).get_entry_map(group)
        +
        +
        +def get_entry_info(dist: _EPDistType, group: str, name: str):
        +    """Return the EntryPoint object for `group`+`name`, or ``None``"""
        +    return get_distribution(dist).get_entry_info(group, name)
        +
        +
        +class IMetadataProvider(Protocol):
        +    def has_metadata(self, name: str) -> bool:
        +        """Does the package's distribution contain the named metadata?"""
        +
        +    def get_metadata(self, name: str) -> str:
        +        """The named metadata resource as a string"""
        +
        +    def get_metadata_lines(self, name: str) -> Iterator[str]:
        +        """Yield named metadata resource as list of non-blank non-comment lines
        +
        +        Leading and trailing whitespace is stripped from each line, and lines
        +        with ``#`` as the first non-blank character are omitted."""
        +
        +    def metadata_isdir(self, name: str) -> bool:
        +        """Is the named metadata a directory?  (like ``os.path.isdir()``)"""
        +
        +    def metadata_listdir(self, name: str) -> list[str]:
        +        """List of metadata names in the directory (like ``os.listdir()``)"""
        +
        +    def run_script(self, script_name: str, namespace: dict[str, Any]) -> None:
        +        """Execute the named script in the supplied namespace dictionary"""
        +
        +
        +class IResourceProvider(IMetadataProvider, Protocol):
        +    """An object that provides access to package resources"""
        +
        +    def get_resource_filename(
        +        self, manager: ResourceManager, resource_name: str
        +    ) -> str:
        +        """Return a true filesystem path for `resource_name`
        +
        +        `manager` must be a ``ResourceManager``"""
        +
        +    def get_resource_stream(
        +        self, manager: ResourceManager, resource_name: str
        +    ) -> _ResourceStream:
        +        """Return a readable file-like object for `resource_name`
        +
        +        `manager` must be a ``ResourceManager``"""
        +
        +    def get_resource_string(
        +        self, manager: ResourceManager, resource_name: str
        +    ) -> bytes:
        +        """Return the contents of `resource_name` as :obj:`bytes`
        +
        +        `manager` must be a ``ResourceManager``"""
        +
        +    def has_resource(self, resource_name: str) -> bool:
        +        """Does the package contain the named resource?"""
        +
        +    def resource_isdir(self, resource_name: str) -> bool:
        +        """Is the named resource a directory?  (like ``os.path.isdir()``)"""
        +
        +    def resource_listdir(self, resource_name: str) -> list[str]:
        +        """List of resource names in the directory (like ``os.listdir()``)"""
        +
        +
        +class WorkingSet:
        +    """A collection of active distributions on sys.path (or a similar list)"""
        +
        +    def __init__(self, entries: Iterable[str] | None = None):
        +        """Create working set from list of path entries (default=sys.path)"""
        +        self.entries: list[str] = []
        +        self.entry_keys = {}
        +        self.by_key = {}
        +        self.normalized_to_canonical_keys = {}
        +        self.callbacks = []
        +
        +        if entries is None:
        +            entries = sys.path
        +
        +        for entry in entries:
        +            self.add_entry(entry)
        +
        +    @classmethod
        +    def _build_master(cls):
        +        """
        +        Prepare the master working set.
        +        """
        +        ws = cls()
        +        try:
        +            from __main__ import __requires__
        +        except ImportError:
        +            # The main program does not list any requirements
        +            return ws
        +
        +        # ensure the requirements are met
        +        try:
        +            ws.require(__requires__)
        +        except VersionConflict:
        +            return cls._build_from_requirements(__requires__)
        +
        +        return ws
        +
        +    @classmethod
        +    def _build_from_requirements(cls, req_spec):
        +        """
        +        Build a working set from a requirement spec. Rewrites sys.path.
        +        """
        +        # try it without defaults already on sys.path
        +        # by starting with an empty path
        +        ws = cls([])
        +        reqs = parse_requirements(req_spec)
        +        dists = ws.resolve(reqs, Environment())
        +        for dist in dists:
        +            ws.add(dist)
        +
        +        # add any missing entries from sys.path
        +        for entry in sys.path:
        +            if entry not in ws.entries:
        +                ws.add_entry(entry)
        +
        +        # then copy back to sys.path
        +        sys.path[:] = ws.entries
        +        return ws
        +
        +    def add_entry(self, entry: str):
        +        """Add a path item to ``.entries``, finding any distributions on it
        +
        +        ``find_distributions(entry, True)`` is used to find distributions
        +        corresponding to the path entry, and they are added.  `entry` is
        +        always appended to ``.entries``, even if it is already present.
        +        (This is because ``sys.path`` can contain the same value more than
        +        once, and the ``.entries`` of the ``sys.path`` WorkingSet should always
        +        equal ``sys.path``.)
        +        """
        +        self.entry_keys.setdefault(entry, [])
        +        self.entries.append(entry)
        +        for dist in find_distributions(entry, True):
        +            self.add(dist, entry, False)
        +
        +    def __contains__(self, dist: Distribution) -> bool:
        +        """True if `dist` is the active distribution for its project"""
        +        return self.by_key.get(dist.key) == dist
        +
        +    def find(self, req: Requirement) -> Distribution | None:
        +        """Find a distribution matching requirement `req`
        +
        +        If there is an active distribution for the requested project, this
        +        returns it as long as it meets the version requirement specified by
        +        `req`.  But, if there is an active distribution for the project and it
        +        does *not* meet the `req` requirement, ``VersionConflict`` is raised.
        +        If there is no active distribution for the requested project, ``None``
        +        is returned.
        +        """
        +        dist = self.by_key.get(req.key)
        +
        +        if dist is None:
        +            canonical_key = self.normalized_to_canonical_keys.get(req.key)
        +
        +            if canonical_key is not None:
        +                req.key = canonical_key
        +                dist = self.by_key.get(canonical_key)
        +
        +        if dist is not None and dist not in req:
        +            # XXX add more info
        +            raise VersionConflict(dist, req)
        +        return dist
        +
        +    def iter_entry_points(self, group: str, name: str | None = None):
        +        """Yield entry point objects from `group` matching `name`
        +
        +        If `name` is None, yields all entry points in `group` from all
        +        distributions in the working set, otherwise only ones matching
        +        both `group` and `name` are yielded (in distribution order).
        +        """
        +        return (
        +            entry
        +            for dist in self
        +            for entry in dist.get_entry_map(group).values()
        +            if name is None or name == entry.name
        +        )
        +
        +    def run_script(self, requires: str, script_name: str):
        +        """Locate distribution for `requires` and run `script_name` script"""
        +        ns = sys._getframe(1).f_globals
        +        name = ns['__name__']
        +        ns.clear()
        +        ns['__name__'] = name
        +        self.require(requires)[0].run_script(script_name, ns)
        +
        +    def __iter__(self) -> Iterator[Distribution]:
        +        """Yield distributions for non-duplicate projects in the working set
        +
        +        The yield order is the order in which the items' path entries were
        +        added to the working set.
        +        """
        +        seen = set()
        +        for item in self.entries:
        +            if item not in self.entry_keys:
        +                # workaround a cache issue
        +                continue
        +
        +            for key in self.entry_keys[item]:
        +                if key not in seen:
        +                    seen.add(key)
        +                    yield self.by_key[key]
        +
        +    def add(
        +        self,
        +        dist: Distribution,
        +        entry: str | None = None,
        +        insert: bool = True,
        +        replace: bool = False,
        +    ):
        +        """Add `dist` to working set, associated with `entry`
        +
        +        If `entry` is unspecified, it defaults to the ``.location`` of `dist`.
        +        On exit from this routine, `entry` is added to the end of the working
        +        set's ``.entries`` (if it wasn't already present).
        +
        +        `dist` is only added to the working set if it's for a project that
        +        doesn't already have a distribution in the set, unless `replace=True`.
        +        If it's added, any callbacks registered with the ``subscribe()`` method
        +        will be called.
        +        """
        +        if insert:
        +            dist.insert_on(self.entries, entry, replace=replace)
        +
        +        if entry is None:
        +            entry = dist.location
        +        keys = self.entry_keys.setdefault(entry, [])
        +        keys2 = self.entry_keys.setdefault(dist.location, [])
        +        if not replace and dist.key in self.by_key:
        +            # ignore hidden distros
        +            return
        +
        +        self.by_key[dist.key] = dist
        +        normalized_name = _packaging_utils.canonicalize_name(dist.key)
        +        self.normalized_to_canonical_keys[normalized_name] = dist.key
        +        if dist.key not in keys:
        +            keys.append(dist.key)
        +        if dist.key not in keys2:
        +            keys2.append(dist.key)
        +        self._added_new(dist)
        +
        +    @overload
        +    def resolve(
        +        self,
        +        requirements: Iterable[Requirement],
        +        env: Environment | None,
        +        installer: _InstallerTypeT[_DistributionT],
        +        replace_conflicting: bool = False,
        +        extras: tuple[str, ...] | None = None,
        +    ) -> list[_DistributionT]: ...
        +    @overload
        +    def resolve(
        +        self,
        +        requirements: Iterable[Requirement],
        +        env: Environment | None = None,
        +        *,
        +        installer: _InstallerTypeT[_DistributionT],
        +        replace_conflicting: bool = False,
        +        extras: tuple[str, ...] | None = None,
        +    ) -> list[_DistributionT]: ...
        +    @overload
        +    def resolve(
        +        self,
        +        requirements: Iterable[Requirement],
        +        env: Environment | None = None,
        +        installer: _InstallerType | None = None,
        +        replace_conflicting: bool = False,
        +        extras: tuple[str, ...] | None = None,
        +    ) -> list[Distribution]: ...
        +    def resolve(
        +        self,
        +        requirements: Iterable[Requirement],
        +        env: Environment | None = None,
        +        installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None,
        +        replace_conflicting: bool = False,
        +        extras: tuple[str, ...] | None = None,
        +    ) -> list[Distribution] | list[_DistributionT]:
        +        """List all distributions needed to (recursively) meet `requirements`
        +
        +        `requirements` must be a sequence of ``Requirement`` objects.  `env`,
        +        if supplied, should be an ``Environment`` instance.  If
        +        not supplied, it defaults to all distributions available within any
        +        entry or distribution in the working set.  `installer`, if supplied,
        +        will be invoked with each requirement that cannot be met by an
        +        already-installed distribution; it should return a ``Distribution`` or
        +        ``None``.
        +
        +        Unless `replace_conflicting=True`, raises a VersionConflict exception
        +        if
        +        any requirements are found on the path that have the correct name but
        +        the wrong version.  Otherwise, if an `installer` is supplied it will be
        +        invoked to obtain the correct version of the requirement and activate
        +        it.
        +
        +        `extras` is a list of the extras to be used with these requirements.
        +        This is important because extra requirements may look like `my_req;
        +        extra = "my_extra"`, which would otherwise be interpreted as a purely
        +        optional requirement.  Instead, we want to be able to assert that these
        +        requirements are truly required.
        +        """
        +
        +        # set up the stack
        +        requirements = list(requirements)[::-1]
        +        # set of processed requirements
        +        processed = set()
        +        # key -> dist
        +        best = {}
        +        to_activate = []
        +
        +        req_extras = _ReqExtras()
        +
        +        # Mapping of requirement to set of distributions that required it;
        +        # useful for reporting info about conflicts.
        +        required_by = collections.defaultdict(set)
        +
        +        while requirements:
        +            # process dependencies breadth-first
        +            req = requirements.pop(0)
        +            if req in processed:
        +                # Ignore cyclic or redundant dependencies
        +                continue
        +
        +            if not req_extras.markers_pass(req, extras):
        +                continue
        +
        +            dist = self._resolve_dist(
        +                req, best, replace_conflicting, env, installer, required_by, to_activate
        +            )
        +
        +            # push the new requirements onto the stack
        +            new_requirements = dist.requires(req.extras)[::-1]
        +            requirements.extend(new_requirements)
        +
        +            # Register the new requirements needed by req
        +            for new_requirement in new_requirements:
        +                required_by[new_requirement].add(req.project_name)
        +                req_extras[new_requirement] = req.extras
        +
        +            processed.add(req)
        +
        +        # return list of distros to activate
        +        return to_activate
        +
        +    def _resolve_dist(
        +        self, req, best, replace_conflicting, env, installer, required_by, to_activate
        +    ) -> Distribution:
        +        dist = best.get(req.key)
        +        if dist is None:
        +            # Find the best distribution and add it to the map
        +            dist = self.by_key.get(req.key)
        +            if dist is None or (dist not in req and replace_conflicting):
        +                ws = self
        +                if env is None:
        +                    if dist is None:
        +                        env = Environment(self.entries)
        +                    else:
        +                        # Use an empty environment and workingset to avoid
        +                        # any further conflicts with the conflicting
        +                        # distribution
        +                        env = Environment([])
        +                        ws = WorkingSet([])
        +                dist = best[req.key] = env.best_match(
        +                    req, ws, installer, replace_conflicting=replace_conflicting
        +                )
        +                if dist is None:
        +                    requirers = required_by.get(req, None)
        +                    raise DistributionNotFound(req, requirers)
        +            to_activate.append(dist)
        +        if dist not in req:
        +            # Oops, the "best" so far conflicts with a dependency
        +            dependent_req = required_by[req]
        +            raise VersionConflict(dist, req).with_context(dependent_req)
        +        return dist
        +
        +    @overload
        +    def find_plugins(
        +        self,
        +        plugin_env: Environment,
        +        full_env: Environment | None,
        +        installer: _InstallerTypeT[_DistributionT],
        +        fallback: bool = True,
        +    ) -> tuple[list[_DistributionT], dict[Distribution, Exception]]: ...
        +    @overload
        +    def find_plugins(
        +        self,
        +        plugin_env: Environment,
        +        full_env: Environment | None = None,
        +        *,
        +        installer: _InstallerTypeT[_DistributionT],
        +        fallback: bool = True,
        +    ) -> tuple[list[_DistributionT], dict[Distribution, Exception]]: ...
        +    @overload
        +    def find_plugins(
        +        self,
        +        plugin_env: Environment,
        +        full_env: Environment | None = None,
        +        installer: _InstallerType | None = None,
        +        fallback: bool = True,
        +    ) -> tuple[list[Distribution], dict[Distribution, Exception]]: ...
        +    def find_plugins(
        +        self,
        +        plugin_env: Environment,
        +        full_env: Environment | None = None,
        +        installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None,
        +        fallback: bool = True,
        +    ) -> tuple[
        +        list[Distribution] | list[_DistributionT],
        +        dict[Distribution, Exception],
        +    ]:
        +        """Find all activatable distributions in `plugin_env`
        +
        +        Example usage::
        +
        +            distributions, errors = working_set.find_plugins(
        +                Environment(plugin_dirlist)
        +            )
        +            # add plugins+libs to sys.path
        +            map(working_set.add, distributions)
        +            # display errors
        +            print('Could not load', errors)
        +
        +        The `plugin_env` should be an ``Environment`` instance that contains
        +        only distributions that are in the project's "plugin directory" or
        +        directories. The `full_env`, if supplied, should be an ``Environment``
        +        contains all currently-available distributions.  If `full_env` is not
        +        supplied, one is created automatically from the ``WorkingSet`` this
        +        method is called on, which will typically mean that every directory on
        +        ``sys.path`` will be scanned for distributions.
        +
        +        `installer` is a standard installer callback as used by the
        +        ``resolve()`` method. The `fallback` flag indicates whether we should
        +        attempt to resolve older versions of a plugin if the newest version
        +        cannot be resolved.
        +
        +        This method returns a 2-tuple: (`distributions`, `error_info`), where
        +        `distributions` is a list of the distributions found in `plugin_env`
        +        that were loadable, along with any other distributions that are needed
        +        to resolve their dependencies.  `error_info` is a dictionary mapping
        +        unloadable plugin distributions to an exception instance describing the
        +        error that occurred. Usually this will be a ``DistributionNotFound`` or
        +        ``VersionConflict`` instance.
        +        """
        +
        +        plugin_projects = list(plugin_env)
        +        # scan project names in alphabetic order
        +        plugin_projects.sort()
        +
        +        error_info: dict[Distribution, Exception] = {}
        +        distributions: dict[Distribution, Exception | None] = {}
        +
        +        if full_env is None:
        +            env = Environment(self.entries)
        +            env += plugin_env
        +        else:
        +            env = full_env + plugin_env
        +
        +        shadow_set = self.__class__([])
        +        # put all our entries in shadow_set
        +        list(map(shadow_set.add, self))
        +
        +        for project_name in plugin_projects:
        +            for dist in plugin_env[project_name]:
        +                req = [dist.as_requirement()]
        +
        +                try:
        +                    resolvees = shadow_set.resolve(req, env, installer)
        +
        +                except ResolutionError as v:
        +                    # save error info
        +                    error_info[dist] = v
        +                    if fallback:
        +                        # try the next older version of project
        +                        continue
        +                    else:
        +                        # give up on this project, keep going
        +                        break
        +
        +                else:
        +                    list(map(shadow_set.add, resolvees))
        +                    distributions.update(dict.fromkeys(resolvees))
        +
        +                    # success, no need to try any more versions of this project
        +                    break
        +
        +        sorted_distributions = list(distributions)
        +        sorted_distributions.sort()
        +
        +        return sorted_distributions, error_info
        +
        +    def require(self, *requirements: _NestedStr):
        +        """Ensure that distributions matching `requirements` are activated
        +
        +        `requirements` must be a string or a (possibly-nested) sequence
        +        thereof, specifying the distributions and versions required.  The
        +        return value is a sequence of the distributions that needed to be
        +        activated to fulfill the requirements; all relevant distributions are
        +        included, even if they were already activated in this working set.
        +        """
        +        needed = self.resolve(parse_requirements(requirements))
        +
        +        for dist in needed:
        +            self.add(dist)
        +
        +        return needed
        +
        +    def subscribe(
        +        self, callback: Callable[[Distribution], object], existing: bool = True
        +    ):
        +        """Invoke `callback` for all distributions
        +
        +        If `existing=True` (default),
        +        call on all existing ones, as well.
        +        """
        +        if callback in self.callbacks:
        +            return
        +        self.callbacks.append(callback)
        +        if not existing:
        +            return
        +        for dist in self:
        +            callback(dist)
        +
        +    def _added_new(self, dist):
        +        for callback in self.callbacks:
        +            callback(dist)
        +
        +    def __getstate__(self):
        +        return (
        +            self.entries[:],
        +            self.entry_keys.copy(),
        +            self.by_key.copy(),
        +            self.normalized_to_canonical_keys.copy(),
        +            self.callbacks[:],
        +        )
        +
        +    def __setstate__(self, e_k_b_n_c):
        +        entries, keys, by_key, normalized_to_canonical_keys, callbacks = e_k_b_n_c
        +        self.entries = entries[:]
        +        self.entry_keys = keys.copy()
        +        self.by_key = by_key.copy()
        +        self.normalized_to_canonical_keys = normalized_to_canonical_keys.copy()
        +        self.callbacks = callbacks[:]
        +
        +
        +class _ReqExtras(Dict["Requirement", Tuple[str, ...]]):
        +    """
        +    Map each requirement to the extras that demanded it.
        +    """
        +
        +    def markers_pass(self, req: Requirement, extras: tuple[str, ...] | None = None):
        +        """
        +        Evaluate markers for req against each extra that
        +        demanded it.
        +
        +        Return False if the req has a marker and fails
        +        evaluation. Otherwise, return True.
        +        """
        +        extra_evals = (
        +            req.marker.evaluate({'extra': extra})
        +            for extra in self.get(req, ()) + (extras or (None,))
        +        )
        +        return not req.marker or any(extra_evals)
        +
        +
        +class Environment:
        +    """Searchable snapshot of distributions on a search path"""
        +
        +    def __init__(
        +        self,
        +        search_path: Iterable[str] | None = None,
        +        platform: str | None = get_supported_platform(),
        +        python: str | None = PY_MAJOR,
        +    ):
        +        """Snapshot distributions available on a search path
        +
        +        Any distributions found on `search_path` are added to the environment.
        +        `search_path` should be a sequence of ``sys.path`` items.  If not
        +        supplied, ``sys.path`` is used.
        +
        +        `platform` is an optional string specifying the name of the platform
        +        that platform-specific distributions must be compatible with.  If
        +        unspecified, it defaults to the current platform.  `python` is an
        +        optional string naming the desired version of Python (e.g. ``'3.6'``);
        +        it defaults to the current version.
        +
        +        You may explicitly set `platform` (and/or `python`) to ``None`` if you
        +        wish to map *all* distributions, not just those compatible with the
        +        running platform or Python version.
        +        """
        +        self._distmap = {}
        +        self.platform = platform
        +        self.python = python
        +        self.scan(search_path)
        +
        +    def can_add(self, dist: Distribution):
        +        """Is distribution `dist` acceptable for this environment?
        +
        +        The distribution must match the platform and python version
        +        requirements specified when this environment was created, or False
        +        is returned.
        +        """
        +        py_compat = (
        +            self.python is None
        +            or dist.py_version is None
        +            or dist.py_version == self.python
        +        )
        +        return py_compat and compatible_platforms(dist.platform, self.platform)
        +
        +    def remove(self, dist: Distribution):
        +        """Remove `dist` from the environment"""
        +        self._distmap[dist.key].remove(dist)
        +
        +    def scan(self, search_path: Iterable[str] | None = None):
        +        """Scan `search_path` for distributions usable in this environment
        +
        +        Any distributions found are added to the environment.
        +        `search_path` should be a sequence of ``sys.path`` items.  If not
        +        supplied, ``sys.path`` is used.  Only distributions conforming to
        +        the platform/python version defined at initialization are added.
        +        """
        +        if search_path is None:
        +            search_path = sys.path
        +
        +        for item in search_path:
        +            for dist in find_distributions(item):
        +                self.add(dist)
        +
        +    def __getitem__(self, project_name: str) -> list[Distribution]:
        +        """Return a newest-to-oldest list of distributions for `project_name`
        +
        +        Uses case-insensitive `project_name` comparison, assuming all the
        +        project's distributions use their project's name converted to all
        +        lowercase as their key.
        +
        +        """
        +        distribution_key = project_name.lower()
        +        return self._distmap.get(distribution_key, [])
        +
        +    def add(self, dist: Distribution):
        +        """Add `dist` if we ``can_add()`` it and it has not already been added"""
        +        if self.can_add(dist) and dist.has_version():
        +            dists = self._distmap.setdefault(dist.key, [])
        +            if dist not in dists:
        +                dists.append(dist)
        +                dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)
        +
        +    @overload
        +    def best_match(
        +        self,
        +        req: Requirement,
        +        working_set: WorkingSet,
        +        installer: _InstallerTypeT[_DistributionT],
        +        replace_conflicting: bool = False,
        +    ) -> _DistributionT: ...
        +    @overload
        +    def best_match(
        +        self,
        +        req: Requirement,
        +        working_set: WorkingSet,
        +        installer: _InstallerType | None = None,
        +        replace_conflicting: bool = False,
        +    ) -> Distribution | None: ...
        +    def best_match(
        +        self,
        +        req: Requirement,
        +        working_set: WorkingSet,
        +        installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None,
        +        replace_conflicting: bool = False,
        +    ) -> Distribution | None:
        +        """Find distribution best matching `req` and usable on `working_set`
        +
        +        This calls the ``find(req)`` method of the `working_set` to see if a
        +        suitable distribution is already active.  (This may raise
        +        ``VersionConflict`` if an unsuitable version of the project is already
        +        active in the specified `working_set`.)  If a suitable distribution
        +        isn't active, this method returns the newest distribution in the
        +        environment that meets the ``Requirement`` in `req`.  If no suitable
        +        distribution is found, and `installer` is supplied, then the result of
        +        calling the environment's ``obtain(req, installer)`` method will be
        +        returned.
        +        """
        +        try:
        +            dist = working_set.find(req)
        +        except VersionConflict:
        +            if not replace_conflicting:
        +                raise
        +            dist = None
        +        if dist is not None:
        +            return dist
        +        for dist in self[req.key]:
        +            if dist in req:
        +                return dist
        +        # try to download/install
        +        return self.obtain(req, installer)
        +
        +    @overload
        +    def obtain(
        +        self,
        +        requirement: Requirement,
        +        installer: _InstallerTypeT[_DistributionT],
        +    ) -> _DistributionT: ...
        +    @overload
        +    def obtain(
        +        self,
        +        requirement: Requirement,
        +        installer: Callable[[Requirement], None] | None = None,
        +    ) -> None: ...
        +    @overload
        +    def obtain(
        +        self,
        +        requirement: Requirement,
        +        installer: _InstallerType | None = None,
        +    ) -> Distribution | None: ...
        +    def obtain(
        +        self,
        +        requirement: Requirement,
        +        installer: Callable[[Requirement], None]
        +        | _InstallerType
        +        | None
        +        | _InstallerTypeT[_DistributionT] = None,
        +    ) -> Distribution | None:
        +        """Obtain a distribution matching `requirement` (e.g. via download)
        +
        +        Obtain a distro that matches requirement (e.g. via download).  In the
        +        base ``Environment`` class, this routine just returns
        +        ``installer(requirement)``, unless `installer` is None, in which case
        +        None is returned instead.  This method is a hook that allows subclasses
        +        to attempt other ways of obtaining a distribution before falling back
        +        to the `installer` argument."""
        +        return installer(requirement) if installer else None
        +
        +    def __iter__(self) -> Iterator[str]:
        +        """Yield the unique project names of the available distributions"""
        +        for key in self._distmap.keys():
        +            if self[key]:
        +                yield key
        +
        +    def __iadd__(self, other: Distribution | Environment):
        +        """In-place addition of a distribution or environment"""
        +        if isinstance(other, Distribution):
        +            self.add(other)
        +        elif isinstance(other, Environment):
        +            for project in other:
        +                for dist in other[project]:
        +                    self.add(dist)
        +        else:
        +            raise TypeError("Can't add %r to environment" % (other,))
        +        return self
        +
        +    def __add__(self, other: Distribution | Environment):
        +        """Add an environment or distribution to an environment"""
        +        new = self.__class__([], platform=None, python=None)
        +        for env in self, other:
        +            new += env
        +        return new
        +
        +
        +# XXX backward compatibility
        +AvailableDistributions = Environment
        +
        +
        +class ExtractionError(RuntimeError):
        +    """An error occurred extracting a resource
        +
        +    The following attributes are available from instances of this exception:
        +
        +    manager
        +        The resource manager that raised this exception
        +
        +    cache_path
        +        The base directory for resource extraction
        +
        +    original_error
        +        The exception instance that caused extraction to fail
        +    """
        +
        +    manager: ResourceManager
        +    cache_path: str
        +    original_error: BaseException | None
        +
        +
        +class ResourceManager:
        +    """Manage resource extraction and packages"""
        +
        +    extraction_path: str | None = None
        +
        +    def __init__(self):
        +        self.cached_files = {}
        +
        +    def resource_exists(self, package_or_requirement: _PkgReqType, resource_name: str):
        +        """Does the named resource exist?"""
        +        return get_provider(package_or_requirement).has_resource(resource_name)
        +
        +    def resource_isdir(self, package_or_requirement: _PkgReqType, resource_name: str):
        +        """Is the named resource an existing directory?"""
        +        return get_provider(package_or_requirement).resource_isdir(resource_name)
        +
        +    def resource_filename(
        +        self, package_or_requirement: _PkgReqType, resource_name: str
        +    ):
        +        """Return a true filesystem path for specified resource"""
        +        return get_provider(package_or_requirement).get_resource_filename(
        +            self, resource_name
        +        )
        +
        +    def resource_stream(self, package_or_requirement: _PkgReqType, resource_name: str):
        +        """Return a readable file-like object for specified resource"""
        +        return get_provider(package_or_requirement).get_resource_stream(
        +            self, resource_name
        +        )
        +
        +    def resource_string(
        +        self, package_or_requirement: _PkgReqType, resource_name: str
        +    ) -> bytes:
        +        """Return specified resource as :obj:`bytes`"""
        +        return get_provider(package_or_requirement).get_resource_string(
        +            self, resource_name
        +        )
        +
        +    def resource_listdir(self, package_or_requirement: _PkgReqType, resource_name: str):
        +        """List the contents of the named resource directory"""
        +        return get_provider(package_or_requirement).resource_listdir(resource_name)
        +
        +    def extraction_error(self) -> NoReturn:
        +        """Give an error message for problems extracting file(s)"""
        +
        +        old_exc = sys.exc_info()[1]
        +        cache_path = self.extraction_path or get_default_cache()
        +
        +        tmpl = textwrap.dedent(
        +            """
        +            Can't extract file(s) to egg cache
        +
        +            The following error occurred while trying to extract file(s)
        +            to the Python egg cache:
        +
        +              {old_exc}
        +
        +            The Python egg cache directory is currently set to:
        +
        +              {cache_path}
        +
        +            Perhaps your account does not have write access to this directory?
        +            You can change the cache directory by setting the PYTHON_EGG_CACHE
        +            environment variable to point to an accessible directory.
        +            """
        +        ).lstrip()
        +        err = ExtractionError(tmpl.format(**locals()))
        +        err.manager = self
        +        err.cache_path = cache_path
        +        err.original_error = old_exc
        +        raise err
        +
        +    def get_cache_path(self, archive_name: str, names: Iterable[StrPath] = ()):
        +        """Return absolute location in cache for `archive_name` and `names`
        +
        +        The parent directory of the resulting path will be created if it does
        +        not already exist.  `archive_name` should be the base filename of the
        +        enclosing egg (which may not be the name of the enclosing zipfile!),
        +        including its ".egg" extension.  `names`, if provided, should be a
        +        sequence of path name parts "under" the egg's extraction location.
        +
        +        This method should only be called by resource providers that need to
        +        obtain an extraction location, and only for names they intend to
        +        extract, as it tracks the generated names for possible cleanup later.
        +        """
        +        extract_path = self.extraction_path or get_default_cache()
        +        target_path = os.path.join(extract_path, archive_name + '-tmp', *names)
        +        try:
        +            _bypass_ensure_directory(target_path)
        +        except Exception:
        +            self.extraction_error()
        +
        +        self._warn_unsafe_extraction_path(extract_path)
        +
        +        self.cached_files[target_path] = True
        +        return target_path
        +
        +    @staticmethod
        +    def _warn_unsafe_extraction_path(path):
        +        """
        +        If the default extraction path is overridden and set to an insecure
        +        location, such as /tmp, it opens up an opportunity for an attacker to
        +        replace an extracted file with an unauthorized payload. Warn the user
        +        if a known insecure location is used.
        +
        +        See Distribute #375 for more details.
        +        """
        +        if os.name == 'nt' and not path.startswith(os.environ['windir']):
        +            # On Windows, permissions are generally restrictive by default
        +            #  and temp directories are not writable by other users, so
        +            #  bypass the warning.
        +            return
        +        mode = os.stat(path).st_mode
        +        if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
        +            msg = (
        +                "Extraction path is writable by group/others "
        +                "and vulnerable to attack when "
        +                "used with get_resource_filename ({path}). "
        +                "Consider a more secure "
        +                "location (set with .set_extraction_path or the "
        +                "PYTHON_EGG_CACHE environment variable)."
        +            ).format(**locals())
        +            warnings.warn(msg, UserWarning)
        +
        +    def postprocess(self, tempname: StrOrBytesPath, filename: StrOrBytesPath):
        +        """Perform any platform-specific postprocessing of `tempname`
        +
        +        This is where Mac header rewrites should be done; other platforms don't
        +        have anything special they should do.
        +
        +        Resource providers should call this method ONLY after successfully
        +        extracting a compressed resource.  They must NOT call it on resources
        +        that are already in the filesystem.
        +
        +        `tempname` is the current (temporary) name of the file, and `filename`
        +        is the name it will be renamed to by the caller after this routine
        +        returns.
        +        """
        +
        +        if os.name == 'posix':
        +            # Make the resource executable
        +            mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777
        +            os.chmod(tempname, mode)
        +
        +    def set_extraction_path(self, path: str):
        +        """Set the base path where resources will be extracted to, if needed.
        +
        +        If you do not call this routine before any extractions take place, the
        +        path defaults to the return value of ``get_default_cache()``.  (Which
        +        is based on the ``PYTHON_EGG_CACHE`` environment variable, with various
        +        platform-specific fallbacks.  See that routine's documentation for more
        +        details.)
        +
        +        Resources are extracted to subdirectories of this path based upon
        +        information given by the ``IResourceProvider``.  You may set this to a
        +        temporary directory, but then you must call ``cleanup_resources()`` to
        +        delete the extracted files when done.  There is no guarantee that
        +        ``cleanup_resources()`` will be able to remove all extracted files.
        +
        +        (Note: you may not change the extraction path for a given resource
        +        manager once resources have been extracted, unless you first call
        +        ``cleanup_resources()``.)
        +        """
        +        if self.cached_files:
        +            raise ValueError("Can't change extraction path, files already extracted")
        +
        +        self.extraction_path = path
        +
        +    def cleanup_resources(self, force: bool = False) -> list[str]:
        +        """
        +        Delete all extracted resource files and directories, returning a list
        +        of the file and directory names that could not be successfully removed.
        +        This function does not have any concurrency protection, so it should
        +        generally only be called when the extraction path is a temporary
        +        directory exclusive to a single process.  This method is not
        +        automatically called; you must call it explicitly or register it as an
        +        ``atexit`` function if you wish to ensure cleanup of a temporary
        +        directory used for extractions.
        +        """
        +        # XXX
        +        return []
        +
        +
        +def get_default_cache() -> str:
        +    """
        +    Return the ``PYTHON_EGG_CACHE`` environment variable
        +    or a platform-relevant user cache dir for an app
        +    named "Python-Eggs".
        +    """
        +    return os.environ.get('PYTHON_EGG_CACHE') or _user_cache_dir(appname='Python-Eggs')
        +
        +
        +def safe_name(name: str):
        +    """Convert an arbitrary string to a standard distribution name
        +
        +    Any runs of non-alphanumeric/. characters are replaced with a single '-'.
        +    """
        +    return re.sub('[^A-Za-z0-9.]+', '-', name)
        +
        +
        +def safe_version(version: str):
        +    """
        +    Convert an arbitrary string to a standard version string
        +    """
        +    try:
        +        # normalize the version
        +        return str(_packaging_version.Version(version))
        +    except _packaging_version.InvalidVersion:
        +        version = version.replace(' ', '.')
        +        return re.sub('[^A-Za-z0-9.]+', '-', version)
        +
        +
        +def _forgiving_version(version):
        +    """Fallback when ``safe_version`` is not safe enough
        +    >>> parse_version(_forgiving_version('0.23ubuntu1'))
        +    
        +    >>> parse_version(_forgiving_version('0.23-'))
        +    
        +    >>> parse_version(_forgiving_version('0.-_'))
        +    
        +    >>> parse_version(_forgiving_version('42.+?1'))
        +    
        +    >>> parse_version(_forgiving_version('hello world'))
        +    
        +    """
        +    version = version.replace(' ', '.')
        +    match = _PEP440_FALLBACK.search(version)
        +    if match:
        +        safe = match["safe"]
        +        rest = version[len(safe) :]
        +    else:
        +        safe = "0"
        +        rest = version
        +    local = f"sanitized.{_safe_segment(rest)}".strip(".")
        +    return f"{safe}.dev0+{local}"
        +
        +
        +def _safe_segment(segment):
        +    """Convert an arbitrary string into a safe segment"""
        +    segment = re.sub('[^A-Za-z0-9.]+', '-', segment)
        +    segment = re.sub('-[^A-Za-z0-9]+', '-', segment)
        +    return re.sub(r'\.[^A-Za-z0-9]+', '.', segment).strip(".-")
        +
        +
        +def safe_extra(extra: str):
        +    """Convert an arbitrary string to a standard 'extra' name
        +
        +    Any runs of non-alphanumeric characters are replaced with a single '_',
        +    and the result is always lowercased.
        +    """
        +    return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower()
        +
        +
        +def to_filename(name: str):
        +    """Convert a project or version name to its filename-escaped form
        +
        +    Any '-' characters are currently replaced with '_'.
        +    """
        +    return name.replace('-', '_')
        +
        +
        +def invalid_marker(text: str):
        +    """
        +    Validate text as a PEP 508 environment marker; return an exception
        +    if invalid or False otherwise.
        +    """
        +    try:
        +        evaluate_marker(text)
        +    except SyntaxError as e:
        +        e.filename = None
        +        e.lineno = None
        +        return e
        +    return False
        +
        +
        +def evaluate_marker(text: str, extra: str | None = None) -> bool:
        +    """
        +    Evaluate a PEP 508 environment marker.
        +    Return a boolean indicating the marker result in this environment.
        +    Raise SyntaxError if marker is invalid.
        +
        +    This implementation uses the 'pyparsing' module.
        +    """
        +    try:
        +        marker = _packaging_markers.Marker(text)
        +        return marker.evaluate()
        +    except _packaging_markers.InvalidMarker as e:
        +        raise SyntaxError(e) from e
        +
        +
        +class NullProvider:
        +    """Try to implement resources and metadata for arbitrary PEP 302 loaders"""
        +
        +    egg_name: str | None = None
        +    egg_info: str | None = None
        +    loader: _LoaderProtocol | None = None
        +
        +    def __init__(self, module: _ModuleLike):
        +        self.loader = getattr(module, '__loader__', None)
        +        self.module_path = os.path.dirname(getattr(module, '__file__', ''))
        +
        +    def get_resource_filename(self, manager: ResourceManager, resource_name: str):
        +        return self._fn(self.module_path, resource_name)
        +
        +    def get_resource_stream(self, manager: ResourceManager, resource_name: str):
        +        return io.BytesIO(self.get_resource_string(manager, resource_name))
        +
        +    def get_resource_string(
        +        self, manager: ResourceManager, resource_name: str
        +    ) -> bytes:
        +        return self._get(self._fn(self.module_path, resource_name))
        +
        +    def has_resource(self, resource_name: str):
        +        return self._has(self._fn(self.module_path, resource_name))
        +
        +    def _get_metadata_path(self, name):
        +        return self._fn(self.egg_info, name)
        +
        +    def has_metadata(self, name: str) -> bool:
        +        if not self.egg_info:
        +            return False
        +
        +        path = self._get_metadata_path(name)
        +        return self._has(path)
        +
        +    def get_metadata(self, name: str):
        +        if not self.egg_info:
        +            return ""
        +        path = self._get_metadata_path(name)
        +        value = self._get(path)
        +        try:
        +            return value.decode('utf-8')
        +        except UnicodeDecodeError as exc:
        +            # Include the path in the error message to simplify
        +            # troubleshooting, and without changing the exception type.
        +            exc.reason += ' in {} file at path: {}'.format(name, path)
        +            raise
        +
        +    def get_metadata_lines(self, name: str) -> Iterator[str]:
        +        return yield_lines(self.get_metadata(name))
        +
        +    def resource_isdir(self, resource_name: str):
        +        return self._isdir(self._fn(self.module_path, resource_name))
        +
        +    def metadata_isdir(self, name: str) -> bool:
        +        return bool(self.egg_info and self._isdir(self._fn(self.egg_info, name)))
        +
        +    def resource_listdir(self, resource_name: str):
        +        return self._listdir(self._fn(self.module_path, resource_name))
        +
        +    def metadata_listdir(self, name: str) -> list[str]:
        +        if self.egg_info:
        +            return self._listdir(self._fn(self.egg_info, name))
        +        return []
        +
        +    def run_script(self, script_name: str, namespace: dict[str, Any]):
        +        script = 'scripts/' + script_name
        +        if not self.has_metadata(script):
        +            raise ResolutionError(
        +                "Script {script!r} not found in metadata at {self.egg_info!r}".format(
        +                    **locals()
        +                ),
        +            )
        +
        +        script_text = self.get_metadata(script).replace('\r\n', '\n')
        +        script_text = script_text.replace('\r', '\n')
        +        script_filename = self._fn(self.egg_info, script)
        +        namespace['__file__'] = script_filename
        +        if os.path.exists(script_filename):
        +            source = _read_utf8_with_fallback(script_filename)
        +            code = compile(source, script_filename, 'exec')
        +            exec(code, namespace, namespace)
        +        else:
        +            from linecache import cache
        +
        +            cache[script_filename] = (
        +                len(script_text),
        +                0,
        +                script_text.split('\n'),
        +                script_filename,
        +            )
        +            script_code = compile(script_text, script_filename, 'exec')
        +            exec(script_code, namespace, namespace)
        +
        +    def _has(self, path) -> bool:
        +        raise NotImplementedError(
        +            "Can't perform this operation for unregistered loader type"
        +        )
        +
        +    def _isdir(self, path) -> bool:
        +        raise NotImplementedError(
        +            "Can't perform this operation for unregistered loader type"
        +        )
        +
        +    def _listdir(self, path) -> list[str]:
        +        raise NotImplementedError(
        +            "Can't perform this operation for unregistered loader type"
        +        )
        +
        +    def _fn(self, base: str | None, resource_name: str):
        +        if base is None:
        +            raise TypeError(
        +                "`base` parameter in `_fn` is `None`. Either override this method or check the parameter first."
        +            )
        +        self._validate_resource_path(resource_name)
        +        if resource_name:
        +            return os.path.join(base, *resource_name.split('/'))
        +        return base
        +
        +    @staticmethod
        +    def _validate_resource_path(path):
        +        """
        +        Validate the resource paths according to the docs.
        +        https://setuptools.pypa.io/en/latest/pkg_resources.html#basic-resource-access
        +
        +        >>> warned = getfixture('recwarn')
        +        >>> warnings.simplefilter('always')
        +        >>> vrp = NullProvider._validate_resource_path
        +        >>> vrp('foo/bar.txt')
        +        >>> bool(warned)
        +        False
        +        >>> vrp('../foo/bar.txt')
        +        >>> bool(warned)
        +        True
        +        >>> warned.clear()
        +        >>> vrp('/foo/bar.txt')
        +        >>> bool(warned)
        +        True
        +        >>> vrp('foo/../../bar.txt')
        +        >>> bool(warned)
        +        True
        +        >>> warned.clear()
        +        >>> vrp('foo/f../bar.txt')
        +        >>> bool(warned)
        +        False
        +
        +        Windows path separators are straight-up disallowed.
        +        >>> vrp(r'\\foo/bar.txt')
        +        Traceback (most recent call last):
        +        ...
        +        ValueError: Use of .. or absolute path in a resource path \
        +is not allowed.
        +
        +        >>> vrp(r'C:\\foo/bar.txt')
        +        Traceback (most recent call last):
        +        ...
        +        ValueError: Use of .. or absolute path in a resource path \
        +is not allowed.
        +
        +        Blank values are allowed
        +
        +        >>> vrp('')
        +        >>> bool(warned)
        +        False
        +
        +        Non-string values are not.
        +
        +        >>> vrp(None)
        +        Traceback (most recent call last):
        +        ...
        +        AttributeError: ...
        +        """
        +        invalid = (
        +            os.path.pardir in path.split(posixpath.sep)
        +            or posixpath.isabs(path)
        +            or ntpath.isabs(path)
        +            or path.startswith("\\")
        +        )
        +        if not invalid:
        +            return
        +
        +        msg = "Use of .. or absolute path in a resource path is not allowed."
        +
        +        # Aggressively disallow Windows absolute paths
        +        if (path.startswith("\\") or ntpath.isabs(path)) and not posixpath.isabs(path):
        +            raise ValueError(msg)
        +
        +        # for compatibility, warn; in future
        +        # raise ValueError(msg)
        +        issue_warning(
        +            msg[:-1] + " and will raise exceptions in a future release.",
        +            DeprecationWarning,
        +        )
        +
        +    def _get(self, path) -> bytes:
        +        if hasattr(self.loader, 'get_data') and self.loader:
        +            # Already checked get_data exists
        +            return self.loader.get_data(path)  # type: ignore[attr-defined]
        +        raise NotImplementedError(
        +            "Can't perform this operation for loaders without 'get_data()'"
        +        )
        +
        +
        +register_loader_type(object, NullProvider)
        +
        +
        +def _parents(path):
        +    """
        +    yield all parents of path including path
        +    """
        +    last = None
        +    while path != last:
        +        yield path
        +        last = path
        +        path, _ = os.path.split(path)
        +
        +
        +class EggProvider(NullProvider):
        +    """Provider based on a virtual filesystem"""
        +
        +    def __init__(self, module: _ModuleLike):
        +        super().__init__(module)
        +        self._setup_prefix()
        +
        +    def _setup_prefix(self):
        +        # Assume that metadata may be nested inside a "basket"
        +        # of multiple eggs and use module_path instead of .archive.
        +        eggs = filter(_is_egg_path, _parents(self.module_path))
        +        egg = next(eggs, None)
        +        egg and self._set_egg(egg)
        +
        +    def _set_egg(self, path: str):
        +        self.egg_name = os.path.basename(path)
        +        self.egg_info = os.path.join(path, 'EGG-INFO')
        +        self.egg_root = path
        +
        +
        +class DefaultProvider(EggProvider):
        +    """Provides access to package resources in the filesystem"""
        +
        +    def _has(self, path) -> bool:
        +        return os.path.exists(path)
        +
        +    def _isdir(self, path) -> bool:
        +        return os.path.isdir(path)
        +
        +    def _listdir(self, path):
        +        return os.listdir(path)
        +
        +    def get_resource_stream(self, manager: object, resource_name: str):
        +        return open(self._fn(self.module_path, resource_name), 'rb')
        +
        +    def _get(self, path) -> bytes:
        +        with open(path, 'rb') as stream:
        +            return stream.read()
        +
        +    @classmethod
        +    def _register(cls):
        +        loader_names = (
        +            'SourceFileLoader',
        +            'SourcelessFileLoader',
        +        )
        +        for name in loader_names:
        +            loader_cls = getattr(importlib.machinery, name, type(None))
        +            register_loader_type(loader_cls, cls)
        +
        +
        +DefaultProvider._register()
        +
        +
        +class EmptyProvider(NullProvider):
        +    """Provider that returns nothing for all requests"""
        +
        +    # A special case, we don't want all Providers inheriting from NullProvider to have a potentially None module_path
        +    module_path: str | None = None  # type: ignore[assignment]
        +
        +    _isdir = _has = lambda self, path: False
        +
        +    def _get(self, path) -> bytes:
        +        return b''
        +
        +    def _listdir(self, path):
        +        return []
        +
        +    def __init__(self):
        +        pass
        +
        +
        +empty_provider = EmptyProvider()
        +
        +
        +class ZipManifests(Dict[str, "MemoizedZipManifests.manifest_mod"]):
        +    """
        +    zip manifest builder
        +    """
        +
        +    # `path` could be `StrPath | IO[bytes]` but that violates the LSP for `MemoizedZipManifests.load`
        +    @classmethod
        +    def build(cls, path: str):
        +        """
        +        Build a dictionary similar to the zipimport directory
        +        caches, except instead of tuples, store ZipInfo objects.
        +
        +        Use a platform-specific path separator (os.sep) for the path keys
        +        for compatibility with pypy on Windows.
        +        """
        +        with zipfile.ZipFile(path) as zfile:
        +            items = (
        +                (
        +                    name.replace('/', os.sep),
        +                    zfile.getinfo(name),
        +                )
        +                for name in zfile.namelist()
        +            )
        +            return dict(items)
        +
        +    load = build
        +
        +
        +class MemoizedZipManifests(ZipManifests):
        +    """
        +    Memoized zipfile manifests.
        +    """
        +
        +    class manifest_mod(NamedTuple):
        +        manifest: dict[str, zipfile.ZipInfo]
        +        mtime: float
        +
        +    def load(self, path: str) -> dict[str, zipfile.ZipInfo]:  # type: ignore[override] # ZipManifests.load is a classmethod
        +        """
        +        Load a manifest at path or return a suitable manifest already loaded.
        +        """
        +        path = os.path.normpath(path)
        +        mtime = os.stat(path).st_mtime
        +
        +        if path not in self or self[path].mtime != mtime:
        +            manifest = self.build(path)
        +            self[path] = self.manifest_mod(manifest, mtime)
        +
        +        return self[path].manifest
        +
        +
        +class ZipProvider(EggProvider):
        +    """Resource support for zips and eggs"""
        +
        +    eagers: list[str] | None = None
        +    _zip_manifests = MemoizedZipManifests()
        +    # ZipProvider's loader should always be a zipimporter or equivalent
        +    loader: zipimport.zipimporter
        +
        +    def __init__(self, module: _ZipLoaderModule):
        +        super().__init__(module)
        +        self.zip_pre = self.loader.archive + os.sep
        +
        +    def _zipinfo_name(self, fspath):
        +        # Convert a virtual filename (full path to file) into a zipfile subpath
        +        # usable with the zipimport directory cache for our target archive
        +        fspath = fspath.rstrip(os.sep)
        +        if fspath == self.loader.archive:
        +            return ''
        +        if fspath.startswith(self.zip_pre):
        +            return fspath[len(self.zip_pre) :]
        +        raise AssertionError("%s is not a subpath of %s" % (fspath, self.zip_pre))
        +
        +    def _parts(self, zip_path):
        +        # Convert a zipfile subpath into an egg-relative path part list.
        +        # pseudo-fs path
        +        fspath = self.zip_pre + zip_path
        +        if fspath.startswith(self.egg_root + os.sep):
        +            return fspath[len(self.egg_root) + 1 :].split(os.sep)
        +        raise AssertionError("%s is not a subpath of %s" % (fspath, self.egg_root))
        +
        +    @property
        +    def zipinfo(self):
        +        return self._zip_manifests.load(self.loader.archive)
        +
        +    def get_resource_filename(self, manager: ResourceManager, resource_name: str):
        +        if not self.egg_name:
        +            raise NotImplementedError(
        +                "resource_filename() only supported for .egg, not .zip"
        +            )
        +        # no need to lock for extraction, since we use temp names
        +        zip_path = self._resource_to_zip(resource_name)
        +        eagers = self._get_eager_resources()
        +        if '/'.join(self._parts(zip_path)) in eagers:
        +            for name in eagers:
        +                self._extract_resource(manager, self._eager_to_zip(name))
        +        return self._extract_resource(manager, zip_path)
        +
        +    @staticmethod
        +    def _get_date_and_size(zip_stat):
        +        size = zip_stat.file_size
        +        # ymdhms+wday, yday, dst
        +        date_time = zip_stat.date_time + (0, 0, -1)
        +        # 1980 offset already done
        +        timestamp = time.mktime(date_time)
        +        return timestamp, size
        +
        +    # FIXME: 'ZipProvider._extract_resource' is too complex (12)
        +    def _extract_resource(self, manager: ResourceManager, zip_path) -> str:  # noqa: C901
        +        if zip_path in self._index():
        +            for name in self._index()[zip_path]:
        +                last = self._extract_resource(manager, os.path.join(zip_path, name))
        +            # return the extracted directory name
        +            return os.path.dirname(last)
        +
        +        timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])
        +
        +        if not WRITE_SUPPORT:
        +            raise OSError(
        +                '"os.rename" and "os.unlink" are not supported on this platform'
        +            )
        +        try:
        +            if not self.egg_name:
        +                raise OSError(
        +                    '"egg_name" is empty. This likely means no egg could be found from the "module_path".'
        +                )
        +            real_path = manager.get_cache_path(self.egg_name, self._parts(zip_path))
        +
        +            if self._is_current(real_path, zip_path):
        +                return real_path
        +
        +            outf, tmpnam = _mkstemp(
        +                ".$extract",
        +                dir=os.path.dirname(real_path),
        +            )
        +            os.write(outf, self.loader.get_data(zip_path))
        +            os.close(outf)
        +            utime(tmpnam, (timestamp, timestamp))
        +            manager.postprocess(tmpnam, real_path)
        +
        +            try:
        +                rename(tmpnam, real_path)
        +
        +            except OSError:
        +                if os.path.isfile(real_path):
        +                    if self._is_current(real_path, zip_path):
        +                        # the file became current since it was checked above,
        +                        #  so proceed.
        +                        return real_path
        +                    # Windows, del old file and retry
        +                    elif os.name == 'nt':
        +                        unlink(real_path)
        +                        rename(tmpnam, real_path)
        +                        return real_path
        +                raise
        +
        +        except OSError:
        +            # report a user-friendly error
        +            manager.extraction_error()
        +
        +        return real_path
        +
        +    def _is_current(self, file_path, zip_path):
        +        """
        +        Return True if the file_path is current for this zip_path
        +        """
        +        timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])
        +        if not os.path.isfile(file_path):
        +            return False
        +        stat = os.stat(file_path)
        +        if stat.st_size != size or stat.st_mtime != timestamp:
        +            return False
        +        # check that the contents match
        +        zip_contents = self.loader.get_data(zip_path)
        +        with open(file_path, 'rb') as f:
        +            file_contents = f.read()
        +        return zip_contents == file_contents
        +
        +    def _get_eager_resources(self):
        +        if self.eagers is None:
        +            eagers = []
        +            for name in ('native_libs.txt', 'eager_resources.txt'):
        +                if self.has_metadata(name):
        +                    eagers.extend(self.get_metadata_lines(name))
        +            self.eagers = eagers
        +        return self.eagers
        +
        +    def _index(self):
        +        try:
        +            return self._dirindex
        +        except AttributeError:
        +            ind = {}
        +            for path in self.zipinfo:
        +                parts = path.split(os.sep)
        +                while parts:
        +                    parent = os.sep.join(parts[:-1])
        +                    if parent in ind:
        +                        ind[parent].append(parts[-1])
        +                        break
        +                    else:
        +                        ind[parent] = [parts.pop()]
        +            self._dirindex = ind
        +            return ind
        +
        +    def _has(self, fspath) -> bool:
        +        zip_path = self._zipinfo_name(fspath)
        +        return zip_path in self.zipinfo or zip_path in self._index()
        +
        +    def _isdir(self, fspath) -> bool:
        +        return self._zipinfo_name(fspath) in self._index()
        +
        +    def _listdir(self, fspath):
        +        return list(self._index().get(self._zipinfo_name(fspath), ()))
        +
        +    def _eager_to_zip(self, resource_name: str):
        +        return self._zipinfo_name(self._fn(self.egg_root, resource_name))
        +
        +    def _resource_to_zip(self, resource_name: str):
        +        return self._zipinfo_name(self._fn(self.module_path, resource_name))
        +
        +
        +register_loader_type(zipimport.zipimporter, ZipProvider)
        +
        +
        +class FileMetadata(EmptyProvider):
        +    """Metadata handler for standalone PKG-INFO files
        +
        +    Usage::
        +
        +        metadata = FileMetadata("/path/to/PKG-INFO")
        +
        +    This provider rejects all data and metadata requests except for PKG-INFO,
        +    which is treated as existing, and will be the contents of the file at
        +    the provided location.
        +    """
        +
        +    def __init__(self, path: StrPath):
        +        self.path = path
        +
        +    def _get_metadata_path(self, name):
        +        return self.path
        +
        +    def has_metadata(self, name: str) -> bool:
        +        return name == 'PKG-INFO' and os.path.isfile(self.path)
        +
        +    def get_metadata(self, name: str):
        +        if name != 'PKG-INFO':
        +            raise KeyError("No metadata except PKG-INFO is available")
        +
        +        with open(self.path, encoding='utf-8', errors="replace") as f:
        +            metadata = f.read()
        +        self._warn_on_replacement(metadata)
        +        return metadata
        +
        +    def _warn_on_replacement(self, metadata):
        +        replacement_char = '�'
        +        if replacement_char in metadata:
        +            tmpl = "{self.path} could not be properly decoded in UTF-8"
        +            msg = tmpl.format(**locals())
        +            warnings.warn(msg)
        +
        +    def get_metadata_lines(self, name: str) -> Iterator[str]:
        +        return yield_lines(self.get_metadata(name))
        +
        +
        +class PathMetadata(DefaultProvider):
        +    """Metadata provider for egg directories
        +
        +    Usage::
        +
        +        # Development eggs:
        +
        +        egg_info = "/path/to/PackageName.egg-info"
        +        base_dir = os.path.dirname(egg_info)
        +        metadata = PathMetadata(base_dir, egg_info)
        +        dist_name = os.path.splitext(os.path.basename(egg_info))[0]
        +        dist = Distribution(basedir, project_name=dist_name, metadata=metadata)
        +
        +        # Unpacked egg directories:
        +
        +        egg_path = "/path/to/PackageName-ver-pyver-etc.egg"
        +        metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO'))
        +        dist = Distribution.from_filename(egg_path, metadata=metadata)
        +    """
        +
        +    def __init__(self, path: str, egg_info: str):
        +        self.module_path = path
        +        self.egg_info = egg_info
        +
        +
        +class EggMetadata(ZipProvider):
        +    """Metadata provider for .egg files"""
        +
        +    def __init__(self, importer: zipimport.zipimporter):
        +        """Create a metadata provider from a zipimporter"""
        +
        +        self.zip_pre = importer.archive + os.sep
        +        self.loader = importer
        +        if importer.prefix:
        +            self.module_path = os.path.join(importer.archive, importer.prefix)
        +        else:
        +            self.module_path = importer.archive
        +        self._setup_prefix()
        +
        +
        +_distribution_finders: dict[type, _DistFinderType[Any]] = _declare_state(
        +    'dict', '_distribution_finders', {}
        +)
        +
        +
        +def register_finder(importer_type: type[_T], distribution_finder: _DistFinderType[_T]):
        +    """Register `distribution_finder` to find distributions in sys.path items
        +
        +    `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
        +    handler), and `distribution_finder` is a callable that, passed a path
        +    item and the importer instance, yields ``Distribution`` instances found on
        +    that path item.  See ``pkg_resources.find_on_path`` for an example."""
        +    _distribution_finders[importer_type] = distribution_finder
        +
        +
        +def find_distributions(path_item: str, only: bool = False):
        +    """Yield distributions accessible via `path_item`"""
        +    importer = get_importer(path_item)
        +    finder = _find_adapter(_distribution_finders, importer)
        +    return finder(importer, path_item, only)
        +
        +
        +def find_eggs_in_zip(
        +    importer: zipimport.zipimporter, path_item: str, only: bool = False
        +) -> Iterator[Distribution]:
        +    """
        +    Find eggs in zip files; possibly multiple nested eggs.
        +    """
        +    if importer.archive.endswith('.whl'):
        +        # wheels are not supported with this finder
        +        # they don't have PKG-INFO metadata, and won't ever contain eggs
        +        return
        +    metadata = EggMetadata(importer)
        +    if metadata.has_metadata('PKG-INFO'):
        +        yield Distribution.from_filename(path_item, metadata=metadata)
        +    if only:
        +        # don't yield nested distros
        +        return
        +    for subitem in metadata.resource_listdir(''):
        +        if _is_egg_path(subitem):
        +            subpath = os.path.join(path_item, subitem)
        +            dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
        +            yield from dists
        +        elif subitem.lower().endswith(('.dist-info', '.egg-info')):
        +            subpath = os.path.join(path_item, subitem)
        +            submeta = EggMetadata(zipimport.zipimporter(subpath))
        +            submeta.egg_info = subpath
        +            yield Distribution.from_location(path_item, subitem, submeta)
        +
        +
        +register_finder(zipimport.zipimporter, find_eggs_in_zip)
        +
        +
        +def find_nothing(
        +    importer: object | None, path_item: str | None, only: bool | None = False
        +):
        +    return ()
        +
        +
        +register_finder(object, find_nothing)
        +
        +
        +def find_on_path(importer: object | None, path_item, only=False):
        +    """Yield distributions accessible on a sys.path directory"""
        +    path_item = _normalize_cached(path_item)
        +
        +    if _is_unpacked_egg(path_item):
        +        yield Distribution.from_filename(
        +            path_item,
        +            metadata=PathMetadata(path_item, os.path.join(path_item, 'EGG-INFO')),
        +        )
        +        return
        +
        +    entries = (os.path.join(path_item, child) for child in safe_listdir(path_item))
        +
        +    # scan for .egg and .egg-info in directory
        +    for entry in sorted(entries):
        +        fullpath = os.path.join(path_item, entry)
        +        factory = dist_factory(path_item, entry, only)
        +        yield from factory(fullpath)
        +
        +
        +def dist_factory(path_item, entry, only):
        +    """Return a dist_factory for the given entry."""
        +    lower = entry.lower()
        +    is_egg_info = lower.endswith('.egg-info')
        +    is_dist_info = lower.endswith('.dist-info') and os.path.isdir(
        +        os.path.join(path_item, entry)
        +    )
        +    is_meta = is_egg_info or is_dist_info
        +    return (
        +        distributions_from_metadata
        +        if is_meta
        +        else find_distributions
        +        if not only and _is_egg_path(entry)
        +        else resolve_egg_link
        +        if not only and lower.endswith('.egg-link')
        +        else NoDists()
        +    )
        +
        +
        +class NoDists:
        +    """
        +    >>> bool(NoDists())
        +    False
        +
        +    >>> list(NoDists()('anything'))
        +    []
        +    """
        +
        +    def __bool__(self):
        +        return False
        +
        +    def __call__(self, fullpath):
        +        return iter(())
        +
        +
        +def safe_listdir(path: StrOrBytesPath):
        +    """
        +    Attempt to list contents of path, but suppress some exceptions.
        +    """
        +    try:
        +        return os.listdir(path)
        +    except (PermissionError, NotADirectoryError):
        +        pass
        +    except OSError as e:
        +        # Ignore the directory if does not exist, not a directory or
        +        # permission denied
        +        if e.errno not in (errno.ENOTDIR, errno.EACCES, errno.ENOENT):
        +            raise
        +    return ()
        +
        +
        +def distributions_from_metadata(path: str):
        +    root = os.path.dirname(path)
        +    if os.path.isdir(path):
        +        if len(os.listdir(path)) == 0:
        +            # empty metadata dir; skip
        +            return
        +        metadata: _MetadataType = PathMetadata(root, path)
        +    else:
        +        metadata = FileMetadata(path)
        +    entry = os.path.basename(path)
        +    yield Distribution.from_location(
        +        root,
        +        entry,
        +        metadata,
        +        precedence=DEVELOP_DIST,
        +    )
        +
        +
        +def non_empty_lines(path):
        +    """
        +    Yield non-empty lines from file at path
        +    """
        +    for line in _read_utf8_with_fallback(path).splitlines():
        +        line = line.strip()
        +        if line:
        +            yield line
        +
        +
        +def resolve_egg_link(path):
        +    """
        +    Given a path to an .egg-link, resolve distributions
        +    present in the referenced path.
        +    """
        +    referenced_paths = non_empty_lines(path)
        +    resolved_paths = (
        +        os.path.join(os.path.dirname(path), ref) for ref in referenced_paths
        +    )
        +    dist_groups = map(find_distributions, resolved_paths)
        +    return next(dist_groups, ())
        +
        +
        +if hasattr(pkgutil, 'ImpImporter'):
        +    register_finder(pkgutil.ImpImporter, find_on_path)
        +
        +register_finder(importlib.machinery.FileFinder, find_on_path)
        +
        +_namespace_handlers: dict[type, _NSHandlerType[Any]] = _declare_state(
        +    'dict', '_namespace_handlers', {}
        +)
        +_namespace_packages: dict[str | None, list[str]] = _declare_state(
        +    'dict', '_namespace_packages', {}
        +)
        +
        +
        +def register_namespace_handler(
        +    importer_type: type[_T], namespace_handler: _NSHandlerType[_T]
        +):
        +    """Register `namespace_handler` to declare namespace packages
        +
        +    `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
        +    handler), and `namespace_handler` is a callable like this::
        +
        +        def namespace_handler(importer, path_entry, moduleName, module):
        +            # return a path_entry to use for child packages
        +
        +    Namespace handlers are only called if the importer object has already
        +    agreed that it can handle the relevant path item, and they should only
        +    return a subpath if the module __path__ does not already contain an
        +    equivalent subpath.  For an example namespace handler, see
        +    ``pkg_resources.file_ns_handler``.
        +    """
        +    _namespace_handlers[importer_type] = namespace_handler
        +
        +
        +def _handle_ns(packageName, path_item):
        +    """Ensure that named package includes a subpath of path_item (if needed)"""
        +
        +    importer = get_importer(path_item)
        +    if importer is None:
        +        return None
        +
        +    # use find_spec (PEP 451) and fall-back to find_module (PEP 302)
        +    try:
        +        spec = importer.find_spec(packageName)
        +    except AttributeError:
        +        # capture warnings due to #1111
        +        with warnings.catch_warnings():
        +            warnings.simplefilter("ignore")
        +            loader = importer.find_module(packageName)
        +    else:
        +        loader = spec.loader if spec else None
        +
        +    if loader is None:
        +        return None
        +    module = sys.modules.get(packageName)
        +    if module is None:
        +        module = sys.modules[packageName] = types.ModuleType(packageName)
        +        module.__path__ = []
        +        _set_parent_ns(packageName)
        +    elif not hasattr(module, '__path__'):
        +        raise TypeError("Not a package:", packageName)
        +    handler = _find_adapter(_namespace_handlers, importer)
        +    subpath = handler(importer, path_item, packageName, module)
        +    if subpath is not None:
        +        path = module.__path__
        +        path.append(subpath)
        +        importlib.import_module(packageName)
        +        _rebuild_mod_path(path, packageName, module)
        +    return subpath
        +
        +
        +def _rebuild_mod_path(orig_path, package_name, module: types.ModuleType):
        +    """
        +    Rebuild module.__path__ ensuring that all entries are ordered
        +    corresponding to their sys.path order
        +    """
        +    sys_path = [_normalize_cached(p) for p in sys.path]
        +
        +    def safe_sys_path_index(entry):
        +        """
        +        Workaround for #520 and #513.
        +        """
        +        try:
        +            return sys_path.index(entry)
        +        except ValueError:
        +            return float('inf')
        +
        +    def position_in_sys_path(path):
        +        """
        +        Return the ordinal of the path based on its position in sys.path
        +        """
        +        path_parts = path.split(os.sep)
        +        module_parts = package_name.count('.') + 1
        +        parts = path_parts[:-module_parts]
        +        return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))
        +
        +    new_path = sorted(orig_path, key=position_in_sys_path)
        +    new_path = [_normalize_cached(p) for p in new_path]
        +
        +    if isinstance(module.__path__, list):
        +        module.__path__[:] = new_path
        +    else:
        +        module.__path__ = new_path
        +
        +
        +def declare_namespace(packageName: str):
        +    """Declare that package 'packageName' is a namespace package"""
        +
        +    msg = (
        +        f"Deprecated call to `pkg_resources.declare_namespace({packageName!r})`.\n"
        +        "Implementing implicit namespace packages (as specified in PEP 420) "
        +        "is preferred to `pkg_resources.declare_namespace`. "
        +        "See https://setuptools.pypa.io/en/latest/references/"
        +        "keywords.html#keyword-namespace-packages"
        +    )
        +    warnings.warn(msg, DeprecationWarning, stacklevel=2)
        +
        +    _imp.acquire_lock()
        +    try:
        +        if packageName in _namespace_packages:
        +            return
        +
        +        path: MutableSequence[str] = sys.path
        +        parent, _, _ = packageName.rpartition('.')
        +
        +        if parent:
        +            declare_namespace(parent)
        +            if parent not in _namespace_packages:
        +                __import__(parent)
        +            try:
        +                path = sys.modules[parent].__path__
        +            except AttributeError as e:
        +                raise TypeError("Not a package:", parent) from e
        +
        +        # Track what packages are namespaces, so when new path items are added,
        +        # they can be updated
        +        _namespace_packages.setdefault(parent or None, []).append(packageName)
        +        _namespace_packages.setdefault(packageName, [])
        +
        +        for path_item in path:
        +            # Ensure all the parent's path items are reflected in the child,
        +            # if they apply
        +            _handle_ns(packageName, path_item)
        +
        +    finally:
        +        _imp.release_lock()
        +
        +
        +def fixup_namespace_packages(path_item: str, parent: str | None = None):
        +    """Ensure that previously-declared namespace packages include path_item"""
        +    _imp.acquire_lock()
        +    try:
        +        for package in _namespace_packages.get(parent, ()):
        +            subpath = _handle_ns(package, path_item)
        +            if subpath:
        +                fixup_namespace_packages(subpath, package)
        +    finally:
        +        _imp.release_lock()
        +
        +
        +def file_ns_handler(
        +    importer: object,
        +    path_item: StrPath,
        +    packageName: str,
        +    module: types.ModuleType,
        +):
        +    """Compute an ns-package subpath for a filesystem or zipfile importer"""
        +
        +    subpath = os.path.join(path_item, packageName.split('.')[-1])
        +    normalized = _normalize_cached(subpath)
        +    for item in module.__path__:
        +        if _normalize_cached(item) == normalized:
        +            break
        +    else:
        +        # Only return the path if it's not already there
        +        return subpath
        +
        +
        +if hasattr(pkgutil, 'ImpImporter'):
        +    register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)
        +
        +register_namespace_handler(zipimport.zipimporter, file_ns_handler)
        +register_namespace_handler(importlib.machinery.FileFinder, file_ns_handler)
        +
        +
        +def null_ns_handler(
        +    importer: object,
        +    path_item: str | None,
        +    packageName: str | None,
        +    module: _ModuleLike | None,
        +):
        +    return None
        +
        +
        +register_namespace_handler(object, null_ns_handler)
        +
        +
        +@overload
        +def normalize_path(filename: StrPath) -> str: ...
        +@overload
        +def normalize_path(filename: BytesPath) -> bytes: ...
        +def normalize_path(filename: StrOrBytesPath):
        +    """Normalize a file/dir name for comparison purposes"""
        +    return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename))))
        +
        +
        +def _cygwin_patch(filename: StrOrBytesPath):  # pragma: nocover
        +    """
        +    Contrary to POSIX 2008, on Cygwin, getcwd (3) contains
        +    symlink components. Using
        +    os.path.abspath() works around this limitation. A fix in os.getcwd()
        +    would probably better, in Cygwin even more so, except
        +    that this seems to be by design...
        +    """
        +    return os.path.abspath(filename) if sys.platform == 'cygwin' else filename
        +
        +
        +if TYPE_CHECKING:
        +    # https://github.com/python/mypy/issues/16261
        +    # https://github.com/python/typeshed/issues/6347
        +    @overload
        +    def _normalize_cached(filename: StrPath) -> str: ...
        +    @overload
        +    def _normalize_cached(filename: BytesPath) -> bytes: ...
        +    def _normalize_cached(filename: StrOrBytesPath) -> str | bytes: ...
        +else:
        +
        +    @functools.lru_cache(maxsize=None)
        +    def _normalize_cached(filename):
        +        return normalize_path(filename)
        +
        +
        +def _is_egg_path(path):
        +    """
        +    Determine if given path appears to be an egg.
        +    """
        +    return _is_zip_egg(path) or _is_unpacked_egg(path)
        +
        +
        +def _is_zip_egg(path):
        +    return (
        +        path.lower().endswith('.egg')
        +        and os.path.isfile(path)
        +        and zipfile.is_zipfile(path)
        +    )
        +
        +
        +def _is_unpacked_egg(path):
        +    """
        +    Determine if given path appears to be an unpacked egg.
        +    """
        +    return path.lower().endswith('.egg') and os.path.isfile(
        +        os.path.join(path, 'EGG-INFO', 'PKG-INFO')
        +    )
        +
        +
        +def _set_parent_ns(packageName):
        +    parts = packageName.split('.')
        +    name = parts.pop()
        +    if parts:
        +        parent = '.'.join(parts)
        +        setattr(sys.modules[parent], name, sys.modules[packageName])
        +
        +
        +MODULE = re.compile(r"\w+(\.\w+)*$").match
        +EGG_NAME = re.compile(
        +    r"""
        +    (?P[^-]+) (
        +        -(?P[^-]+) (
        +            -py(?P[^-]+) (
        +                -(?P.+)
        +            )?
        +        )?
        +    )?
        +    """,
        +    re.VERBOSE | re.IGNORECASE,
        +).match
        +
        +
        +class EntryPoint:
        +    """Object representing an advertised importable object"""
        +
        +    def __init__(
        +        self,
        +        name: str,
        +        module_name: str,
        +        attrs: Iterable[str] = (),
        +        extras: Iterable[str] = (),
        +        dist: Distribution | None = None,
        +    ):
        +        if not MODULE(module_name):
        +            raise ValueError("Invalid module name", module_name)
        +        self.name = name
        +        self.module_name = module_name
        +        self.attrs = tuple(attrs)
        +        self.extras = tuple(extras)
        +        self.dist = dist
        +
        +    def __str__(self):
        +        s = "%s = %s" % (self.name, self.module_name)
        +        if self.attrs:
        +            s += ':' + '.'.join(self.attrs)
        +        if self.extras:
        +            s += ' [%s]' % ','.join(self.extras)
        +        return s
        +
        +    def __repr__(self):
        +        return "EntryPoint.parse(%r)" % str(self)
        +
        +    @overload
        +    def load(
        +        self,
        +        require: Literal[True] = True,
        +        env: Environment | None = None,
        +        installer: _InstallerType | None = None,
        +    ) -> _ResolvedEntryPoint: ...
        +    @overload
        +    def load(
        +        self,
        +        require: Literal[False],
        +        *args: Any,
        +        **kwargs: Any,
        +    ) -> _ResolvedEntryPoint: ...
        +    def load(
        +        self,
        +        require: bool = True,
        +        *args: Environment | _InstallerType | None,
        +        **kwargs: Environment | _InstallerType | None,
        +    ) -> _ResolvedEntryPoint:
        +        """
        +        Require packages for this EntryPoint, then resolve it.
        +        """
        +        if not require or args or kwargs:
        +            warnings.warn(
        +                "Parameters to load are deprecated.  Call .resolve and "
        +                ".require separately.",
        +                PkgResourcesDeprecationWarning,
        +                stacklevel=2,
        +            )
        +        if require:
        +            # We could pass `env` and `installer` directly,
        +            # but keeping `*args` and `**kwargs` for backwards compatibility
        +            self.require(*args, **kwargs)  # type: ignore
        +        return self.resolve()
        +
        +    def resolve(self) -> _ResolvedEntryPoint:
        +        """
        +        Resolve the entry point from its module and attrs.
        +        """
        +        module = __import__(self.module_name, fromlist=['__name__'], level=0)
        +        try:
        +            return functools.reduce(getattr, self.attrs, module)
        +        except AttributeError as exc:
        +            raise ImportError(str(exc)) from exc
        +
        +    def require(
        +        self,
        +        env: Environment | None = None,
        +        installer: _InstallerType | None = None,
        +    ):
        +        if not self.dist:
        +            error_cls = UnknownExtra if self.extras else AttributeError
        +            raise error_cls("Can't require() without a distribution", self)
        +
        +        # Get the requirements for this entry point with all its extras and
        +        # then resolve them. We have to pass `extras` along when resolving so
        +        # that the working set knows what extras we want. Otherwise, for
        +        # dist-info distributions, the working set will assume that the
        +        # requirements for that extra are purely optional and skip over them.
        +        reqs = self.dist.requires(self.extras)
        +        items = working_set.resolve(reqs, env, installer, extras=self.extras)
        +        list(map(working_set.add, items))
        +
        +    pattern = re.compile(
        +        r'\s*'
        +        r'(?P.+?)\s*'
        +        r'=\s*'
        +        r'(?P[\w.]+)\s*'
        +        r'(:\s*(?P[\w.]+))?\s*'
        +        r'(?P\[.*\])?\s*$'
        +    )
        +
        +    @classmethod
        +    def parse(cls, src: str, dist: Distribution | None = None):
        +        """Parse a single entry point from string `src`
        +
        +        Entry point syntax follows the form::
        +
        +            name = some.module:some.attr [extra1, extra2]
        +
        +        The entry name and module name are required, but the ``:attrs`` and
        +        ``[extras]`` parts are optional
        +        """
        +        m = cls.pattern.match(src)
        +        if not m:
        +            msg = "EntryPoint must be in 'name=module:attrs [extras]' format"
        +            raise ValueError(msg, src)
        +        res = m.groupdict()
        +        extras = cls._parse_extras(res['extras'])
        +        attrs = res['attr'].split('.') if res['attr'] else ()
        +        return cls(res['name'], res['module'], attrs, extras, dist)
        +
        +    @classmethod
        +    def _parse_extras(cls, extras_spec):
        +        if not extras_spec:
        +            return ()
        +        req = Requirement.parse('x' + extras_spec)
        +        if req.specs:
        +            raise ValueError
        +        return req.extras
        +
        +    @classmethod
        +    def parse_group(
        +        cls,
        +        group: str,
        +        lines: _NestedStr,
        +        dist: Distribution | None = None,
        +    ):
        +        """Parse an entry point group"""
        +        if not MODULE(group):
        +            raise ValueError("Invalid group name", group)
        +        this: dict[str, Self] = {}
        +        for line in yield_lines(lines):
        +            ep = cls.parse(line, dist)
        +            if ep.name in this:
        +                raise ValueError("Duplicate entry point", group, ep.name)
        +            this[ep.name] = ep
        +        return this
        +
        +    @classmethod
        +    def parse_map(
        +        cls,
        +        data: str | Iterable[str] | dict[str, str | Iterable[str]],
        +        dist: Distribution | None = None,
        +    ):
        +        """Parse a map of entry point groups"""
        +        _data: Iterable[tuple[str | None, str | Iterable[str]]]
        +        if isinstance(data, dict):
        +            _data = data.items()
        +        else:
        +            _data = split_sections(data)
        +        maps: dict[str, dict[str, Self]] = {}
        +        for group, lines in _data:
        +            if group is None:
        +                if not lines:
        +                    continue
        +                raise ValueError("Entry points must be listed in groups")
        +            group = group.strip()
        +            if group in maps:
        +                raise ValueError("Duplicate group name", group)
        +            maps[group] = cls.parse_group(group, lines, dist)
        +        return maps
        +
        +
        +def _version_from_file(lines):
        +    """
        +    Given an iterable of lines from a Metadata file, return
        +    the value of the Version field, if present, or None otherwise.
        +    """
        +
        +    def is_version_line(line):
        +        return line.lower().startswith('version:')
        +
        +    version_lines = filter(is_version_line, lines)
        +    line = next(iter(version_lines), '')
        +    _, _, value = line.partition(':')
        +    return safe_version(value.strip()) or None
        +
        +
        +class Distribution:
        +    """Wrap an actual or potential sys.path entry w/metadata"""
        +
        +    PKG_INFO = 'PKG-INFO'
        +
        +    def __init__(
        +        self,
        +        location: str | None = None,
        +        metadata: _MetadataType = None,
        +        project_name: str | None = None,
        +        version: str | None = None,
        +        py_version: str | None = PY_MAJOR,
        +        platform: str | None = None,
        +        precedence: int = EGG_DIST,
        +    ):
        +        self.project_name = safe_name(project_name or 'Unknown')
        +        if version is not None:
        +            self._version = safe_version(version)
        +        self.py_version = py_version
        +        self.platform = platform
        +        self.location = location
        +        self.precedence = precedence
        +        self._provider = metadata or empty_provider
        +
        +    @classmethod
        +    def from_location(
        +        cls,
        +        location: str,
        +        basename: StrPath,
        +        metadata: _MetadataType = None,
        +        **kw: int,  # We could set `precedence` explicitly, but keeping this as `**kw` for full backwards and subclassing compatibility
        +    ) -> Distribution:
        +        project_name, version, py_version, platform = [None] * 4
        +        basename, ext = os.path.splitext(basename)
        +        if ext.lower() in _distributionImpl:
        +            cls = _distributionImpl[ext.lower()]
        +
        +            match = EGG_NAME(basename)
        +            if match:
        +                project_name, version, py_version, platform = match.group(
        +                    'name', 'ver', 'pyver', 'plat'
        +                )
        +        return cls(
        +            location,
        +            metadata,
        +            project_name=project_name,
        +            version=version,
        +            py_version=py_version,
        +            platform=platform,
        +            **kw,
        +        )._reload_version()
        +
        +    def _reload_version(self):
        +        return self
        +
        +    @property
        +    def hashcmp(self):
        +        return (
        +            self._forgiving_parsed_version,
        +            self.precedence,
        +            self.key,
        +            self.location,
        +            self.py_version or '',
        +            self.platform or '',
        +        )
        +
        +    def __hash__(self):
        +        return hash(self.hashcmp)
        +
        +    def __lt__(self, other: Distribution):
        +        return self.hashcmp < other.hashcmp
        +
        +    def __le__(self, other: Distribution):
        +        return self.hashcmp <= other.hashcmp
        +
        +    def __gt__(self, other: Distribution):
        +        return self.hashcmp > other.hashcmp
        +
        +    def __ge__(self, other: Distribution):
        +        return self.hashcmp >= other.hashcmp
        +
        +    def __eq__(self, other: object):
        +        if not isinstance(other, self.__class__):
        +            # It's not a Distribution, so they are not equal
        +            return False
        +        return self.hashcmp == other.hashcmp
        +
        +    def __ne__(self, other: object):
        +        return not self == other
        +
        +    # These properties have to be lazy so that we don't have to load any
        +    # metadata until/unless it's actually needed.  (i.e., some distributions
        +    # may not know their name or version without loading PKG-INFO)
        +
        +    @property
        +    def key(self):
        +        try:
        +            return self._key
        +        except AttributeError:
        +            self._key = key = self.project_name.lower()
        +            return key
        +
        +    @property
        +    def parsed_version(self):
        +        if not hasattr(self, "_parsed_version"):
        +            try:
        +                self._parsed_version = parse_version(self.version)
        +            except _packaging_version.InvalidVersion as ex:
        +                info = f"(package: {self.project_name})"
        +                if hasattr(ex, "add_note"):
        +                    ex.add_note(info)  # PEP 678
        +                    raise
        +                raise _packaging_version.InvalidVersion(f"{str(ex)} {info}") from None
        +
        +        return self._parsed_version
        +
        +    @property
        +    def _forgiving_parsed_version(self):
        +        try:
        +            return self.parsed_version
        +        except _packaging_version.InvalidVersion as ex:
        +            self._parsed_version = parse_version(_forgiving_version(self.version))
        +
        +            notes = "\n".join(getattr(ex, "__notes__", []))  # PEP 678
        +            msg = f"""!!\n\n
        +            *************************************************************************
        +            {str(ex)}\n{notes}
        +
        +            This is a long overdue deprecation.
        +            For the time being, `pkg_resources` will use `{self._parsed_version}`
        +            as a replacement to avoid breaking existing environments,
        +            but no future compatibility is guaranteed.
        +
        +            If you maintain package {self.project_name} you should implement
        +            the relevant changes to adequate the project to PEP 440 immediately.
        +            *************************************************************************
        +            \n\n!!
        +            """
        +            warnings.warn(msg, DeprecationWarning)
        +
        +            return self._parsed_version
        +
        +    @property
        +    def version(self):
        +        try:
        +            return self._version
        +        except AttributeError as e:
        +            version = self._get_version()
        +            if version is None:
        +                path = self._get_metadata_path_for_display(self.PKG_INFO)
        +                msg = ("Missing 'Version:' header and/or {} file at path: {}").format(
        +                    self.PKG_INFO, path
        +                )
        +                raise ValueError(msg, self) from e
        +
        +            return version
        +
        +    @property
        +    def _dep_map(self):
        +        """
        +        A map of extra to its list of (direct) requirements
        +        for this distribution, including the null extra.
        +        """
        +        try:
        +            return self.__dep_map
        +        except AttributeError:
        +            self.__dep_map = self._filter_extras(self._build_dep_map())
        +        return self.__dep_map
        +
        +    @staticmethod
        +    def _filter_extras(dm: dict[str | None, list[Requirement]]):
        +        """
        +        Given a mapping of extras to dependencies, strip off
        +        environment markers and filter out any dependencies
        +        not matching the markers.
        +        """
        +        for extra in list(filter(None, dm)):
        +            new_extra: str | None = extra
        +            reqs = dm.pop(extra)
        +            new_extra, _, marker = extra.partition(':')
        +            fails_marker = marker and (
        +                invalid_marker(marker) or not evaluate_marker(marker)
        +            )
        +            if fails_marker:
        +                reqs = []
        +            new_extra = safe_extra(new_extra) or None
        +
        +            dm.setdefault(new_extra, []).extend(reqs)
        +        return dm
        +
        +    def _build_dep_map(self):
        +        dm = {}
        +        for name in 'requires.txt', 'depends.txt':
        +            for extra, reqs in split_sections(self._get_metadata(name)):
        +                dm.setdefault(extra, []).extend(parse_requirements(reqs))
        +        return dm
        +
        +    def requires(self, extras: Iterable[str] = ()):
        +        """List of Requirements needed for this distro if `extras` are used"""
        +        dm = self._dep_map
        +        deps: list[Requirement] = []
        +        deps.extend(dm.get(None, ()))
        +        for ext in extras:
        +            try:
        +                deps.extend(dm[safe_extra(ext)])
        +            except KeyError as e:
        +                raise UnknownExtra(
        +                    "%s has no such extra feature %r" % (self, ext)
        +                ) from e
        +        return deps
        +
        +    def _get_metadata_path_for_display(self, name):
        +        """
        +        Return the path to the given metadata file, if available.
        +        """
        +        try:
        +            # We need to access _get_metadata_path() on the provider object
        +            # directly rather than through this class's __getattr__()
        +            # since _get_metadata_path() is marked private.
        +            path = self._provider._get_metadata_path(name)
        +
        +        # Handle exceptions e.g. in case the distribution's metadata
        +        # provider doesn't support _get_metadata_path().
        +        except Exception:
        +            return '[could not detect]'
        +
        +        return path
        +
        +    def _get_metadata(self, name):
        +        if self.has_metadata(name):
        +            yield from self.get_metadata_lines(name)
        +
        +    def _get_version(self):
        +        lines = self._get_metadata(self.PKG_INFO)
        +        return _version_from_file(lines)
        +
        +    def activate(self, path: list[str] | None = None, replace: bool = False):
        +        """Ensure distribution is importable on `path` (default=sys.path)"""
        +        if path is None:
        +            path = sys.path
        +        self.insert_on(path, replace=replace)
        +        if path is sys.path and self.location is not None:
        +            fixup_namespace_packages(self.location)
        +            for pkg in self._get_metadata('namespace_packages.txt'):
        +                if pkg in sys.modules:
        +                    declare_namespace(pkg)
        +
        +    def egg_name(self):
        +        """Return what this distribution's standard .egg filename should be"""
        +        filename = "%s-%s-py%s" % (
        +            to_filename(self.project_name),
        +            to_filename(self.version),
        +            self.py_version or PY_MAJOR,
        +        )
        +
        +        if self.platform:
        +            filename += '-' + self.platform
        +        return filename
        +
        +    def __repr__(self):
        +        if self.location:
        +            return "%s (%s)" % (self, self.location)
        +        else:
        +            return str(self)
        +
        +    def __str__(self):
        +        try:
        +            version = getattr(self, 'version', None)
        +        except ValueError:
        +            version = None
        +        version = version or "[unknown version]"
        +        return "%s %s" % (self.project_name, version)
        +
        +    def __getattr__(self, attr):
        +        """Delegate all unrecognized public attributes to .metadata provider"""
        +        if attr.startswith('_'):
        +            raise AttributeError(attr)
        +        return getattr(self._provider, attr)
        +
        +    def __dir__(self):
        +        return list(
        +            set(super().__dir__())
        +            | set(attr for attr in self._provider.__dir__() if not attr.startswith('_'))
        +        )
        +
        +    @classmethod
        +    def from_filename(
        +        cls,
        +        filename: StrPath,
        +        metadata: _MetadataType = None,
        +        **kw: int,  # We could set `precedence` explicitly, but keeping this as `**kw` for full backwards and subclassing compatibility
        +    ):
        +        return cls.from_location(
        +            _normalize_cached(filename), os.path.basename(filename), metadata, **kw
        +        )
        +
        +    def as_requirement(self):
        +        """Return a ``Requirement`` that matches this distribution exactly"""
        +        if isinstance(self.parsed_version, _packaging_version.Version):
        +            spec = "%s==%s" % (self.project_name, self.parsed_version)
        +        else:
        +            spec = "%s===%s" % (self.project_name, self.parsed_version)
        +
        +        return Requirement.parse(spec)
        +
        +    def load_entry_point(self, group: str, name: str) -> _ResolvedEntryPoint:
        +        """Return the `name` entry point of `group` or raise ImportError"""
        +        ep = self.get_entry_info(group, name)
        +        if ep is None:
        +            raise ImportError("Entry point %r not found" % ((group, name),))
        +        return ep.load()
        +
        +    @overload
        +    def get_entry_map(self, group: None = None) -> dict[str, dict[str, EntryPoint]]: ...
        +    @overload
        +    def get_entry_map(self, group: str) -> dict[str, EntryPoint]: ...
        +    def get_entry_map(self, group: str | None = None):
        +        """Return the entry point map for `group`, or the full entry map"""
        +        if not hasattr(self, "_ep_map"):
        +            self._ep_map = EntryPoint.parse_map(
        +                self._get_metadata('entry_points.txt'), self
        +            )
        +        if group is not None:
        +            return self._ep_map.get(group, {})
        +        return self._ep_map
        +
        +    def get_entry_info(self, group: str, name: str):
        +        """Return the EntryPoint object for `group`+`name`, or ``None``"""
        +        return self.get_entry_map(group).get(name)
        +
        +    # FIXME: 'Distribution.insert_on' is too complex (13)
        +    def insert_on(  # noqa: C901
        +        self,
        +        path: list[str],
        +        loc=None,
        +        replace: bool = False,
        +    ):
        +        """Ensure self.location is on path
        +
        +        If replace=False (default):
        +            - If location is already in path anywhere, do nothing.
        +            - Else:
        +              - If it's an egg and its parent directory is on path,
        +                insert just ahead of the parent.
        +              - Else: add to the end of path.
        +        If replace=True:
        +            - If location is already on path anywhere (not eggs)
        +              or higher priority than its parent (eggs)
        +              do nothing.
        +            - Else:
        +              - If it's an egg and its parent directory is on path,
        +                insert just ahead of the parent,
        +                removing any lower-priority entries.
        +              - Else: add it to the front of path.
        +        """
        +
        +        loc = loc or self.location
        +        if not loc:
        +            return
        +
        +        nloc = _normalize_cached(loc)
        +        bdir = os.path.dirname(nloc)
        +        npath = [(p and _normalize_cached(p) or p) for p in path]
        +
        +        for p, item in enumerate(npath):
        +            if item == nloc:
        +                if replace:
        +                    break
        +                else:
        +                    # don't modify path (even removing duplicates) if
        +                    # found and not replace
        +                    return
        +            elif item == bdir and self.precedence == EGG_DIST:
        +                # if it's an .egg, give it precedence over its directory
        +                # UNLESS it's already been added to sys.path and replace=False
        +                if (not replace) and nloc in npath[p:]:
        +                    return
        +                if path is sys.path:
        +                    self.check_version_conflict()
        +                path.insert(p, loc)
        +                npath.insert(p, nloc)
        +                break
        +        else:
        +            if path is sys.path:
        +                self.check_version_conflict()
        +            if replace:
        +                path.insert(0, loc)
        +            else:
        +                path.append(loc)
        +            return
        +
        +        # p is the spot where we found or inserted loc; now remove duplicates
        +        while True:
        +            try:
        +                np = npath.index(nloc, p + 1)
        +            except ValueError:
        +                break
        +            else:
        +                del npath[np], path[np]
        +                # ha!
        +                p = np
        +
        +        return
        +
        +    def check_version_conflict(self):
        +        if self.key == 'setuptools':
        +            # ignore the inevitable setuptools self-conflicts  :(
        +            return
        +
        +        nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt'))
        +        loc = normalize_path(self.location)
        +        for modname in self._get_metadata('top_level.txt'):
        +            if (
        +                modname not in sys.modules
        +                or modname in nsp
        +                or modname in _namespace_packages
        +            ):
        +                continue
        +            if modname in ('pkg_resources', 'setuptools', 'site'):
        +                continue
        +            fn = getattr(sys.modules[modname], '__file__', None)
        +            if fn and (
        +                normalize_path(fn).startswith(loc) or fn.startswith(self.location)
        +            ):
        +                continue
        +            issue_warning(
        +                "Module %s was already imported from %s, but %s is being added"
        +                " to sys.path" % (modname, fn, self.location),
        +            )
        +
        +    def has_version(self):
        +        try:
        +            self.version
        +        except ValueError:
        +            issue_warning("Unbuilt egg for " + repr(self))
        +            return False
        +        except SystemError:
        +            # TODO: remove this except clause when python/cpython#103632 is fixed.
        +            return False
        +        return True
        +
        +    def clone(self, **kw: str | int | IResourceProvider | None):
        +        """Copy this distribution, substituting in any changed keyword args"""
        +        names = 'project_name version py_version platform location precedence'
        +        for attr in names.split():
        +            kw.setdefault(attr, getattr(self, attr, None))
        +        kw.setdefault('metadata', self._provider)
        +        # Unsafely unpacking. But keeping **kw for backwards and subclassing compatibility
        +        return self.__class__(**kw)  # type:ignore[arg-type]
        +
        +    @property
        +    def extras(self):
        +        return [dep for dep in self._dep_map if dep]
        +
        +
        +class EggInfoDistribution(Distribution):
        +    def _reload_version(self):
        +        """
        +        Packages installed by distutils (e.g. numpy or scipy),
        +        which uses an old safe_version, and so
        +        their version numbers can get mangled when
        +        converted to filenames (e.g., 1.11.0.dev0+2329eae to
        +        1.11.0.dev0_2329eae). These distributions will not be
        +        parsed properly
        +        downstream by Distribution and safe_version, so
        +        take an extra step and try to get the version number from
        +        the metadata file itself instead of the filename.
        +        """
        +        md_version = self._get_version()
        +        if md_version:
        +            self._version = md_version
        +        return self
        +
        +
        +class DistInfoDistribution(Distribution):
        +    """
        +    Wrap an actual or potential sys.path entry
        +    w/metadata, .dist-info style.
        +    """
        +
        +    PKG_INFO = 'METADATA'
        +    EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])")
        +
        +    @property
        +    def _parsed_pkg_info(self):
        +        """Parse and cache metadata"""
        +        try:
        +            return self._pkg_info
        +        except AttributeError:
        +            metadata = self.get_metadata(self.PKG_INFO)
        +            self._pkg_info = email.parser.Parser().parsestr(metadata)
        +            return self._pkg_info
        +
        +    @property
        +    def _dep_map(self):
        +        try:
        +            return self.__dep_map
        +        except AttributeError:
        +            self.__dep_map = self._compute_dependencies()
        +            return self.__dep_map
        +
        +    def _compute_dependencies(self) -> dict[str | None, list[Requirement]]:
        +        """Recompute this distribution's dependencies."""
        +        self.__dep_map: dict[str | None, list[Requirement]] = {None: []}
        +
        +        reqs: list[Requirement] = []
        +        # Including any condition expressions
        +        for req in self._parsed_pkg_info.get_all('Requires-Dist') or []:
        +            reqs.extend(parse_requirements(req))
        +
        +        def reqs_for_extra(extra):
        +            for req in reqs:
        +                if not req.marker or req.marker.evaluate({'extra': extra}):
        +                    yield req
        +
        +        common = types.MappingProxyType(dict.fromkeys(reqs_for_extra(None)))
        +        self.__dep_map[None].extend(common)
        +
        +        for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []:
        +            s_extra = safe_extra(extra.strip())
        +            self.__dep_map[s_extra] = [
        +                r for r in reqs_for_extra(extra) if r not in common
        +            ]
        +
        +        return self.__dep_map
        +
        +
        +_distributionImpl = {
        +    '.egg': Distribution,
        +    '.egg-info': EggInfoDistribution,
        +    '.dist-info': DistInfoDistribution,
        +}
        +
        +
        +def issue_warning(*args, **kw):
        +    level = 1
        +    g = globals()
        +    try:
        +        # find the first stack frame that is *not* code in
        +        # the pkg_resources module, to use for the warning
        +        while sys._getframe(level).f_globals is g:
        +            level += 1
        +    except ValueError:
        +        pass
        +    warnings.warn(stacklevel=level + 1, *args, **kw)
        +
        +
        +def parse_requirements(strs: _NestedStr):
        +    """
        +    Yield ``Requirement`` objects for each specification in `strs`.
        +
        +    `strs` must be a string, or a (possibly-nested) iterable thereof.
        +    """
        +    return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs))))
        +
        +
        +class RequirementParseError(_packaging_requirements.InvalidRequirement):
        +    "Compatibility wrapper for InvalidRequirement"
        +
        +
        +class Requirement(_packaging_requirements.Requirement):
        +    def __init__(self, requirement_string: str):
        +        """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
        +        super().__init__(requirement_string)
        +        self.unsafe_name = self.name
        +        project_name = safe_name(self.name)
        +        self.project_name, self.key = project_name, project_name.lower()
        +        self.specs = [(spec.operator, spec.version) for spec in self.specifier]
        +        # packaging.requirements.Requirement uses a set for its extras. We use a variable-length tuple
        +        self.extras: tuple[str] = tuple(map(safe_extra, self.extras))
        +        self.hashCmp = (
        +            self.key,
        +            self.url,
        +            self.specifier,
        +            frozenset(self.extras),
        +            str(self.marker) if self.marker else None,
        +        )
        +        self.__hash = hash(self.hashCmp)
        +
        +    def __eq__(self, other: object):
        +        return isinstance(other, Requirement) and self.hashCmp == other.hashCmp
        +
        +    def __ne__(self, other):
        +        return not self == other
        +
        +    def __contains__(self, item: Distribution | str | tuple[str, ...]) -> bool:
        +        if isinstance(item, Distribution):
        +            if item.key != self.key:
        +                return False
        +
        +            item = item.version
        +
        +        # Allow prereleases always in order to match the previous behavior of
        +        # this method. In the future this should be smarter and follow PEP 440
        +        # more accurately.
        +        return self.specifier.contains(item, prereleases=True)
        +
        +    def __hash__(self):
        +        return self.__hash
        +
        +    def __repr__(self):
        +        return "Requirement.parse(%r)" % str(self)
        +
        +    @staticmethod
        +    def parse(s: str | Iterable[str]):
        +        (req,) = parse_requirements(s)
        +        return req
        +
        +
        +def _always_object(classes):
        +    """
        +    Ensure object appears in the mro even
        +    for old-style classes.
        +    """
        +    if object not in classes:
        +        return classes + (object,)
        +    return classes
        +
        +
        +def _find_adapter(registry: Mapping[type, _AdapterT], ob: object) -> _AdapterT:
        +    """Return an adapter factory for `ob` from `registry`"""
        +    types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob))))
        +    for t in types:
        +        if t in registry:
        +            return registry[t]
        +    # _find_adapter would previously return None, and immediately be called.
        +    # So we're raising a TypeError to keep backward compatibility if anyone depended on that behaviour.
        +    raise TypeError(f"Could not find adapter for {registry} and {ob}")
        +
        +
        +def ensure_directory(path: StrOrBytesPath):
        +    """Ensure that the parent directory of `path` exists"""
        +    dirname = os.path.dirname(path)
        +    os.makedirs(dirname, exist_ok=True)
        +
        +
        +def _bypass_ensure_directory(path):
        +    """Sandbox-bypassing version of ensure_directory()"""
        +    if not WRITE_SUPPORT:
        +        raise OSError('"os.mkdir" not supported on this platform.')
        +    dirname, filename = split(path)
        +    if dirname and filename and not isdir(dirname):
        +        _bypass_ensure_directory(dirname)
        +        try:
        +            mkdir(dirname, 0o755)
        +        except FileExistsError:
        +            pass
        +
        +
        +def split_sections(s: _NestedStr) -> Iterator[tuple[str | None, list[str]]]:
        +    """Split a string or iterable thereof into (section, content) pairs
        +
        +    Each ``section`` is a stripped version of the section header ("[section]")
        +    and each ``content`` is a list of stripped lines excluding blank lines and
        +    comment-only lines.  If there are any such lines before the first section
        +    header, they're returned in a first ``section`` of ``None``.
        +    """
        +    section = None
        +    content = []
        +    for line in yield_lines(s):
        +        if line.startswith("["):
        +            if line.endswith("]"):
        +                if section or content:
        +                    yield section, content
        +                section = line[1:-1].strip()
        +                content = []
        +            else:
        +                raise ValueError("Invalid section heading", line)
        +        else:
        +            content.append(line)
        +
        +    # wrap up last segment
        +    yield section, content
        +
        +
        +def _mkstemp(*args, **kw):
        +    old_open = os.open
        +    try:
        +        # temporarily bypass sandboxing
        +        os.open = os_open
        +        return tempfile.mkstemp(*args, **kw)
        +    finally:
        +        # and then put it back
        +        os.open = old_open
        +
        +
        +# Silence the PEP440Warning by default, so that end users don't get hit by it
        +# randomly just because they use pkg_resources. We want to append the rule
        +# because we want earlier uses of filterwarnings to take precedence over this
        +# one.
        +warnings.filterwarnings("ignore", category=PEP440Warning, append=True)
        +
        +
        +class PkgResourcesDeprecationWarning(Warning):
        +    """
        +    Base class for warning about deprecations in ``pkg_resources``
        +
        +    This class is not derived from ``DeprecationWarning``, and as such is
        +    visible by default.
        +    """
        +
        +
        +# Ported from ``setuptools`` to avoid introducing an import inter-dependency:
        +_LOCALE_ENCODING = "locale" if sys.version_info >= (3, 10) else None
        +
        +
        +def _read_utf8_with_fallback(file: str, fallback_encoding=_LOCALE_ENCODING) -> str:
        +    """See setuptools.unicode_utils._read_utf8_with_fallback"""
        +    try:
        +        with open(file, "r", encoding="utf-8") as f:
        +            return f.read()
        +    except UnicodeDecodeError:  # pragma: no cover
        +        msg = f"""\
        +        ********************************************************************************
        +        `encoding="utf-8"` fails with {file!r}, trying `encoding={fallback_encoding!r}`.
        +
        +        This fallback behaviour is considered **deprecated** and future versions of
        +        `setuptools/pkg_resources` may not implement it.
        +
        +        Please encode {file!r} with "utf-8" to ensure future builds will succeed.
        +
        +        If this file was produced by `setuptools` itself, cleaning up the cached files
        +        and re-building/re-installing the package with a newer version of `setuptools`
        +        (e.g. by updating `build-system.requires` in its `pyproject.toml`)
        +        might solve the problem.
        +        ********************************************************************************
        +        """
        +        # TODO: Add a deadline?
        +        #       See comment in setuptools.unicode_utils._Utf8EncodingNeeded
        +        warnings.warn(msg, PkgResourcesDeprecationWarning, stacklevel=2)
        +        with open(file, "r", encoding=fallback_encoding) as f:
        +            return f.read()
        +
        +
        +# from jaraco.functools 1.3
        +def _call_aside(f, *args, **kwargs):
        +    f(*args, **kwargs)
        +    return f
        +
        +
        +@_call_aside
        +def _initialize(g=globals()):
        +    "Set up global resource manager (deliberately not state-saved)"
        +    manager = ResourceManager()
        +    g['_manager'] = manager
        +    g.update(
        +        (name, getattr(manager, name))
        +        for name in dir(manager)
        +        if not name.startswith('_')
        +    )
        +
        +
        +@_call_aside
        +def _initialize_master_working_set():
        +    """
        +    Prepare the master working set and make the ``require()``
        +    API available.
        +
        +    This function has explicit effects on the global state
        +    of pkg_resources. It is intended to be invoked once at
        +    the initialization of this module.
        +
        +    Invocation by other packages is unsupported and done
        +    at their own risk.
        +    """
        +    working_set = _declare_state('object', 'working_set', WorkingSet._build_master())
        +
        +    require = working_set.require
        +    iter_entry_points = working_set.iter_entry_points
        +    add_activation_listener = working_set.subscribe
        +    run_script = working_set.run_script
        +    # backward compatibility
        +    run_main = run_script
        +    # Activate all distributions already on sys.path with replace=False and
        +    # ensure that all distributions added to the working set in the future
        +    # (e.g. by calling ``require()``) will get activated as well,
        +    # with higher priority (replace=True).
        +    tuple(dist.activate(replace=False) for dist in working_set)
        +    add_activation_listener(
        +        lambda dist: dist.activate(replace=True),
        +        existing=False,
        +    )
        +    working_set.entries = []
        +    # match order
        +    list(map(working_set.add_entry, sys.path))
        +    globals().update(locals())
        +
        +
        +if TYPE_CHECKING:
        +    # All of these are set by the @_call_aside methods above
        +    __resource_manager = ResourceManager()  # Won't exist at runtime
        +    resource_exists = __resource_manager.resource_exists
        +    resource_isdir = __resource_manager.resource_isdir
        +    resource_filename = __resource_manager.resource_filename
        +    resource_stream = __resource_manager.resource_stream
        +    resource_string = __resource_manager.resource_string
        +    resource_listdir = __resource_manager.resource_listdir
        +    set_extraction_path = __resource_manager.set_extraction_path
        +    cleanup_resources = __resource_manager.cleanup_resources
        +
        +    working_set = WorkingSet()
        +    require = working_set.require
        +    iter_entry_points = working_set.iter_entry_points
        +    add_activation_listener = working_set.subscribe
        +    run_script = working_set.run_script
        +    run_main = run_script
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..b7eae1966a41283d4cff1f080e05bed0cc37681e
        GIT binary patch
        literal 112459
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__init__.py
        new file mode 100644
        index 000000000..edc21fad2
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__init__.py
        @@ -0,0 +1,631 @@
        +"""
        +Utilities for determining application-specific dirs.
        +
        +See  for details and usage.
        +
        +"""
        +
        +from __future__ import annotations
        +
        +import os
        +import sys
        +from typing import TYPE_CHECKING
        +
        +from .api import PlatformDirsABC
        +from .version import __version__
        +from .version import __version_tuple__ as __version_info__
        +
        +if TYPE_CHECKING:
        +    from pathlib import Path
        +    from typing import Literal
        +
        +if sys.platform == "win32":
        +    from pip._vendor.platformdirs.windows import Windows as _Result
        +elif sys.platform == "darwin":
        +    from pip._vendor.platformdirs.macos import MacOS as _Result
        +else:
        +    from pip._vendor.platformdirs.unix import Unix as _Result
        +
        +
        +def _set_platform_dir_class() -> type[PlatformDirsABC]:
        +    if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
        +        if os.getenv("SHELL") or os.getenv("PREFIX"):
        +            return _Result
        +
        +        from pip._vendor.platformdirs.android import _android_folder  # noqa: PLC0415
        +
        +        if _android_folder() is not None:
        +            from pip._vendor.platformdirs.android import Android  # noqa: PLC0415
        +
        +            return Android  # return to avoid redefinition of a result
        +
        +    return _Result
        +
        +
        +if TYPE_CHECKING:
        +    # Work around mypy issue: https://github.com/python/mypy/issues/10962
        +    PlatformDirs = _Result
        +else:
        +    PlatformDirs = _set_platform_dir_class()  #: Currently active platform
        +AppDirs = PlatformDirs  #: Backwards compatibility with appdirs
        +
        +
        +def user_data_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    roaming: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param roaming: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: data directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        roaming=roaming,
        +        ensure_exists=ensure_exists,
        +    ).user_data_dir
        +
        +
        +def site_data_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    multipath: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param multipath: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: data directory shared by users
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        multipath=multipath,
        +        ensure_exists=ensure_exists,
        +    ).site_data_dir
        +
        +
        +def user_config_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    roaming: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param roaming: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: config directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        roaming=roaming,
        +        ensure_exists=ensure_exists,
        +    ).user_config_dir
        +
        +
        +def site_config_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    multipath: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param multipath: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: config directory shared by the users
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        multipath=multipath,
        +        ensure_exists=ensure_exists,
        +    ).site_config_dir
        +
        +
        +def user_cache_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: cache directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).user_cache_dir
        +
        +
        +def site_cache_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `opinion `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: cache directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).site_cache_dir
        +
        +
        +def user_state_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    roaming: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param roaming: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: state directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        roaming=roaming,
        +        ensure_exists=ensure_exists,
        +    ).user_state_dir
        +
        +
        +def user_log_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: log directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).user_log_dir
        +
        +
        +def user_documents_dir() -> str:
        +    """:returns: documents directory tied to the user"""
        +    return PlatformDirs().user_documents_dir
        +
        +
        +def user_downloads_dir() -> str:
        +    """:returns: downloads directory tied to the user"""
        +    return PlatformDirs().user_downloads_dir
        +
        +
        +def user_pictures_dir() -> str:
        +    """:returns: pictures directory tied to the user"""
        +    return PlatformDirs().user_pictures_dir
        +
        +
        +def user_videos_dir() -> str:
        +    """:returns: videos directory tied to the user"""
        +    return PlatformDirs().user_videos_dir
        +
        +
        +def user_music_dir() -> str:
        +    """:returns: music directory tied to the user"""
        +    return PlatformDirs().user_music_dir
        +
        +
        +def user_desktop_dir() -> str:
        +    """:returns: desktop directory tied to the user"""
        +    return PlatformDirs().user_desktop_dir
        +
        +
        +def user_runtime_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `opinion `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: runtime directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).user_runtime_dir
        +
        +
        +def site_runtime_dir(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> str:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `opinion `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: runtime directory shared by users
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).site_runtime_dir
        +
        +
        +def user_data_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    roaming: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param roaming: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: data path tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        roaming=roaming,
        +        ensure_exists=ensure_exists,
        +    ).user_data_path
        +
        +
        +def site_data_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    multipath: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param multipath: See `multipath `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: data path shared by users
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        multipath=multipath,
        +        ensure_exists=ensure_exists,
        +    ).site_data_path
        +
        +
        +def user_config_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    roaming: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param roaming: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: config path tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        roaming=roaming,
        +        ensure_exists=ensure_exists,
        +    ).user_config_path
        +
        +
        +def site_config_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    multipath: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param multipath: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: config path shared by the users
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        multipath=multipath,
        +        ensure_exists=ensure_exists,
        +    ).site_config_path
        +
        +
        +def site_cache_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `opinion `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: cache directory tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).site_cache_path
        +
        +
        +def user_cache_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: cache path tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).user_cache_path
        +
        +
        +def user_state_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    roaming: bool = False,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param roaming: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: state path tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        roaming=roaming,
        +        ensure_exists=ensure_exists,
        +    ).user_state_path
        +
        +
        +def user_log_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `roaming `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: log path tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).user_log_path
        +
        +
        +def user_documents_path() -> Path:
        +    """:returns: documents a path tied to the user"""
        +    return PlatformDirs().user_documents_path
        +
        +
        +def user_downloads_path() -> Path:
        +    """:returns: downloads path tied to the user"""
        +    return PlatformDirs().user_downloads_path
        +
        +
        +def user_pictures_path() -> Path:
        +    """:returns: pictures path tied to the user"""
        +    return PlatformDirs().user_pictures_path
        +
        +
        +def user_videos_path() -> Path:
        +    """:returns: videos path tied to the user"""
        +    return PlatformDirs().user_videos_path
        +
        +
        +def user_music_path() -> Path:
        +    """:returns: music path tied to the user"""
        +    return PlatformDirs().user_music_path
        +
        +
        +def user_desktop_path() -> Path:
        +    """:returns: desktop path tied to the user"""
        +    return PlatformDirs().user_desktop_path
        +
        +
        +def user_runtime_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `opinion `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: runtime path tied to the user
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).user_runtime_path
        +
        +
        +def site_runtime_path(
        +    appname: str | None = None,
        +    appauthor: str | None | Literal[False] = None,
        +    version: str | None = None,
        +    opinion: bool = True,  # noqa: FBT001, FBT002
        +    ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +) -> Path:
        +    """
        +    :param appname: See `appname `.
        +    :param appauthor: See `appauthor `.
        +    :param version: See `version `.
        +    :param opinion: See `opinion `.
        +    :param ensure_exists: See `ensure_exists `.
        +    :returns: runtime path shared by users
        +    """
        +    return PlatformDirs(
        +        appname=appname,
        +        appauthor=appauthor,
        +        version=version,
        +        opinion=opinion,
        +        ensure_exists=ensure_exists,
        +    ).site_runtime_path
        +
        +
        +__all__ = [
        +    "AppDirs",
        +    "PlatformDirs",
        +    "PlatformDirsABC",
        +    "__version__",
        +    "__version_info__",
        +    "site_cache_dir",
        +    "site_cache_path",
        +    "site_config_dir",
        +    "site_config_path",
        +    "site_data_dir",
        +    "site_data_path",
        +    "site_runtime_dir",
        +    "site_runtime_path",
        +    "user_cache_dir",
        +    "user_cache_path",
        +    "user_config_dir",
        +    "user_config_path",
        +    "user_data_dir",
        +    "user_data_path",
        +    "user_desktop_dir",
        +    "user_desktop_path",
        +    "user_documents_dir",
        +    "user_documents_path",
        +    "user_downloads_dir",
        +    "user_downloads_path",
        +    "user_log_dir",
        +    "user_log_path",
        +    "user_music_dir",
        +    "user_music_path",
        +    "user_pictures_dir",
        +    "user_pictures_path",
        +    "user_runtime_dir",
        +    "user_runtime_path",
        +    "user_state_dir",
        +    "user_state_path",
        +    "user_videos_dir",
        +    "user_videos_path",
        +]
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__main__.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__main__.py
        new file mode 100644
        index 000000000..fa8a677a3
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__main__.py
        @@ -0,0 +1,55 @@
        +"""Main entry point."""
        +
        +from __future__ import annotations
        +
        +from pip._vendor.platformdirs import PlatformDirs, __version__
        +
        +PROPS = (
        +    "user_data_dir",
        +    "user_config_dir",
        +    "user_cache_dir",
        +    "user_state_dir",
        +    "user_log_dir",
        +    "user_documents_dir",
        +    "user_downloads_dir",
        +    "user_pictures_dir",
        +    "user_videos_dir",
        +    "user_music_dir",
        +    "user_runtime_dir",
        +    "site_data_dir",
        +    "site_config_dir",
        +    "site_cache_dir",
        +    "site_runtime_dir",
        +)
        +
        +
        +def main() -> None:
        +    """Run the main entry point."""
        +    app_name = "MyApp"
        +    app_author = "MyCompany"
        +
        +    print(f"-- platformdirs {__version__} --")  # noqa: T201
        +
        +    print("-- app dirs (with optional 'version')")  # noqa: T201
        +    dirs = PlatformDirs(app_name, app_author, version="1.0")
        +    for prop in PROPS:
        +        print(f"{prop}: {getattr(dirs, prop)}")  # noqa: T201
        +
        +    print("\n-- app dirs (without optional 'version')")  # noqa: T201
        +    dirs = PlatformDirs(app_name, app_author)
        +    for prop in PROPS:
        +        print(f"{prop}: {getattr(dirs, prop)}")  # noqa: T201
        +
        +    print("\n-- app dirs (without optional 'appauthor')")  # noqa: T201
        +    dirs = PlatformDirs(app_name)
        +    for prop in PROPS:
        +        print(f"{prop}: {getattr(dirs, prop)}")  # noqa: T201
        +
        +    print("\n-- app dirs (with disabled 'appauthor')")  # noqa: T201
        +    dirs = PlatformDirs(app_name, appauthor=False)
        +    for prop in PROPS:
        +        print(f"{prop}: {getattr(dirs, prop)}")  # noqa: T201
        +
        +
        +if __name__ == "__main__":
        +    main()
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..e96130edf86ce2e84f368675510273ce7dedb297
        GIT binary patch
        literal 16525
        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
        SncI?~et4{LjI6WR-~S7}OIu|C
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..25e3afc4bd919d9a55f7a539ef2e74056b477074
        GIT binary patch
        literal 1410
        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
        aa2z?8sbq?3QLVr=r&hAHKPhVFpZ@~r8E~}#
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..dc20b1cb40e9272748bafcc86985f51b93ca17a4
        GIT binary patch
        literal 10870
        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
        Z6Z#CEOU6uYA~W+x+Wpzm%q8Rh{vVx0Ny-2K
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..cdba0eb4995ab2d4676f9abe332747203af7a0b7
        GIT binary patch
        literal 541
        zcmYjN&1xGl5Y}pc?ATq}TmpFnAG|8KlpaheByJNL+|byC>LS+BI99W(6*u1{k0SyUVn`re(qkgyfIQO)ba)0SbY(IoJ5G@^)Y+apVFmx_;M2KQjV^p=4Yozk_l4NILiodf!i91S<$trgz7*UrBbqSU%^rNfuCLfAC)hDkOIu^oh8#2yAez|&8*YHDC`54Q>`b_#2#
        z8dHOI?4n$>%xv7M(L*yDE-k=9!O+?A`W7r(gI=>rE%qg&;vH+1gQ3B6c>~z048xz%
        vva!rmrCT<(mddu+x%;@nY
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..1147db6b2e62f08820d3f3b11bac75ca9474ab61
        GIT binary patch
        literal 9272
        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
        BZ!iD=
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/android.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/android.py
        new file mode 100644
        index 000000000..7004a8524
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/android.py
        @@ -0,0 +1,249 @@
        +"""Android."""
        +
        +from __future__ import annotations
        +
        +import os
        +import re
        +import sys
        +from functools import lru_cache
        +from typing import TYPE_CHECKING, cast
        +
        +from .api import PlatformDirsABC
        +
        +
        +class Android(PlatformDirsABC):
        +    """
        +    Follows the guidance `from here `_.
        +
        +    Makes use of the `appname `, `version
        +    `, `ensure_exists `.
        +
        +    """
        +
        +    @property
        +    def user_data_dir(self) -> str:
        +        """:return: data directory tied to the user, e.g. ``/data/user///files/``"""
        +        return self._append_app_name_and_version(cast(str, _android_folder()), "files")
        +
        +    @property
        +    def site_data_dir(self) -> str:
        +        """:return: data directory shared by users, same as `user_data_dir`"""
        +        return self.user_data_dir
        +
        +    @property
        +    def user_config_dir(self) -> str:
        +        """
        +        :return: config directory tied to the user, e.g. \
        +        ``/data/user///shared_prefs/``
        +        """
        +        return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs")
        +
        +    @property
        +    def site_config_dir(self) -> str:
        +        """:return: config directory shared by the users, same as `user_config_dir`"""
        +        return self.user_config_dir
        +
        +    @property
        +    def user_cache_dir(self) -> str:
        +        """:return: cache directory tied to the user, e.g.,``/data/user///cache/``"""
        +        return self._append_app_name_and_version(cast(str, _android_folder()), "cache")
        +
        +    @property
        +    def site_cache_dir(self) -> str:
        +        """:return: cache directory shared by users, same as `user_cache_dir`"""
        +        return self.user_cache_dir
        +
        +    @property
        +    def user_state_dir(self) -> str:
        +        """:return: state directory tied to the user, same as `user_data_dir`"""
        +        return self.user_data_dir
        +
        +    @property
        +    def user_log_dir(self) -> str:
        +        """
        +        :return: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it,
        +          e.g. ``/data/user///cache//log``
        +        """
        +        path = self.user_cache_dir
        +        if self.opinion:
        +            path = os.path.join(path, "log")  # noqa: PTH118
        +        return path
        +
        +    @property
        +    def user_documents_dir(self) -> str:
        +        """:return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``"""
        +        return _android_documents_folder()
        +
        +    @property
        +    def user_downloads_dir(self) -> str:
        +        """:return: downloads directory tied to the user e.g. ``/storage/emulated/0/Downloads``"""
        +        return _android_downloads_folder()
        +
        +    @property
        +    def user_pictures_dir(self) -> str:
        +        """:return: pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``"""
        +        return _android_pictures_folder()
        +
        +    @property
        +    def user_videos_dir(self) -> str:
        +        """:return: videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``"""
        +        return _android_videos_folder()
        +
        +    @property
        +    def user_music_dir(self) -> str:
        +        """:return: music directory tied to the user e.g. ``/storage/emulated/0/Music``"""
        +        return _android_music_folder()
        +
        +    @property
        +    def user_desktop_dir(self) -> str:
        +        """:return: desktop directory tied to the user e.g. ``/storage/emulated/0/Desktop``"""
        +        return "/storage/emulated/0/Desktop"
        +
        +    @property
        +    def user_runtime_dir(self) -> str:
        +        """
        +        :return: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it,
        +          e.g. ``/data/user///cache//tmp``
        +        """
        +        path = self.user_cache_dir
        +        if self.opinion:
        +            path = os.path.join(path, "tmp")  # noqa: PTH118
        +        return path
        +
        +    @property
        +    def site_runtime_dir(self) -> str:
        +        """:return: runtime directory shared by users, same as `user_runtime_dir`"""
        +        return self.user_runtime_dir
        +
        +
        +@lru_cache(maxsize=1)
        +def _android_folder() -> str | None:  # noqa: C901
        +    """:return: base folder for the Android OS or None if it cannot be found"""
        +    result: str | None = None
        +    # type checker isn't happy with our "import android", just don't do this when type checking see
        +    # https://stackoverflow.com/a/61394121
        +    if not TYPE_CHECKING:
        +        try:
        +            # First try to get a path to android app using python4android (if available)...
        +            from android import mActivity  # noqa: PLC0415
        +
        +            context = cast("android.content.Context", mActivity.getApplicationContext())  # noqa: F821
        +            result = context.getFilesDir().getParentFile().getAbsolutePath()
        +        except Exception:  # noqa: BLE001
        +            result = None
        +    if result is None:
        +        try:
        +            # ...and fall back to using plain pyjnius, if python4android isn't available or doesn't deliver any useful
        +            # result...
        +            from jnius import autoclass  # noqa: PLC0415
        +
        +            context = autoclass("android.content.Context")
        +            result = context.getFilesDir().getParentFile().getAbsolutePath()
        +        except Exception:  # noqa: BLE001
        +            result = None
        +    if result is None:
        +        # and if that fails, too, find an android folder looking at path on the sys.path
        +        # warning: only works for apps installed under /data, not adopted storage etc.
        +        pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
        +        for path in sys.path:
        +            if pattern.match(path):
        +                result = path.split("/files")[0]
        +                break
        +        else:
        +            result = None
        +    if result is None:
        +        # one last try: find an android folder looking at path on the sys.path taking adopted storage paths into
        +        # account
        +        pattern = re.compile(r"/mnt/expand/[a-fA-F0-9-]{36}/(data|user/\d+)/(.+)/files")
        +        for path in sys.path:
        +            if pattern.match(path):
        +                result = path.split("/files")[0]
        +                break
        +        else:
        +            result = None
        +    return result
        +
        +
        +@lru_cache(maxsize=1)
        +def _android_documents_folder() -> str:
        +    """:return: documents folder for the Android OS"""
        +    # Get directories with pyjnius
        +    try:
        +        from jnius import autoclass  # noqa: PLC0415
        +
        +        context = autoclass("android.content.Context")
        +        environment = autoclass("android.os.Environment")
        +        documents_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
        +    except Exception:  # noqa: BLE001
        +        documents_dir = "/storage/emulated/0/Documents"
        +
        +    return documents_dir
        +
        +
        +@lru_cache(maxsize=1)
        +def _android_downloads_folder() -> str:
        +    """:return: downloads folder for the Android OS"""
        +    # Get directories with pyjnius
        +    try:
        +        from jnius import autoclass  # noqa: PLC0415
        +
        +        context = autoclass("android.content.Context")
        +        environment = autoclass("android.os.Environment")
        +        downloads_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
        +    except Exception:  # noqa: BLE001
        +        downloads_dir = "/storage/emulated/0/Downloads"
        +
        +    return downloads_dir
        +
        +
        +@lru_cache(maxsize=1)
        +def _android_pictures_folder() -> str:
        +    """:return: pictures folder for the Android OS"""
        +    # Get directories with pyjnius
        +    try:
        +        from jnius import autoclass  # noqa: PLC0415
        +
        +        context = autoclass("android.content.Context")
        +        environment = autoclass("android.os.Environment")
        +        pictures_dir: str = context.getExternalFilesDir(environment.DIRECTORY_PICTURES).getAbsolutePath()
        +    except Exception:  # noqa: BLE001
        +        pictures_dir = "/storage/emulated/0/Pictures"
        +
        +    return pictures_dir
        +
        +
        +@lru_cache(maxsize=1)
        +def _android_videos_folder() -> str:
        +    """:return: videos folder for the Android OS"""
        +    # Get directories with pyjnius
        +    try:
        +        from jnius import autoclass  # noqa: PLC0415
        +
        +        context = autoclass("android.content.Context")
        +        environment = autoclass("android.os.Environment")
        +        videos_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DCIM).getAbsolutePath()
        +    except Exception:  # noqa: BLE001
        +        videos_dir = "/storage/emulated/0/DCIM/Camera"
        +
        +    return videos_dir
        +
        +
        +@lru_cache(maxsize=1)
        +def _android_music_folder() -> str:
        +    """:return: music folder for the Android OS"""
        +    # Get directories with pyjnius
        +    try:
        +        from jnius import autoclass  # noqa: PLC0415
        +
        +        context = autoclass("android.content.Context")
        +        environment = autoclass("android.os.Environment")
        +        music_dir: str = context.getExternalFilesDir(environment.DIRECTORY_MUSIC).getAbsolutePath()
        +    except Exception:  # noqa: BLE001
        +        music_dir = "/storage/emulated/0/Music"
        +
        +    return music_dir
        +
        +
        +__all__ = [
        +    "Android",
        +]
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/api.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/api.py
        new file mode 100644
        index 000000000..18d660e4f
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/api.py
        @@ -0,0 +1,298 @@
        +"""Base API."""
        +
        +from __future__ import annotations
        +
        +import os
        +from abc import ABC, abstractmethod
        +from pathlib import Path
        +from typing import TYPE_CHECKING
        +
        +if TYPE_CHECKING:
        +    from typing import Iterator, Literal
        +
        +
        +class PlatformDirsABC(ABC):  # noqa: PLR0904
        +    """Abstract base class for platform directories."""
        +
        +    def __init__(  # noqa: PLR0913, PLR0917
        +        self,
        +        appname: str | None = None,
        +        appauthor: str | None | Literal[False] = None,
        +        version: str | None = None,
        +        roaming: bool = False,  # noqa: FBT001, FBT002
        +        multipath: bool = False,  # noqa: FBT001, FBT002
        +        opinion: bool = True,  # noqa: FBT001, FBT002
        +        ensure_exists: bool = False,  # noqa: FBT001, FBT002
        +    ) -> None:
        +        """
        +        Create a new platform directory.
        +
        +        :param appname: See `appname`.
        +        :param appauthor: See `appauthor`.
        +        :param version: See `version`.
        +        :param roaming: See `roaming`.
        +        :param multipath: See `multipath`.
        +        :param opinion: See `opinion`.
        +        :param ensure_exists: See `ensure_exists`.
        +
        +        """
        +        self.appname = appname  #: The name of application.
        +        self.appauthor = appauthor
        +        """
        +        The name of the app author or distributing body for this application.
        +
        +        Typically, it is the owning company name. Defaults to `appname`. You may pass ``False`` to disable it.
        +
        +        """
        +        self.version = version
        +        """
        +        An optional version path element to append to the path.
        +
        +        You might want to use this if you want multiple versions of your app to be able to run independently. If used,
        +        this would typically be ``.``.
        +
        +        """
        +        self.roaming = roaming
        +        """
        +        Whether to use the roaming appdata directory on Windows.
        +
        +        That means that for users on a Windows network setup for roaming profiles, this user data will be synced on
        +        login (see
        +        `here `_).
        +
        +        """
        +        self.multipath = multipath
        +        """
        +        An optional parameter which indicates that the entire list of data dirs should be returned.
        +
        +        By default, the first item would only be returned.
        +
        +        """
        +        self.opinion = opinion  #: A flag to indicating to use opinionated values.
        +        self.ensure_exists = ensure_exists
        +        """
        +        Optionally create the directory (and any missing parents) upon access if it does not exist.
        +
        +        By default, no directories are created.
        +
        +        """
        +
        +    def _append_app_name_and_version(self, *base: str) -> str:
        +        params = list(base[1:])
        +        if self.appname:
        +            params.append(self.appname)
        +            if self.version:
        +                params.append(self.version)
        +        path = os.path.join(base[0], *params)  # noqa: PTH118
        +        self._optionally_create_directory(path)
        +        return path
        +
        +    def _optionally_create_directory(self, path: str) -> None:
        +        if self.ensure_exists:
        +            Path(path).mkdir(parents=True, exist_ok=True)
        +
        +    def _first_item_as_path_if_multipath(self, directory: str) -> Path:
        +        if self.multipath:
        +            # If multipath is True, the first path is returned.
        +            directory = directory.split(os.pathsep)[0]
        +        return Path(directory)
        +
        +    @property
        +    @abstractmethod
        +    def user_data_dir(self) -> str:
        +        """:return: data directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def site_data_dir(self) -> str:
        +        """:return: data directory shared by users"""
        +
        +    @property
        +    @abstractmethod
        +    def user_config_dir(self) -> str:
        +        """:return: config directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def site_config_dir(self) -> str:
        +        """:return: config directory shared by the users"""
        +
        +    @property
        +    @abstractmethod
        +    def user_cache_dir(self) -> str:
        +        """:return: cache directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def site_cache_dir(self) -> str:
        +        """:return: cache directory shared by users"""
        +
        +    @property
        +    @abstractmethod
        +    def user_state_dir(self) -> str:
        +        """:return: state directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_log_dir(self) -> str:
        +        """:return: log directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_documents_dir(self) -> str:
        +        """:return: documents directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_downloads_dir(self) -> str:
        +        """:return: downloads directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_pictures_dir(self) -> str:
        +        """:return: pictures directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_videos_dir(self) -> str:
        +        """:return: videos directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_music_dir(self) -> str:
        +        """:return: music directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_desktop_dir(self) -> str:
        +        """:return: desktop directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def user_runtime_dir(self) -> str:
        +        """:return: runtime directory tied to the user"""
        +
        +    @property
        +    @abstractmethod
        +    def site_runtime_dir(self) -> str:
        +        """:return: runtime directory shared by users"""
        +
        +    @property
        +    def user_data_path(self) -> Path:
        +        """:return: data path tied to the user"""
        +        return Path(self.user_data_dir)
        +
        +    @property
        +    def site_data_path(self) -> Path:
        +        """:return: data path shared by users"""
        +        return Path(self.site_data_dir)
        +
        +    @property
        +    def user_config_path(self) -> Path:
        +        """:return: config path tied to the user"""
        +        return Path(self.user_config_dir)
        +
        +    @property
        +    def site_config_path(self) -> Path:
        +        """:return: config path shared by the users"""
        +        return Path(self.site_config_dir)
        +
        +    @property
        +    def user_cache_path(self) -> Path:
        +        """:return: cache path tied to the user"""
        +        return Path(self.user_cache_dir)
        +
        +    @property
        +    def site_cache_path(self) -> Path:
        +        """:return: cache path shared by users"""
        +        return Path(self.site_cache_dir)
        +
        +    @property
        +    def user_state_path(self) -> Path:
        +        """:return: state path tied to the user"""
        +        return Path(self.user_state_dir)
        +
        +    @property
        +    def user_log_path(self) -> Path:
        +        """:return: log path tied to the user"""
        +        return Path(self.user_log_dir)
        +
        +    @property
        +    def user_documents_path(self) -> Path:
        +        """:return: documents a path tied to the user"""
        +        return Path(self.user_documents_dir)
        +
        +    @property
        +    def user_downloads_path(self) -> Path:
        +        """:return: downloads path tied to the user"""
        +        return Path(self.user_downloads_dir)
        +
        +    @property
        +    def user_pictures_path(self) -> Path:
        +        """:return: pictures path tied to the user"""
        +        return Path(self.user_pictures_dir)
        +
        +    @property
        +    def user_videos_path(self) -> Path:
        +        """:return: videos path tied to the user"""
        +        return Path(self.user_videos_dir)
        +
        +    @property
        +    def user_music_path(self) -> Path:
        +        """:return: music path tied to the user"""
        +        return Path(self.user_music_dir)
        +
        +    @property
        +    def user_desktop_path(self) -> Path:
        +        """:return: desktop path tied to the user"""
        +        return Path(self.user_desktop_dir)
        +
        +    @property
        +    def user_runtime_path(self) -> Path:
        +        """:return: runtime path tied to the user"""
        +        return Path(self.user_runtime_dir)
        +
        +    @property
        +    def site_runtime_path(self) -> Path:
        +        """:return: runtime path shared by users"""
        +        return Path(self.site_runtime_dir)
        +
        +    def iter_config_dirs(self) -> Iterator[str]:
        +        """:yield: all user and site configuration directories."""
        +        yield self.user_config_dir
        +        yield self.site_config_dir
        +
        +    def iter_data_dirs(self) -> Iterator[str]:
        +        """:yield: all user and site data directories."""
        +        yield self.user_data_dir
        +        yield self.site_data_dir
        +
        +    def iter_cache_dirs(self) -> Iterator[str]:
        +        """:yield: all user and site cache directories."""
        +        yield self.user_cache_dir
        +        yield self.site_cache_dir
        +
        +    def iter_runtime_dirs(self) -> Iterator[str]:
        +        """:yield: all user and site runtime directories."""
        +        yield self.user_runtime_dir
        +        yield self.site_runtime_dir
        +
        +    def iter_config_paths(self) -> Iterator[Path]:
        +        """:yield: all user and site configuration paths."""
        +        for path in self.iter_config_dirs():
        +            yield Path(path)
        +
        +    def iter_data_paths(self) -> Iterator[Path]:
        +        """:yield: all user and site data paths."""
        +        for path in self.iter_data_dirs():
        +            yield Path(path)
        +
        +    def iter_cache_paths(self) -> Iterator[Path]:
        +        """:yield: all user and site cache paths."""
        +        for path in self.iter_cache_dirs():
        +            yield Path(path)
        +
        +    def iter_runtime_paths(self) -> Iterator[Path]:
        +        """:yield: all user and site runtime paths."""
        +        for path in self.iter_runtime_dirs():
        +            yield Path(path)
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/macos.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/macos.py
        new file mode 100644
        index 000000000..e4b0391ab
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/macos.py
        @@ -0,0 +1,144 @@
        +"""macOS."""
        +
        +from __future__ import annotations
        +
        +import os.path
        +import sys
        +from typing import TYPE_CHECKING
        +
        +from .api import PlatformDirsABC
        +
        +if TYPE_CHECKING:
        +    from pathlib import Path
        +
        +
        +class MacOS(PlatformDirsABC):
        +    """
        +    Platform directories for the macOS operating system.
        +
        +    Follows the guidance from
        +    `Apple documentation `_.
        +    Makes use of the `appname `,
        +    `version `,
        +    `ensure_exists `.
        +
        +    """
        +
        +    @property
        +    def user_data_dir(self) -> str:
        +        """:return: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``"""
        +        return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support"))  # noqa: PTH111
        +
        +    @property
        +    def site_data_dir(self) -> str:
        +        """
        +        :return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``.
        +          If we're using a Python binary managed by `Homebrew `_, the directory
        +          will be under the Homebrew prefix, e.g. ``/opt/homebrew/share/$appname/$version``.
        +          If `multipath ` is enabled, and we're in Homebrew,
        +          the response is a multi-path string separated by ":", e.g.
        +          ``/opt/homebrew/share/$appname/$version:/Library/Application Support/$appname/$version``
        +        """
        +        is_homebrew = sys.prefix.startswith("/opt/homebrew")
        +        path_list = [self._append_app_name_and_version("/opt/homebrew/share")] if is_homebrew else []
        +        path_list.append(self._append_app_name_and_version("/Library/Application Support"))
        +        if self.multipath:
        +            return os.pathsep.join(path_list)
        +        return path_list[0]
        +
        +    @property
        +    def site_data_path(self) -> Path:
        +        """:return: data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``"""
        +        return self._first_item_as_path_if_multipath(self.site_data_dir)
        +
        +    @property
        +    def user_config_dir(self) -> str:
        +        """:return: config directory tied to the user, same as `user_data_dir`"""
        +        return self.user_data_dir
        +
        +    @property
        +    def site_config_dir(self) -> str:
        +        """:return: config directory shared by the users, same as `site_data_dir`"""
        +        return self.site_data_dir
        +
        +    @property
        +    def user_cache_dir(self) -> str:
        +        """:return: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``"""
        +        return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches"))  # noqa: PTH111
        +
        +    @property
        +    def site_cache_dir(self) -> str:
        +        """
        +        :return: cache directory shared by users, e.g. ``/Library/Caches/$appname/$version``.
        +          If we're using a Python binary managed by `Homebrew `_, the directory
        +          will be under the Homebrew prefix, e.g. ``/opt/homebrew/var/cache/$appname/$version``.
        +          If `multipath ` is enabled, and we're in Homebrew,
        +          the response is a multi-path string separated by ":", e.g.
        +          ``/opt/homebrew/var/cache/$appname/$version:/Library/Caches/$appname/$version``
        +        """
        +        is_homebrew = sys.prefix.startswith("/opt/homebrew")
        +        path_list = [self._append_app_name_and_version("/opt/homebrew/var/cache")] if is_homebrew else []
        +        path_list.append(self._append_app_name_and_version("/Library/Caches"))
        +        if self.multipath:
        +            return os.pathsep.join(path_list)
        +        return path_list[0]
        +
        +    @property
        +    def site_cache_path(self) -> Path:
        +        """:return: cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``"""
        +        return self._first_item_as_path_if_multipath(self.site_cache_dir)
        +
        +    @property
        +    def user_state_dir(self) -> str:
        +        """:return: state directory tied to the user, same as `user_data_dir`"""
        +        return self.user_data_dir
        +
        +    @property
        +    def user_log_dir(self) -> str:
        +        """:return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``"""
        +        return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs"))  # noqa: PTH111
        +
        +    @property
        +    def user_documents_dir(self) -> str:
        +        """:return: documents directory tied to the user, e.g. ``~/Documents``"""
        +        return os.path.expanduser("~/Documents")  # noqa: PTH111
        +
        +    @property
        +    def user_downloads_dir(self) -> str:
        +        """:return: downloads directory tied to the user, e.g. ``~/Downloads``"""
        +        return os.path.expanduser("~/Downloads")  # noqa: PTH111
        +
        +    @property
        +    def user_pictures_dir(self) -> str:
        +        """:return: pictures directory tied to the user, e.g. ``~/Pictures``"""
        +        return os.path.expanduser("~/Pictures")  # noqa: PTH111
        +
        +    @property
        +    def user_videos_dir(self) -> str:
        +        """:return: videos directory tied to the user, e.g. ``~/Movies``"""
        +        return os.path.expanduser("~/Movies")  # noqa: PTH111
        +
        +    @property
        +    def user_music_dir(self) -> str:
        +        """:return: music directory tied to the user, e.g. ``~/Music``"""
        +        return os.path.expanduser("~/Music")  # noqa: PTH111
        +
        +    @property
        +    def user_desktop_dir(self) -> str:
        +        """:return: desktop directory tied to the user, e.g. ``~/Desktop``"""
        +        return os.path.expanduser("~/Desktop")  # noqa: PTH111
        +
        +    @property
        +    def user_runtime_dir(self) -> str:
        +        """:return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``"""
        +        return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems"))  # noqa: PTH111
        +
        +    @property
        +    def site_runtime_dir(self) -> str:
        +        """:return: runtime directory shared by users, same as `user_runtime_dir`"""
        +        return self.user_runtime_dir
        +
        +
        +__all__ = [
        +    "MacOS",
        +]
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/py.typed b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/py.typed
        new file mode 100644
        index 000000000..e69de29bb
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/unix.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/unix.py
        new file mode 100644
        index 000000000..f1942e92e
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/unix.py
        @@ -0,0 +1,269 @@
        +"""Unix."""
        +
        +from __future__ import annotations
        +
        +import os
        +import sys
        +from configparser import ConfigParser
        +from pathlib import Path
        +from typing import Iterator, NoReturn
        +
        +from .api import PlatformDirsABC
        +
        +if sys.platform == "win32":
        +
        +    def getuid() -> NoReturn:
        +        msg = "should only be used on Unix"
        +        raise RuntimeError(msg)
        +
        +else:
        +    from os import getuid
        +
        +
        +class Unix(PlatformDirsABC):  # noqa: PLR0904
        +    """
        +    On Unix/Linux, we follow the `XDG Basedir Spec `_.
        +
        +    The spec allows overriding directories with environment variables. The examples shown are the default values,
        +    alongside the name of the environment variable that overrides them. Makes use of the `appname
        +    `, `version `, `multipath
        +    `, `opinion `, `ensure_exists
        +    `.
        +
        +    """
        +
        +    @property
        +    def user_data_dir(self) -> str:
        +        """
        +        :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or
        +         ``$XDG_DATA_HOME/$appname/$version``
        +        """
        +        path = os.environ.get("XDG_DATA_HOME", "")
        +        if not path.strip():
        +            path = os.path.expanduser("~/.local/share")  # noqa: PTH111
        +        return self._append_app_name_and_version(path)
        +
        +    @property
        +    def _site_data_dirs(self) -> list[str]:
        +        path = os.environ.get("XDG_DATA_DIRS", "")
        +        if not path.strip():
        +            path = f"/usr/local/share{os.pathsep}/usr/share"
        +        return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)]
        +
        +    @property
        +    def site_data_dir(self) -> str:
        +        """
        +        :return: data directories shared by users (if `multipath ` is
        +         enabled and ``XDG_DATA_DIRS`` is set and a multi path the response is also a multi path separated by the
        +         OS path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version``
        +        """
        +        # XDG default for $XDG_DATA_DIRS; only first, if multipath is False
        +        dirs = self._site_data_dirs
        +        if not self.multipath:
        +            return dirs[0]
        +        return os.pathsep.join(dirs)
        +
        +    @property
        +    def user_config_dir(self) -> str:
        +        """
        +        :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or
        +         ``$XDG_CONFIG_HOME/$appname/$version``
        +        """
        +        path = os.environ.get("XDG_CONFIG_HOME", "")
        +        if not path.strip():
        +            path = os.path.expanduser("~/.config")  # noqa: PTH111
        +        return self._append_app_name_and_version(path)
        +
        +    @property
        +    def _site_config_dirs(self) -> list[str]:
        +        path = os.environ.get("XDG_CONFIG_DIRS", "")
        +        if not path.strip():
        +            path = "/etc/xdg"
        +        return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)]
        +
        +    @property
        +    def site_config_dir(self) -> str:
        +        """
        +        :return: config directories shared by users (if `multipath `
        +         is enabled and ``XDG_CONFIG_DIRS`` is set and a multi path the response is also a multi path separated by
        +         the OS path separator), e.g. ``/etc/xdg/$appname/$version``
        +        """
        +        # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
        +        dirs = self._site_config_dirs
        +        if not self.multipath:
        +            return dirs[0]
        +        return os.pathsep.join(dirs)
        +
        +    @property
        +    def user_cache_dir(self) -> str:
        +        """
        +        :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or
        +         ``~/$XDG_CACHE_HOME/$appname/$version``
        +        """
        +        path = os.environ.get("XDG_CACHE_HOME", "")
        +        if not path.strip():
        +            path = os.path.expanduser("~/.cache")  # noqa: PTH111
        +        return self._append_app_name_and_version(path)
        +
        +    @property
        +    def site_cache_dir(self) -> str:
        +        """:return: cache directory shared by users, e.g. ``/var/cache/$appname/$version``"""
        +        return self._append_app_name_and_version("/var/cache")
        +
        +    @property
        +    def user_state_dir(self) -> str:
        +        """
        +        :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or
        +         ``$XDG_STATE_HOME/$appname/$version``
        +        """
        +        path = os.environ.get("XDG_STATE_HOME", "")
        +        if not path.strip():
        +            path = os.path.expanduser("~/.local/state")  # noqa: PTH111
        +        return self._append_app_name_and_version(path)
        +
        +    @property
        +    def user_log_dir(self) -> str:
        +        """:return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it"""
        +        path = self.user_state_dir
        +        if self.opinion:
        +            path = os.path.join(path, "log")  # noqa: PTH118
        +            self._optionally_create_directory(path)
        +        return path
        +
        +    @property
        +    def user_documents_dir(self) -> str:
        +        """:return: documents directory tied to the user, e.g. ``~/Documents``"""
        +        return _get_user_media_dir("XDG_DOCUMENTS_DIR", "~/Documents")
        +
        +    @property
        +    def user_downloads_dir(self) -> str:
        +        """:return: downloads directory tied to the user, e.g. ``~/Downloads``"""
        +        return _get_user_media_dir("XDG_DOWNLOAD_DIR", "~/Downloads")
        +
        +    @property
        +    def user_pictures_dir(self) -> str:
        +        """:return: pictures directory tied to the user, e.g. ``~/Pictures``"""
        +        return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures")
        +
        +    @property
        +    def user_videos_dir(self) -> str:
        +        """:return: videos directory tied to the user, e.g. ``~/Videos``"""
        +        return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")
        +
        +    @property
        +    def user_music_dir(self) -> str:
        +        """:return: music directory tied to the user, e.g. ``~/Music``"""
        +        return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music")
        +
        +    @property
        +    def user_desktop_dir(self) -> str:
        +        """:return: desktop directory tied to the user, e.g. ``~/Desktop``"""
        +        return _get_user_media_dir("XDG_DESKTOP_DIR", "~/Desktop")
        +
        +    @property
        +    def user_runtime_dir(self) -> str:
        +        """
        +        :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
        +         ``$XDG_RUNTIME_DIR/$appname/$version``.
        +
        +         For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/user/$(id -u)/$appname/$version`` if
        +         exists, otherwise ``/tmp/runtime-$(id -u)/$appname/$version``, if``$XDG_RUNTIME_DIR``
        +         is not set.
        +        """
        +        path = os.environ.get("XDG_RUNTIME_DIR", "")
        +        if not path.strip():
        +            if sys.platform.startswith(("freebsd", "openbsd", "netbsd")):
        +                path = f"/var/run/user/{getuid()}"
        +                if not Path(path).exists():
        +                    path = f"/tmp/runtime-{getuid()}"  # noqa: S108
        +            else:
        +                path = f"/run/user/{getuid()}"
        +        return self._append_app_name_and_version(path)
        +
        +    @property
        +    def site_runtime_dir(self) -> str:
        +        """
        +        :return: runtime directory shared by users, e.g. ``/run/$appname/$version`` or \
        +        ``$XDG_RUNTIME_DIR/$appname/$version``.
        +
        +        Note that this behaves almost exactly like `user_runtime_dir` if ``$XDG_RUNTIME_DIR`` is set, but will
        +        fall back to paths associated to the root user instead of a regular logged-in user if it's not set.
        +
        +        If you wish to ensure that a logged-in root user path is returned e.g. ``/run/user/0``, use `user_runtime_dir`
        +        instead.
        +
        +        For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/$appname/$version`` if ``$XDG_RUNTIME_DIR`` is not set.
        +        """
        +        path = os.environ.get("XDG_RUNTIME_DIR", "")
        +        if not path.strip():
        +            if sys.platform.startswith(("freebsd", "openbsd", "netbsd")):
        +                path = "/var/run"
        +            else:
        +                path = "/run"
        +        return self._append_app_name_and_version(path)
        +
        +    @property
        +    def site_data_path(self) -> Path:
        +        """:return: data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``"""
        +        return self._first_item_as_path_if_multipath(self.site_data_dir)
        +
        +    @property
        +    def site_config_path(self) -> Path:
        +        """:return: config path shared by the users, returns the first item, even if ``multipath`` is set to ``True``"""
        +        return self._first_item_as_path_if_multipath(self.site_config_dir)
        +
        +    @property
        +    def site_cache_path(self) -> Path:
        +        """:return: cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``"""
        +        return self._first_item_as_path_if_multipath(self.site_cache_dir)
        +
        +    def iter_config_dirs(self) -> Iterator[str]:
        +        """:yield: all user and site configuration directories."""
        +        yield self.user_config_dir
        +        yield from self._site_config_dirs
        +
        +    def iter_data_dirs(self) -> Iterator[str]:
        +        """:yield: all user and site data directories."""
        +        yield self.user_data_dir
        +        yield from self._site_data_dirs
        +
        +
        +def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str:
        +    media_dir = _get_user_dirs_folder(env_var)
        +    if media_dir is None:
        +        media_dir = os.environ.get(env_var, "").strip()
        +        if not media_dir:
        +            media_dir = os.path.expanduser(fallback_tilde_path)  # noqa: PTH111
        +
        +    return media_dir
        +
        +
        +def _get_user_dirs_folder(key: str) -> str | None:
        +    """
        +    Return directory from user-dirs.dirs config file.
        +
        +    See https://freedesktop.org/wiki/Software/xdg-user-dirs/.
        +
        +    """
        +    user_dirs_config_path = Path(Unix().user_config_dir) / "user-dirs.dirs"
        +    if user_dirs_config_path.exists():
        +        parser = ConfigParser()
        +
        +        with user_dirs_config_path.open() as stream:
        +            # Add fake section header, so ConfigParser doesn't complain
        +            parser.read_string(f"[top]\n{stream.read()}")
        +
        +        if key not in parser["top"]:
        +            return None
        +
        +        path = parser["top"][key].strip('"')
        +        # Handle relative home paths
        +        return path.replace("$HOME", os.path.expanduser("~"))  # noqa: PTH111
        +
        +    return None
        +
        +
        +__all__ = [
        +    "Unix",
        +]
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/version.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/version.py
        new file mode 100644
        index 000000000..afb49243e
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/version.py
        @@ -0,0 +1,16 @@
        +# file generated by setuptools_scm
        +# don't change, don't track in version control
        +TYPE_CHECKING = False
        +if TYPE_CHECKING:
        +    from typing import Tuple, Union
        +    VERSION_TUPLE = Tuple[Union[int, str], ...]
        +else:
        +    VERSION_TUPLE = object
        +
        +version: str
        +__version__: str
        +__version_tuple__: VERSION_TUPLE
        +version_tuple: VERSION_TUPLE
        +
        +__version__ = version = '4.3.6'
        +__version_tuple__ = version_tuple = (4, 3, 6)
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/windows.py b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/windows.py
        new file mode 100644
        index 000000000..d7bc96091
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/platformdirs/windows.py
        @@ -0,0 +1,272 @@
        +"""Windows."""
        +
        +from __future__ import annotations
        +
        +import os
        +import sys
        +from functools import lru_cache
        +from typing import TYPE_CHECKING
        +
        +from .api import PlatformDirsABC
        +
        +if TYPE_CHECKING:
        +    from collections.abc import Callable
        +
        +
        +class Windows(PlatformDirsABC):
        +    """
        +    `MSDN on where to store app data files `_.
        +
        +    Makes use of the `appname `, `appauthor
        +    `, `version `, `roaming
        +    `, `opinion `, `ensure_exists
        +    `.
        +
        +    """
        +
        +    @property
        +    def user_data_dir(self) -> str:
        +        """
        +        :return: data directory tied to the user, e.g.
        +         ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname`` (not roaming) or
        +         ``%USERPROFILE%\\AppData\\Roaming\\$appauthor\\$appname`` (roaming)
        +        """
        +        const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA"
        +        path = os.path.normpath(get_win_folder(const))
        +        return self._append_parts(path)
        +
        +    def _append_parts(self, path: str, *, opinion_value: str | None = None) -> str:
        +        params = []
        +        if self.appname:
        +            if self.appauthor is not False:
        +                author = self.appauthor or self.appname
        +                params.append(author)
        +            params.append(self.appname)
        +            if opinion_value is not None and self.opinion:
        +                params.append(opinion_value)
        +            if self.version:
        +                params.append(self.version)
        +        path = os.path.join(path, *params)  # noqa: PTH118
        +        self._optionally_create_directory(path)
        +        return path
        +
        +    @property
        +    def site_data_dir(self) -> str:
        +        """:return: data directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname``"""
        +        path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
        +        return self._append_parts(path)
        +
        +    @property
        +    def user_config_dir(self) -> str:
        +        """:return: config directory tied to the user, same as `user_data_dir`"""
        +        return self.user_data_dir
        +
        +    @property
        +    def site_config_dir(self) -> str:
        +        """:return: config directory shared by the users, same as `site_data_dir`"""
        +        return self.site_data_dir
        +
        +    @property
        +    def user_cache_dir(self) -> str:
        +        """
        +        :return: cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g.
        +         ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname\\Cache\\$version``
        +        """
        +        path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA"))
        +        return self._append_parts(path, opinion_value="Cache")
        +
        +    @property
        +    def site_cache_dir(self) -> str:
        +        """:return: cache directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname\\Cache\\$version``"""
        +        path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
        +        return self._append_parts(path, opinion_value="Cache")
        +
        +    @property
        +    def user_state_dir(self) -> str:
        +        """:return: state directory tied to the user, same as `user_data_dir`"""
        +        return self.user_data_dir
        +
        +    @property
        +    def user_log_dir(self) -> str:
        +        """:return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it"""
        +        path = self.user_data_dir
        +        if self.opinion:
        +            path = os.path.join(path, "Logs")  # noqa: PTH118
        +            self._optionally_create_directory(path)
        +        return path
        +
        +    @property
        +    def user_documents_dir(self) -> str:
        +        """:return: documents directory tied to the user e.g. ``%USERPROFILE%\\Documents``"""
        +        return os.path.normpath(get_win_folder("CSIDL_PERSONAL"))
        +
        +    @property
        +    def user_downloads_dir(self) -> str:
        +        """:return: downloads directory tied to the user e.g. ``%USERPROFILE%\\Downloads``"""
        +        return os.path.normpath(get_win_folder("CSIDL_DOWNLOADS"))
        +
        +    @property
        +    def user_pictures_dir(self) -> str:
        +        """:return: pictures directory tied to the user e.g. ``%USERPROFILE%\\Pictures``"""
        +        return os.path.normpath(get_win_folder("CSIDL_MYPICTURES"))
        +
        +    @property
        +    def user_videos_dir(self) -> str:
        +        """:return: videos directory tied to the user e.g. ``%USERPROFILE%\\Videos``"""
        +        return os.path.normpath(get_win_folder("CSIDL_MYVIDEO"))
        +
        +    @property
        +    def user_music_dir(self) -> str:
        +        """:return: music directory tied to the user e.g. ``%USERPROFILE%\\Music``"""
        +        return os.path.normpath(get_win_folder("CSIDL_MYMUSIC"))
        +
        +    @property
        +    def user_desktop_dir(self) -> str:
        +        """:return: desktop directory tied to the user, e.g. ``%USERPROFILE%\\Desktop``"""
        +        return os.path.normpath(get_win_folder("CSIDL_DESKTOPDIRECTORY"))
        +
        +    @property
        +    def user_runtime_dir(self) -> str:
        +        """
        +        :return: runtime directory tied to the user, e.g.
        +         ``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname``
        +        """
        +        path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp"))  # noqa: PTH118
        +        return self._append_parts(path)
        +
        +    @property
        +    def site_runtime_dir(self) -> str:
        +        """:return: runtime directory shared by users, same as `user_runtime_dir`"""
        +        return self.user_runtime_dir
        +
        +
        +def get_win_folder_from_env_vars(csidl_name: str) -> str:
        +    """Get folder from environment variables."""
        +    result = get_win_folder_if_csidl_name_not_env_var(csidl_name)
        +    if result is not None:
        +        return result
        +
        +    env_var_name = {
        +        "CSIDL_APPDATA": "APPDATA",
        +        "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
        +        "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA",
        +    }.get(csidl_name)
        +    if env_var_name is None:
        +        msg = f"Unknown CSIDL name: {csidl_name}"
        +        raise ValueError(msg)
        +    result = os.environ.get(env_var_name)
        +    if result is None:
        +        msg = f"Unset environment variable: {env_var_name}"
        +        raise ValueError(msg)
        +    return result
        +
        +
        +def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None:
        +    """Get a folder for a CSIDL name that does not exist as an environment variable."""
        +    if csidl_name == "CSIDL_PERSONAL":
        +        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")  # noqa: PTH118
        +
        +    if csidl_name == "CSIDL_DOWNLOADS":
        +        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Downloads")  # noqa: PTH118
        +
        +    if csidl_name == "CSIDL_MYPICTURES":
        +        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")  # noqa: PTH118
        +
        +    if csidl_name == "CSIDL_MYVIDEO":
        +        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos")  # noqa: PTH118
        +
        +    if csidl_name == "CSIDL_MYMUSIC":
        +        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music")  # noqa: PTH118
        +    return None
        +
        +
        +def get_win_folder_from_registry(csidl_name: str) -> str:
        +    """
        +    Get folder from the registry.
        +
        +    This is a fallback technique at best. I'm not sure if using the registry for these guarantees us the correct answer
        +    for all CSIDL_* names.
        +
        +    """
        +    shell_folder_name = {
        +        "CSIDL_APPDATA": "AppData",
        +        "CSIDL_COMMON_APPDATA": "Common AppData",
        +        "CSIDL_LOCAL_APPDATA": "Local AppData",
        +        "CSIDL_PERSONAL": "Personal",
        +        "CSIDL_DOWNLOADS": "{374DE290-123F-4565-9164-39C4925E467B}",
        +        "CSIDL_MYPICTURES": "My Pictures",
        +        "CSIDL_MYVIDEO": "My Video",
        +        "CSIDL_MYMUSIC": "My Music",
        +    }.get(csidl_name)
        +    if shell_folder_name is None:
        +        msg = f"Unknown CSIDL name: {csidl_name}"
        +        raise ValueError(msg)
        +    if sys.platform != "win32":  # only needed for mypy type checker to know that this code runs only on Windows
        +        raise NotImplementedError
        +    import winreg  # noqa: PLC0415
        +
        +    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
        +    directory, _ = winreg.QueryValueEx(key, shell_folder_name)
        +    return str(directory)
        +
        +
        +def get_win_folder_via_ctypes(csidl_name: str) -> str:
        +    """Get folder with ctypes."""
        +    # There is no 'CSIDL_DOWNLOADS'.
        +    # Use 'CSIDL_PROFILE' (40) and append the default folder 'Downloads' instead.
        +    # https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
        +
        +    import ctypes  # noqa: PLC0415
        +
        +    csidl_const = {
        +        "CSIDL_APPDATA": 26,
        +        "CSIDL_COMMON_APPDATA": 35,
        +        "CSIDL_LOCAL_APPDATA": 28,
        +        "CSIDL_PERSONAL": 5,
        +        "CSIDL_MYPICTURES": 39,
        +        "CSIDL_MYVIDEO": 14,
        +        "CSIDL_MYMUSIC": 13,
        +        "CSIDL_DOWNLOADS": 40,
        +        "CSIDL_DESKTOPDIRECTORY": 16,
        +    }.get(csidl_name)
        +    if csidl_const is None:
        +        msg = f"Unknown CSIDL name: {csidl_name}"
        +        raise ValueError(msg)
        +
        +    buf = ctypes.create_unicode_buffer(1024)
        +    windll = getattr(ctypes, "windll")  # noqa: B009 # using getattr to avoid false positive with mypy type checker
        +    windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
        +
        +    # Downgrade to short path name if it has high-bit chars.
        +    if any(ord(c) > 255 for c in buf):  # noqa: PLR2004
        +        buf2 = ctypes.create_unicode_buffer(1024)
        +        if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
        +            buf = buf2
        +
        +    if csidl_name == "CSIDL_DOWNLOADS":
        +        return os.path.join(buf.value, "Downloads")  # noqa: PTH118
        +
        +    return buf.value
        +
        +
        +def _pick_get_win_folder() -> Callable[[str], str]:
        +    try:
        +        import ctypes  # noqa: PLC0415
        +    except ImportError:
        +        pass
        +    else:
        +        if hasattr(ctypes, "windll"):
        +            return get_win_folder_via_ctypes
        +    try:
        +        import winreg  # noqa: PLC0415, F401
        +    except ImportError:
        +        return get_win_folder_from_env_vars
        +    else:
        +        return get_win_folder_from_registry
        +
        +
        +get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder())
        +
        +__all__ = [
        +    "Windows",
        +]
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__init__.py
        new file mode 100644
        index 000000000..60ae9bb85
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__init__.py
        @@ -0,0 +1,82 @@
        +"""
        +    Pygments
        +    ~~~~~~~~
        +
        +    Pygments is a syntax highlighting package written in Python.
        +
        +    It is a generic syntax highlighter for general use in all kinds of software
        +    such as forum systems, wikis or other applications that need to prettify
        +    source code. Highlights are:
        +
        +    * a wide range of common languages and markup formats is supported
        +    * special attention is paid to details, increasing quality by a fair amount
        +    * support for new languages and formats are added easily
        +    * a number of output formats, presently HTML, LaTeX, RTF, SVG, all image
        +      formats that PIL supports, and ANSI sequences
        +    * it is usable as a command-line tool and as a library
        +    * ... and it highlights even Brainfuck!
        +
        +    The `Pygments master branch`_ is installable with ``easy_install Pygments==dev``.
        +
        +    .. _Pygments master branch:
        +       https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev
        +
        +    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
        +    :license: BSD, see LICENSE for details.
        +"""
        +from io import StringIO, BytesIO
        +
        +__version__ = '2.18.0'
        +__docformat__ = 'restructuredtext'
        +
        +__all__ = ['lex', 'format', 'highlight']
        +
        +
        +def lex(code, lexer):
        +    """
        +    Lex `code` with the `lexer` (must be a `Lexer` instance)
        +    and return an iterable of tokens. Currently, this only calls
        +    `lexer.get_tokens()`.
        +    """
        +    try:
        +        return lexer.get_tokens(code)
        +    except TypeError:
        +        # Heuristic to catch a common mistake.
        +        from pip._vendor.pygments.lexer import RegexLexer
        +        if isinstance(lexer, type) and issubclass(lexer, RegexLexer):
        +            raise TypeError('lex() argument must be a lexer instance, '
        +                            'not a class')
        +        raise
        +
        +
        +def format(tokens, formatter, outfile=None):  # pylint: disable=redefined-builtin
        +    """
        +    Format ``tokens`` (an iterable of tokens) with the formatter ``formatter``
        +    (a `Formatter` instance).
        +
        +    If ``outfile`` is given and a valid file object (an object with a
        +    ``write`` method), the result will be written to it, otherwise it
        +    is returned as a string.
        +    """
        +    try:
        +        if not outfile:
        +            realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO()
        +            formatter.format(tokens, realoutfile)
        +            return realoutfile.getvalue()
        +        else:
        +            formatter.format(tokens, outfile)
        +    except TypeError:
        +        # Heuristic to catch a common mistake.
        +        from pip._vendor.pygments.formatter import Formatter
        +        if isinstance(formatter, type) and issubclass(formatter, Formatter):
        +            raise TypeError('format() argument must be a formatter instance, '
        +                            'not a class')
        +        raise
        +
        +
        +def highlight(code, lexer, formatter, outfile=None):
        +    """
        +    This is the most high-level highlighting function. It combines `lex` and
        +    `format` in one function.
        +    """
        +    return format(lex(code, lexer), formatter, outfile)
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__main__.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__main__.py
        new file mode 100644
        index 000000000..dcc6e5add
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__main__.py
        @@ -0,0 +1,17 @@
        +"""
        +    pygments.__main__
        +    ~~~~~~~~~~~~~~~~~
        +
        +    Main entry point for ``python -m pygments``.
        +
        +    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
        +    :license: BSD, see LICENSE for details.
        +"""
        +
        +import sys
        +from pip._vendor.pygments.cmdline import main
        +
        +try:
        +    sys.exit(main(sys.argv))
        +except KeyboardInterrupt:
        +    sys.exit(1)
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..7dc11c5ea9c8026ebdf06230dfb400b495364432
        GIT binary patch
        literal 2959
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..78bbe7c18184f99f0407091489866b4a2d4bead9
        GIT binary patch
        literal 15450
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..41c1ee4c1d9cf6a528a947101ed83b45d9ba8e7c
        GIT binary patch
        literal 1942
        zcmb7F&2!sC6yIG*wyZd%v}sZ&rHIl(J}r}kLfs4lB<;XVCoqIw6c|VLZfw>1VkIZF
        z$K?Xd@K-SHF*BSwaD_vMKVh$&@-O64-mYw#1URssUH#sNe*1Ry-t)uBNejXF^>@er
        zrGU_%QaC+K5I%&N5{wW|2RbUJ;*HCTaZO#hB-
        zp|97!B5u(&A)*`1m`0L~6&3Rh`X+B
        z^*wS+^lds1Pm3vIO%i0-O1|q<`~+mwc72K)!OR1-R#?0huNQxcC|C+h0Auz
        znElbiFF*P0i=C?4svh_)7Nx9if4sAm+aG+od2f5?p1mJ)o3hOFgS2W5$lR_#gVNfa
        zyX$xE)@q?JI7?aPYPEMlp*Q1z0$c_4zUN0$y%xG=nt2h0ut8i8MX482#sfcMAioQ)
        zO~75iw?kpZ2f0aVy;fIfaB^X^Ib)G1^jQ$ZhpOOSu3HW4+>W%eg*hbFJ|!J?2j4c*Hz5DSqRy<7e#nI;8YKyXA++8-R`>w^0k`
        znL$9>gqg}3$RP16Xa^mUZ7vN*SQxFCvh)b?8IaX-(sw#>$Q(9cU7rQc7CT@;oXA9R
        z9(c{vN%~nQj#iSjm3_F1rq5O~4%frDbHJhlC-9pim$mBqP6~mpBw&UMf>-^-X@D1v
        zIr!ws=Ztcxn)Jnl`hX3UW%Dv>870EJ0&7cQDZ`LhgQ<=-1t3F1R6#>LGdko99Ft1}
        zzXHnwaZ8G{(Pe$1XY6tI^1%EmcnDM3{Q+ABX}s?o;4}NmJ$0CB-xC0B^n?uIS}FN~
        z!2XD86icK-cwNHhsSfM?NXICe9g?Rg!_=stXBbOgLN)38MB@Kqcti`p@Z%YV;{Rg!
        znVP`x#~Fswzc8riJ$bErd-kE9b!;zh
        z6*d%`14WxcM%iALalhHiSX!^=9YZ!Db?*|uh-8-ua+Tti%0=a|D4{Kj`kXn>mWDF_
        zUCrm6mK4WQ&LKp_6;Oo1Z;(%Yp2=BT3-LLL2xA{I!6KFqBP|5SRI^NKa+I
        zD*U_zHo}V!Z
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..abe1ae3c3e9d4506bea653a2ddf4c443ada1a5e3
        GIT binary patch
        literal 2657
        zcmaJ?Pj4JG6t`#gU$)s03Z?uBk`G99q+~ZrP*ts}(vqaKqHGb{ULv8%?AVh`I=eHq
        zy-9bq<yfAyuz!egjY|TT^Igpd;$Iz
        zgnQ!dmN@Ma@6z~>I)j#t_WNR>RWphcEu>ldcD~ik;+H8OCW7gMK(n}?CBhtof5Etj
        zf?=YWRnfHa?J&(oGTuMXZMHR?vW@li>uVe98=tV=$ejJExSwee^jDb@g57!i=-$Jx
        zyG?U#JBdRvP@>H~>u%fq2luyjI^7)>r4p{zL7b>&y%W0Nj%h2y1pS0->LVDwBN644
        zJEyhh?u2fWZ~V8CK<
        zQqTiOq)}n7f_-x=U`k7{qRH+@ED_IyWT0a|J`#BBV}%bs4kQ;wBRI~SvQArNDJ;n2
        z*jz0N)l7;|zV*XSCzd
        zykal&oV;pheqOO}KBIZrJo$|L^Q?7{_C-sK#gkYht!;56k~G6`w;lvN)yhWTmBCuJ
        zxfV$wda+p3G6!h;i)aW9WmtY=JTqzpP@t!T{iou}4$%j-s5!LR5aC+jvKR}5)KLNoEkp42U
        z-41PHdc$XPIwQNmC20@by}k(>ijF;hP4p-Ttn#GhHio-2;@b`iJb1bCQlN`
        zfHqH-c?E)1%`5gj}MTsC9fVJ}Pq1vJN$V5i{BVZqeB*(dH94g6KuQx-x>(2y>U
        z2h)5}uy__?_+so|;H%NZ37rt%Fjt}+IHy{~^~}jj7kz#@gtZ(R&UHc?EVoRE|ssB$R8eS$jPa
        zum;Q{0Z`!b5>(4F2OPk+!W~3|d=o^+f0{tv=>e&NP!~~$kaMhC^C8FbPz2dR!5p2^
        zU>bA=T*q1&C8H*L1d|I&&M{+72Sc6q106%IgR*0_E2au0LOp=$GXqB;_uQq5WdK-P
        zXJT#f)|ty`7!?e?0bCO%nA0$msTB>-J9G}+A06)e?3~aSRF@9R6DXh)`UAD)#B)1x
        z3H`K!JmBm&hLSMUtd|Z4{5-jNmArZoD5QA4@Cb5&flj{Q`-VlnZ!v(;v;n`0GZW@l
        zu|_0(3f;t-&^oPWPI_k%A1gNf*oLbqy(Szz;wtBESnXG33
        dZ+2(idG5BncGITlTS#uBv2>~4s4Xuw{sWO{hu8oB
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..76b0b5d97ca590b8d5c1a266243174385ddeed27
        GIT binary patch
        literal 4120
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..b16d7c56dc890362a0208e619ddb5326a3687139
        GIT binary patch
        literal 26777
        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
        p1@!^z?LRAN*d^o>Ro`uy`m0MWk)5vZRqr
        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
        B!^;2w
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..102879f6327f041a11f14d80f0c0b79ceaacf5b9
        GIT binary patch
        literal 3575
        zcma)8TW=f372X@kB}F+(;yO()z_h8Gu&_wiDS|*S0$*DPFqFWO`=FQQaA!!axZK^&
        z3}um3`%vc}^tpib=)chC{)&C=Q~yGN0{zY`xl*LG$Pk=8b31e9%=eu$FSoZl4$9yE
        zF-UH99p|6axLg7>9^kM0sDyLm2v>Nid*uGk5xxk%aYP`!*Zz@@c0)ALZoc-80^uDv
        zy%rBV$LTPXVmX=1%;>(3d6r3KJO6y8bZqb8G|?qINq;I&tp-xXju+an
        z(UL86l1(b(7b#avo)$`C4fJEaR5&WE#a4Fd@CBXmISmZ^l|P=5t(>KKK4Z{JMh&0H
        zw3sH(QniDjnc9(#1~q#A$z<>ROO2T$2JS4>F5
        zlT`OR{{ncN{g~>@#YN)*{(1+MbdFpA%oFaBZ{g#Y?WdXeYJ3R)ua4|~FL;70NXE?G
        z5N-lP42M&a$5Wt!dA3`8x;s`)1`%e7TwzYd
        zpTXuDaeLsFbCbMBLg?D|H5dCBdq9(ZkLp`4e$VpP1y?O^j~cQm8?tfigTD>Y65%(_
        zQA@N%2j5U^i7vivu`O=k+Y#@G9elUMyWsizW$U1#^{TMfbYN+_fn0^Shw%Sg!WjZQ
        zG)AgSTXKTbS9zgvYuQAzPqMtCWSq~7B*jHukVpTlXSY&>p_op&DI}9e9^*?F<+#Hg4xw`+d1_@d$Rpry9@+3y!2C6cagmei>+)j>~||R`0+u7cf>KnnZ#mOH34f
        zM6M%FI*q2=ahvsTwX?qH{$^@S{@)amYu6*2z?H}JcV`X2#9j4=hiV~*L||g+8p}0%
        zsqYf8`#n$HfD%QT-n#02l56HwCi^nized01fa9&UHuj-ECQ~+rWXfe06%QbgX{u}7
        z*Un#n479viOa03DZvxwDoC6cb-kE!!X5#yy^QH-@g@1TX+09-3wZ5x|MUgJqa1HEX
        zojevwArdg$PD1g<#wR7sLS^+*I&>A4^3k!3k!e18l1rU^j8LC104*%;_1R0rJ)3tB
        zSL?Nu6ZlkeQ3W!>Wv{J1!0Bu-Q13x1JtfQ5nnBgIze^J+?OXT0^{i*h*4p~3E^Yuk
        z@$559*Y>HetPSURD^dB3Z=%8f^_ktE
        zJ~rT!z^N3z55wv-PfeJjqs=GN44Sef3bd(xBCujf#
        z-yc1|O+>i4Kv9TR``r7JcZO&*3)LaTn=|iC=Ny`&El6D}wIOX=sVRK8E#Q9x$7)BA
        zy_QKf5>dt_G))pDjoPi05*G#Hh9o2nEYxZRG^k{XLBYnzUjpttgo+BI?1ZNaq+0}P
        zHp=r<+TGUABm!HKi6q8iJcKGnuOUu5F{
        z+C{_R9eW~*X#aD$olY}`!#)<)L2-yUL(e*U?uvf)?N+^s$owKt@VHZm+HjqHgVkPp
        z1+8nl??5R$SF5Ydr%<(F_9e*u_ON&33J28>34R|^W#g_TAjQ`U7M2ZbxY|Z<=|gqd
        zn5cYF^!zIB{h0dQ`85n{K;Rk1G;QH@Hrx!|TW;tE;3@qskWJICh3soY6(FkXtaIQk
        zi!yo2V}lr%XLu7&x$2j0pV;+t?5b{2b%k9a-lP%Dj4-$f&vp9P3|_OPW*OL&0Nz)wzFLv)f{DTYQ1nTRVzkAV5cvqO)9NjqkP$(`2ErhAgu&&Git|
        z0yp^2vb#LWT0wL)uieNR8hn&!$ReSRAyO4oS)Kw_0BIy`XyBtF0v9O
        zUDZZKm#ZmQFz!fx0MKL`Um-fL8w(H)oF7mLg!bNN)MTvc(NbbOccGQ3~C
        RWxt>kXfin13H_bce*tlKiv$1w
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..053f245ec8afc718838ff94ba315b55833e72b67
        GIT binary patch
        literal 7822
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..949274e833a48676d1912931027554bde252d91c
        GIT binary patch
        literal 4536
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..eaf5223a65e0cffd89e54250f205d7d0ae69a911
        GIT binary patch
        literal 4583
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..860a415f15f3f689ca99fdb95e3f8d7404062a24
        GIT binary patch
        literal 31281
        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
        B@Vo#3
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-38.pyc
        new file mode 100644
        index 0000000000000000000000000000000000000000..09f564699c801932a3b23c5fbaafc7625bc9989a
        GIT binary patch
        literal 10138
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/cmdline.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/cmdline.py
        new file mode 100644
        index 000000000..0a7072eff
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/cmdline.py
        @@ -0,0 +1,668 @@
        +"""
        +    pygments.cmdline
        +    ~~~~~~~~~~~~~~~~
        +
        +    Command line interface.
        +
        +    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
        +    :license: BSD, see LICENSE for details.
        +"""
        +
        +import os
        +import sys
        +import shutil
        +import argparse
        +from textwrap import dedent
        +
        +from pip._vendor.pygments import __version__, highlight
        +from pip._vendor.pygments.util import ClassNotFound, OptionError, docstring_headline, \
        +    guess_decode, guess_decode_from_terminal, terminal_encoding, \
        +    UnclosingTextIOWrapper
        +from pip._vendor.pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \
        +    load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename
        +from pip._vendor.pygments.lexers.special import TextLexer
        +from pip._vendor.pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter
        +from pip._vendor.pygments.formatters import get_all_formatters, get_formatter_by_name, \
        +    load_formatter_from_file, get_formatter_for_filename, find_formatter_class
        +from pip._vendor.pygments.formatters.terminal import TerminalFormatter
        +from pip._vendor.pygments.formatters.terminal256 import Terminal256Formatter, TerminalTrueColorFormatter
        +from pip._vendor.pygments.filters import get_all_filters, find_filter_class
        +from pip._vendor.pygments.styles import get_all_styles, get_style_by_name
        +
        +
        +def _parse_options(o_strs):
        +    opts = {}
        +    if not o_strs:
        +        return opts
        +    for o_str in o_strs:
        +        if not o_str.strip():
        +            continue
        +        o_args = o_str.split(',')
        +        for o_arg in o_args:
        +            o_arg = o_arg.strip()
        +            try:
        +                o_key, o_val = o_arg.split('=', 1)
        +                o_key = o_key.strip()
        +                o_val = o_val.strip()
        +            except ValueError:
        +                opts[o_arg] = True
        +            else:
        +                opts[o_key] = o_val
        +    return opts
        +
        +
        +def _parse_filters(f_strs):
        +    filters = []
        +    if not f_strs:
        +        return filters
        +    for f_str in f_strs:
        +        if ':' in f_str:
        +            fname, fopts = f_str.split(':', 1)
        +            filters.append((fname, _parse_options([fopts])))
        +        else:
        +            filters.append((f_str, {}))
        +    return filters
        +
        +
        +def _print_help(what, name):
        +    try:
        +        if what == 'lexer':
        +            cls = get_lexer_by_name(name)
        +            print(f"Help on the {cls.name} lexer:")
        +            print(dedent(cls.__doc__))
        +        elif what == 'formatter':
        +            cls = find_formatter_class(name)
        +            print(f"Help on the {cls.name} formatter:")
        +            print(dedent(cls.__doc__))
        +        elif what == 'filter':
        +            cls = find_filter_class(name)
        +            print(f"Help on the {name} filter:")
        +            print(dedent(cls.__doc__))
        +        return 0
        +    except (AttributeError, ValueError):
        +        print(f"{what} not found!", file=sys.stderr)
        +        return 1
        +
        +
        +def _print_list(what):
        +    if what == 'lexer':
        +        print()
        +        print("Lexers:")
        +        print("~~~~~~~")
        +
        +        info = []
        +        for fullname, names, exts, _ in get_all_lexers():
        +            tup = (', '.join(names)+':', fullname,
        +                   exts and '(filenames ' + ', '.join(exts) + ')' or '')
        +            info.append(tup)
        +        info.sort()
        +        for i in info:
        +            print(('* {}\n    {} {}').format(*i))
        +
        +    elif what == 'formatter':
        +        print()
        +        print("Formatters:")
        +        print("~~~~~~~~~~~")
        +
        +        info = []
        +        for cls in get_all_formatters():
        +            doc = docstring_headline(cls)
        +            tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and
        +                   '(filenames ' + ', '.join(cls.filenames) + ')' or '')
        +            info.append(tup)
        +        info.sort()
        +        for i in info:
        +            print(('* {}\n    {} {}').format(*i))
        +
        +    elif what == 'filter':
        +        print()
        +        print("Filters:")
        +        print("~~~~~~~~")
        +
        +        for name in get_all_filters():
        +            cls = find_filter_class(name)
        +            print("* " + name + ':')
        +            print(f"    {docstring_headline(cls)}")
        +
        +    elif what == 'style':
        +        print()
        +        print("Styles:")
        +        print("~~~~~~~")
        +
        +        for name in get_all_styles():
        +            cls = get_style_by_name(name)
        +            print("* " + name + ':')
        +            print(f"    {docstring_headline(cls)}")
        +
        +
        +def _print_list_as_json(requested_items):
        +    import json
        +    result = {}
        +    if 'lexer' in requested_items:
        +        info = {}
        +        for fullname, names, filenames, mimetypes in get_all_lexers():
        +            info[fullname] = {
        +                'aliases': names,
        +                'filenames': filenames,
        +                'mimetypes': mimetypes
        +            }
        +        result['lexers'] = info
        +
        +    if 'formatter' in requested_items:
        +        info = {}
        +        for cls in get_all_formatters():
        +            doc = docstring_headline(cls)
        +            info[cls.name] = {
        +                'aliases': cls.aliases,
        +                'filenames': cls.filenames,
        +                'doc': doc
        +            }
        +        result['formatters'] = info
        +
        +    if 'filter' in requested_items:
        +        info = {}
        +        for name in get_all_filters():
        +            cls = find_filter_class(name)
        +            info[name] = {
        +                'doc': docstring_headline(cls)
        +            }
        +        result['filters'] = info
        +
        +    if 'style' in requested_items:
        +        info = {}
        +        for name in get_all_styles():
        +            cls = get_style_by_name(name)
        +            info[name] = {
        +                'doc': docstring_headline(cls)
        +            }
        +        result['styles'] = info
        +
        +    json.dump(result, sys.stdout)
        +
        +def main_inner(parser, argns):
        +    if argns.help:
        +        parser.print_help()
        +        return 0
        +
        +    if argns.V:
        +        print(f'Pygments version {__version__}, (c) 2006-2024 by Georg Brandl, Matthäus '
        +              'Chajdas and contributors.')
        +        return 0
        +
        +    def is_only_option(opt):
        +        return not any(v for (k, v) in vars(argns).items() if k != opt)
        +
        +    # handle ``pygmentize -L``
        +    if argns.L is not None:
        +        arg_set = set()
        +        for k, v in vars(argns).items():
        +            if v:
        +                arg_set.add(k)
        +
        +        arg_set.discard('L')
        +        arg_set.discard('json')
        +
        +        if arg_set:
        +            parser.print_help(sys.stderr)
        +            return 2
        +
        +        # print version
        +        if not argns.json:
        +            main(['', '-V'])
        +        allowed_types = {'lexer', 'formatter', 'filter', 'style'}
        +        largs = [arg.rstrip('s') for arg in argns.L]
        +        if any(arg not in allowed_types for arg in largs):
        +            parser.print_help(sys.stderr)
        +            return 0
        +        if not largs:
        +            largs = allowed_types
        +        if not argns.json:
        +            for arg in largs:
        +                _print_list(arg)
        +        else:
        +            _print_list_as_json(largs)
        +        return 0
        +
        +    # handle ``pygmentize -H``
        +    if argns.H:
        +        if not is_only_option('H'):
        +            parser.print_help(sys.stderr)
        +            return 2
        +        what, name = argns.H
        +        if what not in ('lexer', 'formatter', 'filter'):
        +            parser.print_help(sys.stderr)
        +            return 2
        +        return _print_help(what, name)
        +
        +    # parse -O options
        +    parsed_opts = _parse_options(argns.O or [])
        +
        +    # parse -P options
        +    for p_opt in argns.P or []:
        +        try:
        +            name, value = p_opt.split('=', 1)
        +        except ValueError:
        +            parsed_opts[p_opt] = True
        +        else:
        +            parsed_opts[name] = value
        +
        +    # encodings
        +    inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding'))
        +    outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding'))
        +
        +    # handle ``pygmentize -N``
        +    if argns.N:
        +        lexer = find_lexer_class_for_filename(argns.N)
        +        if lexer is None:
        +            lexer = TextLexer
        +
        +        print(lexer.aliases[0])
        +        return 0
        +
        +    # handle ``pygmentize -C``
        +    if argns.C:
        +        inp = sys.stdin.buffer.read()
        +        try:
        +            lexer = guess_lexer(inp, inencoding=inencoding)
        +        except ClassNotFound:
        +            lexer = TextLexer
        +
        +        print(lexer.aliases[0])
        +        return 0
        +
        +    # handle ``pygmentize -S``
        +    S_opt = argns.S
        +    a_opt = argns.a
        +    if S_opt is not None:
        +        f_opt = argns.f
        +        if not f_opt:
        +            parser.print_help(sys.stderr)
        +            return 2
        +        if argns.l or argns.INPUTFILE:
        +            parser.print_help(sys.stderr)
        +            return 2
        +
        +        try:
        +            parsed_opts['style'] = S_opt
        +            fmter = get_formatter_by_name(f_opt, **parsed_opts)
        +        except ClassNotFound as err:
        +            print(err, file=sys.stderr)
        +            return 1
        +
        +        print(fmter.get_style_defs(a_opt or ''))
        +        return 0
        +
        +    # if no -S is given, -a is not allowed
        +    if argns.a is not None:
        +        parser.print_help(sys.stderr)
        +        return 2
        +
        +    # parse -F options
        +    F_opts = _parse_filters(argns.F or [])
        +
        +    # -x: allow custom (eXternal) lexers and formatters
        +    allow_custom_lexer_formatter = bool(argns.x)
        +
        +    # select lexer
        +    lexer = None
        +
        +    # given by name?
        +    lexername = argns.l
        +    if lexername:
        +        # custom lexer, located relative to user's cwd
        +        if allow_custom_lexer_formatter and '.py' in lexername:
        +            try:
        +                filename = None
        +                name = None
        +                if ':' in lexername:
        +                    filename, name = lexername.rsplit(':', 1)
        +
        +                    if '.py' in name:
        +                        # This can happen on Windows: If the lexername is
        +                        # C:\lexer.py -- return to normal load path in that case
        +                        name = None
        +
        +                if filename and name:
        +                    lexer = load_lexer_from_file(filename, name,
        +                                                 **parsed_opts)
        +                else:
        +                    lexer = load_lexer_from_file(lexername, **parsed_opts)
        +            except ClassNotFound as err:
        +                print('Error:', err, file=sys.stderr)
        +                return 1
        +        else:
        +            try:
        +                lexer = get_lexer_by_name(lexername, **parsed_opts)
        +            except (OptionError, ClassNotFound) as err:
        +                print('Error:', err, file=sys.stderr)
        +                return 1
        +
        +    # read input code
        +    code = None
        +
        +    if argns.INPUTFILE:
        +        if argns.s:
        +            print('Error: -s option not usable when input file specified',
        +                  file=sys.stderr)
        +            return 2
        +
        +        infn = argns.INPUTFILE
        +        try:
        +            with open(infn, 'rb') as infp:
        +                code = infp.read()
        +        except Exception as err:
        +            print('Error: cannot read infile:', err, file=sys.stderr)
        +            return 1
        +        if not inencoding:
        +            code, inencoding = guess_decode(code)
        +
        +        # do we have to guess the lexer?
        +        if not lexer:
        +            try:
        +                lexer = get_lexer_for_filename(infn, code, **parsed_opts)
        +            except ClassNotFound as err:
        +                if argns.g:
        +                    try:
        +                        lexer = guess_lexer(code, **parsed_opts)
        +                    except ClassNotFound:
        +                        lexer = TextLexer(**parsed_opts)
        +                else:
        +                    print('Error:', err, file=sys.stderr)
        +                    return 1
        +            except OptionError as err:
        +                print('Error:', err, file=sys.stderr)
        +                return 1
        +
        +    elif not argns.s:  # treat stdin as full file (-s support is later)
        +        # read code from terminal, always in binary mode since we want to
        +        # decode ourselves and be tolerant with it
        +        code = sys.stdin.buffer.read()  # use .buffer to get a binary stream
        +        if not inencoding:
        +            code, inencoding = guess_decode_from_terminal(code, sys.stdin)
        +            # else the lexer will do the decoding
        +        if not lexer:
        +            try:
        +                lexer = guess_lexer(code, **parsed_opts)
        +            except ClassNotFound:
        +                lexer = TextLexer(**parsed_opts)
        +
        +    else:  # -s option needs a lexer with -l
        +        if not lexer:
        +            print('Error: when using -s a lexer has to be selected with -l',
        +                  file=sys.stderr)
        +            return 2
        +
        +    # process filters
        +    for fname, fopts in F_opts:
        +        try:
        +            lexer.add_filter(fname, **fopts)
        +        except ClassNotFound as err:
        +            print('Error:', err, file=sys.stderr)
        +            return 1
        +
        +    # select formatter
        +    outfn = argns.o
        +    fmter = argns.f
        +    if fmter:
        +        # custom formatter, located relative to user's cwd
        +        if allow_custom_lexer_formatter and '.py' in fmter:
        +            try:
        +                filename = None
        +                name = None
        +                if ':' in fmter:
        +                    # Same logic as above for custom lexer
        +                    filename, name = fmter.rsplit(':', 1)
        +
        +                    if '.py' in name:
        +                        name = None
        +
        +                if filename and name:
        +                    fmter = load_formatter_from_file(filename, name,
        +                                                     **parsed_opts)
        +                else:
        +                    fmter = load_formatter_from_file(fmter, **parsed_opts)
        +            except ClassNotFound as err:
        +                print('Error:', err, file=sys.stderr)
        +                return 1
        +        else:
        +            try:
        +                fmter = get_formatter_by_name(fmter, **parsed_opts)
        +            except (OptionError, ClassNotFound) as err:
        +                print('Error:', err, file=sys.stderr)
        +                return 1
        +
        +    if outfn:
        +        if not fmter:
        +            try:
        +                fmter = get_formatter_for_filename(outfn, **parsed_opts)
        +            except (OptionError, ClassNotFound) as err:
        +                print('Error:', err, file=sys.stderr)
        +                return 1
        +        try:
        +            outfile = open(outfn, 'wb')
        +        except Exception as err:
        +            print('Error: cannot open outfile:', err, file=sys.stderr)
        +            return 1
        +    else:
        +        if not fmter:
        +            if os.environ.get('COLORTERM','') in ('truecolor', '24bit'):
        +                fmter = TerminalTrueColorFormatter(**parsed_opts)
        +            elif '256' in os.environ.get('TERM', ''):
        +                fmter = Terminal256Formatter(**parsed_opts)
        +            else:
        +                fmter = TerminalFormatter(**parsed_opts)
        +        outfile = sys.stdout.buffer
        +
        +    # determine output encoding if not explicitly selected
        +    if not outencoding:
        +        if outfn:
        +            # output file? use lexer encoding for now (can still be None)
        +            fmter.encoding = inencoding
        +        else:
        +            # else use terminal encoding
        +            fmter.encoding = terminal_encoding(sys.stdout)
        +
        +    # provide coloring under Windows, if possible
        +    if not outfn and sys.platform in ('win32', 'cygwin') and \
        +       fmter.name in ('Terminal', 'Terminal256'):  # pragma: no cover
        +        # unfortunately colorama doesn't support binary streams on Py3
        +        outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
        +        fmter.encoding = None
        +        try:
        +            import colorama.initialise
        +        except ImportError:
        +            pass
        +        else:
        +            outfile = colorama.initialise.wrap_stream(
        +                outfile, convert=None, strip=None, autoreset=False, wrap=True)
        +
        +    # When using the LaTeX formatter and the option `escapeinside` is
        +    # specified, we need a special lexer which collects escaped text
        +    # before running the chosen language lexer.
        +    escapeinside = parsed_opts.get('escapeinside', '')
        +    if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter):
        +        left = escapeinside[0]
        +        right = escapeinside[1]
        +        lexer = LatexEmbeddedLexer(left, right, lexer)
        +
        +    # ... and do it!
        +    if not argns.s:
        +        # process whole input as per normal...
        +        try:
        +            highlight(code, lexer, fmter, outfile)
        +        finally:
        +            if outfn:
        +                outfile.close()
        +        return 0
        +    else:
        +        # line by line processing of stdin (eg: for 'tail -f')...
        +        try:
        +            while 1:
        +                line = sys.stdin.buffer.readline()
        +                if not line:
        +                    break
        +                if not inencoding:
        +                    line = guess_decode_from_terminal(line, sys.stdin)[0]
        +                highlight(line, lexer, fmter, outfile)
        +                if hasattr(outfile, 'flush'):
        +                    outfile.flush()
        +            return 0
        +        except KeyboardInterrupt:  # pragma: no cover
        +            return 0
        +        finally:
        +            if outfn:
        +                outfile.close()
        +
        +
        +class HelpFormatter(argparse.HelpFormatter):
        +    def __init__(self, prog, indent_increment=2, max_help_position=16, width=None):
        +        if width is None:
        +            try:
        +                width = shutil.get_terminal_size().columns - 2
        +            except Exception:
        +                pass
        +        argparse.HelpFormatter.__init__(self, prog, indent_increment,
        +                                        max_help_position, width)
        +
        +
        +def main(args=sys.argv):
        +    """
        +    Main command line entry point.
        +    """
        +    desc = "Highlight an input file and write the result to an output file."
        +    parser = argparse.ArgumentParser(description=desc, add_help=False,
        +                                     formatter_class=HelpFormatter)
        +
        +    operation = parser.add_argument_group('Main operation')
        +    lexersel = operation.add_mutually_exclusive_group()
        +    lexersel.add_argument(
        +        '-l', metavar='LEXER',
        +        help='Specify the lexer to use.  (Query names with -L.)  If not '
        +        'given and -g is not present, the lexer is guessed from the filename.')
        +    lexersel.add_argument(
        +        '-g', action='store_true',
        +        help='Guess the lexer from the file contents, or pass through '
        +        'as plain text if nothing can be guessed.')
        +    operation.add_argument(
        +        '-F', metavar='FILTER[:options]', action='append',
        +        help='Add a filter to the token stream.  (Query names with -L.) '
        +        'Filter options are given after a colon if necessary.')
        +    operation.add_argument(
        +        '-f', metavar='FORMATTER',
        +        help='Specify the formatter to use.  (Query names with -L.) '
        +        'If not given, the formatter is guessed from the output filename, '
        +        'and defaults to the terminal formatter if the output is to the '
        +        'terminal or an unknown file extension.')
        +    operation.add_argument(
        +        '-O', metavar='OPTION=value[,OPTION=value,...]', action='append',
        +        help='Give options to the lexer and formatter as a comma-separated '
        +        'list of key-value pairs. '
        +        'Example: `-O bg=light,python=cool`.')
        +    operation.add_argument(
        +        '-P', metavar='OPTION=value', action='append',
        +        help='Give a single option to the lexer and formatter - with this '
        +        'you can pass options whose value contains commas and equal signs. '
        +        'Example: `-P "heading=Pygments, the Python highlighter"`.')
        +    operation.add_argument(
        +        '-o', metavar='OUTPUTFILE',
        +        help='Where to write the output.  Defaults to standard output.')
        +
        +    operation.add_argument(
        +        'INPUTFILE', nargs='?',
        +        help='Where to read the input.  Defaults to standard input.')
        +
        +    flags = parser.add_argument_group('Operation flags')
        +    flags.add_argument(
        +        '-v', action='store_true',
        +        help='Print a detailed traceback on unhandled exceptions, which '
        +        'is useful for debugging and bug reports.')
        +    flags.add_argument(
        +        '-s', action='store_true',
        +        help='Process lines one at a time until EOF, rather than waiting to '
        +        'process the entire file.  This only works for stdin, only for lexers '
        +        'with no line-spanning constructs, and is intended for streaming '
        +        'input such as you get from `tail -f`. '
        +        'Example usage: `tail -f sql.log | pygmentize -s -l sql`.')
        +    flags.add_argument(
        +        '-x', action='store_true',
        +        help='Allow custom lexers and formatters to be loaded from a .py file '
        +        'relative to the current working directory. For example, '
        +        '`-l ./customlexer.py -x`. By default, this option expects a file '
        +        'with a class named CustomLexer or CustomFormatter; you can also '
        +        'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). '
        +        'Users should be very careful not to use this option with untrusted '
        +        'files, because it will import and run them.')
        +    flags.add_argument('--json', help='Output as JSON. This can '
        +        'be only used in conjunction with -L.',
        +        default=False,
        +        action='store_true')
        +
        +    special_modes_group = parser.add_argument_group(
        +        'Special modes - do not do any highlighting')
        +    special_modes = special_modes_group.add_mutually_exclusive_group()
        +    special_modes.add_argument(
        +        '-S', metavar='STYLE -f formatter',
        +        help='Print style definitions for STYLE for a formatter '
        +        'given with -f. The argument given by -a is formatter '
        +        'dependent.')
        +    special_modes.add_argument(
        +        '-L', nargs='*', metavar='WHAT',
        +        help='List lexers, formatters, styles or filters -- '
        +        'give additional arguments for the thing(s) you want to list '
        +        '(e.g. "styles"), or omit them to list everything.')
        +    special_modes.add_argument(
        +        '-N', metavar='FILENAME',
        +        help='Guess and print out a lexer name based solely on the given '
        +        'filename. Does not take input or highlight anything. If no specific '
        +        'lexer can be determined, "text" is printed.')
        +    special_modes.add_argument(
        +        '-C', action='store_true',
        +        help='Like -N, but print out a lexer name based solely on '
        +        'a given content from standard input.')
        +    special_modes.add_argument(
        +        '-H', action='store', nargs=2, metavar=('NAME', 'TYPE'),
        +        help='Print detailed help for the object  of type , '
        +        'where  is one of "lexer", "formatter" or "filter".')
        +    special_modes.add_argument(
        +        '-V', action='store_true',
        +        help='Print the package version.')
        +    special_modes.add_argument(
        +        '-h', '--help', action='store_true',
        +        help='Print this help.')
        +    special_modes_group.add_argument(
        +        '-a', metavar='ARG',
        +        help='Formatter-specific additional argument for the -S (print '
        +        'style sheet) mode.')
        +
        +    argns = parser.parse_args(args[1:])
        +
        +    try:
        +        return main_inner(parser, argns)
        +    except BrokenPipeError:
        +        # someone closed our stdout, e.g. by quitting a pager.
        +        return 0
        +    except Exception:
        +        if argns.v:
        +            print(file=sys.stderr)
        +            print('*' * 65, file=sys.stderr)
        +            print('An unhandled exception occurred while highlighting.',
        +                  file=sys.stderr)
        +            print('Please report the whole traceback to the issue tracker at',
        +                  file=sys.stderr)
        +            print('.',
        +                  file=sys.stderr)
        +            print('*' * 65, file=sys.stderr)
        +            print(file=sys.stderr)
        +            raise
        +        import traceback
        +        info = traceback.format_exception(*sys.exc_info())
        +        msg = info[-1].strip()
        +        if len(info) >= 3:
        +            # extract relevant file and position info
        +            msg += '\n   (f{})'.format(info[-2].split('\n')[0].strip()[1:])
        +        print(file=sys.stderr)
        +        print('*** Error while highlighting:', file=sys.stderr)
        +        print(msg, file=sys.stderr)
        +        print('*** If this is a bug you want to report, please rerun with -v.',
        +              file=sys.stderr)
        +        return 1
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/console.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/console.py
        new file mode 100644
        index 000000000..4c1a06219
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/console.py
        @@ -0,0 +1,70 @@
        +"""
        +    pygments.console
        +    ~~~~~~~~~~~~~~~~
        +
        +    Format colored console output.
        +
        +    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
        +    :license: BSD, see LICENSE for details.
        +"""
        +
        +esc = "\x1b["
        +
        +codes = {}
        +codes[""] = ""
        +codes["reset"] = esc + "39;49;00m"
        +
        +codes["bold"] = esc + "01m"
        +codes["faint"] = esc + "02m"
        +codes["standout"] = esc + "03m"
        +codes["underline"] = esc + "04m"
        +codes["blink"] = esc + "05m"
        +codes["overline"] = esc + "06m"
        +
        +dark_colors = ["black", "red", "green", "yellow", "blue",
        +               "magenta", "cyan", "gray"]
        +light_colors = ["brightblack", "brightred", "brightgreen", "brightyellow", "brightblue",
        +                "brightmagenta", "brightcyan", "white"]
        +
        +x = 30
        +for dark, light in zip(dark_colors, light_colors):
        +    codes[dark] = esc + "%im" % x
        +    codes[light] = esc + "%im" % (60 + x)
        +    x += 1
        +
        +del dark, light, x
        +
        +codes["white"] = codes["bold"]
        +
        +
        +def reset_color():
        +    return codes["reset"]
        +
        +
        +def colorize(color_key, text):
        +    return codes[color_key] + text + codes["reset"]
        +
        +
        +def ansiformat(attr, text):
        +    """
        +    Format ``text`` with a color and/or some attributes::
        +
        +        color       normal color
        +        *color*     bold color
        +        _color_     underlined color
        +        +color+     blinking color
        +    """
        +    result = []
        +    if attr[:1] == attr[-1:] == '+':
        +        result.append(codes['blink'])
        +        attr = attr[1:-1]
        +    if attr[:1] == attr[-1:] == '*':
        +        result.append(codes['bold'])
        +        attr = attr[1:-1]
        +    if attr[:1] == attr[-1:] == '_':
        +        result.append(codes['underline'])
        +        attr = attr[1:-1]
        +    result.append(codes[attr])
        +    result.append(text)
        +    result.append(codes['reset'])
        +    return ''.join(result)
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filter.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filter.py
        new file mode 100644
        index 000000000..aa6f76041
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filter.py
        @@ -0,0 +1,70 @@
        +"""
        +    pygments.filter
        +    ~~~~~~~~~~~~~~~
        +
        +    Module that implements the default filter.
        +
        +    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
        +    :license: BSD, see LICENSE for details.
        +"""
        +
        +
        +def apply_filters(stream, filters, lexer=None):
        +    """
        +    Use this method to apply an iterable of filters to
        +    a stream. If lexer is given it's forwarded to the
        +    filter, otherwise the filter receives `None`.
        +    """
        +    def _apply(filter_, stream):
        +        yield from filter_.filter(lexer, stream)
        +    for filter_ in filters:
        +        stream = _apply(filter_, stream)
        +    return stream
        +
        +
        +def simplefilter(f):
        +    """
        +    Decorator that converts a function into a filter::
        +
        +        @simplefilter
        +        def lowercase(self, lexer, stream, options):
        +            for ttype, value in stream:
        +                yield ttype, value.lower()
        +    """
        +    return type(f.__name__, (FunctionFilter,), {
        +        '__module__': getattr(f, '__module__'),
        +        '__doc__': f.__doc__,
        +        'function': f,
        +    })
        +
        +
        +class Filter:
        +    """
        +    Default filter. Subclass this class or use the `simplefilter`
        +    decorator to create own filters.
        +    """
        +
        +    def __init__(self, **options):
        +        self.options = options
        +
        +    def filter(self, lexer, stream):
        +        raise NotImplementedError()
        +
        +
        +class FunctionFilter(Filter):
        +    """
        +    Abstract class used by `simplefilter` to create simple
        +    function filters on the fly. The `simplefilter` decorator
        +    automatically creates subclasses of this class for
        +    functions passed to it.
        +    """
        +    function = None
        +
        +    def __init__(self, **options):
        +        if not hasattr(self, 'function'):
        +            raise TypeError(f'{self.__class__.__name__!r} used without bound function')
        +        Filter.__init__(self, **options)
        +
        +    def filter(self, lexer, stream):
        +        # pylint: disable=not-callable
        +        yield from self.function(lexer, stream, self.options)
        diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filters/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filters/__init__.py
        new file mode 100644
        index 000000000..9255ca224
        --- /dev/null
        +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filters/__init__.py
        @@ -0,0 +1,940 @@
        +"""
        +    pygments.filters
        +    ~~~~~~~~~~~~~~~~
        +
        +    Module containing filter lookup functions and default
        +    filters.
        +
        +    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
        +    :license: BSD, see LICENSE for details.
        +"""
        +
        +import re
        +
        +from pip._vendor.pygments.token import String, Comment, Keyword, Name, Error, Whitespace, \
        +    string_to_tokentype
        +from pip._vendor.pygments.filter import Filter
        +from pip._vendor.pygments.util import get_list_opt, get_int_opt, get_bool_opt, \
        +    get_choice_opt, ClassNotFound, OptionError
        +from pip._vendor.pygments.plugin import find_plugin_filters
        +
        +
        +def find_filter_class(filtername):
        +    """Lookup a filter by name. Return None if not found."""
        +    if filtername in FILTERS:
        +        return FILTERS[filtername]
        +    for name, cls in find_plugin_filters():
        +        if name == filtername:
        +            return cls
        +    return None
        +
        +
        +def get_filter_by_name(filtername, **options):
        +    """Return an instantiated filter.
        +
        +    Options are passed to the filter initializer if wanted.
        +    Raise a ClassNotFound if not found.
        +    """
        +    cls = find_filter_class(filtername)
        +    if cls:
        +        return cls(**options)
        +    else:
        +        raise ClassNotFound(f'filter {filtername!r} not found')
        +
        +
        +def get_all_filters():
        +    """Return a generator of all filter names."""
        +    yield from FILTERS
        +    for name, _ in find_plugin_filters():
        +        yield name
        +
        +
        +def _replace_special(ttype, value, regex, specialttype,
        +                     replacefunc=lambda x: x):
        +    last = 0
        +    for match in regex.finditer(value):
        +        start, end = match.start(), match.end()
        +        if start != last:
        +            yield ttype, value[last:start]
        +        yield specialttype, replacefunc(value[start:end])
        +        last = end
        +    if last != len(value):
        +        yield ttype, value[last:]
        +
        +
        +class CodeTagFilter(Filter):
        +    """Highlight special code tags in comments and docstrings.
        +
        +    Options accepted:
        +
        +    `codetags` : list of strings
        +       A list of strings that are flagged as code tags.  The default is to
        +       highlight ``XXX``, ``TODO``, ``FIXME``, ``BUG`` and ``NOTE``.
        +
        +    .. versionchanged:: 2.13
        +       Now recognizes ``FIXME`` by default.
        +    """
        +
        +    def __init__(self, **options):
        +        Filter.__init__(self, **options)
        +        tags = get_list_opt(options, 'codetags',
        +                            ['XXX', 'TODO', 'FIXME', 'BUG', 'NOTE'])
        +        self.tag_re = re.compile(r'\b({})\b'.format('|'.join([
        +            re.escape(tag) for tag in tags if tag
        +        ])))
        +
        +    def filter(self, lexer, stream):
        +        regex = self.tag_re
        +        for ttype, value in stream:
        +            if ttype in String.Doc or \
        +               ttype in Comment and \
        +               ttype not in Comment.Preproc:
        +                yield from _replace_special(ttype, value, regex, Comment.Special)
        +            else:
        +                yield ttype, value
        +
        +
        +class SymbolFilter(Filter):
        +    """Convert mathematical symbols such as \\ in Isabelle
        +    or \\longrightarrow in LaTeX into Unicode characters.
        +
        +    This is mostly useful for HTML or console output when you want to
        +    approximate the source rendering you'd see in an IDE.
        +
        +    Options accepted:
        +
        +    `lang` : string
        +       The symbol language. Must be one of ``'isabelle'`` or
        +       ``'latex'``.  The default is ``'isabelle'``.
        +    """
        +
        +    latex_symbols = {
        +        '\\alpha'                : '\U000003b1',
        +        '\\beta'                 : '\U000003b2',
        +        '\\gamma'                : '\U000003b3',
        +        '\\delta'                : '\U000003b4',
        +        '\\varepsilon'           : '\U000003b5',
        +        '\\zeta'                 : '\U000003b6',
        +        '\\eta'                  : '\U000003b7',
        +        '\\vartheta'             : '\U000003b8',
        +        '\\iota'                 : '\U000003b9',
        +        '\\kappa'                : '\U000003ba',
        +        '\\lambda'               : '\U000003bb',
        +        '\\mu'                   : '\U000003bc',
        +        '\\nu'                   : '\U000003bd',
        +        '\\xi'                   : '\U000003be',
        +        '\\pi'                   : '\U000003c0',
        +        '\\varrho'               : '\U000003c1',
        +        '\\sigma'                : '\U000003c3',
        +        '\\tau'                  : '\U000003c4',
        +        '\\upsilon'              : '\U000003c5',
        +        '\\varphi'               : '\U000003c6',
        +        '\\chi'                  : '\U000003c7',
        +        '\\psi'                  : '\U000003c8',
        +        '\\omega'                : '\U000003c9',
        +        '\\Gamma'                : '\U00000393',
        +        '\\Delta'                : '\U00000394',
        +        '\\Theta'                : '\U00000398',
        +        '\\Lambda'               : '\U0000039b',
        +        '\\Xi'                   : '\U0000039e',
        +        '\\Pi'                   : '\U000003a0',
        +        '\\Sigma'                : '\U000003a3',
        +        '\\Upsilon'              : '\U000003a5',
        +        '\\Phi'                  : '\U000003a6',
        +        '\\Psi'                  : '\U000003a8',
        +        '\\Omega'                : '\U000003a9',
        +        '\\leftarrow'            : '\U00002190',
        +        '\\longleftarrow'        : '\U000027f5',
        +        '\\rightarrow'           : '\U00002192',
        +        '\\longrightarrow'       : '\U000027f6',
        +        '\\Leftarrow'            : '\U000021d0',
        +        '\\Longleftarrow'        : '\U000027f8',
        +        '\\Rightarrow'           : '\U000021d2',
        +        '\\Longrightarrow'       : '\U000027f9',
        +        '\\leftrightarrow'       : '\U00002194',
        +        '\\longleftrightarrow'   : '\U000027f7',
        +        '\\Leftrightarrow'       : '\U000021d4',
        +        '\\Longleftrightarrow'   : '\U000027fa',
        +        '\\mapsto'               : '\U000021a6',
        +        '\\longmapsto'           : '\U000027fc',
        +        '\\relbar'               : '\U00002500',
        +        '\\Relbar'               : '\U00002550',
        +        '\\hookleftarrow'        : '\U000021a9',
        +        '\\hookrightarrow'       : '\U000021aa',
        +        '\\leftharpoondown'      : '\U000021bd',
        +        '\\rightharpoondown'     : '\U000021c1',
        +        '\\leftharpoonup'        : '\U000021bc',
        +        '\\rightharpoonup'       : '\U000021c0',
        +        '\\rightleftharpoons'    : '\U000021cc',
        +        '\\leadsto'              : '\U0000219d',
        +        '\\downharpoonleft'      : '\U000021c3',
        +        '\\downharpoonright'     : '\U000021c2',
        +        '\\upharpoonleft'        : '\U000021bf',
        +        '\\upharpoonright'       : '\U000021be',
        +        '\\restriction'          : '\U000021be',
        +        '\\uparrow'              : '\U00002191',
        +        '\\Uparrow'              : '\U000021d1',
        +        '\\downarrow'            : '\U00002193',
        +        '\\Downarrow'            : '\U000021d3',
        +        '\\updownarrow'          : '\U00002195',
        +        '\\Updownarrow'          : '\U000021d5',
        +        '\\langle'               : '\U000027e8',
        +        '\\rangle'               : '\U000027e9',
        +        '\\lceil'                : '\U00002308',
        +        '\\rceil'                : '\U00002309',
        +        '\\lfloor'               : '\U0000230a',
        +        '\\rfloor'               : '\U0000230b',
        +        '\\flqq'                 : '\U000000ab',
        +        '\\frqq'                 : '\U000000bb',
        +        '\\bot'                  : '\U000022a5',
        +        '\\top'                  : '\U000022a4',
        +        '\\wedge'                : '\U00002227',
        +        '\\bigwedge'             : '\U000022c0',
        +        '\\vee'                  : '\U00002228',
        +        '\\bigvee'               : '\U000022c1',
        +        '\\forall'               : '\U00002200',
        +        '\\exists'               : '\U00002203',
        +        '\\nexists'              : '\U00002204',
        +        '\\neg'                  : '\U000000ac',
        +        '\\Box'                  : '\U000025a1',
        +        '\\Diamond'              : '\U000025c7',
        +        '\\vdash'                : '\U000022a2',
        +        '\\models'               : '\U000022a8',
        +        '\\dashv'                : '\U000022a3',
        +        '\\surd'                 : '\U0000221a',
        +        '\\le'                   : '\U00002264',
        +        '\\ge'                   : '\U00002265',
        +        '\\ll'                   : '\U0000226a',
        +        '\\gg'                   : '\U0000226b',
        +        '\\lesssim'              : '\U00002272',
        +        '\\gtrsim'               : '\U00002273',
        +        '\\lessapprox'           : '\U00002a85',
        +        '\\gtrapprox'            : '\U00002a86',
        +        '\\in'                   : '\U00002208',
        +        '\\notin'                : '\U00002209',
        +        '\\subset'               : '\U00002282',
        +        '\\supset'               : '\U00002283',
        +        '\\subseteq'             : '\U00002286',
        +        '\\supseteq'             : '\U00002287',
        +        '\\sqsubset'             : '\U0000228f',
        +        '\\sqsupset'             : '\U00002290',
        +        '\\sqsubseteq'           : '\U00002291',
        +        '\\sqsupseteq'           : '\U00002292',
        +        '\\cap'                  : '\U00002229',
        +        '\\bigcap'               : '\U000022c2',
        +        '\\cup'                  : '\U0000222a',
        +        '\\bigcup'               : '\U000022c3',
        +        '\\sqcup'                : '\U00002294',
        +        '\\bigsqcup'             : '\U00002a06',
        +        '\\sqcap'                : '\U00002293',
        +        '\\Bigsqcap'             : '\U00002a05',
        +        '\\setminus'             : '\U00002216',
        +        '\\propto'               : '\U0000221d',
        +        '\\uplus'                : '\U0000228e',
        +        '\\bigplus'              : '\U00002a04',
        +        '\\sim'                  : '\U0000223c',
        +        '\\doteq'                : '\U00002250',
        +        '\\simeq'                : '\U00002243',
        +        '\\approx'               : '\U00002248',
        +        '\\asymp'                : '\U0000224d',
        +        '\\cong'                 : '\U00002245',
        +        '\\equiv'                : '\U00002261',
        +        '\\Join'                 : '\U000022c8',
        +        '\\bowtie'               : '\U00002a1d',
        +        '\\prec'                 : '\U0000227a',
        +        '\\succ'                 : '\U0000227b',
        +        '\\preceq'               : '\U0000227c',
        +        '\\succeq'               : '\U0000227d',
        +        '\\parallel'             : '\U00002225',
        +        '\\mid'                  : '\U000000a6',
        +        '\\pm'                   : '\U000000b1',
        +        '\\mp'                   : '\U00002213',
        +        '\\times'                : '\U000000d7',
        +        '\\div'                  : '\U000000f7',
        +        '\\cdot'                 : '\U000022c5',
        +        '\\star'                 : '\U000022c6',
        +        '\\circ'                 : '\U00002218',
        +        '\\dagger'               : '\U00002020',
        +        '\\ddagger'              : '\U00002021',
        +        '\\lhd'                  : '\U000022b2',
        +        '\\rhd'                  : '\U000022b3',
        +        '\\unlhd'                : '\U000022b4',
        +        '\\unrhd'                : '\U000022b5',
        +        '\\triangleleft'         : '\U000025c3',
        +        '\\triangleright'        : '\U000025b9',
        +        '\\triangle'             : '\U000025b3',
        +        '\\triangleq'            : '\U0000225c',
        +        '\\oplus'                : '\U00002295',
        +        '\\bigoplus'             : '\U00002a01',
        +        '\\otimes'               : '\U00002297',
        +        '\\bigotimes'            : '\U00002a02',
        +        '\\odot'                 : '\U00002299',
        +        '\\bigodot'              : '\U00002a00',
        +        '\\ominus'               : '\U00002296',
        +        '\\oslash'               : '\U00002298',
        +        '\\dots'                 : '\U00002026',
        +        '\\cdots'                : '\U000022ef',
        +        '\\sum'                  : '\U00002211',
        +        '\\prod'                 : '\U0000220f',
        +        '\\coprod'               : '\U00002210',
        +        '\\infty'                : '\U0000221e',
        +        '\\int'                  : '\U0000222b',
        +        '\\oint'                 : '\U0000222e',
        +        '\\clubsuit'             : '\U00002663',
        +        '\\diamondsuit'          : '\U00002662',
        +        '\\heartsuit'            : '\U00002661',
        +        '\\spadesuit'            : '\U00002660',
        +        '\\aleph'                : '\U00002135',
        +        '\\emptyset'             : '\U00002205',
        +        '\\nabla'                : '\U00002207',
        +        '\\partial'              : '\U00002202',
        +        '\\flat'                 : '\U0000266d',
        +        '\\natural'              : '\U0000266e',
        +        '\\sharp'                : '\U0000266f',
        +        '\\angle'                : '\U00002220',
        +        '\\copyright'            : '\U000000a9',
        +        '\\textregistered'       : '\U000000ae',
        +        '\\textonequarter'       : '\U000000bc',
        +        '\\textonehalf'          : '\U000000bd',
        +        '\\textthreequarters'    : '\U000000be',
        +        '\\textordfeminine'      : '\U000000aa',
        +        '\\textordmasculine'     : '\U000000ba',
        +        '\\euro'                 : '\U000020ac',
        +        '\\pounds'               : '\U000000a3',
        +        '\\yen'                  : '\U000000a5',
        +        '\\textcent'             : '\U000000a2',
        +        '\\textcurrency'         : '\U000000a4',
        +        '\\textdegree'           : '\U000000b0',
        +    }
        +
        +    isabelle_symbols = {
        +        '\\'                 : '\U0001d7ec',
        +        '\\'                  : '\U0001d7ed',
        +        '\\'                  : '\U0001d7ee',
        +        '\\'                : '\U0001d7ef',
        +        '\\'                 : '\U0001d7f0',
        +        '\\'                 : '\U0001d7f1',
        +        '\\'                  : '\U0001d7f2',
        +        '\\'                : '\U0001d7f3',
        +        '\\'                : '\U0001d7f4',
        +        '\\'                 : '\U0001d7f5',
        +        '\\'                    : '\U0001d49c',
        +        '\\'                    : '\U0000212c',
        +        '\\'                    : '\U0001d49e',
        +        '\\'                    : '\U0001d49f',
        +        '\\'                    : '\U00002130',
        +        '\\'                    : '\U00002131',
        +        '\\'                    : '\U0001d4a2',
        +        '\\'                    : '\U0000210b',
        +        '\\'                    : '\U00002110',
        +        '\\'                    : '\U0001d4a5',
        +        '\\'                    : '\U0001d4a6',
        +        '\\'                    : '\U00002112',
        +        '\\'                    : '\U00002133',
        +        '\\'                    : '\U0001d4a9',
        +        '\\'                    : '\U0001d4aa',
        +        '\\

        ' : '\U0001d5c9', + '\\' : '\U0001d5ca', + '\\' : '\U0001d5cb', + '\\' : '\U0001d5cc', + '\\' : '\U0001d5cd', + '\\' : '\U0001d5ce', + '\\' : '\U0001d5cf', + '\\' : '\U0001d5d0', + '\\' : '\U0001d5d1', + '\\' : '\U0001d5d2', + '\\' : '\U0001d5d3', + '\\' : '\U0001d504', + '\\' : '\U0001d505', + '\\' : '\U0000212d', + '\\

        ' : '\U0001d507', + '\\' : '\U0001d508', + '\\' : '\U0001d509', + '\\' : '\U0001d50a', + '\\' : '\U0000210c', + '\\' : '\U00002111', + '\\' : '\U0001d50d', + '\\' : '\U0001d50e', + '\\' : '\U0001d50f', + '\\' : '\U0001d510', + '\\' : '\U0001d511', + '\\' : '\U0001d512', + '\\' : '\U0001d513', + '\\' : '\U0001d514', + '\\' : '\U0000211c', + '\\' : '\U0001d516', + '\\' : '\U0001d517', + '\\' : '\U0001d518', + '\\' : '\U0001d519', + '\\' : '\U0001d51a', + '\\' : '\U0001d51b', + '\\' : '\U0001d51c', + '\\' : '\U00002128', + '\\' : '\U0001d51e', + '\\' : '\U0001d51f', + '\\' : '\U0001d520', + '\\
        ' : '\U0001d521', + '\\' : '\U0001d522', + '\\' : '\U0001d523', + '\\' : '\U0001d524', + '\\' : '\U0001d525', + '\\' : '\U0001d526', + '\\' : '\U0001d527', + '\\' : '\U0001d528', + '\\' : '\U0001d529', + '\\' : '\U0001d52a', + '\\' : '\U0001d52b', + '\\' : '\U0001d52c', + '\\' : '\U0001d52d', + '\\' : '\U0001d52e', + '\\' : '\U0001d52f', + '\\' : '\U0001d530', + '\\' : '\U0001d531', + '\\' : '\U0001d532', + '\\' : '\U0001d533', + '\\' : '\U0001d534', + '\\' : '\U0001d535', + '\\' : '\U0001d536', + '\\' : '\U0001d537', + '\\' : '\U000003b1', + '\\' : '\U000003b2', + '\\' : '\U000003b3', + '\\' : '\U000003b4', + '\\' : '\U000003b5', + '\\' : '\U000003b6', + '\\' : '\U000003b7', + '\\' : '\U000003b8', + '\\' : '\U000003b9', + '\\' : '\U000003ba', + '\\' : '\U000003bb', + '\\' : '\U000003bc', + '\\' : '\U000003bd', + '\\' : '\U000003be', + '\\' : '\U000003c0', + '\\' : '\U000003c1', + '\\' : '\U000003c3', + '\\' : '\U000003c4', + '\\' : '\U000003c5', + '\\' : '\U000003c6', + '\\' : '\U000003c7', + '\\' : '\U000003c8', + '\\' : '\U000003c9', + '\\' : '\U00000393', + '\\' : '\U00000394', + '\\' : '\U00000398', + '\\' : '\U0000039b', + '\\' : '\U0000039e', + '\\' : '\U000003a0', + '\\' : '\U000003a3', + '\\' : '\U000003a5', + '\\' : '\U000003a6', + '\\' : '\U000003a8', + '\\' : '\U000003a9', + '\\' : '\U0001d539', + '\\' : '\U00002102', + '\\' : '\U00002115', + '\\' : '\U0000211a', + '\\' : '\U0000211d', + '\\' : '\U00002124', + '\\' : '\U00002190', + '\\' : '\U000027f5', + '\\' : '\U00002192', + '\\' : '\U000027f6', + '\\' : '\U000021d0', + '\\' : '\U000027f8', + '\\' : '\U000021d2', + '\\' : '\U000027f9', + '\\' : '\U00002194', + '\\' : '\U000027f7', + '\\' : '\U000021d4', + '\\' : '\U000027fa', + '\\' : '\U000021a6', + '\\' : '\U000027fc', + '\\' : '\U00002500', + '\\' : '\U00002550', + '\\' : '\U000021a9', + '\\' : '\U000021aa', + '\\' : '\U000021bd', + '\\' : '\U000021c1', + '\\' : '\U000021bc', + '\\' : '\U000021c0', + '\\' : '\U000021cc', + '\\' : '\U0000219d', + '\\' : '\U000021c3', + '\\' : '\U000021c2', + '\\' : '\U000021bf', + '\\' : '\U000021be', + '\\' : '\U000021be', + '\\' : '\U00002237', + '\\' : '\U00002191', + '\\' : '\U000021d1', + '\\' : '\U00002193', + '\\' : '\U000021d3', + '\\' : '\U00002195', + '\\' : '\U000021d5', + '\\' : '\U000027e8', + '\\' : '\U000027e9', + '\\' : '\U00002308', + '\\' : '\U00002309', + '\\' : '\U0000230a', + '\\' : '\U0000230b', + '\\' : '\U00002987', + '\\' : '\U00002988', + '\\' : '\U000027e6', + '\\' : '\U000027e7', + '\\' : '\U00002983', + '\\' : '\U00002984', + '\\' : '\U000000ab', + '\\' : '\U000000bb', + '\\' : '\U000022a5', + '\\' : '\U000022a4', + '\\' : '\U00002227', + '\\' : '\U000022c0', + '\\' : '\U00002228', + '\\' : '\U000022c1', + '\\' : '\U00002200', + '\\' : '\U00002203', + '\\' : '\U00002204', + '\\' : '\U000000ac', + '\\' : '\U000025a1', + '\\' : '\U000025c7', + '\\' : '\U000022a2', + '\\' : '\U000022a8', + '\\' : '\U000022a9', + '\\' : '\U000022ab', + '\\' : '\U000022a3', + '\\' : '\U0000221a', + '\\' : '\U00002264', + '\\' : '\U00002265', + '\\' : '\U0000226a', + '\\' : '\U0000226b', + '\\' : '\U00002272', + '\\' : '\U00002273', + '\\' : '\U00002a85', + '\\' : '\U00002a86', + '\\' : '\U00002208', + '\\' : '\U00002209', + '\\' : '\U00002282', + '\\' : '\U00002283', + '\\' : '\U00002286', + '\\' : '\U00002287', + '\\' : '\U0000228f', + '\\' : '\U00002290', + '\\' : '\U00002291', + '\\' : '\U00002292', + '\\' : '\U00002229', + '\\' : '\U000022c2', + '\\' : '\U0000222a', + '\\' : '\U000022c3', + '\\' : '\U00002294', + '\\' : '\U00002a06', + '\\' : '\U00002293', + '\\' : '\U00002a05', + '\\' : '\U00002216', + '\\' : '\U0000221d', + '\\' : '\U0000228e', + '\\' : '\U00002a04', + '\\' : '\U00002260', + '\\' : '\U0000223c', + '\\' : '\U00002250', + '\\' : '\U00002243', + '\\' : '\U00002248', + '\\' : '\U0000224d', + '\\' : '\U00002245', + '\\' : '\U00002323', + '\\' : '\U00002261', + '\\' : '\U00002322', + '\\' : '\U000022c8', + '\\' : '\U00002a1d', + '\\' : '\U0000227a', + '\\' : '\U0000227b', + '\\' : '\U0000227c', + '\\' : '\U0000227d', + '\\' : '\U00002225', + '\\' : '\U000000a6', + '\\' : '\U000000b1', + '\\' : '\U00002213', + '\\' : '\U000000d7', + '\\
        ' : '\U000000f7', + '\\' : '\U000022c5', + '\\' : '\U000022c6', + '\\' : '\U00002219', + '\\' : '\U00002218', + '\\' : '\U00002020', + '\\' : '\U00002021', + '\\' : '\U000022b2', + '\\' : '\U000022b3', + '\\' : '\U000022b4', + '\\' : '\U000022b5', + '\\' : '\U000025c3', + '\\' : '\U000025b9', + '\\' : '\U000025b3', + '\\' : '\U0000225c', + '\\' : '\U00002295', + '\\' : '\U00002a01', + '\\' : '\U00002297', + '\\' : '\U00002a02', + '\\' : '\U00002299', + '\\' : '\U00002a00', + '\\' : '\U00002296', + '\\' : '\U00002298', + '\\' : '\U00002026', + '\\' : '\U000022ef', + '\\' : '\U00002211', + '\\' : '\U0000220f', + '\\' : '\U00002210', + '\\' : '\U0000221e', + '\\' : '\U0000222b', + '\\' : '\U0000222e', + '\\' : '\U00002663', + '\\' : '\U00002662', + '\\' : '\U00002661', + '\\' : '\U00002660', + '\\' : '\U00002135', + '\\' : '\U00002205', + '\\' : '\U00002207', + '\\' : '\U00002202', + '\\' : '\U0000266d', + '\\' : '\U0000266e', + '\\' : '\U0000266f', + '\\' : '\U00002220', + '\\' : '\U000000a9', + '\\' : '\U000000ae', + '\\' : '\U000000ad', + '\\' : '\U000000af', + '\\' : '\U000000bc', + '\\' : '\U000000bd', + '\\' : '\U000000be', + '\\' : '\U000000aa', + '\\' : '\U000000ba', + '\\
        ' : '\U000000a7', + '\\' : '\U000000b6', + '\\' : '\U000000a1', + '\\' : '\U000000bf', + '\\' : '\U000020ac', + '\\' : '\U000000a3', + '\\' : '\U000000a5', + '\\' : '\U000000a2', + '\\' : '\U000000a4', + '\\' : '\U000000b0', + '\\' : '\U00002a3f', + '\\' : '\U00002127', + '\\' : '\U000025ca', + '\\' : '\U00002118', + '\\' : '\U00002240', + '\\' : '\U000022c4', + '\\' : '\U000000b4', + '\\' : '\U00000131', + '\\' : '\U000000a8', + '\\' : '\U000000b8', + '\\' : '\U000002dd', + '\\' : '\U000003f5', + '\\' : '\U000023ce', + '\\' : '\U00002039', + '\\' : '\U0000203a', + '\\' : '\U00002302', + '\\<^sub>' : '\U000021e9', + '\\<^sup>' : '\U000021e7', + '\\<^bold>' : '\U00002759', + '\\<^bsub>' : '\U000021d8', + '\\<^esub>' : '\U000021d9', + '\\<^bsup>' : '\U000021d7', + '\\<^esup>' : '\U000021d6', + } + + lang_map = {'isabelle' : isabelle_symbols, 'latex' : latex_symbols} + + def __init__(self, **options): + Filter.__init__(self, **options) + lang = get_choice_opt(options, 'lang', + ['isabelle', 'latex'], 'isabelle') + self.symbols = self.lang_map[lang] + + def filter(self, lexer, stream): + for ttype, value in stream: + if value in self.symbols: + yield ttype, self.symbols[value] + else: + yield ttype, value + + +class KeywordCaseFilter(Filter): + """Convert keywords to lowercase or uppercase or capitalize them, which + means first letter uppercase, rest lowercase. + + This can be useful e.g. if you highlight Pascal code and want to adapt the + code to your styleguide. + + Options accepted: + + `case` : string + The casing to convert keywords to. Must be one of ``'lower'``, + ``'upper'`` or ``'capitalize'``. The default is ``'lower'``. + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + case = get_choice_opt(options, 'case', + ['lower', 'upper', 'capitalize'], 'lower') + self.convert = getattr(str, case) + + def filter(self, lexer, stream): + for ttype, value in stream: + if ttype in Keyword: + yield ttype, self.convert(value) + else: + yield ttype, value + + +class NameHighlightFilter(Filter): + """Highlight a normal Name (and Name.*) token with a different token type. + + Example:: + + filter = NameHighlightFilter( + names=['foo', 'bar', 'baz'], + tokentype=Name.Function, + ) + + This would highlight the names "foo", "bar" and "baz" + as functions. `Name.Function` is the default token type. + + Options accepted: + + `names` : list of strings + A list of names that should be given the different token type. + There is no default. + `tokentype` : TokenType or string + A token type or a string containing a token type name that is + used for highlighting the strings in `names`. The default is + `Name.Function`. + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + self.names = set(get_list_opt(options, 'names', [])) + tokentype = options.get('tokentype') + if tokentype: + self.tokentype = string_to_tokentype(tokentype) + else: + self.tokentype = Name.Function + + def filter(self, lexer, stream): + for ttype, value in stream: + if ttype in Name and value in self.names: + yield self.tokentype, value + else: + yield ttype, value + + +class ErrorToken(Exception): + pass + + +class RaiseOnErrorTokenFilter(Filter): + """Raise an exception when the lexer generates an error token. + + Options accepted: + + `excclass` : Exception class + The exception class to raise. + The default is `pygments.filters.ErrorToken`. + + .. versionadded:: 0.8 + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + self.exception = options.get('excclass', ErrorToken) + try: + # issubclass() will raise TypeError if first argument is not a class + if not issubclass(self.exception, Exception): + raise TypeError + except TypeError: + raise OptionError('excclass option is not an exception class') + + def filter(self, lexer, stream): + for ttype, value in stream: + if ttype is Error: + raise self.exception(value) + yield ttype, value + + +class VisibleWhitespaceFilter(Filter): + """Convert tabs, newlines and/or spaces to visible characters. + + Options accepted: + + `spaces` : string or bool + If this is a one-character string, spaces will be replaces by this string. + If it is another true value, spaces will be replaced by ``·`` (unicode + MIDDLE DOT). If it is a false value, spaces will not be replaced. The + default is ``False``. + `tabs` : string or bool + The same as for `spaces`, but the default replacement character is ``»`` + (unicode RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK). The default value + is ``False``. Note: this will not work if the `tabsize` option for the + lexer is nonzero, as tabs will already have been expanded then. + `tabsize` : int + If tabs are to be replaced by this filter (see the `tabs` option), this + is the total number of characters that a tab should be expanded to. + The default is ``8``. + `newlines` : string or bool + The same as for `spaces`, but the default replacement character is ``¶`` + (unicode PILCROW SIGN). The default value is ``False``. + `wstokentype` : bool + If true, give whitespace the special `Whitespace` token type. This allows + styling the visible whitespace differently (e.g. greyed out), but it can + disrupt background colors. The default is ``True``. + + .. versionadded:: 0.8 + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + for name, default in [('spaces', '·'), + ('tabs', '»'), + ('newlines', '¶')]: + opt = options.get(name, False) + if isinstance(opt, str) and len(opt) == 1: + setattr(self, name, opt) + else: + setattr(self, name, (opt and default or '')) + tabsize = get_int_opt(options, 'tabsize', 8) + if self.tabs: + self.tabs += ' ' * (tabsize - 1) + if self.newlines: + self.newlines += '\n' + self.wstt = get_bool_opt(options, 'wstokentype', True) + + def filter(self, lexer, stream): + if self.wstt: + spaces = self.spaces or ' ' + tabs = self.tabs or '\t' + newlines = self.newlines or '\n' + regex = re.compile(r'\s') + + def replacefunc(wschar): + if wschar == ' ': + return spaces + elif wschar == '\t': + return tabs + elif wschar == '\n': + return newlines + return wschar + + for ttype, value in stream: + yield from _replace_special(ttype, value, regex, Whitespace, + replacefunc) + else: + spaces, tabs, newlines = self.spaces, self.tabs, self.newlines + # simpler processing + for ttype, value in stream: + if spaces: + value = value.replace(' ', spaces) + if tabs: + value = value.replace('\t', tabs) + if newlines: + value = value.replace('\n', newlines) + yield ttype, value + + +class GobbleFilter(Filter): + """Gobbles source code lines (eats initial characters). + + This filter drops the first ``n`` characters off every line of code. This + may be useful when the source code fed to the lexer is indented by a fixed + amount of space that isn't desired in the output. + + Options accepted: + + `n` : int + The number of characters to gobble. + + .. versionadded:: 1.2 + """ + def __init__(self, **options): + Filter.__init__(self, **options) + self.n = get_int_opt(options, 'n', 0) + + def gobble(self, value, left): + if left < len(value): + return value[left:], 0 + else: + return '', left - len(value) + + def filter(self, lexer, stream): + n = self.n + left = n # How many characters left to gobble. + for ttype, value in stream: + # Remove ``left`` tokens from first line, ``n`` from all others. + parts = value.split('\n') + (parts[0], left) = self.gobble(parts[0], left) + for i in range(1, len(parts)): + (parts[i], left) = self.gobble(parts[i], n) + value = '\n'.join(parts) + + if value != '': + yield ttype, value + + +class TokenMergeFilter(Filter): + """Merges consecutive tokens with the same token type in the output + stream of a lexer. + + .. versionadded:: 1.2 + """ + def __init__(self, **options): + Filter.__init__(self, **options) + + def filter(self, lexer, stream): + current_type = None + current_value = None + for ttype, value in stream: + if ttype is current_type: + current_value += value + else: + if current_type is not None: + yield current_type, current_value + current_type = ttype + current_value = value + if current_type is not None: + yield current_type, current_value + + +FILTERS = { + 'codetagify': CodeTagFilter, + 'keywordcase': KeywordCaseFilter, + 'highlight': NameHighlightFilter, + 'raiseonerror': RaiseOnErrorTokenFilter, + 'whitespace': VisibleWhitespaceFilter, + 'gobble': GobbleFilter, + 'tokenmerge': TokenMergeFilter, + 'symbols': SymbolFilter, +} diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30cc07feff111713cd262e79e72e6d9176c37a9d GIT binary patch literal 23496 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 RqpxFA$GXPN1i*j(e*m7?*Z}|l literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatter.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatter.py new file mode 100644 index 000000000..d2666037f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatter.py @@ -0,0 +1,129 @@ +""" + pygments.formatter + ~~~~~~~~~~~~~~~~~~ + + Base formatter class. + + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import codecs + +from pip._vendor.pygments.util import get_bool_opt +from pip._vendor.pygments.styles import get_style_by_name + +__all__ = ['Formatter'] + + +def _lookup_style(style): + if isinstance(style, str): + return get_style_by_name(style) + return style + + +class Formatter: + """ + Converts a token stream to text. + + Formatters should have attributes to help selecting them. These + are similar to the corresponding :class:`~pygments.lexer.Lexer` + attributes. + + .. autoattribute:: name + :no-value: + + .. autoattribute:: aliases + :no-value: + + .. autoattribute:: filenames + :no-value: + + You can pass options as keyword arguments to the constructor. + All formatters accept these basic options: + + ``style`` + The style to use, can be a string or a Style subclass + (default: "default"). Not used by e.g. the + TerminalFormatter. + ``full`` + Tells the formatter to output a "full" document, i.e. + a complete self-contained document. This doesn't have + any effect for some formatters (default: false). + ``title`` + If ``full`` is true, the title that should be used to + caption the document (default: ''). + ``encoding`` + If given, must be an encoding name. This will be used to + convert the Unicode token strings to byte strings in the + output. If it is "" or None, Unicode strings will be written + to the output file, which most file-like objects do not + support (default: None). + ``outencoding`` + Overrides ``encoding`` if given. + + """ + + #: Full name for the formatter, in human-readable form. + name = None + + #: A list of short, unique identifiers that can be used to lookup + #: the formatter from a list, e.g. using :func:`.get_formatter_by_name()`. + aliases = [] + + #: A list of fnmatch patterns that match filenames for which this + #: formatter can produce output. The patterns in this list should be unique + #: among all formatters. + filenames = [] + + #: If True, this formatter outputs Unicode strings when no encoding + #: option is given. + unicodeoutput = True + + def __init__(self, **options): + """ + As with lexers, this constructor takes arbitrary optional arguments, + and if you override it, you should first process your own options, then + call the base class implementation. + """ + self.style = _lookup_style(options.get('style', 'default')) + self.full = get_bool_opt(options, 'full', False) + self.title = options.get('title', '') + self.encoding = options.get('encoding', None) or None + if self.encoding in ('guess', 'chardet'): + # can happen for e.g. pygmentize -O encoding=guess + self.encoding = 'utf-8' + self.encoding = options.get('outencoding') or self.encoding + self.options = options + + def get_style_defs(self, arg=''): + """ + This method must return statements or declarations suitable to define + the current style for subsequent highlighted text (e.g. CSS classes + in the `HTMLFormatter`). + + The optional argument `arg` can be used to modify the generation and + is formatter dependent (it is standardized because it can be given on + the command line). + + This method is called by the ``-S`` :doc:`command-line option `, + the `arg` is then given by the ``-a`` option. + """ + return '' + + def format(self, tokensource, outfile): + """ + This method must format the tokens from the `tokensource` iterable and + write the formatted version to the file object `outfile`. + + Formatter options can control how exactly the tokens are converted. + """ + if self.encoding: + # wrap the outfile in a StreamWriter + outfile = codecs.lookup(self.encoding)[3](outfile) + return self.format_unencoded(tokensource, outfile) + + # Allow writing Formatter[str] or Formatter[bytes]. That's equivalent to + # Formatter. This helps when using third-party type stubs from typeshed. + def __class_getitem__(cls, name): + return cls diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__init__.py new file mode 100644 index 000000000..f19e9931f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__init__.py @@ -0,0 +1,157 @@ +""" + pygments.formatters + ~~~~~~~~~~~~~~~~~~~ + + Pygments formatters. + + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import sys +import types +import fnmatch +from os.path import basename + +from pip._vendor.pygments.formatters._mapping import FORMATTERS +from pip._vendor.pygments.plugin import find_plugin_formatters +from pip._vendor.pygments.util import ClassNotFound + +__all__ = ['get_formatter_by_name', 'get_formatter_for_filename', + 'get_all_formatters', 'load_formatter_from_file'] + list(FORMATTERS) + +_formatter_cache = {} # classes by name +_pattern_cache = {} + + +def _fn_matches(fn, glob): + """Return whether the supplied file name fn matches pattern filename.""" + if glob not in _pattern_cache: + pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob)) + return pattern.match(fn) + return _pattern_cache[glob].match(fn) + + +def _load_formatters(module_name): + """Load a formatter (and all others in the module too).""" + mod = __import__(module_name, None, None, ['__all__']) + for formatter_name in mod.__all__: + cls = getattr(mod, formatter_name) + _formatter_cache[cls.name] = cls + + +def get_all_formatters(): + """Return a generator for all formatter classes.""" + # NB: this returns formatter classes, not info like get_all_lexers(). + for info in FORMATTERS.values(): + if info[1] not in _formatter_cache: + _load_formatters(info[0]) + yield _formatter_cache[info[1]] + for _, formatter in find_plugin_formatters(): + yield formatter + + +def find_formatter_class(alias): + """Lookup a formatter by alias. + + Returns None if not found. + """ + for module_name, name, aliases, _, _ in FORMATTERS.values(): + if alias in aliases: + if name not in _formatter_cache: + _load_formatters(module_name) + return _formatter_cache[name] + for _, cls in find_plugin_formatters(): + if alias in cls.aliases: + return cls + + +def get_formatter_by_name(_alias, **options): + """ + Return an instance of a :class:`.Formatter` subclass that has `alias` in its + aliases list. The formatter is given the `options` at its instantiation. + + Will raise :exc:`pygments.util.ClassNotFound` if no formatter with that + alias is found. + """ + cls = find_formatter_class(_alias) + if cls is None: + raise ClassNotFound(f"no formatter found for name {_alias!r}") + return cls(**options) + + +def load_formatter_from_file(filename, formattername="CustomFormatter", **options): + """ + Return a `Formatter` subclass instance loaded from the provided file, relative + to the current directory. + + The file is expected to contain a Formatter class named ``formattername`` + (by default, CustomFormatter). Users should be very careful with the input, because + this method is equivalent to running ``eval()`` on the input file. The formatter is + given the `options` at its instantiation. + + :exc:`pygments.util.ClassNotFound` is raised if there are any errors loading + the formatter. + + .. versionadded:: 2.2 + """ + try: + # This empty dict will contain the namespace for the exec'd file + custom_namespace = {} + with open(filename, 'rb') as f: + exec(f.read(), custom_namespace) + # Retrieve the class `formattername` from that namespace + if formattername not in custom_namespace: + raise ClassNotFound(f'no valid {formattername} class found in {filename}') + formatter_class = custom_namespace[formattername] + # And finally instantiate it with the options + return formatter_class(**options) + except OSError as err: + raise ClassNotFound(f'cannot read {filename}: {err}') + except ClassNotFound: + raise + except Exception as err: + raise ClassNotFound(f'error when loading custom formatter: {err}') + + +def get_formatter_for_filename(fn, **options): + """ + Return a :class:`.Formatter` subclass instance that has a filename pattern + matching `fn`. The formatter is given the `options` at its instantiation. + + Will raise :exc:`pygments.util.ClassNotFound` if no formatter for that filename + is found. + """ + fn = basename(fn) + for modname, name, _, filenames, _ in FORMATTERS.values(): + for filename in filenames: + if _fn_matches(fn, filename): + if name not in _formatter_cache: + _load_formatters(modname) + return _formatter_cache[name](**options) + for _name, cls in find_plugin_formatters(): + for filename in cls.filenames: + if _fn_matches(fn, filename): + return cls(**options) + raise ClassNotFound(f"no formatter found for file name {fn!r}") + + +class _automodule(types.ModuleType): + """Automatically import formatters.""" + + def __getattr__(self, name): + info = FORMATTERS.get(name) + if info: + _load_formatters(info[0]) + cls = _formatter_cache[info[1]] + setattr(self, name, cls) + return cls + raise AttributeError(name) + + +oldmod = sys.modules[__name__] +newmod = _automodule(__name__) +newmod.__dict__.update(oldmod.__dict__) +sys.modules[__name__] = newmod +del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8048047728305bce4184b66f58de13555548a98e GIT binary patch literal 4983 zcmcgwO>^7E8Qujz5~3wpaco)iS2k|iF*7#hahx>6aWk=HyXn+XTFYr%PC6h&EG0-F zK<|Qz#UXPD(_1I`0h!F$onHDEdg-M$&>;@$W2 zdHM0eLY3kA%imVRxigIYlU`;o2QTlV>F?16ti=KzSP^e=bGKTSx!Wz<+?|$#yB(E! z%%3fo#3bGk4S1GWL5C&(-@MJ^b+Yqeu51Zs2XX8^(dxj|N*| z>>cupnT55;*Lpok?-bodSxRP=PSWdxKvwC-W^Xo^DnHhdpUSLcM(TEEb>qz0ijvK&QjqvFQ?KGo z-~WDfJL$<)Ih4Dhj8^Z;JsBl^vbEI*{-$2-@2A^Ie69c1wXTwKGnCgrzOyUU>Yj}E zR-(C${+7hnu)peIRgfsG$?bRbP`#_37sg@gd5!*lcEan%UUBvd zByblUvs}Kyt6U*Munwn@;-c^{)y+-3K;q=~2ohU-;*9x-@9?|qcPr41HR2;{VvX&Q zouchHx7d^A09vZs!siG!Cgs&j zSrw<~^%I42sAVvcRbX(?oywMGEG*ZTW31oFPUO^b& z#30KWtFhlO&YrlbGoc-epIT!+wnmm2Z3Z?Z=nStFmhFoz8B67-@O>P{h;Vu+(IGdM zy73(;QBk*EFDc?KEARQyKx$Q^89IO>hGDZ#7E=%zxr@o9;UG`7di*cq{ry<1l92qpuOG$0aQhy#MLAc~DcVVr|7 zoH5*eRN5(F*PT(RSaHXgmNmTnAW3!yeKBiX2(5k;`iP1qgyy2tVm*na2)iOqQpgU0 zE|0M5^|BF>65#ome6ormBsIc9$vmp_wCWsnWEko)br-24KcBIq(LZ&DzfnVq!JT3UDDwq&rYrlbei<0(maykDp*c1%xLl-co;qo+1Sow>BRc_ z^w>%H$a!WxhignM&OTvpvtP4^>@hS7XQ`FZ-wQgYK&N6T7+X6=!FmjOh*GZqBP8}Cb|>0Xk<1v_)D|hyQf3A)FhJQe2oaa*JYZD~;y8@Az>>t^m3q4^lH8dL zC4*63^f|-t|LAxBjkD?8F~Mh-1h1sP`4CC=g;XlR@kx1LH&@PVASg7_Xpmeqyxk9= z(`HlLXxuQXfv7fzbMQ10szT?Lfrj-P47+OuSKK!xyEJ3^Y;M1aG zyzVe0GzH?clk_EEn0zWbnL`AOe`l4CHtrGQ+1$OS9chA7y^^^H2)4Cw#10fi+-xcT zfB_2{fJ33%b=xJ;;GRGwJsGO(9}c=3K(0v02T;f(AW z%QFKd&LVkUezKI74cfSl1X~%qBX_3)T4IJ)JaS*y7e8sIZom(-jwzmOzjQp`{O1HS z_5W$6LH3648gO0!XJaS^sT!B)#_bnP|NonApmvjOrmPwkk0zYq>;ER-92Otz^*KfL z&kW`u+dqH+vDY@1vPrlHtDrM*j=oKLIPSQH?u+gWz96ZQ&1)0}X;7ire&z z=-mOx-xoP{KXg>xIFca=ic(vn&)h*r(2?ps!olhtAZ2{8K+QIu0Thw>VC%4ZWXOrl zkvUNhmIz9I0S2k+ST_Jzy#{gx!pm$F4LSHF7@SpfQ9~@=MkNt$4pM1CMBT|8Eu(HO z-`BD9W#Ytvy`l*5ysxoHFQQ`*DET`596mfdBb3I>df@WR^^B@KkE)xZR`IfV&wDcP zBhoKsPyz)>2Sv}%h>JsrX`ZNFQB*Sj0Zo$tUG7+Bq|Q|x?{%SW zM;Wj{1C>NK;BMQ-9$QyXNH&T>61CzBe}ptpIQIMS#=M|7|I+38zYC_e$Z9WLLC7wT z$PR%>Q_bbZPZ-J6pt*_&VCDFXLZXSNiYz#*erRm$5e{O`nvp-CY}D#Knl2|%fPt(W z%g^WzF$ZA>g=1D8^aIrEFB41~IJUzb W+okJ-d(vI3UUrvHEs!e_nEw}1S~zC_ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..579846b9112f0d16394e05af90f4f2bfdce04103 GIT binary patch literal 3915 zcmd5&?y6VsRlUEzSN-MQ-p(3+ zfBpMFUA(on_BBnGe{W;*3I15wfJ#fIpJP0;`MtysB&SH1P7`(7V}=Jv=)?j++ZtT0vZ3>Fz(w3>L6$Wmp=CQGNX zi-{ZSlAVH?bU9P8;bsCKu}qfIgdHn&hqJ<7Bor(Q`}MkJ6EO@5W-9HiM;-qn#V}~x zT(OhW74N{n5RPd7gso}cyBr<*kEZ8Tf&^=U+2!@K#{eHFN6 z#Ls@gMx&2x#mkRJBj$L9%sreiAyZ!G?x~Mx0tHYR3{jS4l55IdsNCwB z$pHd)QTL1w$@(0qA6Hb_^`z6?2~lp|> z`T?l2iYM2=E=z#8vWhiQ$~12b<(*@?%qP%l6I!_GW(jz1wYqc7`(MULB8JtGqjI_T)w(2jZ34>Y37yowtA~aeKvk7Np7bVw^}(kG1~I5zr)i&ENR!IM zdAp8uHSvrW+@c?<5}rI)iP2W4?%m5C`>i&D-t*JG)o6Q@MID3yFx|m87>z;m^(#h) zSg{NcIaUd5QfL4yG~0#!z-07%SS#-;X%yMY^FDjVlQGLxDVa`vS`c`9z^1GvIF`92 z=HM3y=PJRqVKlnK8aZV&nqZV@n5Ih9D=-*sfUA-s43KuSp3!d9UVo@CR0#xEqUQNL z70|qV@vSTcT(t_Ek^lUcA4U&{i%G8&JzQSffd*FR5`5DR9-$`wao=+u#6Nw0bqal{ zgU(U>sgGsIkkG!c#X(@2ox&i{9s*aNscSNZjb=K9lfX=lxwpB$*0sAh)EalJl(!Us zaG`T!P^xevA{Vu4+-h@8jx`3kmQN^cez*!OX8J|jGgW{R+cWC)L926c$e+HmLp-9* z?LYb-nP}*$9_`O88Jb%9qn5+{{)a_03pb5Bcm(VI`Q5F zFb?+q?7qE#eB7>#u8g9abV_$bN;Pz&Al2_Zj@}I4D?t)j673%r63eEH z+P!p3TJ2ulWQ(;Yvou06FR1h6T9DqEG0yXCu?F_wygBJP0jm&jLcse-au^m+Y8|a|=;2 zEFbiLHL%LbgV_ybP^oJ00$f5H@Ptp&K|22=9lXGIQ$-hxYBJsasL-O$<;V2ZVSkLr L#`=H#`}cnU1p7&e literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17cf2bd3112c4e73bcdc6677c7c5bda64e472cb8 GIT binary patch literal 3092 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 OowntBh}e0Bv4ok{!Uw$quWnNF1t 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42d6e1f5f18d4d4e38202b0f22258e1cb6ee8426 GIT binary patch literal 18411 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 R>yuwGzGD3NrC zuY^&sh&Kq3Lymh0aj6PS&1v6O95q6XvV^eIJ9cL%-o@A%+o@PGYGi-)^#LlvF ze>xa^#Llw|-)X_wyBfR5?C-D|ZoDvqbF8_nHD|Z}QTOrmHai*bmc`uDTx3yMa#74B zB36CB{_s;*op*NReT?+)KU(nfe%b4nRquS9_cle+SuN-Ng`KK@^~#kWUA}td>NUT; z>6feAe>j}oFL{(*@(a%Wn_oS?xAbUvPE9hOCNb|8eBS?J`Bv5c;Qqqx#pT;-R>sRH zNsGDqYYK;^E^E71HS5y-l5alG1(VidlyT`U$ zG>tdL?5sGUs#~?$p`o)|`(`UuI}GR;8%_`Hp*V>VuW>@P*9H-jh(*LE;t+9(ctmPM z=-9s=Bhnx;PGo|J^6)`vKc|h6wsNjGgdTB>h_aRJ7Dqq=Cl^t-!^Kf*G%6#j#Rc`0 zVpMrYd0HGGnVRC#PKYVeLLLdm-&x(BBV$_4&O_^=ETq z#vtBr|5+11C6(W{R`ZOv_!eJJc-p$fpYb&B(Yb6rh}uP~w^^>{-OIg?FF!@Vw-bK3 z6gW9tw4U+qvsRk4haw-(ecCFp`Z%rh>H=z-b6;Ex_7bZ&TJ# zR@?_LsPI8Fy`fvWH+<_mrPsttBlQ?e4@_nR76a`V zf!#s)F^k|ga2XN^N{@N0hO)-$*oiSg5gjc<+X$}uB_U7YKTpb41+L1e0LYC#@5a2S zQ1&q(bcNukC^u6cGXAu<*j({54j&>LU@HN&?6^S<;Qt3x(gtUUo#@@3LKe7qP0SpCXTpsB8`m zT2XE(;#HpUJri#FEQ!g|NNoC9)a%h?%AtO_+2aM-cdai#}v{`&~5}KPN_5z@-dz4orgk~VCqYJ z#Y2!%Thj;HQ+=TSLD#hsP(gPnW_Ao_yg)Fo8v`vg2Lvn@(_R_9YbD?*bQH&C&MWG7 znG5PM0vK;m8g1lQ)mrlh5r~@(#X}k%aLI-!Gyv(AG3@b^zuYT=t1(Xd#^V5^h6}enfi(cPfXN72<9= z=dN61^M2D*<_p>ZBr13zYZOBY&Q$1EUU>q;M%F+nS==C^k`O8dRXp7~METG%JkPTio9D$Z z!3|C!!_EyeEHdf)KMvIip8lYa^0J^f6m)z9fsdf*6%>ntf>O|xPtcW5(3MZnl}}E6 zP+PxD(h4B3_$W=Wc=w^;J(0)KdQ7`#J)w)g`$YxK(!0~|s*E6Oiv;j=m6!HH8Wr%< z?QbZX0WxIm7LRdPq<;YK7m-M!cFLu<05B;d{5aHZmbm`3`?zwNkE4!sZ}F5P4NC7m z{!s+g+8Ke8|Z(HA>IG7&OrbFe*f(p@ecn7F{Bx% literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51bbb59297da7dd2f5b47810e878814b451c574d GIT binary patch literal 13933 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 M$xnSw`kb`zzm%R-VgLXD literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e686ce919fee71416a1b4c26515f7bd6337a4d0 GIT binary patch literal 4795 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 L=izVN&fEV7pvz!E literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7e89459d7e70b8f2e8c857d42e9ffe6b328690c GIT binary patch literal 6348 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 E0OkfjZvX%Q literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fffb81243ed4df92ba3ef6ded7d7f80eab79b07 GIT binary patch literal 9238 zcmd5?O>7)ja_+zB>G>fg(KPkr?55Y2R+@!4q$sUcrnO#LKeUZ?W&@G7wUZ5Idgk>E zN9^eyzHZ7QF|-}fo8+(v_TWQ~B#^`Ul3ex_1j!``_Tc0cAV>!Y0t7h_2o|{nNEShy zud2IehLo&qa!H2hH}$IDtA1Touj;Eee0OT9sNnO*f4K1whkwY)%YWsS< zfPPV!=$mc<{h}zXDV4JGXR4wUZF~kh-M;HbVYL&;z7s{R46A7RJ>T(`zIl_!eE9rN zkq512^KD47F?GHjc%U+2cCZ}{wj=w4UbJDqQCoZ44&AM7*KfOFHIeY(%{%i^F#mA< z{)1n$lgaJCe~cCN0^gRH-}Bwjc7!vCTw!-)(6^tE_~c>!V_ruT1fG3mBZ>yg3kx0T zK6z3V?gEdSr-6PDx#5CvA}3*Hlb5QIBde|5r3#B_?bz=JUZ?B$U3*pb!uCeG%JF+< zE4!B7S#Ae|9og${M9cQwjI@^)7jMolEiT=#Au`%nhwid{XYF2c|DCt*-mk6QXUz&Xa(W(gb_8EhDr#I9tq7f5I}mONqUiWx zkChn4v+HAnjt0jUGVZ$EcJGp{M)%qzdh(M)X9ZVy6OF6XRXC|8ri3nxPt>|juK0;k zHxfsz=iq`x^i5F`Wqjv_3a6Wnr|*YtXW*`-cCW3rO^C}rL~FS2;tF3zGgOC4Lm6sl z)uE178yaZ!VGgY!lr`luwN}Z+TBjRpt!`|z0#C$+ZC|+3gR8`5FLI#FiWwWB>vd!S zLM81)S)!&)%@mr?6nP3?zxdsSjiB!?xV!FV&-E7WxsP2h7|`}uc*kjl3xl0#Bk<=3 z*XQ9^ZmZ|cN789;x^m&M>px!bdaZQG_3EvKuot=W1Kf7H@V7y4u+YFL5lDKJwChcV(qA9GfHopoYfWsSk;)z8jquK~NqmK?MkBgW1Zy>tL9!&lS5pc60TSP_b zrYYZv-~x}71D%$BsGV2#G@%bQ+_@#lA&jAVkQ-{`gg?aHLseD$Iq*z!M^T`;b4o`i zKIlA4|5Kt^`cGJTeo{Kgi>#8rLyxAvJLrHG*^_2>$hU{o4eYj-VwP%tlhX3 zYm3)#-N1FdGL?8Xjg_R-I4|9S=d@k+^VoC-19%=Rg(&C?-T|>5Ml#lWel*?&@;v5N zbT+m;M{;yn?WDcS98?;Z-t)T)7!_VYqo{^j#NQ08Z)&sJT+-8QP$uZ?+I{JSUrb6n z<{k99ab`~&V6Qkz<-O^Y@86|6c|mn1p;5jFB9YHVu0M$OCAyn?1u zNKE*3VqB(X9HuT&cNDAGNb)T4C}vG;#4{1%cwQZ`5Si?SHji*FB zWfJl)Hpv4JnMrs{on!U#7QyDyn0J+VT4sXqb=)zy&B?d;Ub-z0%v5bg4etIbT>RC# zwzycyCwgHWj8*bWXrnCM(2Y_zeIIKLDOzQ2nz(9FeaamtXCWrmR##Um7KLNq>AQ_a zTx>M@f!OwlE;kxm+m4sqk!K)1F%E@737DtIMQX-oVv3d_DeRH)Ika19F>mFp3)VFA z+u$GjkjhtZF*+P2UrM_gQZ9T~01Fj~^A@GP#9o-lB4kClg~=PF~qF_FfU8Dv)p_GPs`k9 zjV#rqS#}?*#KLGz4q50ag-pKnjqI)0@fuu~ByLT+IUbf^`!?Llo)m_q zkXq}d%yrJ-!P1UlByJe33qX?>R>atBSMJOXO%08;;+sv;#)q0*yP=hhIo zvzbRfC$y9@fnO6I_y^XZLdf)>z~AaYks|sbl|3b4qAgRYQ_0t2(+{}BDfzSo(;~<6 zgQU!f^QmrE8E^m<5`^Ns6qmXFO~64I7h4ER4Q!6~MqFl7r+G5Y!`}ReGKldr3h1Q| znQ64Pd!9&f20LRs)j+D<9zY28U>NINH^R>@mQ+bAh82mN8+0k{0s^bKZAiprx1{zRh$@KnxM1NfW~KK0nuqmz;jPcj6Y4ZII{<=m&I@Ff!lDMz9PT zc1%Xf7|URW$xl3LVDu6B@6jJo^EYT%Z~!1cK7nLx7Je|y={>E^g|e&`n<^`2%+v7Lfgwlia>qN&Q)iJQimp-G!LiD3)JW5 zlsyZFI(e$OKo{Ved8)ZU7l+o+npK8{VS(InSR5AFElb1F(AZSu%+TD+4NH5**32-6 zm}u5|x~V62Kl)FsfmO^^7DCP%F-W_Wa#GX2ON{rZp@IPDxJNmoI-*krYb zu_i^VbwyokiC7gUR31+^+FlS+80C!qCzv^^v=~eql+J-B9zp_&GX#&Jd8i+M7A%mf z9kC0Jc8#RE5H7roFZl*F6YPoOXq(lHmZ2l53SMp&wbmTHX37W6 zGYp=AZ6TkecR(k1@Z6mI5$0i|$R7a~=nab1d3gX{9=rqQS>P4qPru5nx0GC8srYH@+EBwIkX@S1U{g_G};6X#pqqJ*NLJdSII>(*T=6>mt5mBBRyw~qMd;o8?Z(vsi?;# zG&|c02LA)D@at&E;3jq%ojjf9vq$;{Z*&1ne6joTvkgB9T#ju&!cHU0-#}-GXosUo z^;ZUGV7W&{K9Y#KahUfSQk~N#FaYWj+E`^sCZ-`|(R*QVdhwyX< zLgU;A66eHYe6$^PdY*gI)u$RfEJaiQiishIV!Yy)z1ZZ0wvHLuhv?CZQKE?m9?)UC zcBro@l41_lL+>A(NezW0MFZnfas&`;OB^i*;G9^e+yd|4afLKn!Rg8~m7WMawkSH< z86Ech3p%7pc^bQbmIVYfB;@lK*mXLRjEW?n%g={7`T6c|W26@8Lwz6n4);wY8sh+o z=ukzPu>=Ae9SURiO{AKF^oAM^i_KvH=_P-bEyd6JaQv*T2Q(%@h@dUrt3OHSac?|V z7<;+*l`R{=)kJXpSrS}_`lCFB*Dd=YF06BwXB+e;TeJMUe;z+0^^|ql$BQ3*fe+4{ z7(mMbIC8W>u-gV+FOojEvGrYdhofk04p4;pQKgtjwHrdzYT$0b}X4$|-pbvLyi+c^wT(hlU8+IAt@aw2kxlA;&?!jSI9t zxY|ul+z9({9!bK_3Uyg)>+D%Et=FbD1HB@Cv0?Ho16f!oaq1E5!?8vDLaK) zs$NUZ9sZAxBW!%?XEOJs!-u;B0>&o+*KYPlCjmt3XpVsc!aQ`{#l`WMHZsRgcDah0 z?8mYDvFDM)ehbsG7&-sc5)yBKEvrx0+Q)JfInf9r5_kpd!%t=-*rx&Z%^@;rh)h?K z$n<@M775S?oC2Vy0pKwjNofOo;vv2!^&*_rsL3W|C-oDfJADl2q=Q=(GkH_mP*PCy z%ON+M8F=n8>4G$)qcyvTE0?%fOqn?4Gt~}3G zCy`VQx#)O2mwWW=52^WxnvbdB*v)UXLf!Go#&Mcl{1PpX00`prR2rvst5~a)SZe*N z_;J0O{5deE2rYI@`` tags. By default, the content is enclosed in a ``

        `` tag, itself wrapped in a ``
        `` tag (but see the `nowrap` option). The ``
        ``'s CSS class can be set by the `cssclass` option."), + 'IRCFormatter': ('pygments.formatters.irc', 'IRC', ('irc', 'IRC'), (), 'Format tokens with IRC color sequences'), + 'ImageFormatter': ('pygments.formatters.img', 'img', ('img', 'IMG', 'png'), ('*.png',), 'Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'), + 'JpgImageFormatter': ('pygments.formatters.img', 'img_jpg', ('jpg', 'jpeg'), ('*.jpg',), 'Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'), + 'LatexFormatter': ('pygments.formatters.latex', 'LaTeX', ('latex', 'tex'), ('*.tex',), 'Format tokens as LaTeX code. This needs the `fancyvrb` and `color` standard packages.'), + 'NullFormatter': ('pygments.formatters.other', 'Text only', ('text', 'null'), ('*.txt',), 'Output the text unchanged without any formatting.'), + 'PangoMarkupFormatter': ('pygments.formatters.pangomarkup', 'Pango Markup', ('pango', 'pangomarkup'), (), 'Format tokens as Pango Markup code. It can then be rendered to an SVG.'), + 'RawTokenFormatter': ('pygments.formatters.other', 'Raw tokens', ('raw', 'tokens'), ('*.raw',), 'Format tokens as a raw representation for storing token streams.'), + 'RtfFormatter': ('pygments.formatters.rtf', 'RTF', ('rtf',), ('*.rtf',), 'Format tokens as RTF markup. This formatter automatically outputs full RTF documents with color information and other useful stuff. Perfect for Copy and Paste into Microsoft(R) Word(R) documents.'), + 'SvgFormatter': ('pygments.formatters.svg', 'SVG', ('svg',), ('*.svg',), 'Format tokens as an SVG graphics file. This formatter is still experimental. Each line of code is a ```` element with explicit ``x`` and ``y`` coordinates containing ```` elements with the individual token styles.'), + 'Terminal256Formatter': ('pygments.formatters.terminal256', 'Terminal256', ('terminal256', 'console256', '256'), (), 'Format tokens with ANSI color sequences, for output in a 256-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'), + 'TerminalFormatter': ('pygments.formatters.terminal', 'Terminal', ('terminal', 'console'), (), 'Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly.'), + 'TerminalTrueColorFormatter': ('pygments.formatters.terminal256', 'TerminalTrueColor', ('terminal16m', 'console16m', '16m'), (), 'Format tokens with ANSI color sequences, for output in a true-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'), + 'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.'), +} diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/bbcode.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/bbcode.py new file mode 100644 index 000000000..5a05bd961 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/bbcode.py @@ -0,0 +1,108 @@ +""" + pygments.formatters.bbcode + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + BBcode formatter. + + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + + +from pip._vendor.pygments.formatter import Formatter +from pip._vendor.pygments.util import get_bool_opt + +__all__ = ['BBCodeFormatter'] + + +class BBCodeFormatter(Formatter): + """ + Format tokens with BBcodes. These formatting codes are used by many + bulletin boards, so you can highlight your sourcecode with pygments before + posting it there. + + This formatter has no support for background colors and borders, as there + are no common BBcode tags for that. + + Some board systems (e.g. phpBB) don't support colors in their [code] tag, + so you can't use the highlighting together with that tag. + Text in a [code] tag usually is shown with a monospace font (which this + formatter can do with the ``monofont`` option) and no spaces (which you + need for indentation) are removed. + + Additional options accepted: + + `style` + The style to use, can be a string or a Style subclass (default: + ``'default'``). + + `codetag` + If set to true, put the output into ``[code]`` tags (default: + ``false``) + + `monofont` + If set to true, add a tag to show the code with a monospace font + (default: ``false``). + """ + name = 'BBCode' + aliases = ['bbcode', 'bb'] + filenames = [] + + def __init__(self, **options): + Formatter.__init__(self, **options) + self._code = get_bool_opt(options, 'codetag', False) + self._mono = get_bool_opt(options, 'monofont', False) + + self.styles = {} + self._make_styles() + + def _make_styles(self): + for ttype, ndef in self.style: + start = end = '' + if ndef['color']: + start += '[color=#{}]'.format(ndef['color']) + end = '[/color]' + end + if ndef['bold']: + start += '[b]' + end = '[/b]' + end + if ndef['italic']: + start += '[i]' + end = '[/i]' + end + if ndef['underline']: + start += '[u]' + end = '[/u]' + end + # there are no common BBcodes for background-color and border + + self.styles[ttype] = start, end + + def format_unencoded(self, tokensource, outfile): + if self._code: + outfile.write('[code]') + if self._mono: + outfile.write('[font=monospace]') + + lastval = '' + lasttype = None + + for ttype, value in tokensource: + while ttype not in self.styles: + ttype = ttype.parent + if ttype == lasttype: + lastval += value + else: + if lastval: + start, end = self.styles[lasttype] + outfile.write(''.join((start, lastval, end))) + lastval = value + lasttype = ttype + + if lastval: + start, end = self.styles[lasttype] + outfile.write(''.join((start, lastval, end))) + + if self._mono: + outfile.write('[/font]') + if self._code: + outfile.write('[/code]') + if self._code or self._mono: + outfile.write('\n') diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/groff.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/groff.py new file mode 100644 index 000000000..5c8a958f8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/groff.py @@ -0,0 +1,170 @@ +""" + pygments.formatters.groff + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Formatter for groff output. + + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import math +from pip._vendor.pygments.formatter import Formatter +from pip._vendor.pygments.util import get_bool_opt, get_int_opt + +__all__ = ['GroffFormatter'] + + +class GroffFormatter(Formatter): + """ + Format tokens with groff escapes to change their color and font style. + + .. versionadded:: 2.11 + + Additional options accepted: + + `style` + The style to use, can be a string or a Style subclass (default: + ``'default'``). + + `monospaced` + If set to true, monospace font will be used (default: ``true``). + + `linenos` + If set to true, print the line numbers (default: ``false``). + + `wrap` + Wrap lines to the specified number of characters. Disabled if set to 0 + (default: ``0``). + """ + + name = 'groff' + aliases = ['groff','troff','roff'] + filenames = [] + + def __init__(self, **options): + Formatter.__init__(self, **options) + + self.monospaced = get_bool_opt(options, 'monospaced', True) + self.linenos = get_bool_opt(options, 'linenos', False) + self._lineno = 0 + self.wrap = get_int_opt(options, 'wrap', 0) + self._linelen = 0 + + self.styles = {} + self._make_styles() + + + def _make_styles(self): + regular = '\\f[CR]' if self.monospaced else '\\f[R]' + bold = '\\f[CB]' if self.monospaced else '\\f[B]' + italic = '\\f[CI]' if self.monospaced else '\\f[I]' + + for ttype, ndef in self.style: + start = end = '' + if ndef['color']: + start += '\\m[{}]'.format(ndef['color']) + end = '\\m[]' + end + if ndef['bold']: + start += bold + end = regular + end + if ndef['italic']: + start += italic + end = regular + end + if ndef['bgcolor']: + start += '\\M[{}]'.format(ndef['bgcolor']) + end = '\\M[]' + end + + self.styles[ttype] = start, end + + + def _define_colors(self, outfile): + colors = set() + for _, ndef in self.style: + if ndef['color'] is not None: + colors.add(ndef['color']) + + for color in sorted(colors): + outfile.write('.defcolor ' + color + ' rgb #' + color + '\n') + + + def _write_lineno(self, outfile): + self._lineno += 1 + outfile.write("%s% 4d " % (self._lineno != 1 and '\n' or '', self._lineno)) + + + def _wrap_line(self, line): + length = len(line.rstrip('\n')) + space = ' ' if self.linenos else '' + newline = '' + + if length > self.wrap: + for i in range(0, math.floor(length / self.wrap)): + chunk = line[i*self.wrap:i*self.wrap+self.wrap] + newline += (chunk + '\n' + space) + remainder = length % self.wrap + if remainder > 0: + newline += line[-remainder-1:] + self._linelen = remainder + elif self._linelen + length > self.wrap: + newline = ('\n' + space) + line + self._linelen = length + else: + newline = line + self._linelen += length + + return newline + + + def _escape_chars(self, text): + text = text.replace('\\', '\\[u005C]'). \ + replace('.', '\\[char46]'). \ + replace('\'', '\\[u0027]'). \ + replace('`', '\\[u0060]'). \ + replace('~', '\\[u007E]') + copy = text + + for char in copy: + if len(char) != len(char.encode()): + uni = char.encode('unicode_escape') \ + .decode()[1:] \ + .replace('x', 'u00') \ + .upper() + text = text.replace(char, '\\[u' + uni[1:] + ']') + + return text + + + def format_unencoded(self, tokensource, outfile): + self._define_colors(outfile) + + outfile.write('.nf\n\\f[CR]\n') + + if self.linenos: + self._write_lineno(outfile) + + for ttype, value in tokensource: + while ttype not in self.styles: + ttype = ttype.parent + start, end = self.styles[ttype] + + for line in value.splitlines(True): + if self.wrap > 0: + line = self._wrap_line(line) + + if start and end: + text = self._escape_chars(line.rstrip('\n')) + if text != '': + outfile.write(''.join((start, text, end))) + else: + outfile.write(self._escape_chars(line.rstrip('\n'))) + + if line.endswith('\n'): + if self.linenos: + self._write_lineno(outfile) + self._linelen = 0 + else: + outfile.write('\n') + self._linelen = 0 + + outfile.write('\n.fi') diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/html.py b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/html.py new file mode 100644 index 000000000..7aa938f51 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/pygments/formatters/html.py @@ -0,0 +1,987 @@ +""" + pygments.formatters.html + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Formatter for HTML output. + + :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import functools +import os +import sys +import os.path +from io import StringIO + +from pip._vendor.pygments.formatter import Formatter +from pip._vendor.pygments.token import Token, Text, STANDARD_TYPES +from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt + +try: + import ctags +except ImportError: + ctags = None + +__all__ = ['HtmlFormatter'] + + +_escape_html_table = { + ord('&'): '&', + ord('<'): '<', + ord('>'): '>', + ord('"'): '"', + ord("'"): ''', +} + + +def escape_html(text, table=_escape_html_table): + """Escape &, <, > as well as single and double quotes for HTML.""" + return text.translate(table) + + +def webify(color): + if color.startswith('calc') or color.startswith('var'): + return color + else: + return '#' + color + + +def _get_ttype_class(ttype): + fname = STANDARD_TYPES.get(ttype) + if fname: + return fname + aname = '' + while fname is None: + aname = '-' + ttype[-1] + aname + ttype = ttype.parent + fname = STANDARD_TYPES.get(ttype) + return fname + aname + + +CSSFILE_TEMPLATE = '''\ +/* +generated by Pygments +Copyright 2006-2024 by the Pygments team. +Licensed under the BSD license, see LICENSE for details. +*/ +%(styledefs)s +''' + +DOC_HEADER = '''\ + + + + + %(title)s + + + + +

        %(title)s

        + +''' + +DOC_HEADER_EXTERNALCSS = '''\ + + + + + %(title)s + + + + +

        %(title)s

        + +''' + +DOC_FOOTER = '''\ + + +''' + + +class HtmlFormatter(Formatter): + r""" + Format tokens as HTML 4 ```` tags. By default, the content is enclosed + in a ``
        `` tag, itself wrapped in a ``
        `` tag (but see the `nowrap` option). + The ``
        ``'s CSS class can be set by the `cssclass` option. + + If the `linenos` option is set to ``"table"``, the ``
        `` is
        +    additionally wrapped inside a ```` which has one row and two
        +    cells: one containing the line numbers and one containing the code.
        +    Example:
        +
        +    .. sourcecode:: html
        +
        +        
        +
        + + +
        +
        1
        +            2
        +
        +
        def foo(bar):
        +              pass
        +            
        +
        + + (whitespace added to improve clarity). + + A list of lines can be specified using the `hl_lines` option to make these + lines highlighted (as of Pygments 0.11). + + With the `full` option, a complete HTML 4 document is output, including + the style definitions inside a `` + + +
        {code}
        + + +""" + +CONSOLE_SVG_FORMAT = """\ + + + + + + + + + {lines} + + + {chrome} + + {backgrounds} + + {matrix} + + + +""" + +_SVG_FONT_FAMILY = "Rich Fira Code" +_SVG_CLASSES_PREFIX = "rich-svg" diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_extension.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_extension.py new file mode 100644 index 000000000..cbd6da9be --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_extension.py @@ -0,0 +1,10 @@ +from typing import Any + + +def load_ipython_extension(ip: Any) -> None: # pragma: no cover + # prevent circular import + from pip._vendor.rich.pretty import install + from pip._vendor.rich.traceback import install as tr_install + + install() + tr_install() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_fileno.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_fileno.py new file mode 100644 index 000000000..b17ee6511 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_fileno.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import IO, Callable + + +def get_fileno(file_like: IO[str]) -> int | None: + """Get fileno() from a file, accounting for poorly implemented file-like objects. + + Args: + file_like (IO): A file-like object. + + Returns: + int | None: The result of fileno if available, or None if operation failed. + """ + fileno: Callable[[], int] | None = getattr(file_like, "fileno", None) + if fileno is not None: + try: + return fileno() + except Exception: + # `fileno` is documented as potentially raising a OSError + # Alas, from the issues, there are so many poorly implemented file-like objects, + # that `fileno()` can raise just about anything. + return None + return None diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_inspect.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_inspect.py new file mode 100644 index 000000000..e87698d1f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_inspect.py @@ -0,0 +1,268 @@ +import inspect +from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature +from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union + +from .console import Group, RenderableType +from .control import escape_control_codes +from .highlighter import ReprHighlighter +from .jupyter import JupyterMixin +from .panel import Panel +from .pretty import Pretty +from .table import Table +from .text import Text, TextType + + +def _first_paragraph(doc: str) -> str: + """Get the first paragraph from a docstring.""" + paragraph, _, _ = doc.partition("\n\n") + return paragraph + + +class Inspect(JupyterMixin): + """A renderable to inspect any Python Object. + + Args: + obj (Any): An object to inspect. + title (str, optional): Title to display over inspect result, or None use type. Defaults to None. + help (bool, optional): Show full help text rather than just first paragraph. Defaults to False. + methods (bool, optional): Enable inspection of callables. Defaults to False. + docs (bool, optional): Also render doc strings. Defaults to True. + private (bool, optional): Show private attributes (beginning with underscore). Defaults to False. + dunder (bool, optional): Show attributes starting with double underscore. Defaults to False. + sort (bool, optional): Sort attributes alphabetically. Defaults to True. + all (bool, optional): Show all attributes. Defaults to False. + value (bool, optional): Pretty print value of object. Defaults to True. + """ + + def __init__( + self, + obj: Any, + *, + title: Optional[TextType] = None, + help: bool = False, + methods: bool = False, + docs: bool = True, + private: bool = False, + dunder: bool = False, + sort: bool = True, + all: bool = True, + value: bool = True, + ) -> None: + self.highlighter = ReprHighlighter() + self.obj = obj + self.title = title or self._make_title(obj) + if all: + methods = private = dunder = True + self.help = help + self.methods = methods + self.docs = docs or help + self.private = private or dunder + self.dunder = dunder + self.sort = sort + self.value = value + + def _make_title(self, obj: Any) -> Text: + """Make a default title.""" + title_str = ( + str(obj) + if (isclass(obj) or callable(obj) or ismodule(obj)) + else str(type(obj)) + ) + title_text = self.highlighter(title_str) + return title_text + + def __rich__(self) -> Panel: + return Panel.fit( + Group(*self._render()), + title=self.title, + border_style="scope.border", + padding=(0, 1), + ) + + def _get_signature(self, name: str, obj: Any) -> Optional[Text]: + """Get a signature for a callable.""" + try: + _signature = str(signature(obj)) + ":" + except ValueError: + _signature = "(...)" + except TypeError: + return None + + source_filename: Optional[str] = None + try: + source_filename = getfile(obj) + except (OSError, TypeError): + # OSError is raised if obj has no source file, e.g. when defined in REPL. + pass + + callable_name = Text(name, style="inspect.callable") + if source_filename: + callable_name.stylize(f"link file://{source_filename}") + signature_text = self.highlighter(_signature) + + qualname = name or getattr(obj, "__qualname__", name) + + # If obj is a module, there may be classes (which are callable) to display + if inspect.isclass(obj): + prefix = "class" + elif inspect.iscoroutinefunction(obj): + prefix = "async def" + else: + prefix = "def" + + qual_signature = Text.assemble( + (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"), + (qualname, "inspect.callable"), + signature_text, + ) + + return qual_signature + + def _render(self) -> Iterable[RenderableType]: + """Render object.""" + + def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]: + key, (_error, value) = item + return (callable(value), key.strip("_").lower()) + + def safe_getattr(attr_name: str) -> Tuple[Any, Any]: + """Get attribute or any exception.""" + try: + return (None, getattr(obj, attr_name)) + except Exception as error: + return (error, None) + + obj = self.obj + keys = dir(obj) + total_items = len(keys) + if not self.dunder: + keys = [key for key in keys if not key.startswith("__")] + if not self.private: + keys = [key for key in keys if not key.startswith("_")] + not_shown_count = total_items - len(keys) + items = [(key, safe_getattr(key)) for key in keys] + if self.sort: + items.sort(key=sort_items) + + items_table = Table.grid(padding=(0, 1), expand=False) + items_table.add_column(justify="right") + add_row = items_table.add_row + highlighter = self.highlighter + + if callable(obj): + signature = self._get_signature("", obj) + if signature is not None: + yield signature + yield "" + + if self.docs: + _doc = self._get_formatted_doc(obj) + if _doc is not None: + doc_text = Text(_doc, style="inspect.help") + doc_text = highlighter(doc_text) + yield doc_text + yield "" + + if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)): + yield Panel( + Pretty(obj, indent_guides=True, max_length=10, max_string=60), + border_style="inspect.value.border", + ) + yield "" + + for key, (error, value) in items: + key_text = Text.assemble( + ( + key, + "inspect.attr.dunder" if key.startswith("__") else "inspect.attr", + ), + (" =", "inspect.equals"), + ) + if error is not None: + warning = key_text.copy() + warning.stylize("inspect.error") + add_row(warning, highlighter(repr(error))) + continue + + if callable(value): + if not self.methods: + continue + + _signature_text = self._get_signature(key, value) + if _signature_text is None: + add_row(key_text, Pretty(value, highlighter=highlighter)) + else: + if self.docs: + docs = self._get_formatted_doc(value) + if docs is not None: + _signature_text.append("\n" if "\n" in docs else " ") + doc = highlighter(docs) + doc.stylize("inspect.doc") + _signature_text.append(doc) + + add_row(key_text, _signature_text) + else: + add_row(key_text, Pretty(value, highlighter=highlighter)) + if items_table.row_count: + yield items_table + elif not_shown_count: + yield Text.from_markup( + f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] " + f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options." + ) + + def _get_formatted_doc(self, object_: Any) -> Optional[str]: + """ + Extract the docstring of an object, process it and returns it. + The processing consists in cleaning up the doctring's indentation, + taking only its 1st paragraph if `self.help` is not True, + and escape its control codes. + + Args: + object_ (Any): the object to get the docstring from. + + Returns: + Optional[str]: the processed docstring, or None if no docstring was found. + """ + docs = getdoc(object_) + if docs is None: + return None + docs = cleandoc(docs).strip() + if not self.help: + docs = _first_paragraph(docs) + return escape_control_codes(docs) + + +def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]: + """Returns the MRO of an object's class, or of the object itself if it's a class.""" + if not hasattr(obj, "__mro__"): + # N.B. we cannot use `if type(obj) is type` here because it doesn't work with + # some types of classes, such as the ones that use abc.ABCMeta. + obj = type(obj) + return getattr(obj, "__mro__", ()) + + +def get_object_types_mro_as_strings(obj: object) -> Collection[str]: + """ + Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class. + + Examples: + `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']` + """ + return [ + f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}' + for type_ in get_object_types_mro(obj) + ] + + +def is_object_one_of_types( + obj: object, fully_qualified_types_names: Collection[str] +) -> bool: + """ + Returns `True` if the given object's class (or the object itself, if it's a class) has one of the + fully qualified names in its MRO. + """ + for type_name in get_object_types_mro_as_strings(obj): + if type_name in fully_qualified_types_names: + return True + return False diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_log_render.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_log_render.py new file mode 100644 index 000000000..fc16c8443 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_log_render.py @@ -0,0 +1,94 @@ +from datetime import datetime +from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable + + +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import Console, ConsoleRenderable, RenderableType + from .table import Table + +FormatTimeCallable = Callable[[datetime], Text] + + +class LogRender: + def __init__( + self, + show_time: bool = True, + show_level: bool = False, + show_path: bool = True, + time_format: Union[str, FormatTimeCallable] = "[%x %X]", + omit_repeated_times: bool = True, + level_width: Optional[int] = 8, + ) -> None: + self.show_time = show_time + self.show_level = show_level + self.show_path = show_path + self.time_format = time_format + self.omit_repeated_times = omit_repeated_times + self.level_width = level_width + self._last_time: Optional[Text] = None + + def __call__( + self, + console: "Console", + renderables: Iterable["ConsoleRenderable"], + log_time: Optional[datetime] = None, + time_format: Optional[Union[str, FormatTimeCallable]] = None, + level: TextType = "", + path: Optional[str] = None, + line_no: Optional[int] = None, + link_path: Optional[str] = None, + ) -> "Table": + from .containers import Renderables + from .table import Table + + output = Table.grid(padding=(0, 1)) + output.expand = True + if self.show_time: + output.add_column(style="log.time") + if self.show_level: + output.add_column(style="log.level", width=self.level_width) + output.add_column(ratio=1, style="log.message", overflow="fold") + if self.show_path and path: + output.add_column(style="log.path") + row: List["RenderableType"] = [] + if self.show_time: + log_time = log_time or console.get_datetime() + time_format = time_format or self.time_format + if callable(time_format): + log_time_display = time_format(log_time) + else: + log_time_display = Text(log_time.strftime(time_format)) + if log_time_display == self._last_time and self.omit_repeated_times: + row.append(Text(" " * len(log_time_display))) + else: + row.append(log_time_display) + self._last_time = log_time_display + if self.show_level: + row.append(level) + + row.append(Renderables(renderables)) + if self.show_path and path: + path_text = Text() + path_text.append( + path, style=f"link file://{link_path}" if link_path else "" + ) + if line_no: + path_text.append(":") + path_text.append( + f"{line_no}", + style=f"link file://{link_path}#{line_no}" if link_path else "", + ) + row.append(path_text) + + output.add_row(*row) + return output + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + + c = Console() + c.print("[on blue]Hello", justify="right") + c.log("[on blue]hello", justify="right") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_loop.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_loop.py new file mode 100644 index 000000000..01c6cafbe --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_loop.py @@ -0,0 +1,43 @@ +from typing import Iterable, Tuple, TypeVar + +T = TypeVar("T") + + +def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for first value.""" + iter_values = iter(values) + try: + value = next(iter_values) + except StopIteration: + return + yield True, value + for value in iter_values: + yield False, value + + +def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value + + +def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_null_file.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_null_file.py new file mode 100644 index 000000000..6ae05d3e2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_null_file.py @@ -0,0 +1,69 @@ +from types import TracebackType +from typing import IO, Iterable, Iterator, List, Optional, Type + + +class NullFile(IO[str]): + def close(self) -> None: + pass + + def isatty(self) -> bool: + return False + + def read(self, __n: int = 1) -> str: + return "" + + def readable(self) -> bool: + return False + + def readline(self, __limit: int = 1) -> str: + return "" + + def readlines(self, __hint: int = 1) -> List[str]: + return [] + + def seek(self, __offset: int, __whence: int = 1) -> int: + return 0 + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + return 0 + + def truncate(self, __size: Optional[int] = 1) -> int: + return 0 + + def writable(self) -> bool: + return False + + def writelines(self, __lines: Iterable[str]) -> None: + pass + + def __next__(self) -> str: + return "" + + def __iter__(self) -> Iterator[str]: + return iter([""]) + + def __enter__(self) -> IO[str]: + return self + + def __exit__( + self, + __t: Optional[Type[BaseException]], + __value: Optional[BaseException], + __traceback: Optional[TracebackType], + ) -> None: + pass + + def write(self, text: str) -> int: + return 0 + + def flush(self) -> None: + pass + + def fileno(self) -> int: + return -1 + + +NULL_FILE = NullFile() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_palettes.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_palettes.py new file mode 100644 index 000000000..3c748d33e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_palettes.py @@ -0,0 +1,309 @@ +from .palette import Palette + + +# Taken from https://en.wikipedia.org/wiki/ANSI_escape_code (Windows 10 column) +WINDOWS_PALETTE = Palette( + [ + (12, 12, 12), + (197, 15, 31), + (19, 161, 14), + (193, 156, 0), + (0, 55, 218), + (136, 23, 152), + (58, 150, 221), + (204, 204, 204), + (118, 118, 118), + (231, 72, 86), + (22, 198, 12), + (249, 241, 165), + (59, 120, 255), + (180, 0, 158), + (97, 214, 214), + (242, 242, 242), + ] +) + +# # The standard ansi colors (including bright variants) +STANDARD_PALETTE = Palette( + [ + (0, 0, 0), + (170, 0, 0), + (0, 170, 0), + (170, 85, 0), + (0, 0, 170), + (170, 0, 170), + (0, 170, 170), + (170, 170, 170), + (85, 85, 85), + (255, 85, 85), + (85, 255, 85), + (255, 255, 85), + (85, 85, 255), + (255, 85, 255), + (85, 255, 255), + (255, 255, 255), + ] +) + + +# The 256 color palette +EIGHT_BIT_PALETTE = Palette( + [ + (0, 0, 0), + (128, 0, 0), + (0, 128, 0), + (128, 128, 0), + (0, 0, 128), + (128, 0, 128), + (0, 128, 128), + (192, 192, 192), + (128, 128, 128), + (255, 0, 0), + (0, 255, 0), + (255, 255, 0), + (0, 0, 255), + (255, 0, 255), + (0, 255, 255), + (255, 255, 255), + (0, 0, 0), + (0, 0, 95), + (0, 0, 135), + (0, 0, 175), + (0, 0, 215), + (0, 0, 255), + (0, 95, 0), + (0, 95, 95), + (0, 95, 135), + (0, 95, 175), + (0, 95, 215), + (0, 95, 255), + (0, 135, 0), + (0, 135, 95), + (0, 135, 135), + (0, 135, 175), + (0, 135, 215), + (0, 135, 255), + (0, 175, 0), + (0, 175, 95), + (0, 175, 135), + (0, 175, 175), + (0, 175, 215), + (0, 175, 255), + (0, 215, 0), + (0, 215, 95), + (0, 215, 135), + (0, 215, 175), + (0, 215, 215), + (0, 215, 255), + (0, 255, 0), + (0, 255, 95), + (0, 255, 135), + (0, 255, 175), + (0, 255, 215), + (0, 255, 255), + (95, 0, 0), + (95, 0, 95), + (95, 0, 135), + (95, 0, 175), + (95, 0, 215), + (95, 0, 255), + (95, 95, 0), + (95, 95, 95), + (95, 95, 135), + (95, 95, 175), + (95, 95, 215), + (95, 95, 255), + (95, 135, 0), + (95, 135, 95), + (95, 135, 135), + (95, 135, 175), + (95, 135, 215), + (95, 135, 255), + (95, 175, 0), + (95, 175, 95), + (95, 175, 135), + (95, 175, 175), + (95, 175, 215), + (95, 175, 255), + (95, 215, 0), + (95, 215, 95), + (95, 215, 135), + (95, 215, 175), + (95, 215, 215), + (95, 215, 255), + (95, 255, 0), + (95, 255, 95), + (95, 255, 135), + (95, 255, 175), + (95, 255, 215), + (95, 255, 255), + (135, 0, 0), + (135, 0, 95), + (135, 0, 135), + (135, 0, 175), + (135, 0, 215), + (135, 0, 255), + (135, 95, 0), + (135, 95, 95), + (135, 95, 135), + (135, 95, 175), + (135, 95, 215), + (135, 95, 255), + (135, 135, 0), + (135, 135, 95), + (135, 135, 135), + (135, 135, 175), + (135, 135, 215), + (135, 135, 255), + (135, 175, 0), + (135, 175, 95), + (135, 175, 135), + (135, 175, 175), + (135, 175, 215), + (135, 175, 255), + (135, 215, 0), + (135, 215, 95), + (135, 215, 135), + (135, 215, 175), + (135, 215, 215), + (135, 215, 255), + (135, 255, 0), + (135, 255, 95), + (135, 255, 135), + (135, 255, 175), + (135, 255, 215), + (135, 255, 255), + (175, 0, 0), + (175, 0, 95), + (175, 0, 135), + (175, 0, 175), + (175, 0, 215), + (175, 0, 255), + (175, 95, 0), + (175, 95, 95), + (175, 95, 135), + (175, 95, 175), + (175, 95, 215), + (175, 95, 255), + (175, 135, 0), + (175, 135, 95), + (175, 135, 135), + (175, 135, 175), + (175, 135, 215), + (175, 135, 255), + (175, 175, 0), + (175, 175, 95), + (175, 175, 135), + (175, 175, 175), + (175, 175, 215), + (175, 175, 255), + (175, 215, 0), + (175, 215, 95), + (175, 215, 135), + (175, 215, 175), + (175, 215, 215), + (175, 215, 255), + (175, 255, 0), + (175, 255, 95), + (175, 255, 135), + (175, 255, 175), + (175, 255, 215), + (175, 255, 255), + (215, 0, 0), + (215, 0, 95), + (215, 0, 135), + (215, 0, 175), + (215, 0, 215), + (215, 0, 255), + (215, 95, 0), + (215, 95, 95), + (215, 95, 135), + (215, 95, 175), + (215, 95, 215), + (215, 95, 255), + (215, 135, 0), + (215, 135, 95), + (215, 135, 135), + (215, 135, 175), + (215, 135, 215), + (215, 135, 255), + (215, 175, 0), + (215, 175, 95), + (215, 175, 135), + (215, 175, 175), + (215, 175, 215), + (215, 175, 255), + (215, 215, 0), + (215, 215, 95), + (215, 215, 135), + (215, 215, 175), + (215, 215, 215), + (215, 215, 255), + (215, 255, 0), + (215, 255, 95), + (215, 255, 135), + (215, 255, 175), + (215, 255, 215), + (215, 255, 255), + (255, 0, 0), + (255, 0, 95), + (255, 0, 135), + (255, 0, 175), + (255, 0, 215), + (255, 0, 255), + (255, 95, 0), + (255, 95, 95), + (255, 95, 135), + (255, 95, 175), + (255, 95, 215), + (255, 95, 255), + (255, 135, 0), + (255, 135, 95), + (255, 135, 135), + (255, 135, 175), + (255, 135, 215), + (255, 135, 255), + (255, 175, 0), + (255, 175, 95), + (255, 175, 135), + (255, 175, 175), + (255, 175, 215), + (255, 175, 255), + (255, 215, 0), + (255, 215, 95), + (255, 215, 135), + (255, 215, 175), + (255, 215, 215), + (255, 215, 255), + (255, 255, 0), + (255, 255, 95), + (255, 255, 135), + (255, 255, 175), + (255, 255, 215), + (255, 255, 255), + (8, 8, 8), + (18, 18, 18), + (28, 28, 28), + (38, 38, 38), + (48, 48, 48), + (58, 58, 58), + (68, 68, 68), + (78, 78, 78), + (88, 88, 88), + (98, 98, 98), + (108, 108, 108), + (118, 118, 118), + (128, 128, 128), + (138, 138, 138), + (148, 148, 148), + (158, 158, 158), + (168, 168, 168), + (178, 178, 178), + (188, 188, 188), + (198, 198, 198), + (208, 208, 208), + (218, 218, 218), + (228, 228, 228), + (238, 238, 238), + ] +) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_pick.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_pick.py new file mode 100644 index 000000000..4f6d8b2d7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_pick.py @@ -0,0 +1,17 @@ +from typing import Optional + + +def pick_bool(*values: Optional[bool]) -> bool: + """Pick the first non-none bool or return the last value. + + Args: + *values (bool): Any number of boolean or None values. + + Returns: + bool: First non-none boolean. + """ + assert values, "1 or more values required" + for value in values: + if value is not None: + return value + return bool(value) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_ratio.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_ratio.py new file mode 100644 index 000000000..95267b0cb --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_ratio.py @@ -0,0 +1,159 @@ +import sys +from fractions import Fraction +from math import ceil +from typing import cast, List, Optional, Sequence + +if sys.version_info >= (3, 8): + from typing import Protocol +else: + from pip._vendor.typing_extensions import Protocol # pragma: no cover + + +class Edge(Protocol): + """Any object that defines an edge (such as Layout).""" + + size: Optional[int] = None + ratio: int = 1 + minimum_size: int = 1 + + +def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]: + """Divide total space to satisfy size, ratio, and minimum_size, constraints. + + The returned list of integers should add up to total in most cases, unless it is + impossible to satisfy all the constraints. For instance, if there are two edges + with a minimum size of 20 each and `total` is 30 then the returned list will be + greater than total. In practice, this would mean that a Layout object would + clip the rows that would overflow the screen height. + + Args: + total (int): Total number of characters. + edges (List[Edge]): Edges within total space. + + Returns: + List[int]: Number of characters for each edge. + """ + # Size of edge or None for yet to be determined + sizes = [(edge.size or None) for edge in edges] + + _Fraction = Fraction + + # While any edges haven't been calculated + while None in sizes: + # Get flexible edges and index to map these back on to sizes list + flexible_edges = [ + (index, edge) + for index, (size, edge) in enumerate(zip(sizes, edges)) + if size is None + ] + # Remaining space in total + remaining = total - sum(size or 0 for size in sizes) + if remaining <= 0: + # No room for flexible edges + return [ + ((edge.minimum_size or 1) if size is None else size) + for size, edge in zip(sizes, edges) + ] + # Calculate number of characters in a ratio portion + portion = _Fraction( + remaining, sum((edge.ratio or 1) for _, edge in flexible_edges) + ) + + # If any edges will be less than their minimum, replace size with the minimum + for index, edge in flexible_edges: + if portion * edge.ratio <= edge.minimum_size: + sizes[index] = edge.minimum_size + # New fixed size will invalidate calculations, so we need to repeat the process + break + else: + # Distribute flexible space and compensate for rounding error + # Since edge sizes can only be integers we need to add the remainder + # to the following line + remainder = _Fraction(0) + for index, edge in flexible_edges: + size, remainder = divmod(portion * edge.ratio + remainder, 1) + sizes[index] = size + break + # Sizes now contains integers only + return cast(List[int], sizes) + + +def ratio_reduce( + total: int, ratios: List[int], maximums: List[int], values: List[int] +) -> List[int]: + """Divide an integer total in to parts based on ratios. + + Args: + total (int): The total to divide. + ratios (List[int]): A list of integer ratios. + maximums (List[int]): List of maximums values for each slot. + values (List[int]): List of values + + Returns: + List[int]: A list of integers guaranteed to sum to total. + """ + ratios = [ratio if _max else 0 for ratio, _max in zip(ratios, maximums)] + total_ratio = sum(ratios) + if not total_ratio: + return values[:] + total_remaining = total + result: List[int] = [] + append = result.append + for ratio, maximum, value in zip(ratios, maximums, values): + if ratio and total_ratio > 0: + distributed = min(maximum, round(ratio * total_remaining / total_ratio)) + append(value - distributed) + total_remaining -= distributed + total_ratio -= ratio + else: + append(value) + return result + + +def ratio_distribute( + total: int, ratios: List[int], minimums: Optional[List[int]] = None +) -> List[int]: + """Distribute an integer total in to parts based on ratios. + + Args: + total (int): The total to divide. + ratios (List[int]): A list of integer ratios. + minimums (List[int]): List of minimum values for each slot. + + Returns: + List[int]: A list of integers guaranteed to sum to total. + """ + if minimums: + ratios = [ratio if _min else 0 for ratio, _min in zip(ratios, minimums)] + total_ratio = sum(ratios) + assert total_ratio > 0, "Sum of ratios must be > 0" + + total_remaining = total + distributed_total: List[int] = [] + append = distributed_total.append + if minimums is None: + _minimums = [0] * len(ratios) + else: + _minimums = minimums + for ratio, minimum in zip(ratios, _minimums): + if total_ratio > 0: + distributed = max(minimum, ceil(ratio * total_remaining / total_ratio)) + else: + distributed = total_remaining + append(distributed) + total_ratio -= ratio + total_remaining -= distributed + return distributed_total + + +if __name__ == "__main__": + from dataclasses import dataclass + + @dataclass + class E: + size: Optional[int] = None + ratio: int = 1 + minimum_size: int = 1 + + resolved = ratio_resolve(110, [E(None, 1, 1), E(None, 1, 1), E(None, 1, 1)]) + print(sum(resolved)) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_spinners.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_spinners.py new file mode 100644 index 000000000..d0bb1fe75 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_spinners.py @@ -0,0 +1,482 @@ +""" +Spinners are from: +* cli-spinners: + MIT License + Copyright (c) Sindre Sorhus (sindresorhus.com) + 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. +""" + +SPINNERS = { + "dots": { + "interval": 80, + "frames": "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", + }, + "dots2": {"interval": 80, "frames": "⣾⣽⣻⢿⡿⣟⣯⣷"}, + "dots3": { + "interval": 80, + "frames": "⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓", + }, + "dots4": { + "interval": 80, + "frames": "⠄⠆⠇⠋⠙⠸⠰⠠⠰⠸⠙⠋⠇⠆", + }, + "dots5": { + "interval": 80, + "frames": "⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋", + }, + "dots6": { + "interval": 80, + "frames": "⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁", + }, + "dots7": { + "interval": 80, + "frames": "⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈", + }, + "dots8": { + "interval": 80, + "frames": "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈", + }, + "dots9": {"interval": 80, "frames": "⢹⢺⢼⣸⣇⡧⡗⡏"}, + "dots10": {"interval": 80, "frames": "⢄⢂⢁⡁⡈⡐⡠"}, + "dots11": {"interval": 100, "frames": "⠁⠂⠄⡀⢀⠠⠐⠈"}, + "dots12": { + "interval": 80, + "frames": [ + "⢀⠀", + "⡀⠀", + "⠄⠀", + "⢂⠀", + "⡂⠀", + "⠅⠀", + "⢃⠀", + "⡃⠀", + "⠍⠀", + "⢋⠀", + "⡋⠀", + "⠍⠁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⢈⠩", + "⡀⢙", + "⠄⡙", + "⢂⠩", + "⡂⢘", + "⠅⡘", + "⢃⠨", + "⡃⢐", + "⠍⡐", + "⢋⠠", + "⡋⢀", + "⠍⡁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⠈⠩", + "⠀⢙", + "⠀⡙", + "⠀⠩", + "⠀⢘", + "⠀⡘", + "⠀⠨", + "⠀⢐", + "⠀⡐", + "⠀⠠", + "⠀⢀", + "⠀⡀", + ], + }, + "dots8Bit": { + "interval": 80, + "frames": "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙" + "⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻" + "⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕" + "⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷" + "⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿", + }, + "line": {"interval": 130, "frames": ["-", "\\", "|", "/"]}, + "line2": {"interval": 100, "frames": "⠂-–—–-"}, + "pipe": {"interval": 100, "frames": "┤┘┴└├┌┬┐"}, + "simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]}, + "simpleDotsScrolling": { + "interval": 200, + "frames": [". ", ".. ", "...", " ..", " .", " "], + }, + "star": {"interval": 70, "frames": "✶✸✹✺✹✷"}, + "star2": {"interval": 80, "frames": "+x*"}, + "flip": { + "interval": 70, + "frames": "___-``'´-___", + }, + "hamburger": {"interval": 100, "frames": "☱☲☴"}, + "growVertical": { + "interval": 120, + "frames": "▁▃▄▅▆▇▆▅▄▃", + }, + "growHorizontal": { + "interval": 120, + "frames": "▏▎▍▌▋▊▉▊▋▌▍▎", + }, + "balloon": {"interval": 140, "frames": " .oO@* "}, + "balloon2": {"interval": 120, "frames": ".oO°Oo."}, + "noise": {"interval": 100, "frames": "▓▒░"}, + "bounce": {"interval": 120, "frames": "⠁⠂⠄⠂"}, + "boxBounce": {"interval": 120, "frames": "▖▘▝▗"}, + "boxBounce2": {"interval": 100, "frames": "▌▀▐▄"}, + "triangle": {"interval": 50, "frames": "◢◣◤◥"}, + "arc": {"interval": 100, "frames": "◜◠◝◞◡◟"}, + "circle": {"interval": 120, "frames": "◡⊙◠"}, + "squareCorners": {"interval": 180, "frames": "◰◳◲◱"}, + "circleQuarters": {"interval": 120, "frames": "◴◷◶◵"}, + "circleHalves": {"interval": 50, "frames": "◐◓◑◒"}, + "squish": {"interval": 100, "frames": "╫╪"}, + "toggle": {"interval": 250, "frames": "⊶⊷"}, + "toggle2": {"interval": 80, "frames": "▫▪"}, + "toggle3": {"interval": 120, "frames": "□■"}, + "toggle4": {"interval": 100, "frames": "■□▪▫"}, + "toggle5": {"interval": 100, "frames": "▮▯"}, + "toggle6": {"interval": 300, "frames": "ဝ၀"}, + "toggle7": {"interval": 80, "frames": "⦾⦿"}, + "toggle8": {"interval": 100, "frames": "◍◌"}, + "toggle9": {"interval": 100, "frames": "◉◎"}, + "toggle10": {"interval": 100, "frames": "㊂㊀㊁"}, + "toggle11": {"interval": 50, "frames": "⧇⧆"}, + "toggle12": {"interval": 120, "frames": "☗☖"}, + "toggle13": {"interval": 80, "frames": "=*-"}, + "arrow": {"interval": 100, "frames": "←↖↑↗→↘↓↙"}, + "arrow2": { + "interval": 80, + "frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "], + }, + "arrow3": { + "interval": 120, + "frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"], + }, + "bouncingBar": { + "interval": 80, + "frames": [ + "[ ]", + "[= ]", + "[== ]", + "[=== ]", + "[ ===]", + "[ ==]", + "[ =]", + "[ ]", + "[ =]", + "[ ==]", + "[ ===]", + "[====]", + "[=== ]", + "[== ]", + "[= ]", + ], + }, + "bouncingBall": { + "interval": 80, + "frames": [ + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "( ●)", + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "(● )", + ], + }, + "smiley": {"interval": 200, "frames": ["😄 ", "😝 "]}, + "monkey": {"interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "]}, + "hearts": {"interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "]}, + "clock": { + "interval": 100, + "frames": [ + "🕛 ", + "🕐 ", + "🕑 ", + "🕒 ", + "🕓 ", + "🕔 ", + "🕕 ", + "🕖 ", + "🕗 ", + "🕘 ", + "🕙 ", + "🕚 ", + ], + }, + "earth": {"interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "]}, + "material": { + "interval": 17, + "frames": [ + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███████▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "██████████▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "█████████████▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁██████████████▁▁▁▁", + "▁▁▁██████████████▁▁▁", + "▁▁▁▁█████████████▁▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁▁▁████████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁▁█████████████▁▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁▁███████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁▁█████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + ], + }, + "moon": { + "interval": 80, + "frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "], + }, + "runner": {"interval": 140, "frames": ["🚶 ", "🏃 "]}, + "pong": { + "interval": 80, + "frames": [ + "▐⠂ ▌", + "▐⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂▌", + "▐ ⠠▌", + "▐ ⡀▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐⠠ ▌", + ], + }, + "shark": { + "interval": 120, + "frames": [ + "▐|\\____________▌", + "▐_|\\___________▌", + "▐__|\\__________▌", + "▐___|\\_________▌", + "▐____|\\________▌", + "▐_____|\\_______▌", + "▐______|\\______▌", + "▐_______|\\_____▌", + "▐________|\\____▌", + "▐_________|\\___▌", + "▐__________|\\__▌", + "▐___________|\\_▌", + "▐____________|\\▌", + "▐____________/|▌", + "▐___________/|_▌", + "▐__________/|__▌", + "▐_________/|___▌", + "▐________/|____▌", + "▐_______/|_____▌", + "▐______/|______▌", + "▐_____/|_______▌", + "▐____/|________▌", + "▐___/|_________▌", + "▐__/|__________▌", + "▐_/|___________▌", + "▐/|____________▌", + ], + }, + "dqpb": {"interval": 100, "frames": "dqpb"}, + "weather": { + "interval": 100, + "frames": [ + "☀️ ", + "☀️ ", + "☀️ ", + "🌤 ", + "⛅️ ", + "🌥 ", + "☁️ ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "⛈ ", + "🌨 ", + "🌧 ", + "🌨 ", + "☁️ ", + "🌥 ", + "⛅️ ", + "🌤 ", + "☀️ ", + "☀️ ", + ], + }, + "christmas": {"interval": 400, "frames": "🌲🎄"}, + "grenade": { + "interval": 80, + "frames": [ + "، ", + "′ ", + " ´ ", + " ‾ ", + " ⸌", + " ⸊", + " |", + " ⁎", + " ⁕", + " ෴ ", + " ⁓", + " ", + " ", + " ", + ], + }, + "point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]}, + "layer": {"interval": 150, "frames": "-=≡"}, + "betaWave": { + "interval": 80, + "frames": [ + "ρββββββ", + "βρβββββ", + "ββρββββ", + "βββρβββ", + "ββββρββ", + "βββββρβ", + "ββββββρ", + ], + }, + "aesthetic": { + "interval": 80, + "frames": [ + "▰▱▱▱▱▱▱", + "▰▰▱▱▱▱▱", + "▰▰▰▱▱▱▱", + "▰▰▰▰▱▱▱", + "▰▰▰▰▰▱▱", + "▰▰▰▰▰▰▱", + "▰▰▰▰▰▰▰", + "▰▱▱▱▱▱▱", + ], + }, +} diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_stack.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_stack.py new file mode 100644 index 000000000..194564e76 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_stack.py @@ -0,0 +1,16 @@ +from typing import List, TypeVar + +T = TypeVar("T") + + +class Stack(List[T]): + """A small shim over builtin list.""" + + @property + def top(self) -> T: + """Get top of stack.""" + return self[-1] + + def push(self, item: T) -> None: + """Push an item on to the stack (append in stack nomenclature).""" + self.append(item) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_timer.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_timer.py new file mode 100644 index 000000000..a2ca6be03 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_timer.py @@ -0,0 +1,19 @@ +""" +Timer context manager, only used in debug. + +""" + +from time import time + +import contextlib +from typing import Generator + + +@contextlib.contextmanager +def timer(subject: str = "time") -> Generator[None, None, None]: + """print the elapsed time. (only used in debugging)""" + start = time() + yield + elapsed = time() - start + elapsed_ms = elapsed * 1000 + print(f"{subject} elapsed {elapsed_ms:.1f}ms") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_win32_console.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_win32_console.py new file mode 100644 index 000000000..2eba1b9b4 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_win32_console.py @@ -0,0 +1,661 @@ +"""Light wrapper around the Win32 Console API - this module should only be imported on Windows + +The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions +""" + +import ctypes +import sys +from typing import Any + +windll: Any = None +if sys.platform == "win32": + windll = ctypes.LibraryLoader(ctypes.WinDLL) +else: + raise ImportError(f"{__name__} can only be imported on Windows") + +import time +from ctypes import Structure, byref, wintypes +from typing import IO, NamedTuple, Type, cast + +from pip._vendor.rich.color import ColorSystem +from pip._vendor.rich.style import Style + +STDOUT = -11 +ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + +COORD = wintypes._COORD + + +class LegacyWindowsError(Exception): + pass + + +class WindowsCoordinates(NamedTuple): + """Coordinates in the Windows Console API are (y, x), not (x, y). + This class is intended to prevent that confusion. + Rows and columns are indexed from 0. + This class can be used in place of wintypes._COORD in arguments and argtypes. + """ + + row: int + col: int + + @classmethod + def from_param(cls, value: "WindowsCoordinates") -> COORD: + """Converts a WindowsCoordinates into a wintypes _COORD structure. + This classmethod is internally called by ctypes to perform the conversion. + + Args: + value (WindowsCoordinates): The input coordinates to convert. + + Returns: + wintypes._COORD: The converted coordinates struct. + """ + return COORD(value.col, value.row) + + +class CONSOLE_SCREEN_BUFFER_INFO(Structure): + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + + +class CONSOLE_CURSOR_INFO(ctypes.Structure): + _fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)] + + +_GetStdHandle = windll.kernel32.GetStdHandle +_GetStdHandle.argtypes = [ + wintypes.DWORD, +] +_GetStdHandle.restype = wintypes.HANDLE + + +def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE: + """Retrieves a handle to the specified standard device (standard input, standard output, or standard error). + + Args: + handle (int): Integer identifier for the handle. Defaults to -11 (stdout). + + Returns: + wintypes.HANDLE: The handle + """ + return cast(wintypes.HANDLE, _GetStdHandle(handle)) + + +_GetConsoleMode = windll.kernel32.GetConsoleMode +_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD] +_GetConsoleMode.restype = wintypes.BOOL + + +def GetConsoleMode(std_handle: wintypes.HANDLE) -> int: + """Retrieves the current input mode of a console's input buffer + or the current output mode of a console screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + + Raises: + LegacyWindowsError: If any error occurs while calling the Windows console API. + + Returns: + int: Value representing the current console mode as documented at + https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters + """ + + console_mode = wintypes.DWORD() + success = bool(_GetConsoleMode(std_handle, console_mode)) + if not success: + raise LegacyWindowsError("Unable to get legacy Windows Console Mode") + return console_mode.value + + +_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW +_FillConsoleOutputCharacterW.argtypes = [ + wintypes.HANDLE, + ctypes.c_char, + wintypes.DWORD, + cast(Type[COORD], WindowsCoordinates), + ctypes.POINTER(wintypes.DWORD), +] +_FillConsoleOutputCharacterW.restype = wintypes.BOOL + + +def FillConsoleOutputCharacter( + std_handle: wintypes.HANDLE, + char: str, + length: int, + start: WindowsCoordinates, +) -> int: + """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + char (str): The character to write. Must be a string of length 1. + length (int): The number of times to write the character. + start (WindowsCoordinates): The coordinates to start writing at. + + Returns: + int: The number of characters written. + """ + character = ctypes.c_char(char.encode()) + num_characters = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + _FillConsoleOutputCharacterW( + std_handle, + character, + num_characters, + start, + byref(num_written), + ) + return num_written.value + + +_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute +_FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + cast(Type[COORD], WindowsCoordinates), + ctypes.POINTER(wintypes.DWORD), +] +_FillConsoleOutputAttribute.restype = wintypes.BOOL + + +def FillConsoleOutputAttribute( + std_handle: wintypes.HANDLE, + attributes: int, + length: int, + start: WindowsCoordinates, +) -> int: + """Sets the character attributes for a specified number of character cells, + beginning at the specified coordinates in a screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + attributes (int): Integer value representing the foreground and background colours of the cells. + length (int): The number of cells to set the output attribute of. + start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set. + + Returns: + int: The number of cells whose attributes were actually set. + """ + num_cells = wintypes.DWORD(length) + style_attrs = wintypes.WORD(attributes) + num_written = wintypes.DWORD(0) + _FillConsoleOutputAttribute( + std_handle, style_attrs, num_cells, start, byref(num_written) + ) + return num_written.value + + +_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute +_SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, +] +_SetConsoleTextAttribute.restype = wintypes.BOOL + + +def SetConsoleTextAttribute( + std_handle: wintypes.HANDLE, attributes: wintypes.WORD +) -> bool: + """Set the colour attributes for all text written after this function is called. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + attributes (int): Integer value representing the foreground and background colours. + + + Returns: + bool: True if the attribute was set successfully, otherwise False. + """ + return bool(_SetConsoleTextAttribute(std_handle, attributes)) + + +_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo +_GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO), +] +_GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + +def GetConsoleScreenBufferInfo( + std_handle: wintypes.HANDLE, +) -> CONSOLE_SCREEN_BUFFER_INFO: + """Retrieves information about the specified console screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + + Returns: + CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about + screen size, cursor position, colour attributes, and more.""" + console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO() + _GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info)) + return console_screen_buffer_info + + +_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition +_SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + cast(Type[COORD], WindowsCoordinates), +] +_SetConsoleCursorPosition.restype = wintypes.BOOL + + +def SetConsoleCursorPosition( + std_handle: wintypes.HANDLE, coords: WindowsCoordinates +) -> bool: + """Set the position of the cursor in the console screen + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + coords (WindowsCoordinates): The coordinates to move the cursor to. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleCursorPosition(std_handle, coords)) + + +_GetConsoleCursorInfo = windll.kernel32.GetConsoleCursorInfo +_GetConsoleCursorInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_CURSOR_INFO), +] +_GetConsoleCursorInfo.restype = wintypes.BOOL + + +def GetConsoleCursorInfo( + std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO +) -> bool: + """Get the cursor info - used to get cursor visibility and width + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct that receives information + about the console's cursor. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_GetConsoleCursorInfo(std_handle, byref(cursor_info))) + + +_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo +_SetConsoleCursorInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_CURSOR_INFO), +] +_SetConsoleCursorInfo.restype = wintypes.BOOL + + +def SetConsoleCursorInfo( + std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO +) -> bool: + """Set the cursor info - used for adjusting cursor visibility and width + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info))) + + +_SetConsoleTitle = windll.kernel32.SetConsoleTitleW +_SetConsoleTitle.argtypes = [wintypes.LPCWSTR] +_SetConsoleTitle.restype = wintypes.BOOL + + +def SetConsoleTitle(title: str) -> bool: + """Sets the title of the current console window + + Args: + title (str): The new title of the console window. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleTitle(title)) + + +class LegacyWindowsTerm: + """This class allows interaction with the legacy Windows Console API. It should only be used in the context + of environments where virtual terminal processing is not available. However, if it is used in a Windows environment, + the entire API should work. + + Args: + file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout. + """ + + BRIGHT_BIT = 8 + + # Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers + ANSI_TO_WINDOWS = [ + 0, # black The Windows colours are defined in wincon.h as follows: + 4, # red define FOREGROUND_BLUE 0x0001 -- 0000 0001 + 2, # green define FOREGROUND_GREEN 0x0002 -- 0000 0010 + 6, # yellow define FOREGROUND_RED 0x0004 -- 0000 0100 + 1, # blue define FOREGROUND_INTENSITY 0x0008 -- 0000 1000 + 5, # magenta define BACKGROUND_BLUE 0x0010 -- 0001 0000 + 3, # cyan define BACKGROUND_GREEN 0x0020 -- 0010 0000 + 7, # white define BACKGROUND_RED 0x0040 -- 0100 0000 + 8, # bright black (grey) define BACKGROUND_INTENSITY 0x0080 -- 1000 0000 + 12, # bright red + 10, # bright green + 14, # bright yellow + 9, # bright blue + 13, # bright magenta + 11, # bright cyan + 15, # bright white + ] + + def __init__(self, file: "IO[str]") -> None: + handle = GetStdHandle(STDOUT) + self._handle = handle + default_text = GetConsoleScreenBufferInfo(handle).wAttributes + self._default_text = default_text + + self._default_fore = default_text & 7 + self._default_back = (default_text >> 4) & 7 + self._default_attrs = self._default_fore | (self._default_back << 4) + + self._file = file + self.write = file.write + self.flush = file.flush + + @property + def cursor_position(self) -> WindowsCoordinates: + """Returns the current position of the cursor (0-based) + + Returns: + WindowsCoordinates: The current cursor position. + """ + coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition + return WindowsCoordinates(row=coord.Y, col=coord.X) + + @property + def screen_size(self) -> WindowsCoordinates: + """Returns the current size of the console screen buffer, in character columns and rows + + Returns: + WindowsCoordinates: The width and height of the screen as WindowsCoordinates. + """ + screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize + return WindowsCoordinates(row=screen_size.Y, col=screen_size.X) + + def write_text(self, text: str) -> None: + """Write text directly to the terminal without any modification of styles + + Args: + text (str): The text to write to the console + """ + self.write(text) + self.flush() + + def write_styled(self, text: str, style: Style) -> None: + """Write styled text to the terminal. + + Args: + text (str): The text to write + style (Style): The style of the text + """ + color = style.color + bgcolor = style.bgcolor + if style.reverse: + color, bgcolor = bgcolor, color + + if color: + fore = color.downgrade(ColorSystem.WINDOWS).number + fore = fore if fore is not None else 7 # Default to ANSI 7: White + if style.bold: + fore = fore | self.BRIGHT_BIT + if style.dim: + fore = fore & ~self.BRIGHT_BIT + fore = self.ANSI_TO_WINDOWS[fore] + else: + fore = self._default_fore + + if bgcolor: + back = bgcolor.downgrade(ColorSystem.WINDOWS).number + back = back if back is not None else 0 # Default to ANSI 0: Black + back = self.ANSI_TO_WINDOWS[back] + else: + back = self._default_back + + assert fore is not None + assert back is not None + + SetConsoleTextAttribute( + self._handle, attributes=ctypes.c_ushort(fore | (back << 4)) + ) + self.write_text(text) + SetConsoleTextAttribute(self._handle, attributes=self._default_text) + + def move_cursor_to(self, new_position: WindowsCoordinates) -> None: + """Set the position of the cursor + + Args: + new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor. + """ + if new_position.col < 0 or new_position.row < 0: + return + SetConsoleCursorPosition(self._handle, coords=new_position) + + def erase_line(self) -> None: + """Erase all content on the line the cursor is currently located at""" + screen_size = self.screen_size + cursor_position = self.cursor_position + cells_to_erase = screen_size.col + start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0) + FillConsoleOutputCharacter( + self._handle, " ", length=cells_to_erase, start=start_coordinates + ) + FillConsoleOutputAttribute( + self._handle, + self._default_attrs, + length=cells_to_erase, + start=start_coordinates, + ) + + def erase_end_of_line(self) -> None: + """Erase all content from the cursor position to the end of that line""" + cursor_position = self.cursor_position + cells_to_erase = self.screen_size.col - cursor_position.col + FillConsoleOutputCharacter( + self._handle, " ", length=cells_to_erase, start=cursor_position + ) + FillConsoleOutputAttribute( + self._handle, + self._default_attrs, + length=cells_to_erase, + start=cursor_position, + ) + + def erase_start_of_line(self) -> None: + """Erase all content from the cursor position to the start of that line""" + row, col = self.cursor_position + start = WindowsCoordinates(row, 0) + FillConsoleOutputCharacter(self._handle, " ", length=col, start=start) + FillConsoleOutputAttribute( + self._handle, self._default_attrs, length=col, start=start + ) + + def move_cursor_up(self) -> None: + """Move the cursor up a single cell""" + cursor_position = self.cursor_position + SetConsoleCursorPosition( + self._handle, + coords=WindowsCoordinates( + row=cursor_position.row - 1, col=cursor_position.col + ), + ) + + def move_cursor_down(self) -> None: + """Move the cursor down a single cell""" + cursor_position = self.cursor_position + SetConsoleCursorPosition( + self._handle, + coords=WindowsCoordinates( + row=cursor_position.row + 1, + col=cursor_position.col, + ), + ) + + def move_cursor_forward(self) -> None: + """Move the cursor forward a single cell. Wrap to the next line if required.""" + row, col = self.cursor_position + if col == self.screen_size.col - 1: + row += 1 + col = 0 + else: + col += 1 + SetConsoleCursorPosition( + self._handle, coords=WindowsCoordinates(row=row, col=col) + ) + + def move_cursor_to_column(self, column: int) -> None: + """Move cursor to the column specified by the zero-based column index, staying on the same row + + Args: + column (int): The zero-based column index to move the cursor to. + """ + row, _ = self.cursor_position + SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column)) + + def move_cursor_backward(self) -> None: + """Move the cursor backward a single cell. Wrap to the previous line if required.""" + row, col = self.cursor_position + if col == 0: + row -= 1 + col = self.screen_size.col - 1 + else: + col -= 1 + SetConsoleCursorPosition( + self._handle, coords=WindowsCoordinates(row=row, col=col) + ) + + def hide_cursor(self) -> None: + """Hide the cursor""" + current_cursor_size = self._get_cursor_size() + invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=0) + SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor) + + def show_cursor(self) -> None: + """Show the cursor""" + current_cursor_size = self._get_cursor_size() + visible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=1) + SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor) + + def set_title(self, title: str) -> None: + """Set the title of the terminal window + + Args: + title (str): The new title of the console window + """ + assert len(title) < 255, "Console title must be less than 255 characters" + SetConsoleTitle(title) + + def _get_cursor_size(self) -> int: + """Get the percentage of the character cell that is filled by the cursor""" + cursor_info = CONSOLE_CURSOR_INFO() + GetConsoleCursorInfo(self._handle, cursor_info=cursor_info) + return int(cursor_info.dwSize) + + +if __name__ == "__main__": + handle = GetStdHandle() + + from pip._vendor.rich.console import Console + + console = Console() + + term = LegacyWindowsTerm(sys.stdout) + term.set_title("Win32 Console Examples") + + style = Style(color="black", bgcolor="red") + + heading = Style.parse("black on green") + + # Check colour output + console.rule("Checking colour output") + console.print("[on red]on red!") + console.print("[blue]blue!") + console.print("[yellow]yellow!") + console.print("[bold yellow]bold yellow!") + console.print("[bright_yellow]bright_yellow!") + console.print("[dim bright_yellow]dim bright_yellow!") + console.print("[italic cyan]italic cyan!") + console.print("[bold white on blue]bold white on blue!") + console.print("[reverse bold white on blue]reverse bold white on blue!") + console.print("[bold black on cyan]bold black on cyan!") + console.print("[black on green]black on green!") + console.print("[blue on green]blue on green!") + console.print("[white on black]white on black!") + console.print("[black on white]black on white!") + console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!") + + # Check cursor movement + console.rule("Checking cursor movement") + console.print() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("went back and wrapped to prev line") + time.sleep(1) + term.move_cursor_up() + term.write_text("we go up") + time.sleep(1) + term.move_cursor_down() + term.write_text("and down") + time.sleep(1) + term.move_cursor_up() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("we went up and back 2") + time.sleep(1) + term.move_cursor_down() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("we went down and back 2") + time.sleep(1) + + # Check erasing of lines + term.hide_cursor() + console.print() + console.rule("Checking line erasing") + console.print("\n...Deleting to the start of the line...") + term.write_text("The red arrow shows the cursor location, and direction of erase") + time.sleep(1) + term.move_cursor_to_column(16) + term.write_styled("<", Style.parse("black on red")) + term.move_cursor_backward() + time.sleep(1) + term.erase_start_of_line() + time.sleep(1) + + console.print("\n\n...And to the end of the line...") + term.write_text("The red arrow shows the cursor location, and direction of erase") + time.sleep(1) + + term.move_cursor_to_column(16) + term.write_styled(">", Style.parse("black on red")) + time.sleep(1) + term.erase_end_of_line() + time.sleep(1) + + console.print("\n\n...Now the whole line will be erased...") + term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan")) + time.sleep(1) + term.erase_line() + + term.show_cursor() + print("\n") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows.py new file mode 100644 index 000000000..7520a9f90 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows.py @@ -0,0 +1,71 @@ +import sys +from dataclasses import dataclass + + +@dataclass +class WindowsConsoleFeatures: + """Windows features available.""" + + vt: bool = False + """The console supports VT codes.""" + truecolor: bool = False + """The console supports truecolor.""" + + +try: + import ctypes + from ctypes import LibraryLoader + + if sys.platform == "win32": + windll = LibraryLoader(ctypes.WinDLL) + else: + windll = None + raise ImportError("Not windows") + + from pip._vendor.rich._win32_console import ( + ENABLE_VIRTUAL_TERMINAL_PROCESSING, + GetConsoleMode, + GetStdHandle, + LegacyWindowsError, + ) + +except (AttributeError, ImportError, ValueError): + # Fallback if we can't load the Windows DLL + def get_windows_console_features() -> WindowsConsoleFeatures: + features = WindowsConsoleFeatures() + return features + +else: + + def get_windows_console_features() -> WindowsConsoleFeatures: + """Get windows console features. + + Returns: + WindowsConsoleFeatures: An instance of WindowsConsoleFeatures. + """ + handle = GetStdHandle() + try: + console_mode = GetConsoleMode(handle) + success = True + except LegacyWindowsError: + console_mode = 0 + success = False + vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + truecolor = False + if vt: + win_version = sys.getwindowsversion() + truecolor = win_version.major > 10 or ( + win_version.major == 10 and win_version.build >= 15063 + ) + features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor) + return features + + +if __name__ == "__main__": + import platform + + features = get_windows_console_features() + from pip._vendor.rich import print + + print(f'platform="{platform.system()}"') + print(repr(features)) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows_renderer.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows_renderer.py new file mode 100644 index 000000000..5ece05649 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_windows_renderer.py @@ -0,0 +1,56 @@ +from typing import Iterable, Sequence, Tuple, cast + +from pip._vendor.rich._win32_console import LegacyWindowsTerm, WindowsCoordinates +from pip._vendor.rich.segment import ControlCode, ControlType, Segment + + +def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None: + """Makes appropriate Windows Console API calls based on the segments in the buffer. + + Args: + buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls. + term (LegacyWindowsTerm): Used to call the Windows Console API. + """ + for text, style, control in buffer: + if not control: + if style: + term.write_styled(text, style) + else: + term.write_text(text) + else: + control_codes: Sequence[ControlCode] = control + for control_code in control_codes: + control_type = control_code[0] + if control_type == ControlType.CURSOR_MOVE_TO: + _, x, y = cast(Tuple[ControlType, int, int], control_code) + term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1)) + elif control_type == ControlType.CARRIAGE_RETURN: + term.write_text("\r") + elif control_type == ControlType.HOME: + term.move_cursor_to(WindowsCoordinates(0, 0)) + elif control_type == ControlType.CURSOR_UP: + term.move_cursor_up() + elif control_type == ControlType.CURSOR_DOWN: + term.move_cursor_down() + elif control_type == ControlType.CURSOR_FORWARD: + term.move_cursor_forward() + elif control_type == ControlType.CURSOR_BACKWARD: + term.move_cursor_backward() + elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN: + _, column = cast(Tuple[ControlType, int], control_code) + term.move_cursor_to_column(column - 1) + elif control_type == ControlType.HIDE_CURSOR: + term.hide_cursor() + elif control_type == ControlType.SHOW_CURSOR: + term.show_cursor() + elif control_type == ControlType.ERASE_IN_LINE: + _, mode = cast(Tuple[ControlType, int], control_code) + if mode == 0: + term.erase_end_of_line() + elif mode == 1: + term.erase_start_of_line() + elif mode == 2: + term.erase_line() + elif control_type == ControlType.SET_WINDOW_TITLE: + _, title = cast(Tuple[ControlType, str], control_code) + term.set_title(title) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/_wrap.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_wrap.py new file mode 100644 index 000000000..2e94ff6f4 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/_wrap.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import re +from typing import Iterable + +from ._loop import loop_last +from .cells import cell_len, chop_cells + +re_word = re.compile(r"\s*\S+\s*") + + +def words(text: str) -> Iterable[tuple[int, int, str]]: + """Yields each word from the text as a tuple + containing (start_index, end_index, word). A "word" in this context may + include the actual word and any whitespace to the right. + """ + position = 0 + word_match = re_word.match(text, position) + while word_match is not None: + start, end = word_match.span() + word = word_match.group(0) + yield start, end, word + word_match = re_word.match(text, end) + + +def divide_line(text: str, width: int, fold: bool = True) -> list[int]: + """Given a string of text, and a width (measured in cells), return a list + of cell offsets which the string should be split at in order for it to fit + within the given width. + + Args: + text: The text to examine. + width: The available cell width. + fold: If True, words longer than `width` will be folded onto a new line. + + Returns: + A list of indices to break the line at. + """ + break_positions: list[int] = [] # offsets to insert the breaks at + append = break_positions.append + cell_offset = 0 + _cell_len = cell_len + + for start, _end, word in words(text): + word_length = _cell_len(word.rstrip()) + remaining_space = width - cell_offset + word_fits_remaining_space = remaining_space >= word_length + + if word_fits_remaining_space: + # Simplest case - the word fits within the remaining width for this line. + cell_offset += _cell_len(word) + else: + # Not enough space remaining for this word on the current line. + if word_length > width: + # The word doesn't fit on any line, so we can't simply + # place it on the next line... + if fold: + # Fold the word across multiple lines. + folded_word = chop_cells(word, width=width) + for last, line in loop_last(folded_word): + if start: + append(start) + if last: + cell_offset = _cell_len(line) + else: + start += len(line) + else: + # Folding isn't allowed, so crop the word. + if start: + append(start) + cell_offset = _cell_len(word) + elif cell_offset and start: + # The word doesn't fit within the remaining space on the current + # line, but it *can* fit on to the next (empty) line. + append(start) + cell_offset = _cell_len(word) + + return break_positions + + +if __name__ == "__main__": # pragma: no cover + from .console import Console + + console = Console(width=10) + console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345") + print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10)) + + console = Console(width=20) + console.rule() + console.print("TextualはPythonの高速アプリケーション開発フレームワークです") + + console.rule() + console.print("アプリケーションは1670万色を使用でき") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/abc.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/abc.py new file mode 100644 index 000000000..e6e498efa --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/abc.py @@ -0,0 +1,33 @@ +from abc import ABC + + +class RichRenderable(ABC): + """An abstract base class for Rich renderables. + + Note that there is no need to extend this class, the intended use is to check if an + object supports the Rich renderable protocol. For example:: + + if isinstance(my_object, RichRenderable): + console.print(my_object) + + """ + + @classmethod + def __subclasshook__(cls, other: type) -> bool: + """Check if this class supports the rich render protocol.""" + return hasattr(other, "__rich_console__") or hasattr(other, "__rich__") + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.text import Text + + t = Text() + print(isinstance(Text, RichRenderable)) + print(isinstance(t, RichRenderable)) + + class Foo: + pass + + f = Foo() + print(isinstance(f, RichRenderable)) + print(isinstance("", RichRenderable)) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/align.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/align.py new file mode 100644 index 000000000..330dcc511 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/align.py @@ -0,0 +1,312 @@ +import sys +from itertools import chain +from typing import TYPE_CHECKING, Iterable, Optional + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + +from .constrain import Constrain +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + +AlignMethod = Literal["left", "center", "right"] +VerticalAlignMethod = Literal["top", "middle", "bottom"] + + +class Align(JupyterMixin): + """Align a renderable by adding spaces if necessary. + + Args: + renderable (RenderableType): A console renderable. + align (AlignMethod): One of "left", "center", or "right"" + style (StyleType, optional): An optional style to apply to the background. + vertical (Optional[VerticalAlignMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None. + pad (bool, optional): Pad the right with spaces. Defaults to True. + width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None. + height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None. + + Raises: + ValueError: if ``align`` is not one of the expected values. + """ + + def __init__( + self, + renderable: "RenderableType", + align: AlignMethod = "left", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> None: + if align not in ("left", "center", "right"): + raise ValueError( + f'invalid value for align, expected "left", "center", or "right" (not {align!r})' + ) + if vertical is not None and vertical not in ("top", "middle", "bottom"): + raise ValueError( + f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})' + ) + self.renderable = renderable + self.align = align + self.style = style + self.vertical = vertical + self.pad = pad + self.width = width + self.height = height + + def __repr__(self) -> str: + return f"Align({self.renderable!r}, {self.align!r})" + + @classmethod + def left( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the left.""" + return cls( + renderable, + "left", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + @classmethod + def center( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the center.""" + return cls( + renderable, + "center", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + @classmethod + def right( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the right.""" + return cls( + renderable, + "right", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + align = self.align + width = console.measure(self.renderable, options=options).maximum + rendered = console.render( + Constrain( + self.renderable, width if self.width is None else min(width, self.width) + ), + options.update(height=None), + ) + lines = list(Segment.split_lines(rendered)) + width, height = Segment.get_shape(lines) + lines = Segment.set_shape(lines, width, height) + new_line = Segment.line() + excess_space = options.max_width - width + style = console.get_style(self.style) if self.style is not None else None + + def generate_segments() -> Iterable[Segment]: + if excess_space <= 0: + # Exact fit + for line in lines: + yield from line + yield new_line + + elif align == "left": + # Pad on the right + pad = Segment(" " * excess_space, style) if self.pad else None + for line in lines: + yield from line + if pad: + yield pad + yield new_line + + elif align == "center": + # Pad left and right + left = excess_space // 2 + pad = Segment(" " * left, style) + pad_right = ( + Segment(" " * (excess_space - left), style) if self.pad else None + ) + for line in lines: + if left: + yield pad + yield from line + if pad_right: + yield pad_right + yield new_line + + elif align == "right": + # Padding on left + pad = Segment(" " * excess_space, style) + for line in lines: + yield pad + yield from line + yield new_line + + blank_line = ( + Segment(f"{' ' * (self.width or options.max_width)}\n", style) + if self.pad + else Segment("\n") + ) + + def blank_lines(count: int) -> Iterable[Segment]: + if count > 0: + for _ in range(count): + yield blank_line + + vertical_height = self.height or options.height + iter_segments: Iterable[Segment] + if self.vertical and vertical_height is not None: + if self.vertical == "top": + bottom_space = vertical_height - height + iter_segments = chain(generate_segments(), blank_lines(bottom_space)) + elif self.vertical == "middle": + top_space = (vertical_height - height) // 2 + bottom_space = vertical_height - top_space - height + iter_segments = chain( + blank_lines(top_space), + generate_segments(), + blank_lines(bottom_space), + ) + else: # self.vertical == "bottom": + top_space = vertical_height - height + iter_segments = chain(blank_lines(top_space), generate_segments()) + else: + iter_segments = generate_segments() + if self.style: + style = console.get_style(self.style) + iter_segments = Segment.apply_style(iter_segments, style) + yield from iter_segments + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + measurement = Measurement.get(console, options, self.renderable) + return measurement + + +class VerticalCenter(JupyterMixin): + """Vertically aligns a renderable. + + Warn: + This class is deprecated and may be removed in a future version. Use Align class with + `vertical="middle"`. + + Args: + renderable (RenderableType): A renderable object. + style (StyleType, optional): An optional style to apply to the background. Defaults to None. + """ + + def __init__( + self, + renderable: "RenderableType", + style: Optional[StyleType] = None, + ) -> None: + self.renderable = renderable + self.style = style + + def __repr__(self) -> str: + return f"VerticalCenter({self.renderable!r})" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + style = console.get_style(self.style) if self.style is not None else None + lines = console.render_lines( + self.renderable, options.update(height=None), pad=False + ) + width, _height = Segment.get_shape(lines) + new_line = Segment.line() + height = options.height or options.size.height + top_space = (height - len(lines)) // 2 + bottom_space = height - top_space - len(lines) + blank_line = Segment(f"{' ' * width}", style) + + def blank_lines(count: int) -> Iterable[Segment]: + for _ in range(count): + yield blank_line + yield new_line + + if top_space > 0: + yield from blank_lines(top_space) + for line in lines: + yield from line + yield new_line + if bottom_space > 0: + yield from blank_lines(bottom_space) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + measurement = Measurement.get(console, options, self.renderable) + return measurement + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console, Group + from pip._vendor.rich.highlighter import ReprHighlighter + from pip._vendor.rich.panel import Panel + + highlighter = ReprHighlighter() + console = Console() + + panel = Panel( + Group( + Align.left(highlighter("align='left'")), + Align.center(highlighter("align='center'")), + Align.right(highlighter("align='right'")), + ), + width=60, + style="on dark_blue", + title="Align", + ) + + console.print( + Align.center(panel, vertical="middle", style="on red", height=console.height) + ) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/ansi.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/ansi.py new file mode 100644 index 000000000..7de86ce50 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/ansi.py @@ -0,0 +1,241 @@ +import re +import sys +from contextlib import suppress +from typing import Iterable, NamedTuple, Optional + +from .color import Color +from .style import Style +from .text import Text + +re_ansi = re.compile( + r""" +(?:\x1b[0-?])| +(?:\x1b\](.*?)\x1b\\)| +(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~])) +""", + re.VERBOSE, +) + + +class _AnsiToken(NamedTuple): + """Result of ansi tokenized string.""" + + plain: str = "" + sgr: Optional[str] = "" + osc: Optional[str] = "" + + +def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]: + """Tokenize a string in to plain text and ANSI codes. + + Args: + ansi_text (str): A String containing ANSI codes. + + Yields: + AnsiToken: A named tuple of (plain, sgr, osc) + """ + + position = 0 + sgr: Optional[str] + osc: Optional[str] + for match in re_ansi.finditer(ansi_text): + start, end = match.span(0) + osc, sgr = match.groups() + if start > position: + yield _AnsiToken(ansi_text[position:start]) + if sgr: + if sgr == "(": + position = end + 1 + continue + if sgr.endswith("m"): + yield _AnsiToken("", sgr[1:-1], osc) + else: + yield _AnsiToken("", sgr, osc) + position = end + if position < len(ansi_text): + yield _AnsiToken(ansi_text[position:]) + + +SGR_STYLE_MAP = { + 1: "bold", + 2: "dim", + 3: "italic", + 4: "underline", + 5: "blink", + 6: "blink2", + 7: "reverse", + 8: "conceal", + 9: "strike", + 21: "underline2", + 22: "not dim not bold", + 23: "not italic", + 24: "not underline", + 25: "not blink", + 26: "not blink2", + 27: "not reverse", + 28: "not conceal", + 29: "not strike", + 30: "color(0)", + 31: "color(1)", + 32: "color(2)", + 33: "color(3)", + 34: "color(4)", + 35: "color(5)", + 36: "color(6)", + 37: "color(7)", + 39: "default", + 40: "on color(0)", + 41: "on color(1)", + 42: "on color(2)", + 43: "on color(3)", + 44: "on color(4)", + 45: "on color(5)", + 46: "on color(6)", + 47: "on color(7)", + 49: "on default", + 51: "frame", + 52: "encircle", + 53: "overline", + 54: "not frame not encircle", + 55: "not overline", + 90: "color(8)", + 91: "color(9)", + 92: "color(10)", + 93: "color(11)", + 94: "color(12)", + 95: "color(13)", + 96: "color(14)", + 97: "color(15)", + 100: "on color(8)", + 101: "on color(9)", + 102: "on color(10)", + 103: "on color(11)", + 104: "on color(12)", + 105: "on color(13)", + 106: "on color(14)", + 107: "on color(15)", +} + + +class AnsiDecoder: + """Translate ANSI code in to styled Text.""" + + def __init__(self) -> None: + self.style = Style.null() + + def decode(self, terminal_text: str) -> Iterable[Text]: + """Decode ANSI codes in an iterable of lines. + + Args: + lines (Iterable[str]): An iterable of lines of terminal output. + + Yields: + Text: Marked up Text. + """ + for line in terminal_text.splitlines(): + yield self.decode_line(line) + + def decode_line(self, line: str) -> Text: + """Decode a line containing ansi codes. + + Args: + line (str): A line of terminal output. + + Returns: + Text: A Text instance marked up according to ansi codes. + """ + from_ansi = Color.from_ansi + from_rgb = Color.from_rgb + _Style = Style + text = Text() + append = text.append + line = line.rsplit("\r", 1)[-1] + for plain_text, sgr, osc in _ansi_tokenize(line): + if plain_text: + append(plain_text, self.style or None) + elif osc is not None: + if osc.startswith("8;"): + _params, semicolon, link = osc[2:].partition(";") + if semicolon: + self.style = self.style.update_link(link or None) + elif sgr is not None: + # Translate in to semi-colon separated codes + # Ignore invalid codes, because we want to be lenient + codes = [ + min(255, int(_code) if _code else 0) + for _code in sgr.split(";") + if _code.isdigit() or _code == "" + ] + iter_codes = iter(codes) + for code in iter_codes: + if code == 0: + # reset + self.style = _Style.null() + elif code in SGR_STYLE_MAP: + # styles + self.style += _Style.parse(SGR_STYLE_MAP[code]) + elif code == 38: + #  Foreground + with suppress(StopIteration): + color_type = next(iter_codes) + if color_type == 5: + self.style += _Style.from_color( + from_ansi(next(iter_codes)) + ) + elif color_type == 2: + self.style += _Style.from_color( + from_rgb( + next(iter_codes), + next(iter_codes), + next(iter_codes), + ) + ) + elif code == 48: + # Background + with suppress(StopIteration): + color_type = next(iter_codes) + if color_type == 5: + self.style += _Style.from_color( + None, from_ansi(next(iter_codes)) + ) + elif color_type == 2: + self.style += _Style.from_color( + None, + from_rgb( + next(iter_codes), + next(iter_codes), + next(iter_codes), + ), + ) + + return text + + +if sys.platform != "win32" and __name__ == "__main__": # pragma: no cover + import io + import os + import pty + import sys + + decoder = AnsiDecoder() + + stdout = io.BytesIO() + + def read(fd: int) -> bytes: + data = os.read(fd, 1024) + stdout.write(data) + return data + + pty.spawn(sys.argv[1:], read) + + from .console import Console + + console = Console(record=True) + + stdout_result = stdout.getvalue().decode("utf-8") + print(stdout_result) + + for line in decoder.decode(stdout_result): + console.print(line) + + console.save_html("stdout.html") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/bar.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/bar.py new file mode 100644 index 000000000..022284b57 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/bar.py @@ -0,0 +1,93 @@ +from typing import Optional, Union + +from .color import Color +from .console import Console, ConsoleOptions, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style + +# There are left-aligned characters for 1/8 to 7/8, but +# the right-aligned characters exist only for 1/8 and 4/8. +BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"] +END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"] +FULL_BLOCK = "█" + + +class Bar(JupyterMixin): + """Renders a solid block bar. + + Args: + size (float): Value for the end of the bar. + begin (float): Begin point (between 0 and size, inclusive). + end (float): End point (between 0 and size, inclusive). + width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None. + color (Union[Color, str], optional): Color of the bar. Defaults to "default". + bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default". + """ + + def __init__( + self, + size: float, + begin: float, + end: float, + *, + width: Optional[int] = None, + color: Union[Color, str] = "default", + bgcolor: Union[Color, str] = "default", + ): + self.size = size + self.begin = max(begin, 0) + self.end = min(end, size) + self.width = width + self.style = Style(color=color, bgcolor=bgcolor) + + def __repr__(self) -> str: + return f"Bar({self.size}, {self.begin}, {self.end})" + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + width = min( + self.width if self.width is not None else options.max_width, + options.max_width, + ) + + if self.begin >= self.end: + yield Segment(" " * width, self.style) + yield Segment.line() + return + + prefix_complete_eights = int(width * 8 * self.begin / self.size) + prefix_bar_count = prefix_complete_eights // 8 + prefix_eights_count = prefix_complete_eights % 8 + + body_complete_eights = int(width * 8 * self.end / self.size) + body_bar_count = body_complete_eights // 8 + body_eights_count = body_complete_eights % 8 + + # When start and end fall into the same cell, we ideally should render + # a symbol that's "center-aligned", but there is no good symbol in Unicode. + # In this case, we fall back to right-aligned block symbol for simplicity. + + prefix = " " * prefix_bar_count + if prefix_eights_count: + prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count] + + body = FULL_BLOCK * body_bar_count + if body_eights_count: + body += END_BLOCK_ELEMENTS[body_eights_count] + + suffix = " " * (width - len(body)) + + yield Segment(prefix + body[len(prefix) :] + suffix, self.style) + yield Segment.line() + + def __rich_measure__( + self, console: Console, options: ConsoleOptions + ) -> Measurement: + return ( + Measurement(self.width, self.width) + if self.width is not None + else Measurement(4, options.max_width) + ) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/box.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/box.py new file mode 100644 index 000000000..0511a9e48 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/box.py @@ -0,0 +1,480 @@ +import sys +from typing import TYPE_CHECKING, Iterable, List + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +from ._loop import loop_last + +if TYPE_CHECKING: + from pip._vendor.rich.console import ConsoleOptions + + +class Box: + """Defines characters to render boxes. + + ┌─┬┐ top + │ ││ head + ├─┼┤ head_row + │ ││ mid + ├─┼┤ row + ├─┼┤ foot_row + │ ││ foot + └─┴┘ bottom + + Args: + box (str): Characters making up box. + ascii (bool, optional): True if this box uses ascii characters only. Default is False. + """ + + def __init__(self, box: str, *, ascii: bool = False) -> None: + self._box = box + self.ascii = ascii + line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines() + # top + self.top_left, self.top, self.top_divider, self.top_right = iter(line1) + # head + self.head_left, _, self.head_vertical, self.head_right = iter(line2) + # head_row + ( + self.head_row_left, + self.head_row_horizontal, + self.head_row_cross, + self.head_row_right, + ) = iter(line3) + + # mid + self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4) + # row + self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5) + # foot_row + ( + self.foot_row_left, + self.foot_row_horizontal, + self.foot_row_cross, + self.foot_row_right, + ) = iter(line6) + # foot + self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7) + # bottom + self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter( + line8 + ) + + def __repr__(self) -> str: + return "Box(...)" + + def __str__(self) -> str: + return self._box + + def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box": + """Substitute this box for another if it won't render due to platform issues. + + Args: + options (ConsoleOptions): Console options used in rendering. + safe (bool, optional): Substitute this for another Box if there are known problems + displaying on the platform (currently only relevant on Windows). Default is True. + + Returns: + Box: A different Box or the same Box. + """ + box = self + if options.legacy_windows and safe: + box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box) + if options.ascii_only and not box.ascii: + box = ASCII + return box + + def get_plain_headed_box(self) -> "Box": + """If this box uses special characters for the borders of the header, then + return the equivalent box that does not. + + Returns: + Box: The most similar Box that doesn't use header-specific box characters. + If the current Box already satisfies this criterion, then it's returned. + """ + return PLAIN_HEADED_SUBSTITUTIONS.get(self, self) + + def get_top(self, widths: Iterable[int]) -> str: + """Get the top of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.top_left) + for last, width in loop_last(widths): + append(self.top * width) + if not last: + append(self.top_divider) + append(self.top_right) + return "".join(parts) + + def get_row( + self, + widths: Iterable[int], + level: Literal["head", "row", "foot", "mid"] = "row", + edge: bool = True, + ) -> str: + """Get the top of a simple box. + + Args: + width (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + if level == "head": + left = self.head_row_left + horizontal = self.head_row_horizontal + cross = self.head_row_cross + right = self.head_row_right + elif level == "row": + left = self.row_left + horizontal = self.row_horizontal + cross = self.row_cross + right = self.row_right + elif level == "mid": + left = self.mid_left + horizontal = " " + cross = self.mid_vertical + right = self.mid_right + elif level == "foot": + left = self.foot_row_left + horizontal = self.foot_row_horizontal + cross = self.foot_row_cross + right = self.foot_row_right + else: + raise ValueError("level must be 'head', 'row' or 'foot'") + + parts: List[str] = [] + append = parts.append + if edge: + append(left) + for last, width in loop_last(widths): + append(horizontal * width) + if not last: + append(cross) + if edge: + append(right) + return "".join(parts) + + def get_bottom(self, widths: Iterable[int]) -> str: + """Get the bottom of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.bottom_left) + for last, width in loop_last(widths): + append(self.bottom * width) + if not last: + append(self.bottom_divider) + append(self.bottom_right) + return "".join(parts) + + +# fmt: off +ASCII: Box = Box( + "+--+\n" + "| ||\n" + "|-+|\n" + "| ||\n" + "|-+|\n" + "|-+|\n" + "| ||\n" + "+--+\n", + ascii=True, +) + +ASCII2: Box = Box( + "+-++\n" + "| ||\n" + "+-++\n" + "| ||\n" + "+-++\n" + "+-++\n" + "| ||\n" + "+-++\n", + ascii=True, +) + +ASCII_DOUBLE_HEAD: Box = Box( + "+-++\n" + "| ||\n" + "+=++\n" + "| ||\n" + "+-++\n" + "+-++\n" + "| ||\n" + "+-++\n", + ascii=True, +) + +SQUARE: Box = Box( + "┌─┬┐\n" + "│ ││\n" + "├─┼┤\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "└─┴┘\n" +) + +SQUARE_DOUBLE_HEAD: Box = Box( + "┌─┬┐\n" + "│ ││\n" + "╞═╪╡\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "└─┴┘\n" +) + +MINIMAL: Box = Box( + " ╷ \n" + " │ \n" + "╶─┼╴\n" + " │ \n" + "╶─┼╴\n" + "╶─┼╴\n" + " │ \n" + " ╵ \n" +) + + +MINIMAL_HEAVY_HEAD: Box = Box( + " ╷ \n" + " │ \n" + "╺━┿╸\n" + " │ \n" + "╶─┼╴\n" + "╶─┼╴\n" + " │ \n" + " ╵ \n" +) + +MINIMAL_DOUBLE_HEAD: Box = Box( + " ╷ \n" + " │ \n" + " ═╪ \n" + " │ \n" + " ─┼ \n" + " ─┼ \n" + " │ \n" + " ╵ \n" +) + + +SIMPLE: Box = Box( + " \n" + " \n" + " ── \n" + " \n" + " \n" + " ── \n" + " \n" + " \n" +) + +SIMPLE_HEAD: Box = Box( + " \n" + " \n" + " ── \n" + " \n" + " \n" + " \n" + " \n" + " \n" +) + + +SIMPLE_HEAVY: Box = Box( + " \n" + " \n" + " ━━ \n" + " \n" + " \n" + " ━━ \n" + " \n" + " \n" +) + + +HORIZONTALS: Box = Box( + " ── \n" + " \n" + " ── \n" + " \n" + " ── \n" + " ── \n" + " \n" + " ── \n" +) + +ROUNDED: Box = Box( + "╭─┬╮\n" + "│ ││\n" + "├─┼┤\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "╰─┴╯\n" +) + +HEAVY: Box = Box( + "┏━┳┓\n" + "┃ ┃┃\n" + "┣━╋┫\n" + "┃ ┃┃\n" + "┣━╋┫\n" + "┣━╋┫\n" + "┃ ┃┃\n" + "┗━┻┛\n" +) + +HEAVY_EDGE: Box = Box( + "┏━┯┓\n" + "┃ │┃\n" + "┠─┼┨\n" + "┃ │┃\n" + "┠─┼┨\n" + "┠─┼┨\n" + "┃ │┃\n" + "┗━┷┛\n" +) + +HEAVY_HEAD: Box = Box( + "┏━┳┓\n" + "┃ ┃┃\n" + "┡━╇┩\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "└─┴┘\n" +) + +DOUBLE: Box = Box( + "╔═╦╗\n" + "║ ║║\n" + "╠═╬╣\n" + "║ ║║\n" + "╠═╬╣\n" + "╠═╬╣\n" + "║ ║║\n" + "╚═╩╝\n" +) + +DOUBLE_EDGE: Box = Box( + "╔═╤╗\n" + "║ │║\n" + "╟─┼╢\n" + "║ │║\n" + "╟─┼╢\n" + "╟─┼╢\n" + "║ │║\n" + "╚═╧╝\n" +) + +MARKDOWN: Box = Box( + " \n" + "| ||\n" + "|-||\n" + "| ||\n" + "|-||\n" + "|-||\n" + "| ||\n" + " \n", + ascii=True, +) +# fmt: on + +# Map Boxes that don't render with raster fonts on to equivalent that do +LEGACY_WINDOWS_SUBSTITUTIONS = { + ROUNDED: SQUARE, + MINIMAL_HEAVY_HEAD: MINIMAL, + SIMPLE_HEAVY: SIMPLE, + HEAVY: SQUARE, + HEAVY_EDGE: SQUARE, + HEAVY_HEAD: SQUARE, +} + +# Map headed boxes to their headerless equivalents +PLAIN_HEADED_SUBSTITUTIONS = { + HEAVY_HEAD: SQUARE, + SQUARE_DOUBLE_HEAD: SQUARE, + MINIMAL_DOUBLE_HEAD: MINIMAL, + MINIMAL_HEAVY_HEAD: MINIMAL, + ASCII_DOUBLE_HEAD: ASCII2, +} + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.columns import Columns + from pip._vendor.rich.panel import Panel + + from . import box as box + from .console import Console + from .table import Table + from .text import Text + + console = Console(record=True) + + BOXES = [ + "ASCII", + "ASCII2", + "ASCII_DOUBLE_HEAD", + "SQUARE", + "SQUARE_DOUBLE_HEAD", + "MINIMAL", + "MINIMAL_HEAVY_HEAD", + "MINIMAL_DOUBLE_HEAD", + "SIMPLE", + "SIMPLE_HEAD", + "SIMPLE_HEAVY", + "HORIZONTALS", + "ROUNDED", + "HEAVY", + "HEAVY_EDGE", + "HEAVY_HEAD", + "DOUBLE", + "DOUBLE_EDGE", + "MARKDOWN", + ] + + console.print(Panel("[bold green]Box Constants", style="green"), justify="center") + console.print() + + columns = Columns(expand=True, padding=2) + for box_name in sorted(BOXES): + table = Table( + show_footer=True, style="dim", border_style="not dim", expand=True + ) + table.add_column("Header 1", "Footer 1") + table.add_column("Header 2", "Footer 2") + table.add_row("Cell", "Cell") + table.add_row("Cell", "Cell") + table.box = getattr(box, box_name) + table.title = Text(f"box.{box_name}", style="magenta") + columns.add_renderable(table) + console.print(columns) + + # console.save_svg("box.svg") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/cells.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/cells.py new file mode 100644 index 000000000..a85462271 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/cells.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +from functools import lru_cache +from typing import Callable + +from ._cell_widths import CELL_WIDTHS + +# Ranges of unicode ordinals that produce a 1-cell wide character +# This is non-exhaustive, but covers most common Western characters +_SINGLE_CELL_UNICODE_RANGES: list[tuple[int, int]] = [ + (0x20, 0x7E), # Latin (excluding non-printable) + (0xA0, 0xAC), + (0xAE, 0x002FF), + (0x00370, 0x00482), # Greek / Cyrillic + (0x02500, 0x025FC), # Box drawing, box elements, geometric shapes + (0x02800, 0x028FF), # Braille +] + +# A set of characters that are a single cell wide +_SINGLE_CELLS = frozenset( + [ + character + for _start, _end in _SINGLE_CELL_UNICODE_RANGES + for character in map(chr, range(_start, _end + 1)) + ] +) + +# When called with a string this will return True if all +# characters are single-cell, otherwise False +_is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset + + +@lru_cache(4096) +def cached_cell_len(text: str) -> int: + """Get the number of cells required to display text. + + This method always caches, which may use up a lot of memory. It is recommended to use + `cell_len` over this method. + + Args: + text (str): Text to display. + + Returns: + int: Get the number of cells required to display text. + """ + if _is_single_cell_widths(text): + return len(text) + return sum(map(get_character_cell_size, text)) + + +def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int: + """Get the number of cells required to display text. + + Args: + text (str): Text to display. + + Returns: + int: Get the number of cells required to display text. + """ + if len(text) < 512: + return _cell_len(text) + if _is_single_cell_widths(text): + return len(text) + return sum(map(get_character_cell_size, text)) + + +@lru_cache(maxsize=4096) +def get_character_cell_size(character: str) -> int: + """Get the cell size of a character. + + Args: + character (str): A single character. + + Returns: + int: Number of cells (0, 1 or 2) occupied by that character. + """ + codepoint = ord(character) + _table = CELL_WIDTHS + lower_bound = 0 + upper_bound = len(_table) - 1 + index = (lower_bound + upper_bound) // 2 + while True: + start, end, width = _table[index] + if codepoint < start: + upper_bound = index - 1 + elif codepoint > end: + lower_bound = index + 1 + else: + return 0 if width == -1 else width + if upper_bound < lower_bound: + break + index = (lower_bound + upper_bound) // 2 + return 1 + + +def set_cell_size(text: str, total: int) -> str: + """Set the length of a string to fit within given number of cells.""" + + if _is_single_cell_widths(text): + size = len(text) + if size < total: + return text + " " * (total - size) + return text[:total] + + if total <= 0: + return "" + cell_size = cell_len(text) + if cell_size == total: + return text + if cell_size < total: + return text + " " * (total - cell_size) + + start = 0 + end = len(text) + + # Binary search until we find the right size + while True: + pos = (start + end) // 2 + before = text[: pos + 1] + before_len = cell_len(before) + if before_len == total + 1 and cell_len(before[-1]) == 2: + return before[:-1] + " " + if before_len == total: + return before + if before_len > total: + end = pos + else: + start = pos + + +def chop_cells( + text: str, + width: int, +) -> list[str]: + """Split text into lines such that each line fits within the available (cell) width. + + Args: + text: The text to fold such that it fits in the given width. + width: The width available (number of cells). + + Returns: + A list of strings such that each string in the list has cell width + less than or equal to the available width. + """ + _get_character_cell_size = get_character_cell_size + lines: list[list[str]] = [[]] + + append_new_line = lines.append + append_to_last_line = lines[-1].append + + total_width = 0 + + for character in text: + cell_width = _get_character_cell_size(character) + char_doesnt_fit = total_width + cell_width > width + + if char_doesnt_fit: + append_new_line([character]) + append_to_last_line = lines[-1].append + total_width = cell_width + else: + append_to_last_line(character) + total_width += cell_width + + return ["".join(line) for line in lines] + + +if __name__ == "__main__": # pragma: no cover + print(get_character_cell_size("😽")) + for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8): + print(line) + for n in range(80, 1, -1): + print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|") + print("x" * n) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/color.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/color.py new file mode 100644 index 000000000..e2c23a6a9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/color.py @@ -0,0 +1,621 @@ +import re +import sys +from colorsys import rgb_to_hls +from enum import IntEnum +from functools import lru_cache +from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple + +from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE +from .color_triplet import ColorTriplet +from .repr import Result, rich_repr +from .terminal_theme import DEFAULT_TERMINAL_THEME + +if TYPE_CHECKING: # pragma: no cover + from .terminal_theme import TerminalTheme + from .text import Text + + +WINDOWS = sys.platform == "win32" + + +class ColorSystem(IntEnum): + """One of the 3 color system supported by terminals.""" + + STANDARD = 1 + EIGHT_BIT = 2 + TRUECOLOR = 3 + WINDOWS = 4 + + def __repr__(self) -> str: + return f"ColorSystem.{self.name}" + + def __str__(self) -> str: + return repr(self) + + +class ColorType(IntEnum): + """Type of color stored in Color class.""" + + DEFAULT = 0 + STANDARD = 1 + EIGHT_BIT = 2 + TRUECOLOR = 3 + WINDOWS = 4 + + def __repr__(self) -> str: + return f"ColorType.{self.name}" + + +ANSI_COLOR_NAMES = { + "black": 0, + "red": 1, + "green": 2, + "yellow": 3, + "blue": 4, + "magenta": 5, + "cyan": 6, + "white": 7, + "bright_black": 8, + "bright_red": 9, + "bright_green": 10, + "bright_yellow": 11, + "bright_blue": 12, + "bright_magenta": 13, + "bright_cyan": 14, + "bright_white": 15, + "grey0": 16, + "gray0": 16, + "navy_blue": 17, + "dark_blue": 18, + "blue3": 20, + "blue1": 21, + "dark_green": 22, + "deep_sky_blue4": 25, + "dodger_blue3": 26, + "dodger_blue2": 27, + "green4": 28, + "spring_green4": 29, + "turquoise4": 30, + "deep_sky_blue3": 32, + "dodger_blue1": 33, + "green3": 40, + "spring_green3": 41, + "dark_cyan": 36, + "light_sea_green": 37, + "deep_sky_blue2": 38, + "deep_sky_blue1": 39, + "spring_green2": 47, + "cyan3": 43, + "dark_turquoise": 44, + "turquoise2": 45, + "green1": 46, + "spring_green1": 48, + "medium_spring_green": 49, + "cyan2": 50, + "cyan1": 51, + "dark_red": 88, + "deep_pink4": 125, + "purple4": 55, + "purple3": 56, + "blue_violet": 57, + "orange4": 94, + "grey37": 59, + "gray37": 59, + "medium_purple4": 60, + "slate_blue3": 62, + "royal_blue1": 63, + "chartreuse4": 64, + "dark_sea_green4": 71, + "pale_turquoise4": 66, + "steel_blue": 67, + "steel_blue3": 68, + "cornflower_blue": 69, + "chartreuse3": 76, + "cadet_blue": 73, + "sky_blue3": 74, + "steel_blue1": 81, + "pale_green3": 114, + "sea_green3": 78, + "aquamarine3": 79, + "medium_turquoise": 80, + "chartreuse2": 112, + "sea_green2": 83, + "sea_green1": 85, + "aquamarine1": 122, + "dark_slate_gray2": 87, + "dark_magenta": 91, + "dark_violet": 128, + "purple": 129, + "light_pink4": 95, + "plum4": 96, + "medium_purple3": 98, + "slate_blue1": 99, + "yellow4": 106, + "wheat4": 101, + "grey53": 102, + "gray53": 102, + "light_slate_grey": 103, + "light_slate_gray": 103, + "medium_purple": 104, + "light_slate_blue": 105, + "dark_olive_green3": 149, + "dark_sea_green": 108, + "light_sky_blue3": 110, + "sky_blue2": 111, + "dark_sea_green3": 150, + "dark_slate_gray3": 116, + "sky_blue1": 117, + "chartreuse1": 118, + "light_green": 120, + "pale_green1": 156, + "dark_slate_gray1": 123, + "red3": 160, + "medium_violet_red": 126, + "magenta3": 164, + "dark_orange3": 166, + "indian_red": 167, + "hot_pink3": 168, + "medium_orchid3": 133, + "medium_orchid": 134, + "medium_purple2": 140, + "dark_goldenrod": 136, + "light_salmon3": 173, + "rosy_brown": 138, + "grey63": 139, + "gray63": 139, + "medium_purple1": 141, + "gold3": 178, + "dark_khaki": 143, + "navajo_white3": 144, + "grey69": 145, + "gray69": 145, + "light_steel_blue3": 146, + "light_steel_blue": 147, + "yellow3": 184, + "dark_sea_green2": 157, + "light_cyan3": 152, + "light_sky_blue1": 153, + "green_yellow": 154, + "dark_olive_green2": 155, + "dark_sea_green1": 193, + "pale_turquoise1": 159, + "deep_pink3": 162, + "magenta2": 200, + "hot_pink2": 169, + "orchid": 170, + "medium_orchid1": 207, + "orange3": 172, + "light_pink3": 174, + "pink3": 175, + "plum3": 176, + "violet": 177, + "light_goldenrod3": 179, + "tan": 180, + "misty_rose3": 181, + "thistle3": 182, + "plum2": 183, + "khaki3": 185, + "light_goldenrod2": 222, + "light_yellow3": 187, + "grey84": 188, + "gray84": 188, + "light_steel_blue1": 189, + "yellow2": 190, + "dark_olive_green1": 192, + "honeydew2": 194, + "light_cyan1": 195, + "red1": 196, + "deep_pink2": 197, + "deep_pink1": 199, + "magenta1": 201, + "orange_red1": 202, + "indian_red1": 204, + "hot_pink": 206, + "dark_orange": 208, + "salmon1": 209, + "light_coral": 210, + "pale_violet_red1": 211, + "orchid2": 212, + "orchid1": 213, + "orange1": 214, + "sandy_brown": 215, + "light_salmon1": 216, + "light_pink1": 217, + "pink1": 218, + "plum1": 219, + "gold1": 220, + "navajo_white1": 223, + "misty_rose1": 224, + "thistle1": 225, + "yellow1": 226, + "light_goldenrod1": 227, + "khaki1": 228, + "wheat1": 229, + "cornsilk1": 230, + "grey100": 231, + "gray100": 231, + "grey3": 232, + "gray3": 232, + "grey7": 233, + "gray7": 233, + "grey11": 234, + "gray11": 234, + "grey15": 235, + "gray15": 235, + "grey19": 236, + "gray19": 236, + "grey23": 237, + "gray23": 237, + "grey27": 238, + "gray27": 238, + "grey30": 239, + "gray30": 239, + "grey35": 240, + "gray35": 240, + "grey39": 241, + "gray39": 241, + "grey42": 242, + "gray42": 242, + "grey46": 243, + "gray46": 243, + "grey50": 244, + "gray50": 244, + "grey54": 245, + "gray54": 245, + "grey58": 246, + "gray58": 246, + "grey62": 247, + "gray62": 247, + "grey66": 248, + "gray66": 248, + "grey70": 249, + "gray70": 249, + "grey74": 250, + "gray74": 250, + "grey78": 251, + "gray78": 251, + "grey82": 252, + "gray82": 252, + "grey85": 253, + "gray85": 253, + "grey89": 254, + "gray89": 254, + "grey93": 255, + "gray93": 255, +} + + +class ColorParseError(Exception): + """The color could not be parsed.""" + + +RE_COLOR = re.compile( + r"""^ +\#([0-9a-f]{6})$| +color\(([0-9]{1,3})\)$| +rgb\(([\d\s,]+)\)$ +""", + re.VERBOSE, +) + + +@rich_repr +class Color(NamedTuple): + """Terminal color definition.""" + + name: str + """The name of the color (typically the input to Color.parse).""" + type: ColorType + """The type of the color.""" + number: Optional[int] = None + """The color number, if a standard color, or None.""" + triplet: Optional[ColorTriplet] = None + """A triplet of color components, if an RGB color.""" + + def __rich__(self) -> "Text": + """Displays the actual color if Rich printed.""" + from .style import Style + from .text import Text + + return Text.assemble( + f"", + ) + + def __rich_repr__(self) -> Result: + yield self.name + yield self.type + yield "number", self.number, None + yield "triplet", self.triplet, None + + @property + def system(self) -> ColorSystem: + """Get the native color system for this color.""" + if self.type == ColorType.DEFAULT: + return ColorSystem.STANDARD + return ColorSystem(int(self.type)) + + @property + def is_system_defined(self) -> bool: + """Check if the color is ultimately defined by the system.""" + return self.system not in (ColorSystem.EIGHT_BIT, ColorSystem.TRUECOLOR) + + @property + def is_default(self) -> bool: + """Check if the color is a default color.""" + return self.type == ColorType.DEFAULT + + def get_truecolor( + self, theme: Optional["TerminalTheme"] = None, foreground: bool = True + ) -> ColorTriplet: + """Get an equivalent color triplet for this color. + + Args: + theme (TerminalTheme, optional): Optional terminal theme, or None to use default. Defaults to None. + foreground (bool, optional): True for a foreground color, or False for background. Defaults to True. + + Returns: + ColorTriplet: A color triplet containing RGB components. + """ + + if theme is None: + theme = DEFAULT_TERMINAL_THEME + if self.type == ColorType.TRUECOLOR: + assert self.triplet is not None + return self.triplet + elif self.type == ColorType.EIGHT_BIT: + assert self.number is not None + return EIGHT_BIT_PALETTE[self.number] + elif self.type == ColorType.STANDARD: + assert self.number is not None + return theme.ansi_colors[self.number] + elif self.type == ColorType.WINDOWS: + assert self.number is not None + return WINDOWS_PALETTE[self.number] + else: # self.type == ColorType.DEFAULT: + assert self.number is None + return theme.foreground_color if foreground else theme.background_color + + @classmethod + def from_ansi(cls, number: int) -> "Color": + """Create a Color number from it's 8-bit ansi number. + + Args: + number (int): A number between 0-255 inclusive. + + Returns: + Color: A new Color instance. + """ + return cls( + name=f"color({number})", + type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT), + number=number, + ) + + @classmethod + def from_triplet(cls, triplet: "ColorTriplet") -> "Color": + """Create a truecolor RGB color from a triplet of values. + + Args: + triplet (ColorTriplet): A color triplet containing red, green and blue components. + + Returns: + Color: A new color object. + """ + return cls(name=triplet.hex, type=ColorType.TRUECOLOR, triplet=triplet) + + @classmethod + def from_rgb(cls, red: float, green: float, blue: float) -> "Color": + """Create a truecolor from three color components in the range(0->255). + + Args: + red (float): Red component in range 0-255. + green (float): Green component in range 0-255. + blue (float): Blue component in range 0-255. + + Returns: + Color: A new color object. + """ + return cls.from_triplet(ColorTriplet(int(red), int(green), int(blue))) + + @classmethod + def default(cls) -> "Color": + """Get a Color instance representing the default color. + + Returns: + Color: Default color. + """ + return cls(name="default", type=ColorType.DEFAULT) + + @classmethod + @lru_cache(maxsize=1024) + def parse(cls, color: str) -> "Color": + """Parse a color definition.""" + original_color = color + color = color.lower().strip() + + if color == "default": + return cls(color, type=ColorType.DEFAULT) + + color_number = ANSI_COLOR_NAMES.get(color) + if color_number is not None: + return cls( + color, + type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT), + number=color_number, + ) + + color_match = RE_COLOR.match(color) + if color_match is None: + raise ColorParseError(f"{original_color!r} is not a valid color") + + color_24, color_8, color_rgb = color_match.groups() + if color_24: + triplet = ColorTriplet( + int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16) + ) + return cls(color, ColorType.TRUECOLOR, triplet=triplet) + + elif color_8: + number = int(color_8) + if number > 255: + raise ColorParseError(f"color number must be <= 255 in {color!r}") + return cls( + color, + type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT), + number=number, + ) + + else: # color_rgb: + components = color_rgb.split(",") + if len(components) != 3: + raise ColorParseError( + f"expected three components in {original_color!r}" + ) + red, green, blue = components + triplet = ColorTriplet(int(red), int(green), int(blue)) + if not all(component <= 255 for component in triplet): + raise ColorParseError( + f"color components must be <= 255 in {original_color!r}" + ) + return cls(color, ColorType.TRUECOLOR, triplet=triplet) + + @lru_cache(maxsize=1024) + def get_ansi_codes(self, foreground: bool = True) -> Tuple[str, ...]: + """Get the ANSI escape codes for this color.""" + _type = self.type + if _type == ColorType.DEFAULT: + return ("39" if foreground else "49",) + + elif _type == ColorType.WINDOWS: + number = self.number + assert number is not None + fore, back = (30, 40) if number < 8 else (82, 92) + return (str(fore + number if foreground else back + number),) + + elif _type == ColorType.STANDARD: + number = self.number + assert number is not None + fore, back = (30, 40) if number < 8 else (82, 92) + return (str(fore + number if foreground else back + number),) + + elif _type == ColorType.EIGHT_BIT: + assert self.number is not None + return ("38" if foreground else "48", "5", str(self.number)) + + else: # self.standard == ColorStandard.TRUECOLOR: + assert self.triplet is not None + red, green, blue = self.triplet + return ("38" if foreground else "48", "2", str(red), str(green), str(blue)) + + @lru_cache(maxsize=1024) + def downgrade(self, system: ColorSystem) -> "Color": + """Downgrade a color system to a system with fewer colors.""" + + if self.type in (ColorType.DEFAULT, system): + return self + # Convert to 8-bit color from truecolor color + if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + _h, l, s = rgb_to_hls(*self.triplet.normalized) + # If saturation is under 15% assume it is grayscale + if s < 0.15: + gray = round(l * 25.0) + if gray == 0: + color_number = 16 + elif gray == 25: + color_number = 231 + else: + color_number = 231 + gray + return Color(self.name, ColorType.EIGHT_BIT, number=color_number) + + red, green, blue = self.triplet + six_red = red / 95 if red < 95 else 1 + (red - 95) / 40 + six_green = green / 95 if green < 95 else 1 + (green - 95) / 40 + six_blue = blue / 95 if blue < 95 else 1 + (blue - 95) / 40 + + color_number = ( + 16 + 36 * round(six_red) + 6 * round(six_green) + round(six_blue) + ) + return Color(self.name, ColorType.EIGHT_BIT, number=color_number) + + # Convert to standard from truecolor or 8-bit + elif system == ColorSystem.STANDARD: + if self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + triplet = self.triplet + else: # self.system == ColorSystem.EIGHT_BIT + assert self.number is not None + triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number]) + + color_number = STANDARD_PALETTE.match(triplet) + return Color(self.name, ColorType.STANDARD, number=color_number) + + elif system == ColorSystem.WINDOWS: + if self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + triplet = self.triplet + else: # self.system == ColorSystem.EIGHT_BIT + assert self.number is not None + if self.number < 16: + return Color(self.name, ColorType.WINDOWS, number=self.number) + triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number]) + + color_number = WINDOWS_PALETTE.match(triplet) + return Color(self.name, ColorType.WINDOWS, number=color_number) + + return self + + +def parse_rgb_hex(hex_color: str) -> ColorTriplet: + """Parse six hex characters in to RGB triplet.""" + assert len(hex_color) == 6, "must be 6 characters" + color = ColorTriplet( + int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) + ) + return color + + +def blend_rgb( + color1: ColorTriplet, color2: ColorTriplet, cross_fade: float = 0.5 +) -> ColorTriplet: + """Blend one RGB color in to another.""" + r1, g1, b1 = color1 + r2, g2, b2 = color2 + new_color = ColorTriplet( + int(r1 + (r2 - r1) * cross_fade), + int(g1 + (g2 - g1) * cross_fade), + int(b1 + (b2 - b1) * cross_fade), + ) + return new_color + + +if __name__ == "__main__": # pragma: no cover + from .console import Console + from .table import Table + from .text import Text + + console = Console() + + table = Table(show_footer=False, show_edge=True) + table.add_column("Color", width=10, overflow="ellipsis") + table.add_column("Number", justify="right", style="yellow") + table.add_column("Name", style="green") + table.add_column("Hex", style="blue") + table.add_column("RGB", style="magenta") + + colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items()) + for color_number, name in colors: + if "grey" in name: + continue + color_cell = Text(" " * 10, style=f"on {name}") + if color_number < 16: + table.add_row(color_cell, f"{color_number}", Text(f'"{name}"')) + else: + color = EIGHT_BIT_PALETTE[color_number] # type: ignore[has-type] + table.add_row( + color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb + ) + + console.print(table) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/color_triplet.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/color_triplet.py new file mode 100644 index 000000000..02cab3282 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/color_triplet.py @@ -0,0 +1,38 @@ +from typing import NamedTuple, Tuple + + +class ColorTriplet(NamedTuple): + """The red, green, and blue components of a color.""" + + red: int + """Red component in 0 to 255 range.""" + green: int + """Green component in 0 to 255 range.""" + blue: int + """Blue component in 0 to 255 range.""" + + @property + def hex(self) -> str: + """get the color triplet in CSS style.""" + red, green, blue = self + return f"#{red:02x}{green:02x}{blue:02x}" + + @property + def rgb(self) -> str: + """The color in RGB format. + + Returns: + str: An rgb color, e.g. ``"rgb(100,23,255)"``. + """ + red, green, blue = self + return f"rgb({red},{green},{blue})" + + @property + def normalized(self) -> Tuple[float, float, float]: + """Convert components into floats between 0 and 1. + + Returns: + Tuple[float, float, float]: A tuple of three normalized colour components. + """ + red, green, blue = self + return red / 255.0, green / 255.0, blue / 255.0 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/columns.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/columns.py new file mode 100644 index 000000000..669a3a707 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/columns.py @@ -0,0 +1,187 @@ +from collections import defaultdict +from itertools import chain +from operator import itemgetter +from typing import Dict, Iterable, List, Optional, Tuple + +from .align import Align, AlignMethod +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .constrain import Constrain +from .measure import Measurement +from .padding import Padding, PaddingDimensions +from .table import Table +from .text import TextType +from .jupyter import JupyterMixin + + +class Columns(JupyterMixin): + """Display renderables in neat columns. + + Args: + renderables (Iterable[RenderableType]): Any number of Rich renderables (including str). + width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None. + padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1). + expand (bool, optional): Expand columns to full width. Defaults to False. + equal (bool, optional): Arrange in to equal sized columns. Defaults to False. + column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False. + right_to_left (bool, optional): Start column from right hand side. Defaults to False. + align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None. + title (TextType, optional): Optional title for Columns. + """ + + def __init__( + self, + renderables: Optional[Iterable[RenderableType]] = None, + padding: PaddingDimensions = (0, 1), + *, + width: Optional[int] = None, + expand: bool = False, + equal: bool = False, + column_first: bool = False, + right_to_left: bool = False, + align: Optional[AlignMethod] = None, + title: Optional[TextType] = None, + ) -> None: + self.renderables = list(renderables or []) + self.width = width + self.padding = padding + self.expand = expand + self.equal = equal + self.column_first = column_first + self.right_to_left = right_to_left + self.align: Optional[AlignMethod] = align + self.title = title + + def add_renderable(self, renderable: RenderableType) -> None: + """Add a renderable to the columns. + + Args: + renderable (RenderableType): Any renderable object. + """ + self.renderables.append(renderable) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + render_str = console.render_str + renderables = [ + render_str(renderable) if isinstance(renderable, str) else renderable + for renderable in self.renderables + ] + if not renderables: + return + _top, right, _bottom, left = Padding.unpack(self.padding) + width_padding = max(left, right) + max_width = options.max_width + widths: Dict[int, int] = defaultdict(int) + column_count = len(renderables) + + get_measurement = Measurement.get + renderable_widths = [ + get_measurement(console, options, renderable).maximum + for renderable in renderables + ] + if self.equal: + renderable_widths = [max(renderable_widths)] * len(renderable_widths) + + def iter_renderables( + column_count: int, + ) -> Iterable[Tuple[int, Optional[RenderableType]]]: + item_count = len(renderables) + if self.column_first: + width_renderables = list(zip(renderable_widths, renderables)) + + column_lengths: List[int] = [item_count // column_count] * column_count + for col_no in range(item_count % column_count): + column_lengths[col_no] += 1 + + row_count = (item_count + column_count - 1) // column_count + cells = [[-1] * column_count for _ in range(row_count)] + row = col = 0 + for index in range(item_count): + cells[row][col] = index + column_lengths[col] -= 1 + if column_lengths[col]: + row += 1 + else: + col += 1 + row = 0 + for index in chain.from_iterable(cells): + if index == -1: + break + yield width_renderables[index] + else: + yield from zip(renderable_widths, renderables) + # Pad odd elements with spaces + if item_count % column_count: + for _ in range(column_count - (item_count % column_count)): + yield 0, None + + table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False) + table.expand = self.expand + table.title = self.title + + if self.width is not None: + column_count = (max_width) // (self.width + width_padding) + for _ in range(column_count): + table.add_column(width=self.width) + else: + while column_count > 1: + widths.clear() + column_no = 0 + for renderable_width, _ in iter_renderables(column_count): + widths[column_no] = max(widths[column_no], renderable_width) + total_width = sum(widths.values()) + width_padding * ( + len(widths) - 1 + ) + if total_width > max_width: + column_count = len(widths) - 1 + break + else: + column_no = (column_no + 1) % column_count + else: + break + + get_renderable = itemgetter(1) + _renderables = [ + get_renderable(_renderable) + for _renderable in iter_renderables(column_count) + ] + if self.equal: + _renderables = [ + None + if renderable is None + else Constrain(renderable, renderable_widths[0]) + for renderable in _renderables + ] + if self.align: + align = self.align + _Align = Align + _renderables = [ + None if renderable is None else _Align(renderable, align) + for renderable in _renderables + ] + + right_to_left = self.right_to_left + add_row = table.add_row + for start in range(0, len(_renderables), column_count): + row = _renderables[start : start + column_count] + if right_to_left: + row = row[::-1] + add_row(*row) + yield table + + +if __name__ == "__main__": # pragma: no cover + import os + + console = Console() + + files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))] + columns = Columns(files, padding=(0, 1), expand=False, equal=False) + console.print(columns) + console.rule() + columns.column_first = True + console.print(columns) + columns.right_to_left = True + console.rule() + console.print(columns) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/console.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/console.py new file mode 100644 index 000000000..572884542 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/console.py @@ -0,0 +1,2661 @@ +import inspect +import os +import sys +import threading +import zlib +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from datetime import datetime +from functools import wraps +from getpass import getpass +from html import escape +from inspect import isclass +from itertools import islice +from math import ceil +from time import monotonic +from types import FrameType, ModuleType, TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + Mapping, + NamedTuple, + Optional, + TextIO, + Tuple, + Type, + Union, + cast, +) + +from pip._vendor.rich._null_file import NULL_FILE + +if sys.version_info >= (3, 8): + from typing import Literal, Protocol, runtime_checkable +else: + from pip._vendor.typing_extensions import ( + Literal, + Protocol, + runtime_checkable, + ) # pragma: no cover + +from . import errors, themes +from ._emoji_replace import _emoji_replace +from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT +from ._fileno import get_fileno +from ._log_render import FormatTimeCallable, LogRender +from .align import Align, AlignMethod +from .color import ColorSystem, blend_rgb +from .control import Control +from .emoji import EmojiVariant +from .highlighter import NullHighlighter, ReprHighlighter +from .markup import render as render_markup +from .measure import Measurement, measure_renderables +from .pager import Pager, SystemPager +from .pretty import Pretty, is_expandable +from .protocol import rich_cast +from .region import Region +from .scope import render_scope +from .screen import Screen +from .segment import Segment +from .style import Style, StyleType +from .styled import Styled +from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme +from .text import Text, TextType +from .theme import Theme, ThemeStack + +if TYPE_CHECKING: + from ._windows import WindowsConsoleFeatures + from .live import Live + from .status import Status + +JUPYTER_DEFAULT_COLUMNS = 115 +JUPYTER_DEFAULT_LINES = 100 +WINDOWS = sys.platform == "win32" + +HighlighterType = Callable[[Union[str, "Text"]], "Text"] +JustifyMethod = Literal["default", "left", "center", "right", "full"] +OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"] + + +class NoChange: + pass + + +NO_CHANGE = NoChange() + +try: + _STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr] +except Exception: + _STDIN_FILENO = 0 +try: + _STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr] +except Exception: + _STDOUT_FILENO = 1 +try: + _STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr] +except Exception: + _STDERR_FILENO = 2 + +_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO) +_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO) + + +_TERM_COLORS = { + "kitty": ColorSystem.EIGHT_BIT, + "256color": ColorSystem.EIGHT_BIT, + "16color": ColorSystem.STANDARD, +} + + +class ConsoleDimensions(NamedTuple): + """Size of the terminal.""" + + width: int + """The width of the console in 'cells'.""" + height: int + """The height of the console in lines.""" + + +@dataclass +class ConsoleOptions: + """Options for __rich_console__ method.""" + + size: ConsoleDimensions + """Size of console.""" + legacy_windows: bool + """legacy_windows: flag for legacy windows.""" + min_width: int + """Minimum width of renderable.""" + max_width: int + """Maximum width of renderable.""" + is_terminal: bool + """True if the target is a terminal, otherwise False.""" + encoding: str + """Encoding of terminal.""" + max_height: int + """Height of container (starts as terminal)""" + justify: Optional[JustifyMethod] = None + """Justify value override for renderable.""" + overflow: Optional[OverflowMethod] = None + """Overflow value override for renderable.""" + no_wrap: Optional[bool] = False + """Disable wrapping for text.""" + highlight: Optional[bool] = None + """Highlight override for render_str.""" + markup: Optional[bool] = None + """Enable markup when rendering strings.""" + height: Optional[int] = None + + @property + def ascii_only(self) -> bool: + """Check if renderables should use ascii only.""" + return not self.encoding.startswith("utf") + + def copy(self) -> "ConsoleOptions": + """Return a copy of the options. + + Returns: + ConsoleOptions: a copy of self. + """ + options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) + options.__dict__ = self.__dict__.copy() + return options + + def update( + self, + *, + width: Union[int, NoChange] = NO_CHANGE, + min_width: Union[int, NoChange] = NO_CHANGE, + max_width: Union[int, NoChange] = NO_CHANGE, + justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE, + overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE, + no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE, + highlight: Union[Optional[bool], NoChange] = NO_CHANGE, + markup: Union[Optional[bool], NoChange] = NO_CHANGE, + height: Union[Optional[int], NoChange] = NO_CHANGE, + ) -> "ConsoleOptions": + """Update values, return a copy.""" + options = self.copy() + if not isinstance(width, NoChange): + options.min_width = options.max_width = max(0, width) + if not isinstance(min_width, NoChange): + options.min_width = min_width + if not isinstance(max_width, NoChange): + options.max_width = max_width + if not isinstance(justify, NoChange): + options.justify = justify + if not isinstance(overflow, NoChange): + options.overflow = overflow + if not isinstance(no_wrap, NoChange): + options.no_wrap = no_wrap + if not isinstance(highlight, NoChange): + options.highlight = highlight + if not isinstance(markup, NoChange): + options.markup = markup + if not isinstance(height, NoChange): + if height is not None: + options.max_height = height + options.height = None if height is None else max(0, height) + return options + + def update_width(self, width: int) -> "ConsoleOptions": + """Update just the width, return a copy. + + Args: + width (int): New width (sets both min_width and max_width) + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + return options + + def update_height(self, height: int) -> "ConsoleOptions": + """Update the height, and return a copy. + + Args: + height (int): New height + + Returns: + ~ConsoleOptions: New Console options instance. + """ + options = self.copy() + options.max_height = options.height = height + return options + + def reset_height(self) -> "ConsoleOptions": + """Return a copy of the options with height set to ``None``. + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.height = None + return options + + def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": + """Update the width and height, and return a copy. + + Args: + width (int): New width (sets both min_width and max_width). + height (int): New height. + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + options.height = options.max_height = height + return options + + +@runtime_checkable +class RichCast(Protocol): + """An object that may be 'cast' to a console renderable.""" + + def __rich__( + self, + ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover + ... + + +@runtime_checkable +class ConsoleRenderable(Protocol): + """An object that supports the console protocol.""" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": # pragma: no cover + ... + + +# A type that may be rendered by Console. +RenderableType = Union[ConsoleRenderable, RichCast, str] +"""A string or any object that may be rendered by Rich.""" + +# The result of calling a __rich_console__ method. +RenderResult = Iterable[Union[RenderableType, Segment]] + +_null_highlighter = NullHighlighter() + + +class CaptureError(Exception): + """An error in the Capture context manager.""" + + +class NewLine: + """A renderable to generate new line(s)""" + + def __init__(self, count: int = 1) -> None: + self.count = count + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> Iterable[Segment]: + yield Segment("\n" * self.count) + + +class ScreenUpdate: + """Render a list of lines at a given offset.""" + + def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None: + self._lines = lines + self.x = x + self.y = y + + def __rich_console__( + self, console: "Console", options: ConsoleOptions + ) -> RenderResult: + x = self.x + move_to = Control.move_to + for offset, line in enumerate(self._lines, self.y): + yield move_to(x, offset) + yield from line + + +class Capture: + """Context manager to capture the result of printing to the console. + See :meth:`~rich.console.Console.capture` for how to use. + + Args: + console (Console): A console instance to capture output. + """ + + def __init__(self, console: "Console") -> None: + self._console = console + self._result: Optional[str] = None + + def __enter__(self) -> "Capture": + self._console.begin_capture() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self._result = self._console.end_capture() + + def get(self) -> str: + """Get the result of the capture.""" + if self._result is None: + raise CaptureError( + "Capture result is not available until context manager exits." + ) + return self._result + + +class ThemeContext: + """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage.""" + + def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None: + self.console = console + self.theme = theme + self.inherit = inherit + + def __enter__(self) -> "ThemeContext": + self.console.push_theme(self.theme) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.console.pop_theme() + + +class PagerContext: + """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage.""" + + def __init__( + self, + console: "Console", + pager: Optional[Pager] = None, + styles: bool = False, + links: bool = False, + ) -> None: + self._console = console + self.pager = SystemPager() if pager is None else pager + self.styles = styles + self.links = links + + def __enter__(self) -> "PagerContext": + self._console._enter_buffer() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if exc_type is None: + with self._console._lock: + buffer: List[Segment] = self._console._buffer[:] + del self._console._buffer[:] + segments: Iterable[Segment] = buffer + if not self.styles: + segments = Segment.strip_styles(segments) + elif not self.links: + segments = Segment.strip_links(segments) + content = self._console._render_buffer(segments) + self.pager.show(content) + self._console._exit_buffer() + + +class ScreenContext: + """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage.""" + + def __init__( + self, console: "Console", hide_cursor: bool, style: StyleType = "" + ) -> None: + self.console = console + self.hide_cursor = hide_cursor + self.screen = Screen(style=style) + self._changed = False + + def update( + self, *renderables: RenderableType, style: Optional[StyleType] = None + ) -> None: + """Update the screen. + + Args: + renderable (RenderableType, optional): Optional renderable to replace current renderable, + or None for no change. Defaults to None. + style: (Style, optional): Replacement style, or None for no change. Defaults to None. + """ + if renderables: + self.screen.renderable = ( + Group(*renderables) if len(renderables) > 1 else renderables[0] + ) + if style is not None: + self.screen.style = style + self.console.print(self.screen, end="") + + def __enter__(self) -> "ScreenContext": + self._changed = self.console.set_alt_screen(True) + if self._changed and self.hide_cursor: + self.console.show_cursor(False) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if self._changed: + self.console.set_alt_screen(False) + if self.hide_cursor: + self.console.show_cursor(True) + + +class Group: + """Takes a group of renderables and returns a renderable object that renders the group. + + Args: + renderables (Iterable[RenderableType]): An iterable of renderable objects. + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None: + self._renderables = renderables + self.fit = fit + self._render: Optional[List[RenderableType]] = None + + @property + def renderables(self) -> List["RenderableType"]: + if self._render is None: + self._render = list(self._renderables) + return self._render + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.fit: + return measure_renderables(console, options, self.renderables) + else: + return Measurement(options.max_width, options.max_width) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> RenderResult: + yield from self.renderables + + +def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: + """A decorator that turns an iterable of renderables in to a group. + + Args: + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def decorator( + method: Callable[..., Iterable[RenderableType]] + ) -> Callable[..., Group]: + """Convert a method that returns an iterable of renderables in to a Group.""" + + @wraps(method) + def _replace(*args: Any, **kwargs: Any) -> Group: + renderables = method(*args, **kwargs) + return Group(*renderables, fit=fit) + + return _replace + + return decorator + + +def _is_jupyter() -> bool: # pragma: no cover + """Check if we're running in a Jupyter notebook.""" + try: + get_ipython # type: ignore[name-defined] + except NameError: + return False + ipython = get_ipython() # type: ignore[name-defined] + shell = ipython.__class__.__name__ + if ( + "google.colab" in str(ipython.__class__) + or os.getenv("DATABRICKS_RUNTIME_VERSION") + or shell == "ZMQInteractiveShell" + ): + return True # Jupyter notebook or qtconsole + elif shell == "TerminalInteractiveShell": + return False # Terminal running IPython + else: + return False # Other type (?) + + +COLOR_SYSTEMS = { + "standard": ColorSystem.STANDARD, + "256": ColorSystem.EIGHT_BIT, + "truecolor": ColorSystem.TRUECOLOR, + "windows": ColorSystem.WINDOWS, +} + +_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()} + + +@dataclass +class ConsoleThreadLocals(threading.local): + """Thread local values for Console context.""" + + theme_stack: ThemeStack + buffer: List[Segment] = field(default_factory=list) + buffer_index: int = 0 + + +class RenderHook(ABC): + """Provides hooks in to the render process.""" + + @abstractmethod + def process_renderables( + self, renderables: List[ConsoleRenderable] + ) -> List[ConsoleRenderable]: + """Called with a list of objects to render. + + This method can return a new list of renderables, or modify and return the same list. + + Args: + renderables (List[ConsoleRenderable]): A number of renderable objects. + + Returns: + List[ConsoleRenderable]: A replacement list of renderables. + """ + + +_windows_console_features: Optional["WindowsConsoleFeatures"] = None + + +def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover + global _windows_console_features + if _windows_console_features is not None: + return _windows_console_features + from ._windows import get_windows_console_features + + _windows_console_features = get_windows_console_features() + return _windows_console_features + + +def detect_legacy_windows() -> bool: + """Detect legacy Windows.""" + return WINDOWS and not get_windows_console_features().vt + + +class Console: + """A high level console interface. + + Args: + color_system (str, optional): The color system supported by your terminal, + either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect. + force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None. + force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None. + force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None. + soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False. + theme (Theme, optional): An optional style theme object, or ``None`` for default theme. + stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False. + file (IO, optional): A file object where the console should write to. Defaults to stdout. + quiet (bool, Optional): Boolean to suppress all output. Defaults to False. + width (int, optional): The width of the terminal. Leave as default to auto-detect width. + height (int, optional): The height of the terminal. Leave as default to auto-detect height. + style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None. + no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None. + tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8. + record (bool, optional): Boolean to enable recording of terminal output, + required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False. + markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True. + emoji (bool, optional): Enable emoji code. Defaults to True. + emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. + highlight (bool, optional): Enable automatic highlighting. Defaults to True. + log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True. + log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True. + log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ". + highlighter (HighlighterType, optional): Default highlighter. + legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``. + safe_box (bool, optional): Restrict box options that don't render on legacy Windows. + get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log), + or None for datetime.now. + get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic. + """ + + _environ: Mapping[str, str] = os.environ + + def __init__( + self, + *, + color_system: Optional[ + Literal["auto", "standard", "256", "truecolor", "windows"] + ] = "auto", + force_terminal: Optional[bool] = None, + force_jupyter: Optional[bool] = None, + force_interactive: Optional[bool] = None, + soft_wrap: bool = False, + theme: Optional[Theme] = None, + stderr: bool = False, + file: Optional[IO[str]] = None, + quiet: bool = False, + width: Optional[int] = None, + height: Optional[int] = None, + style: Optional[StyleType] = None, + no_color: Optional[bool] = None, + tab_size: int = 8, + record: bool = False, + markup: bool = True, + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, + highlight: bool = True, + log_time: bool = True, + log_path: bool = True, + log_time_format: Union[str, FormatTimeCallable] = "[%X]", + highlighter: Optional["HighlighterType"] = ReprHighlighter(), + legacy_windows: Optional[bool] = None, + safe_box: bool = True, + get_datetime: Optional[Callable[[], datetime]] = None, + get_time: Optional[Callable[[], float]] = None, + _environ: Optional[Mapping[str, str]] = None, + ): + # Copy of os.environ allows us to replace it for testing + if _environ is not None: + self._environ = _environ + + self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter + if self.is_jupyter: + if width is None: + jupyter_columns = self._environ.get("JUPYTER_COLUMNS") + if jupyter_columns is not None and jupyter_columns.isdigit(): + width = int(jupyter_columns) + else: + width = JUPYTER_DEFAULT_COLUMNS + if height is None: + jupyter_lines = self._environ.get("JUPYTER_LINES") + if jupyter_lines is not None and jupyter_lines.isdigit(): + height = int(jupyter_lines) + else: + height = JUPYTER_DEFAULT_LINES + + self.tab_size = tab_size + self.record = record + self._markup = markup + self._emoji = emoji + self._emoji_variant: Optional[EmojiVariant] = emoji_variant + self._highlight = highlight + self.legacy_windows: bool = ( + (detect_legacy_windows() and not self.is_jupyter) + if legacy_windows is None + else legacy_windows + ) + + if width is None: + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) - self.legacy_windows + if height is None: + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + self.soft_wrap = soft_wrap + self._width = width + self._height = height + + self._color_system: Optional[ColorSystem] + + self._force_terminal = None + if force_terminal is not None: + self._force_terminal = force_terminal + + self._file = file + self.quiet = quiet + self.stderr = stderr + + if color_system is None: + self._color_system = None + elif color_system == "auto": + self._color_system = self._detect_color_system() + else: + self._color_system = COLOR_SYSTEMS[color_system] + + self._lock = threading.RLock() + self._log_render = LogRender( + show_time=log_time, + show_path=log_path, + time_format=log_time_format, + ) + self.highlighter: HighlighterType = highlighter or _null_highlighter + self.safe_box = safe_box + self.get_datetime = get_datetime or datetime.now + self.get_time = get_time or monotonic + self.style = style + self.no_color = ( + no_color if no_color is not None else "NO_COLOR" in self._environ + ) + self.is_interactive = ( + (self.is_terminal and not self.is_dumb_terminal) + if force_interactive is None + else force_interactive + ) + + self._record_buffer_lock = threading.RLock() + self._thread_locals = ConsoleThreadLocals( + theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme) + ) + self._record_buffer: List[Segment] = [] + self._render_hooks: List[RenderHook] = [] + self._live: Optional["Live"] = None + self._is_alt_screen = False + + def __repr__(self) -> str: + return f"" + + @property + def file(self) -> IO[str]: + """Get the file object to write to.""" + file = self._file or (sys.stderr if self.stderr else sys.stdout) + file = getattr(file, "rich_proxied_file", file) + if file is None: + file = NULL_FILE + return file + + @file.setter + def file(self, new_file: IO[str]) -> None: + """Set a new file object.""" + self._file = new_file + + @property + def _buffer(self) -> List[Segment]: + """Get a thread local buffer.""" + return self._thread_locals.buffer + + @property + def _buffer_index(self) -> int: + """Get a thread local buffer.""" + return self._thread_locals.buffer_index + + @_buffer_index.setter + def _buffer_index(self, value: int) -> None: + self._thread_locals.buffer_index = value + + @property + def _theme_stack(self) -> ThemeStack: + """Get the thread local theme stack.""" + return self._thread_locals.theme_stack + + def _detect_color_system(self) -> Optional[ColorSystem]: + """Detect color system from env vars.""" + if self.is_jupyter: + return ColorSystem.TRUECOLOR + if not self.is_terminal or self.is_dumb_terminal: + return None + if WINDOWS: # pragma: no cover + if self.legacy_windows: # pragma: no cover + return ColorSystem.WINDOWS + windows_console_features = get_windows_console_features() + return ( + ColorSystem.TRUECOLOR + if windows_console_features.truecolor + else ColorSystem.EIGHT_BIT + ) + else: + color_term = self._environ.get("COLORTERM", "").strip().lower() + if color_term in ("truecolor", "24bit"): + return ColorSystem.TRUECOLOR + term = self._environ.get("TERM", "").strip().lower() + _term_name, _hyphen, colors = term.rpartition("-") + color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD) + return color_system + + def _enter_buffer(self) -> None: + """Enter in to a buffer context, and buffer all output.""" + self._buffer_index += 1 + + def _exit_buffer(self) -> None: + """Leave buffer context, and render content if required.""" + self._buffer_index -= 1 + self._check_buffer() + + def set_live(self, live: "Live") -> None: + """Set Live instance. Used by Live context manager. + + Args: + live (Live): Live instance using this Console. + + Raises: + errors.LiveError: If this Console has a Live context currently active. + """ + with self._lock: + if self._live is not None: + raise errors.LiveError("Only one live display may be active at once") + self._live = live + + def clear_live(self) -> None: + """Clear the Live instance.""" + with self._lock: + self._live = None + + def push_render_hook(self, hook: RenderHook) -> None: + """Add a new render hook to the stack. + + Args: + hook (RenderHook): Render hook instance. + """ + with self._lock: + self._render_hooks.append(hook) + + def pop_render_hook(self) -> None: + """Pop the last renderhook from the stack.""" + with self._lock: + self._render_hooks.pop() + + def __enter__(self) -> "Console": + """Own context manager to enter buffer context.""" + self._enter_buffer() + return self + + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: + """Exit buffer context.""" + self._exit_buffer() + + def begin_capture(self) -> None: + """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output.""" + self._enter_buffer() + + def end_capture(self) -> str: + """End capture mode and return captured string. + + Returns: + str: Console output. + """ + render_result = self._render_buffer(self._buffer) + del self._buffer[:] + self._exit_buffer() + return render_result + + def push_theme(self, theme: Theme, *, inherit: bool = True) -> None: + """Push a new theme on to the top of the stack, replacing the styles from the previous theme. + Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather + than calling this method directly. + + Args: + theme (Theme): A theme instance. + inherit (bool, optional): Inherit existing styles. Defaults to True. + """ + self._theme_stack.push_theme(theme, inherit=inherit) + + def pop_theme(self) -> None: + """Remove theme from top of stack, restoring previous theme.""" + self._theme_stack.pop_theme() + + def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext: + """Use a different theme for the duration of the context manager. + + Args: + theme (Theme): Theme instance to user. + inherit (bool, optional): Inherit existing console styles. Defaults to True. + + Returns: + ThemeContext: [description] + """ + return ThemeContext(self, theme, inherit) + + @property + def color_system(self) -> Optional[str]: + """Get color system string. + + Returns: + Optional[str]: "standard", "256" or "truecolor". + """ + + if self._color_system is not None: + return _COLOR_SYSTEMS_NAMES[self._color_system] + else: + return None + + @property + def encoding(self) -> str: + """Get the encoding of the console file, e.g. ``"utf-8"``. + + Returns: + str: A standard encoding string. + """ + return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower() + + @property + def is_terminal(self) -> bool: + """Check if the console is writing to a terminal. + + Returns: + bool: True if the console writing to a device capable of + understanding terminal codes, otherwise False. + """ + if self._force_terminal is not None: + return self._force_terminal + + if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith( + "idlelib" + ): + # Return False for Idle which claims to be a tty but can't handle ansi codes + return False + + if self.is_jupyter: + # return False for Jupyter, which may have FORCE_COLOR set + return False + + # If FORCE_COLOR env var has any value at all, we assume a terminal. + force_color = self._environ.get("FORCE_COLOR") + if force_color is not None: + self._force_terminal = True + return True + + isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None) + try: + return False if isatty is None else isatty() + except ValueError: + # in some situation (at the end of a pytest run for example) isatty() can raise + # ValueError: I/O operation on closed file + # return False because we aren't in a terminal anymore + return False + + @property + def is_dumb_terminal(self) -> bool: + """Detect dumb terminal. + + Returns: + bool: True if writing to a dumb terminal, otherwise False. + + """ + _term = self._environ.get("TERM", "") + is_dumb = _term.lower() in ("dumb", "unknown") + return self.is_terminal and is_dumb + + @property + def options(self) -> ConsoleOptions: + """Get default console options.""" + return ConsoleOptions( + max_height=self.size.height, + size=self.size, + legacy_windows=self.legacy_windows, + min_width=1, + max_width=self.width, + encoding=self.encoding, + is_terminal=self.is_terminal, + ) + + @property + def size(self) -> ConsoleDimensions: + """Get the size of the console. + + Returns: + ConsoleDimensions: A named tuple containing the dimensions. + """ + + if self._width is not None and self._height is not None: + return ConsoleDimensions(self._width - self.legacy_windows, self._height) + + if self.is_dumb_terminal: + return ConsoleDimensions(80, 25) + + width: Optional[int] = None + height: Optional[int] = None + + streams = _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS + for file_descriptor in streams: + try: + width, height = os.get_terminal_size(file_descriptor) + except (AttributeError, ValueError, OSError): # Probably not a terminal + pass + else: + break + + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + # get_terminal_size can report 0, 0 if run from pseudo-terminal + width = width or 80 + height = height or 25 + return ConsoleDimensions( + width - self.legacy_windows if self._width is None else self._width, + height if self._height is None else self._height, + ) + + @size.setter + def size(self, new_size: Tuple[int, int]) -> None: + """Set a new size for the terminal. + + Args: + new_size (Tuple[int, int]): New width and height. + """ + width, height = new_size + self._width = width + self._height = height + + @property + def width(self) -> int: + """Get the width of the console. + + Returns: + int: The width (in characters) of the console. + """ + return self.size.width + + @width.setter + def width(self, width: int) -> None: + """Set width. + + Args: + width (int): New width. + """ + self._width = width + + @property + def height(self) -> int: + """Get the height of the console. + + Returns: + int: The height (in lines) of the console. + """ + return self.size.height + + @height.setter + def height(self, height: int) -> None: + """Set height. + + Args: + height (int): new height. + """ + self._height = height + + def bell(self) -> None: + """Play a 'bell' sound (if supported by the terminal).""" + self.control(Control.bell()) + + def capture(self) -> Capture: + """A context manager to *capture* the result of print() or log() in a string, + rather than writing it to the console. + + Example: + >>> from rich.console import Console + >>> console = Console() + >>> with console.capture() as capture: + ... console.print("[bold magenta]Hello World[/]") + >>> print(capture.get()) + + Returns: + Capture: Context manager with disables writing to the terminal. + """ + capture = Capture(self) + return capture + + def pager( + self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False + ) -> PagerContext: + """A context manager to display anything printed within a "pager". The pager application + is defined by the system and will typically support at least pressing a key to scroll. + + Args: + pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None. + styles (bool, optional): Show styles in pager. Defaults to False. + links (bool, optional): Show links in pager. Defaults to False. + + Example: + >>> from rich.console import Console + >>> from rich.__main__ import make_test_card + >>> console = Console() + >>> with console.pager(): + console.print(make_test_card()) + + Returns: + PagerContext: A context manager. + """ + return PagerContext(self, pager=pager, styles=styles, links=links) + + def line(self, count: int = 1) -> None: + """Write new line(s). + + Args: + count (int, optional): Number of new lines. Defaults to 1. + """ + + assert count >= 0, "count must be >= 0" + self.print(NewLine(count)) + + def clear(self, home: bool = True) -> None: + """Clear the screen. + + Args: + home (bool, optional): Also move the cursor to 'home' position. Defaults to True. + """ + if home: + self.control(Control.clear(), Control.home()) + else: + self.control(Control.clear()) + + def status( + self, + status: RenderableType, + *, + spinner: str = "dots", + spinner_style: StyleType = "status.spinner", + speed: float = 1.0, + refresh_per_second: float = 12.5, + ) -> "Status": + """Display a status and spinner. + + Args: + status (RenderableType): A status renderable (str or Text typically). + spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots". + spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner". + speed (float, optional): Speed factor for spinner animation. Defaults to 1.0. + refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5. + + Returns: + Status: A Status object that may be used as a context manager. + """ + from .status import Status + + status_renderable = Status( + status, + console=self, + spinner=spinner, + spinner_style=spinner_style, + speed=speed, + refresh_per_second=refresh_per_second, + ) + return status_renderable + + def show_cursor(self, show: bool = True) -> bool: + """Show or hide the cursor. + + Args: + show (bool, optional): Set visibility of the cursor. + """ + if self.is_terminal: + self.control(Control.show_cursor(show)) + return True + return False + + def set_alt_screen(self, enable: bool = True) -> bool: + """Enables alternative screen mode. + + Note, if you enable this mode, you should ensure that is disabled before + the application exits. See :meth:`~rich.Console.screen` for a context manager + that handles this for you. + + Args: + enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True. + + Returns: + bool: True if the control codes were written. + + """ + changed = False + if self.is_terminal and not self.legacy_windows: + self.control(Control.alt_screen(enable)) + changed = True + self._is_alt_screen = enable + return changed + + @property + def is_alt_screen(self) -> bool: + """Check if the alt screen was enabled. + + Returns: + bool: True if the alt screen was enabled, otherwise False. + """ + return self._is_alt_screen + + def set_window_title(self, title: str) -> bool: + """Set the title of the console terminal window. + + Warning: There is no means within Rich of "resetting" the window title to its + previous value, meaning the title you set will persist even after your application + exits. + + ``fish`` shell resets the window title before and after each command by default, + negating this issue. Windows Terminal and command prompt will also reset the title for you. + Most other shells and terminals, however, do not do this. + + Some terminals may require configuration changes before you can set the title. + Some terminals may not support setting the title at all. + + Other software (including the terminal itself, the shell, custom prompts, plugins, etc.) + may also set the terminal window title. This could result in whatever value you write + using this method being overwritten. + + Args: + title (str): The new title of the terminal window. + + Returns: + bool: True if the control code to change the terminal title was + written, otherwise False. Note that a return value of True + does not guarantee that the window title has actually changed, + since the feature may be unsupported/disabled in some terminals. + """ + if self.is_terminal: + self.control(Control.title(title)) + return True + return False + + def screen( + self, hide_cursor: bool = True, style: Optional[StyleType] = None + ) -> "ScreenContext": + """Context manager to enable and disable 'alternative screen' mode. + + Args: + hide_cursor (bool, optional): Also hide the cursor. Defaults to False. + style (Style, optional): Optional style for screen. Defaults to None. + + Returns: + ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit. + """ + return ScreenContext(self, hide_cursor=hide_cursor, style=style or "") + + def measure( + self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None + ) -> Measurement: + """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains + information regarding the number of characters required to print the renderable. + + Args: + renderable (RenderableType): Any renderable or string. + options (Optional[ConsoleOptions], optional): Options to use when measuring, or None + to use default options. Defaults to None. + + Returns: + Measurement: A measurement of the renderable. + """ + measurement = Measurement.get(self, options or self.options, renderable) + return measurement + + def render( + self, renderable: RenderableType, options: Optional[ConsoleOptions] = None + ) -> Iterable[Segment]: + """Render an object in to an iterable of `Segment` instances. + + This method contains the logic for rendering objects with the console protocol. + You are unlikely to need to use it directly, unless you are extending the library. + + Args: + renderable (RenderableType): An object supporting the console protocol, or + an object that may be converted to a string. + options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None. + + Returns: + Iterable[Segment]: An iterable of segments that may be rendered. + """ + + _options = options or self.options + if _options.max_width < 1: + # No space to render anything. This prevents potential recursion errors. + return + render_iterable: RenderResult + + renderable = rich_cast(renderable) + if hasattr(renderable, "__rich_console__") and not isclass(renderable): + render_iterable = renderable.__rich_console__(self, _options) + elif isinstance(renderable, str): + text_renderable = self.render_str( + renderable, highlight=_options.highlight, markup=_options.markup + ) + render_iterable = text_renderable.__rich_console__(self, _options) + else: + raise errors.NotRenderableError( + f"Unable to render {renderable!r}; " + "A str, Segment or object with __rich_console__ method is required" + ) + + try: + iter_render = iter(render_iterable) + except TypeError: + raise errors.NotRenderableError( + f"object {render_iterable!r} is not renderable" + ) + _Segment = Segment + _options = _options.reset_height() + for render_output in iter_render: + if isinstance(render_output, _Segment): + yield render_output + else: + yield from self.render(render_output, _options) + + def render_lines( + self, + renderable: RenderableType, + options: Optional[ConsoleOptions] = None, + *, + style: Optional[Style] = None, + pad: bool = True, + new_lines: bool = False, + ) -> List[List[Segment]]: + """Render objects in to a list of lines. + + The output of render_lines is useful when further formatting of rendered console text + is required, such as the Panel class which draws a border around any renderable object. + + Args: + renderable (RenderableType): Any object renderable in the console. + options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``. + style (Style, optional): Optional style to apply to renderables. Defaults to ``None``. + pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``. + new_lines (bool, optional): Include "\n" characters at end of lines. + + Returns: + List[List[Segment]]: A list of lines, where a line is a list of Segment objects. + """ + with self._lock: + render_options = options or self.options + _rendered = self.render(renderable, render_options) + if style: + _rendered = Segment.apply_style(_rendered, style) + + render_height = render_options.height + if render_height is not None: + render_height = max(0, render_height) + + lines = list( + islice( + Segment.split_and_crop_lines( + _rendered, + render_options.max_width, + include_new_lines=new_lines, + pad=pad, + style=style, + ), + None, + render_height, + ) + ) + if render_options.height is not None: + extra_lines = render_options.height - len(lines) + if extra_lines > 0: + pad_line = [ + ( + [ + Segment(" " * render_options.max_width, style), + Segment("\n"), + ] + if new_lines + else [Segment(" " * render_options.max_width, style)] + ) + ] + lines.extend(pad_line * extra_lines) + + return lines + + def render_str( + self, + text: str, + *, + style: Union[str, Style] = "", + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + highlighter: Optional[HighlighterType] = None, + ) -> "Text": + """Convert a string to a Text instance. This is called automatically if + you print or log a string. + + Args: + text (str): Text to render. + style (Union[str, Style], optional): Style to apply to rendered text. + justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default. + highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default. + highlighter (HighlighterType, optional): Optional highlighter to apply. + Returns: + ConsoleRenderable: Renderable object. + + """ + emoji_enabled = emoji or (emoji is None and self._emoji) + markup_enabled = markup or (markup is None and self._markup) + highlight_enabled = highlight or (highlight is None and self._highlight) + + if markup_enabled: + rich_text = render_markup( + text, + style=style, + emoji=emoji_enabled, + emoji_variant=self._emoji_variant, + ) + rich_text.justify = justify + rich_text.overflow = overflow + else: + rich_text = Text( + ( + _emoji_replace(text, default_variant=self._emoji_variant) + if emoji_enabled + else text + ), + justify=justify, + overflow=overflow, + style=style, + ) + + _highlighter = (highlighter or self.highlighter) if highlight_enabled else None + if _highlighter is not None: + highlight_text = _highlighter(str(rich_text)) + highlight_text.copy_styles(rich_text) + return highlight_text + + return rich_text + + def get_style( + self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None + ) -> Style: + """Get a Style instance by its theme name or parse a definition. + + Args: + name (str): The name of a style or a style definition. + + Returns: + Style: A Style object. + + Raises: + MissingStyle: If no style could be parsed from name. + + """ + if isinstance(name, Style): + return name + + try: + style = self._theme_stack.get(name) + if style is None: + style = Style.parse(name) + return style.copy() if style.link else style + except errors.StyleSyntaxError as error: + if default is not None: + return self.get_style(default) + raise errors.MissingStyle( + f"Failed to get style {name!r}; {error}" + ) from None + + def _collect_renderables( + self, + objects: Iterable[Any], + sep: str, + end: str, + *, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + ) -> List[ConsoleRenderable]: + """Combine a number of renderables and text into one renderable. + + Args: + objects (Iterable[Any]): Anything that Rich can render. + sep (str): String to write between print data. + end (str): String to write at end of print data. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. + + Returns: + List[ConsoleRenderable]: A list of things to render. + """ + renderables: List[ConsoleRenderable] = [] + _append = renderables.append + text: List[Text] = [] + append_text = text.append + + append = _append + if justify in ("left", "center", "right"): + + def align_append(renderable: RenderableType) -> None: + _append(Align(renderable, cast(AlignMethod, justify))) + + append = align_append + + _highlighter: HighlighterType = _null_highlighter + if highlight or (highlight is None and self._highlight): + _highlighter = self.highlighter + + def check_text() -> None: + if text: + sep_text = Text(sep, justify=justify, end=end) + append(sep_text.join(text)) + text.clear() + + for renderable in objects: + renderable = rich_cast(renderable) + if isinstance(renderable, str): + append_text( + self.render_str( + renderable, + emoji=emoji, + markup=markup, + highlight=highlight, + highlighter=_highlighter, + ) + ) + elif isinstance(renderable, Text): + append_text(renderable) + elif isinstance(renderable, ConsoleRenderable): + check_text() + append(renderable) + elif is_expandable(renderable): + check_text() + append(Pretty(renderable, highlighter=_highlighter)) + else: + append_text(_highlighter(str(renderable))) + + check_text() + + if self.style is not None: + style = self.get_style(self.style) + renderables = [Styled(renderable, style) for renderable in renderables] + + return renderables + + def rule( + self, + title: TextType = "", + *, + characters: str = "─", + style: Union[str, Style] = "rule.line", + align: AlignMethod = "center", + ) -> None: + """Draw a line with optional centered title. + + Args: + title (str, optional): Text to render over the rule. Defaults to "". + characters (str, optional): Character(s) to form the line. Defaults to "─". + style (str, optional): Style of line. Defaults to "rule.line". + align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". + """ + from .rule import Rule + + rule = Rule(title=title, characters=characters, style=style, align=align) + self.print(rule) + + def control(self, *control: Control) -> None: + """Insert non-printing control codes. + + Args: + control_codes (str): Control codes, such as those that may move the cursor. + """ + if not self.is_dumb_terminal: + with self: + self._buffer.extend(_control.segment for _control in control) + + def out( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + highlight: Optional[bool] = None, + ) -> None: + """Output to the terminal. This is a low-level way of writing to the terminal which unlike + :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will + optionally apply highlighting and a basic style. + + Args: + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use + console default. Defaults to ``None``. + """ + raw_output: str = sep.join(str(_object) for _object in objects) + self.print( + raw_output, + style=style, + highlight=highlight, + emoji=False, + markup=False, + no_wrap=True, + overflow="ignore", + crop=False, + end=end, + ) + + def print( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + no_wrap: Optional[bool] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + width: Optional[int] = None, + height: Optional[int] = None, + crop: bool = True, + soft_wrap: Optional[bool] = None, + new_line_start: bool = False, + ) -> None: + """Print to the console. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``. + overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None. + no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``. + width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``. + crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True. + soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for + Console default. Defaults to ``None``. + new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``. + """ + if not objects: + objects = (NewLine(),) + + if soft_wrap is None: + soft_wrap = self.soft_wrap + if soft_wrap: + if no_wrap is None: + no_wrap = True + if overflow is None: + overflow = "ignore" + crop = False + render_hooks = self._render_hooks[:] + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + render_options = self.options.update( + justify=justify, + overflow=overflow, + width=min(width, self.width) if width is not None else NO_CHANGE, + height=height, + no_wrap=no_wrap, + markup=markup, + highlight=highlight, + ) + + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + if style is None: + for renderable in renderables: + extend(render(renderable, render_options)) + else: + for renderable in renderables: + extend( + Segment.apply_style( + render(renderable, render_options), self.get_style(style) + ) + ) + if new_line_start: + if ( + len("".join(segment.text for segment in new_segments).splitlines()) + > 1 + ): + new_segments.insert(0, Segment.line()) + if crop: + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + else: + self._buffer.extend(new_segments) + + def print_json( + self, + json: Optional[str] = None, + *, + data: Any = None, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = False, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + ) -> None: + """Pretty prints JSON. Output will be valid JSON. + + Args: + json (Optional[str]): A string containing JSON. + data (Any): If json is not supplied, then encode this data. + indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2. + highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + """ + from pip._vendor.rich.json import JSON + + if json is None: + json_renderable = JSON.from_data( + data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + else: + if not isinstance(json, str): + raise TypeError( + f"json must be str. Did you mean print_json(data={json!r}) ?" + ) + json_renderable = JSON( + json, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + self.print(json_renderable, soft_wrap=True) + + def update_screen( + self, + renderable: RenderableType, + *, + region: Optional[Region] = None, + options: Optional[ConsoleOptions] = None, + ) -> None: + """Update the screen at a given offset. + + Args: + renderable (RenderableType): A Rich renderable. + region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None. + x (int, optional): x offset. Defaults to 0. + y (int, optional): y offset. Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + render_options = options or self.options + if region is None: + x = y = 0 + render_options = render_options.update_dimensions( + render_options.max_width, render_options.height or self.height + ) + else: + x, y, width, height = region + render_options = render_options.update_dimensions(width, height) + + lines = self.render_lines(renderable, options=render_options) + self.update_screen_lines(lines, x, y) + + def update_screen_lines( + self, lines: List[List[Segment]], x: int = 0, y: int = 0 + ) -> None: + """Update lines of the screen at a given offset. + + Args: + lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`). + x (int, optional): x offset (column no). Defaults to 0. + y (int, optional): y offset (column no). Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + screen_update = ScreenUpdate(lines, x, y) + segments = self.render(screen_update) + self._buffer.extend(segments) + self._check_buffer() + + def print_exception( + self, + *, + width: Optional[int] = 100, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ) -> None: + """Prints a rich render of the last exception and traceback. + + Args: + width (Optional[int], optional): Number of characters used to render code. Defaults to 100. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + """ + from .traceback import Traceback + + traceback = Traceback( + width=width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + suppress=suppress, + max_frames=max_frames, + ) + self.print(traceback) + + @staticmethod + def _caller_frame_info( + offset: int, + currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe, + ) -> Tuple[str, int, Dict[str, Any]]: + """Get caller frame information. + + Args: + offset (int): the caller offset within the current frame stack. + currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to + retrieve the current frame. Defaults to ``inspect.currentframe``. + + Returns: + Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and + the dictionary of local variables associated with the caller frame. + + Raises: + RuntimeError: If the stack offset is invalid. + """ + # Ignore the frame of this local helper + offset += 1 + + frame = currentframe() + if frame is not None: + # Use the faster currentframe where implemented + while offset and frame is not None: + frame = frame.f_back + offset -= 1 + assert frame is not None + return frame.f_code.co_filename, frame.f_lineno, frame.f_locals + else: + # Fallback to the slower stack + frame_info = inspect.stack()[offset] + return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals + + def log( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + log_locals: bool = False, + _stack_offset: int = 1, + ) -> None: + """Log rich content to the terminal. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None. + log_locals (bool, optional): Boolean to enable logging of locals where ``log()`` + was called. Defaults to False. + _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1. + """ + if not objects: + objects = (NewLine(),) + + render_hooks = self._render_hooks[:] + + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + if style is not None: + renderables = [Styled(renderable, style) for renderable in renderables] + + filename, line_no, locals = self._caller_frame_info(_stack_offset) + link_path = None if filename.startswith("<") else os.path.abspath(filename) + path = filename.rpartition(os.sep)[-1] + if log_locals: + locals_map = { + key: value + for key, value in locals.items() + if not key.startswith("__") + } + renderables.append(render_scope(locals_map, title="[i]locals")) + + renderables = [ + self._log_render( + self, + renderables, + log_time=self.get_datetime(), + path=path, + line_no=line_no, + link_path=link_path, + ) + ] + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + render_options = self.options + for renderable in renderables: + extend(render(renderable, render_options)) + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + + def on_broken_pipe(self) -> None: + """This function is called when a `BrokenPipeError` is raised. + + This can occur when piping Textual output in Linux and macOS. + The default implementation is to exit the app, but you could implement + this method in a subclass to change the behavior. + + See https://docs.python.org/3/library/signal.html#note-on-sigpipe for details. + """ + self.quiet = True + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + raise SystemExit(1) + + def _check_buffer(self) -> None: + """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False) + Rendering is supported on Windows, Unix and Jupyter environments. For + legacy Windows consoles, the win32 API is called directly. + This method will also record what it renders if recording is enabled via Console.record. + """ + if self.quiet: + del self._buffer[:] + return + + try: + self._write_buffer() + except BrokenPipeError: + self.on_broken_pipe() + + def _write_buffer(self) -> None: + """Write the buffer to the output file.""" + + with self._lock: + if self.record and not self._buffer_index: + with self._record_buffer_lock: + self._record_buffer.extend(self._buffer[:]) + + if self._buffer_index == 0: + if self.is_jupyter: # pragma: no cover + from .jupyter import display + + display(self._buffer, self._render_buffer(self._buffer[:])) + del self._buffer[:] + else: + if WINDOWS: + use_legacy_windows_render = False + if self.legacy_windows: + fileno = get_fileno(self.file) + if fileno is not None: + use_legacy_windows_render = ( + fileno in _STD_STREAMS_OUTPUT + ) + + if use_legacy_windows_render: + from pip._vendor.rich._win32_console import LegacyWindowsTerm + from pip._vendor.rich._windows_renderer import legacy_windows_render + + buffer = self._buffer[:] + if self.no_color and self._color_system: + buffer = list(Segment.remove_color(buffer)) + + legacy_windows_render(buffer, LegacyWindowsTerm(self.file)) + else: + # Either a non-std stream on legacy Windows, or modern Windows. + text = self._render_buffer(self._buffer[:]) + # https://bugs.python.org/issue37871 + # https://github.com/python/cpython/issues/82052 + # We need to avoid writing more than 32Kb in a single write, due to the above bug + write = self.file.write + # Worse case scenario, every character is 4 bytes of utf-8 + MAX_WRITE = 32 * 1024 // 4 + try: + if len(text) <= MAX_WRITE: + write(text) + else: + batch: List[str] = [] + batch_append = batch.append + size = 0 + for line in text.splitlines(True): + if size + len(line) > MAX_WRITE and batch: + write("".join(batch)) + batch.clear() + size = 0 + batch_append(line) + size += len(line) + if batch: + write("".join(batch)) + batch.clear() + except UnicodeEncodeError as error: + error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" + raise + else: + text = self._render_buffer(self._buffer[:]) + try: + self.file.write(text) + except UnicodeEncodeError as error: + error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" + raise + + self.file.flush() + del self._buffer[:] + + def _render_buffer(self, buffer: Iterable[Segment]) -> str: + """Render buffered output, and clear buffer.""" + output: List[str] = [] + append = output.append + color_system = self._color_system + legacy_windows = self.legacy_windows + not_terminal = not self.is_terminal + if self.no_color and color_system: + buffer = Segment.remove_color(buffer) + for text, style, control in buffer: + if style: + append( + style.render( + text, + color_system=color_system, + legacy_windows=legacy_windows, + ) + ) + elif not (not_terminal and control): + append(text) + + rendered = "".join(output) + return rendered + + def input( + self, + prompt: TextType = "", + *, + markup: bool = True, + emoji: bool = True, + password: bool = False, + stream: Optional[TextIO] = None, + ) -> str: + """Displays a prompt and waits for input from the user. The prompt may contain color / style. + + It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded. + + Args: + prompt (Union[str, Text]): Text to render in the prompt. + markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True. + emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True. + password: (bool, optional): Hide typed text. Defaults to False. + stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None. + + Returns: + str: Text read from stdin. + """ + if prompt: + self.print(prompt, markup=markup, emoji=emoji, end="") + if password: + result = getpass("", stream=stream) + else: + if stream: + result = stream.readline() + else: + result = input() + return result + + def export_text(self, *, clear: bool = True, styles: bool = False) -> str: + """Generate text from console contents (requires record=True argument in constructor). + + Args: + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text. + Defaults to ``False``. + + Returns: + str: String containing console contents. + + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + + with self._record_buffer_lock: + if styles: + text = "".join( + (style.render(text) if style else text) + for text, style, _ in self._record_buffer + ) + else: + text = "".join( + segment.text + for segment in self._record_buffer + if not segment.control + ) + if clear: + del self._record_buffer[:] + return text + + def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None: + """Generate text from console and save to a given location (requires record=True argument in constructor). + + Args: + path (str): Path to write text files. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text. + Defaults to ``False``. + + """ + text = self.export_text(clear=clear, styles=styles) + with open(path, "w", encoding="utf-8") as write_file: + write_file.write(text) + + def export_html( + self, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: Optional[str] = None, + inline_styles: bool = False, + ) -> str: + """Generate HTML from console contents (requires record=True argument in constructor). + + Args: + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML. In addition to '{foreground}', + '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + Returns: + str: String containing console contents as HTML. + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + fragments: List[str] = [] + append = fragments.append + _theme = theme or DEFAULT_TERMINAL_THEME + stylesheet = "" + + render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format + + with self._record_buffer_lock: + if inline_styles: + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + if style.link: + text = f'
        {text}' + text = f'{text}' if rule else text + append(text) + else: + styles: Dict[str, int] = {} + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + style_number = styles.setdefault(rule, len(styles) + 1) + if style.link: + text = f'{text}' + else: + text = f'{text}' + append(text) + stylesheet_rules: List[str] = [] + stylesheet_append = stylesheet_rules.append + for style_rule, style_number in styles.items(): + if style_rule: + stylesheet_append(f".r{style_number} {{{style_rule}}}") + stylesheet = "\n".join(stylesheet_rules) + + rendered_code = render_code_format.format( + code="".join(fragments), + stylesheet=stylesheet, + foreground=_theme.foreground_color.hex, + background=_theme.background_color.hex, + ) + if clear: + del self._record_buffer[:] + return rendered_code + + def save_html( + self, + path: str, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_HTML_FORMAT, + inline_styles: bool = False, + ) -> None: + """Generate HTML from console contents and write to a file (requires record=True argument in constructor). + + Args: + path (str): Path to write html file. + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML. In addition to '{foreground}', + '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + """ + html = self.export_html( + theme=theme, + clear=clear, + code_format=code_format, + inline_styles=inline_styles, + ) + with open(path, "w", encoding="utf-8") as write_file: + write_file.write(html) + + def export_svg( + self, + *, + title: str = "Rich", + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_SVG_FORMAT, + font_aspect_ratio: float = 0.61, + unique_id: Optional[str] = None, + ) -> str: + """ + Generate an SVG from the console contents (requires record=True in Console constructor). + + Args: + title (str, optional): The title of the tab in the output image + theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` + code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables + into the string in order to form the final SVG output. The default template used and the variables + injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. + font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` + string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). + If you aren't specifying a different font inside ``code_format``, you probably don't need this. + unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node + ids). If not set, this defaults to a computed value based on the recorded content. + """ + + from pip._vendor.rich.cells import cell_len + + style_cache: Dict[Style, str] = {} + + def get_svg_style(style: Style) -> str: + """Convert a Style to CSS rules for SVG.""" + if style in style_cache: + return style_cache[style] + css_rules = [] + color = ( + _theme.foreground_color + if (style.color is None or style.color.is_default) + else style.color.get_truecolor(_theme) + ) + bgcolor = ( + _theme.background_color + if (style.bgcolor is None or style.bgcolor.is_default) + else style.bgcolor.get_truecolor(_theme) + ) + if style.reverse: + color, bgcolor = bgcolor, color + if style.dim: + color = blend_rgb(color, bgcolor, 0.4) + css_rules.append(f"fill: {color.hex}") + if style.bold: + css_rules.append("font-weight: bold") + if style.italic: + css_rules.append("font-style: italic;") + if style.underline: + css_rules.append("text-decoration: underline;") + if style.strike: + css_rules.append("text-decoration: line-through;") + + css = ";".join(css_rules) + style_cache[style] = css + return css + + _theme = theme or SVG_EXPORT_THEME + + width = self.width + char_height = 20 + char_width = char_height * font_aspect_ratio + line_height = char_height * 1.22 + + margin_top = 1 + margin_right = 1 + margin_bottom = 1 + margin_left = 1 + + padding_top = 40 + padding_right = 8 + padding_bottom = 8 + padding_left = 8 + + padding_width = padding_left + padding_right + padding_height = padding_top + padding_bottom + margin_width = margin_left + margin_right + margin_height = margin_top + margin_bottom + + text_backgrounds: List[str] = [] + text_group: List[str] = [] + classes: Dict[str, int] = {} + style_no = 1 + + def escape_text(text: str) -> str: + """HTML escape text and replace spaces with nbsp.""" + return escape(text).replace(" ", " ") + + def make_tag( + name: str, content: Optional[str] = None, **attribs: object + ) -> str: + """Make a tag from name, content, and attributes.""" + + def stringify(value: object) -> str: + if isinstance(value, (float)): + return format(value, "g") + return str(value) + + tag_attribs = " ".join( + f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"' + for k, v in attribs.items() + ) + return ( + f"<{name} {tag_attribs}>{content}" + if content + else f"<{name} {tag_attribs}/>" + ) + + with self._record_buffer_lock: + segments = list(Segment.filter_control(self._record_buffer)) + if clear: + self._record_buffer.clear() + + if unique_id is None: + unique_id = "terminal-" + str( + zlib.adler32( + ("".join(repr(segment) for segment in segments)).encode( + "utf-8", + "ignore", + ) + + title.encode("utf-8", "ignore") + ) + ) + y = 0 + for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)): + x = 0 + for text, style, _control in line: + style = style or Style() + rules = get_svg_style(style) + if rules not in classes: + classes[rules] = style_no + style_no += 1 + class_name = f"r{classes[rules]}" + + if style.reverse: + has_background = True + background = ( + _theme.foreground_color.hex + if style.color is None + else style.color.get_truecolor(_theme).hex + ) + else: + bgcolor = style.bgcolor + has_background = bgcolor is not None and not bgcolor.is_default + background = ( + _theme.background_color.hex + if style.bgcolor is None + else style.bgcolor.get_truecolor(_theme).hex + ) + + text_length = cell_len(text) + if has_background: + text_backgrounds.append( + make_tag( + "rect", + fill=background, + x=x * char_width, + y=y * line_height + 1.5, + width=char_width * text_length, + height=line_height + 0.25, + shape_rendering="crispEdges", + ) + ) + + if text != " " * len(text): + text_group.append( + make_tag( + "text", + escape_text(text), + _class=f"{unique_id}-{class_name}", + x=x * char_width, + y=y * line_height + char_height, + textLength=char_width * len(text), + clip_path=f"url(#{unique_id}-line-{y})", + ) + ) + x += cell_len(text) + + line_offsets = [line_no * line_height + 1.5 for line_no in range(y)] + lines = "\n".join( + f""" + {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)} + """ + for line_no, offset in enumerate(line_offsets) + ) + + styles = "\n".join( + f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items() + ) + backgrounds = "".join(text_backgrounds) + matrix = "".join(text_group) + + terminal_width = ceil(width * char_width + padding_width) + terminal_height = (y + 1) * line_height + padding_height + chrome = make_tag( + "rect", + fill=_theme.background_color.hex, + stroke="rgba(255,255,255,0.35)", + stroke_width="1", + x=margin_left, + y=margin_top, + width=terminal_width, + height=terminal_height, + rx=8, + ) + + title_color = _theme.foreground_color.hex + if title: + chrome += make_tag( + "text", + escape_text(title), + _class=f"{unique_id}-title", + fill=title_color, + text_anchor="middle", + x=terminal_width // 2, + y=margin_top + char_height + 6, + ) + chrome += f""" + + + + + + """ + + svg = code_format.format( + unique_id=unique_id, + char_width=char_width, + char_height=char_height, + line_height=line_height, + terminal_width=char_width * width - 1, + terminal_height=(y + 1) * line_height - 1, + width=terminal_width + margin_width, + height=terminal_height + margin_height, + terminal_x=margin_left + padding_left, + terminal_y=margin_top + padding_top, + styles=styles, + chrome=chrome, + backgrounds=backgrounds, + matrix=matrix, + lines=lines, + ) + return svg + + def save_svg( + self, + path: str, + *, + title: str = "Rich", + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_SVG_FORMAT, + font_aspect_ratio: float = 0.61, + unique_id: Optional[str] = None, + ) -> None: + """Generate an SVG file from the console contents (requires record=True in Console constructor). + + Args: + path (str): The path to write the SVG to. + title (str, optional): The title of the tab in the output image + theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` + code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables + into the string in order to form the final SVG output. The default template used and the variables + injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. + font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` + string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). + If you aren't specifying a different font inside ``code_format``, you probably don't need this. + unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node + ids). If not set, this defaults to a computed value based on the recorded content. + """ + svg = self.export_svg( + title=title, + theme=theme, + clear=clear, + code_format=code_format, + font_aspect_ratio=font_aspect_ratio, + unique_id=unique_id, + ) + with open(path, "w", encoding="utf-8") as write_file: + write_file.write(svg) + + +def _svg_hash(svg_main_code: str) -> str: + """Returns a unique hash for the given SVG main code. + + Args: + svg_main_code (str): The content we're going to inject in the SVG envelope. + + Returns: + str: a hash of the given content + """ + return str(zlib.adler32(svg_main_code.encode())) + + +if __name__ == "__main__": # pragma: no cover + console = Console(record=True) + + console.log( + "JSONRPC [i]request[/i]", + 5, + 1.3, + True, + False, + None, + { + "jsonrpc": "2.0", + "method": "subtract", + "params": {"minuend": 42, "subtrahend": 23}, + "id": 3, + }, + ) + + console.log("Hello, World!", "{'a': 1}", repr(console)) + + console.print( + { + "name": None, + "empty": [], + "quiz": { + "sport": { + "answered": True, + "q1": { + "question": "Which one is correct team name in NBA?", + "options": [ + "New York Bulls", + "Los Angeles Kings", + "Golden State Warriors", + "Huston Rocket", + ], + "answer": "Huston Rocket", + }, + }, + "maths": { + "answered": False, + "q1": { + "question": "5 + 7 = ?", + "options": [10, 11, 12, 13], + "answer": 12, + }, + "q2": { + "question": "12 - 8 = ?", + "options": [1, 2, 3, 4], + "answer": 4, + }, + }, + }, + } + ) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/constrain.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/constrain.py new file mode 100644 index 000000000..65fdf5634 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/constrain.py @@ -0,0 +1,37 @@ +from typing import Optional, TYPE_CHECKING + +from .jupyter import JupyterMixin +from .measure import Measurement + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + + +class Constrain(JupyterMixin): + """Constrain the width of a renderable to a given number of characters. + + Args: + renderable (RenderableType): A renderable object. + width (int, optional): The maximum width (in characters) to render. Defaults to 80. + """ + + def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None: + self.renderable = renderable + self.width = width + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.width is None: + yield self.renderable + else: + child_options = options.update_width(min(self.width, options.max_width)) + yield from console.render(self.renderable, child_options) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.width is not None: + options = options.update_width(self.width) + measurement = Measurement.get(console, options, self.renderable) + return measurement diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/containers.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/containers.py new file mode 100644 index 000000000..901ff8ba6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/containers.py @@ -0,0 +1,167 @@ +from itertools import zip_longest +from typing import ( + TYPE_CHECKING, + Iterable, + Iterator, + List, + Optional, + TypeVar, + Union, + overload, +) + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + JustifyMethod, + OverflowMethod, + RenderResult, + RenderableType, + ) + from .text import Text + +from .cells import cell_len +from .measure import Measurement + +T = TypeVar("T") + + +class Renderables: + """A list subclass which renders its contents to the console.""" + + def __init__( + self, renderables: Optional[Iterable["RenderableType"]] = None + ) -> None: + self._renderables: List["RenderableType"] = ( + list(renderables) if renderables is not None else [] + ) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._renderables + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + dimensions = [ + Measurement.get(console, options, renderable) + for renderable in self._renderables + ] + if not dimensions: + return Measurement(1, 1) + _min = max(dimension.minimum for dimension in dimensions) + _max = max(dimension.maximum for dimension in dimensions) + return Measurement(_min, _max) + + def append(self, renderable: "RenderableType") -> None: + self._renderables.append(renderable) + + def __iter__(self) -> Iterable["RenderableType"]: + return iter(self._renderables) + + +class Lines: + """A list subclass which can render to the console.""" + + def __init__(self, lines: Iterable["Text"] = ()) -> None: + self._lines: List["Text"] = list(lines) + + def __repr__(self) -> str: + return f"Lines({self._lines!r})" + + def __iter__(self) -> Iterator["Text"]: + return iter(self._lines) + + @overload + def __getitem__(self, index: int) -> "Text": + ... + + @overload + def __getitem__(self, index: slice) -> List["Text"]: + ... + + def __getitem__(self, index: Union[slice, int]) -> Union["Text", List["Text"]]: + return self._lines[index] + + def __setitem__(self, index: int, value: "Text") -> "Lines": + self._lines[index] = value + return self + + def __len__(self) -> int: + return self._lines.__len__() + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._lines + + def append(self, line: "Text") -> None: + self._lines.append(line) + + def extend(self, lines: Iterable["Text"]) -> None: + self._lines.extend(lines) + + def pop(self, index: int = -1) -> "Text": + return self._lines.pop(index) + + def justify( + self, + console: "Console", + width: int, + justify: "JustifyMethod" = "left", + overflow: "OverflowMethod" = "fold", + ) -> None: + """Justify and overflow text to a given width. + + Args: + console (Console): Console instance. + width (int): Number of cells available per line. + justify (str, optional): Default justify method for text: "left", "center", "full" or "right". Defaults to "left". + overflow (str, optional): Default overflow for text: "crop", "fold", or "ellipsis". Defaults to "fold". + + """ + from .text import Text + + if justify == "left": + for line in self._lines: + line.truncate(width, overflow=overflow, pad=True) + elif justify == "center": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left((width - cell_len(line.plain)) // 2) + line.pad_right(width - cell_len(line.plain)) + elif justify == "right": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left(width - cell_len(line.plain)) + elif justify == "full": + for line_index, line in enumerate(self._lines): + if line_index == len(self._lines) - 1: + break + words = line.split(" ") + words_size = sum(cell_len(word.plain) for word in words) + num_spaces = len(words) - 1 + spaces = [1 for _ in range(num_spaces)] + index = 0 + if spaces: + while words_size + num_spaces < width: + spaces[len(spaces) - index - 1] += 1 + num_spaces += 1 + index = (index + 1) % len(spaces) + tokens: List[Text] = [] + for index, (word, next_word) in enumerate( + zip_longest(words, words[1:]) + ): + tokens.append(word) + if index < len(spaces): + style = word.get_style_at_offset(console, -1) + next_style = next_word.get_style_at_offset(console, 0) + space_style = style if style == next_style else line.style + tokens.append(Text(" " * spaces[index], style=space_style)) + self[line_index] = Text("").join(tokens) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/control.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/control.py new file mode 100644 index 000000000..88fcb9295 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/control.py @@ -0,0 +1,225 @@ +import sys +import time +from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union + +if sys.version_info >= (3, 8): + from typing import Final +else: + from pip._vendor.typing_extensions import Final # pragma: no cover + +from .segment import ControlCode, ControlType, Segment + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + +STRIP_CONTROL_CODES: Final = [ + 7, # Bell + 8, # Backspace + 11, # Vertical tab + 12, # Form feed + 13, # Carriage return +] +_CONTROL_STRIP_TRANSLATE: Final = { + _codepoint: None for _codepoint in STRIP_CONTROL_CODES +} + +CONTROL_ESCAPE: Final = { + 7: "\\a", + 8: "\\b", + 11: "\\v", + 12: "\\f", + 13: "\\r", +} + +CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = { + ControlType.BELL: lambda: "\x07", + ControlType.CARRIAGE_RETURN: lambda: "\r", + ControlType.HOME: lambda: "\x1b[H", + ControlType.CLEAR: lambda: "\x1b[2J", + ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h", + ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l", + ControlType.SHOW_CURSOR: lambda: "\x1b[?25h", + ControlType.HIDE_CURSOR: lambda: "\x1b[?25l", + ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A", + ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B", + ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C", + ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D", + ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G", + ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K", + ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H", + ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07", +} + + +class Control: + """A renderable that inserts a control code (non printable but may move cursor). + + Args: + *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a + tuple of ControlType and an integer parameter + """ + + __slots__ = ["segment"] + + def __init__(self, *codes: Union[ControlType, ControlCode]) -> None: + control_codes: List[ControlCode] = [ + (code,) if isinstance(code, ControlType) else code for code in codes + ] + _format_map = CONTROL_CODES_FORMAT + rendered_codes = "".join( + _format_map[code](*parameters) for code, *parameters in control_codes + ) + self.segment = Segment(rendered_codes, None, control_codes) + + @classmethod + def bell(cls) -> "Control": + """Ring the 'bell'.""" + return cls(ControlType.BELL) + + @classmethod + def home(cls) -> "Control": + """Move cursor to 'home' position.""" + return cls(ControlType.HOME) + + @classmethod + def move(cls, x: int = 0, y: int = 0) -> "Control": + """Move cursor relative to current position. + + Args: + x (int): X offset. + y (int): Y offset. + + Returns: + ~Control: Control object. + + """ + + def get_codes() -> Iterable[ControlCode]: + control = ControlType + if x: + yield ( + control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD, + abs(x), + ) + if y: + yield ( + control.CURSOR_DOWN if y > 0 else control.CURSOR_UP, + abs(y), + ) + + control = cls(*get_codes()) + return control + + @classmethod + def move_to_column(cls, x: int, y: int = 0) -> "Control": + """Move to the given column, optionally add offset to row. + + Returns: + x (int): absolute x (column) + y (int): optional y offset (row) + + Returns: + ~Control: Control object. + """ + + return ( + cls( + (ControlType.CURSOR_MOVE_TO_COLUMN, x), + ( + ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP, + abs(y), + ), + ) + if y + else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x)) + ) + + @classmethod + def move_to(cls, x: int, y: int) -> "Control": + """Move cursor to absolute position. + + Args: + x (int): x offset (column) + y (int): y offset (row) + + Returns: + ~Control: Control object. + """ + return cls((ControlType.CURSOR_MOVE_TO, x, y)) + + @classmethod + def clear(cls) -> "Control": + """Clear the screen.""" + return cls(ControlType.CLEAR) + + @classmethod + def show_cursor(cls, show: bool) -> "Control": + """Show or hide the cursor.""" + return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR) + + @classmethod + def alt_screen(cls, enable: bool) -> "Control": + """Enable or disable alt screen.""" + if enable: + return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME) + else: + return cls(ControlType.DISABLE_ALT_SCREEN) + + @classmethod + def title(cls, title: str) -> "Control": + """Set the terminal window title + + Args: + title (str): The new terminal window title + """ + return cls((ControlType.SET_WINDOW_TITLE, title)) + + def __str__(self) -> str: + return self.segment.text + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.segment.text: + yield self.segment + + +def strip_control_codes( + text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE +) -> str: + """Remove control codes from text. + + Args: + text (str): A string possibly contain control codes. + + Returns: + str: String with control codes removed. + """ + return text.translate(_translate_table) + + +def escape_control_codes( + text: str, + _translate_table: Dict[int, str] = CONTROL_ESCAPE, +) -> str: + """Replace control codes with their "escaped" equivalent in the given text. + (e.g. "\b" becomes "\\b") + + Args: + text (str): A string possibly containing control codes. + + Returns: + str: String with control codes replaced with their escaped version. + """ + return text.translate(_translate_table) + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + + console = Console() + console.print("Look at the title of your terminal window ^") + # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!"))) + for i in range(10): + console.set_window_title("🚀 Loading" + "." * i) + time.sleep(0.5) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/default_styles.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/default_styles.py new file mode 100644 index 000000000..6c0d73231 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/default_styles.py @@ -0,0 +1,191 @@ +from typing import Dict + +from .style import Style + +DEFAULT_STYLES: Dict[str, Style] = { + "none": Style.null(), + "reset": Style( + color="default", + bgcolor="default", + dim=False, + bold=False, + italic=False, + underline=False, + blink=False, + blink2=False, + reverse=False, + conceal=False, + strike=False, + ), + "dim": Style(dim=True), + "bright": Style(dim=False), + "bold": Style(bold=True), + "strong": Style(bold=True), + "code": Style(reverse=True, bold=True), + "italic": Style(italic=True), + "emphasize": Style(italic=True), + "underline": Style(underline=True), + "blink": Style(blink=True), + "blink2": Style(blink2=True), + "reverse": Style(reverse=True), + "strike": Style(strike=True), + "black": Style(color="black"), + "red": Style(color="red"), + "green": Style(color="green"), + "yellow": Style(color="yellow"), + "magenta": Style(color="magenta"), + "cyan": Style(color="cyan"), + "white": Style(color="white"), + "inspect.attr": Style(color="yellow", italic=True), + "inspect.attr.dunder": Style(color="yellow", italic=True, dim=True), + "inspect.callable": Style(bold=True, color="red"), + "inspect.async_def": Style(italic=True, color="bright_cyan"), + "inspect.def": Style(italic=True, color="bright_cyan"), + "inspect.class": Style(italic=True, color="bright_cyan"), + "inspect.error": Style(bold=True, color="red"), + "inspect.equals": Style(), + "inspect.help": Style(color="cyan"), + "inspect.doc": Style(dim=True), + "inspect.value.border": Style(color="green"), + "live.ellipsis": Style(bold=True, color="red"), + "layout.tree.row": Style(dim=False, color="red"), + "layout.tree.column": Style(dim=False, color="blue"), + "logging.keyword": Style(bold=True, color="yellow"), + "logging.level.notset": Style(dim=True), + "logging.level.debug": Style(color="green"), + "logging.level.info": Style(color="blue"), + "logging.level.warning": Style(color="yellow"), + "logging.level.error": Style(color="red", bold=True), + "logging.level.critical": Style(color="red", bold=True, reverse=True), + "log.level": Style.null(), + "log.time": Style(color="cyan", dim=True), + "log.message": Style.null(), + "log.path": Style(dim=True), + "repr.ellipsis": Style(color="yellow"), + "repr.indent": Style(color="green", dim=True), + "repr.error": Style(color="red", bold=True), + "repr.str": Style(color="green", italic=False, bold=False), + "repr.brace": Style(bold=True), + "repr.comma": Style(bold=True), + "repr.ipv4": Style(bold=True, color="bright_green"), + "repr.ipv6": Style(bold=True, color="bright_green"), + "repr.eui48": Style(bold=True, color="bright_green"), + "repr.eui64": Style(bold=True, color="bright_green"), + "repr.tag_start": Style(bold=True), + "repr.tag_name": Style(color="bright_magenta", bold=True), + "repr.tag_contents": Style(color="default"), + "repr.tag_end": Style(bold=True), + "repr.attrib_name": Style(color="yellow", italic=False), + "repr.attrib_equal": Style(bold=True), + "repr.attrib_value": Style(color="magenta", italic=False), + "repr.number": Style(color="cyan", bold=True, italic=False), + "repr.number_complex": Style(color="cyan", bold=True, italic=False), # same + "repr.bool_true": Style(color="bright_green", italic=True), + "repr.bool_false": Style(color="bright_red", italic=True), + "repr.none": Style(color="magenta", italic=True), + "repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False), + "repr.uuid": Style(color="bright_yellow", bold=False), + "repr.call": Style(color="magenta", bold=True), + "repr.path": Style(color="magenta"), + "repr.filename": Style(color="bright_magenta"), + "rule.line": Style(color="bright_green"), + "rule.text": Style.null(), + "json.brace": Style(bold=True), + "json.bool_true": Style(color="bright_green", italic=True), + "json.bool_false": Style(color="bright_red", italic=True), + "json.null": Style(color="magenta", italic=True), + "json.number": Style(color="cyan", bold=True, italic=False), + "json.str": Style(color="green", italic=False, bold=False), + "json.key": Style(color="blue", bold=True), + "prompt": Style.null(), + "prompt.choices": Style(color="magenta", bold=True), + "prompt.default": Style(color="cyan", bold=True), + "prompt.invalid": Style(color="red"), + "prompt.invalid.choice": Style(color="red"), + "pretty": Style.null(), + "scope.border": Style(color="blue"), + "scope.key": Style(color="yellow", italic=True), + "scope.key.special": Style(color="yellow", italic=True, dim=True), + "scope.equals": Style(color="red"), + "table.header": Style(bold=True), + "table.footer": Style(bold=True), + "table.cell": Style.null(), + "table.title": Style(italic=True), + "table.caption": Style(italic=True, dim=True), + "traceback.error": Style(color="red", italic=True), + "traceback.border.syntax_error": Style(color="bright_red"), + "traceback.border": Style(color="red"), + "traceback.text": Style.null(), + "traceback.title": Style(color="red", bold=True), + "traceback.exc_type": Style(color="bright_red", bold=True), + "traceback.exc_value": Style.null(), + "traceback.offset": Style(color="bright_red", bold=True), + "traceback.error_range": Style(underline=True, bold=True, dim=False), + "bar.back": Style(color="grey23"), + "bar.complete": Style(color="rgb(249,38,114)"), + "bar.finished": Style(color="rgb(114,156,31)"), + "bar.pulse": Style(color="rgb(249,38,114)"), + "progress.description": Style.null(), + "progress.filesize": Style(color="green"), + "progress.filesize.total": Style(color="green"), + "progress.download": Style(color="green"), + "progress.elapsed": Style(color="yellow"), + "progress.percentage": Style(color="magenta"), + "progress.remaining": Style(color="cyan"), + "progress.data.speed": Style(color="red"), + "progress.spinner": Style(color="green"), + "status.spinner": Style(color="green"), + "tree": Style(), + "tree.line": Style(), + "markdown.paragraph": Style(), + "markdown.text": Style(), + "markdown.em": Style(italic=True), + "markdown.emph": Style(italic=True), # For commonmark backwards compatibility + "markdown.strong": Style(bold=True), + "markdown.code": Style(bold=True, color="cyan", bgcolor="black"), + "markdown.code_block": Style(color="cyan", bgcolor="black"), + "markdown.block_quote": Style(color="magenta"), + "markdown.list": Style(color="cyan"), + "markdown.item": Style(), + "markdown.item.bullet": Style(color="yellow", bold=True), + "markdown.item.number": Style(color="yellow", bold=True), + "markdown.hr": Style(color="yellow"), + "markdown.h1.border": Style(), + "markdown.h1": Style(bold=True), + "markdown.h2": Style(bold=True, underline=True), + "markdown.h3": Style(bold=True), + "markdown.h4": Style(bold=True, dim=True), + "markdown.h5": Style(underline=True), + "markdown.h6": Style(italic=True), + "markdown.h7": Style(italic=True, dim=True), + "markdown.link": Style(color="bright_blue"), + "markdown.link_url": Style(color="blue", underline=True), + "markdown.s": Style(strike=True), + "iso8601.date": Style(color="blue"), + "iso8601.time": Style(color="magenta"), + "iso8601.timezone": Style(color="yellow"), +} + + +if __name__ == "__main__": # pragma: no cover + import argparse + import io + + from pip._vendor.rich.console import Console + from pip._vendor.rich.table import Table + from pip._vendor.rich.text import Text + + parser = argparse.ArgumentParser() + parser.add_argument("--html", action="store_true", help="Export as HTML table") + args = parser.parse_args() + html: bool = args.html + console = Console(record=True, width=70, file=io.StringIO()) if html else Console() + + table = Table("Name", "Styling") + + for style_name, style in DEFAULT_STYLES.items(): + table.add_row(Text(style_name, style=style), str(style)) + + console.print(table) + if html: + print(console.export_html(inline_styles=True)) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/diagnose.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/diagnose.py new file mode 100644 index 000000000..ad3618389 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/diagnose.py @@ -0,0 +1,37 @@ +import os +import platform + +from pip._vendor.rich import inspect +from pip._vendor.rich.console import Console, get_windows_console_features +from pip._vendor.rich.panel import Panel +from pip._vendor.rich.pretty import Pretty + + +def report() -> None: # pragma: no cover + """Print a report to the terminal with debugging information""" + console = Console() + inspect(console) + features = get_windows_console_features() + inspect(features) + + env_names = ( + "TERM", + "COLORTERM", + "CLICOLOR", + "NO_COLOR", + "TERM_PROGRAM", + "COLUMNS", + "LINES", + "JUPYTER_COLUMNS", + "JUPYTER_LINES", + "JPY_PARENT_PID", + "VSCODE_VERBOSE_LOGGING", + ) + env = {name: os.getenv(name) for name in env_names} + console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables")) + + console.print(f'platform="{platform.system()}"') + + +if __name__ == "__main__": # pragma: no cover + report() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/emoji.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/emoji.py new file mode 100644 index 000000000..791f0465d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/emoji.py @@ -0,0 +1,96 @@ +import sys +from typing import TYPE_CHECKING, Optional, Union + +from .jupyter import JupyterMixin +from .segment import Segment +from .style import Style +from ._emoji_codes import EMOJI +from ._emoji_replace import _emoji_replace + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + + +EmojiVariant = Literal["emoji", "text"] + + +class NoEmoji(Exception): + """No emoji by that name.""" + + +class Emoji(JupyterMixin): + __slots__ = ["name", "style", "_char", "variant"] + + VARIANTS = {"text": "\uFE0E", "emoji": "\uFE0F"} + + def __init__( + self, + name: str, + style: Union[str, Style] = "none", + variant: Optional[EmojiVariant] = None, + ) -> None: + """A single emoji character. + + Args: + name (str): Name of emoji. + style (Union[str, Style], optional): Optional style. Defaults to None. + + Raises: + NoEmoji: If the emoji doesn't exist. + """ + self.name = name + self.style = style + self.variant = variant + try: + self._char = EMOJI[name] + except KeyError: + raise NoEmoji(f"No emoji called {name!r}") + if variant is not None: + self._char += self.VARIANTS.get(variant, "") + + @classmethod + def replace(cls, text: str) -> str: + """Replace emoji markup with corresponding unicode characters. + + Args: + text (str): A string with emojis codes, e.g. "Hello :smiley:!" + + Returns: + str: A string with emoji codes replaces with actual emoji. + """ + return _emoji_replace(text) + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self._char + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + yield Segment(self._char, console.get_style(self.style)) + + +if __name__ == "__main__": # pragma: no cover + import sys + + from pip._vendor.rich.columns import Columns + from pip._vendor.rich.console import Console + + console = Console(record=True) + + columns = Columns( + (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200D" not in name), + column_first=True, + ) + + console.print(columns) + if len(sys.argv) > 1: + console.save_html(sys.argv[1]) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/errors.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/errors.py new file mode 100644 index 000000000..0bcbe53ef --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/errors.py @@ -0,0 +1,34 @@ +class ConsoleError(Exception): + """An error in console operation.""" + + +class StyleError(Exception): + """An error in styles.""" + + +class StyleSyntaxError(ConsoleError): + """Style was badly formatted.""" + + +class MissingStyle(StyleError): + """No such style.""" + + +class StyleStackError(ConsoleError): + """Style stack is invalid.""" + + +class NotRenderableError(ConsoleError): + """Object is not renderable.""" + + +class MarkupError(ConsoleError): + """Markup was badly formatted.""" + + +class LiveError(ConsoleError): + """Error related to Live display.""" + + +class NoAltScreen(ConsoleError): + """Alt screen mode was required.""" diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/file_proxy.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/file_proxy.py new file mode 100644 index 000000000..4b0b0da6c --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/file_proxy.py @@ -0,0 +1,57 @@ +import io +from typing import IO, TYPE_CHECKING, Any, List + +from .ansi import AnsiDecoder +from .text import Text + +if TYPE_CHECKING: + from .console import Console + + +class FileProxy(io.TextIOBase): + """Wraps a file (e.g. sys.stdout) and redirects writes to a console.""" + + def __init__(self, console: "Console", file: IO[str]) -> None: + self.__console = console + self.__file = file + self.__buffer: List[str] = [] + self.__ansi_decoder = AnsiDecoder() + + @property + def rich_proxied_file(self) -> IO[str]: + """Get proxied file.""" + return self.__file + + def __getattr__(self, name: str) -> Any: + return getattr(self.__file, name) + + def write(self, text: str) -> int: + if not isinstance(text, str): + raise TypeError(f"write() argument must be str, not {type(text).__name__}") + buffer = self.__buffer + lines: List[str] = [] + while text: + line, new_line, text = text.partition("\n") + if new_line: + lines.append("".join(buffer) + line) + buffer.clear() + else: + buffer.append(line) + break + if lines: + console = self.__console + with console: + output = Text("\n").join( + self.__ansi_decoder.decode_line(line) for line in lines + ) + console.print(output) + return len(text) + + def flush(self) -> None: + output = "".join(self.__buffer) + if output: + self.__console.print(output) + del self.__buffer[:] + + def fileno(self) -> int: + return self.__file.fileno() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/filesize.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/filesize.py new file mode 100644 index 000000000..83bc9118d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/filesize.py @@ -0,0 +1,88 @@ +"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2 + +The functions declared in this module should cover the different +use cases needed to generate a string representation of a file size +using several different units. Since there are many standards regarding +file size units, three different functions have been implemented. + +See Also: + * `Wikipedia: Binary prefix `_ + +""" + +__all__ = ["decimal"] + +from typing import Iterable, List, Optional, Tuple + + +def _to_str( + size: int, + suffixes: Iterable[str], + base: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + if size == 1: + return "1 byte" + elif size < base: + return f"{size:,} bytes" + + for i, suffix in enumerate(suffixes, 2): # noqa: B007 + unit = base**i + if size < unit: + break + return "{:,.{precision}f}{separator}{}".format( + (base * size / unit), + suffix, + precision=precision, + separator=separator, + ) + + +def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]: + """Pick a suffix and base for the given size.""" + for i, suffix in enumerate(suffixes): + unit = base**i + if size < unit * base: + break + return unit, suffix + + +def decimal( + size: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + """Convert a filesize in to a string (powers of 1000, SI prefixes). + + In this convention, ``1000 B = 1 kB``. + + This is typically the format used to advertise the storage + capacity of USB flash drives and the like (*256 MB* meaning + actually a storage capacity of more than *256 000 000 B*), + or used by **Mac OS X** since v10.6 to report file sizes. + + Arguments: + int (size): A file size. + int (precision): The number of decimal places to include (default = 1). + str (separator): The string to separate the value from the units (default = " "). + + Returns: + `str`: A string containing a abbreviated file size and units. + + Example: + >>> filesize.decimal(30000) + '30.0 kB' + >>> filesize.decimal(30000, precision=2, separator="") + '30.00kB' + + """ + return _to_str( + size, + ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"), + 1000, + precision=precision, + separator=separator, + ) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/rich/highlighter.py b/venv/lib/python3.8/site-packages/pip/_vendor/rich/highlighter.py new file mode 100644 index 000000000..e4c462e2b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/rich/highlighter.py @@ -0,0 +1,232 @@ +import re +from abc import ABC, abstractmethod +from typing import List, Union + +from .text import Span, Text + + +def _combine_regex(*regexes: str) -> str: + """Combine a number of regexes in to a single regex. + + Returns: + str: New regex with all regexes ORed together. + """ + return "|".join(regexes) + + +class Highlighter(ABC): + """Abstract base class for highlighters.""" + + def __call__(self, text: Union[str, Text]) -> Text: + """Highlight a str or Text instance. + + Args: + text (Union[str, ~Text]): Text to highlight. + + Raises: + TypeError: If not called with text or str. + + Returns: + Text: A test instance with highlighting applied. + """ + if isinstance(text, str): + highlight_text = Text(text) + elif isinstance(text, Text): + highlight_text = text.copy() + else: + raise TypeError(f"str or Text instance required, not {text!r}") + self.highlight(highlight_text) + return highlight_text + + @abstractmethod + def highlight(self, text: Text) -> None: + """Apply highlighting in place to text. + + Args: + text (~Text): A text object highlight. + """ + + +class NullHighlighter(Highlighter): + """A highlighter object that doesn't highlight. + + May be used to disable highlighting entirely. + + """ + + def highlight(self, text: Text) -> None: + """Nothing to do""" + + +class RegexHighlighter(Highlighter): + """Applies highlighting from a list of regular expressions.""" + + highlights: List[str] = [] + base_style: str = "" + + def highlight(self, text: Text) -> None: + """Highlight :class:`rich.text.Text` using regular expressions. + + Args: + text (~Text): Text to highlighted. + + """ + + highlight_regex = text.highlight_regex + for re_highlight in self.highlights: + highlight_regex(re_highlight, style_prefix=self.base_style) + + +class ReprHighlighter(RegexHighlighter): + """Highlights the text typically produced from ``__repr__`` methods.""" + + base_style = "repr." + highlights = [ + r"(?P<)(?P[-\w.:|]*)(?P[\w\W]*)(?P>)", + r'(?P[\w_]{1,50})=(?P"?[\w_]+"?)?', + r"(?P[][{}()])", + _combine_regex( + r"(?P[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})", + r"(?P([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})", + r"(?P[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})", + r"(?P[\w.]*?)\(", + r"\b(?PTrue)\b|\b(?PFalse)\b|\b(?PNone)\b", + r"(?P\.\.\.)", + r"(?P(?(?\B(/[-\w._+]+)*\/)(?P[-\w._+]*)?", + r"(?b?'''.*?(?(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)", + ), + ] + + +class JSONHighlighter(RegexHighlighter): + """Highlights JSON""" + + # Captures the start and end of JSON strings, handling escaped quotes + JSON_STR = r"(?b?\".*?(?[\{\[\(\)\]\}])", + r"\b(?Ptrue)\b|\b(?Pfalse)\b|\b(?Pnull)\b", + r"(?P(? None: + super().highlight(text) + + # Additional work to handle highlighting JSON keys + plain = text.plain + append = text.spans.append + whitespace = self.JSON_WHITESPACE + for match in re.finditer(self.JSON_STR, plain): + start, end = match.span() + cursor = end + while cursor < len(plain): + char = plain[cursor] + cursor += 1 + if char == ":": + append(Span(start, end, "json.key")) + elif char in whitespace: + continue + break + + +class ISO8601Highlighter(RegexHighlighter): + """Highlights the ISO8601 date time strings. + Regex reference: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html + """ + + base_style = "iso8601." + highlights = [ + # + # Dates + # + # Calendar month (e.g. 2008-08). The hyphen is required + r"^(?P[0-9]{4})-(?P1[0-2]|0[1-9])$", + # Calendar date w/o hyphens (e.g. 20080830) + r"^(?P(?P[0-9]{4})(?P1[0-2]|0[1-9])(?P3[01]|0[1-9]|[12][0-9]))$", + # Ordinal date (e.g. 2008-243). The hyphen is optional + r"^(?P(?P[0-9]{4})-?(?P36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9]))$", + # + # Weeks + # + # Week of the year (e.g., 2008-W35). The hyphen is optional + r"^(?P(?P[0-9]{4})-?W(?P5[0-3]|[1-4][0-9]|0[1-9]))$", + # Week date (e.g., 2008-W35-6). The hyphens are optional + r"^(?P(?P[0-9]{4})-?W(?P5[0-3]|[1-4][0-9]|0[1-9])-?(?P[1-7]))$", + # + # Times + # + # Hours and minutes (e.g., 17:21). The colon is optional + r"^(?P

        ' : '\U0001d4ab', + '\\' : '\U0001d4ac', + '\\' : '\U0000211b', + '\\' : '\U0001d4ae', + '\\' : '\U0001d4af', + '\\' : '\U0001d4b0', + '\\' : '\U0001d4b1', + '\\' : '\U0001d4b2', + '\\' : '\U0001d4b3', + '\\' : '\U0001d4b4', + '\\' : '\U0001d4b5', + '\\' : '\U0001d5ba', + '\\' : '\U0001d5bb', + '\\' : '\U0001d5bc', + '\\' : '\U0001d5bd', + '\\' : '\U0001d5be', + '\\' : '\U0001d5bf', + '\\' : '\U0001d5c0', + '\\' : '\U0001d5c1', + '\\' : '\U0001d5c2', + '\\' : '\U0001d5c3', + '\\' : '\U0001d5c4', + '\\' : '\U0001d5c5', + '\\' : '\U0001d5c6', + '\\' : '\U0001d5c7', + '\\' : '\U0001d5c8', + '\\

        !I7S{BS4i1|@R7s@ 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/t64.exe b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/t64.exe new file mode 100644 index 0000000000000000000000000000000000000000..e8bebdba6d8f242244bf397ab067965d47c5093e GIT binary patch literal 108032 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%0AHbpwE%ZM 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 H#ex3;_fXA_ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/util.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/util.py new file mode 100644 index 000000000..0d5bd7a8b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/util.py @@ -0,0 +1,1984 @@ +# +# Copyright (C) 2012-2023 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import codecs +from collections import deque +import contextlib +import csv +from glob import iglob as std_iglob +import io +import json +import logging +import os +import py_compile +import re +import socket +try: + import ssl +except ImportError: # pragma: no cover + ssl = None +import subprocess +import sys +import tarfile +import tempfile +import textwrap + +try: + import threading +except ImportError: # pragma: no cover + import dummy_threading as threading +import time + +from . import DistlibException +from .compat import (string_types, text_type, shutil, raw_input, StringIO, cache_from_source, urlopen, urljoin, httplib, + xmlrpclib, HTTPHandler, BaseConfigurator, valid_ident, Container, configparser, URLError, ZipFile, + fsdecode, unquote, urlparse) + +logger = logging.getLogger(__name__) + +# +# Requirement parsing code as per PEP 508 +# + +IDENTIFIER = re.compile(r'^([\w\.-]+)\s*') +VERSION_IDENTIFIER = re.compile(r'^([\w\.*+-]+)\s*') +COMPARE_OP = re.compile(r'^(<=?|>=?|={2,3}|[~!]=)\s*') +MARKER_OP = re.compile(r'^((<=?)|(>=?)|={2,3}|[~!]=|in|not\s+in)\s*') +OR = re.compile(r'^or\b\s*') +AND = re.compile(r'^and\b\s*') +NON_SPACE = re.compile(r'(\S+)\s*') +STRING_CHUNK = re.compile(r'([\s\w\.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]+)') + + +def parse_marker(marker_string): + """ + Parse a marker string and return a dictionary containing a marker expression. + + The dictionary will contain keys "op", "lhs" and "rhs" for non-terminals in + the expression grammar, or strings. A string contained in quotes is to be + interpreted as a literal string, and a string not contained in quotes is a + variable (such as os_name). + """ + + def marker_var(remaining): + # either identifier, or literal string + m = IDENTIFIER.match(remaining) + if m: + result = m.groups()[0] + remaining = remaining[m.end():] + elif not remaining: + raise SyntaxError('unexpected end of input') + else: + q = remaining[0] + if q not in '\'"': + raise SyntaxError('invalid expression: %s' % remaining) + oq = '\'"'.replace(q, '') + remaining = remaining[1:] + parts = [q] + while remaining: + # either a string chunk, or oq, or q to terminate + if remaining[0] == q: + break + elif remaining[0] == oq: + parts.append(oq) + remaining = remaining[1:] + else: + m = STRING_CHUNK.match(remaining) + if not m: + raise SyntaxError('error in string literal: %s' % remaining) + parts.append(m.groups()[0]) + remaining = remaining[m.end():] + else: + s = ''.join(parts) + raise SyntaxError('unterminated string: %s' % s) + parts.append(q) + result = ''.join(parts) + remaining = remaining[1:].lstrip() # skip past closing quote + return result, remaining + + def marker_expr(remaining): + if remaining and remaining[0] == '(': + result, remaining = marker(remaining[1:].lstrip()) + if remaining[0] != ')': + raise SyntaxError('unterminated parenthesis: %s' % remaining) + remaining = remaining[1:].lstrip() + else: + lhs, remaining = marker_var(remaining) + while remaining: + m = MARKER_OP.match(remaining) + if not m: + break + op = m.groups()[0] + remaining = remaining[m.end():] + rhs, remaining = marker_var(remaining) + lhs = {'op': op, 'lhs': lhs, 'rhs': rhs} + result = lhs + return result, remaining + + def marker_and(remaining): + lhs, remaining = marker_expr(remaining) + while remaining: + m = AND.match(remaining) + if not m: + break + remaining = remaining[m.end():] + rhs, remaining = marker_expr(remaining) + lhs = {'op': 'and', 'lhs': lhs, 'rhs': rhs} + return lhs, remaining + + def marker(remaining): + lhs, remaining = marker_and(remaining) + while remaining: + m = OR.match(remaining) + if not m: + break + remaining = remaining[m.end():] + rhs, remaining = marker_and(remaining) + lhs = {'op': 'or', 'lhs': lhs, 'rhs': rhs} + return lhs, remaining + + return marker(marker_string) + + +def parse_requirement(req): + """ + Parse a requirement passed in as a string. Return a Container + whose attributes contain the various parts of the requirement. + """ + remaining = req.strip() + if not remaining or remaining.startswith('#'): + return None + m = IDENTIFIER.match(remaining) + if not m: + raise SyntaxError('name expected: %s' % remaining) + distname = m.groups()[0] + remaining = remaining[m.end():] + extras = mark_expr = versions = uri = None + if remaining and remaining[0] == '[': + i = remaining.find(']', 1) + if i < 0: + raise SyntaxError('unterminated extra: %s' % remaining) + s = remaining[1:i] + remaining = remaining[i + 1:].lstrip() + extras = [] + while s: + m = IDENTIFIER.match(s) + if not m: + raise SyntaxError('malformed extra: %s' % s) + extras.append(m.groups()[0]) + s = s[m.end():] + if not s: + break + if s[0] != ',': + raise SyntaxError('comma expected in extras: %s' % s) + s = s[1:].lstrip() + if not extras: + extras = None + if remaining: + if remaining[0] == '@': + # it's a URI + remaining = remaining[1:].lstrip() + m = NON_SPACE.match(remaining) + if not m: + raise SyntaxError('invalid URI: %s' % remaining) + uri = m.groups()[0] + t = urlparse(uri) + # there are issues with Python and URL parsing, so this test + # is a bit crude. See bpo-20271, bpo-23505. Python doesn't + # always parse invalid URLs correctly - it should raise + # exceptions for malformed URLs + if not (t.scheme and t.netloc): + raise SyntaxError('Invalid URL: %s' % uri) + remaining = remaining[m.end():].lstrip() + else: + + def get_versions(ver_remaining): + """ + Return a list of operator, version tuples if any are + specified, else None. + """ + m = COMPARE_OP.match(ver_remaining) + versions = None + if m: + versions = [] + while True: + op = m.groups()[0] + ver_remaining = ver_remaining[m.end():] + m = VERSION_IDENTIFIER.match(ver_remaining) + if not m: + raise SyntaxError('invalid version: %s' % ver_remaining) + v = m.groups()[0] + versions.append((op, v)) + ver_remaining = ver_remaining[m.end():] + if not ver_remaining or ver_remaining[0] != ',': + break + ver_remaining = ver_remaining[1:].lstrip() + # Some packages have a trailing comma which would break things + # See issue #148 + if not ver_remaining: + break + m = COMPARE_OP.match(ver_remaining) + if not m: + raise SyntaxError('invalid constraint: %s' % ver_remaining) + if not versions: + versions = None + return versions, ver_remaining + + if remaining[0] != '(': + versions, remaining = get_versions(remaining) + else: + i = remaining.find(')', 1) + if i < 0: + raise SyntaxError('unterminated parenthesis: %s' % remaining) + s = remaining[1:i] + remaining = remaining[i + 1:].lstrip() + # As a special diversion from PEP 508, allow a version number + # a.b.c in parentheses as a synonym for ~= a.b.c (because this + # is allowed in earlier PEPs) + if COMPARE_OP.match(s): + versions, _ = get_versions(s) + else: + m = VERSION_IDENTIFIER.match(s) + if not m: + raise SyntaxError('invalid constraint: %s' % s) + v = m.groups()[0] + s = s[m.end():].lstrip() + if s: + raise SyntaxError('invalid constraint: %s' % s) + versions = [('~=', v)] + + if remaining: + if remaining[0] != ';': + raise SyntaxError('invalid requirement: %s' % remaining) + remaining = remaining[1:].lstrip() + + mark_expr, remaining = parse_marker(remaining) + + if remaining and remaining[0] != '#': + raise SyntaxError('unexpected trailing data: %s' % remaining) + + if not versions: + rs = distname + else: + rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions])) + return Container(name=distname, extras=extras, constraints=versions, marker=mark_expr, url=uri, requirement=rs) + + +def get_resources_dests(resources_root, rules): + """Find destinations for resources files""" + + def get_rel_path(root, path): + # normalizes and returns a lstripped-/-separated path + root = root.replace(os.path.sep, '/') + path = path.replace(os.path.sep, '/') + assert path.startswith(root) + return path[len(root):].lstrip('/') + + destinations = {} + for base, suffix, dest in rules: + prefix = os.path.join(resources_root, base) + for abs_base in iglob(prefix): + abs_glob = os.path.join(abs_base, suffix) + for abs_path in iglob(abs_glob): + resource_file = get_rel_path(resources_root, abs_path) + if dest is None: # remove the entry if it was here + destinations.pop(resource_file, None) + else: + rel_path = get_rel_path(abs_base, abs_path) + rel_dest = dest.replace(os.path.sep, '/').rstrip('/') + destinations[resource_file] = rel_dest + '/' + rel_path + return destinations + + +def in_venv(): + if hasattr(sys, 'real_prefix'): + # virtualenv venvs + result = True + else: + # PEP 405 venvs + result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix) + return result + + +def get_executable(): + # The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as + # changes to the stub launcher mean that sys.executable always points + # to the stub on OS X + # if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__' + # in os.environ): + # result = os.environ['__PYVENV_LAUNCHER__'] + # else: + # result = sys.executable + # return result + # Avoid normcasing: see issue #143 + # result = os.path.normcase(sys.executable) + result = sys.executable + if not isinstance(result, text_type): + result = fsdecode(result) + return result + + +def proceed(prompt, allowed_chars, error_prompt=None, default=None): + p = prompt + while True: + s = raw_input(p) + p = prompt + if not s and default: + s = default + if s: + c = s[0].lower() + if c in allowed_chars: + break + if error_prompt: + p = '%c: %s\n%s' % (c, error_prompt, prompt) + return c + + +def extract_by_key(d, keys): + if isinstance(keys, string_types): + keys = keys.split() + result = {} + for key in keys: + if key in d: + result[key] = d[key] + return result + + +def read_exports(stream): + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getreader('utf-8')(stream) + # Try to load as JSON, falling back on legacy format + data = stream.read() + stream = StringIO(data) + try: + jdata = json.load(stream) + result = jdata['extensions']['python.exports']['exports'] + for group, entries in result.items(): + for k, v in entries.items(): + s = '%s = %s' % (k, v) + entry = get_export_entry(s) + assert entry is not None + entries[k] = entry + return result + except Exception: + stream.seek(0, 0) + + def read_stream(cp, stream): + if hasattr(cp, 'read_file'): + cp.read_file(stream) + else: + cp.readfp(stream) + + cp = configparser.ConfigParser() + try: + read_stream(cp, stream) + except configparser.MissingSectionHeaderError: + stream.close() + data = textwrap.dedent(data) + stream = StringIO(data) + read_stream(cp, stream) + + result = {} + for key in cp.sections(): + result[key] = entries = {} + for name, value in cp.items(key): + s = '%s = %s' % (name, value) + entry = get_export_entry(s) + assert entry is not None + # entry.dist = self + entries[name] = entry + return result + + +def write_exports(exports, stream): + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getwriter('utf-8')(stream) + cp = configparser.ConfigParser() + for k, v in exports.items(): + # TODO check k, v for valid values + cp.add_section(k) + for entry in v.values(): + if entry.suffix is None: + s = entry.prefix + else: + s = '%s:%s' % (entry.prefix, entry.suffix) + if entry.flags: + s = '%s [%s]' % (s, ', '.join(entry.flags)) + cp.set(k, entry.name, s) + cp.write(stream) + + +@contextlib.contextmanager +def tempdir(): + td = tempfile.mkdtemp() + try: + yield td + finally: + shutil.rmtree(td) + + +@contextlib.contextmanager +def chdir(d): + cwd = os.getcwd() + try: + os.chdir(d) + yield + finally: + os.chdir(cwd) + + +@contextlib.contextmanager +def socket_timeout(seconds=15): + cto = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(seconds) + yield + finally: + socket.setdefaulttimeout(cto) + + +class cached_property(object): + + def __init__(self, func): + self.func = func + # for attr in ('__name__', '__module__', '__doc__'): + # setattr(self, attr, getattr(func, attr, None)) + + def __get__(self, obj, cls=None): + if obj is None: + return self + value = self.func(obj) + object.__setattr__(obj, self.func.__name__, value) + # obj.__dict__[self.func.__name__] = value = self.func(obj) + return value + + +def convert_path(pathname): + """Return 'pathname' as a name that will work on the native filesystem. + + The path is split on '/' and put back together again using the current + directory separator. Needed because filenames in the setup script are + always supplied in Unix style, and have to be converted to the local + convention before we can actually use them in the filesystem. Raises + ValueError on non-Unix-ish systems if 'pathname' either starts or + ends with a slash. + """ + if os.sep == '/': + return pathname + if not pathname: + return pathname + if pathname[0] == '/': + raise ValueError("path '%s' cannot be absolute" % pathname) + if pathname[-1] == '/': + raise ValueError("path '%s' cannot end with '/'" % pathname) + + paths = pathname.split('/') + while os.curdir in paths: + paths.remove(os.curdir) + if not paths: + return os.curdir + return os.path.join(*paths) + + +class FileOperator(object): + + def __init__(self, dry_run=False): + self.dry_run = dry_run + self.ensured = set() + self._init_record() + + def _init_record(self): + self.record = False + self.files_written = set() + self.dirs_created = set() + + def record_as_written(self, path): + if self.record: + self.files_written.add(path) + + def newer(self, source, target): + """Tell if the target is newer than the source. + + Returns true if 'source' exists and is more recently modified than + 'target', or if 'source' exists and 'target' doesn't. + + Returns false if both exist and 'target' is the same age or younger + than 'source'. Raise PackagingFileError if 'source' does not exist. + + Note that this test is not very accurate: files created in the same + second will have the same "age". + """ + if not os.path.exists(source): + raise DistlibException("file '%r' does not exist" % os.path.abspath(source)) + if not os.path.exists(target): + return True + + return os.stat(source).st_mtime > os.stat(target).st_mtime + + def copy_file(self, infile, outfile, check=True): + """Copy a file respecting dry-run and force flags. + """ + self.ensure_dir(os.path.dirname(outfile)) + logger.info('Copying %s to %s', infile, outfile) + if not self.dry_run: + msg = None + if check: + if os.path.islink(outfile): + msg = '%s is a symlink' % outfile + elif os.path.exists(outfile) and not os.path.isfile(outfile): + msg = '%s is a non-regular file' % outfile + if msg: + raise ValueError(msg + ' which would be overwritten') + shutil.copyfile(infile, outfile) + self.record_as_written(outfile) + + def copy_stream(self, instream, outfile, encoding=None): + assert not os.path.isdir(outfile) + self.ensure_dir(os.path.dirname(outfile)) + logger.info('Copying stream %s to %s', instream, outfile) + if not self.dry_run: + if encoding is None: + outstream = open(outfile, 'wb') + else: + outstream = codecs.open(outfile, 'w', encoding=encoding) + try: + shutil.copyfileobj(instream, outstream) + finally: + outstream.close() + self.record_as_written(outfile) + + def write_binary_file(self, path, data): + self.ensure_dir(os.path.dirname(path)) + if not self.dry_run: + if os.path.exists(path): + os.remove(path) + with open(path, 'wb') as f: + f.write(data) + self.record_as_written(path) + + def write_text_file(self, path, data, encoding): + self.write_binary_file(path, data.encode(encoding)) + + def set_mode(self, bits, mask, files): + if os.name == 'posix' or (os.name == 'java' and os._name == 'posix'): + # Set the executable bits (owner, group, and world) on + # all the files specified. + for f in files: + if self.dry_run: + logger.info("changing mode of %s", f) + else: + mode = (os.stat(f).st_mode | bits) & mask + logger.info("changing mode of %s to %o", f, mode) + os.chmod(f, mode) + + set_executable_mode = lambda s, f: s.set_mode(0o555, 0o7777, f) + + def ensure_dir(self, path): + path = os.path.abspath(path) + if path not in self.ensured and not os.path.exists(path): + self.ensured.add(path) + d, f = os.path.split(path) + self.ensure_dir(d) + logger.info('Creating %s' % path) + if not self.dry_run: + os.mkdir(path) + if self.record: + self.dirs_created.add(path) + + def byte_compile(self, path, optimize=False, force=False, prefix=None, hashed_invalidation=False): + dpath = cache_from_source(path, not optimize) + logger.info('Byte-compiling %s to %s', path, dpath) + if not self.dry_run: + if force or self.newer(path, dpath): + if not prefix: + diagpath = None + else: + assert path.startswith(prefix) + diagpath = path[len(prefix):] + compile_kwargs = {} + if hashed_invalidation and hasattr(py_compile, 'PycInvalidationMode'): + if not isinstance(hashed_invalidation, py_compile.PycInvalidationMode): + hashed_invalidation = py_compile.PycInvalidationMode.CHECKED_HASH + compile_kwargs['invalidation_mode'] = hashed_invalidation + py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error + self.record_as_written(dpath) + return dpath + + def ensure_removed(self, path): + if os.path.exists(path): + if os.path.isdir(path) and not os.path.islink(path): + logger.debug('Removing directory tree at %s', path) + if not self.dry_run: + shutil.rmtree(path) + if self.record: + if path in self.dirs_created: + self.dirs_created.remove(path) + else: + if os.path.islink(path): + s = 'link' + else: + s = 'file' + logger.debug('Removing %s %s', s, path) + if not self.dry_run: + os.remove(path) + if self.record: + if path in self.files_written: + self.files_written.remove(path) + + def is_writable(self, path): + result = False + while not result: + if os.path.exists(path): + result = os.access(path, os.W_OK) + break + parent = os.path.dirname(path) + if parent == path: + break + path = parent + return result + + def commit(self): + """ + Commit recorded changes, turn off recording, return + changes. + """ + assert self.record + result = self.files_written, self.dirs_created + self._init_record() + return result + + def rollback(self): + if not self.dry_run: + for f in list(self.files_written): + if os.path.exists(f): + os.remove(f) + # dirs should all be empty now, except perhaps for + # __pycache__ subdirs + # reverse so that subdirs appear before their parents + dirs = sorted(self.dirs_created, reverse=True) + for d in dirs: + flist = os.listdir(d) + if flist: + assert flist == ['__pycache__'] + sd = os.path.join(d, flist[0]) + os.rmdir(sd) + os.rmdir(d) # should fail if non-empty + self._init_record() + + +def resolve(module_name, dotted_path): + if module_name in sys.modules: + mod = sys.modules[module_name] + else: + mod = __import__(module_name) + if dotted_path is None: + result = mod + else: + parts = dotted_path.split('.') + result = getattr(mod, parts.pop(0)) + for p in parts: + result = getattr(result, p) + return result + + +class ExportEntry(object): + + def __init__(self, name, prefix, suffix, flags): + self.name = name + self.prefix = prefix + self.suffix = suffix + self.flags = flags + + @cached_property + def value(self): + return resolve(self.prefix, self.suffix) + + def __repr__(self): # pragma: no cover + return '' % (self.name, self.prefix, self.suffix, self.flags) + + def __eq__(self, other): + if not isinstance(other, ExportEntry): + result = False + else: + result = (self.name == other.name and self.prefix == other.prefix and self.suffix == other.suffix and + self.flags == other.flags) + return result + + __hash__ = object.__hash__ + + +ENTRY_RE = re.compile( + r'''(?P([^\[]\S*)) + \s*=\s*(?P(\w+)([:\.]\w+)*) + \s*(\[\s*(?P[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? + ''', re.VERBOSE) + + +def get_export_entry(specification): + m = ENTRY_RE.search(specification) + if not m: + result = None + if '[' in specification or ']' in specification: + raise DistlibException("Invalid specification " + "'%s'" % specification) + else: + d = m.groupdict() + name = d['name'] + path = d['callable'] + colons = path.count(':') + if colons == 0: + prefix, suffix = path, None + else: + if colons != 1: + raise DistlibException("Invalid specification " + "'%s'" % specification) + prefix, suffix = path.split(':') + flags = d['flags'] + if flags is None: + if '[' in specification or ']' in specification: + raise DistlibException("Invalid specification " + "'%s'" % specification) + flags = [] + else: + flags = [f.strip() for f in flags.split(',')] + result = ExportEntry(name, prefix, suffix, flags) + return result + + +def get_cache_base(suffix=None): + """ + Return the default base location for distlib caches. If the directory does + not exist, it is created. Use the suffix provided for the base directory, + and default to '.distlib' if it isn't provided. + + On Windows, if LOCALAPPDATA is defined in the environment, then it is + assumed to be a directory, and will be the parent directory of the result. + On POSIX, and on Windows if LOCALAPPDATA is not defined, the user's home + directory - using os.expanduser('~') - will be the parent directory of + the result. + + The result is just the directory '.distlib' in the parent directory as + determined above, or with the name specified with ``suffix``. + """ + if suffix is None: + suffix = '.distlib' + if os.name == 'nt' and 'LOCALAPPDATA' in os.environ: + result = os.path.expandvars('$localappdata') + else: + # Assume posix, or old Windows + result = os.path.expanduser('~') + # we use 'isdir' instead of 'exists', because we want to + # fail if there's a file with that name + if os.path.isdir(result): + usable = os.access(result, os.W_OK) + if not usable: + logger.warning('Directory exists but is not writable: %s', result) + else: + try: + os.makedirs(result) + usable = True + except OSError: + logger.warning('Unable to create %s', result, exc_info=True) + usable = False + if not usable: + result = tempfile.mkdtemp() + logger.warning('Default location unusable, using %s', result) + return os.path.join(result, suffix) + + +def path_to_cache_dir(path, use_abspath=True): + """ + Convert an absolute path to a directory name for use in a cache. + + The algorithm used is: + + #. On Windows, any ``':'`` in the drive is replaced with ``'---'``. + #. Any occurrence of ``os.sep`` is replaced with ``'--'``. + #. ``'.cache'`` is appended. + """ + d, p = os.path.splitdrive(os.path.abspath(path) if use_abspath else path) + if d: + d = d.replace(':', '---') + p = p.replace(os.sep, '--') + return d + p + '.cache' + + +def ensure_slash(s): + if not s.endswith('/'): + return s + '/' + return s + + +def parse_credentials(netloc): + username = password = None + if '@' in netloc: + prefix, netloc = netloc.rsplit('@', 1) + if ':' not in prefix: + username = prefix + else: + username, password = prefix.split(':', 1) + if username: + username = unquote(username) + if password: + password = unquote(password) + return username, password, netloc + + +def get_process_umask(): + result = os.umask(0o22) + os.umask(result) + return result + + +def is_string_sequence(seq): + result = True + i = None + for i, s in enumerate(seq): + if not isinstance(s, string_types): + result = False + break + assert i is not None + return result + + +PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-' + '([a-z0-9_.+-]+)', re.I) +PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)') + + +def split_filename(filename, project_name=None): + """ + Extract name, version, python version from a filename (no extension) + + Return name, version, pyver or None + """ + result = None + pyver = None + filename = unquote(filename).replace(' ', '-') + m = PYTHON_VERSION.search(filename) + if m: + pyver = m.group(1) + filename = filename[:m.start()] + if project_name and len(filename) > len(project_name) + 1: + m = re.match(re.escape(project_name) + r'\b', filename) + if m: + n = m.end() + result = filename[:n], filename[n + 1:], pyver + if result is None: + m = PROJECT_NAME_AND_VERSION.match(filename) + if m: + result = m.group(1), m.group(3), pyver + return result + + +# Allow spaces in name because of legacy dists like "Twisted Core" +NAME_VERSION_RE = re.compile(r'(?P[\w .-]+)\s*' + r'\(\s*(?P[^\s)]+)\)$') + + +def parse_name_and_version(p): + """ + A utility method used to get name and version from a string. + + From e.g. a Provides-Dist value. + + :param p: A value in a form 'foo (1.0)' + :return: The name and version as a tuple. + """ + m = NAME_VERSION_RE.match(p) + if not m: + raise DistlibException('Ill-formed name/version string: \'%s\'' % p) + d = m.groupdict() + return d['name'].strip().lower(), d['ver'] + + +def get_extras(requested, available): + result = set() + requested = set(requested or []) + available = set(available or []) + if '*' in requested: + requested.remove('*') + result |= available + for r in requested: + if r == '-': + result.add(r) + elif r.startswith('-'): + unwanted = r[1:] + if unwanted not in available: + logger.warning('undeclared extra: %s' % unwanted) + if unwanted in result: + result.remove(unwanted) + else: + if r not in available: + logger.warning('undeclared extra: %s' % r) + result.add(r) + return result + + +# +# Extended metadata functionality +# + + +def _get_external_data(url): + result = {} + try: + # urlopen might fail if it runs into redirections, + # because of Python issue #13696. Fixed in locators + # using a custom redirect handler. + resp = urlopen(url) + headers = resp.info() + ct = headers.get('Content-Type') + if not ct.startswith('application/json'): + logger.debug('Unexpected response for JSON request: %s', ct) + else: + reader = codecs.getreader('utf-8')(resp) + # data = reader.read().decode('utf-8') + # result = json.loads(data) + result = json.load(reader) + except Exception as e: + logger.exception('Failed to get external data for %s: %s', url, e) + return result + + +_external_data_base_url = 'https://www.red-dove.com/pypi/projects/' + + +def get_project_data(name): + url = '%s/%s/project.json' % (name[0].upper(), name) + url = urljoin(_external_data_base_url, url) + result = _get_external_data(url) + return result + + +def get_package_data(name, version): + url = '%s/%s/package-%s.json' % (name[0].upper(), name, version) + url = urljoin(_external_data_base_url, url) + return _get_external_data(url) + + +class Cache(object): + """ + A class implementing a cache for resources that need to live in the file system + e.g. shared libraries. This class was moved from resources to here because it + could be used by other modules, e.g. the wheel module. + """ + + def __init__(self, base): + """ + Initialise an instance. + + :param base: The base directory where the cache should be located. + """ + # we use 'isdir' instead of 'exists', because we want to + # fail if there's a file with that name + if not os.path.isdir(base): # pragma: no cover + os.makedirs(base) + if (os.stat(base).st_mode & 0o77) != 0: + logger.warning('Directory \'%s\' is not private', base) + self.base = os.path.abspath(os.path.normpath(base)) + + def prefix_to_dir(self, prefix, use_abspath=True): + """ + Converts a resource prefix to a directory name in the cache. + """ + return path_to_cache_dir(prefix, use_abspath=use_abspath) + + def clear(self): + """ + Clear the cache. + """ + not_removed = [] + for fn in os.listdir(self.base): + fn = os.path.join(self.base, fn) + try: + if os.path.islink(fn) or os.path.isfile(fn): + os.remove(fn) + elif os.path.isdir(fn): + shutil.rmtree(fn) + except Exception: + not_removed.append(fn) + return not_removed + + +class EventMixin(object): + """ + A very simple publish/subscribe system. + """ + + def __init__(self): + self._subscribers = {} + + def add(self, event, subscriber, append=True): + """ + Add a subscriber for an event. + + :param event: The name of an event. + :param subscriber: The subscriber to be added (and called when the + event is published). + :param append: Whether to append or prepend the subscriber to an + existing subscriber list for the event. + """ + subs = self._subscribers + if event not in subs: + subs[event] = deque([subscriber]) + else: + sq = subs[event] + if append: + sq.append(subscriber) + else: + sq.appendleft(subscriber) + + def remove(self, event, subscriber): + """ + Remove a subscriber for an event. + + :param event: The name of an event. + :param subscriber: The subscriber to be removed. + """ + subs = self._subscribers + if event not in subs: + raise ValueError('No subscribers: %r' % event) + subs[event].remove(subscriber) + + def get_subscribers(self, event): + """ + Return an iterator for the subscribers for an event. + :param event: The event to return subscribers for. + """ + return iter(self._subscribers.get(event, ())) + + def publish(self, event, *args, **kwargs): + """ + Publish a event and return a list of values returned by its + subscribers. + + :param event: The event to publish. + :param args: The positional arguments to pass to the event's + subscribers. + :param kwargs: The keyword arguments to pass to the event's + subscribers. + """ + result = [] + for subscriber in self.get_subscribers(event): + try: + value = subscriber(event, *args, **kwargs) + except Exception: + logger.exception('Exception during event publication') + value = None + result.append(value) + logger.debug('publish %s: args = %s, kwargs = %s, result = %s', event, args, kwargs, result) + return result + + +# +# Simple sequencing +# +class Sequencer(object): + + def __init__(self): + self._preds = {} + self._succs = {} + self._nodes = set() # nodes with no preds/succs + + def add_node(self, node): + self._nodes.add(node) + + def remove_node(self, node, edges=False): + if node in self._nodes: + self._nodes.remove(node) + if edges: + for p in set(self._preds.get(node, ())): + self.remove(p, node) + for s in set(self._succs.get(node, ())): + self.remove(node, s) + # Remove empties + for k, v in list(self._preds.items()): + if not v: + del self._preds[k] + for k, v in list(self._succs.items()): + if not v: + del self._succs[k] + + def add(self, pred, succ): + assert pred != succ + self._preds.setdefault(succ, set()).add(pred) + self._succs.setdefault(pred, set()).add(succ) + + def remove(self, pred, succ): + assert pred != succ + try: + preds = self._preds[succ] + succs = self._succs[pred] + except KeyError: # pragma: no cover + raise ValueError('%r not a successor of anything' % succ) + try: + preds.remove(pred) + succs.remove(succ) + except KeyError: # pragma: no cover + raise ValueError('%r not a successor of %r' % (succ, pred)) + + def is_step(self, step): + return (step in self._preds or step in self._succs or step in self._nodes) + + def get_steps(self, final): + if not self.is_step(final): + raise ValueError('Unknown: %r' % final) + result = [] + todo = [] + seen = set() + todo.append(final) + while todo: + step = todo.pop(0) + if step in seen: + # if a step was already seen, + # move it to the end (so it will appear earlier + # when reversed on return) ... but not for the + # final step, as that would be confusing for + # users + if step != final: + result.remove(step) + result.append(step) + else: + seen.add(step) + result.append(step) + preds = self._preds.get(step, ()) + todo.extend(preds) + return reversed(result) + + @property + def strong_connections(self): + # http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm + index_counter = [0] + stack = [] + lowlinks = {} + index = {} + result = [] + + graph = self._succs + + def strongconnect(node): + # set the depth index for this node to the smallest unused index + index[node] = index_counter[0] + lowlinks[node] = index_counter[0] + index_counter[0] += 1 + stack.append(node) + + # Consider successors + try: + successors = graph[node] + except Exception: + successors = [] + for successor in successors: + if successor not in lowlinks: + # Successor has not yet been visited + strongconnect(successor) + lowlinks[node] = min(lowlinks[node], lowlinks[successor]) + elif successor in stack: + # the successor is in the stack and hence in the current + # strongly connected component (SCC) + lowlinks[node] = min(lowlinks[node], index[successor]) + + # If `node` is a root node, pop the stack and generate an SCC + if lowlinks[node] == index[node]: + connected_component = [] + + while True: + successor = stack.pop() + connected_component.append(successor) + if successor == node: + break + component = tuple(connected_component) + # storing the result + result.append(component) + + for node in graph: + if node not in lowlinks: + strongconnect(node) + + return result + + @property + def dot(self): + result = ['digraph G {'] + for succ in self._preds: + preds = self._preds[succ] + for pred in preds: + result.append(' %s -> %s;' % (pred, succ)) + for node in self._nodes: + result.append(' %s;' % node) + result.append('}') + return '\n'.join(result) + + +# +# Unarchiving functionality for zip, tar, tgz, tbz, whl +# + +ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz', '.whl') + + +def unarchive(archive_filename, dest_dir, format=None, check=True): + + def check_path(path): + if not isinstance(path, text_type): + path = path.decode('utf-8') + p = os.path.abspath(os.path.join(dest_dir, path)) + if not p.startswith(dest_dir) or p[plen] != os.sep: + raise ValueError('path outside destination: %r' % p) + + dest_dir = os.path.abspath(dest_dir) + plen = len(dest_dir) + archive = None + if format is None: + if archive_filename.endswith(('.zip', '.whl')): + format = 'zip' + elif archive_filename.endswith(('.tar.gz', '.tgz')): + format = 'tgz' + mode = 'r:gz' + elif archive_filename.endswith(('.tar.bz2', '.tbz')): + format = 'tbz' + mode = 'r:bz2' + elif archive_filename.endswith('.tar'): + format = 'tar' + mode = 'r' + else: # pragma: no cover + raise ValueError('Unknown format for %r' % archive_filename) + try: + if format == 'zip': + archive = ZipFile(archive_filename, 'r') + if check: + names = archive.namelist() + for name in names: + check_path(name) + else: + archive = tarfile.open(archive_filename, mode) + if check: + names = archive.getnames() + for name in names: + check_path(name) + if format != 'zip' and sys.version_info[0] < 3: + # See Python issue 17153. If the dest path contains Unicode, + # tarfile extraction fails on Python 2.x if a member path name + # contains non-ASCII characters - it leads to an implicit + # bytes -> unicode conversion using ASCII to decode. + for tarinfo in archive.getmembers(): + if not isinstance(tarinfo.name, text_type): + tarinfo.name = tarinfo.name.decode('utf-8') + + # Limit extraction of dangerous items, if this Python + # allows it easily. If not, just trust the input. + # See: https://docs.python.org/3/library/tarfile.html#extraction-filters + def extraction_filter(member, path): + """Run tarfile.tar_filter, but raise the expected ValueError""" + # This is only called if the current Python has tarfile filters + try: + return tarfile.tar_filter(member, path) + except tarfile.FilterError as exc: + raise ValueError(str(exc)) + + archive.extraction_filter = extraction_filter + + archive.extractall(dest_dir) + + finally: + if archive: + archive.close() + + +def zip_dir(directory): + """zip a directory tree into a BytesIO object""" + result = io.BytesIO() + dlen = len(directory) + with ZipFile(result, "w") as zf: + for root, dirs, files in os.walk(directory): + for name in files: + full = os.path.join(root, name) + rel = root[dlen:] + dest = os.path.join(rel, name) + zf.write(full, dest) + return result + + +# +# Simple progress bar +# + +UNITS = ('', 'K', 'M', 'G', 'T', 'P') + + +class Progress(object): + unknown = 'UNKNOWN' + + def __init__(self, minval=0, maxval=100): + assert maxval is None or maxval >= minval + self.min = self.cur = minval + self.max = maxval + self.started = None + self.elapsed = 0 + self.done = False + + def update(self, curval): + assert self.min <= curval + assert self.max is None or curval <= self.max + self.cur = curval + now = time.time() + if self.started is None: + self.started = now + else: + self.elapsed = now - self.started + + def increment(self, incr): + assert incr >= 0 + self.update(self.cur + incr) + + def start(self): + self.update(self.min) + return self + + def stop(self): + if self.max is not None: + self.update(self.max) + self.done = True + + @property + def maximum(self): + return self.unknown if self.max is None else self.max + + @property + def percentage(self): + if self.done: + result = '100 %' + elif self.max is None: + result = ' ?? %' + else: + v = 100.0 * (self.cur - self.min) / (self.max - self.min) + result = '%3d %%' % v + return result + + def format_duration(self, duration): + if (duration <= 0) and self.max is None or self.cur == self.min: + result = '??:??:??' + # elif duration < 1: + # result = '--:--:--' + else: + result = time.strftime('%H:%M:%S', time.gmtime(duration)) + return result + + @property + def ETA(self): + if self.done: + prefix = 'Done' + t = self.elapsed + # import pdb; pdb.set_trace() + else: + prefix = 'ETA ' + if self.max is None: + t = -1 + elif self.elapsed == 0 or (self.cur == self.min): + t = 0 + else: + # import pdb; pdb.set_trace() + t = float(self.max - self.min) + t /= self.cur - self.min + t = (t - 1) * self.elapsed + return '%s: %s' % (prefix, self.format_duration(t)) + + @property + def speed(self): + if self.elapsed == 0: + result = 0.0 + else: + result = (self.cur - self.min) / self.elapsed + for unit in UNITS: + if result < 1000: + break + result /= 1000.0 + return '%d %sB/s' % (result, unit) + + +# +# Glob functionality +# + +RICH_GLOB = re.compile(r'\{([^}]*)\}') +_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]') +_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$') + + +def iglob(path_glob): + """Extended globbing function that supports ** and {opt1,opt2,opt3}.""" + if _CHECK_RECURSIVE_GLOB.search(path_glob): + msg = """invalid glob %r: recursive glob "**" must be used alone""" + raise ValueError(msg % path_glob) + if _CHECK_MISMATCH_SET.search(path_glob): + msg = """invalid glob %r: mismatching set marker '{' or '}'""" + raise ValueError(msg % path_glob) + return _iglob(path_glob) + + +def _iglob(path_glob): + rich_path_glob = RICH_GLOB.split(path_glob, 1) + if len(rich_path_glob) > 1: + assert len(rich_path_glob) == 3, rich_path_glob + prefix, set, suffix = rich_path_glob + for item in set.split(','): + for path in _iglob(''.join((prefix, item, suffix))): + yield path + else: + if '**' not in path_glob: + for item in std_iglob(path_glob): + yield item + else: + prefix, radical = path_glob.split('**', 1) + if prefix == '': + prefix = '.' + if radical == '': + radical = '*' + else: + # we support both + radical = radical.lstrip('/') + radical = radical.lstrip('\\') + for path, dir, files in os.walk(prefix): + path = os.path.normpath(path) + for fn in _iglob(os.path.join(path, radical)): + yield fn + + +if ssl: + from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname, CertificateError) + + # + # HTTPSConnection which verifies certificates/matches domains + # + + class HTTPSConnection(httplib.HTTPSConnection): + ca_certs = None # set this to the path to the certs file (.pem) + check_domain = True # only used if ca_certs is not None + + # noinspection PyPropertyAccess + def connect(self): + sock = socket.create_connection((self.host, self.port), self.timeout) + if getattr(self, '_tunnel_host', False): + self.sock = sock + self._tunnel() + + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + if hasattr(ssl, 'OP_NO_SSLv2'): + context.options |= ssl.OP_NO_SSLv2 + if getattr(self, 'cert_file', None): + context.load_cert_chain(self.cert_file, self.key_file) + kwargs = {} + if self.ca_certs: + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations(cafile=self.ca_certs) + if getattr(ssl, 'HAS_SNI', False): + kwargs['server_hostname'] = self.host + + self.sock = context.wrap_socket(sock, **kwargs) + if self.ca_certs and self.check_domain: + try: + match_hostname(self.sock.getpeercert(), self.host) + logger.debug('Host verified: %s', self.host) + except CertificateError: # pragma: no cover + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + + class HTTPSHandler(BaseHTTPSHandler): + + def __init__(self, ca_certs, check_domain=True): + BaseHTTPSHandler.__init__(self) + self.ca_certs = ca_certs + self.check_domain = check_domain + + def _conn_maker(self, *args, **kwargs): + """ + This is called to create a connection instance. Normally you'd + pass a connection class to do_open, but it doesn't actually check for + a class, and just expects a callable. As long as we behave just as a + constructor would have, we should be OK. If it ever changes so that + we *must* pass a class, we'll create an UnsafeHTTPSConnection class + which just sets check_domain to False in the class definition, and + choose which one to pass to do_open. + """ + result = HTTPSConnection(*args, **kwargs) + if self.ca_certs: + result.ca_certs = self.ca_certs + result.check_domain = self.check_domain + return result + + def https_open(self, req): + try: + return self.do_open(self._conn_maker, req) + except URLError as e: + if 'certificate verify failed' in str(e.reason): + raise CertificateError('Unable to verify server certificate ' + 'for %s' % req.host) + else: + raise + + # + # To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The- + # Middle proxy using HTTP listens on port 443, or an index mistakenly serves + # HTML containing a http://xyz link when it should be https://xyz), + # you can use the following handler class, which does not allow HTTP traffic. + # + # It works by inheriting from HTTPHandler - so build_opener won't add a + # handler for HTTP itself. + # + class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler): + + def http_open(self, req): + raise URLError('Unexpected HTTP request on what should be a secure ' + 'connection: %s' % req) + + +# +# XML-RPC with timeouts +# +class Transport(xmlrpclib.Transport): + + def __init__(self, timeout, use_datetime=0): + self.timeout = timeout + xmlrpclib.Transport.__init__(self, use_datetime) + + def make_connection(self, host): + h, eh, x509 = self.get_host_info(host) + if not self._connection or host != self._connection[0]: + self._extra_headers = eh + self._connection = host, httplib.HTTPConnection(h) + return self._connection[1] + + +if ssl: + + class SafeTransport(xmlrpclib.SafeTransport): + + def __init__(self, timeout, use_datetime=0): + self.timeout = timeout + xmlrpclib.SafeTransport.__init__(self, use_datetime) + + def make_connection(self, host): + h, eh, kwargs = self.get_host_info(host) + if not kwargs: + kwargs = {} + kwargs['timeout'] = self.timeout + if not self._connection or host != self._connection[0]: + self._extra_headers = eh + self._connection = host, httplib.HTTPSConnection(h, None, **kwargs) + return self._connection[1] + + +class ServerProxy(xmlrpclib.ServerProxy): + + def __init__(self, uri, **kwargs): + self.timeout = timeout = kwargs.pop('timeout', None) + # The above classes only come into play if a timeout + # is specified + if timeout is not None: + # scheme = splittype(uri) # deprecated as of Python 3.8 + scheme = urlparse(uri)[0] + use_datetime = kwargs.get('use_datetime', 0) + if scheme == 'https': + tcls = SafeTransport + else: + tcls = Transport + kwargs['transport'] = t = tcls(timeout, use_datetime=use_datetime) + self.transport = t + xmlrpclib.ServerProxy.__init__(self, uri, **kwargs) + + +# +# CSV functionality. This is provided because on 2.x, the csv module can't +# handle Unicode. However, we need to deal with Unicode in e.g. RECORD files. +# + + +def _csv_open(fn, mode, **kwargs): + if sys.version_info[0] < 3: + mode += 'b' + else: + kwargs['newline'] = '' + # Python 3 determines encoding from locale. Force 'utf-8' + # file encoding to match other forced utf-8 encoding + kwargs['encoding'] = 'utf-8' + return open(fn, mode, **kwargs) + + +class CSVBase(object): + defaults = { + 'delimiter': str(','), # The strs are used because we need native + 'quotechar': str('"'), # str in the csv API (2.x won't take + 'lineterminator': str('\n') # Unicode) + } + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.stream.close() + + +class CSVReader(CSVBase): + + def __init__(self, **kwargs): + if 'stream' in kwargs: + stream = kwargs['stream'] + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getreader('utf-8')(stream) + self.stream = stream + else: + self.stream = _csv_open(kwargs['path'], 'r') + self.reader = csv.reader(self.stream, **self.defaults) + + def __iter__(self): + return self + + def next(self): + result = next(self.reader) + if sys.version_info[0] < 3: + for i, item in enumerate(result): + if not isinstance(item, text_type): + result[i] = item.decode('utf-8') + return result + + __next__ = next + + +class CSVWriter(CSVBase): + + def __init__(self, fn, **kwargs): + self.stream = _csv_open(fn, 'w') + self.writer = csv.writer(self.stream, **self.defaults) + + def writerow(self, row): + if sys.version_info[0] < 3: + r = [] + for item in row: + if isinstance(item, text_type): + item = item.encode('utf-8') + r.append(item) + row = r + self.writer.writerow(row) + + +# +# Configurator functionality +# + + +class Configurator(BaseConfigurator): + + value_converters = dict(BaseConfigurator.value_converters) + value_converters['inc'] = 'inc_convert' + + def __init__(self, config, base=None): + super(Configurator, self).__init__(config) + self.base = base or os.getcwd() + + def configure_custom(self, config): + + def convert(o): + if isinstance(o, (list, tuple)): + result = type(o)([convert(i) for i in o]) + elif isinstance(o, dict): + if '()' in o: + result = self.configure_custom(o) + else: + result = {} + for k in o: + result[k] = convert(o[k]) + else: + result = self.convert(o) + return result + + c = config.pop('()') + if not callable(c): + c = self.resolve(c) + props = config.pop('.', None) + # Check for valid identifiers + args = config.pop('[]', ()) + if args: + args = tuple([convert(o) for o in args]) + items = [(k, convert(config[k])) for k in config if valid_ident(k)] + kwargs = dict(items) + result = c(*args, **kwargs) + if props: + for n, v in props.items(): + setattr(result, n, convert(v)) + return result + + def __getitem__(self, key): + result = self.config[key] + if isinstance(result, dict) and '()' in result: + self.config[key] = result = self.configure_custom(result) + return result + + def inc_convert(self, value): + """Default converter for the inc:// protocol.""" + if not os.path.isabs(value): + value = os.path.join(self.base, value) + with codecs.open(value, 'r', encoding='utf-8') as f: + result = json.load(f) + return result + + +class SubprocessMixin(object): + """ + Mixin for running subprocesses and capturing their output + """ + + def __init__(self, verbose=False, progress=None): + self.verbose = verbose + self.progress = progress + + def reader(self, stream, context): + """ + Read lines from a subprocess' output stream and either pass to a progress + callable (if specified) or write progress information to sys.stderr. + """ + progress = self.progress + verbose = self.verbose + while True: + s = stream.readline() + if not s: + break + if progress is not None: + progress(s, context) + else: + if not verbose: + sys.stderr.write('.') + else: + sys.stderr.write(s.decode('utf-8')) + sys.stderr.flush() + stream.close() + + def run_command(self, cmd, **kwargs): + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) + t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout')) + t1.start() + t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr')) + t2.start() + p.wait() + t1.join() + t2.join() + if self.progress is not None: + self.progress('done.', 'main') + elif self.verbose: + sys.stderr.write('done.\n') + return p + + +def normalize_name(name): + """Normalize a python package name a la PEP 503""" + # https://www.python.org/dev/peps/pep-0503/#normalized-names + return re.sub('[-_.]+', '-', name).lower() + + +# def _get_pypirc_command(): +# """ +# Get the distutils command for interacting with PyPI configurations. +# :return: the command. +# """ +# from distutils.core import Distribution +# from distutils.config import PyPIRCCommand +# d = Distribution() +# return PyPIRCCommand(d) + + +class PyPIRCFile(object): + + DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/' + DEFAULT_REALM = 'pypi' + + def __init__(self, fn=None, url=None): + if fn is None: + fn = os.path.join(os.path.expanduser('~'), '.pypirc') + self.filename = fn + self.url = url + + def read(self): + result = {} + + if os.path.exists(self.filename): + repository = self.url or self.DEFAULT_REPOSITORY + + config = configparser.RawConfigParser() + config.read(self.filename) + sections = config.sections() + if 'distutils' in sections: + # let's get the list of servers + index_servers = config.get('distutils', 'index-servers') + _servers = [server.strip() for server in index_servers.split('\n') if server.strip() != ''] + if _servers == []: + # nothing set, let's try to get the default pypi + if 'pypi' in sections: + _servers = ['pypi'] + else: + for server in _servers: + result = {'server': server} + result['username'] = config.get(server, 'username') + + # optional params + for key, default in (('repository', self.DEFAULT_REPOSITORY), ('realm', self.DEFAULT_REALM), + ('password', None)): + if config.has_option(server, key): + result[key] = config.get(server, key) + else: + result[key] = default + + # work around people having "repository" for the "pypi" + # section of their config set to the HTTP (rather than + # HTTPS) URL + if (server == 'pypi' and repository in (self.DEFAULT_REPOSITORY, 'pypi')): + result['repository'] = self.DEFAULT_REPOSITORY + elif (result['server'] != repository and result['repository'] != repository): + result = {} + elif 'server-login' in sections: + # old format + server = 'server-login' + if config.has_option(server, 'repository'): + repository = config.get(server, 'repository') + else: + repository = self.DEFAULT_REPOSITORY + result = { + 'username': config.get(server, 'username'), + 'password': config.get(server, 'password'), + 'repository': repository, + 'server': server, + 'realm': self.DEFAULT_REALM + } + return result + + def update(self, username, password): + # import pdb; pdb.set_trace() + config = configparser.RawConfigParser() + fn = self.filename + config.read(fn) + if not config.has_section('pypi'): + config.add_section('pypi') + config.set('pypi', 'username', username) + config.set('pypi', 'password', password) + with open(fn, 'w') as f: + config.write(f) + + +def _load_pypirc(index): + """ + Read the PyPI access configuration as supported by distutils. + """ + return PyPIRCFile(url=index.url).read() + + +def _store_pypirc(index): + PyPIRCFile().update(index.username, index.password) + + +# +# get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor +# tweaks +# + + +def get_host_platform(): + """Return a string that identifies the current platform. This is used mainly to + distinguish platform-specific build directories and platform-specific built + distributions. Typically includes the OS name and version and the + architecture (as supplied by 'os.uname()'), although the exact information + included depends on the OS; eg. on Linux, the kernel version isn't + particularly important. + + Examples of returned values: + linux-i586 + linux-alpha (?) + solaris-2.6-sun4u + + Windows will return one of: + win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + win32 (all others - specifically, sys.platform is returned) + + For other non-POSIX platforms, currently just returns 'sys.platform'. + + """ + if os.name == 'nt': + if 'amd64' in sys.version.lower(): + return 'win-amd64' + if '(arm)' in sys.version.lower(): + return 'win-arm32' + if '(arm64)' in sys.version.lower(): + return 'win-arm64' + return sys.platform + + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + + if os.name != 'posix' or not hasattr(os, 'uname'): + # XXX what about the architecture? NT is Intel or Alpha, + # Mac OS is M68k or PPC, etc. + return sys.platform + + # Try to distinguish various flavours of Unix + + (osname, host, release, version, machine) = os.uname() + + # Convert the OS name to lowercase, remove '/' characters, and translate + # spaces (for "Power Macintosh") + osname = osname.lower().replace('/', '') + machine = machine.replace(' ', '_').replace('/', '-') + + if osname[:5] == 'linux': + # At least on Linux/Intel, 'machine' is the processor -- + # i386, etc. + # XXX what about Alpha, SPARC, etc? + return "%s-%s" % (osname, machine) + + elif osname[:5] == 'sunos': + if release[0] >= '5': # SunOS 5 == Solaris 2 + osname = 'solaris' + release = '%d.%s' % (int(release[0]) - 3, release[2:]) + # We can't use 'platform.architecture()[0]' because a + # bootstrap problem. We use a dict to get an error + # if some suspicious happens. + bitness = {2147483647: '32bit', 9223372036854775807: '64bit'} + machine += '.%s' % bitness[sys.maxsize] + # fall through to standard osname-release-machine representation + elif osname[:3] == 'aix': + from _aix_support import aix_platform + return aix_platform() + elif osname[:6] == 'cygwin': + osname = 'cygwin' + rel_re = re.compile(r'[\d.]+', re.ASCII) + m = rel_re.match(release) + if m: + release = m.group() + elif osname[:6] == 'darwin': + import _osx_support + try: + from distutils import sysconfig + except ImportError: + import sysconfig + osname, release, machine = _osx_support.get_platform_osx(sysconfig.get_config_vars(), osname, release, machine) + + return '%s-%s-%s' % (osname, release, machine) + + +_TARGET_TO_PLAT = { + 'x86': 'win32', + 'x64': 'win-amd64', + 'arm': 'win-arm32', +} + + +def get_platform(): + if os.name != 'nt': + return get_host_platform() + cross_compilation_target = os.environ.get('VSCMD_ARG_TGT_ARCH') + if cross_compilation_target not in _TARGET_TO_PLAT: + return get_host_platform() + return _TARGET_TO_PLAT[cross_compilation_target] diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/version.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/version.py new file mode 100644 index 000000000..d70a96ef5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/version.py @@ -0,0 +1,750 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2023 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +""" +Implementation of a flexible versioning scheme providing support for PEP-440, +setuptools-compatible and semantic versioning. +""" + +import logging +import re + +from .compat import string_types +from .util import parse_requirement + +__all__ = ['NormalizedVersion', 'NormalizedMatcher', + 'LegacyVersion', 'LegacyMatcher', + 'SemanticVersion', 'SemanticMatcher', + 'UnsupportedVersionError', 'get_scheme'] + +logger = logging.getLogger(__name__) + + +class UnsupportedVersionError(ValueError): + """This is an unsupported version.""" + pass + + +class Version(object): + def __init__(self, s): + self._string = s = s.strip() + self._parts = parts = self.parse(s) + assert isinstance(parts, tuple) + assert len(parts) > 0 + + def parse(self, s): + raise NotImplementedError('please implement in a subclass') + + def _check_compatible(self, other): + if type(self) != type(other): + raise TypeError('cannot compare %r and %r' % (self, other)) + + def __eq__(self, other): + self._check_compatible(other) + return self._parts == other._parts + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + self._check_compatible(other) + return self._parts < other._parts + + def __gt__(self, other): + return not (self.__lt__(other) or self.__eq__(other)) + + def __le__(self, other): + return self.__lt__(other) or self.__eq__(other) + + def __ge__(self, other): + return self.__gt__(other) or self.__eq__(other) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + def __hash__(self): + return hash(self._parts) + + def __repr__(self): + return "%s('%s')" % (self.__class__.__name__, self._string) + + def __str__(self): + return self._string + + @property + def is_prerelease(self): + raise NotImplementedError('Please implement in subclasses.') + + +class Matcher(object): + version_class = None + + # value is either a callable or the name of a method + _operators = { + '<': lambda v, c, p: v < c, + '>': lambda v, c, p: v > c, + '<=': lambda v, c, p: v == c or v < c, + '>=': lambda v, c, p: v == c or v > c, + '==': lambda v, c, p: v == c, + '===': lambda v, c, p: v == c, + # by default, compatible => >=. + '~=': lambda v, c, p: v == c or v > c, + '!=': lambda v, c, p: v != c, + } + + # this is a method only to support alternative implementations + # via overriding + def parse_requirement(self, s): + return parse_requirement(s) + + def __init__(self, s): + if self.version_class is None: + raise ValueError('Please specify a version class') + self._string = s = s.strip() + r = self.parse_requirement(s) + if not r: + raise ValueError('Not valid: %r' % s) + self.name = r.name + self.key = self.name.lower() # for case-insensitive comparisons + clist = [] + if r.constraints: + # import pdb; pdb.set_trace() + for op, s in r.constraints: + if s.endswith('.*'): + if op not in ('==', '!='): + raise ValueError('\'.*\' not allowed for ' + '%r constraints' % op) + # Could be a partial version (e.g. for '2.*') which + # won't parse as a version, so keep it as a string + vn, prefix = s[:-2], True + # Just to check that vn is a valid version + self.version_class(vn) + else: + # Should parse as a version, so we can create an + # instance for the comparison + vn, prefix = self.version_class(s), False + clist.append((op, vn, prefix)) + self._parts = tuple(clist) + + def match(self, version): + """ + Check if the provided version matches the constraints. + + :param version: The version to match against this instance. + :type version: String or :class:`Version` instance. + """ + if isinstance(version, string_types): + version = self.version_class(version) + for operator, constraint, prefix in self._parts: + f = self._operators.get(operator) + if isinstance(f, string_types): + f = getattr(self, f) + if not f: + msg = ('%r not implemented ' + 'for %s' % (operator, self.__class__.__name__)) + raise NotImplementedError(msg) + if not f(version, constraint, prefix): + return False + return True + + @property + def exact_version(self): + result = None + if len(self._parts) == 1 and self._parts[0][0] in ('==', '==='): + result = self._parts[0][1] + return result + + def _check_compatible(self, other): + if type(self) != type(other) or self.name != other.name: + raise TypeError('cannot compare %s and %s' % (self, other)) + + def __eq__(self, other): + self._check_compatible(other) + return self.key == other.key and self._parts == other._parts + + def __ne__(self, other): + return not self.__eq__(other) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + def __hash__(self): + return hash(self.key) + hash(self._parts) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self._string) + + def __str__(self): + return self._string + + +PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|alpha|b|beta|c|rc|pre|preview)(\d+)?)?' + r'(\.(post|r|rev)(\d+)?)?([._-]?(dev)(\d+)?)?' + r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$', re.I) + + +def _pep_440_key(s): + s = s.strip() + m = PEP440_VERSION_RE.match(s) + if not m: + raise UnsupportedVersionError('Not a valid version: %s' % s) + groups = m.groups() + nums = tuple(int(v) for v in groups[1].split('.')) + while len(nums) > 1 and nums[-1] == 0: + nums = nums[:-1] + + if not groups[0]: + epoch = 0 + else: + epoch = int(groups[0][:-1]) + pre = groups[4:6] + post = groups[7:9] + dev = groups[10:12] + local = groups[13] + if pre == (None, None): + pre = () + else: + if pre[1] is None: + pre = pre[0], 0 + else: + pre = pre[0], int(pre[1]) + if post == (None, None): + post = () + else: + if post[1] is None: + post = post[0], 0 + else: + post = post[0], int(post[1]) + if dev == (None, None): + dev = () + else: + if dev[1] is None: + dev = dev[0], 0 + else: + dev = dev[0], int(dev[1]) + if local is None: + local = () + else: + parts = [] + for part in local.split('.'): + # to ensure that numeric compares as > lexicographic, avoid + # comparing them directly, but encode a tuple which ensures + # correct sorting + if part.isdigit(): + part = (1, int(part)) + else: + part = (0, part) + parts.append(part) + local = tuple(parts) + if not pre: + # either before pre-release, or final release and after + if not post and dev: + # before pre-release + pre = ('a', -1) # to sort before a0 + else: + pre = ('z',) # to sort after all pre-releases + # now look at the state of post and dev. + if not post: + post = ('_',) # sort before 'a' + if not dev: + dev = ('final',) + + return epoch, nums, pre, post, dev, local + + +_normalized_key = _pep_440_key + + +class NormalizedVersion(Version): + """A rational version. + + Good: + 1.2 # equivalent to "1.2.0" + 1.2.0 + 1.2a1 + 1.2.3a2 + 1.2.3b1 + 1.2.3c1 + 1.2.3.4 + TODO: fill this out + + Bad: + 1 # minimum two numbers + 1.2a # release level must have a release serial + 1.2.3b + """ + def parse(self, s): + result = _normalized_key(s) + # _normalized_key loses trailing zeroes in the release + # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0 + # However, PEP 440 prefix matching needs it: for example, + # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0). + m = PEP440_VERSION_RE.match(s) # must succeed + groups = m.groups() + self._release_clause = tuple(int(v) for v in groups[1].split('.')) + return result + + PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev']) + + @property + def is_prerelease(self): + return any(t[0] in self.PREREL_TAGS for t in self._parts if t) + + +def _match_prefix(x, y): + x = str(x) + y = str(y) + if x == y: + return True + if not x.startswith(y): + return False + n = len(y) + return x[n] == '.' + + +class NormalizedMatcher(Matcher): + version_class = NormalizedVersion + + # value is either a callable or the name of a method + _operators = { + '~=': '_match_compatible', + '<': '_match_lt', + '>': '_match_gt', + '<=': '_match_le', + '>=': '_match_ge', + '==': '_match_eq', + '===': '_match_arbitrary', + '!=': '_match_ne', + } + + def _adjust_local(self, version, constraint, prefix): + if prefix: + strip_local = '+' not in constraint and version._parts[-1] + else: + # both constraint and version are + # NormalizedVersion instances. + # If constraint does not have a local component, + # ensure the version doesn't, either. + strip_local = not constraint._parts[-1] and version._parts[-1] + if strip_local: + s = version._string.split('+', 1)[0] + version = self.version_class(s) + return version, constraint + + def _match_lt(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version >= constraint: + return False + release_clause = constraint._release_clause + pfx = '.'.join([str(i) for i in release_clause]) + return not _match_prefix(version, pfx) + + def _match_gt(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version <= constraint: + return False + release_clause = constraint._release_clause + pfx = '.'.join([str(i) for i in release_clause]) + return not _match_prefix(version, pfx) + + def _match_le(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + return version <= constraint + + def _match_ge(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + return version >= constraint + + def _match_eq(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if not prefix: + result = (version == constraint) + else: + result = _match_prefix(version, constraint) + return result + + def _match_arbitrary(self, version, constraint, prefix): + return str(version) == str(constraint) + + def _match_ne(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if not prefix: + result = (version != constraint) + else: + result = not _match_prefix(version, constraint) + return result + + def _match_compatible(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version == constraint: + return True + if version < constraint: + return False +# if not prefix: +# return True + release_clause = constraint._release_clause + if len(release_clause) > 1: + release_clause = release_clause[:-1] + pfx = '.'.join([str(i) for i in release_clause]) + return _match_prefix(version, pfx) + + +_REPLACEMENTS = ( + (re.compile('[.+-]$'), ''), # remove trailing puncts + (re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start + (re.compile('^[.-]'), ''), # remove leading puncts + (re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses + (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion) + (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion) + (re.compile('[.]{2,}'), '.'), # multiple runs of '.' + (re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha + (re.compile(r'\b(pre-alpha|prealpha)\b'), + 'pre.alpha'), # standardise + (re.compile(r'\(beta\)$'), 'beta'), # remove parentheses +) + +_SUFFIX_REPLACEMENTS = ( + (re.compile('^[:~._+-]+'), ''), # remove leading puncts + (re.compile('[,*")([\\]]'), ''), # remove unwanted chars + (re.compile('[~:+_ -]'), '.'), # replace illegal chars + (re.compile('[.]{2,}'), '.'), # multiple runs of '.' + (re.compile(r'\.$'), ''), # trailing '.' +) + +_NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)') + + +def _suggest_semantic_version(s): + """ + Try to suggest a semantic form for a version for which + _suggest_normalized_version couldn't come up with anything. + """ + result = s.strip().lower() + for pat, repl in _REPLACEMENTS: + result = pat.sub(repl, result) + if not result: + result = '0.0.0' + + # Now look for numeric prefix, and separate it out from + # the rest. + # import pdb; pdb.set_trace() + m = _NUMERIC_PREFIX.match(result) + if not m: + prefix = '0.0.0' + suffix = result + else: + prefix = m.groups()[0].split('.') + prefix = [int(i) for i in prefix] + while len(prefix) < 3: + prefix.append(0) + if len(prefix) == 3: + suffix = result[m.end():] + else: + suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():] + prefix = prefix[:3] + prefix = '.'.join([str(i) for i in prefix]) + suffix = suffix.strip() + if suffix: + # import pdb; pdb.set_trace() + # massage the suffix. + for pat, repl in _SUFFIX_REPLACEMENTS: + suffix = pat.sub(repl, suffix) + + if not suffix: + result = prefix + else: + sep = '-' if 'dev' in suffix else '+' + result = prefix + sep + suffix + if not is_semver(result): + result = None + return result + + +def _suggest_normalized_version(s): + """Suggest a normalized version close to the given version string. + + If you have a version string that isn't rational (i.e. NormalizedVersion + doesn't like it) then you might be able to get an equivalent (or close) + rational version from this function. + + This does a number of simple normalizations to the given string, based + on observation of versions currently in use on PyPI. Given a dump of + those version during PyCon 2009, 4287 of them: + - 2312 (53.93%) match NormalizedVersion without change + with the automatic suggestion + - 3474 (81.04%) match when using this suggestion method + + @param s {str} An irrational version string. + @returns A rational version string, or None, if couldn't determine one. + """ + try: + _normalized_key(s) + return s # already rational + except UnsupportedVersionError: + pass + + rs = s.lower() + + # part of this could use maketrans + for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), + ('beta', 'b'), ('rc', 'c'), ('-final', ''), + ('-pre', 'c'), + ('-release', ''), ('.release', ''), ('-stable', ''), + ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), + ('final', '')): + rs = rs.replace(orig, repl) + + # if something ends with dev or pre, we add a 0 + rs = re.sub(r"pre$", r"pre0", rs) + rs = re.sub(r"dev$", r"dev0", rs) + + # if we have something like "b-2" or "a.2" at the end of the + # version, that is probably beta, alpha, etc + # let's remove the dash or dot + rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs) + + # 1.0-dev-r371 -> 1.0.dev371 + # 0.1-dev-r79 -> 0.1.dev79 + rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) + + # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 + rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) + + # Clean: v0.3, v1.0 + if rs.startswith('v'): + rs = rs[1:] + + # Clean leading '0's on numbers. + # TODO: unintended side-effect on, e.g., "2003.05.09" + # PyPI stats: 77 (~2%) better + rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) + + # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers + # zero. + # PyPI stats: 245 (7.56%) better + rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) + + # the 'dev-rNNN' tag is a dev tag + rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) + + # clean the - when used as a pre delimiter + rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) + + # a terminal "dev" or "devel" can be changed into ".dev0" + rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) + + # a terminal "dev" can be changed into ".dev0" + rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) + + # a terminal "final" or "stable" can be removed + rs = re.sub(r"(final|stable)$", "", rs) + + # The 'r' and the '-' tags are post release tags + # 0.4a1.r10 -> 0.4a1.post10 + # 0.9.33-17222 -> 0.9.33.post17222 + # 0.9.33-r17222 -> 0.9.33.post17222 + rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) + + # Clean 'r' instead of 'dev' usage: + # 0.9.33+r17222 -> 0.9.33.dev17222 + # 1.0dev123 -> 1.0.dev123 + # 1.0.git123 -> 1.0.dev123 + # 1.0.bzr123 -> 1.0.dev123 + # 0.1a0dev.123 -> 0.1a0.dev123 + # PyPI stats: ~150 (~4%) better + rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) + + # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: + # 0.2.pre1 -> 0.2c1 + # 0.2-c1 -> 0.2c1 + # 1.0preview123 -> 1.0c123 + # PyPI stats: ~21 (0.62%) better + rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) + + # Tcl/Tk uses "px" for their post release markers + rs = re.sub(r"p(\d+)$", r".post\1", rs) + + try: + _normalized_key(rs) + except UnsupportedVersionError: + rs = None + return rs + +# +# Legacy version processing (distribute-compatible) +# + + +_VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I) +_VERSION_REPLACE = { + 'pre': 'c', + 'preview': 'c', + '-': 'final-', + 'rc': 'c', + 'dev': '@', + '': None, + '.': None, +} + + +def _legacy_key(s): + def get_parts(s): + result = [] + for p in _VERSION_PART.split(s.lower()): + p = _VERSION_REPLACE.get(p, p) + if p: + if '0' <= p[:1] <= '9': + p = p.zfill(8) + else: + p = '*' + p + result.append(p) + result.append('*final') + return result + + result = [] + for p in get_parts(s): + if p.startswith('*'): + if p < '*final': + while result and result[-1] == '*final-': + result.pop() + while result and result[-1] == '00000000': + result.pop() + result.append(p) + return tuple(result) + + +class LegacyVersion(Version): + def parse(self, s): + return _legacy_key(s) + + @property + def is_prerelease(self): + result = False + for x in self._parts: + if (isinstance(x, string_types) and x.startswith('*') and x < '*final'): + result = True + break + return result + + +class LegacyMatcher(Matcher): + version_class = LegacyVersion + + _operators = dict(Matcher._operators) + _operators['~='] = '_match_compatible' + + numeric_re = re.compile(r'^(\d+(\.\d+)*)') + + def _match_compatible(self, version, constraint, prefix): + if version < constraint: + return False + m = self.numeric_re.match(str(constraint)) + if not m: + logger.warning('Cannot compute compatible match for version %s ' + ' and constraint %s', version, constraint) + return True + s = m.groups()[0] + if '.' in s: + s = s.rsplit('.', 1)[0] + return _match_prefix(version, s) + +# +# Semantic versioning +# + + +_SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)' + r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?' + r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I) + + +def is_semver(s): + return _SEMVER_RE.match(s) + + +def _semantic_key(s): + def make_tuple(s, absent): + if s is None: + result = (absent,) + else: + parts = s[1:].split('.') + # We can't compare ints and strings on Python 3, so fudge it + # by zero-filling numeric values so simulate a numeric comparison + result = tuple([p.zfill(8) if p.isdigit() else p for p in parts]) + return result + + m = is_semver(s) + if not m: + raise UnsupportedVersionError(s) + groups = m.groups() + major, minor, patch = [int(i) for i in groups[:3]] + # choose the '|' and '*' so that versions sort correctly + pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*') + return (major, minor, patch), pre, build + + +class SemanticVersion(Version): + def parse(self, s): + return _semantic_key(s) + + @property + def is_prerelease(self): + return self._parts[1][0] != '|' + + +class SemanticMatcher(Matcher): + version_class = SemanticVersion + + +class VersionScheme(object): + def __init__(self, key, matcher, suggester=None): + self.key = key + self.matcher = matcher + self.suggester = suggester + + def is_valid_version(self, s): + try: + self.matcher.version_class(s) + result = True + except UnsupportedVersionError: + result = False + return result + + def is_valid_matcher(self, s): + try: + self.matcher(s) + result = True + except UnsupportedVersionError: + result = False + return result + + def is_valid_constraint_list(self, s): + """ + Used for processing some metadata fields + """ + # See issue #140. Be tolerant of a single trailing comma. + if s.endswith(','): + s = s[:-1] + return self.is_valid_matcher('dummy_name (%s)' % s) + + def suggest(self, s): + if self.suggester is None: + result = None + else: + result = self.suggester(s) + return result + + +_SCHEMES = { + 'normalized': VersionScheme(_normalized_key, NormalizedMatcher, + _suggest_normalized_version), + 'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s), + 'semantic': VersionScheme(_semantic_key, SemanticMatcher, + _suggest_semantic_version), +} + +_SCHEMES['default'] = _SCHEMES['normalized'] + + +def get_scheme(name): + if name not in _SCHEMES: + raise ValueError('unknown scheme name: %r' % name) + return _SCHEMES[name] diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/w32.exe b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/w32.exe new file mode 100644 index 0000000000000000000000000000000000000000..4ee2d3a31b59e8b50f433ecdf0be9e496e8cc3b8 GIT binary patch literal 91648 zcmeFae|%KMxj%k3yGb@-le0hq;dg{!!Jx)2QPL&2NH)YubYozb6#{w->ALj?hI0Tb zfutv^IULt|d$ph1tM^LLe(bgP*4xStt3bli1S)Dki}6A=wyDl~VvT~yA~EOle&*~Z zLEHPee|*2M?>}EO=bV{&=9!sio_Xe(XP%j@zU@)LDhPrNe}*9l2k@qU9{&9A9|*qL*S!FoDk2>;;Q0JsgS!E#|Np=L2Pm*g>+^@! ze&i91{FPlELF?b0n7dSnw8>K<1Jbpj5K{a`{t6`RF%zVzp#$RtAuNQP=%hh(?A64I^Ygs)dNNee8#q3xXNzV;c>_P>vRaEk?dT?X4biy~du%5rpGZV<1M3 zzmNFhrHE_%95G^j>^H-I1F8V$R{33Z_8aO_-fc?lLSFI>WH*S(_W$| zEz50})3iji%A$Gg#qH6Gk|F&Kt#dsmrqZ{-9|(1WDpBu{%Lw&M0}{1yNNwzAKSwdH zR5U**Gx~fn0CfuEkR<$t!$OHl9 zn!q6&MqJoZ%kG^j2(?;2E8}DqR_2m)FOSE|AZ&Toiyeia3pGY5lG?_n`QATWAQ)P~|=!tEXsh zU$OUmI32|X0sO>hxy%N6qa0nJRrgw}d&0u}YG%mze@J;(U`x%C4pUIGv77 zYdch^dxXJmzmH`Af4w&Dz+yxwM~mvw2kB~EzrK>1K%}}q&D9nbz*$4Azlc|z<5~q= zS_0MWoYuw>9sH6QpeR}~%g}S{HRnr&vEDsi%B*t7Hvd((s@{G=a_2XY(c2$fzmBt< z(&zApufh;=4XAR0&2|VvFbJNQ;SVKdEHzo!k7SH3I~W!zQl)-hAmjn|sQ0`N+~8yP z@Rpv}cpsK=zRGhC%Qr^73EyEKAc+(7!Z9e(o>7!?9svgY>>cz5Skm1gzolDU1C>$Q$#bsOzzk^=+Iwq-t^8C&P<4uKOr4fmo6 z-xK$*vIn$9+8f_Hp02dp+$S3Xwf@j>9!M=^+SoPd^XTG3(RB{+HAbG@{odwc?PBq; zW<~BvO2UxFD~Vyrp>?(=(tPX@jHaLxvnic6cb&cA9U|w)1&CX>W$O)=DctzS{7BaXS3D){WO&bYlpvW?)} z-UNLhseA+V_8BQxxoAjPwZ_{(hWfeAl+_JMPNi|kkg`E~ zHytAW_lL8LQc5=ROQ?ozsNq2Z*d(We{d5SH&|^U1W6j9joI}G$D53rjZyJ-AMo{QQ z4i^MmxFQ;P5=a6*cJ*!OrK5Rf7^7$rAO|DIkSJsbf*6U6bl4^R73l$lak^HG^x}gZ z8#0VeThUv*guw)OXD}-x8i$iPBO*Fza5cbOXy0BOe23S0-?uvbI+hCKU;|D9x~D>?c=hFcrP$2 zx~3zT|sZ!mvFLn z@U`oBqzyVkZAkc-l!S4pP4s~RaepQ>A}U}a;eH~CTT1b0ZKwj^K6ZA1H(cpVLknDj(J>*9+R9G)3H>K?yaf1XL)kzAQujFqyC@Re;^qR0g&Wyh&aTt!C)Fq0OntaVW-w zmOs?I4z&a-+PXDK{jnr-toT638u_*^=mE;2*^(_>sHcZ#D{Z!5jgKt?o11;u8F>rM zUx|UH7ezOv>Eo#m6aH3l>Ry606`Dh&0s5hM=P%#|lv8-NWLNi|1&p548KL)|5UH>< z?QsgYjz^!OkzB7jZs;)8P_~-}Nw=#la)#8d5GToJ=fS2?h4d!ZKu~+t-Mu+~*Z8I{ zawJF77uizgQuncjPxLhHQ)C;UY)w4d%akn`h(^xS2Iz#~1icDUbDSybjaZackNWcp|c0DgU zoVMQVthdBuhHUE~^%dq0#wRTvHq1OvsjlC7>8$v~J;AJH!L6BZ#^_reSdp1~ua# z!q-9y)GIq@&X$YA&Nb2Hh2hygmIH$Wk&Y8PkYxO7lmg*T#LI}<*87quqLE%q62P<) zn4$+Mpmj!lY4w_2X-lh*9G5>YK5{al^=rJ=(JG`kdCDoIw4Y3&Q(bNP1Avend|83@%ex?0*%A&donqn~TCUiUGly;CIiY0<`f)6* z>dV}6A^XkeU@*uX=q$bLKaoEM zfzA$s3xYAtpmnh(+aa$fvJ51KC#_RUm=9IeI`pDY6Y>Ek*0F2?@SqSi17gXBc4V^C zsI4YI0pw_)o78Ko9J;;U{d;Kwx?e3)W?47dYP3ScIB;sYk>gUDaZ>59xn)N~U#dw;`O52-3W78xL~nNQ zH7F33hN6c0P*jjT2L&ngG};hq#e?mp>e5WO61wztHHaw!z=0;D9csZNs3qJTn?)@X zXwr->(!_@wK2^$BQ#tF`Nz+2~#nO`{IiNbGkxZpnB4M~N)I;p{%xrX(odt;mqiQMrjqI5s^?E z-%{(&s*T;sCpLV$FE-r7PRtjCvP|h?1eO@rQ8xAxqSQ>|O%oqQrBIaBot2?McT2g1 zr>;x&c5|#+U6=lRr?>YR(4e`o_XWH*j|W3)!~{eLZ?8@Npwrv)A_wR>kOb%%1?b@b zCOa3RF-oITkmq;uRzr3XIqt(CBxYfJ@r!iHj@UI68)N$}1M7261yN~v!+wRK0gg^` z8)9ie#-2#UVuc1krkddxE!YtJelo~0v5(PFl<8XZaxesgwZMJ_7C6CEv5*s_E0K)i zN`_&uwYDS9jYN$`K79GNXb`buvC`ao@Wie0-%Dh9; zM7A1Ph#A=}gsFN+wev-BYApJ-Pdh3dfRMjq_E)7{tGY|g)v7}3%{DF9opZpkRFEcU zIh(SvV6NIEXR;^V=+_Lb(&kRzmW9Mg8ZwQf+u21I(kpF<9q?>Qlk=3SO=&2^qGxM% zVD{K35i7zYHo+u7pVQS=8z}QS#g-ESYFTHGoRO!pryw|!Lh%FWfd@dbo$==hpwSUA zr1xN8$ct9@bF&dvTFSOOL0($?p19xURuW4aa+JhvTQPkUiu?O9?e~+$i0E+?ox!8q zpmZ74PN||EO6c|V?tUqzFCuv!eJrI-%UL>C5#{vCKx67ouwgzkENYUTSGGHH3@Q$?P{XM_M`r8O&R% ze@Cl!K;ZnT_*1kf_bV;xLW{b@{g$t_sGYY6^*TlM>XKr-UPp@C^@+sJB%_H_uyzJh zJ5!ugOB(pao~`%#14=pZpdc5p>#oLdnaP^3gzVxx8~uWdtuvBMi^0-@CHg4K{sb(m zB_f7=dDyY8EWHRgq?}q7rBMa$`UNI}*I*gItkJ<=q<-powd^heV82FOrvQlz5w)f+ zc0dr83bEZD$Qyq+ZyoYy9uS~T=o#*g#!Rha52b`{KK9~b@H#*^0TIxSv?tOvTcq%E z$iXj^4`;Q1n4#IgK89p>cStr=C{4Wv*>7mRun8HqzrG&k;~2Z{dO>I^Cuu53>C9SA zvXshV6KN$kI-~IRx%(StHxJ*AvbRpvUN1h@egW1SXAOJ)GC^p**&#HcX?nKOQ3wOl zlb~*@uP}ouiM+;1N}JQib^sXad~=lv4(#@?@bC`n1?5x2^#=9h`+~*NEcIyL9s>S3-_)fk?QthQ9r#Ss zkFAg1V62HnZx~)rPmD}Fhww~^LezJH!tBk9{`g0*KL-3dV)rBoYidT#E42`st}_Am zIVf%yV0$!`p=B^$#1~k&p!YL3I03dJue~9Y>$>_MKt)Z^Jb6&+=W8AHWizE|P<@mO zB#|zVL~1XrSGl%ZRv`by)fWE~=v7-AHvESLV1?o20Cx4XW6(|1>V*4Mc{1CM!aD`% zE&{s`pPCT=4}6zZ+c%Hrg|anNyV>zNNKW^wJC=oeT&GqKehTwq!*$V$EPFW_Ew)Z% z2MO^}cTAezDV%@=*2nwUU!d&=5tY>`5IvMOJ0zOc4Z)nlY`k0=t@?w!Sv6G6fUzl$ z;x>4;VXiefmYyxVYoQl?fX0A5AcGlzqh<}H2%y69= z94zzZZMlq$d5+pJ>=aeYdBXwFJ_@jPulTFRyunI`16_(u5bZt5u2mLb??KP(^qwc9 z5mss~{{_+}fussdv><1>*!me_wTtfV25h&u8;8V)UPeT~xR#75Fv9yQ1!4XUn`Mcm zF;V;;g!}x)A+51L9s!iQ?tH^qrSZHV&3cKZQP(N=J6p1}_Cai7wCkB#j6Pz;NAz)g z?s0c-P19m9T5eqfq9^?9hp2AQ$G)sE+temKK(cUy#hWPZp6?yfi~P)vo)1#&t*~7R z(rmDc;Z3w!(7c-r=pEkkSgc1bN9me3Fa8S6KaCF9YSC%bJ$5!3^+&g{pTn5kDL``- z;y)y)n;nH(XECJrpzMsdm@!VhFYE{jpF+oN132wM^p?p^>FP2$Pr9N^E|9O}*hOHc zeF*kjuZjFdj+`&FeuN3d)y95@7_4;)%;ByQEekH;mbbB{$C7o-GAW%p!rQC!Y(FI_ zVG5CvY_P1M%-c90=A|SEC@To{WnR0CbbjHw`oWQhf$#bYV-> zYsb^be+I%B6OQ#VGO5vDwPQ|ulIJ1Wk(AFG(4r)Nz6`WrIF#yfCW{8to&rot$zXHe zJjmiP@(h!IfEYKkmN`JDpz>@F<|`q*0?TrIU>}KnA3Yz9P_!E9#xoiz;bd{ZRLUcJ z5LBaQ>G^m!J)e4uo_o9KdGG|D^$$Ou;IC*Oe?4`KzamHZ>)AH``uA7xdh&!M9_NIYirmI{UbuiZ#cY=A(bC(_|i#v1&B0 z6aAdWcIA!q^P2!5KEk>j>OO3!)pm%g85%t&vpYrs|BVC29|6;f`^N`JGrUUNzXaLp zA>}4$o%QY`(irn#KWvo(BHFPu9}j5x5A{l!pjHn_iy`dA{fn@Cr=0oX{%a_62Vjo? z7G|0@EaR1L2{L7-OxTd)=28p7XaWSkcc{31uzOnD9PbcqUL^H@M=c~T6b1K1jKiN# z9BJbxM9^bKd?R=o8%J5x-~B=CIF_xqVF!4<4f~^h_JN3A<0JghR>0Qgql1}i^aQvzva}nPuTE;R6XMlA#Px~!y>l4$V<0@YWB-k zosU0D!McPdCyLvxN7^r@qonBcr>IOAa5O6~wFIT!&lm8J;&!t!v|&`JEytv5w;t28 znigftR>N!eK!rOuxZEKWV(#^j+~lJF&83_Ik+%EOK`wm}SC%1KwmP+290Tok$v)Ul zbG>BMmSeI(!2=Z~Hk(8!AsisVHSc+=cW50gS0GpmNw9tw?W47d9cCvkWO7Ct%>3vH zDrp|cxw$aLa`OTOu0WSzpYdxZrFF>6O-k6!O36zU?GHrSwguhkc2HzrktaO00d#>tHSbGLyYzk*-x1 zml#q>vMTb7;#Vc-jgtMwzf%jvk%1wq=d(94uP1A92A^sHuLKr4DcuwMui*T{NJ9j&gxbWgA6hZ21J;h&I_yUOYuqRx|4q#m##8B(ObaF@3!P}u) z6h4#+weihIc$_ioG9ygjW223kHIkiJ@W4#u(| z*H~v$dL~a#C7vEhOozIv!$1Yzu2(B|42w^-VQY#@O0(5iB|rn~$C@Ikft8tcuZuxe zQ3LG(teTuK$vMkphdcpGJ6fh;&d%h9EZNC^Gm<&3A&Kol7%egt1oy=)S7?it!KGuR z1m6Egu4ELD#JG`tpH?!9X3KSK6TZ|%Z~!U@BmqMJbREY!r(RvLW0>Hlz!+*kIpFIb zX+I*Q@3LpUEeLxGpH-a15?$2UZ@elbX zGZX5xl-q{gRG+~raI2Qk=lNJ4eY!ihOw}^#mBri2?z)6Rlq;4Q?D4PbSTmyKl}N7>iGnlawFcX7Tbr| zILGt@p~O|RHw=A(RyiFV1%Ii;hoDmZbga7S9RgWJIrko;Zu$#~;L{wo0!p6z!36I0^{oPXQ70vxoH*53|M8M`L)GmazI;KnwRdf-D>R z^1j_%J07;kSp!Wi$YOzNj}bC`z*Aw7CSl3r258uVwtkcHfGefxes(#1W6~xs6EI=6 zxqSiVO>e@Lpgy2yrM@JkjvW`2a^mH~a#`xB6H5yu4eyljL z!aRn^1J$>Y2?2fSfxVfA>9xBT?2 zlRZVK8*8}i_lSu9QwVGC>f6W)+0u(Y1$FngCn_wpXK zNExtk9Mi4gY)rD`)uFTEH^}5B)05+o##JEVcS9oRjCngcJm= zH!{t87U0mC1cJYT;Qs-Sq(s3~&t3!BVl0Fz$7%154{*~mvZus}59e3$SI%Pono+=C>3RG$*U4X3I`De(hv^=?G_SB{A%ENUEW#Vcojny4 z<~Ehi^d{m9{I{DD_bf!HGkqSOSuibhrZ3>BNt=ynfZ5%O)sSg?F-;wNwgCua{k97< zzzof;9wFN)4?fA}p_CHXk0oM1wP`w~;d$a>tldl5WvT&G~^mNDZEsjJSbs}kxe z^6G?Jl(M8yA_o;EaR(Mdi3VwolGrLuW_>U#C3T?EPslnEzavc$UbY}w&vBhms)sCk z*2`HW39EqUB^S9`2A#z-7nT{~F*px)A_``p~ppteftWazj zz^B5-Nw-!>%M?~1U8%5a=`!pIh>_jMQRYpEkrROF^d=A#lvkbc7~bRF;~M8c%p*qr zoyU9lR$`~OpTOcz;4u(O6C;n}16B&<&%@9{lvmTnb)x<8Cd9(~Qjnz)yoLHb3R@}N zrm$SeTLn6xQuzwY5F=B7aWE#df!N-0|D`V{yQ}PO-Z*3&CJsDL1?t!E*tuD&hEg%J zl6l5Azbc?ST|B_e!QF79IIgFS>mG675+i3x<7({3HvZGV6)s$F_Dk6kDG|2%Lk@I0 zyP*bECT&2Bv;s)!$QT7e50=X^btTV|9?C-w%v2CTJPV|JM>eVfy-QyTU#LQ!yl`x? zlakW}eReoAw2&v~k0ei1I&vx5B8=e|7yP4^NE28mnxAA6=f@bq(?d;&fgTa{l3yV7 zn2?WZo`>m?)TTn{-$r^Kc$apss~lwT+h$W2soU)8w&`k>lbhnh&d|Ki(CS|Um+K67 zu>qgo4jD=-SkiHN+X|(c7CE?sUAzF$3 zwY88ESObYIv=HIBjD;`w*=ER+V33D3+Y198%=-?XpZ%UkSL**r-VD9^cfyOWD1iO>J>chIb@~hXG?ZZi(h}Oty7&c@psuII z)1>C_^4&k!!_@V5b(Xq*x^jy%CF|HY)LiRi*u=qv)YuEMY_g$FBen0VG6w*YR zxKz1tWy7b@s*jZmJ)SMXL#pzwrBDm=mlqZCyXuy^QG z?<#Ue@mJgfUqo)Gz6Qrfe&-J!!s$z;lGW2p$&T|Urej+-=IPtMBCQWmeOD9 zYGTPP$>j31fN%0>o9(Vra)=6O8692&l7f%O=mSnHeqcwv*=Bvidi3@i*s}pQ)`E`H4u_at?pAU4omB!HwsbP8g0dcdC`gou?p=cMK^(+ z{5-DBaLy(Fzh}5xF*!PZE1wXICx7g1q>^*O`E{2Lz zYxUzGYjfFmHs9SN&sWL|F%t_?VTj)B&rnB{qqKh{YO5gmZ#D8MUO5WGq60DPG1Lbt zciC!(G=Y1G1++J{W9-LfH1=?4#C@e!g+WtFohkS0LwaA)bt~T8G$j3qF!) zUu8>?6rX~a6XZI)vdD?9&eK*D6|B0|S5p)6RgUn0xi@iCtthf@Q0G!30a~K1wgY;B zYl^nXp$rd8Zs1s`d@(H9+@Ec^D!On>bnS1P+Bno$dB7&fCqfo##WFklQMDkvgl`^r z1_Awr4L`)dCfXE$m@%NW4KB4oS_lUSPI93Q>`6C@{<*`P(|u-q#MQuaJU1J!osbg0g$DJEqRC>zpw^hrQKCzzqQ7Yit^ZA zng&pjBX&5_&>5A(z6t2u#h=@3q-D;C+CCTXr7q$k$0$)c3sTlQ$x*2c7j1tNP8{pk z#grlrh#KghYDTR#qYHAze~2;%v?Wl=Co0%jlyU;bn*p}`3`)Hm3VMgVdAKkg-VbLv z2R(Q|~VNN#@t8>Y)x4w(k~;|%gZfjwv+ zxEBs60wkEJBRkL+!5~&G(S^Li*hIH!o%-G30=5x*(&RRGQ5M+-AWM0Z=suX(R-=2k zC3B`+%xM7!=cE;La)52Yzz(vjM>4^#sgF~a=$QewhLTdtPzXDWm0tEwfQ$j_e01-q z81+ZZmSOWmp6Fw9D3NQ>Y3b zbl6n6pF^2uGpW$f;VM$+_#V{L6CrX_RNeCoLVKR0$1mye0v<_w3|Uok&NK99na>6> z@agvk99CFL+pwB%==L8kPV7DY^ zp>aEJlJd1!I4xM*F3rXt4*yB`mY1N2{`x7*vO-io5BduQIl-)!t-v zM55>>yOyEXXmINgrEfxik}j))mazK*bs-i<0{aM)s{Ya;s?%S8d%D_J#uE)$`*lx%Dla_=vao)6Lhsfy;CmQVg9)| z4(6YWHf-|Ta=Tjnmb(0Q#LG*xTQ;cGU7gD}JPb4xvp~yXASYHFT23E6tUOp&eYSCm zR(%!()dFkPit%>CdshD=dl9+Rj8s>`qRw&5GPZ80HCFWm^(i1GOu@8Lfg7U0-oh?r zFvim^trq+sQ+3MFyj|+@4cNT4gG{4LZ~#;o?e#scTpDZ}4H~5sFbPF-1Jo{~3*5?2 zPtYkdEJKrg7Blj@K~Cdu@Ox0txW;+5J1Pe*lv;!WJ#Fh zGfKNgAmPcGLn+2?!bj=3=(b*DQgV0+@hxC?43p=G_1?t-I#h9rIR9b7E89CO*RBTT zm3lk8sh6ue^3)xhIvzX*;x6lW@FpZ~_@1h~Qn|vPeZEU%VeL^;}R1WGs_86)YL$Rm_&=;Yi@H2L>Y5arauxaiv zN~wjgj2xxeu#1!H=1?An2E_dkFkvS%2z%AHKr~K^(i3CogT8>76(>`PNwLW8C)(U> zGhn#N^0T9VK&)#3CG{bmj@N++{A?!*;^o-GPWA(Wp%jp@i^%xD$nL`JNG%DpL#u>y zKaGQ}9I$eM8M@O9Ei^-G%+O*pRBeVlX6Sk|RAGj$F+){mXpR|LTTb0Xg-oCbkn44b z5!0r!BZPTnDw=Q}O#s!!W`lZ6g4F%XTL4PYJYe?HY?z7xK;tu=lYE5p&Z&_+4~%6ta%s$B z3$YafTNF0iOIM(}p(b!+z&IU}=)LDCb=>JthzIz1I32p32Y8pBR>}zi9@R7~_RuhZ ztS=`(_(&?{1SD{$!lK6G!Vn;>mCcksOCfZUTDIbusY+GY+zpK+R& zDlZ^{jXMcuD5dOLDa{wgX%i+{S;NFHy#S>fQ`5CTq+c zYY^eaG{jC#0K3qnJSn=&iP$AP_LXF;balIt=8G;okep!22_Aw`E9c~Uo6daM{KngR z$O|5ms%f0$?!Er74f}EPI35y$<^)aoIWWw{>EQx6ph9aRH&IyO6Q5x~X25vMYTkf0 zMe{z4f#kJ5)<~ODIA#Md#!3Kbl89jUqkGAbVz4_{MKa%i;Dvsi9-v3yZ=N22nnzFv z2#Oy69Td)v8vEyAG@4COAEIxZLQsr(4Igksq|8;@lLkl&E!0torHifY&5O9*@e{bx znwH0^0`ajU+L5}{T8x%tDYZqbT5N^a^wPXqzqtRt4A-eZw8Bn4b?|62%B6$Ca{1USuJ8E z31LD&rhV}Wkkec!;RwC`N^UE-4`*2b^ja_r{B`5rEVUKj3 zY9^~JjxD0)V^le8vy54!CNq#?ObK?k<9E(85SQ5=B{8W1I}sqM?RR;IN>1v{eI)+I zIBq5F^)I1{IOKIEs2;R|h1?g#mb(B5b>{>|1uXecf-=pBmni}x0Ah!`Q;02p1r3^q zi4)aHGR1l6(P*TdG67eaYJ zQ2b7X zR9{z1|Kj(9R5&`FUx6BvD3P!XGNh%hwq1_ptq-T$fvdi3`f|zcDW|1+1FMZjr~n*P zqcmS@-9@%!8EBY_z-UhpE_jcvBlj`u@5y}(q_08UiS$xF0u>Y>;-p+hCWVONY%Xw` zOPguFf8FI%`2_<;AXX`|N*begwuo(G20bW$)n@Tx3=?k=B^C6~0s@^H?XC`H&Q0ee0 zbE2?}P79V|sf14-<_1pcIJPGSef(>JL@T0dWi z(?ufF13p$VomRRym7uk&J8bF>ySl^C{=Rcudvu5M`RAX1iIf2JEr5$(KTIq5U3r#P zr{@-VF#6yO1P=*3&W^a7<-wFNYVMnk0XJr7;6NvX%HT7$*l;N9q(|Uh)HFR3;azR5 zE&QGgkGr$s{I1Af0ooBE`CHi1CY3~=`UDZ;zGLD(tNYm2NyY6RU-YpYN)H#_%I==V zd1}`(3l4*{w8^K@U0di17i29;}uB& zOp&7iAsAQ*b1l1q0~Hb79`;f(1MLgB{DazhSijXIV`ronX#N!yb{(J3zqQ9HCfbrqnXFyW+X|J7Q?_O|t;n6>jMfzD zIm#DPwmK+S7_I4Pb?Vj*9H(%<(a@1{oy>&OBKIF#1UbyU(TC}6BRES-?B<7umT!_~ zC_C>Gq{-UW5SY`BN%Jg*3$#>rJ%D4-5B~$+m!^KWHG~ZPIOrd`l9_ z?VG;TI!2BI^%ag`R`NZE4jNz^&>xWG19VHru$-&K*l*F1*htxp%AjG`dOy&F#)>Wf zNR{*UDs3g@{rGJ7u-RZ=k_?vs*=*41Ww9lMZ*hMo)>aNwk(l3)n5dWepm)=tps4E* z#Ybp#*>vp1KUx5OJ#_e<0Y!x6aCLesgtlrS-h*#feh}3?23w7dXR?c&W#o&jtq-Bk*?kzibeMOUO>7BJR}^B4-7tP zN2)!MkLHnzztce#pFtxWcci2ig&>ijm!PuGa_0qoE^G*LVe>_^xwUK$okm4XwcT+!vqGF5|4hA2p&s!<>Re^xChu5`Z_uNZ9++qDN@LA~fqR7|?Jd=WV~g|r8W zVu)CW^GvsC4-&}`nv4+4qiMgxpxZ~e*gr!lR2@54=a6nFaAE*{mD3~lGg+7M#*;S>Hj+Ji*@ z@@>>x)5?BAb)xD{_CVV1qa;=ZK-~DSW2A3)nteN7%S7$Rp%O56%pst%^+Ry4Uv3+X zc2Km^$-BDK&%T71z>Qid&pPxmoojVt0RpaA=+{czgVMZ!x`>5osME5@&;E)}n3#jG zmke`zM<#Y7iG{V!b$m(6-G{c3-1&JgCg5I-X55d*C*aCW(A;)l0-nxZqv%a{C1+tj zRRpu}OUOaCta*@Frp>IBY%5B~U49lse~`Et@yg<3@DQLcW0YI&H5uXn9dl9o^5|%b z*zyoaR*Mrjj%alsdkDp7a-Vi{9$fujmGjZiXw~6*<-(M$wtA(l5ZnFwP5fL;*uqc7 z%%qj>p%Xxli3*2gg3s;jk9?lINoiTW-!cW^7j>4w*81NCLq zABZivkTAHR&D}1x(Cs}MuA;EP{%nmMB1r5Ky5HTNroCInotGNFcen_pa$nyQfaX_; z)Fl0TqpdtgCJgu2ByfBv$IkTgI_9MADb}j8g1~8xP$|6m(8fOUPaUj`hzIKqxx$8K09fogw(tn(1 z#<_6s&@Q31()KkkX|aWdJ2^>v6Sn0BPHSA;{s`Q_&k+hvHri-3aGC^VXEfbxYtrw7 zKXfykXVb$2tM%;YGH&ru+CE23_yR7W;JTF?FlLrOpGnruH5dFRs7v@IYQW;rvy_BY zw&BzMwK_=)P#-;;z&hzpJ!6@2(JDS1!k5AO9Ct%PO@hf`4Nj|Iiq{7?6VYZH@#f(p z;OImUHo|X|rO(48xMdb}&pc#_KFF|Aa+{G>QV#lQj{3dn;WvT=D+lw?;$>VnphfNf z7+35oxRRLOSPITcfLjcCL7CSf{SRx_{xxiLG>;ltd;=jgs|JkD|0%|UCPuhs{Dj2O zWVmp-&R}y5_U}#z8rqNxTi2_NJ??Tkv)#B-8|1zXWz%1Pww-jV5Pj_9(*P#Uz;Tsc z#mL(}&vb!EIYrQBC66J`iC-K7Fb~0VV!6IMr7@&zX{X;K5ocj{{TXm$r?5(gcC6`r ztLseDg*4go3(_s6GHm{11GE@M;2jGBaPW8t7mdK@xTT6O0O7AH!e<#S%c9crCAlnT z(rHEIRbxFeD)lz({H)VrDfvz_B?K<)_od*_pnkQVZ3mx0wqfH5tThWqJ_Qeo&GWOF zD9Fe9QBDvRzG-bF+Q~CCFLZ!t`Rco%rMVk-7LD0PjlmYiC4MfXDsV!OpUWa?ohEQU z6+fEsVmsi2(DKz)Ln&Iq+&0jz-~{C=yNvU{-=>-o(Px6_`joOpyPQWWO!UXq$t2mD z1uwhCAqLqcRO&4*2=|^;NBG%6UI@GtXf1582>^mSGj0j(#W5uBS+)Xgpq}{C`zVP% zW)a?$kp6o|30nV{(&^Yq&n3;^7n84hLt++jO z^vFQ+d;#_)Iv8-#H#|^6W^pr<(Qo zZ;l828ybY!e&l$-V4(~GXXm)mKYHEqlW{&k<2rf&7eF5I{cLHpN4y}2*}Coh)f<0sKJ^7^dD7VzJ4^| zf>}sXSA-{5q-_W05#YggJ!Uks6%=908r*XU*_i|<$9>0^39xt3Swbnxg9RPTT@9aJ zVoN4Fa7`4R7sbbv_eE>_wBzI2gYnyH={mk-Y-TF90w6sr=jH?w51Wa`wndPy+@yD0APUPe=k1 zKmoC(iLm2-l_j`f7ni5pTu_|25jV5AFYcHT0Norcv5ds7{-ytb{wnHPU65LPE=WK{LG`CexMDy9o(4;rMz%jZEX$v!>xia=Loc5;_JC6 zbyu~$)viy%W+yH#we#ZCk>bi4hv4l&@($(EF+x7^@`=)%-b`_q&;Ibv2=C8?+8_Ud zHwi9xV#_D^e9j|AKBRXVrheW4e#TNcMh8Vq+CgN_LDvQKbW}>sFt!vJTj05V0kbcp z@prMrB69_ulNVbwphEq{mLK2)Zt*{mYjDjIK~k$q{48dE4@*V3J9Zxe@B{ZJ9l_bp z5eBV-M5oN)`XrHdd2B^LSo{MylkVjx#CmQAKZybj76)A}J)q)5cjX#4+r)jArE0F8 zM|(6%5AG^+W}#;=C4gGw_(N8IrEj{nik}b)53g3eed?-SFvK*rA5IENAxwvOkGi%u z!i)vZbD!I8cXQpOVHS%5LM>pMM*!I?(6LcO{ANINFZ%#o+f#1tbdqt`iV_=iI-Fqe znVGL0$*hN($%46a>&-au>V8vf$p8`veKGQ-jSlxYq|#j#B}Hj%U5Qrjuz0AgDr^f% zQ>tL7Ix^TrLqhq}u)4|#a~54j{CzW4p29wOn^*G$UP-5pd{k4vRy%SMZgWl?YQ;Un z5CKm1FSJSB4b`9@YTr!uQ=Wrt&+KRXlcpMK-aV+0dGiu!DF5pgG*(nAa_510)s?vm zG|~F{z*1Y47L&fe>&En^3)#4Egl>Ae3m5k^H4Y1{<1{h44BI$lAw4HTDYsXiaA$gm9vO=3$gs)o9zi7n(c05g1c zlRH~ZZ?fBTYg6Gxt*)O=?Z(tPI2oRIcZvtPoC+ZCIAl*J-Tw3eYF%C#6PzUF4ucu=X z8MZ1Z56+9((n}rI94&KKwb8mFBVC{CuIraGc*4`@CnL;nMEC^0cjzG=Z3Fx=RERD^ z`avbwPS4je&9-}}k!gDRE(sMX?jV>Sqhbk?V2d=`@MKbr65ko=P+_#FfUKhzmKaSWt+8I z_h{lk#WiTs+-0pRuSv%eVtdxEiJI8DGI#DWT1BKnmoF>s)+Y>hKLM9Yh($ZgH(Lhf zrns*EBqLD?XXbuv=ZxMXNmpRB$qQ4LdG(Svf>PZFkIsHfYvd)-_ZviE2N6@P7}3!E zpif*e-`Zp>Ph&W*VTF651|68WuPLQB6-8BQYcn7pnF62n#>(y~| zl~r3BdOp4vv9wTbAI5@mCS9PYlx1U+kd~*;a5ld@fS9O-a(kV)Y>snD8r{Hs32%?# zk~N%5R;9RP9&W5oWJ|w>S8=^+B~D2Yr_jd*e9YA{J1R0R&(^OX&dD@^Bb;@HiNG)3 z#97CT&oYsq{wW5UN(?jwV_M3!4P+1l<)-Ob^_qaP0ESM1NQ?gn>WE4HaQZr&beqh| z-b{&DwiKUgNjyv{OX6-ZavObY>=WW^@sV5sVlC4{`u9zMA^8wm(gx*~!t6hn&8@mQClA8vF=S zXO)uI-PE!lT|lw?+@e02eS5U*TTMJ;&-Rj>B?re}Bgs6ww1Lo?lNB_kJgi=@)34t_ z#}aW9G$yCHwm;iyY*mi>oW{#8xeQbFu3-srid0U~0(4jb*Tt-~?1W zhBr%4&-o(JM= z6Ye8|LjmwcsVK6=l({y-JwgI#9aU|nE`izVV%L}+*Q;?16Y>-7gngnAq8^K85_=}c z(FzVLJ%^9!$i9^CdvWEkPLpCUkPv~z6P@y9@kOSo-w z5^VJd7~B2w|AgCWS=V8z-{0b;0U8o1UK)l!im9Ej470Gm#Y^=_GI^6a zGFk+>L@j-&$*+p(*Si23W znXv4D*|A`B0|+K{BFk^FJ$0}RDYP{Zc?BP|X-LB4<7~`u1MXqz z4RQZubuUAbx|c5GRrem{&*$j*cn_ZS?$ct+Gk}L@uJwX?@M*-A@f_mA+YlYSp{HI+ ztllb$`;UnGn!4g1LFo~RJ2j5$du>_bzLt`H#E0$e%(hOAP!V*M{w+k+M5g#q4)7R7 zk@GvrG^RwUxoFc*@KPgYdrdl+6fh}66o~g=ZH01il1r*mm{nR>+#wagZ#WMDF|rS- zS$hdQz8{}Tls;|o=SRLR@qfj4GmodSAAgi`@L)WG&wAyHE97Dg65p2U9I6Nu2DN?! ziEZ#Hij1#B46YN#=l9yO#r?mB>rofZ)=^R!0Y6L465>~;@XoyiFC`LQIs%iVKs=3O zq?EB3DIEV0iYqldaidhDBtnu$N!%r2mA)0H)sSR1iA=ijgLvRl^1I~m)(gpKtSRC@ zN3=;0XZ)ul--Yo<@Xc?wyjGrqE11(3$`fGTO=bj*;&&5p!eyp@(@cFffjSvGQLkHg z>IUpG@{c>kmU8}auh_DXf5>7>G5^>mw#?=qtHqWn^zkgQ1t|CyUbjX$hjm7%m!Yo+ z=H2!2ei-F>vp0x8ye19bEr;>sXZq-TAD!u2DmlRbx0w#)V&r`+Ig}%Xot&EplgZT@ zXEKujp6J0L6vHpMQ4jXxQ{|UaL!DM10s77ek(~D3;_o6j7@^jdpvq$8I3i6Xh4F*< z9!--olkwU?%1weh{V)*;yfnZu6q+ohtr=3EqC&-%3y>99ri|PDG5x}i044DV>0;2THdX=Y{5N0Dy(G$NGbr>@y(2{RyRW|6P zE|aXp&f;M~jS?-S2~QLs8Rd`or&#VN&r*ej_=or&LqlBJKLezRcM(Wplla5GfDgo$ z*YH#l8^wnY@%ZnGk9_zg$)h`^dz8dIQjwClSB(4&X(l;VM5)%C;_c6FVaW2)4HDB0+xEe)c3bs5LIh zgKC7tI_T;gOLv^3&o+@!M$Kw^%PKKod=%i?vLQ9{7(SAnZtVRD0^*?~#$IAIXb=h&uESShaEl~1l+W1@(_9DCSilYdgUO|;Jdg7 z@yu9=su75$>#vl@Cwl(M822BgE zBcloR4L~J(*Q7oqBWS)Ikl_+6DJZ{r#gfZeK8k&&x{B@GktV`>shzp z=OY9u!`hCGsB^_UJKfVye5V{F*@8}Z4qdBp=?Wuj@h`{&13x%%rJf68k}X`UXTT_! z7cLCet5ND&{5k}l01~u=-S{fz0<-ua#j2bYpL57X`b=|D3KyEQ(k<*Ec%dl=XOHABa>=riv ztJwdf9OQyc{+{*}H5Xf}cW8W~x>#chu^uFVVC2ls7xb%?C~uZN*BsGiiwxe2sdn)O{RSCv+Yu*%u-%nxkaR_|L0==; zO2A)AGUecNC>E!LjlJ~2uHOTQXL(#u<7%eH6=G;3*oWfWNS6aYPb&>eB5`pv(PD;3VvYFj<~m(=kOz(}+a07ZN%yLuF2+XzGq5CN_RxZm57 z&&G|ytOQK_he*b@5qS_k|11-#K=5!IR*(#7Z z>0;Qa!`M7bDTqs6LO$yJki3OljF34nNt;q&O44S>zo%%^f^_U5phoW8Ay@nqdp=*_BdwEF9AU7nR zR=yDX!gPVMuNIzT?qDiAyi_GYd#hmo)^V+bq&-{5B#x zf-J~DKi#&}AMi0@Kaq)C;cvuj7G1c5Zts+ThcjiU4*TbSMe1Do6yP@4nNF8dk@&<7 zZ8(37lb%#AlQ|T9l5Jgwm#L5bv6;{F-zb1b!#uKP^uqx8DH(v)#p=s)@pEMOWtK9# zoJhX=Yl}OgtJ5(3yz?%a#l0eOkhh5Pc4A*BhO-##4lpvAgFYD9BceSNMVg6yl&RR& zVGIMlx!y4dA%R+|87g*sDs8DP6R?3MG%wJHuHx{_zdZ@J zfudLn>2Crwvy#?K^5^_$W6E1IomBucMjBl)WR5htNR#huEH=m4p7FpFl`($UrSzf->eQiDyKT34H2&D1`_<+`0@ zvd4*KJN!j)(%NgcsS_csnwHF#=^jGw&~Ikr*P|+F$9J)j3Dc#0S4L|Zd^sGuW{Ue_ zzDkyHy-CiAMkX1%U`?ua?8-2_j#f)03P5o!XK>h>uC)C{)5St5z3F0>{I9@d>$m~l z05JWbnPN*E)u5h}Y>Ttxj7t67&HA5tLKhNf@<0XgV+2$|r(wYrnRHQ2<>%r>(sK4LUA=N^JoHNUL}q z?Wn>{i?gKV_!*jVY&-e@JcnJj#B5~Ft_(EKY@C59FV#{#hYgpTAl6DF6G^Kam2p2L zDzayGqzObZ%HWLWA`&fGw+6j#RSE^eGU6=g;1|S}%^?3^@b(ed`ppwiCGht0*S5}B zh?|md0t!NIH?h20Du5p_vyiQFQGsm5rCHjlA=)UZt&KLGXW5w7Xs$l$C`YN+Z~ivz zyIe3f-=HPhKTM0-MC^uM>tl~&(;O!%aYAW+GP`v+;3ovfX$im)RPE^nSE;28PnhM& z%M8vKW2&B#+}*GpzH44WSYPH!O74=R@k4$kXaIh;v^eHJneg~({eEmRnX_hx zvqi|X(xvqQQ9zk`ZN6F+GSD4I0MOKgahJV6lh5bikT5+B@&U$x}l$*D7?p>gvn z{RSp6T5aI)%^!GPei>2*w**Q#5&Y!9VAmeH-AdmGM@7TZqmc@H(4dYsQVPt#!j{U82)_g7!u5D@9V4TK5(%Z*gM%?R|^JXltDj^`7;y zStcurk#}KrIzWbih}S7k81y;?PbS!=%zIX{HB}o;IA5joSc_vXA#>Yr@o18kRY~)z zq(XKCIw>v&&NbMx_%gRuKG}n=?ufq&K(+e3J`Ht)P$0Ad(Ds=Zz<6*xVbyu~)`XpO z6~lG-y8?eR@aI&RM}D?!)pmd;iso& zK()J!Rgw}_D5x{pe0+5ZIJ<&l8~DUeYTRzh2CcjT+(o-P{heHe!Y?*Qy(cgX`Y;l( z=R<>~2iP`YJd>|`+ni(ARW3%CS){qm8?l@+cJl1Vg}h(Sc%63b-`fuwPVE`Lun&Wb zwcyDw6^g^31U2eOIlsyk|5qIJ188Bw!n19FDuhY;-`O>Y>J)NNZ~bZ0bGF(T@tmzT z>;!!+t9%9pXJ|M8J8Sgvbn=x6V&A{%xTSSP8txtaN;<#9IjyLvu&K5B%chNAR<%|e z^3H+wCb8u?a1RKSCT)TP67c`H7x+mu;u@DJ?1jD11Q#i&ljK9W zqAv*^uvw!M{+!;kb2{bptZJNZ%knzGgmXIG?NSA15{n?shf;~xY=qLx5MC6rVYP)c zTst(g!#ixz3oxx5po{M~7i1$YO}F7GBnPk9@=Nfz%RoIm@I@3OPaz1|#RbEhc3>1d z=1G3~Y?AJS&v@j14#V*4#t+_uPxR9kT-a;dQjEhmVq^`X#6#X;mr*6Q%tW9{j4a2C zZb}}e?V$Jsgs_IdPlm9K3y3(*aYv*RxK0%jD-pqczu;rg@(8FLw({SqzLwRTNm5O0 z*+SG$q)B1;LWmKvALCcrK6&}DAg`uYbH6R}Z$|}RLn1)M2k!*qqmtr70emD&|FAe& zy4XT~re+OJnUQmjpav0AJL8#rs_dN$tAkKJjE6iGHN6$9k|1mPKkR)8Tvb)O{$f&8 z6iiJV%A?{?m|~8I!y!O{Kv7h*G(rIZ5fILCD3zl?iAPMEXEU{I{IndJ%rU3RAsfsF zt*katNoi_DuK)9_z4zgOXjb>T_jmvITkxK>_8Q;!UGJKwmyId}uK|=NgRns)(%vR1 z%M*U$zjWce9nGa;|6oODFWsf$VX%dmXL;R>9VWXI$|48hN$kDpJ$K~Wfi+rz-KkxXWFa-D5)a?VJuqXge5pDgVzT?ZnhWi|I{}2m( z?d?@<-iD#-xb)oH;cKZ;lgv{!$t+i~HS3Zr94Eq=im20IlPrYU51ZprRiP?!Z8e$w zk}9`#r(-z}vVY(}wtNK!U8q6!SKq*{--}x6-2^?3@-dhwSI4L|@5j)0#b#s+m!}6K zcC{H9ONjPB|DMmfX3wW(7A1KAyHKGPKC&5Zj`gy z_F+G<>lqbHd{72)FlE9y<|p1@Ic{zzZUF(oc`iib#WqtSEnLci^h4*KuyqZcdmMKN z==nkB9Itv!Xd`h_6u@M7-?%hBvS`|`~=ynf-4 zj_zXP^e|7H8NpG*?Vc6iz|wzH!{BW-w=eLyWQ={suo}wPbGH>$wy%~QCd5QEe7L9X z!u!Xr$L53P&giOMd$gCF>i)3+XF}Ie&V-;*T631HoxT@bKZX<#Dqu_kEdL#+c#E2}4Jeuu)J@vzh1r6-&A1M?s5$*!E{zNF<27n?UPgu1 zQolzn@?~EwhD@g+8HS2Y+>&8jxd&!}7Tg?^>SQA#=Fj#f4p=iE>E|5f7dq0{?(+&t zso0S?^7R0;6{d+xAd|WRzmqtzhmwbLbIxtfLteYW&d>10%kc}I z02X-i4XEU|HFu6QOB!4Bt$)!s{$b~DNw)ix&*_ato!#un*qdXA2=<9!(4K$Isns&* zzNPnBF`e-m9gE7n^Re02%WDr7!&lvld<`ml0?tdJ2T@-SyW;?Fd~`g7QCfYReO)-r zW6@AgM7wYp?m+b_+N~mg#}zr|I$eI1FAtnl#Y=@m;VGA|rMjP@T^>Xb8(iSJ(B+Ca z?nIh4KsF#1FaaU`MSEbiElN*vmuK}zF>|xv(OC|5!v6K|Mn|1098{k-jBVdQ=7jN-amCXsQGV&m( zIu9EEacXrH((U#DQjxe=EvW3%`*GR^^TS4-{Ho_kF!Lk?PVhm`ORX@c0u}=n4fX1H zXyGugq8}CaPAcxb+gOBbL5nalr9stue!!AMG&I;>G&TvRMYNyMym233(V&4C>f@B2W}lle0o!9_ zi}%re)(>e1pjL4Xio8`F{!eQPFD;-uc68RCoyQy!h>pMVwN1ER& z<6uJV7m#sIQ!ygU%4pcF;19MB>kGf=oDK_ZOux1v@PXx7x}M{6yO`_hhAES-YG%7t zS^TFXvv3Zxa%7eVu4hEd_0HguWIF~(7%o{vMXwdaB`iv_U4w|=-a#J3u5=h$tsqu( z!9_}R!Fb?%M9ne#)wDuoSW+dW6vPlKv&gAcL!E%k1QhL^3d>*oILytzM>F&euGpH+!s zP8aJ!&GFh0U*dQVFL8`fFL9vF!uvXy4$@!ZXr^7`LZ;we)o1xkUrS0a^{hCE=ETIx z6PM?2$Ma$KKPYUt~?diNK$)vjf;|UtWxN=l&cG1W?fzMtLHqN@$j-Y zU%^}DC+<*MX=D9EOGZ$mSgyIpD3*q>2e=9Qm_Rkb6?S-DJM_ey`(pYB;_@4u&OKAK zS32=-Y;@T6xgOTis6fnxgiZ{yH$wT~mC4u{9OUf7H5Q(I>WcLfdm#FCTn_Vo3#9?) z*X==&Lfm&yiQyb4$w$H3e1-BX1_ykFa)|2{%6>TKTl*EtzJT~QF=*{Q)*o8xC+!}ryX*>Jy98qGT?g5hNCqGmxK>BrQYI>sty|`w`hys}pVlH_K(Bm!S=tG9Bjc-jy{nbXca<~TZo>XEmL>!4faKV_VvdY48S#>7 zs=#7H>em?O6zvSEv1VwL)*A1g@~JE(1#R7>pl!7jSSSU}l@tV2OF>&o0YrdXhYRNo zoG&XeP?_1=J@3QzU6}Wi@4SG0xDUhjTABBdge;M$eamgvJN?KXQbGwpN?>DCh(rQx zF%Vh>@dVcrwU%glbY@jQH#*Ky2eZCHO$9b7W*HCgm~5)x=yzw^y4b`$qFh3nj5ZB7 z{b(H-3l_85DBVwMrgS`&qV(pPm(l~Z-;3a{uXdW+42$-?(t|Xda>aL7?Ng<9)81El zi1v=sduuzD-d1}<>HW3!N*|~_54~uoRnD+#E0k5F_K?z}wPi|=(@K?|pv_nMXl=IA z$7$1*K0(V=`mI`u(o?nZO3%Y%u7OSzHabv}v^KQe zSWIJpGA^XiM;RB>_}c<{SW4qbWxSWh6Uz7?jUOxHqcrYU#wTdpp^U3&d_@`8(D;Hf zs!JuFRK~5eeLxv^(zsX|_s}?B8Bth;HUmb5@+;ORuDx-zQdCC|>V}UYur7=Sp zyU{pN8AE6srHs949Hxx@X+&8D_5*3`sf<<{J1b)(jct@Mn#KSa70Pk^@>C89G@eB< zlp9CW_`NcYqwx!6oIvA;%6Kb{Z^MY8rnwrd7dzW{XT2~Vb?XSu(5!)%@#=B?;1~!H8urcUjQaSI6m@hxE1F=w|n?M=N*J=!!Zf6SHd2>`9<}V(=HGHmEJ)G zKjQT1PK){2CwgM&g}L>(HZ3>1e;#bmc2)GkoGTWUycc>_e2LCg@+XDFkKw~OmbCaMZyTnz7x{=Kj9y!b$;=Kv3TcOcbyK#GUGQ$xH`xJ?Tf z5bkMj1?F?lR(tJ0mE`hmlK*zRKdZz2B8=nGaX)VcQ#=YKAPe@>|w=Z7)9V%$$=lfKX2B7Sv^g{^tm=LG@5r^aRpuVabYN%XeR?(>L@d9%VRCvcoxOK;(Rbc5qJ2qpFj zl$P}Vn)?I$tKSg(hGM3l9nu%EIE70b+G$b13-{~jJ zIAhJm6Hd|CmCIL{u#qCc=_PyDcm5<>IFM1i$;Q9!Hzq2^zlfN$BNn6mJg zVu=cjySYlbktsMNct=N!zDrB2_*_&%da!Z5WC{wvqDIaQq_WpmoO$JHVo6(kIqP}t zE}Dg1o-Gz7^jvfgw}h``Uamt2!NA|irNIMEuY4YcoO9lV4Z%CCpq2_-kd6FXBnl%Yg=gv zWhZn4+2p_Y3_5v5n@PXGu5<{CD^9LTRY$rj>U8585wBAL8HtDg;DaIK7RBG!-(Qr5i|JX&F;CJdTpePTOw*lheZ#C;dxen2w6n z)4^MGenw6&|Ed#|E`;s7lX&!LNTnNPXe%Y_h1Cj^)D9{=J<|N+bRI zm!E_*_1a6(>`&2LfltR~$_#e=6H)B$1x31~h2n(s9e==w^X*<|f|VeYl|;i6jy@N3 zUepXnlpkZJIex>bWF3!PqOo--F8Lff|GaF5$=VN`m3ok1oT@D==?dS-LlsHg*%-m( zls^x}3!GqD97ekx!Ky;QiAw(k*&w^TlM8hmb!UdUAsr33qSY)Az+K0jB1_zFA@ULa zo0@rj?zLaXzX$0^V*{hmf743?ymrmsOpEiR?3KStStiO& zZOT$;=T6x$Qg);Ash4$C#+eUxT9)zCAk2-dUk$RtocAeV-*knCl6tTGBn?jjk*JH3 zvj!Df{?=Y+#*WPx)0n4SDl1SR%gUk=ZSXc606B!?!rSnsn|>&oV_Lo&>0kT+nC{9| zkg+SFthf&Y*Oj$4Kd%kU{;$$Qk1dlf+OV16RK;7*V!hm|jBW&WR15(w49PS3 zas$~Sd-pdBK3=m>(o(dV(u){bCThlCq}mW+&o0`m{MfF;IpR=N1aY6j5BIZrZvaxX zUtlZ^sTg7u&tLI5zM`jD+psdXV&zMTp`(9mH8Kv;UQprNGW-S@i!5rtn4eQ260E8U z4)gI!{Vm#~(>Zp>Y*?L&*78M=QVLQfgRgD9pqYQ8TT;4PXI^}Gce|n@Eh}o$a)nmla@O8J->Bl~MIU#p=gr3*_a|x<7=gQJsE5aGdM0W)w8K84 zIS?rHKpPpN1!8c0uhYB z3z9M3vl=;@F7!l8fICqAu&E6v!ctyE`n;wDH!Ib{@mGDq^b)6E2b-r~Log|~h=*UD zZ)-qG;$3$feYH2?$ya~p+iQoYldtOF>jyOszN*gJ{O=v8<*FwI^=GowsaNM*&aIS{ zJ&&fKbi+aZZa%@^k~QkLQvLF!=+Y8R{l24qSE=8f_|=vb;O7vgAaUvy-{_qNYuoW@ z^n@A}@)*D?a8ajTd8?^;>NTYi`bT)!)Q%ha8qWo9UgM25yvC&sZb-nMAl&%V&RMzb zybs>4_VU}ntM{hEIV(>4qcf_YL*?1b!FZcM`wF@A((Mgd<-OrH^gJXQ=Y*zqk}uI9EH6r)uA< zeX6#Mk?5yteceyh(we7gPhwgrtNwU&*zU9-9xPkk@;Z8OTXC`jKESob&`L{mj_RD& zd93rEJTCth#T+TuJXafyt2xvGs=aV+uz}vo--NNKj7IPBmt1C#@~2>)uk0NtT;&hA zoIJ~&FgtLrwj1`p>Y48>tSa41OfZ98n(gw6gSE6QzX>+Z5*m37_D$npt#TSd&#N^! ziSk}Xyq#;tOGn1rT*b>T^g&dNG`q*h@A4mfiCv6sjN`WqTcqPRoT_#FhEug4z+NXv zz*N4A-omo&S4J3?Sn|;Gu>3hV=`0B-U+ywn%I|@Bk=MY!_NyJYd-S#kq9bO61&fTZ zx$rZvm-2DF?qfB1D+`fhNz9$qo^ClYX{INsL5S#}VHG9`xEBiOyM$jcB-o;ba)3N@9x zkJ<21{yKK}QC803OztLh1^kQ0u#dxcHn}6w7N^eSLU@7zzR}o4v<8C$2hZeE5)ne^ zg`qqV;m($jVEuIM<8X50IZww_JKGypJ2{nTW7n(Q2UVUdgaqgw138te zKaQbmqZRsTGg{)X8-90}kU>PKd@uZ{6SM5j0`Eeentd5g#GiW;ovPZZBOzOe|E6J8 z3F@x(vXaw?Frlo3r9VOQZ;42G8rIfsw<>>3s-oGVVO0y2$KA>wE%ksn^n`HbvzZ(& zoiIuZf)CWs#rDyOq(G{3vGi?guDV5Tr1T~!de)bBh1ob6OV0_~_Aim|<-A*8os8{^ z9KgA3m47%HE7|cshm)}{11Fx0oyC)}*Q%4TU%~B+$}ES@3%!oBWgFRoI4W)}Dmj5< zyq!vO=HbZ{lst}!V$~U0X0=6~k^K}VuFd&6OK8LkS1tyX4~DoH>{c}jPRf#>sN2x( zWE5w&2?z2146O4y?;Li{nN(HY00`6wh0|y7E6N^Yp6G|YA0rcR(ExsVe3I_yaf*s8 zds7F_*iP21R4yfjDrcUeq__DhvX$^WZ;*3?ve~7dx9#>kN{y=Aao+X`ctw8U44z79 z15{bnY7vN+Fjh>09Hj#0C;D3?w;5~9z z$@_>fWMRn%&~}%+gJ0yNC~pcs$Y=!cFKNm6cjMYIi!D%R$eC@p0K|XGFm=XuSk*%n z&A>-m8s}?OY2|laXKPLJjN-HuIJ?f)D(;!Wp(FukYn9L$XKU%flnS1l<=I*yU1mRp zqlYj^RXMpItflno2Wuy@cILraT`*h+YspjnU~SYXb*fgO2tMFK2{_>>I(}`FAzJmF z`yHVnwsEqIR!;T1%Ok{EVU1DeW$WWK6&9I)4ptI)=v+ltRrNiAd0Aa3^{TI;evAY2 zEnd9FUSH!YxB^#yVpo;!hI*d- zAn){6JXp~XtZendcV?R(u0T6iag}@Pveq&}3;YEcy_q$FROf7u$qkGPr&9U**g^c3 zDS1I1OJ7=R1keSH=WM&{MsGyEnH;WYr8jNp!G?^`UO!zk@~?1|6rO-3d2F-e$n12d z;xtae!v^OpWf=_#S@k$CQ&zH&AJ|Q$^}(?pRkPdshALZIce#PCH853S6q*GpX~u;s z8|+V2bSx@UL{_xc1E72}I}M_6a)SMM#RoCxdV?q|HtR%Pz_l+=)g04<2viup!j;u!eC`Szvw3P@mzT&{eHqHNm^d}KwMAnqAxsbyArgyGk~i5h#5=E#_~Jox<6w5 zh?C#APIt`lidNc6tk~<^(4K=4$muAXn_oFSkB#ciF~_xfK!)3`v{#vgK4R3L%T^Bw zY4NLJjU6MU;k;D7aZ!w)@9ywG=1xUZe0Z0IH-{6fSZO=MeT!0XYq5uM@shzJf9(nv zLfi$5$BRv_T%mE)Zs2OgV5;JCbF^jbxW%B$|4r<=Ejn&BV#Uq`x3MPoX>yR4 zY3q@Nde^c(PWvD;CgK3)+{$?&xH@#!6;9mJIp{R5lx$*LBiaNk9CRA5vkb=mBsE1~ z+cPX|ZsnYV>ByrBOVLp8^RpT|u}j-Gz0{+qo@Yg4UW1PS8Au3^B$ykqQj=N875j0) zDwYs@#$jg{4`LdxOYu=0W(hAEwY3PH7+?$Ne>LB=@x?YAecuue0OOFEDxsCy%cwPQ z?mcu|bNki0r+WSyn_zd7`@1)o@2_~9bo+0tm!RzgKh?!Ib9rEX;q&+``qg7zCrlF0 zvGjTmt3m$vZ65`GjU1cRK<457ChjsS*$Us>B^3~+g5+EJ-{OHy-VTOGV*bVHXmyMe z@8vhatWCf^1O19ecVsw&6b5H*TjEai1f+zhvpj01=3g=Zo+@5b6&N?$t8F?cXdmc> zwOW|5B*$$w&rCv-vk{x4;wzn{dw>+q|A%*CPq(feBa9J+7D{%~FU*0!CE)BgUWf9Vgu&lT-+cct_?^*UcrYB=j1I!13^4h^?e3MB^WPQk8&Qy4sE!3gvZ){+g8Cah{^j# z`?;IS$AH6G4P}d25TTX4C(w2BTis_k4=2=dk>6t0l6aEd8HL-f)eY5w+AL+qm#0v1 z;{95CAiZ*15wG;KA-;+$o%@6L;B^AeaTSelyWd{_hqjhQ3Xket1L}NzMNkrDOuOw( z*77{6AKmr8Gyrw&RxMi=g`;NcFyzc5yn&fT~VpGrFggz2Q3a@ z8)RH)eXPK5^S0u8Z2Q@<)+}#qI$MrHfR|-)5)(6RzF7^l*~y^I$pg(Tg3l*tZ(~HK z-m-Lh_rp|z{fDB?XiKWNUt7hG3IOGJEU%J3jVm$(HEv%p=IWBCf&vsD=3pYO631-| zVV~R5vIZq;?Is{yEZ5?Wx-ntLY$LICGjyh>I(K+(1t|*isluz< zbJ4g1($))Ch4j$w+5$Iqa{=%1$Hn7GS}SF+4cC?wns@*Ob+ljW_Y zJPt}%GDbvpv@|4CvESYdPD7QGo~mt2X^2zZxB%o|4(5Rq++>MuirUcgiioiD{)_(r ziJ`sxi+_P$*86pA(i(9K>x}W-Ca8`+p-#nx+-O-ndYvH!GqXHz#;&s}Ep9W=@Y<1} zU5P{up4{3SX>UwKO5lR63FCedU&y-gpbpo@9wjZqy@!=H0s5i!?5DWnMh@{W z8V)VX8}~So8=4<*R6>bS9UN4~ln29vXzj?&xt?c_l@0U6k*>uhh;fh}$89hsWO*BL z?0HPZ!O6@Nri$avey@}^EuHR7_Vm=_{1`4cU_5rLwmSon0N?Wz8HF#F;)5B`xWWa6 zg%#>J(1QvUr#Mb88q>UZw8vHUtJS5UxDwv2itUJ-i}kxT@e-l6&broH)wXzy?_s1| zv@^g}cL(CoOs`L{zKo3X#ii5OSgOUM4~M&gC7~I7xZR2@AI*V|3xHuy;oW+X|-A0wRqP9EA-Va~c+Uhn>pzcJ4smW*4dvNRh8r|CnMpQS18< zQ=e6@@Gy$|0VlVVV*S8ZJA}x2zkU@+H&M73DD?V7U6-sSBHg0?KK4dhUxf@N7Dds1 zi9%noIqdhs>2r{5!Bh}!)OcDi3?j_S;r)vv!AK?(h8lrOCfSHue~vc#q0*?zj@xl0 z8R*%s(LTDJv@&|_7>wqY5j+%Quk2_)jU!0@#k^M(N#P`|#z^;IJXD<&1qT8`O$ms( zI-&agWzF?184dMbx7Yqf;kchX$$#T)Z>*{4lPZVK&Hr6kG5Ae@?tp#+bc}{=ovC{Y z+tt)@(rJ(*#@pd+SQghaXi-zgm-8L_JQg;s=u*>mfn%R%h2}`{F05!=?c!+fDBZmDVx_ohbmERA=PuRC z`j>1Is2UCxeNnjWyZW}Yih5;_zyeKWZau(oG_|>7E%>>GO>KR^2v`*+!KyGBR=Md` z{NyCyXU47gnU@ELDX=TdgSAl`yt1@q8D6(6^p3$43YKKNYz=s@XXvmP``K}~;GwYB zP!vS)!Qg!(o!dskSL8l-91?=u?19lcr4(m?E(x?TV9 zD)N=8hAf=XOzSwCnQ}{Mh&C-1sO+jh%OA)p>8)}2RJ~pQlBFODDK&o;-FK0W7X|yG z$Lx>j8ztfO+nA?S5!H^&GOKItT^OC}Ey)@eGa*#xu-|7|v=`u>)q$y=MW>ow))EY1 zxfoUseB{DQV8kKAW zaC={)h)L-0Yj_&(YXrZI2R88HkMY391IHElsJknM8}C!F6~u83HUXFSz($KiZtohY z6`ZWD*>Gha(w&F_TVTai+%i>#>fO(Yd;MWGu}W*_N;ecQZ8UPRt)jkjMH`l$_U zwZm9OZ6m;?P^t=D4`Imj$nqSk9U22<^+WZxc5uTbOmAb`i{2CU#*RHd{Y-BNa!lRS zF>!}FKbeT3R-?VPcx>6lI7FW*8{pgf>=ziLB%b}k7$#xJ zk)G#OmWKZ(CmVt z`DtkadSaSwY)ZCWnTNTV!?UwR3;MZGrlx&#Ayo$K8f>Nj)X005N7OKe-|uc8rg5Wm1-+Qa1T?o#GRD znF&8>z}X_YiXO1dMr_yMD;@FW;G5haPPCyEke?}l3ptgeaHRAwlrAs&Z*|B>g;P9Y zAYR>7?lQ;C{J!3m-#LhZ97uyda%&1`wIGE&gq^IYPB+rXhOSdfUNBzWWg2{%*qbtI zrqk1vPTg|FSZd}s<2Lyn0enZ}oB2ZC>n=Kn$t&X5{of)Nwdn|P(P83b@~tikQfu-1 zdekju`C@LTs8XTR!_rk(92O;Wlv@**>ioN2^mD~$;&FNTHxp^qo#KUg=$>BRzmXnG z63gh{Nw1G9y*$XIp7%2lS3csjgMZoJ+Jz;@EQ{3~)8+7b(c2Z@S}IM&z&xPR>F8NY zj))CoVm+YOEv%;~GncOKd%DRF@w+gdY2Y$zIZ~dX_@AQcF1-$z`ls>#9eJslQmW#2 ziopew{_1i_?z2{~p+03jngRc8c_x7xlec=!OwO2jq{kEDmOqy-4~$ipLCOW&Y_{Hd z%x1jR<^1yFO#u~Cz;83g0vEPszK5Wf!F1R@rz2l-6>nH;ur1BQH_fcY**dYM(>cS| zL$BTSFqE!pUQn*shgy3ScZfhA2OU%V=sa4H7W!=;<6aTyw?fCR2O*$eLHi?C(Tw)c z1EE)bWEHKTKLNcp^bqK6pl|)qD%wKNgMKybp|^vc@qrbXLP2j2E#R;fa*TfNdsejA z=)Xe04f=ZM=w^!Np>w8WG4yy$O6J2*#fUC5Dns3D$d8aWG8+ zr^#)mB0!_wzv_-QgfB}C1m76*6y=NUFKcQ&G~=48xH%KQgH`z2DMTTh-hXSa;!da$ z+W=-ryc2wbp9*G=g z-NAkr`;%0|K@v5`Be6v4fnptm9g)6?Xe?uI} z-^nPAc-R;2trmgEaMmoeqPuon_Oq2eY z4&!01%@&09iH`eFd}X?Qj|R5%QQ&5mg!EGp1N(bs$kFiS{v8j#vKMN`n+W%s-%Oi0 zF+KLfmAZF1VQNogv>hv&|4nMX{v<QYtK;`8J?G)sq!!- zf3nKIv`P8)lziP!N{%uj9f9}-;$@ryaWNi1j)+g0Ef5Fvd@(d_l88;o5s4`_dwwb$ zpoZpWD%0o;dwzjomz0*8lV*I3w(F+&+#KbaXgBl}8^a2O6YwwKPr#3WyYTSz^7g4$ z-?u@-Mt+T(_y;t-qFM76SGEjn)w)gFtFCTmY2Tq^P^Zpax?Xc_aJTEa_Xz2EeXrhq z`u6J|Iv{M|put0|;WtD?MhzVn9TR(FT>S762_r`(jvg~MY25gmZk{mlmgHMgCQVLF zOP`XFnKd^!x&wea6gLvv0e7&fIy0B{l`PUWgvHn-nzV5GRv@n(Apdm6?Oo z7kE_vd3s#lpBer_>i??EpOpZn)Es|iiu|fJ2jDuhzoKgUzdUNIeFcbWvu;vX&I`x< z1x3Xrcig$Kbdhs$*^;}KF1!1ld+)pdfd?OY_>o5+dwls5E1q1rYV}i3uX*O#=bnGz z#ec0`_tMMjH*DPW%B!!v{>J7vw`|?EeaFtXcJ1D?ci-FZyu1Iv!9(vIe*c3HKRWX9 z(N8}8?DJ#CzxeX26JLMxtycbB#rHq_c(U@RpHH3s<;>Z0zy9`n)gQRw^`ABb7q93q zt0DNGrvHCB{eR5=KWm6D?yef*|7rStFxGvDoIzQ@hX*EYOQw~W82e-JC0$ccGTjq?kgxoxPp0d-w^=L#qX4+Gxq}`A^D+%-D ziE3uN_MCWE^lCYB$i_IGs%wrtJ6ku3dIJ9*7y}BgdZM0ZfOk|>ln+&(5R7f;+0s8@ zTzy~BFC-)>0q4&w7DcY_tr@pM2{9{g7L+f1i2B0U7n(2r_^DJqhvN>%dGlCY( zv!|#r0moDNXl`hVEhXC$m7kxRZ^_KD#HHACCTF03pO29i)zFm8?6g#iE!UEpmzHBm zo0T@%ZcCYz%?|#U92)4B4jHhd$W$bo;j7(|4|RC}Ue$im^2yl?B2n0LE$G78 z+sWWQXWck@=4=e-05}Hc_+DQ*F=uMJh)yU41)>W^{}#h6#N^x@TYhe~B_ky#H9IZe zQk%Z&Je`teLzZURER%Cn(=55^mOOKZmSZ#h*Tgq1e_Cb^QyT zB{{cpsJLwKu@3%&U5YNfJiGI|MqZ_+sVsMQx7Z8Fj4`8!Ue~{dUzK8)j$JG%2KJN` z_mFkTL6`Pjz)CDl&SazvT`T+Rz`bS;)=1$38A-{XVrS+SSh}XoK+TkyZb_S#XPbS^ zrPG-NkuoZ+6t0`5AjeICrBGU?rA^Dl6h#N4G<9&5D@z@Am|LMx{Y_Qu87`ivv0Ueh!fuy#f2HT8-FNh=>At5{`V0H$?gDCOZ>uoztc}M#{cFZk+r8d^OS?yh)pS2t z3y(DeYr5kyEyYjb(`Yc}rvX$Zezn7gM$~k#jbBl8P4^>i@gIZRSGC-0=hHi}7Y`4# z#DchL-Ys)8yQf2ck;AL~CAgVu<9j;6O7AXu-Q#&=lvTV4aCfgAzR$Rt?zoB9#ZQK- zkG&mKCO>P3cP7_#ug!03YEAdr@$XC1-I0X2A7(S1+VP}LuNhBm_q7Ez-D{`Q^6r}M zwZk8J;nMEUzF5<}cKiXGt>XLB!lhEr!FUjL0rn)B>OB+mY_{z<4S`?`y@OmFDk8UG4?BDWEO3Abh&CO3llb)TX#}+k9Tq(i}3NokU z7%_~_txihZ;sO5{0vh8Nj8KjdZxRxLKBo~xh~}QKjn7TBXQvIt@L+h#G!P_KdyQn@ z-3^QCsHb?v+p}$%H_W!Bjm}NVOihc(z&gQX#)H9VzRjM;U`e8cHlxuu(AgsHGw5~2 z6pm7$hf39n#wsSiIaXBzBlJ}GOc)bOY{ zb0nB!;I9vuqcBeH<(ZI{Hnp0=oxO~Fipj~d+hQ;V!2UtcgxmtRT$o7wTtS|~dQSny z46}_<_?X0~QD%BhLB@?jw`hjSnnZN?lSGt9qAef)F~D|!@&Qpq2c5Z=7#)`so)Du3 zu7Tv7p07GJaNoz(C1!qZW)8Zm;(%v#T1uYU<|f`&W_?TBk0Ofh5g2}DPEN^A1baBe zAVR(3(o$xmU0|Q7=VCsjlUzqihrP$-WGF#SjhZzXOI8SvOktv8HFdkU+1(bRfP8Z+8*jN$JUWA;{Fqei zo_7z8>^4Jp@<~E=DnSzOD(dvw2fG(lJPD*!WY8QQksw4r=IMy2c$ym`Pk?1+s;W6K zOC)lMi!N6>3&l&`iP>pbN4(A}E+Rf94-yJ#0R3CNMgcPxNFmZ0ibhiaMT)8sqpF9D zdbollag>OE%+TjfHaG)W@ufM(4WfC-Jmik0qq#V|0nZ@#HjxF2wL2#xgo{ z@>B!=ZEoB&-M&#g;Xlb^Xtuo|gAv_ePfy2~13PiSaRZCo{Gn(i?D-INy)>j^f9$lpl>9Uo zkCVt(H(rfR$vCb#k;t(BCCb_bnGQu=U%4<4 z4={8$jeBD}+`elY=}!2M`VR*#mjjd+zFNDk)fSBOb@{pyx+zPXTcFt+5D4%AGzMUv zT+QcW23*bObAH>;u%q4(KsT;Im~M=hVX6BA7%#<~c1;0S7(n~>0NVEf@SCNC-<-AO zH>w~NW)yVVCmK3SKI0z`VA|OLx=#aG0H`X|9xycE3V+f;{|*3go^R+2pmXn85rFQ+ z0LHTnz@>u6jqerEiO(tk(|Z~~f13cLX+MDO9|7nd2&Rxf1oDN-L)Too@#*?D-7jW; zG55df-;Br9&G7$|I)?8`t$)BOE(iX-xCUHQ>FHW7s{Xxr{ddKF@kMDJgNrYU{&!*c z?@#BVB4D)G7Zm}^zaO{%#uWaSi$a1A`dgmfzN^r{-MV1*g7yaMo;s!*R;KME03z$<{YfHi<801pC|0vv#Bz^#DM0Q!#x34YvbC#Np%_tUx3E1k=Ng1Z#>dI-U#dN zUtu3Sl!G0o|D)>9_PTl2QeK8K*+ssN0v}Cf@wum!X{OG;qr2*k4?+m?CIPo>{23G) zsyFr&mj zcNfEk4HHR8Ng^{dQ`qfx;h;x{1OE>3;DZl}r=NaWy!z^^V(Zqe;-il~66epKXPvKn zDbM(Tym3H#*E8Kf_aHpT&0-(m$|EeIpUZe~jrXbr$L4=8*$jB&8kkt2*%O zF?^k&2ZjHFszvWqy>o_f@caCz2{G_cm5uoE?eoUo=GQ#>&H<2+PX72Fd0~DtG63ov zzyBz?zG>V$2jF8dyd(Z+9`4y+_n^Mdy?bC)hv(l>e(8>I--i#B=MY@YB>?~ZD@VU` zuIhkeE8W2#`NxdkDimtS@EAyXu31~s~a1S=*}vBg&QMhdfOe8{9ssZa{!Gq$ z_wJ1|N;ma^Me!Y_M4?p}+eEs#;E;-NRw9Q(~8?D_` zpOSui-tBT@`W8{R<>Z$1skx#MS(Ls77yoamNQNX#E&TD98Tvha?%uD* zj2W{fJ+Ak2(37XeP9GuVhGaRfw9A&8$0UnFxpK>fF^uokC5*pN${)_2T`QCn-;Vn* z+O?Ab_1j6|WdLXg>*@y65B(GTbv}_tR>db&OqWodI&~7k!NJ&v8X^V_8YIHQaaTi3 zjN;wMks}3eyu(FoFEDIzN?OH7+KP2}g}Tpj93b>W&z~K_o@5EQX zN>N!^DbAift7L_2_3VKbJOOi~5E|d5m`_{_;Xz?;6tm@Y@t|BL zHpq3@H+9IYPepTt?~s7LU7iqwmI@KK7W0@0Kcy0V0ohb6Lik4!{zZh}itz6t{0X=4 zoe|!eAjD01LfDpKPxo3Oo6#Wm=|t|Io|f@GX5mGz>?I-B?Gf_OF}Ls*gzt;+aR@&N;b$TI5`5)>LnJ&fIWm5dQPKw_T zNmuwb2!9>I4?*}*2tOI&XCVASgny{16i@Y(V*N-dwojMhz%nU5T_;7wq3ZCy2u}{Q zK=?KY-yY#RBYZc6AKX-maXqD&K2nOs(-GG)DYmSW;^?93@HMN3WL>#iMnv2YenWVq zvWmO5Yp0+N9Xbzlc?iEDHYO%AGB#q!knqUl>$-O7(kW=zu#U-whsYQ><39$5$jId2 zYv^IvFpJ?H8y^uBI|R{3MT8HzAw0PoJ#^~Sp}pZA6K@TV4UdS7KzL%-y<2ebU;{^b ziH(mn)zCJ9Et9*$17is4(B9G!NX9V!*w~mMvB_68YZVB0c<5?)=$wr3L&6zBL~L@K zX3bjZ?h41C4i+F8S@bK%s{Cz@(B*!Pm$3(^JSfUEiRZK+h8zOpmdo&zw zIFMH{k*KZ=_vFwX0S)STcr=X$hD0$wb_fKi+C98qpO9V+ynL^Sfn&rCkmcUJBOHU;-Qq_^ z$Ak~485-rIri!l-IqFxrweXz3W5{uIF17+3I{cIVTp{A94YIDjoI?z$)t|d`KgI0N zjT$vd^+)EV4RiH9o}M*WEX<4&59UXR^$ViJ+K1aBFOP{Yzx-01IB`N$R8)xXzyDtR{L@L*H~8h3U&J4O{2`W|m0}(G3Wt7ot7m(( zM?=>S4IRqm(#dG(W}~6I3k}^~velNlwbG0-7>8F5w|LLdvuk5GrJ{PLi zty`lI3DHW;mbMP;)T2j_YiVz3-8!&!`)k_;G-+}*`opa{bnVosOQ#-91N^UPjPKU% zJ9g;;zfA+$^$6|}EZTOswrh{88un-k4^0|3zPfGuPF;I6YS{H^!@^?edTkG1U%##h zbIlb^8n+H?d2NqIzP=3``1tr;d^PIWut9@hj04(tYU9-wt_X4!zL9=#+vZ(52KVR@+y(d}{cD4RgMx$G2P^v8 zLUBd0eS7sO3jJ{}OnrDnUs-_%^tu%7O+WOHaH1WKFG552T-*xq5ElIYBjt(E=&=j* z*j?ZQGgJQWP#2^AQfU+4Mt~OW+O=yBoe%`*+@L`NjDT=VM{x>WXlyG6GzOR)hw;Be zkw1(Vqnpc88I;<@80di`b6-+fde7=%zd?mhw)%^rSk2s@Q-=ThWU!lojZ4gPF``E`f^m@%{SjX zq%Raax4}H(T^L8pEnBw8Z@>LkqCJ(Eci{w;Ja+7u#Q0m%9W+Rc!9UY9tsJy|@!osy zJ-%(*w!#~3xS6@T=nB+_yL@i&2dHGz3z0`VuWsGDYf6HGd&6CV?RvoryDE=@4)O#ScR zzhlRa#mL7Fkcnc_vVHq@MFZsn^G%X@fO#e*FT{~$0%cg~hYuf?1eOiv2X&SYmK}z9 z`|Y5|NZ;-?@T!+Fb^+bCRXut+$it7qA9cMnf%uzX=7p*MopQo*KzT{} zT*_fMbQkdf_)i*;2Yug`@_Nva^AXlNsxaSu2(M(msl=-Ca`DGJDP?(auU@@6Ko*Kg z2l7GDW70vLyrdkF|8>zpzLV$7BgzfspY<-rYzm=*8l4@kg5>&HA76kM>9s%>1BE{7pHh{4+l&SCkEt7M5|!8Red3!_-+W zs1m=BayV#+h5Qc%ze&R&9PSH!SEnHaWdZZ#vg;P*o%QJDs8+-ub@4LH@%KSKEMYlg z-BK4Fly~<$G3AOn`A>JY3k=J8#$Am)fig-OK9e#AG(>@hwLiG~xTqNq8_0(*LQJb) zfBjXWov8t&!32|zy7I)N#hs4dfB#(`di626dQPl-c=`}|f38&_cGEXfj`>Q;k;kQs zCk-DPXduRb!|FK`J${-mX5u%UOqi@pj>GmBp)vrtY{zs6U*@?gh4~XF)8DKQ#5FBm1ojm zl;`fOlTfF2LY>qeG_-p~uUiq#{8}_9{5y2$;Hv-8#C!=#0I!1lmBPfS{<6Mxo|8|H`07jF-f z&w_^4pkakg$Dhw)+*$z|G`u(h8d#oLC&i*pGRre*Fv~M(Fv~ORq^_?k{Lj|LpXH40 z3;J0uAPpwero${N?sU*h8mKlEhRT=b43ID07ABvW1sY}ylD|}bCqF`QCJo7;0c&hB z$*nxIPGWfuM_FK<)E{*c+h?;*>aqn-g6R0yu9sR7fAsGc_vzE81Ntb%q=WS$b;=Cs zV4sINX`vn4TC-n9H@3IzGf=(e2$dV>h01lH;RVpJ1~k0!P_8_E62fyCsvLAqL!R_! z`wSY)K1P=}^}x0H&-%iY{{aIAbY@WZtUdcc3Swh^27j@!9yjdQ|XERS&$Gi8B*&iZ}l>Z$I2g}W%;T6!ZelBQ0 zSzvi44J^;BlO967N*b1uhO8Upof)7ZJyOm~jZ!oaPxdj`KAU}vK4{;dE|7oL8EnJY zk07jBvqnDm+;fusQWMPlpnj=xf_Yha@bMJ6t*D=TV?Jm=S$G*V{L3iMtdmyQ2g}Fu z2g?WZhRAz!tn#kxaOngMcYuZkpkXd(psL-+xCLbc>Vo=*_(K+y{6nsbNy{B~+##QT z{&_`@NdxtZ(LtIhTMVlmdRcBS?kBf`hSx!ZD$k&Sb&^@0pFo}TX#NoSK%Q0J0~(fq zhDD&E1adS#Em9%MIw{<(kMT0fJ=6vDKk+x^KP)V)GjSlE*cIZUf%)Q2$E9fb1Lw$8# z%C~FDK0jMA&(Z!a!#;-CoSd9}#FgcdWnkXCd5Q*i`KnDz?L6T(>jajCKT{v=e9_g% zV0kVx%5$x7m+b!#f98F3bTsopKJ&~os_(@<4dw^rj2SarG+c^~OXUgiVV$&|b<&@e zXV5@Z2e0e0YDGR_ugx~pow7@pE;2beSz>R7WWTPUpg`JeHo185VpXm&<)i8y_J!)A zg}i6mz`BZkA@)Z(9-v}*KJb)XR{e(Y#>yG0eRl6-JYzjbs=loVXnd-ef`n+ zR`_3c-F1DjwI>HxAXlItt@7c?C!drfMvRcyTO`ZM$|Tm5< zP_76Z!;l8nr<6msh2+srXQX`ZY2trI?prmZx{rafz&-}cGwY;>XT=l!KOKKz?ynR} zq4Oy}ko9F4_xHg*nCDT3CEO(Ls*@-alHWJpc%$m~U_XqiKZ!eWWgd|R6KF?%Q}#H{ zVc(8?ckm*;mjEoVRQ5tG-K(nCW-@5P=TH4nnHjj}AUN0IMv zO3Z1=Yp%IQqHnEmAs*~+xC3c0!K8ya<0DX}%(z*%zWdlD`9_Hq?X%v;VEg>s+z7ep zo+Qc&zx;9-`F+{*57!LOe)zy9j{5931FeFAfo$8Nv< z_VdIO`$iS8Oq(=Zd+oInd)HJxu+5;%v+rZlL41inWuA0$Y(Smmfn|i{!ORotltK1Q z%(jPPZ;qcKFMnzNlVywY4_R7){bKL4oMJ^>&Y3eudje&N^Ub7*xN*)I zd+${FjfjYlR;yKFcZ$jfx-*PP19elb%ses63Cjm@H+9Mp#|)G!_5+!B=KKcm=W?4L z;Bu6zKSB3mECF-?4~tozH*emoXdr($Z;0}tXh=y(k#TWxO17{juka_Y$#c>|yjc!d z|InUe1j-Nl+FMW#SO!=o7%%H!;C>$Q6bA+dT9NNhV%|vu(p>Z5y3XY&)qgN$ko_N% zuf*rrv11A!y^9dg&$k;)^e;Ht=8n z`j-mFIRfxWL)|tJZR8x_z8!XV0%@*$T<-c!E8@%k59@!F$0ht`d81BTnD49?Ag8J? zfprp9x1y}8_KkH7<(o1`{8Orni((ii21?x9Q3XR-mEvdPI$RW z;cv<*b$1yi4JHsb(nT3!S-A7gJ0nKx!1g6lez zd&&}d&N75L<3Zr@cW`&%?=JtWBi!?&t~?=KoMWFbVS>cITqWNo&)s$UqbyO@Y0ox* zxSQ)E7$ZIQ4{%rbGYxb6i?(M8<&td>>0lWopICpeu48-To+sq(#*G{0!w)~KpCl1`{roCyWDoqE$SUF@_;+ z#Fw(Q1$8HBF>y!#^r^oo?>g4iziR#mlc;<0`rD; z%m>z;ET_yf)Wb4->GM+F|KJ+_?z;NzCvg6iEybrVgH`}AJWBjIRa&i z`mSBORJ}_cu)I>i=FAq{Lh*?(dEM>$}7#I}<> z=9rVbXFjkmVCDnAS?9B^Wj%&5g<97D&HvT7lMW^S?y^x^&dqPL+`8+Ob$-(i^MW!@ zK9feuI^^g)`Uo2GitQ@*Sp1c?iTM4Qx&j`!2jSldOoMwLxKHCBE9pSvvSsGXDp6ma zA2M_1ah%Vo)ODED_kW%4WyI%U=s5j?^q~HsZpLTE?}oR7(c^oop&$PzdZB@*)O8O- z_cHYQhVF0Z?F@a0p%)su)O8Q)+^1y$SOI@?pO!a*vVTjUJYTf0h@74ez$~~t18`UT z=88(~`9MThr(#-x7z>7=&5un>OB;#)ekA7n!(kr+V4u4Mz$K@Q`?GUW&cIqv^oRNy z8|Q+#rf@yhb|zqM_`{zSN|(Zcw6?`9{rxbun~pMlj`bP(oWG#HEk|4MJ?f>S=$}7{ zd9p0@fg|C+C!hoR8z-k=jf3lCoC~#L{+4UrTrYnAl#~ZBD~Iz)R6oN1#ne@(!&f5B zbF4pEM{>MG90=wbhSBFgk9zeK>fFPqH(z7KTr=f*BiFgOzQQ?it`B{1TFQf<{W8{i z+p(U;yg~}*p2iXf_MJHPWc$c^oox~8YW8K>PoW#lR5xR-iff)+YvURZ_b+gbit9UE z_vacL)y0U1eLIc;Fb2)Rcp;1JFWWVabD1vt(d3Cizv?^bG3viuFS`kIw_NYzTG{$a zch@@2T*Pvh2bDg@gX|*^hy#K3JApi5`}M=uN9EH+W7WC@=fkNc{ebnZ@AP%kMBu=6 zU+#h6+&7$dh(#$1(4cgY$P;Y?}!t z4(xM~C+u6lI4?r2^>KZM^N3VTo4~bKt{-#F&s`q5mxXIIT|INgOAvmwhxxkf}&*Yjf&eF;GC}Zw8P#(wDl1J>{S7m~GX(*4i*6G?Y zeXOUXU|wOYJ047%a!#F!>up>k;d&w0sWv{4_or)y-~A?K{xPh10tfErVVU5XEtN?> z`xST#FQ>4uFpGUqf{6#`O~?x>&aZQ=iEA8OE8{v7*EhIM#x*9cZE%f@>q~Pc>u~>q zOi3J+JYwC|T>EBx?&-4*Ah2&kaL0qVF#osRIYe^3>m}g8IZe)ubB%@a$aOBRuW+qV zt#KLY&rKc5q*R$O<#7=B0OiVm_RX*kk%DuVV_6q)ywCnQ=_W4Z0rmG*W~y~Vu2*sG zmTQJwdtPp=7qU!neS~X;4y;j`JYZt(bu`y*pvy6*-u1nMOX`b=O_0 z-ebMX_K)>HWr6&lEHZ84Ld7!ib3ta{r2{^3|enwHLsY$0uhG zl`q|wCo4{%b5bp9HP|qC@ZesU@10FLu#TBKY0{+KIKM5YPMxaefw4zY&0UydAM%|1 zApbeOqb!gg#J{#;obdM^WXrv+t&u)Iur4+RITS>kIBn#9a6a#LKb5@Raoh5dHW@F)_!+_Z_GO5c=c@|8e52uzRd5$;G(bp!XR zl6J}*^NYMD-K>)__wv+VOk1VTbWqo*`oAtb%sw#A97vpZRCC4b7jn*t^7s$ZR_T*o zv;CzkkzU40V4cS@$Ffa2DP!Ef!@Ut5D<%xByQ zLHYXu^I@fkgYzeUSMjV#z~2OxuRp8bE{a!GJDl0|aGlp}=KwDN9sqF}K!0YKb_hZk z0y`55VJ1jN9e1!eE;zSq5f@X~tHrqMRFKDr<;$0^#~RHU)KNcTU&!50J@r(LdIxI& zClN>Pv(G-O?BU0I<;s;Yn9qLmg%@5>`DwOc%xBc;AG#;T^)&2%<~-}~h~qfq?=;IH z$HtV`7cmA&M8A&v8i!zP^$5m+P@Bv+=$A6hKAhQ~G0PO|vSf^#IA-D;!h7h}^v1qU zD6@YoxPHL2Sk_oC5h$+=OO=A*3CF;j^e&mQGk$)7zL9kk%Mo#9-+{7y@Qqb+S+3qU zoNSEWIDX{#m16~tRYEXUdI)%Nt(9YP;!gQzy3fpuQGJ|K7=t|moEZnl;v9Q%E`sAe zjywBf+{rm=_Njn$pdVK|0*`M&9rak#4rRI!}X;XDGzY~&yFiER-3K~%5b5vBTD z9Di`^!Lbkfx*Tsj|7fXX92|#70}tjO?w(QW+^oaMhvPd}$c>IcYCOrlJNxY%H*oAU zH*J_oBk`D&YcQ_GId555Sg7`ha9xdgNqI5(uRP=D;I_4@-#-^)A48GY4dfr%1yyfx zO^*8q7}k`zb@K+x?~Wf<{Nxzw|Fw7R;Z+n@{02e=U!({Eg_KJOe1c%+u{%3EGdr7L zfPev_Am*!p2_XrA5Fj@|q^J?GJPak!D6s|%5;Z`?sKCcdL5hlqZKTM{d_2Slz5pYY zAXs|lLU?HV+5Xo**!%6>-8-){zd3Wx+`V_t@9~^)`==W9$LOB1^~Ua)v4m&W!fI@!;7Z*HI3!r*_H5G&DbxaH^y$8HJ2F=V^6P?#he>o z-dNTgP5#F5H`q0LIWWFbux{X~1-~Y&!<&B*-a+`V#7LXpEPsV4P`$?v( z`JQcjH~bcJRasNd!`D?OM8O_tdT|fH@jva2c}#w@w={Vp;6`RJ2zT_pSn46b z%a|YLLCl8Qav(x^Ce)l4$O4weK%jRx4+?O&UFyPaK^z(oJ}>~5BM}h&d;&x=p9su> zdBATu2#({!k^4;i^PF&ssV(?3=F1@PM1~+GKP=Bkc(gU*Qrn5}c?e?77?~!5Y&p{;2F*sgb{-jCm`e0BMJ1W5*d$J}79dG}({L;drnOc_4bqYAT$VRby)~6wS~Ls1@pjx}n=q zJW4?$(P%Ud6{1JbO!PEbjy9pQs0|*93-B!bGG32Az-6R?w4nFXa#~H-)1&kRJx}kn z23o0Bx;4$3WzDyqv?{ITR*kjAddJ#peP_kmkJ^M~vA5W*4t6}Jx0C9mJK0W=Q|v5u zmN`|<`_574dfu5kJexnp=keuyBj3Sm`5u0rw{|jHh1$#INr{FZ4 zg{R^gya6A^pW_J9oKSLr947T7mln`Dw3M!~M%wf2KiSXON9@n-2o}YnS$F2Lcs7{b z&&IG~wuDu)H`#maTNdk3M>%QEd}lJhPJO7pQPk_>b=7fN>9KmE{=F{JOLdiAqc=iq zGh>vu!66xKL1}mw{uFp4hYom44 zYGX&+_uHpg(23=hyqdqx-{nWRbUzkj<%{wa`L3*&u_{?@P>0lU^}TB9we&i8ojvMF zudg@HTj-U0-*^w`Og&Cd(o^+AdZvCtFVs)#3jLy9rPt~=^xJxu{zxCzpXrnOjE?l9 z{5F19zlV=}<|q3@{84@&9(Wl+_d4Ob@kBfy*W)(CA-zc@AaI zw#<{$WRWbE3*|F%sazq~%1xkIJLDc&C%=&2$uqL4>Z*iFQx)n3Rjc->I?%K0J>;$M z-uL!;Ep=O+rnB^P{ivR!=j*3*xn8E1>sRzT{kncj|5YE*hx8}yOqZ z>r?An%dtJXpIv9SXNXleuQ*>h4Nemt#oO~P{60R4=kmw-eEuANoBzm(+Xrym<#rHr z#d7gm*+Y7AupBG%0k;K!*W2=M@1UC;m9! z1O+LAzVt=8C?A!hBj_0V5hdb5_(8k?tj^zYQ__aqNwP>Td5SzsR+5j&SL8H7)TMo( zFHg`#v<7tK0FAJ2wr;UHTj#7!b`N`?U2fm%_|8X8yxY&cSLBK&lFD@|&nxtLYN=n< zn{}zb#Du*IC}>}!>1Y+&gQB3FWuyl5@JpCai8Rf+%g(T8*(H3p+fv>mAC?tzj(5s$ z@Xz^1@Yzr{fFe?&K zR^O>sUaaSM{k=Kfau3=s2^gBpz{8*k`%wS~aSxmTc4`i0WE1I3d(r3VX1bmJKnGaU zty1e5YnfGLRfCn@Z=JWIVP@TC_q361*&OCrygk=0u}keTK(GnBiQUHHn8i3#Yy_Ld z*0U{a8{5uyvRbyA?PGPUo72-lj^%K$<*S^bd@7&8XT$tk%vbTXd=qHPPO#;5{4;)n z{{Zu`In2jsH`UE|i`^*MRz}MNu-jdrSL4AxcQ<%aPu&2aMKira zUJKn_KcI8;unRW7#;i!-Pp!y9OVN6C2%ViI}HN@jsh=YdvF0lhvU z&&f85stIb5>g+x4Re4*zqh2#TNH5X}VBcV7Lcr-jU^pB`A_}rUMplr6AaM;?$uaOR zQM4yTG=)wC`%yz{X&r5#?VvZQR-siEUQ$Pd*A)Sn4OeSaqMc+X+l6kSm=3mfrkE{? zMTsaCWujbEh)PiY~7RSx4&t!mV2wN7nP0UfD1_~&??sFQTE wPSHbksvfS#=v?sF1-cMyei8V_+29u|jW%D~t|4#@folj{L*N<${}%}S3sXxjq5uE@ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/w64-arm.exe b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/w64-arm.exe new file mode 100644 index 0000000000000000000000000000000000000000..951d5817c9e6d81c94a173a0d9fead7f1f143331 GIT binary patch literal 168448 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/w64.exe b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/w64.exe new file mode 100644 index 0000000000000000000000000000000000000000..5763076d2878093971a0ef9870e1cde7f556b18b GIT binary patch literal 101888 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

      9-9LkKUfKc;4)T_?PK6kRc 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb41caefbfc72d8adf37b7dfeb3801ead0c84615 GIT binary patch literal 223 zcmYk0F$%&!5Jfj&A%X|7NTx_*A!1=EHeww%=4*y*c9+d25Nj{v6|6mltw*r4asz^X z_-~l`GcfzP#6s^%g8Cyep z+Q_^i+xyb!*rxH(0XZY~P97>a>YzJPoPSr+WKIox#5Tn8gmB81GS(hWOGU=QNzX<% bl((!3%0k9I%M#l^#*5k%t-^Bn?iDOP9~D1U literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ef2f647622d83fb65b748943d41d90e2a13b17a GIT binary patch literal 4820 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa85193fedfedb446d469fc3151c2862c3dcfd7a GIT binary patch literal 1402 zcmZuxPmdcl6t`z4Gf6tTn?+SAEJ7o3A%|oHrJ@IfP`k??396+nYRP3ZiQgvPc}UJ;-tRqsx3||O*#7u4<-axv z`O7I+8_to>up5SgkpW?pd4dio{(a#U{=lbhE)YS{7&Pi}Lxe>%h>GT*S?i%_741R0 z9!KI@5f5TYpaDMY!5(Y=7O)m;KMMx?%zH?>*A^dr12$*5&66=m(7EMGCPIz*C^=I) zF>0nqkT7nn=EIp=HU0MAh>zUw((1MSQ*HRDjKfARS`o+xiD3z!OpZC`Ob+=YvMn~eh<6(1%<6+ z&-~}!%3u2DbQLVgiyh}JHhs#_ShtL0$ZBVY* zFAfUDK+E3N;1O~cOu1xLXDrkZ8JuTDE;0CQ@!qM1GS_P?n@oDYoFld8NJrnQLUfU$ zgA!@L4cUf;``wm~FwRv_=60e(H#{`El*rbES4ml)4%rq;hDn*N*?l?z5IVq}+7(Xa zDN_aKipy?OJ3e*G30HB|!EU=PO7ymhcN0zvH@C$u&AB{NnVW5Z%gf)1ehZKI=UF;Y z1*EWmDF=}r!vzRcy2MVu&W9!~=XRoGue{Ye(*Q#bJ*!>j>GT5AFco~bF}c;hlNxTJ zw}}XD4~H=QSNyICf9L;0MjA^99(PPmU>HS;?&}iAOEl2X4Yb6Vi zw&zs`pGjlUp+4l%Y*cDB24jx2R=T>m0rJTf$HzGrz;LqzJ(CD7`-a;|W=dWQ1uo4` z0nIy)p(qczhOt$8-V3VE$id1$o>$Rm4X%n9UX%+{g&OQk%dzh+(X=-HW9;{_o1bv7 zb?ljc;jR3oe@0iqk~|+#Vm%goPgkuaec>-#m-P7|C5v5@f@N??SK$*P2LZ9IX;)7u zSw(2wx8c%btqZ!0o_SBmyM%?S=)34C`I<=YDRG)F$wP8=whUP~?U)~J^o-i}8GX@N z2Fvi$F%~UE6Cg;64Rtfa=lZEmi>*xW8^WIa>=UwQ=vx4<~%EMiA1o)o5vc;TsthlxXnl6 z3dT3eIr4pm91W~0L=Sn}E0G&qnKjfr=yi33!LNccw-X(@>2@=pIm z1B^KIt#%oz({oJMRPa&ba%b>fYPf}d6C_;9%QWNCA|1I%RoPUlN$abpTd14Z56XFU ztEpirY=G4<#!-KTGRG1a%x+S{w5EMsb%W%?DAhX)8` zkRe>AG@jA0noY3$jrP!^dbRf0KpkwWQM;O}ZoX`VZ^uy956czOUVGUGA0A-_|E0n@ zH*Wu*lf}2d@v_b&AK$I|7BxFph69JB{XS;*Ouz>eC36FZz;Amo-h4lZX&iV2KkEJc EFFv%sv;Y7A literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df23aa9faea8b766872f77750146ed4e823de921 GIT binary patch literal 2298 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 P?m^&Y8euPfCV&0|P3U8O literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9aa5c2af353da41c705a0f32d399c26d4b0e203a GIT binary patch literal 1438 zcmZWpUuz^q5bvIUyE~bLT#&JuCfe+iSX zT|$1-!jr>8;Vqo<6I?jqG$sA!P5TsOE44DaZ&Q6{r%vYf-OTHIMt0Ix*6z0{0dm;^ ze?j{z5P*v_02_i$!rkv(?(x=FPJflt4@tNEJAQ>6bZOnbQ>vuMmcnu4fT^#1)s$|N75$qiBEIf&>yQR7IKi4ii8G9E$Kt(`p1pl%HsH}ysa zD(10@Bc2EtszOX_S3y?t+7qBEkq;dWW;t5;UctGGQ~rv}+~Rc7k%1y#(AhVNURvCm z+Y5^lWpjH@z9ZcE!Tyq7ICFaG%$)`O_G3zB8*_T<&TX`IA6OUe5y^KPqP&SO?o)Ds zyv<%fUoGy<$=q8YuSet_`EmV-yh=VGhvX-eHB+2kc%OU6sLQQmJZrRimp_mG+UV~g zCHgVmT63b2-7~hwM0HO6%N0c7E-cOZ?15pJ6*;i?a;cEsL-@2pUNXoPt2Dt(je*_W zyUT9fdYKJ$LRcw^A=2CPeTL)EkS19ft1(jrJ0An2EIDDa8tQ8&RhmwXrRuRe=uMm_ z5?*2r*^(XL*L%j(!EEz3NR{NVE&=u^Kd-YbIcthR0|K6?c#r}+DFmBcV=tCdM0AR| z?5QG4yIm^Un5fzQZk*?ZT7rILwI$gAhH)jqgw}+#tPaKtkW3qmn*e+3xeTJq<8AeI2Kw$HRMG3-WQHCbEuz_oxR}mme*~5kS0kvpg zS=9f>vGv};Y5nax>w1Uh)?beI3SFZCZBy|KT6EXymS!B2BQ{X3JvGI@!AMvr^lu=* zUW8%rLg43@6%*;)N@3%ThWBU9wW~OKl}b|Cdz`Z-iJcLs{U*2V$Z#A*LPM`@se}Pz l_$tQLd|<6xf#o|Gl6ky8{{{Tcq6`24 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4190dba98b4bb5ef23e1359966df21fceaa8a6a6 GIT binary patch literal 2695 zcmZ{mOOG5y5`Z)7-PJYYvBw4sEGi5J8Zq4xW*3-6Xa%;pjEulSW*b4Nx-+}GJoRX0 zX3sFKnv)UYu!tXkEzM|8+|eF5^DFX-IPepAtd@wZnx4U0s?=Heh{#9e7ZLrz+FD59 z`R@Bc_G6cjAFy%u383*0_{sl3!3d){88%-!w4iO}cHs=2!X3I4`|aE-{Go5^P97AU zVaL?nJS@7yZV?S5N;o-M||2u;N7{(sxr-cR7cgEmx-Kb zWyuA!<09SViR5ZAS5=kEWW+!v*`INqC#jf9nC09!&GSSqMsrb(xs-ZyQi&o}$+#+t zv}BE(t)sJTy?)ZF7HYmw+7qydD909Vu6-Q%+VFcDe)1D26sgHQdT23PTVI2 zP34u^X7;P(&@Nw7^vF6U%D(IDI$|689n&Az^vGq_F_k+tWft^N7;|g-HFOQ! zeE#9yj~0^0TU&cvj4G(9DweZq|KsJxLxN$?6FwYVd$=+e`uvou84u3O?XVzMJuU za2;o@=6I;VPCAl<`GJ~M<<|V#)3Elm!5%O726;AWMXvQ<8^}!YtvL** zQx2-xe2`?N;sW>@09+zP*5qKc$Z|GlILi4n9Ut`P2l~=O;=m=-!~o*s~L~yshS}bST{xj5={e-SBX>{#KwX#oD^87#6f@g_>Qq-J5Wtp zMZ|Ww8IJ`|6=!i-#gi=OvLD|1iNc|mudjxRN&t$pw2XmSBh?RYf~>J@{K9xvRTAKb z5g}D?I~EWjiNHyEf%ar2V19EQ0&h(F`>7~FUfVE*wkoL|JeGFuR#^#LKZ4qiV(}Oj zr>NS6oEkEl0&iYF0o*4ID2X|F8-t9YKtSRgq!UnwMo0&BtjOBXo)r+GC>^Q1BXQR=m3rIV(M#%B#k2+zrbr$xF9NZ&#QKr+D8 zE~8&MhaR&G5;lY@A5ik|(XT!~3=kyd07@1m%ZmV#2LZ{d1LlEF=ME|V<`CuXhN7kf z-#R$*n2+FrCZvWMz@g-yEc!y0g`y}HA~FWtj# z-MvGsmHcg^_o312b(jBo3!Tzj{-Q(|23%TCju+%6P%%j2ts^E>HVvC%=iE&ZB k3`GEWYQu`aBpX)fcxaUWMwiT&V(I-7J=qw_YuZ2m4mG&rr2qf` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/build_tracker.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/build_tracker.py new file mode 100644 index 000000000..0ed8dd235 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/build_tracker.py @@ -0,0 +1,138 @@ +import contextlib +import hashlib +import logging +import os +from types import TracebackType +from typing import Dict, Generator, Optional, Type, Union + +from pip._internal.req.req_install import InstallRequirement +from pip._internal.utils.temp_dir import TempDirectory + +logger = logging.getLogger(__name__) + + +@contextlib.contextmanager +def update_env_context_manager(**changes: str) -> Generator[None, None, None]: + target = os.environ + + # Save values from the target and change them. + non_existent_marker = object() + saved_values: Dict[str, Union[object, str]] = {} + for name, new_value in changes.items(): + try: + saved_values[name] = target[name] + except KeyError: + saved_values[name] = non_existent_marker + target[name] = new_value + + try: + yield + finally: + # Restore original values in the target. + for name, original_value in saved_values.items(): + if original_value is non_existent_marker: + del target[name] + else: + assert isinstance(original_value, str) # for mypy + target[name] = original_value + + +@contextlib.contextmanager +def get_build_tracker() -> Generator["BuildTracker", None, None]: + root = os.environ.get("PIP_BUILD_TRACKER") + with contextlib.ExitStack() as ctx: + if root is None: + root = ctx.enter_context(TempDirectory(kind="build-tracker")).path + ctx.enter_context(update_env_context_manager(PIP_BUILD_TRACKER=root)) + logger.debug("Initialized build tracking at %s", root) + + with BuildTracker(root) as tracker: + yield tracker + + +class TrackerId(str): + """Uniquely identifying string provided to the build tracker.""" + + +class BuildTracker: + """Ensure that an sdist cannot request itself as a setup requirement. + + When an sdist is prepared, it identifies its setup requirements in the + context of ``BuildTracker.track()``. If a requirement shows up recursively, this + raises an exception. + + This stops fork bombs embedded in malicious packages.""" + + def __init__(self, root: str) -> None: + self._root = root + self._entries: Dict[TrackerId, InstallRequirement] = {} + logger.debug("Created build tracker: %s", self._root) + + def __enter__(self) -> "BuildTracker": + logger.debug("Entered build tracker: %s", self._root) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.cleanup() + + def _entry_path(self, key: TrackerId) -> str: + hashed = hashlib.sha224(key.encode()).hexdigest() + return os.path.join(self._root, hashed) + + def add(self, req: InstallRequirement, key: TrackerId) -> None: + """Add an InstallRequirement to build tracking.""" + + # Get the file to write information about this requirement. + entry_path = self._entry_path(key) + + # Try reading from the file. If it exists and can be read from, a build + # is already in progress, so a LookupError is raised. + try: + with open(entry_path) as fp: + contents = fp.read() + except FileNotFoundError: + pass + else: + message = f"{req.link} is already being built: {contents}" + raise LookupError(message) + + # If we're here, req should really not be building already. + assert key not in self._entries + + # Start tracking this requirement. + with open(entry_path, "w", encoding="utf-8") as fp: + fp.write(str(req)) + self._entries[key] = req + + logger.debug("Added %s to build tracker %r", req, self._root) + + def remove(self, req: InstallRequirement, key: TrackerId) -> None: + """Remove an InstallRequirement from build tracking.""" + + # Delete the created file and the corresponding entry. + os.unlink(self._entry_path(key)) + del self._entries[key] + + logger.debug("Removed %s from build tracker %r", req, self._root) + + def cleanup(self) -> None: + for key, req in list(self._entries.items()): + self.remove(req, key) + + logger.debug("Removed build tracker: %r", self._root) + + @contextlib.contextmanager + def track(self, req: InstallRequirement, key: str) -> Generator[None, None, None]: + """Ensure that `key` cannot install itself as a setup requirement. + + :raises LookupError: If `key` was already provided in a parent invocation of + the context introduced by this method.""" + tracker_id = TrackerId(key) + self.add(req, tracker_id) + yield + self.remove(req, tracker_id) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata.py new file mode 100644 index 000000000..c66ac354d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata.py @@ -0,0 +1,39 @@ +"""Metadata generation logic for source distributions. +""" + +import os + +from pip._vendor.pyproject_hooks import BuildBackendHookCaller + +from pip._internal.build_env import BuildEnvironment +from pip._internal.exceptions import ( + InstallationSubprocessError, + MetadataGenerationFailed, +) +from pip._internal.utils.subprocess import runner_with_spinner_message +from pip._internal.utils.temp_dir import TempDirectory + + +def generate_metadata( + build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str +) -> str: + """Generate metadata using mechanisms described in PEP 517. + + Returns the generated metadata directory. + """ + metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True) + + metadata_dir = metadata_tmpdir.path + + with build_env: + # Note that BuildBackendHookCaller implements a fallback for + # prepare_metadata_for_build_wheel, so we don't have to + # consider the possibility that this hook doesn't exist. + runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)") + with backend.subprocess_runner(runner): + try: + distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir) + except InstallationSubprocessError as error: + raise MetadataGenerationFailed(package_details=details) from error + + return os.path.join(metadata_dir, distinfo_dir) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_editable.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_editable.py new file mode 100644 index 000000000..3397ccf0f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_editable.py @@ -0,0 +1,42 @@ +"""Metadata generation logic for source distributions. +""" + +import os + +from pip._vendor.pyproject_hooks import BuildBackendHookCaller + +from pip._internal.build_env import BuildEnvironment +from pip._internal.exceptions import ( + InstallationSubprocessError, + MetadataGenerationFailed, +) +from pip._internal.utils.subprocess import runner_with_spinner_message +from pip._internal.utils.temp_dir import TempDirectory + + +def generate_editable_metadata( + build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str +) -> str: + """Generate metadata using mechanisms described in PEP 660. + + Returns the generated metadata directory. + """ + metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True) + + metadata_dir = metadata_tmpdir.path + + with build_env: + # Note that BuildBackendHookCaller implements a fallback for + # prepare_metadata_for_build_wheel/editable, so we don't have to + # consider the possibility that this hook doesn't exist. + runner = runner_with_spinner_message( + "Preparing editable metadata (pyproject.toml)" + ) + with backend.subprocess_runner(runner): + try: + distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir) + except InstallationSubprocessError as error: + raise MetadataGenerationFailed(package_details=details) from error + + assert distinfo_dir is not None + return os.path.join(metadata_dir, distinfo_dir) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_legacy.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_legacy.py new file mode 100644 index 000000000..c01dd1c67 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/metadata_legacy.py @@ -0,0 +1,74 @@ +"""Metadata generation logic for legacy source distributions. +""" + +import logging +import os + +from pip._internal.build_env import BuildEnvironment +from pip._internal.cli.spinners import open_spinner +from pip._internal.exceptions import ( + InstallationError, + InstallationSubprocessError, + MetadataGenerationFailed, +) +from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args +from pip._internal.utils.subprocess import call_subprocess +from pip._internal.utils.temp_dir import TempDirectory + +logger = logging.getLogger(__name__) + + +def _find_egg_info(directory: str) -> str: + """Find an .egg-info subdirectory in `directory`.""" + filenames = [f for f in os.listdir(directory) if f.endswith(".egg-info")] + + if not filenames: + raise InstallationError(f"No .egg-info directory found in {directory}") + + if len(filenames) > 1: + raise InstallationError( + f"More than one .egg-info directory found in {directory}" + ) + + return os.path.join(directory, filenames[0]) + + +def generate_metadata( + build_env: BuildEnvironment, + setup_py_path: str, + source_dir: str, + isolated: bool, + details: str, +) -> str: + """Generate metadata using setup.py-based defacto mechanisms. + + Returns the generated metadata directory. + """ + logger.debug( + "Running setup.py (path:%s) egg_info for package %s", + setup_py_path, + details, + ) + + egg_info_dir = TempDirectory(kind="pip-egg-info", globally_managed=True).path + + args = make_setuptools_egg_info_args( + setup_py_path, + egg_info_dir=egg_info_dir, + no_user_config=isolated, + ) + + with build_env: + with open_spinner("Preparing metadata (setup.py)") as spinner: + try: + call_subprocess( + args, + cwd=source_dir, + command_desc="python setup.py egg_info", + spinner=spinner, + ) + except InstallationSubprocessError as error: + raise MetadataGenerationFailed(package_details=details) from error + + # Return the .egg-info directory. + return _find_egg_info(egg_info_dir) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel.py new file mode 100644 index 000000000..064811ad1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel.py @@ -0,0 +1,37 @@ +import logging +import os +from typing import Optional + +from pip._vendor.pyproject_hooks import BuildBackendHookCaller + +from pip._internal.utils.subprocess import runner_with_spinner_message + +logger = logging.getLogger(__name__) + + +def build_wheel_pep517( + name: str, + backend: BuildBackendHookCaller, + metadata_directory: str, + tempd: str, +) -> Optional[str]: + """Build one InstallRequirement using the PEP 517 build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + assert metadata_directory is not None + try: + logger.debug("Destination directory: %s", tempd) + + runner = runner_with_spinner_message( + f"Building wheel for {name} (pyproject.toml)" + ) + with backend.subprocess_runner(runner): + wheel_name = backend.build_wheel( + tempd, + metadata_directory=metadata_directory, + ) + except Exception: + logger.error("Failed building wheel for %s", name) + return None + return os.path.join(tempd, wheel_name) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_editable.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_editable.py new file mode 100644 index 000000000..719d69dd8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_editable.py @@ -0,0 +1,46 @@ +import logging +import os +from typing import Optional + +from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing + +from pip._internal.utils.subprocess import runner_with_spinner_message + +logger = logging.getLogger(__name__) + + +def build_wheel_editable( + name: str, + backend: BuildBackendHookCaller, + metadata_directory: str, + tempd: str, +) -> Optional[str]: + """Build one InstallRequirement using the PEP 660 build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + assert metadata_directory is not None + try: + logger.debug("Destination directory: %s", tempd) + + runner = runner_with_spinner_message( + f"Building editable for {name} (pyproject.toml)" + ) + with backend.subprocess_runner(runner): + try: + wheel_name = backend.build_editable( + tempd, + metadata_directory=metadata_directory, + ) + except HookMissing as e: + logger.error( + "Cannot build editable %s because the build " + "backend does not have the %s hook", + name, + e, + ) + return None + except Exception: + logger.error("Failed building editable for %s", name) + return None + return os.path.join(tempd, wheel_name) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_legacy.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_legacy.py new file mode 100644 index 000000000..3ee2a7058 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/build/wheel_legacy.py @@ -0,0 +1,102 @@ +import logging +import os.path +from typing import List, Optional + +from pip._internal.cli.spinners import open_spinner +from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args +from pip._internal.utils.subprocess import call_subprocess, format_command_args + +logger = logging.getLogger(__name__) + + +def format_command_result( + command_args: List[str], + command_output: str, +) -> str: + """Format command information for logging.""" + command_desc = format_command_args(command_args) + text = f"Command arguments: {command_desc}\n" + + if not command_output: + text += "Command output: None" + elif logger.getEffectiveLevel() > logging.DEBUG: + text += "Command output: [use --verbose to show]" + else: + if not command_output.endswith("\n"): + command_output += "\n" + text += f"Command output:\n{command_output}" + + return text + + +def get_legacy_build_wheel_path( + names: List[str], + temp_dir: str, + name: str, + command_args: List[str], + command_output: str, +) -> Optional[str]: + """Return the path to the wheel in the temporary build directory.""" + # Sort for determinism. + names = sorted(names) + if not names: + msg = f"Legacy build of wheel for {name!r} created no files.\n" + msg += format_command_result(command_args, command_output) + logger.warning(msg) + return None + + if len(names) > 1: + msg = ( + f"Legacy build of wheel for {name!r} created more than one file.\n" + f"Filenames (choosing first): {names}\n" + ) + msg += format_command_result(command_args, command_output) + logger.warning(msg) + + return os.path.join(temp_dir, names[0]) + + +def build_wheel_legacy( + name: str, + setup_py_path: str, + source_dir: str, + global_options: List[str], + build_options: List[str], + tempd: str, +) -> Optional[str]: + """Build one unpacked package using the "legacy" build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + wheel_args = make_setuptools_bdist_wheel_args( + setup_py_path, + global_options=global_options, + build_options=build_options, + destination_dir=tempd, + ) + + spin_message = f"Building wheel for {name} (setup.py)" + with open_spinner(spin_message) as spinner: + logger.debug("Destination directory: %s", tempd) + + try: + output = call_subprocess( + wheel_args, + command_desc="python setup.py bdist_wheel", + cwd=source_dir, + spinner=spinner, + ) + except Exception: + spinner.finish("error") + logger.error("Failed building wheel for %s", name) + return None + + names = os.listdir(tempd) + wheel_path = get_legacy_build_wheel_path( + names=names, + temp_dir=tempd, + name=name, + command_args=wheel_args, + command_output=output, + ) + return wheel_path diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/check.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/check.py new file mode 100644 index 000000000..4b6fbc4c3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/check.py @@ -0,0 +1,181 @@ +"""Validation of dependencies of packages +""" + +import logging +from contextlib import suppress +from email.parser import Parser +from functools import reduce +from typing import ( + Callable, + Dict, + FrozenSet, + Generator, + Iterable, + List, + NamedTuple, + Optional, + Set, + Tuple, +) + +from pip._vendor.packaging.requirements import Requirement +from pip._vendor.packaging.tags import Tag, parse_tag +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.version import Version + +from pip._internal.distributions import make_distribution_for_install_requirement +from pip._internal.metadata import get_default_environment +from pip._internal.metadata.base import BaseDistribution +from pip._internal.req.req_install import InstallRequirement + +logger = logging.getLogger(__name__) + + +class PackageDetails(NamedTuple): + version: Version + dependencies: List[Requirement] + + +# Shorthands +PackageSet = Dict[NormalizedName, PackageDetails] +Missing = Tuple[NormalizedName, Requirement] +Conflicting = Tuple[NormalizedName, Version, Requirement] + +MissingDict = Dict[NormalizedName, List[Missing]] +ConflictingDict = Dict[NormalizedName, List[Conflicting]] +CheckResult = Tuple[MissingDict, ConflictingDict] +ConflictDetails = Tuple[PackageSet, CheckResult] + + +def create_package_set_from_installed() -> Tuple[PackageSet, bool]: + """Converts a list of distributions into a PackageSet.""" + package_set = {} + problems = False + env = get_default_environment() + for dist in env.iter_installed_distributions(local_only=False, skip=()): + name = dist.canonical_name + try: + dependencies = list(dist.iter_dependencies()) + package_set[name] = PackageDetails(dist.version, dependencies) + except (OSError, ValueError) as e: + # Don't crash on unreadable or broken metadata. + logger.warning("Error parsing dependencies of %s: %s", name, e) + problems = True + return package_set, problems + + +def check_package_set( + package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None +) -> CheckResult: + """Check if a package set is consistent + + If should_ignore is passed, it should be a callable that takes a + package name and returns a boolean. + """ + + missing = {} + conflicting = {} + + for package_name, package_detail in package_set.items(): + # Info about dependencies of package_name + missing_deps: Set[Missing] = set() + conflicting_deps: Set[Conflicting] = set() + + if should_ignore and should_ignore(package_name): + continue + + for req in package_detail.dependencies: + name = canonicalize_name(req.name) + + # Check if it's missing + if name not in package_set: + missed = True + if req.marker is not None: + missed = req.marker.evaluate({"extra": ""}) + if missed: + missing_deps.add((name, req)) + continue + + # Check if there's a conflict + version = package_set[name].version + if not req.specifier.contains(version, prereleases=True): + conflicting_deps.add((name, version, req)) + + if missing_deps: + missing[package_name] = sorted(missing_deps, key=str) + if conflicting_deps: + conflicting[package_name] = sorted(conflicting_deps, key=str) + + return missing, conflicting + + +def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails: + """For checking if the dependency graph would be consistent after \ + installing given requirements + """ + # Start from the current state + package_set, _ = create_package_set_from_installed() + # Install packages + would_be_installed = _simulate_installation_of(to_install, package_set) + + # Only warn about directly-dependent packages; create a whitelist of them + whitelist = _create_whitelist(would_be_installed, package_set) + + return ( + package_set, + check_package_set( + package_set, should_ignore=lambda name: name not in whitelist + ), + ) + + +def check_unsupported( + packages: Iterable[BaseDistribution], + supported_tags: Iterable[Tag], +) -> Generator[BaseDistribution, None, None]: + for p in packages: + with suppress(FileNotFoundError): + wheel_file = p.read_text("WHEEL") + wheel_tags: FrozenSet[Tag] = reduce( + frozenset.union, + map(parse_tag, Parser().parsestr(wheel_file).get_all("Tag", [])), + frozenset(), + ) + if wheel_tags.isdisjoint(supported_tags): + yield p + + +def _simulate_installation_of( + to_install: List[InstallRequirement], package_set: PackageSet +) -> Set[NormalizedName]: + """Computes the version of packages after installing to_install.""" + # Keep track of packages that were installed + installed = set() + + # Modify it as installing requirement_set would (assuming no errors) + for inst_req in to_install: + abstract_dist = make_distribution_for_install_requirement(inst_req) + dist = abstract_dist.get_metadata_distribution() + name = dist.canonical_name + package_set[name] = PackageDetails(dist.version, list(dist.iter_dependencies())) + + installed.add(name) + + return installed + + +def _create_whitelist( + would_be_installed: Set[NormalizedName], package_set: PackageSet +) -> Set[NormalizedName]: + packages_affected = set(would_be_installed) + + for package_name in package_set: + if package_name in packages_affected: + continue + + for req in package_set[package_name].dependencies: + if canonicalize_name(req.name) in packages_affected: + packages_affected.add(package_name) + break + + return packages_affected diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/freeze.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/freeze.py new file mode 100644 index 000000000..ae5dd37f9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/freeze.py @@ -0,0 +1,256 @@ +import collections +import logging +import os +from dataclasses import dataclass, field +from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set + +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.version import InvalidVersion + +from pip._internal.exceptions import BadCommand, InstallationError +from pip._internal.metadata import BaseDistribution, get_environment +from pip._internal.req.constructors import ( + install_req_from_editable, + install_req_from_line, +) +from pip._internal.req.req_file import COMMENT_RE +from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference + +logger = logging.getLogger(__name__) + + +class _EditableInfo(NamedTuple): + requirement: str + comments: List[str] + + +def freeze( + requirement: Optional[List[str]] = None, + local_only: bool = False, + user_only: bool = False, + paths: Optional[List[str]] = None, + isolated: bool = False, + exclude_editable: bool = False, + skip: Container[str] = (), +) -> Generator[str, None, None]: + installations: Dict[str, FrozenRequirement] = {} + + dists = get_environment(paths).iter_installed_distributions( + local_only=local_only, + skip=(), + user_only=user_only, + ) + for dist in dists: + req = FrozenRequirement.from_dist(dist) + if exclude_editable and req.editable: + continue + installations[req.canonical_name] = req + + if requirement: + # the options that don't get turned into an InstallRequirement + # should only be emitted once, even if the same option is in multiple + # requirements files, so we need to keep track of what has been emitted + # so that we don't emit it again if it's seen again + emitted_options: Set[str] = set() + # keep track of which files a requirement is in so that we can + # give an accurate warning if a requirement appears multiple times. + req_files: Dict[str, List[str]] = collections.defaultdict(list) + for req_file_path in requirement: + with open(req_file_path) as req_file: + for line in req_file: + if ( + not line.strip() + or line.strip().startswith("#") + or line.startswith( + ( + "-r", + "--requirement", + "-f", + "--find-links", + "-i", + "--index-url", + "--pre", + "--trusted-host", + "--process-dependency-links", + "--extra-index-url", + "--use-feature", + ) + ) + ): + line = line.rstrip() + if line not in emitted_options: + emitted_options.add(line) + yield line + continue + + if line.startswith("-e") or line.startswith("--editable"): + if line.startswith("-e"): + line = line[2:].strip() + else: + line = line[len("--editable") :].strip().lstrip("=") + line_req = install_req_from_editable( + line, + isolated=isolated, + ) + else: + line_req = install_req_from_line( + COMMENT_RE.sub("", line).strip(), + isolated=isolated, + ) + + if not line_req.name: + logger.info( + "Skipping line in requirement file [%s] because " + "it's not clear what it would install: %s", + req_file_path, + line.strip(), + ) + logger.info( + " (add #egg=PackageName to the URL to avoid" + " this warning)" + ) + else: + line_req_canonical_name = canonicalize_name(line_req.name) + if line_req_canonical_name not in installations: + # either it's not installed, or it is installed + # but has been processed already + if not req_files[line_req.name]: + logger.warning( + "Requirement file [%s] contains %s, but " + "package %r is not installed", + req_file_path, + COMMENT_RE.sub("", line).strip(), + line_req.name, + ) + else: + req_files[line_req.name].append(req_file_path) + else: + yield str(installations[line_req_canonical_name]).rstrip() + del installations[line_req_canonical_name] + req_files[line_req.name].append(req_file_path) + + # Warn about requirements that were included multiple times (in a + # single requirements file or in different requirements files). + for name, files in req_files.items(): + if len(files) > 1: + logger.warning( + "Requirement %s included multiple times [%s]", + name, + ", ".join(sorted(set(files))), + ) + + yield ("## The following requirements were added by pip freeze:") + for installation in sorted(installations.values(), key=lambda x: x.name.lower()): + if installation.canonical_name not in skip: + yield str(installation).rstrip() + + +def _format_as_name_version(dist: BaseDistribution) -> str: + try: + dist_version = dist.version + except InvalidVersion: + # legacy version + return f"{dist.raw_name}==={dist.raw_version}" + else: + return f"{dist.raw_name}=={dist_version}" + + +def _get_editable_info(dist: BaseDistribution) -> _EditableInfo: + """ + Compute and return values (req, comments) for use in + FrozenRequirement.from_dist(). + """ + editable_project_location = dist.editable_project_location + assert editable_project_location + location = os.path.normcase(os.path.abspath(editable_project_location)) + + from pip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs + + vcs_backend = vcs.get_backend_for_dir(location) + + if vcs_backend is None: + display = _format_as_name_version(dist) + logger.debug( + 'No VCS found for editable requirement "%s" in: %r', + display, + location, + ) + return _EditableInfo( + requirement=location, + comments=[f"# Editable install with no version control ({display})"], + ) + + vcs_name = type(vcs_backend).__name__ + + try: + req = vcs_backend.get_src_requirement(location, dist.raw_name) + except RemoteNotFoundError: + display = _format_as_name_version(dist) + return _EditableInfo( + requirement=location, + comments=[f"# Editable {vcs_name} install with no remote ({display})"], + ) + except RemoteNotValidError as ex: + display = _format_as_name_version(dist) + return _EditableInfo( + requirement=location, + comments=[ + f"# Editable {vcs_name} install ({display}) with either a deleted " + f"local remote or invalid URI:", + f"# '{ex.url}'", + ], + ) + except BadCommand: + logger.warning( + "cannot determine version of editable source in %s " + "(%s command not found in path)", + location, + vcs_backend.name, + ) + return _EditableInfo(requirement=location, comments=[]) + except InstallationError as exc: + logger.warning("Error when trying to get requirement for VCS system %s", exc) + else: + return _EditableInfo(requirement=req, comments=[]) + + logger.warning("Could not determine repository location of %s", location) + + return _EditableInfo( + requirement=location, + comments=["## !! Could not determine repository location"], + ) + + +@dataclass(frozen=True) +class FrozenRequirement: + name: str + req: str + editable: bool + comments: Iterable[str] = field(default_factory=tuple) + + @property + def canonical_name(self) -> NormalizedName: + return canonicalize_name(self.name) + + @classmethod + def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement": + editable = dist.editable + if editable: + req, comments = _get_editable_info(dist) + else: + comments = [] + direct_url = dist.direct_url + if direct_url: + # if PEP 610 metadata is present, use it + req = direct_url_as_pep440_direct_reference(direct_url, dist.raw_name) + else: + # name==version requirement + req = _format_as_name_version(dist) + + return cls(dist.raw_name, req, editable, comments=comments) + + def __str__(self) -> str: + req = self.req + if self.editable: + req = f"-e {req}" + return "\n".join(list(self.comments) + [str(req)]) + "\n" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/install/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/install/__init__.py new file mode 100644 index 000000000..24d6a5dd3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/install/__init__.py @@ -0,0 +1,2 @@ +"""For modules related to installing packages. +""" diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d0f9f5fe03ff8c9805559bcb8a1080a144eadff GIT binary patch literal 281 zcmYjMF-`+95cH)Xij+UNjl|s+*H9otfv6A-qM@;Txk<8$&$jI4q~rnog)dO^inKg| zjtU2Y#7Hx%(XM89ALsL#DtLP*ZcF_*n*XSZxh~ApBE5Q2ef6Lgll}Q^NO}nUvqRPi z&I)=hL9@?donyb&(Jr@kjoi%c7uDg+m_96xc~xmrqJHG-g*IUTVUI1t(G5np5C?dn zJKJT7JJ|$Z$F^Q2pkvgMN-7eK@FO|yP9tq|L74^h-%^Sk$*>nBZykk*WQBvzbh?mC V>9QEp#NAOO)2``Q<8C*$ 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 DjDNK| literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/install/editable_legacy.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/install/editable_legacy.py new file mode 100644 index 000000000..9aaa699a6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/install/editable_legacy.py @@ -0,0 +1,47 @@ +"""Legacy editable installation process, i.e. `setup.py develop`. +""" + +import logging +from typing import Optional, Sequence + +from pip._internal.build_env import BuildEnvironment +from pip._internal.utils.logging import indent_log +from pip._internal.utils.setuptools_build import make_setuptools_develop_args +from pip._internal.utils.subprocess import call_subprocess + +logger = logging.getLogger(__name__) + + +def install_editable( + *, + global_options: Sequence[str], + prefix: Optional[str], + home: Optional[str], + use_user_site: bool, + name: str, + setup_py_path: str, + isolated: bool, + build_env: BuildEnvironment, + unpacked_source_directory: str, +) -> None: + """Install a package in editable mode. Most arguments are pass-through + to setuptools. + """ + logger.info("Running setup.py develop for %s", name) + + args = make_setuptools_develop_args( + setup_py_path, + global_options=global_options, + no_user_config=isolated, + prefix=prefix, + home=home, + use_user_site=use_user_site, + ) + + with indent_log(): + with build_env: + call_subprocess( + args, + command_desc="python setup.py develop", + cwd=unpacked_source_directory, + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/install/wheel.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/install/wheel.py new file mode 100644 index 000000000..aef42aa9e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/install/wheel.py @@ -0,0 +1,741 @@ +"""Support for installing and building the "wheel" binary package format. +""" + +import collections +import compileall +import contextlib +import csv +import importlib +import logging +import os.path +import re +import shutil +import sys +import warnings +from base64 import urlsafe_b64encode +from email.message import Message +from itertools import chain, filterfalse, starmap +from typing import ( + IO, + TYPE_CHECKING, + Any, + BinaryIO, + Callable, + Dict, + Generator, + Iterable, + Iterator, + List, + NewType, + Optional, + Protocol, + Sequence, + Set, + Tuple, + Union, + cast, +) +from zipfile import ZipFile, ZipInfo + +from pip._vendor.distlib.scripts import ScriptMaker +from pip._vendor.distlib.util import get_export_entry +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.exceptions import InstallationError +from pip._internal.locations import get_major_minor_version +from pip._internal.metadata import ( + BaseDistribution, + FilesystemWheel, + get_wheel_distribution, +) +from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl +from pip._internal.models.scheme import SCHEME_KEYS, Scheme +from pip._internal.utils.filesystem import adjacent_tmp_file, replace +from pip._internal.utils.misc import StreamWrapper, ensure_dir, hash_file, partition +from pip._internal.utils.unpacking import ( + current_umask, + is_within_directory, + set_extracted_file_to_default_mode_plus_executable, + zip_item_is_executable, +) +from pip._internal.utils.wheel import parse_wheel + +if TYPE_CHECKING: + + class File(Protocol): + src_record_path: "RecordPath" + dest_path: str + changed: bool + + def save(self) -> None: + pass + + +logger = logging.getLogger(__name__) + +RecordPath = NewType("RecordPath", str) +InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]] + + +def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]: + """Return (encoded_digest, length) for path using hashlib.sha256()""" + h, length = hash_file(path, blocksize) + digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=") + return (digest, str(length)) + + +def csv_io_kwargs(mode: str) -> Dict[str, Any]: + """Return keyword arguments to properly open a CSV file + in the given mode. + """ + return {"mode": mode, "newline": "", "encoding": "utf-8"} + + +def fix_script(path: str) -> bool: + """Replace #!python with #!/path/to/python + Return True if file was changed. + """ + # XXX RECORD hashes will need to be updated + assert os.path.isfile(path) + + with open(path, "rb") as script: + firstline = script.readline() + if not firstline.startswith(b"#!python"): + return False + exename = sys.executable.encode(sys.getfilesystemencoding()) + firstline = b"#!" + exename + os.linesep.encode("ascii") + rest = script.read() + with open(path, "wb") as script: + script.write(firstline) + script.write(rest) + return True + + +def wheel_root_is_purelib(metadata: Message) -> bool: + return metadata.get("Root-Is-Purelib", "").lower() == "true" + + +def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]: + console_scripts = {} + gui_scripts = {} + for entry_point in dist.iter_entry_points(): + if entry_point.group == "console_scripts": + console_scripts[entry_point.name] = entry_point.value + elif entry_point.group == "gui_scripts": + gui_scripts[entry_point.name] = entry_point.value + return console_scripts, gui_scripts + + +def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]: + """Determine if any scripts are not on PATH and format a warning. + Returns a warning message if one or more scripts are not on PATH, + otherwise None. + """ + if not scripts: + return None + + # Group scripts by the path they were installed in + grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set) + for destfile in scripts: + parent_dir = os.path.dirname(destfile) + script_name = os.path.basename(destfile) + grouped_by_dir[parent_dir].add(script_name) + + # We don't want to warn for directories that are on PATH. + not_warn_dirs = [ + os.path.normcase(os.path.normpath(i)).rstrip(os.sep) + for i in os.environ.get("PATH", "").split(os.pathsep) + ] + # If an executable sits with sys.executable, we don't warn for it. + # This covers the case of venv invocations without activating the venv. + not_warn_dirs.append( + os.path.normcase(os.path.normpath(os.path.dirname(sys.executable))) + ) + warn_for: Dict[str, Set[str]] = { + parent_dir: scripts + for parent_dir, scripts in grouped_by_dir.items() + if os.path.normcase(os.path.normpath(parent_dir)) not in not_warn_dirs + } + if not warn_for: + return None + + # Format a message + msg_lines = [] + for parent_dir, dir_scripts in warn_for.items(): + sorted_scripts: List[str] = sorted(dir_scripts) + if len(sorted_scripts) == 1: + start_text = f"script {sorted_scripts[0]} is" + else: + start_text = "scripts {} are".format( + ", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1] + ) + + msg_lines.append( + f"The {start_text} installed in '{parent_dir}' which is not on PATH." + ) + + last_line_fmt = ( + "Consider adding {} to PATH or, if you prefer " + "to suppress this warning, use --no-warn-script-location." + ) + if len(msg_lines) == 1: + msg_lines.append(last_line_fmt.format("this directory")) + else: + msg_lines.append(last_line_fmt.format("these directories")) + + # Add a note if any directory starts with ~ + warn_for_tilde = any( + i[0] == "~" for i in os.environ.get("PATH", "").split(os.pathsep) if i + ) + if warn_for_tilde: + tilde_warning_msg = ( + "NOTE: The current PATH contains path(s) starting with `~`, " + "which may not be expanded by all applications." + ) + msg_lines.append(tilde_warning_msg) + + # Returns the formatted multiline message + return "\n".join(msg_lines) + + +def _normalized_outrows( + outrows: Iterable[InstalledCSVRow], +) -> List[Tuple[str, str, str]]: + """Normalize the given rows of a RECORD file. + + Items in each row are converted into str. Rows are then sorted to make + the value more predictable for tests. + + Each row is a 3-tuple (path, hash, size) and corresponds to a record of + a RECORD file (see PEP 376 and PEP 427 for details). For the rows + passed to this function, the size can be an integer as an int or string, + or the empty string. + """ + # Normally, there should only be one row per path, in which case the + # second and third elements don't come into play when sorting. + # However, in cases in the wild where a path might happen to occur twice, + # we don't want the sort operation to trigger an error (but still want + # determinism). Since the third element can be an int or string, we + # coerce each element to a string to avoid a TypeError in this case. + # For additional background, see-- + # https://github.com/pypa/pip/issues/5868 + return sorted( + (record_path, hash_, str(size)) for record_path, hash_, size in outrows + ) + + +def _record_to_fs_path(record_path: RecordPath, lib_dir: str) -> str: + return os.path.join(lib_dir, record_path) + + +def _fs_to_record_path(path: str, lib_dir: str) -> RecordPath: + # On Windows, do not handle relative paths if they belong to different + # logical disks + if os.path.splitdrive(path)[0].lower() == os.path.splitdrive(lib_dir)[0].lower(): + path = os.path.relpath(path, lib_dir) + + path = path.replace(os.path.sep, "/") + return cast("RecordPath", path) + + +def get_csv_rows_for_installed( + old_csv_rows: List[List[str]], + installed: Dict[RecordPath, RecordPath], + changed: Set[RecordPath], + generated: List[str], + lib_dir: str, +) -> List[InstalledCSVRow]: + """ + :param installed: A map from archive RECORD path to installation RECORD + path. + """ + installed_rows: List[InstalledCSVRow] = [] + for row in old_csv_rows: + if len(row) > 3: + logger.warning("RECORD line has more than three elements: %s", row) + old_record_path = cast("RecordPath", row[0]) + new_record_path = installed.pop(old_record_path, old_record_path) + if new_record_path in changed: + digest, length = rehash(_record_to_fs_path(new_record_path, lib_dir)) + else: + digest = row[1] if len(row) > 1 else "" + length = row[2] if len(row) > 2 else "" + installed_rows.append((new_record_path, digest, length)) + for f in generated: + path = _fs_to_record_path(f, lib_dir) + digest, length = rehash(f) + installed_rows.append((path, digest, length)) + return installed_rows + [ + (installed_record_path, "", "") for installed_record_path in installed.values() + ] + + +def get_console_script_specs(console: Dict[str, str]) -> List[str]: + """ + Given the mapping from entrypoint name to callable, return the relevant + console script specs. + """ + # Don't mutate caller's version + console = console.copy() + + scripts_to_generate = [] + + # Special case pip and setuptools to generate versioned wrappers + # + # The issue is that some projects (specifically, pip and setuptools) use + # code in setup.py to create "versioned" entry points - pip2.7 on Python + # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into + # the wheel metadata at build time, and so if the wheel is installed with + # a *different* version of Python the entry points will be wrong. The + # correct fix for this is to enhance the metadata to be able to describe + # such versioned entry points. + # Currently, projects using versioned entry points will either have + # incorrect versioned entry points, or they will not be able to distribute + # "universal" wheels (i.e., they will need a wheel per Python version). + # + # Because setuptools and pip are bundled with _ensurepip and virtualenv, + # we need to use universal wheels. As a workaround, we + # override the versioned entry points in the wheel and generate the + # correct ones. + # + # To add the level of hack in this section of code, in order to support + # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment + # variable which will control which version scripts get installed. + # + # ENSUREPIP_OPTIONS=altinstall + # - Only pipX.Y and easy_install-X.Y will be generated and installed + # ENSUREPIP_OPTIONS=install + # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note + # that this option is technically if ENSUREPIP_OPTIONS is set and is + # not altinstall + # DEFAULT + # - The default behavior is to install pip, pipX, pipX.Y, easy_install + # and easy_install-X.Y. + pip_script = console.pop("pip", None) + if pip_script: + if "ENSUREPIP_OPTIONS" not in os.environ: + scripts_to_generate.append("pip = " + pip_script) + + if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": + scripts_to_generate.append(f"pip{sys.version_info[0]} = {pip_script}") + + scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}") + # Delete any other versioned pip entry points + pip_ep = [k for k in console if re.match(r"pip(\d+(\.\d+)?)?$", k)] + for k in pip_ep: + del console[k] + easy_install_script = console.pop("easy_install", None) + if easy_install_script: + if "ENSUREPIP_OPTIONS" not in os.environ: + scripts_to_generate.append("easy_install = " + easy_install_script) + + scripts_to_generate.append( + f"easy_install-{get_major_minor_version()} = {easy_install_script}" + ) + # Delete any other versioned easy_install entry points + easy_install_ep = [ + k for k in console if re.match(r"easy_install(-\d+\.\d+)?$", k) + ] + for k in easy_install_ep: + del console[k] + + # Generate the console entry points specified in the wheel + scripts_to_generate.extend(starmap("{} = {}".format, console.items())) + + return scripts_to_generate + + +class ZipBackedFile: + def __init__( + self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile + ) -> None: + self.src_record_path = src_record_path + self.dest_path = dest_path + self._zip_file = zip_file + self.changed = False + + def _getinfo(self) -> ZipInfo: + return self._zip_file.getinfo(self.src_record_path) + + def save(self) -> None: + # When we open the output file below, any existing file is truncated + # before we start writing the new contents. This is fine in most + # cases, but can cause a segfault if pip has loaded a shared + # object (e.g. from pyopenssl through its vendored urllib3) + # Since the shared object is mmap'd an attempt to call a + # symbol in it will then cause a segfault. Unlinking the file + # allows writing of new contents while allowing the process to + # continue to use the old copy. + if os.path.exists(self.dest_path): + os.unlink(self.dest_path) + + zipinfo = self._getinfo() + + # optimization: the file is created by open(), + # skip the decompression when there is 0 bytes to decompress. + with open(self.dest_path, "wb") as dest: + if zipinfo.file_size > 0: + with self._zip_file.open(zipinfo) as f: + blocksize = min(zipinfo.file_size, 1024 * 1024) + shutil.copyfileobj(f, dest, blocksize) + + if zip_item_is_executable(zipinfo): + set_extracted_file_to_default_mode_plus_executable(self.dest_path) + + +class ScriptFile: + def __init__(self, file: "File") -> None: + self._file = file + self.src_record_path = self._file.src_record_path + self.dest_path = self._file.dest_path + self.changed = False + + def save(self) -> None: + self._file.save() + self.changed = fix_script(self.dest_path) + + +class MissingCallableSuffix(InstallationError): + def __init__(self, entry_point: str) -> None: + super().__init__( + f"Invalid script entry point: {entry_point} - A callable " + "suffix is required. Cf https://packaging.python.org/" + "specifications/entry-points/#use-for-scripts for more " + "information." + ) + + +def _raise_for_invalid_entrypoint(specification: str) -> None: + entry = get_export_entry(specification) + if entry is not None and entry.suffix is None: + raise MissingCallableSuffix(str(entry)) + + +class PipScriptMaker(ScriptMaker): + def make( + self, specification: str, options: Optional[Dict[str, Any]] = None + ) -> List[str]: + _raise_for_invalid_entrypoint(specification) + return super().make(specification, options) + + +def _install_wheel( # noqa: C901, PLR0915 function is too long + name: str, + wheel_zip: ZipFile, + wheel_path: str, + scheme: Scheme, + pycompile: bool = True, + warn_script_location: bool = True, + direct_url: Optional[DirectUrl] = None, + requested: bool = False, +) -> None: + """Install a wheel. + + :param name: Name of the project to install + :param wheel_zip: open ZipFile for wheel being installed + :param scheme: Distutils scheme dictating the install directories + :param req_description: String used in place of the requirement, for + logging + :param pycompile: Whether to byte-compile installed Python files + :param warn_script_location: Whether to check that scripts are installed + into a directory on PATH + :raises UnsupportedWheel: + * when the directory holds an unpacked wheel with incompatible + Wheel-Version + * when the .dist-info dir does not match the wheel + """ + info_dir, metadata = parse_wheel(wheel_zip, name) + + if wheel_root_is_purelib(metadata): + lib_dir = scheme.purelib + else: + lib_dir = scheme.platlib + + # Record details of the files moved + # installed = files copied from the wheel to the destination + # changed = files changed while installing (scripts #! line typically) + # generated = files newly generated during the install (script wrappers) + installed: Dict[RecordPath, RecordPath] = {} + changed: Set[RecordPath] = set() + generated: List[str] = [] + + def record_installed( + srcfile: RecordPath, destfile: str, modified: bool = False + ) -> None: + """Map archive RECORD paths to installation RECORD paths.""" + newpath = _fs_to_record_path(destfile, lib_dir) + installed[srcfile] = newpath + if modified: + changed.add(newpath) + + def is_dir_path(path: RecordPath) -> bool: + return path.endswith("/") + + def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None: + if not is_within_directory(dest_dir_path, target_path): + message = ( + "The wheel {!r} has a file {!r} trying to install" + " outside the target directory {!r}" + ) + raise InstallationError( + message.format(wheel_path, target_path, dest_dir_path) + ) + + def root_scheme_file_maker( + zip_file: ZipFile, dest: str + ) -> Callable[[RecordPath], "File"]: + def make_root_scheme_file(record_path: RecordPath) -> "File": + normed_path = os.path.normpath(record_path) + dest_path = os.path.join(dest, normed_path) + assert_no_path_traversal(dest, dest_path) + return ZipBackedFile(record_path, dest_path, zip_file) + + return make_root_scheme_file + + def data_scheme_file_maker( + zip_file: ZipFile, scheme: Scheme + ) -> Callable[[RecordPath], "File"]: + scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS} + + def make_data_scheme_file(record_path: RecordPath) -> "File": + normed_path = os.path.normpath(record_path) + try: + _, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2) + except ValueError: + message = ( + f"Unexpected file in {wheel_path}: {record_path!r}. .data directory" + " contents should be named like: '/'." + ) + raise InstallationError(message) + + try: + scheme_path = scheme_paths[scheme_key] + except KeyError: + valid_scheme_keys = ", ".join(sorted(scheme_paths)) + message = ( + f"Unknown scheme key used in {wheel_path}: {scheme_key} " + f"(for file {record_path!r}). .data directory contents " + f"should be in subdirectories named with a valid scheme " + f"key ({valid_scheme_keys})" + ) + raise InstallationError(message) + + dest_path = os.path.join(scheme_path, dest_subpath) + assert_no_path_traversal(scheme_path, dest_path) + return ZipBackedFile(record_path, dest_path, zip_file) + + return make_data_scheme_file + + def is_data_scheme_path(path: RecordPath) -> bool: + return path.split("/", 1)[0].endswith(".data") + + paths = cast(List[RecordPath], wheel_zip.namelist()) + file_paths = filterfalse(is_dir_path, paths) + root_scheme_paths, data_scheme_paths = partition(is_data_scheme_path, file_paths) + + make_root_scheme_file = root_scheme_file_maker(wheel_zip, lib_dir) + files: Iterator[File] = map(make_root_scheme_file, root_scheme_paths) + + def is_script_scheme_path(path: RecordPath) -> bool: + parts = path.split("/", 2) + return len(parts) > 2 and parts[0].endswith(".data") and parts[1] == "scripts" + + other_scheme_paths, script_scheme_paths = partition( + is_script_scheme_path, data_scheme_paths + ) + + make_data_scheme_file = data_scheme_file_maker(wheel_zip, scheme) + other_scheme_files = map(make_data_scheme_file, other_scheme_paths) + files = chain(files, other_scheme_files) + + # Get the defined entry points + distribution = get_wheel_distribution( + FilesystemWheel(wheel_path), + canonicalize_name(name), + ) + console, gui = get_entrypoints(distribution) + + def is_entrypoint_wrapper(file: "File") -> bool: + # EP, EP.exe and EP-script.py are scripts generated for + # entry point EP by setuptools + path = file.dest_path + name = os.path.basename(path) + if name.lower().endswith(".exe"): + matchname = name[:-4] + elif name.lower().endswith("-script.py"): + matchname = name[:-10] + elif name.lower().endswith(".pya"): + matchname = name[:-4] + else: + matchname = name + # Ignore setuptools-generated scripts + return matchname in console or matchname in gui + + script_scheme_files: Iterator[File] = map( + make_data_scheme_file, script_scheme_paths + ) + script_scheme_files = filterfalse(is_entrypoint_wrapper, script_scheme_files) + script_scheme_files = map(ScriptFile, script_scheme_files) + files = chain(files, script_scheme_files) + + existing_parents = set() + for file in files: + # directory creation is lazy and after file filtering + # to ensure we don't install empty dirs; empty dirs can't be + # uninstalled. + parent_dir = os.path.dirname(file.dest_path) + if parent_dir not in existing_parents: + ensure_dir(parent_dir) + existing_parents.add(parent_dir) + file.save() + record_installed(file.src_record_path, file.dest_path, file.changed) + + def pyc_source_file_paths() -> Generator[str, None, None]: + # We de-duplicate installation paths, since there can be overlap (e.g. + # file in .data maps to same location as file in wheel root). + # Sorting installation paths makes it easier to reproduce and debug + # issues related to permissions on existing files. + for installed_path in sorted(set(installed.values())): + full_installed_path = os.path.join(lib_dir, installed_path) + if not os.path.isfile(full_installed_path): + continue + if not full_installed_path.endswith(".py"): + continue + yield full_installed_path + + def pyc_output_path(path: str) -> str: + """Return the path the pyc file would have been written to.""" + return importlib.util.cache_from_source(path) + + # Compile all of the pyc files for the installed files + if pycompile: + with contextlib.redirect_stdout( + StreamWrapper.from_stream(sys.stdout) + ) as stdout: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + for path in pyc_source_file_paths(): + success = compileall.compile_file(path, force=True, quiet=True) + if success: + pyc_path = pyc_output_path(path) + assert os.path.exists(pyc_path) + pyc_record_path = cast( + "RecordPath", pyc_path.replace(os.path.sep, "/") + ) + record_installed(pyc_record_path, pyc_path) + logger.debug(stdout.getvalue()) + + maker = PipScriptMaker(None, scheme.scripts) + + # Ensure old scripts are overwritten. + # See https://github.com/pypa/pip/issues/1800 + maker.clobber = True + + # Ensure we don't generate any variants for scripts because this is almost + # never what somebody wants. + # See https://bitbucket.org/pypa/distlib/issue/35/ + maker.variants = {""} + + # This is required because otherwise distlib creates scripts that are not + # executable. + # See https://bitbucket.org/pypa/distlib/issue/32/ + maker.set_mode = True + + # Generate the console and GUI entry points specified in the wheel + scripts_to_generate = get_console_script_specs(console) + + gui_scripts_to_generate = list(starmap("{} = {}".format, gui.items())) + + generated_console_scripts = maker.make_multiple(scripts_to_generate) + generated.extend(generated_console_scripts) + + generated.extend(maker.make_multiple(gui_scripts_to_generate, {"gui": True})) + + if warn_script_location: + msg = message_about_scripts_not_on_PATH(generated_console_scripts) + if msg is not None: + logger.warning(msg) + + generated_file_mode = 0o666 & ~current_umask() + + @contextlib.contextmanager + def _generate_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]: + with adjacent_tmp_file(path, **kwargs) as f: + yield f + os.chmod(f.name, generated_file_mode) + replace(f.name, path) + + dest_info_dir = os.path.join(lib_dir, info_dir) + + # Record pip as the installer + installer_path = os.path.join(dest_info_dir, "INSTALLER") + with _generate_file(installer_path) as installer_file: + installer_file.write(b"pip\n") + generated.append(installer_path) + + # Record the PEP 610 direct URL reference + if direct_url is not None: + direct_url_path = os.path.join(dest_info_dir, DIRECT_URL_METADATA_NAME) + with _generate_file(direct_url_path) as direct_url_file: + direct_url_file.write(direct_url.to_json().encode("utf-8")) + generated.append(direct_url_path) + + # Record the REQUESTED file + if requested: + requested_path = os.path.join(dest_info_dir, "REQUESTED") + with open(requested_path, "wb"): + pass + generated.append(requested_path) + + record_text = distribution.read_text("RECORD") + record_rows = list(csv.reader(record_text.splitlines())) + + rows = get_csv_rows_for_installed( + record_rows, + installed=installed, + changed=changed, + generated=generated, + lib_dir=lib_dir, + ) + + # Record details of all files installed + record_path = os.path.join(dest_info_dir, "RECORD") + + with _generate_file(record_path, **csv_io_kwargs("w")) as record_file: + # Explicitly cast to typing.IO[str] as a workaround for the mypy error: + # "writer" has incompatible type "BinaryIO"; expected "_Writer" + writer = csv.writer(cast("IO[str]", record_file)) + writer.writerows(_normalized_outrows(rows)) + + +@contextlib.contextmanager +def req_error_context(req_description: str) -> Generator[None, None, None]: + try: + yield + except InstallationError as e: + message = f"For req: {req_description}. {e.args[0]}" + raise InstallationError(message) from e + + +def install_wheel( + name: str, + wheel_path: str, + scheme: Scheme, + req_description: str, + pycompile: bool = True, + warn_script_location: bool = True, + direct_url: Optional[DirectUrl] = None, + requested: bool = False, +) -> None: + with ZipFile(wheel_path, allowZip64=True) as z: + with req_error_context(req_description): + _install_wheel( + name=name, + wheel_zip=z, + wheel_path=wheel_path, + scheme=scheme, + pycompile=pycompile, + warn_script_location=warn_script_location, + direct_url=direct_url, + requested=requested, + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/operations/prepare.py b/venv/lib/python3.8/site-packages/pip/_internal/operations/prepare.py new file mode 100644 index 000000000..e6aa34472 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/operations/prepare.py @@ -0,0 +1,732 @@ +"""Prepares a distribution for installation +""" + +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + +import mimetypes +import os +import shutil +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, Iterable, List, Optional + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.distributions import make_distribution_for_install_requirement +from pip._internal.distributions.installed import InstalledDistribution +from pip._internal.exceptions import ( + DirectoryUrlHashUnsupported, + HashMismatch, + HashUnpinned, + InstallationError, + MetadataInconsistent, + NetworkConnectionError, + VcsHashUnsupported, +) +from pip._internal.index.package_finder import PackageFinder +from pip._internal.metadata import BaseDistribution, get_metadata_distribution +from pip._internal.models.direct_url import ArchiveInfo +from pip._internal.models.link import Link +from pip._internal.models.wheel import Wheel +from pip._internal.network.download import BatchDownloader, Downloader +from pip._internal.network.lazy_wheel import ( + HTTPRangeRequestUnsupported, + dist_from_wheel_url, +) +from pip._internal.network.session import PipSession +from pip._internal.operations.build.build_tracker import BuildTracker +from pip._internal.req.req_install import InstallRequirement +from pip._internal.utils._log import getLogger +from pip._internal.utils.direct_url_helpers import ( + direct_url_for_editable, + direct_url_from_link, +) +from pip._internal.utils.hashes import Hashes, MissingHashes +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ( + display_path, + hash_file, + hide_url, + redact_auth_from_requirement, +) +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.unpacking import unpack_file +from pip._internal.vcs import vcs + +logger = getLogger(__name__) + + +def _get_prepared_distribution( + req: InstallRequirement, + build_tracker: BuildTracker, + finder: PackageFinder, + build_isolation: bool, + check_build_deps: bool, +) -> BaseDistribution: + """Prepare a distribution for installation.""" + abstract_dist = make_distribution_for_install_requirement(req) + tracker_id = abstract_dist.build_tracker_id + if tracker_id is not None: + with build_tracker.track(req, tracker_id): + abstract_dist.prepare_distribution_metadata( + finder, build_isolation, check_build_deps + ) + return abstract_dist.get_metadata_distribution() + + +def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None: + vcs_backend = vcs.get_backend_for_scheme(link.scheme) + assert vcs_backend is not None + vcs_backend.unpack(location, url=hide_url(link.url), verbosity=verbosity) + + +@dataclass +class File: + path: str + content_type: Optional[str] = None + + def __post_init__(self) -> None: + if self.content_type is None: + self.content_type = mimetypes.guess_type(self.path)[0] + + +def get_http_url( + link: Link, + download: Downloader, + download_dir: Optional[str] = None, + hashes: Optional[Hashes] = None, +) -> File: + temp_dir = TempDirectory(kind="unpack", globally_managed=True) + # If a download dir is specified, is the file already downloaded there? + already_downloaded_path = None + if download_dir: + already_downloaded_path = _check_download_dir(link, download_dir, hashes) + + if already_downloaded_path: + from_path = already_downloaded_path + content_type = None + else: + # let's download to a tmp dir + from_path, content_type = download(link, temp_dir.path) + if hashes: + hashes.check_against_path(from_path) + + return File(from_path, content_type) + + +def get_file_url( + link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None +) -> File: + """Get file and optionally check its hash.""" + # If a download dir is specified, is the file already there and valid? + already_downloaded_path = None + if download_dir: + already_downloaded_path = _check_download_dir(link, download_dir, hashes) + + if already_downloaded_path: + from_path = already_downloaded_path + else: + from_path = link.file_path + + # If --require-hashes is off, `hashes` is either empty, the + # link's embedded hash, or MissingHashes; it is required to + # match. If --require-hashes is on, we are satisfied by any + # hash in `hashes` matching: a URL-based or an option-based + # one; no internet-sourced hash will be in `hashes`. + if hashes: + hashes.check_against_path(from_path) + return File(from_path, None) + + +def unpack_url( + link: Link, + location: str, + download: Downloader, + verbosity: int, + download_dir: Optional[str] = None, + hashes: Optional[Hashes] = None, +) -> Optional[File]: + """Unpack link into location, downloading if required. + + :param hashes: A Hashes object, one of whose embedded hashes must match, + or HashMismatch will be raised. If the Hashes is empty, no matches are + required, and unhashable types of requirements (like VCS ones, which + would ordinarily raise HashUnsupported) are allowed. + """ + # non-editable vcs urls + if link.is_vcs: + unpack_vcs_link(link, location, verbosity=verbosity) + return None + + assert not link.is_existing_dir() + + # file urls + if link.is_file: + file = get_file_url(link, download_dir, hashes=hashes) + + # http urls + else: + file = get_http_url( + link, + download, + download_dir, + hashes=hashes, + ) + + # unpack the archive to the build dir location. even when only downloading + # archives, they have to be unpacked to parse dependencies, except wheels + if not link.is_wheel: + unpack_file(file.path, location, file.content_type) + + return file + + +def _check_download_dir( + link: Link, + download_dir: str, + hashes: Optional[Hashes], + warn_on_hash_mismatch: bool = True, +) -> Optional[str]: + """Check download_dir for previously downloaded file with correct hash + If a correct file is found return its path else None + """ + download_path = os.path.join(download_dir, link.filename) + + if not os.path.exists(download_path): + return None + + # If already downloaded, does its hash match? + logger.info("File was already downloaded %s", download_path) + if hashes: + try: + hashes.check_against_path(download_path) + except HashMismatch: + if warn_on_hash_mismatch: + logger.warning( + "Previously-downloaded file %s has bad hash. Re-downloading.", + download_path, + ) + os.unlink(download_path) + return None + return download_path + + +class RequirementPreparer: + """Prepares a Requirement""" + + def __init__( + self, + build_dir: str, + download_dir: Optional[str], + src_dir: str, + build_isolation: bool, + check_build_deps: bool, + build_tracker: BuildTracker, + session: PipSession, + progress_bar: str, + finder: PackageFinder, + require_hashes: bool, + use_user_site: bool, + lazy_wheel: bool, + verbosity: int, + legacy_resolver: bool, + ) -> None: + super().__init__() + + self.src_dir = src_dir + self.build_dir = build_dir + self.build_tracker = build_tracker + self._session = session + self._download = Downloader(session, progress_bar) + self._batch_download = BatchDownloader(session, progress_bar) + self.finder = finder + + # Where still-packed archives should be written to. If None, they are + # not saved, and are deleted immediately after unpacking. + self.download_dir = download_dir + + # Is build isolation allowed? + self.build_isolation = build_isolation + + # Should check build dependencies? + self.check_build_deps = check_build_deps + + # Should hash-checking be required? + self.require_hashes = require_hashes + + # Should install in user site-packages? + self.use_user_site = use_user_site + + # Should wheels be downloaded lazily? + self.use_lazy_wheel = lazy_wheel + + # How verbose should underlying tooling be? + self.verbosity = verbosity + + # Are we using the legacy resolver? + self.legacy_resolver = legacy_resolver + + # Memoized downloaded files, as mapping of url: path. + self._downloaded: Dict[str, str] = {} + + # Previous "header" printed for a link-based InstallRequirement + self._previous_requirement_header = ("", "") + + def _log_preparing_link(self, req: InstallRequirement) -> None: + """Provide context for the requirement being prepared.""" + if req.link.is_file and not req.is_wheel_from_cache: + message = "Processing %s" + information = str(display_path(req.link.file_path)) + else: + message = "Collecting %s" + information = redact_auth_from_requirement(req.req) if req.req else str(req) + + # If we used req.req, inject requirement source if available (this + # would already be included if we used req directly) + if req.req and req.comes_from: + if isinstance(req.comes_from, str): + comes_from: Optional[str] = req.comes_from + else: + comes_from = req.comes_from.from_path() + if comes_from: + information += f" (from {comes_from})" + + if (message, information) != self._previous_requirement_header: + self._previous_requirement_header = (message, information) + logger.info(message, information) + + if req.is_wheel_from_cache: + with indent_log(): + logger.info("Using cached %s", req.link.filename) + + def _ensure_link_req_src_dir( + self, req: InstallRequirement, parallel_builds: bool + ) -> None: + """Ensure source_dir of a linked InstallRequirement.""" + # Since source_dir is only set for editable requirements. + if req.link.is_wheel: + # We don't need to unpack wheels, so no need for a source + # directory. + return + assert req.source_dir is None + if req.link.is_existing_dir(): + # build local directories in-tree + req.source_dir = req.link.file_path + return + + # We always delete unpacked sdists after pip runs. + req.ensure_has_source_dir( + self.build_dir, + autodelete=True, + parallel_builds=parallel_builds, + ) + req.ensure_pristine_source_checkout() + + def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes: + # By the time this is called, the requirement's link should have + # been checked so we can tell what kind of requirements req is + # and raise some more informative errors than otherwise. + # (For example, we can raise VcsHashUnsupported for a VCS URL + # rather than HashMissing.) + if not self.require_hashes: + return req.hashes(trust_internet=True) + + # We could check these first 2 conditions inside unpack_url + # and save repetition of conditions, but then we would + # report less-useful error messages for unhashable + # requirements, complaining that there's no hash provided. + if req.link.is_vcs: + raise VcsHashUnsupported() + if req.link.is_existing_dir(): + raise DirectoryUrlHashUnsupported() + + # Unpinned packages are asking for trouble when a new version + # is uploaded. This isn't a security check, but it saves users + # a surprising hash mismatch in the future. + # file:/// URLs aren't pinnable, so don't complain about them + # not being pinned. + if not req.is_direct and not req.is_pinned: + raise HashUnpinned() + + # If known-good hashes are missing for this requirement, + # shim it with a facade object that will provoke hash + # computation and then raise a HashMissing exception + # showing the user what the hash should be. + return req.hashes(trust_internet=False) or MissingHashes() + + def _fetch_metadata_only( + self, + req: InstallRequirement, + ) -> Optional[BaseDistribution]: + if self.legacy_resolver: + logger.debug( + "Metadata-only fetching is not used in the legacy resolver", + ) + return None + if self.require_hashes: + logger.debug( + "Metadata-only fetching is not used as hash checking is required", + ) + return None + # Try PEP 658 metadata first, then fall back to lazy wheel if unavailable. + return self._fetch_metadata_using_link_data_attr( + req + ) or self._fetch_metadata_using_lazy_wheel(req.link) + + def _fetch_metadata_using_link_data_attr( + self, + req: InstallRequirement, + ) -> Optional[BaseDistribution]: + """Fetch metadata from the data-dist-info-metadata attribute, if possible.""" + # (1) Get the link to the metadata file, if provided by the backend. + metadata_link = req.link.metadata_link() + if metadata_link is None: + return None + assert req.req is not None + logger.verbose( + "Obtaining dependency information for %s from %s", + req.req, + metadata_link, + ) + # (2) Download the contents of the METADATA file, separate from the dist itself. + metadata_file = get_http_url( + metadata_link, + self._download, + hashes=metadata_link.as_hashes(), + ) + with open(metadata_file.path, "rb") as f: + metadata_contents = f.read() + # (3) Generate a dist just from those file contents. + metadata_dist = get_metadata_distribution( + metadata_contents, + req.link.filename, + req.req.name, + ) + # (4) Ensure the Name: field from the METADATA file matches the name from the + # install requirement. + # + # NB: raw_name will fall back to the name from the install requirement if + # the Name: field is not present, but it's noted in the raw_name docstring + # that that should NEVER happen anyway. + if canonicalize_name(metadata_dist.raw_name) != canonicalize_name(req.req.name): + raise MetadataInconsistent( + req, "Name", req.req.name, metadata_dist.raw_name + ) + return metadata_dist + + def _fetch_metadata_using_lazy_wheel( + self, + link: Link, + ) -> Optional[BaseDistribution]: + """Fetch metadata using lazy wheel, if possible.""" + # --use-feature=fast-deps must be provided. + if not self.use_lazy_wheel: + return None + if link.is_file or not link.is_wheel: + logger.debug( + "Lazy wheel is not used as %r does not point to a remote wheel", + link, + ) + return None + + wheel = Wheel(link.filename) + name = canonicalize_name(wheel.name) + logger.info( + "Obtaining dependency information from %s %s", + name, + wheel.version, + ) + url = link.url.split("#", 1)[0] + try: + return dist_from_wheel_url(name, url, self._session) + except HTTPRangeRequestUnsupported: + logger.debug("%s does not support range requests", url) + return None + + def _complete_partial_requirements( + self, + partially_downloaded_reqs: Iterable[InstallRequirement], + parallel_builds: bool = False, + ) -> None: + """Download any requirements which were only fetched by metadata.""" + # Download to a temporary directory. These will be copied over as + # needed for downstream 'download', 'wheel', and 'install' commands. + temp_dir = TempDirectory(kind="unpack", globally_managed=True).path + + # Map each link to the requirement that owns it. This allows us to set + # `req.local_file_path` on the appropriate requirement after passing + # all the links at once into BatchDownloader. + links_to_fully_download: Dict[Link, InstallRequirement] = {} + for req in partially_downloaded_reqs: + assert req.link + links_to_fully_download[req.link] = req + + batch_download = self._batch_download( + links_to_fully_download.keys(), + temp_dir, + ) + for link, (filepath, _) in batch_download: + logger.debug("Downloading link %s to %s", link, filepath) + req = links_to_fully_download[link] + # Record the downloaded file path so wheel reqs can extract a Distribution + # in .get_dist(). + req.local_file_path = filepath + # Record that the file is downloaded so we don't do it again in + # _prepare_linked_requirement(). + self._downloaded[req.link.url] = filepath + + # If this is an sdist, we need to unpack it after downloading, but the + # .source_dir won't be set up until we are in _prepare_linked_requirement(). + # Add the downloaded archive to the install requirement to unpack after + # preparing the source dir. + if not req.is_wheel: + req.needs_unpacked_archive(Path(filepath)) + + # This step is necessary to ensure all lazy wheels are processed + # successfully by the 'download', 'wheel', and 'install' commands. + for req in partially_downloaded_reqs: + self._prepare_linked_requirement(req, parallel_builds) + + def prepare_linked_requirement( + self, req: InstallRequirement, parallel_builds: bool = False + ) -> BaseDistribution: + """Prepare a requirement to be obtained from req.link.""" + assert req.link + self._log_preparing_link(req) + with indent_log(): + # Check if the relevant file is already available + # in the download directory + file_path = None + if self.download_dir is not None and req.link.is_wheel: + hashes = self._get_linked_req_hashes(req) + file_path = _check_download_dir( + req.link, + self.download_dir, + hashes, + # When a locally built wheel has been found in cache, we don't warn + # about re-downloading when the already downloaded wheel hash does + # not match. This is because the hash must be checked against the + # original link, not the cached link. It that case the already + # downloaded file will be removed and re-fetched from cache (which + # implies a hash check against the cache entry's origin.json). + warn_on_hash_mismatch=not req.is_wheel_from_cache, + ) + + if file_path is not None: + # The file is already available, so mark it as downloaded + self._downloaded[req.link.url] = file_path + else: + # The file is not available, attempt to fetch only metadata + metadata_dist = self._fetch_metadata_only(req) + if metadata_dist is not None: + req.needs_more_preparation = True + return metadata_dist + + # None of the optimizations worked, fully prepare the requirement + return self._prepare_linked_requirement(req, parallel_builds) + + def prepare_linked_requirements_more( + self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False + ) -> None: + """Prepare linked requirements more, if needed.""" + reqs = [req for req in reqs if req.needs_more_preparation] + for req in reqs: + # Determine if any of these requirements were already downloaded. + if self.download_dir is not None and req.link.is_wheel: + hashes = self._get_linked_req_hashes(req) + file_path = _check_download_dir(req.link, self.download_dir, hashes) + if file_path is not None: + self._downloaded[req.link.url] = file_path + req.needs_more_preparation = False + + # Prepare requirements we found were already downloaded for some + # reason. The other downloads will be completed separately. + partially_downloaded_reqs: List[InstallRequirement] = [] + for req in reqs: + if req.needs_more_preparation: + partially_downloaded_reqs.append(req) + else: + self._prepare_linked_requirement(req, parallel_builds) + + # TODO: separate this part out from RequirementPreparer when the v1 + # resolver can be removed! + self._complete_partial_requirements( + partially_downloaded_reqs, + parallel_builds=parallel_builds, + ) + + def _prepare_linked_requirement( + self, req: InstallRequirement, parallel_builds: bool + ) -> BaseDistribution: + assert req.link + link = req.link + + hashes = self._get_linked_req_hashes(req) + + if hashes and req.is_wheel_from_cache: + assert req.download_info is not None + assert link.is_wheel + assert link.is_file + # We need to verify hashes, and we have found the requirement in the cache + # of locally built wheels. + if ( + isinstance(req.download_info.info, ArchiveInfo) + and req.download_info.info.hashes + and hashes.has_one_of(req.download_info.info.hashes) + ): + # At this point we know the requirement was built from a hashable source + # artifact, and we verified that the cache entry's hash of the original + # artifact matches one of the hashes we expect. We don't verify hashes + # against the cached wheel, because the wheel is not the original. + hashes = None + else: + logger.warning( + "The hashes of the source archive found in cache entry " + "don't match, ignoring cached built wheel " + "and re-downloading source." + ) + req.link = req.cached_wheel_source_link + link = req.link + + self._ensure_link_req_src_dir(req, parallel_builds) + + if link.is_existing_dir(): + local_file = None + elif link.url not in self._downloaded: + try: + local_file = unpack_url( + link, + req.source_dir, + self._download, + self.verbosity, + self.download_dir, + hashes, + ) + except NetworkConnectionError as exc: + raise InstallationError( + f"Could not install requirement {req} because of HTTP " + f"error {exc} for URL {link}" + ) + else: + file_path = self._downloaded[link.url] + if hashes: + hashes.check_against_path(file_path) + local_file = File(file_path, content_type=None) + + # If download_info is set, we got it from the wheel cache. + if req.download_info is None: + # Editables don't go through this function (see + # prepare_editable_requirement). + assert not req.editable + req.download_info = direct_url_from_link(link, req.source_dir) + # Make sure we have a hash in download_info. If we got it as part of the + # URL, it will have been verified and we can rely on it. Otherwise we + # compute it from the downloaded file. + # FIXME: https://github.com/pypa/pip/issues/11943 + if ( + isinstance(req.download_info.info, ArchiveInfo) + and not req.download_info.info.hashes + and local_file + ): + hash = hash_file(local_file.path)[0].hexdigest() + # We populate info.hash for backward compatibility. + # This will automatically populate info.hashes. + req.download_info.info.hash = f"sha256={hash}" + + # For use in later processing, + # preserve the file path on the requirement. + if local_file: + req.local_file_path = local_file.path + + dist = _get_prepared_distribution( + req, + self.build_tracker, + self.finder, + self.build_isolation, + self.check_build_deps, + ) + return dist + + def save_linked_requirement(self, req: InstallRequirement) -> None: + assert self.download_dir is not None + assert req.link is not None + link = req.link + if link.is_vcs or (link.is_existing_dir() and req.editable): + # Make a .zip of the source_dir we already created. + req.archive(self.download_dir) + return + + if link.is_existing_dir(): + logger.debug( + "Not copying link to destination directory " + "since it is a directory: %s", + link, + ) + return + if req.local_file_path is None: + # No distribution was downloaded for this requirement. + return + + download_location = os.path.join(self.download_dir, link.filename) + if not os.path.exists(download_location): + shutil.copy(req.local_file_path, download_location) + download_path = display_path(download_location) + logger.info("Saved %s", download_path) + + def prepare_editable_requirement( + self, + req: InstallRequirement, + ) -> BaseDistribution: + """Prepare an editable requirement.""" + assert req.editable, "cannot prepare a non-editable req as editable" + + logger.info("Obtaining %s", req) + + with indent_log(): + if self.require_hashes: + raise InstallationError( + f"The editable requirement {req} cannot be installed when " + "requiring hashes, because there is no single file to " + "hash." + ) + req.ensure_has_source_dir(self.src_dir) + req.update_editable() + assert req.source_dir + req.download_info = direct_url_for_editable(req.unpacked_source_directory) + + dist = _get_prepared_distribution( + req, + self.build_tracker, + self.finder, + self.build_isolation, + self.check_build_deps, + ) + + req.check_if_exists(self.use_user_site) + + return dist + + def prepare_installed_requirement( + self, + req: InstallRequirement, + skip_reason: str, + ) -> BaseDistribution: + """Prepare an already-installed requirement.""" + assert req.satisfied_by, "req should have been satisfied but isn't" + assert skip_reason is not None, ( + "did not get skip reason skipped but req.satisfied_by " + f"is set to {req.satisfied_by}" + ) + logger.info( + "Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version + ) + with indent_log(): + if self.require_hashes: + logger.debug( + "Since it is already installed, we are trusting this " + "package without checking its hash. To ensure a " + "completely repeatable environment, install into an " + "empty virtualenv." + ) + return InstalledDistribution(req).get_metadata_distribution() diff --git a/venv/lib/python3.8/site-packages/pip/_internal/pyproject.py b/venv/lib/python3.8/site-packages/pip/_internal/pyproject.py new file mode 100644 index 000000000..0e8452f39 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/pyproject.py @@ -0,0 +1,185 @@ +import importlib.util +import os +import sys +from collections import namedtuple +from typing import Any, List, Optional + +if sys.version_info >= (3, 11): + import tomllib +else: + from pip._vendor import tomli as tomllib + +from pip._vendor.packaging.requirements import InvalidRequirement + +from pip._internal.exceptions import ( + InstallationError, + InvalidPyProjectBuildRequires, + MissingPyProjectBuildRequires, +) +from pip._internal.utils.packaging import get_requirement + + +def _is_list_of_str(obj: Any) -> bool: + return isinstance(obj, list) and all(isinstance(item, str) for item in obj) + + +def make_pyproject_path(unpacked_source_directory: str) -> str: + return os.path.join(unpacked_source_directory, "pyproject.toml") + + +BuildSystemDetails = namedtuple( + "BuildSystemDetails", ["requires", "backend", "check", "backend_path"] +) + + +def load_pyproject_toml( + use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str +) -> Optional[BuildSystemDetails]: + """Load the pyproject.toml file. + + Parameters: + use_pep517 - Has the user requested PEP 517 processing? None + means the user hasn't explicitly specified. + pyproject_toml - Location of the project's pyproject.toml file + setup_py - Location of the project's setup.py file + req_name - The name of the requirement we're processing (for + error reporting) + + Returns: + None if we should use the legacy code path, otherwise a tuple + ( + requirements from pyproject.toml, + name of PEP 517 backend, + requirements we should check are installed after setting + up the build environment + directory paths to import the backend from (backend-path), + relative to the project root. + ) + """ + has_pyproject = os.path.isfile(pyproject_toml) + has_setup = os.path.isfile(setup_py) + + if not has_pyproject and not has_setup: + raise InstallationError( + f"{req_name} does not appear to be a Python project: " + f"neither 'setup.py' nor 'pyproject.toml' found." + ) + + if has_pyproject: + with open(pyproject_toml, encoding="utf-8") as f: + pp_toml = tomllib.loads(f.read()) + build_system = pp_toml.get("build-system") + else: + build_system = None + + # The following cases must use PEP 517 + # We check for use_pep517 being non-None and falsy because that means + # the user explicitly requested --no-use-pep517. The value 0 as + # opposed to False can occur when the value is provided via an + # environment variable or config file option (due to the quirk of + # strtobool() returning an integer in pip's configuration code). + if has_pyproject and not has_setup: + if use_pep517 is not None and not use_pep517: + raise InstallationError( + "Disabling PEP 517 processing is invalid: " + "project does not have a setup.py" + ) + use_pep517 = True + elif build_system and "build-backend" in build_system: + if use_pep517 is not None and not use_pep517: + raise InstallationError( + "Disabling PEP 517 processing is invalid: " + "project specifies a build backend of {} " + "in pyproject.toml".format(build_system["build-backend"]) + ) + use_pep517 = True + + # If we haven't worked out whether to use PEP 517 yet, + # and the user hasn't explicitly stated a preference, + # we do so if the project has a pyproject.toml file + # or if we cannot import setuptools or wheels. + + # We fallback to PEP 517 when without setuptools or without the wheel package, + # so setuptools can be installed as a default build backend. + # For more info see: + # https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/9 + # https://github.com/pypa/pip/issues/8559 + elif use_pep517 is None: + use_pep517 = ( + has_pyproject + or not importlib.util.find_spec("setuptools") + or not importlib.util.find_spec("wheel") + ) + + # At this point, we know whether we're going to use PEP 517. + assert use_pep517 is not None + + # If we're using the legacy code path, there is nothing further + # for us to do here. + if not use_pep517: + return None + + if build_system is None: + # Either the user has a pyproject.toml with no build-system + # section, or the user has no pyproject.toml, but has opted in + # explicitly via --use-pep517. + # In the absence of any explicit backend specification, we + # assume the setuptools backend that most closely emulates the + # traditional direct setup.py execution, and require wheel and + # a version of setuptools that supports that backend. + + build_system = { + "requires": ["setuptools>=40.8.0"], + "build-backend": "setuptools.build_meta:__legacy__", + } + + # If we're using PEP 517, we have build system information (either + # from pyproject.toml, or defaulted by the code above). + # Note that at this point, we do not know if the user has actually + # specified a backend, though. + assert build_system is not None + + # Ensure that the build-system section in pyproject.toml conforms + # to PEP 518. + + # Specifying the build-system table but not the requires key is invalid + if "requires" not in build_system: + raise MissingPyProjectBuildRequires(package=req_name) + + # Error out if requires is not a list of strings + requires = build_system["requires"] + if not _is_list_of_str(requires): + raise InvalidPyProjectBuildRequires( + package=req_name, + reason="It is not a list of strings.", + ) + + # Each requirement must be valid as per PEP 508 + for requirement in requires: + try: + get_requirement(requirement) + except InvalidRequirement as error: + raise InvalidPyProjectBuildRequires( + package=req_name, + reason=f"It contains an invalid requirement: {requirement!r}", + ) from error + + backend = build_system.get("build-backend") + backend_path = build_system.get("backend-path", []) + check: List[str] = [] + if backend is None: + # If the user didn't specify a backend, we assume they want to use + # the setuptools backend. But we can't be sure they have included + # a version of setuptools which supplies the backend. So we + # make a note to check that this requirement is present once + # we have set up the environment. + # This is quite a lot of work to check for a very specific case. But + # the problem is, that case is potentially quite common - projects that + # adopted PEP 518 early for the ability to specify requirements to + # execute setup.py, but never considered needing to mention the build + # tools themselves. The original PEP 518 code had a similar check (but + # implemented in a different way). + backend = "setuptools.build_meta:__legacy__" + check = ["setuptools>=40.8.0"] + + return BuildSystemDetails(requires, backend, check, backend_path) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/req/__init__.py new file mode 100644 index 000000000..422d851d7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/req/__init__.py @@ -0,0 +1,90 @@ +import collections +import logging +from dataclasses import dataclass +from typing import Generator, List, Optional, Sequence, Tuple + +from pip._internal.utils.logging import indent_log + +from .req_file import parse_requirements +from .req_install import InstallRequirement +from .req_set import RequirementSet + +__all__ = [ + "RequirementSet", + "InstallRequirement", + "parse_requirements", + "install_given_reqs", +] + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class InstallationResult: + name: str + + +def _validate_requirements( + requirements: List[InstallRequirement], +) -> Generator[Tuple[str, InstallRequirement], None, None]: + for req in requirements: + assert req.name, f"invalid to-be-installed requirement: {req}" + yield req.name, req + + +def install_given_reqs( + requirements: List[InstallRequirement], + global_options: Sequence[str], + root: Optional[str], + home: Optional[str], + prefix: Optional[str], + warn_script_location: bool, + use_user_site: bool, + pycompile: bool, +) -> List[InstallationResult]: + """ + Install everything in the given list. + + (to be called after having downloaded and unpacked the packages) + """ + to_install = collections.OrderedDict(_validate_requirements(requirements)) + + if to_install: + logger.info( + "Installing collected packages: %s", + ", ".join(to_install.keys()), + ) + + installed = [] + + with indent_log(): + for req_name, requirement in to_install.items(): + if requirement.should_reinstall: + logger.info("Attempting uninstall: %s", req_name) + with indent_log(): + uninstalled_pathset = requirement.uninstall(auto_confirm=True) + else: + uninstalled_pathset = None + + try: + requirement.install( + global_options, + root=root, + home=home, + prefix=prefix, + warn_script_location=warn_script_location, + use_user_site=use_user_site, + pycompile=pycompile, + ) + except Exception: + # if install did not succeed, rollback previous uninstall + if uninstalled_pathset and not requirement.install_succeeded: + uninstalled_pathset.rollback() + raise + else: + if uninstalled_pathset and requirement.install_succeeded: + uninstalled_pathset.commit() + + installed.append(InstallationResult(req_name)) + + return installed diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c863456106d21174a21bda703fd9b3a47e4591ce GIT binary patch literal 2290 zcmaJ?&2QX96rZuZUfb*aNSdZeTfhY>x)RAADx|8aP%BzhNL4EOL5oFYc|EfmC;rOJ zY}%}xOVew`A3%{d5*O|qDh{0Z3pmb|Q*WF=AXM?jyV<5fVytXY9sUb1G_pueJTv^2nU+OQ zfo}ajbd{>lq|2~s>4i;B5*JpTCipJuF24fb(MA_bn}i~al92OmP1-kUMp-CwCXHJ$ z7t*?2h&a!}L|S+0&WL6am9=}LBB8L?4}u>MQ@d8=@5jB(Jf%H4rrR-1de`YLP4Ys8qIWCo^IowhHuG$) zxVSc8l=fq~CRiA4Q`Q4FcY8_PpQ&72ztrQgplbz)!y$#GanTFnOi%_H=|Mz#0Zd~N z1nb2fYr_%LF}McA(W-e@lP>7ZlOat;#fW8*%GXg-`UZ3rQ@#$QMAy-y5BG72w-7%o z0D2|<7JUcMd|L5JJkY*G80{DYL0Gko@%cF04U?F7B46v%wOQ&Y@s4eN+3RYo1LkGx z8t0U$6x?7eXTV-jY#o%}1d^Rnyyc*>8;Z9gR2Xx|p~1N6<0 z7y;ipRI&d4Yi-}zZ<1EpfAuJ^blnRG56i^ zS&%kLTxz9W8fC3C%Q~q)uVeH8Wfu%2+--;bj3HlO$7L@;yUAac?xb}HoTun3lr23} zl3$ess5@yZNoh|yVxe@DN093)Z}u&1$htKiIUcmx-Sz}$7iD9%6JVCh#%)F@qvU!Vi3)nsgc>-cP&MOhkV|7Lk27g((>=}UAUUgTQx)Ul~ z{3;OCZfOQKu!h~&M$OPH+`%Sxv4vMOL(?jW2GZI0mt(gy2c(vw+G(wOhMfm1vaTWv zIh2k0Lq$1LB}LxoSzvV_@(dKr^?AWuABi~O>oa@P#8}{CFiWK`Nya`P~|!`6ED4XZ4<&c{ibsB&wl|t CiD>lz literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/constructors.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/constructors.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5b5f4151c159e29263296787f52340d9e059ff7 GIT binary patch literal 13979 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_file.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_file.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67f32328caa6338d3333203ed497e4c29ad4487e GIT binary patch literal 15478 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_install.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_install.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e810cdfd2cfa91d35dfdd3d86a8a9952afa5166a GIT binary patch literal 24862 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_set.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/req/__pycache__/req_set.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0fc4b9c2586514e30cab44253132fc2f37cbbc34 GIT binary patch literal 4002 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 Tuple[str, Optional[str]]: + m = re.match(r"^(.+)(\[[^\]]+\])$", path) + extras = None + if m: + path_no_extras = m.group(1) + extras = m.group(2) + else: + path_no_extras = path + + return path_no_extras, extras + + +def convert_extras(extras: Optional[str]) -> Set[str]: + if not extras: + return set() + return get_requirement("placeholder" + extras.lower()).extras + + +def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requirement: + """ + Returns a new requirement based on the given one, with the supplied extras. If the + given requirement already has extras those are replaced (or dropped if no new extras + are given). + """ + match: Optional[re.Match[str]] = re.fullmatch( + # see https://peps.python.org/pep-0508/#complete-grammar + r"([\w\t .-]+)(\[[^\]]*\])?(.*)", + str(req), + flags=re.ASCII, + ) + # ireq.req is a valid requirement so the regex should always match + assert ( + match is not None + ), f"regex match on requirement {req} failed, this should never happen" + pre: Optional[str] = match.group(1) + post: Optional[str] = match.group(3) + assert ( + pre is not None and post is not None + ), f"regex group selection for requirement {req} failed, this should never happen" + extras: str = "[{}]".format(",".join(sorted(new_extras)) if new_extras else "") + return get_requirement(f"{pre}{extras}{post}") + + +def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]: + """Parses an editable requirement into: + - a requirement name + - an URL + - extras + - editable options + Accepted requirements: + svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir + .[some_extra] + """ + + url = editable_req + + # If a file path is specified with extras, strip off the extras. + url_no_extras, extras = _strip_extras(url) + + if os.path.isdir(url_no_extras): + # Treating it as code that has already been checked out + url_no_extras = path_to_url(url_no_extras) + + if url_no_extras.lower().startswith("file:"): + package_name = Link(url_no_extras).egg_fragment + if extras: + return ( + package_name, + url_no_extras, + get_requirement("placeholder" + extras.lower()).extras, + ) + else: + return package_name, url_no_extras, set() + + for version_control in vcs: + if url.lower().startswith(f"{version_control}:"): + url = f"{version_control}+{url}" + break + + link = Link(url) + + if not link.is_vcs: + backends = ", ".join(vcs.all_schemes) + raise InstallationError( + f"{editable_req} is not a valid editable requirement. " + f"It should either be a path to a local project or a VCS URL " + f"(beginning with {backends})." + ) + + package_name = link.egg_fragment + if not package_name: + raise InstallationError( + f"Could not detect requirement name for '{editable_req}', " + "please specify one with #egg=your_package_name" + ) + return package_name, url, set() + + +def check_first_requirement_in_file(filename: str) -> None: + """Check if file is parsable as a requirements file. + + This is heavily based on ``pkg_resources.parse_requirements``, but + simplified to just check the first meaningful line. + + :raises InvalidRequirement: If the first meaningful line cannot be parsed + as an requirement. + """ + with open(filename, encoding="utf-8", errors="ignore") as f: + # Create a steppable iterator, so we can handle \-continuations. + lines = ( + line + for line in (line.strip() for line in f) + if line and not line.startswith("#") # Skip blank lines/comments. + ) + + for line in lines: + # Drop comments -- a hash without a space may be in a URL. + if " #" in line: + line = line[: line.find(" #")] + # If there is a line continuation, drop it, and append the next line. + if line.endswith("\\"): + line = line[:-2].strip() + next(lines, "") + get_requirement(line) + return + + +def deduce_helpful_msg(req: str) -> str: + """Returns helpful msg in case requirements file does not exist, + or cannot be parsed. + + :params req: Requirements file path + """ + if not os.path.exists(req): + return f" File '{req}' does not exist." + msg = " The path does exist. " + # Try to parse and check if it is a requirements file. + try: + check_first_requirement_in_file(req) + except InvalidRequirement: + logger.debug("Cannot parse '%s' as requirements file", req) + else: + msg += ( + f"The argument you provided " + f"({req}) appears to be a" + f" requirements file. If that is the" + f" case, use the '-r' flag to install" + f" the packages specified within it." + ) + return msg + + +@dataclass(frozen=True) +class RequirementParts: + requirement: Optional[Requirement] + link: Optional[Link] + markers: Optional[Marker] + extras: Set[str] + + +def parse_req_from_editable(editable_req: str) -> RequirementParts: + name, url, extras_override = parse_editable(editable_req) + + if name is not None: + try: + req: Optional[Requirement] = get_requirement(name) + except InvalidRequirement as exc: + raise InstallationError(f"Invalid requirement: {name!r}: {exc}") + else: + req = None + + link = Link(url) + + return RequirementParts(req, link, None, extras_override) + + +# ---- The actual constructors follow ---- + + +def install_req_from_editable( + editable_req: str, + comes_from: Optional[Union[InstallRequirement, str]] = None, + *, + use_pep517: Optional[bool] = None, + isolated: bool = False, + global_options: Optional[List[str]] = None, + hash_options: Optional[Dict[str, List[str]]] = None, + constraint: bool = False, + user_supplied: bool = False, + permit_editable_wheels: bool = False, + config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, +) -> InstallRequirement: + parts = parse_req_from_editable(editable_req) + + return InstallRequirement( + parts.requirement, + comes_from=comes_from, + user_supplied=user_supplied, + editable=True, + permit_editable_wheels=permit_editable_wheels, + link=parts.link, + constraint=constraint, + use_pep517=use_pep517, + isolated=isolated, + global_options=global_options, + hash_options=hash_options, + config_settings=config_settings, + extras=parts.extras, + ) + + +def _looks_like_path(name: str) -> bool: + """Checks whether the string "looks like" a path on the filesystem. + + This does not check whether the target actually exists, only judge from the + appearance. + + Returns true if any of the following conditions is true: + * a path separator is found (either os.path.sep or os.path.altsep); + * a dot is found (which represents the current directory). + """ + if os.path.sep in name: + return True + if os.path.altsep is not None and os.path.altsep in name: + return True + if name.startswith("."): + return True + return False + + +def _get_url_from_path(path: str, name: str) -> Optional[str]: + """ + First, it checks whether a provided path is an installable directory. If it + is, returns the path. + + If false, check if the path is an archive file (such as a .whl). + The function checks if the path is a file. If false, if the path has + an @, it will treat it as a PEP 440 URL requirement and return the path. + """ + if _looks_like_path(name) and os.path.isdir(path): + if is_installable_dir(path): + return path_to_url(path) + # TODO: The is_installable_dir test here might not be necessary + # now that it is done in load_pyproject_toml too. + raise InstallationError( + f"Directory {name!r} is not installable. Neither 'setup.py' " + "nor 'pyproject.toml' found." + ) + if not is_archive_file(path): + return None + if os.path.isfile(path): + return path_to_url(path) + urlreq_parts = name.split("@", 1) + if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]): + # If the path contains '@' and the part before it does not look + # like a path, try to treat it as a PEP 440 URL req instead. + return None + logger.warning( + "Requirement %r looks like a filename, but the file does not exist", + name, + ) + return path_to_url(path) + + +def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts: + if is_url(name): + marker_sep = "; " + else: + marker_sep = ";" + if marker_sep in name: + name, markers_as_string = name.split(marker_sep, 1) + markers_as_string = markers_as_string.strip() + if not markers_as_string: + markers = None + else: + markers = Marker(markers_as_string) + else: + markers = None + name = name.strip() + req_as_string = None + path = os.path.normpath(os.path.abspath(name)) + link = None + extras_as_string = None + + if is_url(name): + link = Link(name) + else: + p, extras_as_string = _strip_extras(path) + url = _get_url_from_path(p, name) + if url is not None: + link = Link(url) + + # it's a local file, dir, or url + if link: + # Handle relative file URLs + if link.scheme == "file" and re.search(r"\.\./", link.url): + link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path)))) + # wheel file + if link.is_wheel: + wheel = Wheel(link.filename) # can raise InvalidWheelFilename + req_as_string = f"{wheel.name}=={wheel.version}" + else: + # set the req to the egg fragment. when it's not there, this + # will become an 'unnamed' requirement + req_as_string = link.egg_fragment + + # a requirement specifier + else: + req_as_string = name + + extras = convert_extras(extras_as_string) + + def with_source(text: str) -> str: + if not line_source: + return text + return f"{text} (from {line_source})" + + def _parse_req_string(req_as_string: str) -> Requirement: + try: + return get_requirement(req_as_string) + except InvalidRequirement as exc: + if os.path.sep in req_as_string: + add_msg = "It looks like a path." + add_msg += deduce_helpful_msg(req_as_string) + elif "=" in req_as_string and not any( + op in req_as_string for op in operators + ): + add_msg = "= is not a valid operator. Did you mean == ?" + else: + add_msg = "" + msg = with_source(f"Invalid requirement: {req_as_string!r}: {exc}") + if add_msg: + msg += f"\nHint: {add_msg}" + raise InstallationError(msg) + + if req_as_string is not None: + req: Optional[Requirement] = _parse_req_string(req_as_string) + else: + req = None + + return RequirementParts(req, link, markers, extras) + + +def install_req_from_line( + name: str, + comes_from: Optional[Union[str, InstallRequirement]] = None, + *, + use_pep517: Optional[bool] = None, + isolated: bool = False, + global_options: Optional[List[str]] = None, + hash_options: Optional[Dict[str, List[str]]] = None, + constraint: bool = False, + line_source: Optional[str] = None, + user_supplied: bool = False, + config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, +) -> InstallRequirement: + """Creates an InstallRequirement from a name, which might be a + requirement, directory containing 'setup.py', filename, or URL. + + :param line_source: An optional string describing where the line is from, + for logging purposes in case of an error. + """ + parts = parse_req_from_line(name, line_source) + + return InstallRequirement( + parts.requirement, + comes_from, + link=parts.link, + markers=parts.markers, + use_pep517=use_pep517, + isolated=isolated, + global_options=global_options, + hash_options=hash_options, + config_settings=config_settings, + constraint=constraint, + extras=parts.extras, + user_supplied=user_supplied, + ) + + +def install_req_from_req_string( + req_string: str, + comes_from: Optional[InstallRequirement] = None, + isolated: bool = False, + use_pep517: Optional[bool] = None, + user_supplied: bool = False, +) -> InstallRequirement: + try: + req = get_requirement(req_string) + except InvalidRequirement as exc: + raise InstallationError(f"Invalid requirement: {req_string!r}: {exc}") + + domains_not_allowed = [ + PyPI.file_storage_domain, + TestPyPI.file_storage_domain, + ] + if ( + req.url + and comes_from + and comes_from.link + and comes_from.link.netloc in domains_not_allowed + ): + # Explicitly disallow pypi packages that depend on external urls + raise InstallationError( + "Packages installed from PyPI cannot depend on packages " + "which are not also hosted on PyPI.\n" + f"{comes_from.name} depends on {req} " + ) + + return InstallRequirement( + req, + comes_from, + isolated=isolated, + use_pep517=use_pep517, + user_supplied=user_supplied, + ) + + +def install_req_from_parsed_requirement( + parsed_req: ParsedRequirement, + isolated: bool = False, + use_pep517: Optional[bool] = None, + user_supplied: bool = False, + config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, +) -> InstallRequirement: + if parsed_req.is_editable: + req = install_req_from_editable( + parsed_req.requirement, + comes_from=parsed_req.comes_from, + use_pep517=use_pep517, + constraint=parsed_req.constraint, + isolated=isolated, + user_supplied=user_supplied, + config_settings=config_settings, + ) + + else: + req = install_req_from_line( + parsed_req.requirement, + comes_from=parsed_req.comes_from, + use_pep517=use_pep517, + isolated=isolated, + global_options=( + parsed_req.options.get("global_options", []) + if parsed_req.options + else [] + ), + hash_options=( + parsed_req.options.get("hashes", {}) if parsed_req.options else {} + ), + constraint=parsed_req.constraint, + line_source=parsed_req.line_source, + user_supplied=user_supplied, + config_settings=config_settings, + ) + return req + + +def install_req_from_link_and_ireq( + link: Link, ireq: InstallRequirement +) -> InstallRequirement: + return InstallRequirement( + req=ireq.req, + comes_from=ireq.comes_from, + editable=ireq.editable, + link=link, + markers=ireq.markers, + use_pep517=ireq.use_pep517, + isolated=ireq.isolated, + global_options=ireq.global_options, + hash_options=ireq.hash_options, + config_settings=ireq.config_settings, + user_supplied=ireq.user_supplied, + ) + + +def install_req_drop_extras(ireq: InstallRequirement) -> InstallRequirement: + """ + Creates a new InstallationRequirement using the given template but without + any extras. Sets the original requirement as the new one's parent + (comes_from). + """ + return InstallRequirement( + req=( + _set_requirement_extras(ireq.req, set()) if ireq.req is not None else None + ), + comes_from=ireq, + editable=ireq.editable, + link=ireq.link, + markers=ireq.markers, + use_pep517=ireq.use_pep517, + isolated=ireq.isolated, + global_options=ireq.global_options, + hash_options=ireq.hash_options, + constraint=ireq.constraint, + extras=[], + config_settings=ireq.config_settings, + user_supplied=ireq.user_supplied, + permit_editable_wheels=ireq.permit_editable_wheels, + ) + + +def install_req_extend_extras( + ireq: InstallRequirement, + extras: Collection[str], +) -> InstallRequirement: + """ + Returns a copy of an installation requirement with some additional extras. + Makes a shallow copy of the ireq object. + """ + result = copy.copy(ireq) + result.extras = {*ireq.extras, *extras} + result.req = ( + _set_requirement_extras(ireq.req, result.extras) + if ireq.req is not None + else None + ) + return result diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/req_file.py b/venv/lib/python3.8/site-packages/pip/_internal/req/req_file.py new file mode 100644 index 000000000..f6ba70fe7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/req/req_file.py @@ -0,0 +1,623 @@ +""" +Requirements file parsing +""" + +import codecs +import locale +import logging +import optparse +import os +import re +import shlex +import sys +import urllib.parse +from dataclasses import dataclass +from optparse import Values +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Generator, + Iterable, + List, + NoReturn, + Optional, + Tuple, +) + +from pip._internal.cli import cmdoptions +from pip._internal.exceptions import InstallationError, RequirementsFileParseError +from pip._internal.models.search_scope import SearchScope + +if TYPE_CHECKING: + from pip._internal.index.package_finder import PackageFinder + from pip._internal.network.session import PipSession + +__all__ = ["parse_requirements"] + +ReqFileLines = Iterable[Tuple[int, str]] + +LineParser = Callable[[str], Tuple[str, Values]] + +SCHEME_RE = re.compile(r"^(http|https|file):", re.I) +COMMENT_RE = re.compile(r"(^|\s+)#.*$") + +# Matches environment variable-style values in '${MY_VARIABLE_1}' with the +# variable name consisting of only uppercase letters, digits or the '_' +# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1, +# 2013 Edition. +ENV_VAR_RE = re.compile(r"(?P\$\{(?P[A-Z0-9_]+)\})") + +SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [ + cmdoptions.index_url, + cmdoptions.extra_index_url, + cmdoptions.no_index, + cmdoptions.constraints, + cmdoptions.requirements, + cmdoptions.editable, + cmdoptions.find_links, + cmdoptions.no_binary, + cmdoptions.only_binary, + cmdoptions.prefer_binary, + cmdoptions.require_hashes, + cmdoptions.pre, + cmdoptions.trusted_host, + cmdoptions.use_new_feature, +] + +# options to be passed to requirements +SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [ + cmdoptions.global_options, + cmdoptions.hash, + cmdoptions.config_settings, +] + +SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [ + cmdoptions.config_settings, +] + + +# the 'dest' string values +SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ] +SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [ + str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ +] + +# order of BOMS is important: codecs.BOM_UTF16_LE is a prefix of codecs.BOM_UTF32_LE +# so data.startswith(BOM_UTF16_LE) would be true for UTF32_LE data +BOMS: List[Tuple[bytes, str]] = [ + (codecs.BOM_UTF8, "utf-8"), + (codecs.BOM_UTF32, "utf-32"), + (codecs.BOM_UTF32_BE, "utf-32-be"), + (codecs.BOM_UTF32_LE, "utf-32-le"), + (codecs.BOM_UTF16, "utf-16"), + (codecs.BOM_UTF16_BE, "utf-16-be"), + (codecs.BOM_UTF16_LE, "utf-16-le"), +] + +PEP263_ENCODING_RE = re.compile(rb"coding[:=]\s*([-\w.]+)") +DEFAULT_ENCODING = "utf-8" + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class ParsedRequirement: + # TODO: replace this with slots=True when dropping Python 3.9 support. + __slots__ = ( + "requirement", + "is_editable", + "comes_from", + "constraint", + "options", + "line_source", + ) + + requirement: str + is_editable: bool + comes_from: str + constraint: bool + options: Optional[Dict[str, Any]] + line_source: Optional[str] + + +@dataclass(frozen=True) +class ParsedLine: + __slots__ = ("filename", "lineno", "args", "opts", "constraint") + + filename: str + lineno: int + args: str + opts: Values + constraint: bool + + @property + def is_editable(self) -> bool: + return bool(self.opts.editables) + + @property + def requirement(self) -> Optional[str]: + if self.args: + return self.args + elif self.is_editable: + # We don't support multiple -e on one line + return self.opts.editables[0] + return None + + +def parse_requirements( + filename: str, + session: "PipSession", + finder: Optional["PackageFinder"] = None, + options: Optional[optparse.Values] = None, + constraint: bool = False, +) -> Generator[ParsedRequirement, None, None]: + """Parse a requirements file and yield ParsedRequirement instances. + + :param filename: Path or url of requirements file. + :param session: PipSession instance. + :param finder: Instance of pip.index.PackageFinder. + :param options: cli options. + :param constraint: If true, parsing a constraint file rather than + requirements file. + """ + line_parser = get_line_parser(finder) + parser = RequirementsFileParser(session, line_parser) + + for parsed_line in parser.parse(filename, constraint): + parsed_req = handle_line( + parsed_line, options=options, finder=finder, session=session + ) + if parsed_req is not None: + yield parsed_req + + +def preprocess(content: str) -> ReqFileLines: + """Split, filter, and join lines, and return a line iterator + + :param content: the content of the requirements file + """ + lines_enum: ReqFileLines = enumerate(content.splitlines(), start=1) + lines_enum = join_lines(lines_enum) + lines_enum = ignore_comments(lines_enum) + lines_enum = expand_env_variables(lines_enum) + return lines_enum + + +def handle_requirement_line( + line: ParsedLine, + options: Optional[optparse.Values] = None, +) -> ParsedRequirement: + # preserve for the nested code path + line_comes_from = "{} {} (line {})".format( + "-c" if line.constraint else "-r", + line.filename, + line.lineno, + ) + + assert line.requirement is not None + + # get the options that apply to requirements + if line.is_editable: + supported_dest = SUPPORTED_OPTIONS_EDITABLE_REQ_DEST + else: + supported_dest = SUPPORTED_OPTIONS_REQ_DEST + req_options = {} + for dest in supported_dest: + if dest in line.opts.__dict__ and line.opts.__dict__[dest]: + req_options[dest] = line.opts.__dict__[dest] + + line_source = f"line {line.lineno} of {line.filename}" + return ParsedRequirement( + requirement=line.requirement, + is_editable=line.is_editable, + comes_from=line_comes_from, + constraint=line.constraint, + options=req_options, + line_source=line_source, + ) + + +def handle_option_line( + opts: Values, + filename: str, + lineno: int, + finder: Optional["PackageFinder"] = None, + options: Optional[optparse.Values] = None, + session: Optional["PipSession"] = None, +) -> None: + if opts.hashes: + logger.warning( + "%s line %s has --hash but no requirement, and will be ignored.", + filename, + lineno, + ) + + if options: + # percolate options upward + if opts.require_hashes: + options.require_hashes = opts.require_hashes + if opts.features_enabled: + options.features_enabled.extend( + f for f in opts.features_enabled if f not in options.features_enabled + ) + + # set finder options + if finder: + find_links = finder.find_links + index_urls = finder.index_urls + no_index = finder.search_scope.no_index + if opts.no_index is True: + no_index = True + index_urls = [] + if opts.index_url and not no_index: + index_urls = [opts.index_url] + if opts.extra_index_urls and not no_index: + index_urls.extend(opts.extra_index_urls) + if opts.find_links: + # FIXME: it would be nice to keep track of the source + # of the find_links: support a find-links local path + # relative to a requirements file. + value = opts.find_links[0] + req_dir = os.path.dirname(os.path.abspath(filename)) + relative_to_reqs_file = os.path.join(req_dir, value) + if os.path.exists(relative_to_reqs_file): + value = relative_to_reqs_file + find_links.append(value) + + if session: + # We need to update the auth urls in session + session.update_index_urls(index_urls) + + search_scope = SearchScope( + find_links=find_links, + index_urls=index_urls, + no_index=no_index, + ) + finder.search_scope = search_scope + + if opts.pre: + finder.set_allow_all_prereleases() + + if opts.prefer_binary: + finder.set_prefer_binary() + + if session: + for host in opts.trusted_hosts or []: + source = f"line {lineno} of {filename}" + session.add_trusted_host(host, source=source) + + +def handle_line( + line: ParsedLine, + options: Optional[optparse.Values] = None, + finder: Optional["PackageFinder"] = None, + session: Optional["PipSession"] = None, +) -> Optional[ParsedRequirement]: + """Handle a single parsed requirements line; This can result in + creating/yielding requirements, or updating the finder. + + :param line: The parsed line to be processed. + :param options: CLI options. + :param finder: The finder - updated by non-requirement lines. + :param session: The session - updated by non-requirement lines. + + Returns a ParsedRequirement object if the line is a requirement line, + otherwise returns None. + + For lines that contain requirements, the only options that have an effect + are from SUPPORTED_OPTIONS_REQ, and they are scoped to the + requirement. Other options from SUPPORTED_OPTIONS may be present, but are + ignored. + + For lines that do not contain requirements, the only options that have an + effect are from SUPPORTED_OPTIONS. Options from SUPPORTED_OPTIONS_REQ may + be present, but are ignored. These lines may contain multiple options + (although our docs imply only one is supported), and all our parsed and + affect the finder. + """ + + if line.requirement is not None: + parsed_req = handle_requirement_line(line, options) + return parsed_req + else: + handle_option_line( + line.opts, + line.filename, + line.lineno, + finder, + options, + session, + ) + return None + + +class RequirementsFileParser: + def __init__( + self, + session: "PipSession", + line_parser: LineParser, + ) -> None: + self._session = session + self._line_parser = line_parser + + def parse( + self, filename: str, constraint: bool + ) -> Generator[ParsedLine, None, None]: + """Parse a given file, yielding parsed lines.""" + yield from self._parse_and_recurse( + filename, constraint, [{os.path.abspath(filename): None}] + ) + + def _parse_and_recurse( + self, + filename: str, + constraint: bool, + parsed_files_stack: List[Dict[str, Optional[str]]], + ) -> Generator[ParsedLine, None, None]: + for line in self._parse_file(filename, constraint): + if line.requirement is None and ( + line.opts.requirements or line.opts.constraints + ): + # parse a nested requirements file + if line.opts.requirements: + req_path = line.opts.requirements[0] + nested_constraint = False + else: + req_path = line.opts.constraints[0] + nested_constraint = True + + # original file is over http + if SCHEME_RE.search(filename): + # do a url join so relative paths work + req_path = urllib.parse.urljoin(filename, req_path) + # original file and nested file are paths + elif not SCHEME_RE.search(req_path): + # do a join so relative paths work + # and then abspath so that we can identify recursive references + req_path = os.path.abspath( + os.path.join( + os.path.dirname(filename), + req_path, + ) + ) + parsed_files = parsed_files_stack[0] + if req_path in parsed_files: + initial_file = parsed_files[req_path] + tail = ( + f" and again in {initial_file}" + if initial_file is not None + else "" + ) + raise RequirementsFileParseError( + f"{req_path} recursively references itself in {filename}{tail}" + ) + # Keeping a track where was each file first included in + new_parsed_files = parsed_files.copy() + new_parsed_files[req_path] = filename + yield from self._parse_and_recurse( + req_path, nested_constraint, [new_parsed_files, *parsed_files_stack] + ) + else: + yield line + + def _parse_file( + self, filename: str, constraint: bool + ) -> Generator[ParsedLine, None, None]: + _, content = get_file_content(filename, self._session) + + lines_enum = preprocess(content) + + for line_number, line in lines_enum: + try: + args_str, opts = self._line_parser(line) + except OptionParsingError as e: + # add offending line + msg = f"Invalid requirement: {line}\n{e.msg}" + raise RequirementsFileParseError(msg) + + yield ParsedLine( + filename, + line_number, + args_str, + opts, + constraint, + ) + + +def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser: + def parse_line(line: str) -> Tuple[str, Values]: + # Build new parser for each line since it accumulates appendable + # options. + parser = build_parser() + defaults = parser.get_default_values() + defaults.index_url = None + if finder: + defaults.format_control = finder.format_control + + args_str, options_str = break_args_options(line) + + try: + options = shlex.split(options_str) + except ValueError as e: + raise OptionParsingError(f"Could not split options: {options_str}") from e + + opts, _ = parser.parse_args(options, defaults) + + return args_str, opts + + return parse_line + + +def break_args_options(line: str) -> Tuple[str, str]: + """Break up the line into an args and options string. We only want to shlex + (and then optparse) the options, not the args. args can contain markers + which are corrupted by shlex. + """ + tokens = line.split(" ") + args = [] + options = tokens[:] + for token in tokens: + if token.startswith("-") or token.startswith("--"): + break + else: + args.append(token) + options.pop(0) + return " ".join(args), " ".join(options) + + +class OptionParsingError(Exception): + def __init__(self, msg: str) -> None: + self.msg = msg + + +def build_parser() -> optparse.OptionParser: + """ + Return a parser for parsing requirement lines + """ + parser = optparse.OptionParser(add_help_option=False) + + option_factories = SUPPORTED_OPTIONS + SUPPORTED_OPTIONS_REQ + for option_factory in option_factories: + option = option_factory() + parser.add_option(option) + + # By default optparse sys.exits on parsing errors. We want to wrap + # that in our own exception. + def parser_exit(self: Any, msg: str) -> "NoReturn": + raise OptionParsingError(msg) + + # NOTE: mypy disallows assigning to a method + # https://github.com/python/mypy/issues/2427 + parser.exit = parser_exit # type: ignore + + return parser + + +def join_lines(lines_enum: ReqFileLines) -> ReqFileLines: + """Joins a line ending in '\' with the previous line (except when following + comments). The joined line takes on the index of the first line. + """ + primary_line_number = None + new_line: List[str] = [] + for line_number, line in lines_enum: + if not line.endswith("\\") or COMMENT_RE.match(line): + if COMMENT_RE.match(line): + # this ensures comments are always matched later + line = " " + line + if new_line: + new_line.append(line) + assert primary_line_number is not None + yield primary_line_number, "".join(new_line) + new_line = [] + else: + yield line_number, line + else: + if not new_line: + primary_line_number = line_number + new_line.append(line.strip("\\")) + + # last line contains \ + if new_line: + assert primary_line_number is not None + yield primary_line_number, "".join(new_line) + + # TODO: handle space after '\'. + + +def ignore_comments(lines_enum: ReqFileLines) -> ReqFileLines: + """ + Strips comments and filter empty lines. + """ + for line_number, line in lines_enum: + line = COMMENT_RE.sub("", line) + line = line.strip() + if line: + yield line_number, line + + +def expand_env_variables(lines_enum: ReqFileLines) -> ReqFileLines: + """Replace all environment variables that can be retrieved via `os.getenv`. + + The only allowed format for environment variables defined in the + requirement file is `${MY_VARIABLE_1}` to ensure two things: + + 1. Strings that contain a `$` aren't accidentally (partially) expanded. + 2. Ensure consistency across platforms for requirement files. + + These points are the result of a discussion on the `github pull + request #3514 `_. + + Valid characters in variable names follow the `POSIX standard + `_ and are limited + to uppercase letter, digits and the `_` (underscore). + """ + for line_number, line in lines_enum: + for env_var, var_name in ENV_VAR_RE.findall(line): + value = os.getenv(var_name) + if not value: + continue + + line = line.replace(env_var, value) + + yield line_number, line + + +def get_file_content(url: str, session: "PipSession") -> Tuple[str, str]: + """Gets the content of a file; it may be a filename, file: URL, or + http: URL. Returns (location, content). Content is unicode. + Respects # -*- coding: declarations on the retrieved files. + + :param url: File path or url. + :param session: PipSession instance. + """ + scheme = urllib.parse.urlsplit(url).scheme + # Pip has special support for file:// URLs (LocalFSAdapter). + if scheme in ["http", "https", "file"]: + # Delay importing heavy network modules until absolutely necessary. + from pip._internal.network.utils import raise_for_status + + resp = session.get(url) + raise_for_status(resp) + return resp.url, resp.text + + # Assume this is a bare path. + try: + with open(url, "rb") as f: + raw_content = f.read() + except OSError as exc: + raise InstallationError(f"Could not open requirements file: {exc}") + + content = _decode_req_file(raw_content, url) + + return url, content + + +def _decode_req_file(data: bytes, url: str) -> str: + for bom, encoding in BOMS: + if data.startswith(bom): + return data[len(bom) :].decode(encoding) + + for line in data.split(b"\n")[:2]: + if line[0:1] == b"#": + result = PEP263_ENCODING_RE.search(line) + if result is not None: + encoding = result.groups()[0].decode("ascii") + return data.decode(encoding) + + try: + return data.decode(DEFAULT_ENCODING) + except UnicodeDecodeError: + locale_encoding = locale.getpreferredencoding(False) or sys.getdefaultencoding() + logging.warning( + "unable to decode data from %s with default encoding %s, " + "falling back to encoding from locale: %s. " + "If this is intentional you should specify the encoding with a " + "PEP-263 style comment, e.g. '# -*- coding: %s -*-'", + url, + DEFAULT_ENCODING, + locale_encoding, + locale_encoding, + ) + return data.decode(locale_encoding) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/req_install.py b/venv/lib/python3.8/site-packages/pip/_internal/req/req_install.py new file mode 100644 index 000000000..3262d8265 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/req/req_install.py @@ -0,0 +1,934 @@ +import functools +import logging +import os +import shutil +import sys +import uuid +import zipfile +from optparse import Values +from pathlib import Path +from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union + +from pip._vendor.packaging.markers import Marker +from pip._vendor.packaging.requirements import Requirement +from pip._vendor.packaging.specifiers import SpecifierSet +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import Version +from pip._vendor.packaging.version import parse as parse_version +from pip._vendor.pyproject_hooks import BuildBackendHookCaller + +from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment +from pip._internal.exceptions import InstallationError, PreviousBuildDirError +from pip._internal.locations import get_scheme +from pip._internal.metadata import ( + BaseDistribution, + get_default_environment, + get_directory_distribution, + get_wheel_distribution, +) +from pip._internal.metadata.base import FilesystemWheel +from pip._internal.models.direct_url import DirectUrl +from pip._internal.models.link import Link +from pip._internal.operations.build.metadata import generate_metadata +from pip._internal.operations.build.metadata_editable import generate_editable_metadata +from pip._internal.operations.build.metadata_legacy import ( + generate_metadata as generate_metadata_legacy, +) +from pip._internal.operations.install.editable_legacy import ( + install_editable as install_editable_legacy, +) +from pip._internal.operations.install.wheel import install_wheel +from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path +from pip._internal.req.req_uninstall import UninstallPathSet +from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.hashes import Hashes +from pip._internal.utils.misc import ( + ConfiguredBuildBackendHookCaller, + ask_path_exists, + backup_dir, + display_path, + hide_url, + is_installable_dir, + redact_auth_from_requirement, + redact_auth_from_url, +) +from pip._internal.utils.packaging import get_requirement +from pip._internal.utils.subprocess import runner_with_spinner_message +from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds +from pip._internal.utils.unpacking import unpack_file +from pip._internal.utils.virtualenv import running_under_virtualenv +from pip._internal.vcs import vcs + +logger = logging.getLogger(__name__) + + +class InstallRequirement: + """ + Represents something that may be installed later on, may have information + about where to fetch the relevant requirement and also contains logic for + installing the said requirement. + """ + + def __init__( + self, + req: Optional[Requirement], + comes_from: Optional[Union[str, "InstallRequirement"]], + editable: bool = False, + link: Optional[Link] = None, + markers: Optional[Marker] = None, + use_pep517: Optional[bool] = None, + isolated: bool = False, + *, + global_options: Optional[List[str]] = None, + hash_options: Optional[Dict[str, List[str]]] = None, + config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + constraint: bool = False, + extras: Collection[str] = (), + user_supplied: bool = False, + permit_editable_wheels: bool = False, + ) -> None: + assert req is None or isinstance(req, Requirement), req + self.req = req + self.comes_from = comes_from + self.constraint = constraint + self.editable = editable + self.permit_editable_wheels = permit_editable_wheels + + # source_dir is the local directory where the linked requirement is + # located, or unpacked. In case unpacking is needed, creating and + # populating source_dir is done by the RequirementPreparer. Note this + # is not necessarily the directory where pyproject.toml or setup.py is + # located - that one is obtained via unpacked_source_directory. + self.source_dir: Optional[str] = None + if self.editable: + assert link + if link.is_file: + self.source_dir = os.path.normpath(os.path.abspath(link.file_path)) + + # original_link is the direct URL that was provided by the user for the + # requirement, either directly or via a constraints file. + if link is None and req and req.url: + # PEP 508 URL requirement + link = Link(req.url) + self.link = self.original_link = link + + # When this InstallRequirement is a wheel obtained from the cache of locally + # built wheels, this is the source link corresponding to the cache entry, which + # was used to download and build the cached wheel. + self.cached_wheel_source_link: Optional[Link] = None + + # Information about the location of the artifact that was downloaded . This + # property is guaranteed to be set in resolver results. + self.download_info: Optional[DirectUrl] = None + + # Path to any downloaded or already-existing package. + self.local_file_path: Optional[str] = None + if self.link and self.link.is_file: + self.local_file_path = self.link.file_path + + if extras: + self.extras = extras + elif req: + self.extras = req.extras + else: + self.extras = set() + if markers is None and req: + markers = req.marker + self.markers = markers + + # This holds the Distribution object if this requirement is already installed. + self.satisfied_by: Optional[BaseDistribution] = None + # Whether the installation process should try to uninstall an existing + # distribution before installing this requirement. + self.should_reinstall = False + # Temporary build location + self._temp_build_dir: Optional[TempDirectory] = None + # Set to True after successful installation + self.install_succeeded: Optional[bool] = None + # Supplied options + self.global_options = global_options if global_options else [] + self.hash_options = hash_options if hash_options else {} + self.config_settings = config_settings + # Set to True after successful preparation of this requirement + self.prepared = False + # User supplied requirement are explicitly requested for installation + # by the user via CLI arguments or requirements files, as opposed to, + # e.g. dependencies, extras or constraints. + self.user_supplied = user_supplied + + self.isolated = isolated + self.build_env: BuildEnvironment = NoOpBuildEnvironment() + + # For PEP 517, the directory where we request the project metadata + # gets stored. We need this to pass to build_wheel, so the backend + # can ensure that the wheel matches the metadata (see the PEP for + # details). + self.metadata_directory: Optional[str] = None + + # The static build requirements (from pyproject.toml) + self.pyproject_requires: Optional[List[str]] = None + + # Build requirements that we will check are available + self.requirements_to_check: List[str] = [] + + # The PEP 517 backend we should use to build the project + self.pep517_backend: Optional[BuildBackendHookCaller] = None + + # Are we using PEP 517 for this requirement? + # After pyproject.toml has been loaded, the only valid values are True + # and False. Before loading, None is valid (meaning "use the default"). + # Setting an explicit value before loading pyproject.toml is supported, + # but after loading this flag should be treated as read only. + self.use_pep517 = use_pep517 + + # If config settings are provided, enforce PEP 517. + if self.config_settings: + if self.use_pep517 is False: + logger.warning( + "--no-use-pep517 ignored for %s " + "because --config-settings are specified.", + self, + ) + self.use_pep517 = True + + # This requirement needs more preparation before it can be built + self.needs_more_preparation = False + + # This requirement needs to be unpacked before it can be installed. + self._archive_source: Optional[Path] = None + + def __str__(self) -> str: + if self.req: + s = redact_auth_from_requirement(self.req) + if self.link: + s += f" from {redact_auth_from_url(self.link.url)}" + elif self.link: + s = redact_auth_from_url(self.link.url) + else: + s = "" + if self.satisfied_by is not None: + if self.satisfied_by.location is not None: + location = display_path(self.satisfied_by.location) + else: + location = "" + s += f" in {location}" + if self.comes_from: + if isinstance(self.comes_from, str): + comes_from: Optional[str] = self.comes_from + else: + comes_from = self.comes_from.from_path() + if comes_from: + s += f" (from {comes_from})" + return s + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} object: " + f"{str(self)} editable={self.editable!r}>" + ) + + def format_debug(self) -> str: + """An un-tested helper for getting state, for debugging.""" + attributes = vars(self) + names = sorted(attributes) + + state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names)) + return "<{name} object: {{{state}}}>".format( + name=self.__class__.__name__, + state=", ".join(state), + ) + + # Things that are valid for all kinds of requirements? + @property + def name(self) -> Optional[str]: + if self.req is None: + return None + return self.req.name + + @functools.cached_property + def supports_pyproject_editable(self) -> bool: + if not self.use_pep517: + return False + assert self.pep517_backend + with self.build_env: + runner = runner_with_spinner_message( + "Checking if build backend supports build_editable" + ) + with self.pep517_backend.subprocess_runner(runner): + return "build_editable" in self.pep517_backend._supported_features() + + @property + def specifier(self) -> SpecifierSet: + assert self.req is not None + return self.req.specifier + + @property + def is_direct(self) -> bool: + """Whether this requirement was specified as a direct URL.""" + return self.original_link is not None + + @property + def is_pinned(self) -> bool: + """Return whether I am pinned to an exact version. + + For example, some-package==1.2 is pinned; some-package>1.2 is not. + """ + assert self.req is not None + specifiers = self.req.specifier + return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="} + + def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool: + if not extras_requested: + # Provide an extra to safely evaluate the markers + # without matching any extra + extras_requested = ("",) + if self.markers is not None: + return any( + self.markers.evaluate({"extra": extra}) for extra in extras_requested + ) + else: + return True + + @property + def has_hash_options(self) -> bool: + """Return whether any known-good hashes are specified as options. + + These activate --require-hashes mode; hashes specified as part of a + URL do not. + + """ + return bool(self.hash_options) + + def hashes(self, trust_internet: bool = True) -> Hashes: + """Return a hash-comparer that considers my option- and URL-based + hashes to be known-good. + + Hashes in URLs--ones embedded in the requirements file, not ones + downloaded from an index server--are almost peers with ones from + flags. They satisfy --require-hashes (whether it was implicitly or + explicitly activated) but do not activate it. md5 and sha224 are not + allowed in flags, which should nudge people toward good algos. We + always OR all hashes together, even ones from URLs. + + :param trust_internet: Whether to trust URL-based (#md5=...) hashes + downloaded from the internet, as by populate_link() + + """ + good_hashes = self.hash_options.copy() + if trust_internet: + link = self.link + elif self.is_direct and self.user_supplied: + link = self.original_link + else: + link = None + if link and link.hash: + assert link.hash_name is not None + good_hashes.setdefault(link.hash_name, []).append(link.hash) + return Hashes(good_hashes) + + def from_path(self) -> Optional[str]: + """Format a nice indicator to show where this "comes from" """ + if self.req is None: + return None + s = str(self.req) + if self.comes_from: + comes_from: Optional[str] + if isinstance(self.comes_from, str): + comes_from = self.comes_from + else: + comes_from = self.comes_from.from_path() + if comes_from: + s += "->" + comes_from + return s + + def ensure_build_location( + self, build_dir: str, autodelete: bool, parallel_builds: bool + ) -> str: + assert build_dir is not None + if self._temp_build_dir is not None: + assert self._temp_build_dir.path + return self._temp_build_dir.path + if self.req is None: + # Some systems have /tmp as a symlink which confuses custom + # builds (such as numpy). Thus, we ensure that the real path + # is returned. + self._temp_build_dir = TempDirectory( + kind=tempdir_kinds.REQ_BUILD, globally_managed=True + ) + + return self._temp_build_dir.path + + # This is the only remaining place where we manually determine the path + # for the temporary directory. It is only needed for editables where + # it is the value of the --src option. + + # When parallel builds are enabled, add a UUID to the build directory + # name so multiple builds do not interfere with each other. + dir_name: str = canonicalize_name(self.req.name) + if parallel_builds: + dir_name = f"{dir_name}_{uuid.uuid4().hex}" + + # FIXME: Is there a better place to create the build_dir? (hg and bzr + # need this) + if not os.path.exists(build_dir): + logger.debug("Creating directory %s", build_dir) + os.makedirs(build_dir) + actual_build_dir = os.path.join(build_dir, dir_name) + # `None` indicates that we respect the globally-configured deletion + # settings, which is what we actually want when auto-deleting. + delete_arg = None if autodelete else False + return TempDirectory( + path=actual_build_dir, + delete=delete_arg, + kind=tempdir_kinds.REQ_BUILD, + globally_managed=True, + ).path + + def _set_requirement(self) -> None: + """Set requirement after generating metadata.""" + assert self.req is None + assert self.metadata is not None + assert self.source_dir is not None + + # Construct a Requirement object from the generated metadata + if isinstance(parse_version(self.metadata["Version"]), Version): + op = "==" + else: + op = "===" + + self.req = get_requirement( + "".join( + [ + self.metadata["Name"], + op, + self.metadata["Version"], + ] + ) + ) + + def warn_on_mismatching_name(self) -> None: + assert self.req is not None + metadata_name = canonicalize_name(self.metadata["Name"]) + if canonicalize_name(self.req.name) == metadata_name: + # Everything is fine. + return + + # If we're here, there's a mismatch. Log a warning about it. + logger.warning( + "Generating metadata for package %s " + "produced metadata for project name %s. Fix your " + "#egg=%s fragments.", + self.name, + metadata_name, + self.name, + ) + self.req = get_requirement(metadata_name) + + def check_if_exists(self, use_user_site: bool) -> None: + """Find an installed distribution that satisfies or conflicts + with this requirement, and set self.satisfied_by or + self.should_reinstall appropriately. + """ + if self.req is None: + return + existing_dist = get_default_environment().get_distribution(self.req.name) + if not existing_dist: + return + + version_compatible = self.req.specifier.contains( + existing_dist.version, + prereleases=True, + ) + if not version_compatible: + self.satisfied_by = None + if use_user_site: + if existing_dist.in_usersite: + self.should_reinstall = True + elif running_under_virtualenv() and existing_dist.in_site_packages: + raise InstallationError( + f"Will not install to the user site because it will " + f"lack sys.path precedence to {existing_dist.raw_name} " + f"in {existing_dist.location}" + ) + else: + self.should_reinstall = True + else: + if self.editable: + self.should_reinstall = True + # when installing editables, nothing pre-existing should ever + # satisfy + self.satisfied_by = None + else: + self.satisfied_by = existing_dist + + # Things valid for wheels + @property + def is_wheel(self) -> bool: + if not self.link: + return False + return self.link.is_wheel + + @property + def is_wheel_from_cache(self) -> bool: + # When True, it means that this InstallRequirement is a local wheel file in the + # cache of locally built wheels. + return self.cached_wheel_source_link is not None + + # Things valid for sdists + @property + def unpacked_source_directory(self) -> str: + assert self.source_dir, f"No source dir for {self}" + return os.path.join( + self.source_dir, self.link and self.link.subdirectory_fragment or "" + ) + + @property + def setup_py_path(self) -> str: + assert self.source_dir, f"No source dir for {self}" + setup_py = os.path.join(self.unpacked_source_directory, "setup.py") + + return setup_py + + @property + def setup_cfg_path(self) -> str: + assert self.source_dir, f"No source dir for {self}" + setup_cfg = os.path.join(self.unpacked_source_directory, "setup.cfg") + + return setup_cfg + + @property + def pyproject_toml_path(self) -> str: + assert self.source_dir, f"No source dir for {self}" + return make_pyproject_path(self.unpacked_source_directory) + + def load_pyproject_toml(self) -> None: + """Load the pyproject.toml file. + + After calling this routine, all of the attributes related to PEP 517 + processing for this requirement have been set. In particular, the + use_pep517 attribute can be used to determine whether we should + follow the PEP 517 or legacy (setup.py) code path. + """ + pyproject_toml_data = load_pyproject_toml( + self.use_pep517, self.pyproject_toml_path, self.setup_py_path, str(self) + ) + + if pyproject_toml_data is None: + assert not self.config_settings + self.use_pep517 = False + return + + self.use_pep517 = True + requires, backend, check, backend_path = pyproject_toml_data + self.requirements_to_check = check + self.pyproject_requires = requires + self.pep517_backend = ConfiguredBuildBackendHookCaller( + self, + self.unpacked_source_directory, + backend, + backend_path=backend_path, + ) + + def isolated_editable_sanity_check(self) -> None: + """Check that an editable requirement if valid for use with PEP 517/518. + + This verifies that an editable that has a pyproject.toml either supports PEP 660 + or as a setup.py or a setup.cfg + """ + if ( + self.editable + and self.use_pep517 + and not self.supports_pyproject_editable + and not os.path.isfile(self.setup_py_path) + and not os.path.isfile(self.setup_cfg_path) + ): + raise InstallationError( + f"Project {self} has a 'pyproject.toml' and its build " + f"backend is missing the 'build_editable' hook. Since it does not " + f"have a 'setup.py' nor a 'setup.cfg', " + f"it cannot be installed in editable mode. " + f"Consider using a build backend that supports PEP 660." + ) + + def prepare_metadata(self) -> None: + """Ensure that project metadata is available. + + Under PEP 517 and PEP 660, call the backend hook to prepare the metadata. + Under legacy processing, call setup.py egg-info. + """ + assert self.source_dir, f"No source dir for {self}" + details = self.name or f"from {self.link}" + + if self.use_pep517: + assert self.pep517_backend is not None + if ( + self.editable + and self.permit_editable_wheels + and self.supports_pyproject_editable + ): + self.metadata_directory = generate_editable_metadata( + build_env=self.build_env, + backend=self.pep517_backend, + details=details, + ) + else: + self.metadata_directory = generate_metadata( + build_env=self.build_env, + backend=self.pep517_backend, + details=details, + ) + else: + self.metadata_directory = generate_metadata_legacy( + build_env=self.build_env, + setup_py_path=self.setup_py_path, + source_dir=self.unpacked_source_directory, + isolated=self.isolated, + details=details, + ) + + # Act on the newly generated metadata, based on the name and version. + if not self.name: + self._set_requirement() + else: + self.warn_on_mismatching_name() + + self.assert_source_matches_version() + + @property + def metadata(self) -> Any: + if not hasattr(self, "_metadata"): + self._metadata = self.get_dist().metadata + + return self._metadata + + def get_dist(self) -> BaseDistribution: + if self.metadata_directory: + return get_directory_distribution(self.metadata_directory) + elif self.local_file_path and self.is_wheel: + assert self.req is not None + return get_wheel_distribution( + FilesystemWheel(self.local_file_path), + canonicalize_name(self.req.name), + ) + raise AssertionError( + f"InstallRequirement {self} has no metadata directory and no wheel: " + f"can't make a distribution." + ) + + def assert_source_matches_version(self) -> None: + assert self.source_dir, f"No source dir for {self}" + version = self.metadata["version"] + if self.req and self.req.specifier and version not in self.req.specifier: + logger.warning( + "Requested %s, but installing version %s", + self, + version, + ) + else: + logger.debug( + "Source in %s has version %s, which satisfies requirement %s", + display_path(self.source_dir), + version, + self, + ) + + # For both source distributions and editables + def ensure_has_source_dir( + self, + parent_dir: str, + autodelete: bool = False, + parallel_builds: bool = False, + ) -> None: + """Ensure that a source_dir is set. + + This will create a temporary build dir if the name of the requirement + isn't known yet. + + :param parent_dir: The ideal pip parent_dir for the source_dir. + Generally src_dir for editables and build_dir for sdists. + :return: self.source_dir + """ + if self.source_dir is None: + self.source_dir = self.ensure_build_location( + parent_dir, + autodelete=autodelete, + parallel_builds=parallel_builds, + ) + + def needs_unpacked_archive(self, archive_source: Path) -> None: + assert self._archive_source is None + self._archive_source = archive_source + + def ensure_pristine_source_checkout(self) -> None: + """Ensure the source directory has not yet been built in.""" + assert self.source_dir is not None + if self._archive_source is not None: + unpack_file(str(self._archive_source), self.source_dir) + elif is_installable_dir(self.source_dir): + # If a checkout exists, it's unwise to keep going. + # version inconsistencies are logged later, but do not fail + # the installation. + raise PreviousBuildDirError( + f"pip can't proceed with requirements '{self}' due to a " + f"pre-existing build directory ({self.source_dir}). This is likely " + "due to a previous installation that failed . pip is " + "being responsible and not assuming it can delete this. " + "Please delete it and try again." + ) + + # For editable installations + def update_editable(self) -> None: + if not self.link: + logger.debug( + "Cannot update repository at %s; repository location is unknown", + self.source_dir, + ) + return + assert self.editable + assert self.source_dir + if self.link.scheme == "file": + # Static paths don't get updated + return + vcs_backend = vcs.get_backend_for_scheme(self.link.scheme) + # Editable requirements are validated in Requirement constructors. + # So here, if it's neither a path nor a valid VCS URL, it's a bug. + assert vcs_backend, f"Unsupported VCS URL {self.link.url}" + hidden_url = hide_url(self.link.url) + vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0) + + # Top-level Actions + def uninstall( + self, auto_confirm: bool = False, verbose: bool = False + ) -> Optional[UninstallPathSet]: + """ + Uninstall the distribution currently satisfying this requirement. + + Prompts before removing or modifying files unless + ``auto_confirm`` is True. + + Refuses to delete or modify files outside of ``sys.prefix`` - + thus uninstallation within a virtual environment can only + modify that virtual environment, even if the virtualenv is + linked to global site-packages. + + """ + assert self.req + dist = get_default_environment().get_distribution(self.req.name) + if not dist: + logger.warning("Skipping %s as it is not installed.", self.name) + return None + logger.info("Found existing installation: %s", dist) + + uninstalled_pathset = UninstallPathSet.from_dist(dist) + uninstalled_pathset.remove(auto_confirm, verbose) + return uninstalled_pathset + + def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str: + def _clean_zip_name(name: str, prefix: str) -> str: + assert name.startswith( + prefix + os.path.sep + ), f"name {name!r} doesn't start with prefix {prefix!r}" + name = name[len(prefix) + 1 :] + name = name.replace(os.path.sep, "/") + return name + + assert self.req is not None + path = os.path.join(parentdir, path) + name = _clean_zip_name(path, rootdir) + return self.req.name + "/" + name + + def archive(self, build_dir: Optional[str]) -> None: + """Saves archive to provided build_dir. + + Used for saving downloaded VCS requirements as part of `pip download`. + """ + assert self.source_dir + if build_dir is None: + return + + create_archive = True + archive_name = "{}-{}.zip".format(self.name, self.metadata["version"]) + archive_path = os.path.join(build_dir, archive_name) + + if os.path.exists(archive_path): + response = ask_path_exists( + f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, " + "(b)ackup, (a)bort ", + ("i", "w", "b", "a"), + ) + if response == "i": + create_archive = False + elif response == "w": + logger.warning("Deleting %s", display_path(archive_path)) + os.remove(archive_path) + elif response == "b": + dest_file = backup_dir(archive_path) + logger.warning( + "Backing up %s to %s", + display_path(archive_path), + display_path(dest_file), + ) + shutil.move(archive_path, dest_file) + elif response == "a": + sys.exit(-1) + + if not create_archive: + return + + zip_output = zipfile.ZipFile( + archive_path, + "w", + zipfile.ZIP_DEFLATED, + allowZip64=True, + ) + with zip_output: + dir = os.path.normcase(os.path.abspath(self.unpacked_source_directory)) + for dirpath, dirnames, filenames in os.walk(dir): + for dirname in dirnames: + dir_arcname = self._get_archive_name( + dirname, + parentdir=dirpath, + rootdir=dir, + ) + zipdir = zipfile.ZipInfo(dir_arcname + "/") + zipdir.external_attr = 0x1ED << 16 # 0o755 + zip_output.writestr(zipdir, "") + for filename in filenames: + file_arcname = self._get_archive_name( + filename, + parentdir=dirpath, + rootdir=dir, + ) + filename = os.path.join(dirpath, filename) + zip_output.write(filename, file_arcname) + + logger.info("Saved %s", display_path(archive_path)) + + def install( + self, + global_options: Optional[Sequence[str]] = None, + root: Optional[str] = None, + home: Optional[str] = None, + prefix: Optional[str] = None, + warn_script_location: bool = True, + use_user_site: bool = False, + pycompile: bool = True, + ) -> None: + assert self.req is not None + scheme = get_scheme( + self.req.name, + user=use_user_site, + home=home, + root=root, + isolated=self.isolated, + prefix=prefix, + ) + + if self.editable and not self.is_wheel: + deprecated( + reason=( + f"Legacy editable install of {self} (setup.py develop) " + "is deprecated." + ), + replacement=( + "to add a pyproject.toml or enable --use-pep517, " + "and use setuptools >= 64. " + "If the resulting installation is not behaving as expected, " + "try using --config-settings editable_mode=compat. " + "Please consult the setuptools documentation for more information" + ), + gone_in="25.1", + issue=11457, + ) + if self.config_settings: + logger.warning( + "--config-settings ignored for legacy editable install of %s. " + "Consider upgrading to a version of setuptools " + "that supports PEP 660 (>= 64).", + self, + ) + install_editable_legacy( + global_options=global_options if global_options is not None else [], + prefix=prefix, + home=home, + use_user_site=use_user_site, + name=self.req.name, + setup_py_path=self.setup_py_path, + isolated=self.isolated, + build_env=self.build_env, + unpacked_source_directory=self.unpacked_source_directory, + ) + self.install_succeeded = True + return + + assert self.is_wheel + assert self.local_file_path + + install_wheel( + self.req.name, + self.local_file_path, + scheme=scheme, + req_description=str(self.req), + pycompile=pycompile, + warn_script_location=warn_script_location, + direct_url=self.download_info if self.is_direct else None, + requested=self.user_supplied, + ) + self.install_succeeded = True + + +def check_invalid_constraint_type(req: InstallRequirement) -> str: + # Check for unsupported forms + problem = "" + if not req.name: + problem = "Unnamed requirements are not allowed as constraints" + elif req.editable: + problem = "Editable requirements are not allowed as constraints" + elif req.extras: + problem = "Constraints cannot have extras" + + if problem: + deprecated( + reason=( + "Constraints are only allowed to take the form of a package " + "name and a version specifier. Other forms were originally " + "permitted as an accident of the implementation, but were " + "undocumented. The new implementation of the resolver no " + "longer supports these forms." + ), + replacement="replacing the constraint with a requirement", + # No plan yet for when the new resolver becomes default + gone_in=None, + issue=8210, + ) + + return problem + + +def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> bool: + if getattr(options, option, None): + return True + for req in reqs: + if getattr(req, option, None): + return True + return False + + +def check_legacy_setup_py_options( + options: Values, + reqs: List[InstallRequirement], +) -> None: + has_build_options = _has_option(options, reqs, "build_options") + has_global_options = _has_option(options, reqs, "global_options") + if has_build_options or has_global_options: + deprecated( + reason="--build-option and --global-option are deprecated.", + issue=11859, + replacement="to use --config-settings", + gone_in=None, + ) + logger.warning( + "Implying --no-binary=:all: due to the presence of " + "--build-option / --global-option. " + ) + options.format_control.disallow_binaries() diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/req_set.py b/venv/lib/python3.8/site-packages/pip/_internal/req/req_set.py new file mode 100644 index 000000000..ec7a6e07a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/req/req_set.py @@ -0,0 +1,82 @@ +import logging +from collections import OrderedDict +from typing import Dict, List + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.req.req_install import InstallRequirement + +logger = logging.getLogger(__name__) + + +class RequirementSet: + def __init__(self, check_supported_wheels: bool = True) -> None: + """Create a RequirementSet.""" + + self.requirements: Dict[str, InstallRequirement] = OrderedDict() + self.check_supported_wheels = check_supported_wheels + + self.unnamed_requirements: List[InstallRequirement] = [] + + def __str__(self) -> str: + requirements = sorted( + (req for req in self.requirements.values() if not req.comes_from), + key=lambda req: canonicalize_name(req.name or ""), + ) + return " ".join(str(req.req) for req in requirements) + + def __repr__(self) -> str: + requirements = sorted( + self.requirements.values(), + key=lambda req: canonicalize_name(req.name or ""), + ) + + format_string = "<{classname} object; {count} requirement(s): {reqs}>" + return format_string.format( + classname=self.__class__.__name__, + count=len(requirements), + reqs=", ".join(str(req.req) for req in requirements), + ) + + def add_unnamed_requirement(self, install_req: InstallRequirement) -> None: + assert not install_req.name + self.unnamed_requirements.append(install_req) + + def add_named_requirement(self, install_req: InstallRequirement) -> None: + assert install_req.name + + project_name = canonicalize_name(install_req.name) + self.requirements[project_name] = install_req + + def has_requirement(self, name: str) -> bool: + project_name = canonicalize_name(name) + + return ( + project_name in self.requirements + and not self.requirements[project_name].constraint + ) + + def get_requirement(self, name: str) -> InstallRequirement: + project_name = canonicalize_name(name) + + if project_name in self.requirements: + return self.requirements[project_name] + + raise KeyError(f"No project with the name {name!r}") + + @property + def all_requirements(self) -> List[InstallRequirement]: + return self.unnamed_requirements + list(self.requirements.values()) + + @property + def requirements_to_install(self) -> List[InstallRequirement]: + """Return the list of requirements that need to be installed. + + TODO remove this property together with the legacy resolver, since the new + resolver only returns requirements that need to be installed. + """ + return [ + install_req + for install_req in self.all_requirements + if not install_req.constraint and not install_req.satisfied_by + ] diff --git a/venv/lib/python3.8/site-packages/pip/_internal/req/req_uninstall.py b/venv/lib/python3.8/site-packages/pip/_internal/req/req_uninstall.py new file mode 100644 index 000000000..26df20844 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/req/req_uninstall.py @@ -0,0 +1,633 @@ +import functools +import os +import sys +import sysconfig +from importlib.util import cache_from_source +from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple + +from pip._internal.exceptions import LegacyDistutilsInstall, UninstallMissingRecord +from pip._internal.locations import get_bin_prefix, get_bin_user +from pip._internal.metadata import BaseDistribution +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.egg_link import egg_link_path_from_location +from pip._internal.utils.logging import getLogger, indent_log +from pip._internal.utils.misc import ask, normalize_path, renames, rmtree +from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory +from pip._internal.utils.virtualenv import running_under_virtualenv + +logger = getLogger(__name__) + + +def _script_names( + bin_dir: str, script_name: str, is_gui: bool +) -> Generator[str, None, None]: + """Create the fully qualified name of the files created by + {console,gui}_scripts for the given ``dist``. + Returns the list of file names + """ + exe_name = os.path.join(bin_dir, script_name) + yield exe_name + if not WINDOWS: + return + yield f"{exe_name}.exe" + yield f"{exe_name}.exe.manifest" + if is_gui: + yield f"{exe_name}-script.pyw" + else: + yield f"{exe_name}-script.py" + + +def _unique( + fn: Callable[..., Generator[Any, None, None]] +) -> Callable[..., Generator[Any, None, None]]: + @functools.wraps(fn) + def unique(*args: Any, **kw: Any) -> Generator[Any, None, None]: + seen: Set[Any] = set() + for item in fn(*args, **kw): + if item not in seen: + seen.add(item) + yield item + + return unique + + +@_unique +def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]: + """ + Yield all the uninstallation paths for dist based on RECORD-without-.py[co] + + Yield paths to all the files in RECORD. For each .py file in RECORD, add + the .pyc and .pyo in the same directory. + + UninstallPathSet.add() takes care of the __pycache__ .py[co]. + + If RECORD is not found, raises an error, + with possible information from the INSTALLER file. + + https://packaging.python.org/specifications/recording-installed-packages/ + """ + location = dist.location + assert location is not None, "not installed" + + entries = dist.iter_declared_entries() + if entries is None: + raise UninstallMissingRecord(distribution=dist) + + for entry in entries: + path = os.path.join(location, entry) + yield path + if path.endswith(".py"): + dn, fn = os.path.split(path) + base = fn[:-3] + path = os.path.join(dn, base + ".pyc") + yield path + path = os.path.join(dn, base + ".pyo") + yield path + + +def compact(paths: Iterable[str]) -> Set[str]: + """Compact a path set to contain the minimal number of paths + necessary to contain all paths in the set. If /a/path/ and + /a/path/to/a/file.txt are both in the set, leave only the + shorter path.""" + + sep = os.path.sep + short_paths: Set[str] = set() + for path in sorted(paths, key=len): + should_skip = any( + path.startswith(shortpath.rstrip("*")) + and path[len(shortpath.rstrip("*").rstrip(sep))] == sep + for shortpath in short_paths + ) + if not should_skip: + short_paths.add(path) + return short_paths + + +def compress_for_rename(paths: Iterable[str]) -> Set[str]: + """Returns a set containing the paths that need to be renamed. + + This set may include directories when the original sequence of paths + included every file on disk. + """ + case_map = {os.path.normcase(p): p for p in paths} + remaining = set(case_map) + unchecked = sorted({os.path.split(p)[0] for p in case_map.values()}, key=len) + wildcards: Set[str] = set() + + def norm_join(*a: str) -> str: + return os.path.normcase(os.path.join(*a)) + + for root in unchecked: + if any(os.path.normcase(root).startswith(w) for w in wildcards): + # This directory has already been handled. + continue + + all_files: Set[str] = set() + all_subdirs: Set[str] = set() + for dirname, subdirs, files in os.walk(root): + all_subdirs.update(norm_join(root, dirname, d) for d in subdirs) + all_files.update(norm_join(root, dirname, f) for f in files) + # If all the files we found are in our remaining set of files to + # remove, then remove them from the latter set and add a wildcard + # for the directory. + if not (all_files - remaining): + remaining.difference_update(all_files) + wildcards.add(root + os.sep) + + return set(map(case_map.__getitem__, remaining)) | wildcards + + +def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str]]: + """Returns a tuple of 2 sets of which paths to display to user + + The first set contains paths that would be deleted. Files of a package + are not added and the top-level directory of the package has a '*' added + at the end - to signify that all it's contents are removed. + + The second set contains files that would have been skipped in the above + folders. + """ + + will_remove = set(paths) + will_skip = set() + + # Determine folders and files + folders = set() + files = set() + for path in will_remove: + if path.endswith(".pyc"): + continue + if path.endswith("__init__.py") or ".dist-info" in path: + folders.add(os.path.dirname(path)) + files.add(path) + + _normcased_files = set(map(os.path.normcase, files)) + + folders = compact(folders) + + # This walks the tree using os.walk to not miss extra folders + # that might get added. + for folder in folders: + for dirpath, _, dirfiles in os.walk(folder): + for fname in dirfiles: + if fname.endswith(".pyc"): + continue + + file_ = os.path.join(dirpath, fname) + if ( + os.path.isfile(file_) + and os.path.normcase(file_) not in _normcased_files + ): + # We are skipping this file. Add it to the set. + will_skip.add(file_) + + will_remove = files | {os.path.join(folder, "*") for folder in folders} + + return will_remove, will_skip + + +class StashedUninstallPathSet: + """A set of file rename operations to stash files while + tentatively uninstalling them.""" + + def __init__(self) -> None: + # Mapping from source file root to [Adjacent]TempDirectory + # for files under that directory. + self._save_dirs: Dict[str, TempDirectory] = {} + # (old path, new path) tuples for each move that may need + # to be undone. + self._moves: List[Tuple[str, str]] = [] + + def _get_directory_stash(self, path: str) -> str: + """Stashes a directory. + + Directories are stashed adjacent to their original location if + possible, or else moved/copied into the user's temp dir.""" + + try: + save_dir: TempDirectory = AdjacentTempDirectory(path) + except OSError: + save_dir = TempDirectory(kind="uninstall") + self._save_dirs[os.path.normcase(path)] = save_dir + + return save_dir.path + + def _get_file_stash(self, path: str) -> str: + """Stashes a file. + + If no root has been provided, one will be created for the directory + in the user's temp directory.""" + path = os.path.normcase(path) + head, old_head = os.path.dirname(path), None + save_dir = None + + while head != old_head: + try: + save_dir = self._save_dirs[head] + break + except KeyError: + pass + head, old_head = os.path.dirname(head), head + else: + # Did not find any suitable root + head = os.path.dirname(path) + save_dir = TempDirectory(kind="uninstall") + self._save_dirs[head] = save_dir + + relpath = os.path.relpath(path, head) + if relpath and relpath != os.path.curdir: + return os.path.join(save_dir.path, relpath) + return save_dir.path + + def stash(self, path: str) -> str: + """Stashes the directory or file and returns its new location. + Handle symlinks as files to avoid modifying the symlink targets. + """ + path_is_dir = os.path.isdir(path) and not os.path.islink(path) + if path_is_dir: + new_path = self._get_directory_stash(path) + else: + new_path = self._get_file_stash(path) + + self._moves.append((path, new_path)) + if path_is_dir and os.path.isdir(new_path): + # If we're moving a directory, we need to + # remove the destination first or else it will be + # moved to inside the existing directory. + # We just created new_path ourselves, so it will + # be removable. + os.rmdir(new_path) + renames(path, new_path) + return new_path + + def commit(self) -> None: + """Commits the uninstall by removing stashed files.""" + for save_dir in self._save_dirs.values(): + save_dir.cleanup() + self._moves = [] + self._save_dirs = {} + + def rollback(self) -> None: + """Undoes the uninstall by moving stashed files back.""" + for p in self._moves: + logger.info("Moving to %s\n from %s", *p) + + for new_path, path in self._moves: + try: + logger.debug("Replacing %s from %s", new_path, path) + if os.path.isfile(new_path) or os.path.islink(new_path): + os.unlink(new_path) + elif os.path.isdir(new_path): + rmtree(new_path) + renames(path, new_path) + except OSError as ex: + logger.error("Failed to restore %s", new_path) + logger.debug("Exception: %s", ex) + + self.commit() + + @property + def can_rollback(self) -> bool: + return bool(self._moves) + + +class UninstallPathSet: + """A set of file paths to be removed in the uninstallation of a + requirement.""" + + def __init__(self, dist: BaseDistribution) -> None: + self._paths: Set[str] = set() + self._refuse: Set[str] = set() + self._pth: Dict[str, UninstallPthEntries] = {} + self._dist = dist + self._moved_paths = StashedUninstallPathSet() + # Create local cache of normalize_path results. Creating an UninstallPathSet + # can result in hundreds/thousands of redundant calls to normalize_path with + # the same args, which hurts performance. + self._normalize_path_cached = functools.lru_cache(normalize_path) + + def _permitted(self, path: str) -> bool: + """ + Return True if the given path is one we are permitted to + remove/modify, False otherwise. + + """ + # aka is_local, but caching normalized sys.prefix + if not running_under_virtualenv(): + return True + return path.startswith(self._normalize_path_cached(sys.prefix)) + + def add(self, path: str) -> None: + head, tail = os.path.split(path) + + # we normalize the head to resolve parent directory symlinks, but not + # the tail, since we only want to uninstall symlinks, not their targets + path = os.path.join(self._normalize_path_cached(head), os.path.normcase(tail)) + + if not os.path.exists(path): + return + if self._permitted(path): + self._paths.add(path) + else: + self._refuse.add(path) + + # __pycache__ files can show up after 'installed-files.txt' is created, + # due to imports + if os.path.splitext(path)[1] == ".py": + self.add(cache_from_source(path)) + + def add_pth(self, pth_file: str, entry: str) -> None: + pth_file = self._normalize_path_cached(pth_file) + if self._permitted(pth_file): + if pth_file not in self._pth: + self._pth[pth_file] = UninstallPthEntries(pth_file) + self._pth[pth_file].add(entry) + else: + self._refuse.add(pth_file) + + def remove(self, auto_confirm: bool = False, verbose: bool = False) -> None: + """Remove paths in ``self._paths`` with confirmation (unless + ``auto_confirm`` is True).""" + + if not self._paths: + logger.info( + "Can't uninstall '%s'. No files were found to uninstall.", + self._dist.raw_name, + ) + return + + dist_name_version = f"{self._dist.raw_name}-{self._dist.raw_version}" + logger.info("Uninstalling %s:", dist_name_version) + + with indent_log(): + if auto_confirm or self._allowed_to_proceed(verbose): + moved = self._moved_paths + + for_rename = compress_for_rename(self._paths) + + for path in sorted(compact(for_rename)): + moved.stash(path) + logger.verbose("Removing file or directory %s", path) + + for pth in self._pth.values(): + pth.remove() + + logger.info("Successfully uninstalled %s", dist_name_version) + + def _allowed_to_proceed(self, verbose: bool) -> bool: + """Display which files would be deleted and prompt for confirmation""" + + def _display(msg: str, paths: Iterable[str]) -> None: + if not paths: + return + + logger.info(msg) + with indent_log(): + for path in sorted(compact(paths)): + logger.info(path) + + if not verbose: + will_remove, will_skip = compress_for_output_listing(self._paths) + else: + # In verbose mode, display all the files that are going to be + # deleted. + will_remove = set(self._paths) + will_skip = set() + + _display("Would remove:", will_remove) + _display("Would not remove (might be manually added):", will_skip) + _display("Would not remove (outside of prefix):", self._refuse) + if verbose: + _display("Will actually move:", compress_for_rename(self._paths)) + + return ask("Proceed (Y/n)? ", ("y", "n", "")) != "n" + + def rollback(self) -> None: + """Rollback the changes previously made by remove().""" + if not self._moved_paths.can_rollback: + logger.error( + "Can't roll back %s; was not uninstalled", + self._dist.raw_name, + ) + return + logger.info("Rolling back uninstall of %s", self._dist.raw_name) + self._moved_paths.rollback() + for pth in self._pth.values(): + pth.rollback() + + def commit(self) -> None: + """Remove temporary save dir: rollback will no longer be possible.""" + self._moved_paths.commit() + + @classmethod + def from_dist(cls, dist: BaseDistribution) -> "UninstallPathSet": + dist_location = dist.location + info_location = dist.info_location + if dist_location is None: + logger.info( + "Not uninstalling %s since it is not installed", + dist.canonical_name, + ) + return cls(dist) + + normalized_dist_location = normalize_path(dist_location) + if not dist.local: + logger.info( + "Not uninstalling %s at %s, outside environment %s", + dist.canonical_name, + normalized_dist_location, + sys.prefix, + ) + return cls(dist) + + if normalized_dist_location in { + p + for p in {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")} + if p + }: + logger.info( + "Not uninstalling %s at %s, as it is in the standard library.", + dist.canonical_name, + normalized_dist_location, + ) + return cls(dist) + + paths_to_remove = cls(dist) + develop_egg_link = egg_link_path_from_location(dist.raw_name) + + # Distribution is installed with metadata in a "flat" .egg-info + # directory. This means it is not a modern .dist-info installation, an + # egg, or legacy editable. + setuptools_flat_installation = ( + dist.installed_with_setuptools_egg_info + and info_location is not None + and os.path.exists(info_location) + # If dist is editable and the location points to a ``.egg-info``, + # we are in fact in the legacy editable case. + and not info_location.endswith(f"{dist.setuptools_filename}.egg-info") + ) + + # Uninstall cases order do matter as in the case of 2 installs of the + # same package, pip needs to uninstall the currently detected version + if setuptools_flat_installation: + if info_location is not None: + paths_to_remove.add(info_location) + installed_files = dist.iter_declared_entries() + if installed_files is not None: + for installed_file in installed_files: + paths_to_remove.add(os.path.join(dist_location, installed_file)) + # FIXME: need a test for this elif block + # occurs with --single-version-externally-managed/--record outside + # of pip + elif dist.is_file("top_level.txt"): + try: + namespace_packages = dist.read_text("namespace_packages.txt") + except FileNotFoundError: + namespaces = [] + else: + namespaces = namespace_packages.splitlines(keepends=False) + for top_level_pkg in [ + p + for p in dist.read_text("top_level.txt").splitlines() + if p and p not in namespaces + ]: + path = os.path.join(dist_location, top_level_pkg) + paths_to_remove.add(path) + paths_to_remove.add(f"{path}.py") + paths_to_remove.add(f"{path}.pyc") + paths_to_remove.add(f"{path}.pyo") + + elif dist.installed_by_distutils: + raise LegacyDistutilsInstall(distribution=dist) + + elif dist.installed_as_egg: + # package installed by easy_install + # We cannot match on dist.egg_name because it can slightly vary + # i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg + paths_to_remove.add(dist_location) + easy_install_egg = os.path.split(dist_location)[1] + easy_install_pth = os.path.join( + os.path.dirname(dist_location), + "easy-install.pth", + ) + paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg) + + elif dist.installed_with_dist_info: + for path in uninstallation_paths(dist): + paths_to_remove.add(path) + + elif develop_egg_link: + # PEP 660 modern editable is handled in the ``.dist-info`` case + # above, so this only covers the setuptools-style editable. + with open(develop_egg_link) as fh: + link_pointer = os.path.normcase(fh.readline().strip()) + normalized_link_pointer = paths_to_remove._normalize_path_cached( + link_pointer + ) + assert os.path.samefile( + normalized_link_pointer, normalized_dist_location + ), ( + f"Egg-link {develop_egg_link} (to {link_pointer}) does not match " + f"installed location of {dist.raw_name} (at {dist_location})" + ) + paths_to_remove.add(develop_egg_link) + easy_install_pth = os.path.join( + os.path.dirname(develop_egg_link), "easy-install.pth" + ) + paths_to_remove.add_pth(easy_install_pth, dist_location) + + else: + logger.debug( + "Not sure how to uninstall: %s - Check: %s", + dist, + dist_location, + ) + + if dist.in_usersite: + bin_dir = get_bin_user() + else: + bin_dir = get_bin_prefix() + + # find distutils scripts= scripts + try: + for script in dist.iter_distutils_script_names(): + paths_to_remove.add(os.path.join(bin_dir, script)) + if WINDOWS: + paths_to_remove.add(os.path.join(bin_dir, f"{script}.bat")) + except (FileNotFoundError, NotADirectoryError): + pass + + # find console_scripts and gui_scripts + def iter_scripts_to_remove( + dist: BaseDistribution, + bin_dir: str, + ) -> Generator[str, None, None]: + for entry_point in dist.iter_entry_points(): + if entry_point.group == "console_scripts": + yield from _script_names(bin_dir, entry_point.name, False) + elif entry_point.group == "gui_scripts": + yield from _script_names(bin_dir, entry_point.name, True) + + for s in iter_scripts_to_remove(dist, bin_dir): + paths_to_remove.add(s) + + return paths_to_remove + + +class UninstallPthEntries: + def __init__(self, pth_file: str) -> None: + self.file = pth_file + self.entries: Set[str] = set() + self._saved_lines: Optional[List[bytes]] = None + + def add(self, entry: str) -> None: + entry = os.path.normcase(entry) + # On Windows, os.path.normcase converts the entry to use + # backslashes. This is correct for entries that describe absolute + # paths outside of site-packages, but all the others use forward + # slashes. + # os.path.splitdrive is used instead of os.path.isabs because isabs + # treats non-absolute paths with drive letter markings like c:foo\bar + # as absolute paths. It also does not recognize UNC paths if they don't + # have more than "\\sever\share". Valid examples: "\\server\share\" or + # "\\server\share\folder". + if WINDOWS and not os.path.splitdrive(entry)[0]: + entry = entry.replace("\\", "/") + self.entries.add(entry) + + def remove(self) -> None: + logger.verbose("Removing pth entries from %s:", self.file) + + # If the file doesn't exist, log a warning and return + if not os.path.isfile(self.file): + logger.warning("Cannot remove entries from nonexistent file %s", self.file) + return + with open(self.file, "rb") as fh: + # windows uses '\r\n' with py3k, but uses '\n' with py2.x + lines = fh.readlines() + self._saved_lines = lines + if any(b"\r\n" in line for line in lines): + endline = "\r\n" + else: + endline = "\n" + # handle missing trailing newline + if lines and not lines[-1].endswith(endline.encode("utf-8")): + lines[-1] = lines[-1] + endline.encode("utf-8") + for entry in self.entries: + try: + logger.verbose("Removing entry: %s", entry) + lines.remove((entry + endline).encode("utf-8")) + except ValueError: + pass + with open(self.file, "wb") as fh: + fh.writelines(lines) + + def rollback(self) -> bool: + if self._saved_lines is None: + logger.error("Cannot roll back changes to %s, none were made", self.file) + return False + logger.debug("Rolling %s back to previous state", self.file) + with open(self.file, "wb") as fh: + fh.writelines(self._saved_lines) + return True diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b24ecfede9aa1c85f2b002311f385d24cefc38d7 GIT binary patch literal 217 zcmYk0F$%&!5Jfj&A%X|7NTx_*A!1=EHeww%=4*y*c9+d25DV|)6|6mltw*r4asz^X z_-~j$GcfzZGN*<;VjITtgmB81GS(hWOGOTiQLSgA YDGMq4EK6+v7%ys5vrNHt{rB)%z3M32KkD zkI@(CD{$s1eC5O|a45`d+7d*ud%~ zndFKUJmG~%MBybKC!bJ$5hOvQdnzniNsBY!5P=6lmyHDkC!))e_CAxo3@(`rE<~~- z!(-NK{lI%!&vVgUrc^dm;QYhfSQmY$ZC;g`>T$PmP#TNjBbd%|4F!}Iz3WTnG1w7Z zw7l-2zd$drgiDqPgyeS_1Q1GoPJ*KwMc7BrF{ zYwCPPlZb~%#7=$YT8(cG9RyH40~>!fdxpvD*CF=}pm3>I-V4 RequirementSet: + raise NotImplementedError() + + def get_installation_order( + self, req_set: RequirementSet + ) -> List[InstallRequirement]: + raise NotImplementedError() diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..063415df67481f637a636d8ae4aad0198d39cb6a GIT binary patch literal 224 zcmYk0F$%&!5Jfj&A%X|7NTx_*A!1=EHeww%@tX{8c9+d25PJ{f6|6mltw*r4asz^X z_-~l`GcfzU65*6BWvo4%mWmu2qgu~K clR_zrp0W_K&$7h!kMW{5hgM-Zy!Q$gA7ubP%K!iX literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38bfdd222f0495b5e7b1463da285cc31c333db7b GIT binary patch literal 14958 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 None: + """ + Check whether the given Python version is compatible with a distribution's + "Requires-Python" value. + + :param version_info: A 3-tuple of ints representing the Python + major-minor-micro version to check. + :param ignore_requires_python: Whether to ignore the "Requires-Python" + value if the given Python version isn't compatible. + + :raises UnsupportedPythonVersion: When the given Python version isn't + compatible. + """ + # This idiosyncratically converts the SpecifierSet to str and let + # check_requires_python then parse it again into SpecifierSet. But this + # is the legacy resolver so I'm just not going to bother refactoring. + try: + requires_python = str(dist.requires_python) + except FileNotFoundError as e: + raise NoneMetadataError(dist, str(e)) + try: + is_compatible = check_requires_python( + requires_python, + version_info=version_info, + ) + except specifiers.InvalidSpecifier as exc: + logger.warning( + "Package %r has an invalid Requires-Python: %s", dist.raw_name, exc + ) + return + + if is_compatible: + return + + version = ".".join(map(str, version_info)) + if ignore_requires_python: + logger.debug( + "Ignoring failed Requires-Python check for package %r: %s not in %r", + dist.raw_name, + version, + requires_python, + ) + return + + raise UnsupportedPythonVersion( + f"Package {dist.raw_name!r} requires a different Python: " + f"{version} not in {requires_python!r}" + ) + + +class Resolver(BaseResolver): + """Resolves which packages need to be installed/uninstalled to perform \ + the requested operation without breaking the requirements of any package. + """ + + _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"} + + def __init__( + self, + preparer: RequirementPreparer, + finder: PackageFinder, + wheel_cache: Optional[WheelCache], + make_install_req: InstallRequirementProvider, + use_user_site: bool, + ignore_dependencies: bool, + ignore_installed: bool, + ignore_requires_python: bool, + force_reinstall: bool, + upgrade_strategy: str, + py_version_info: Optional[Tuple[int, ...]] = None, + ) -> None: + super().__init__() + assert upgrade_strategy in self._allowed_strategies + + if py_version_info is None: + py_version_info = sys.version_info[:3] + else: + py_version_info = normalize_version_info(py_version_info) + + self._py_version_info = py_version_info + + self.preparer = preparer + self.finder = finder + self.wheel_cache = wheel_cache + + self.upgrade_strategy = upgrade_strategy + self.force_reinstall = force_reinstall + self.ignore_dependencies = ignore_dependencies + self.ignore_installed = ignore_installed + self.ignore_requires_python = ignore_requires_python + self.use_user_site = use_user_site + self._make_install_req = make_install_req + + self._discovered_dependencies: DiscoveredDependencies = defaultdict(list) + + def resolve( + self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + ) -> RequirementSet: + """Resolve what operations need to be done + + As a side-effect of this method, the packages (and their dependencies) + are downloaded, unpacked and prepared for installation. This + preparation is done by ``pip.operations.prepare``. + + Once PyPI has static dependency metadata available, it would be + possible to move the preparation to become a step separated from + dependency resolution. + """ + requirement_set = RequirementSet(check_supported_wheels=check_supported_wheels) + for req in root_reqs: + if req.constraint: + check_invalid_constraint_type(req) + self._add_requirement_to_set(requirement_set, req) + + # Actually prepare the files, and collect any exceptions. Most hash + # exceptions cannot be checked ahead of time, because + # _populate_link() needs to be called before we can make decisions + # based on link type. + discovered_reqs: List[InstallRequirement] = [] + hash_errors = HashErrors() + for req in chain(requirement_set.all_requirements, discovered_reqs): + try: + discovered_reqs.extend(self._resolve_one(requirement_set, req)) + except HashError as exc: + exc.req = req + hash_errors.append(exc) + + if hash_errors: + raise hash_errors + + return requirement_set + + def _add_requirement_to_set( + self, + requirement_set: RequirementSet, + install_req: InstallRequirement, + parent_req_name: Optional[str] = None, + extras_requested: Optional[Iterable[str]] = None, + ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]: + """Add install_req as a requirement to install. + + :param parent_req_name: The name of the requirement that needed this + added. The name is used because when multiple unnamed requirements + resolve to the same name, we could otherwise end up with dependency + links that point outside the Requirements set. parent_req must + already be added. Note that None implies that this is a user + supplied requirement, vs an inferred one. + :param extras_requested: an iterable of extras used to evaluate the + environment markers. + :return: Additional requirements to scan. That is either [] if + the requirement is not applicable, or [install_req] if the + requirement is applicable and has just been added. + """ + # If the markers do not match, ignore this requirement. + if not install_req.match_markers(extras_requested): + logger.info( + "Ignoring %s: markers '%s' don't match your environment", + install_req.name, + install_req.markers, + ) + return [], None + + # If the wheel is not supported, raise an error. + # Should check this after filtering out based on environment markers to + # allow specifying different wheels based on the environment/OS, in a + # single requirements file. + if install_req.link and install_req.link.is_wheel: + wheel = Wheel(install_req.link.filename) + tags = compatibility_tags.get_supported() + if requirement_set.check_supported_wheels and not wheel.supported(tags): + raise InstallationError( + f"{wheel.filename} is not a supported wheel on this platform." + ) + + # This next bit is really a sanity check. + assert ( + not install_req.user_supplied or parent_req_name is None + ), "a user supplied req shouldn't have a parent" + + # Unnamed requirements are scanned again and the requirement won't be + # added as a dependency until after scanning. + if not install_req.name: + requirement_set.add_unnamed_requirement(install_req) + return [install_req], None + + try: + existing_req: Optional[InstallRequirement] = ( + requirement_set.get_requirement(install_req.name) + ) + except KeyError: + existing_req = None + + has_conflicting_requirement = ( + parent_req_name is None + and existing_req + and not existing_req.constraint + and existing_req.extras == install_req.extras + and existing_req.req + and install_req.req + and existing_req.req.specifier != install_req.req.specifier + ) + if has_conflicting_requirement: + raise InstallationError( + f"Double requirement given: {install_req} " + f"(already in {existing_req}, name={install_req.name!r})" + ) + + # When no existing requirement exists, add the requirement as a + # dependency and it will be scanned again after. + if not existing_req: + requirement_set.add_named_requirement(install_req) + # We'd want to rescan this requirement later + return [install_req], install_req + + # Assume there's no need to scan, and that we've already + # encountered this for scanning. + if install_req.constraint or not existing_req.constraint: + return [], existing_req + + does_not_satisfy_constraint = install_req.link and not ( + existing_req.link and install_req.link.path == existing_req.link.path + ) + if does_not_satisfy_constraint: + raise InstallationError( + f"Could not satisfy constraints for '{install_req.name}': " + "installation from path or url cannot be " + "constrained to a version" + ) + # If we're now installing a constraint, mark the existing + # object for real installation. + existing_req.constraint = False + # If we're now installing a user supplied requirement, + # mark the existing object as such. + if install_req.user_supplied: + existing_req.user_supplied = True + existing_req.extras = tuple( + sorted(set(existing_req.extras) | set(install_req.extras)) + ) + logger.debug( + "Setting %s extras to: %s", + existing_req, + existing_req.extras, + ) + # Return the existing requirement for addition to the parent and + # scanning again. + return [existing_req], existing_req + + def _is_upgrade_allowed(self, req: InstallRequirement) -> bool: + if self.upgrade_strategy == "to-satisfy-only": + return False + elif self.upgrade_strategy == "eager": + return True + else: + assert self.upgrade_strategy == "only-if-needed" + return req.user_supplied or req.constraint + + def _set_req_to_reinstall(self, req: InstallRequirement) -> None: + """ + Set a requirement to be installed. + """ + # Don't uninstall the conflict if doing a user install and the + # conflict is not a user install. + assert req.satisfied_by is not None + if not self.use_user_site or req.satisfied_by.in_usersite: + req.should_reinstall = True + req.satisfied_by = None + + def _check_skip_installed( + self, req_to_install: InstallRequirement + ) -> Optional[str]: + """Check if req_to_install should be skipped. + + This will check if the req is installed, and whether we should upgrade + or reinstall it, taking into account all the relevant user options. + + After calling this req_to_install will only have satisfied_by set to + None if the req_to_install is to be upgraded/reinstalled etc. Any + other value will be a dist recording the current thing installed that + satisfies the requirement. + + Note that for vcs urls and the like we can't assess skipping in this + routine - we simply identify that we need to pull the thing down, + then later on it is pulled down and introspected to assess upgrade/ + reinstalls etc. + + :return: A text reason for why it was skipped, or None. + """ + if self.ignore_installed: + return None + + req_to_install.check_if_exists(self.use_user_site) + if not req_to_install.satisfied_by: + return None + + if self.force_reinstall: + self._set_req_to_reinstall(req_to_install) + return None + + if not self._is_upgrade_allowed(req_to_install): + if self.upgrade_strategy == "only-if-needed": + return "already satisfied, skipping upgrade" + return "already satisfied" + + # Check for the possibility of an upgrade. For link-based + # requirements we have to pull the tree down and inspect to assess + # the version #, so it's handled way down. + if not req_to_install.link: + try: + self.finder.find_requirement(req_to_install, upgrade=True) + except BestVersionAlreadyInstalled: + # Then the best version is installed. + return "already up-to-date" + except DistributionNotFound: + # No distribution found, so we squash the error. It will + # be raised later when we re-try later to do the install. + # Why don't we just raise here? + pass + + self._set_req_to_reinstall(req_to_install) + return None + + def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]: + upgrade = self._is_upgrade_allowed(req) + best_candidate = self.finder.find_requirement(req, upgrade) + if not best_candidate: + return None + + # Log a warning per PEP 592 if necessary before returning. + link = best_candidate.link + if link.is_yanked: + reason = link.yanked_reason or "" + msg = ( + # Mark this as a unicode string to prevent + # "UnicodeEncodeError: 'ascii' codec can't encode character" + # in Python 2 when the reason contains non-ascii characters. + "The candidate selected for download or install is a " + f"yanked version: {best_candidate}\n" + f"Reason for being yanked: {reason}" + ) + logger.warning(msg) + + return link + + def _populate_link(self, req: InstallRequirement) -> None: + """Ensure that if a link can be found for this, that it is found. + + Note that req.link may still be None - if the requirement is already + installed and not needed to be upgraded based on the return value of + _is_upgrade_allowed(). + + If preparer.require_hashes is True, don't use the wheel cache, because + cached wheels, always built locally, have different hashes than the + files downloaded from the index server and thus throw false hash + mismatches. Furthermore, cached wheels at present have undeterministic + contents due to file modification times. + """ + if req.link is None: + req.link = self._find_requirement_link(req) + + if self.wheel_cache is None or self.preparer.require_hashes: + return + + assert req.link is not None, "_find_requirement_link unexpectedly returned None" + cache_entry = self.wheel_cache.get_cache_entry( + link=req.link, + package_name=req.name, + supported_tags=get_supported(), + ) + if cache_entry is not None: + logger.debug("Using cached wheel link: %s", cache_entry.link) + if req.link is req.original_link and cache_entry.persistent: + req.cached_wheel_source_link = req.link + if cache_entry.origin is not None: + req.download_info = cache_entry.origin + else: + # Legacy cache entry that does not have origin.json. + # download_info may miss the archive_info.hashes field. + req.download_info = direct_url_from_link( + req.link, link_is_in_wheel_cache=cache_entry.persistent + ) + req.link = cache_entry.link + + def _get_dist_for(self, req: InstallRequirement) -> BaseDistribution: + """Takes a InstallRequirement and returns a single AbstractDist \ + representing a prepared variant of the same. + """ + if req.editable: + return self.preparer.prepare_editable_requirement(req) + + # satisfied_by is only evaluated by calling _check_skip_installed, + # so it must be None here. + assert req.satisfied_by is None + skip_reason = self._check_skip_installed(req) + + if req.satisfied_by: + return self.preparer.prepare_installed_requirement(req, skip_reason) + + # We eagerly populate the link, since that's our "legacy" behavior. + self._populate_link(req) + dist = self.preparer.prepare_linked_requirement(req) + + # NOTE + # The following portion is for determining if a certain package is + # going to be re-installed/upgraded or not and reporting to the user. + # This should probably get cleaned up in a future refactor. + + # req.req is only avail after unpack for URL + # pkgs repeat check_if_exists to uninstall-on-upgrade + # (#14) + if not self.ignore_installed: + req.check_if_exists(self.use_user_site) + + if req.satisfied_by: + should_modify = ( + self.upgrade_strategy != "to-satisfy-only" + or self.force_reinstall + or self.ignore_installed + or req.link.scheme == "file" + ) + if should_modify: + self._set_req_to_reinstall(req) + else: + logger.info( + "Requirement already satisfied (use --upgrade to upgrade): %s", + req, + ) + return dist + + def _resolve_one( + self, + requirement_set: RequirementSet, + req_to_install: InstallRequirement, + ) -> List[InstallRequirement]: + """Prepare a single requirements file. + + :return: A list of additional InstallRequirements to also install. + """ + # Tell user what we are doing for this requirement: + # obtain (editable), skipping, processing (local url), collecting + # (remote url or package name) + if req_to_install.constraint or req_to_install.prepared: + return [] + + req_to_install.prepared = True + + # Parse and return dependencies + dist = self._get_dist_for(req_to_install) + # This will raise UnsupportedPythonVersion if the given Python + # version isn't compatible with the distribution's Requires-Python. + _check_dist_requires_python( + dist, + version_info=self._py_version_info, + ignore_requires_python=self.ignore_requires_python, + ) + + more_reqs: List[InstallRequirement] = [] + + def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None: + # This idiosyncratically converts the Requirement to str and let + # make_install_req then parse it again into Requirement. But this is + # the legacy resolver so I'm just not going to bother refactoring. + sub_install_req = self._make_install_req(str(subreq), req_to_install) + parent_req_name = req_to_install.name + to_scan_again, add_to_parent = self._add_requirement_to_set( + requirement_set, + sub_install_req, + parent_req_name=parent_req_name, + extras_requested=extras_requested, + ) + if parent_req_name and add_to_parent: + self._discovered_dependencies[parent_req_name].append(add_to_parent) + more_reqs.extend(to_scan_again) + + with indent_log(): + # We add req_to_install before its dependencies, so that we + # can refer to it when adding dependencies. + assert req_to_install.name is not None + if not requirement_set.has_requirement(req_to_install.name): + # 'unnamed' requirements will get added here + # 'unnamed' requirements can only come from being directly + # provided by the user. + assert req_to_install.user_supplied + self._add_requirement_to_set( + requirement_set, req_to_install, parent_req_name=None + ) + + if not self.ignore_dependencies: + if req_to_install.extras: + logger.debug( + "Installing extra requirements: %r", + ",".join(req_to_install.extras), + ) + missing_requested = sorted( + set(req_to_install.extras) - set(dist.iter_provided_extras()) + ) + for missing in missing_requested: + logger.warning( + "%s %s does not provide the extra '%s'", + dist.raw_name, + dist.version, + missing, + ) + + available_requested = sorted( + set(dist.iter_provided_extras()) & set(req_to_install.extras) + ) + for subreq in dist.iter_dependencies(available_requested): + add_req(subreq, extras_requested=available_requested) + + return more_reqs + + def get_installation_order( + self, req_set: RequirementSet + ) -> List[InstallRequirement]: + """Create the installation order. + + The installation order is topological - requirements are installed + before the requiring thing. We break cycles at an arbitrary point, + and make no other guarantees. + """ + # The current implementation, which we may change at any point + # installs the user specified things in the order given, except when + # dependencies must come earlier to achieve topological order. + order = [] + ordered_reqs: Set[InstallRequirement] = set() + + def schedule(req: InstallRequirement) -> None: + if req.satisfied_by or req in ordered_reqs: + return + if req.constraint: + return + ordered_reqs.add(req) + for dep in self._discovered_dependencies[req.name]: + schedule(dep) + order.append(req) + + for install_req in req_set.requirements.values(): + schedule(install_req) + return order diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25a6e158d533678e9f808994bc98895d39511ca5 GIT binary patch literal 228 zcmYjLI|{-;5M9ASgdD^onZm|G#KKZ+#5yGAse_x{WwQyy8+a72VC^YvJ%XK;6BP8p zd-HwFY@%qW1lJpJ7ayJcqbJ2&SoK8;wN|6v>Y{wt{>kf12U~)0!jYkAfd<;T5+=|x z%PVqiI9Lee#e^%i80 bQn@4ER2Dd-Y2w=ZU|QOu)|hoKK85NHeJ(+f literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cac0fd2463d22ab01889197ae28728d3336ce0e6 GIT binary patch literal 6295 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01ea69a9c93aacc3586009e14185cabe29470d3a GIT binary patch literal 19702 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3a8d9eed3a537898c876a4373ef1470251c740a GIT binary patch literal 21651 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 X!AS+spJmQEWmg`re7bU`^2|R0+;-^H literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..805064af44d2655b36603078c750f2142b817004 GIT binary patch literal 8741 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 Co(tmu literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/base.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/base.py new file mode 100644 index 000000000..0f31dc9b3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/base.py @@ -0,0 +1,139 @@ +from dataclasses import dataclass +from typing import FrozenSet, Iterable, Optional, Tuple + +from pip._vendor.packaging.specifiers import SpecifierSet +from pip._vendor.packaging.utils import NormalizedName +from pip._vendor.packaging.version import Version + +from pip._internal.models.link import Link, links_equivalent +from pip._internal.req.req_install import InstallRequirement +from pip._internal.utils.hashes import Hashes + +CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]] + + +def format_name(project: NormalizedName, extras: FrozenSet[NormalizedName]) -> str: + if not extras: + return project + extras_expr = ",".join(sorted(extras)) + return f"{project}[{extras_expr}]" + + +@dataclass(frozen=True) +class Constraint: + specifier: SpecifierSet + hashes: Hashes + links: FrozenSet[Link] + + @classmethod + def empty(cls) -> "Constraint": + return Constraint(SpecifierSet(), Hashes(), frozenset()) + + @classmethod + def from_ireq(cls, ireq: InstallRequirement) -> "Constraint": + links = frozenset([ireq.link]) if ireq.link else frozenset() + return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links) + + def __bool__(self) -> bool: + return bool(self.specifier) or bool(self.hashes) or bool(self.links) + + def __and__(self, other: InstallRequirement) -> "Constraint": + if not isinstance(other, InstallRequirement): + return NotImplemented + specifier = self.specifier & other.specifier + hashes = self.hashes & other.hashes(trust_internet=False) + links = self.links + if other.link: + links = links.union([other.link]) + return Constraint(specifier, hashes, links) + + def is_satisfied_by(self, candidate: "Candidate") -> bool: + # Reject if there are any mismatched URL constraints on this package. + if self.links and not all(_match_link(link, candidate) for link in self.links): + return False + # We can safely always allow prereleases here since PackageFinder + # already implements the prerelease logic, and would have filtered out + # prerelease candidates if the user does not expect them. + return self.specifier.contains(candidate.version, prereleases=True) + + +class Requirement: + @property + def project_name(self) -> NormalizedName: + """The "project name" of a requirement. + + This is different from ``name`` if this requirement contains extras, + in which case ``name`` would contain the ``[...]`` part, while this + refers to the name of the project. + """ + raise NotImplementedError("Subclass should override") + + @property + def name(self) -> str: + """The name identifying this requirement in the resolver. + + This is different from ``project_name`` if this requirement contains + extras, where ``project_name`` would not contain the ``[...]`` part. + """ + raise NotImplementedError("Subclass should override") + + def is_satisfied_by(self, candidate: "Candidate") -> bool: + return False + + def get_candidate_lookup(self) -> CandidateLookup: + raise NotImplementedError("Subclass should override") + + def format_for_error(self) -> str: + raise NotImplementedError("Subclass should override") + + +def _match_link(link: Link, candidate: "Candidate") -> bool: + if candidate.source_link: + return links_equivalent(link, candidate.source_link) + return False + + +class Candidate: + @property + def project_name(self) -> NormalizedName: + """The "project name" of the candidate. + + This is different from ``name`` if this candidate contains extras, + in which case ``name`` would contain the ``[...]`` part, while this + refers to the name of the project. + """ + raise NotImplementedError("Override in subclass") + + @property + def name(self) -> str: + """The name identifying this candidate in the resolver. + + This is different from ``project_name`` if this candidate contains + extras, where ``project_name`` would not contain the ``[...]`` part. + """ + raise NotImplementedError("Override in subclass") + + @property + def version(self) -> Version: + raise NotImplementedError("Override in subclass") + + @property + def is_installed(self) -> bool: + raise NotImplementedError("Override in subclass") + + @property + def is_editable(self) -> bool: + raise NotImplementedError("Override in subclass") + + @property + def source_link(self) -> Optional[Link]: + raise NotImplementedError("Override in subclass") + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + raise NotImplementedError("Override in subclass") + + def get_install_requirement(self) -> Optional[InstallRequirement]: + raise NotImplementedError("Override in subclass") + + def format_for_error(self) -> str: + raise NotImplementedError("Subclass should override") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/candidates.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/candidates.py new file mode 100644 index 000000000..6617644fe --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/candidates.py @@ -0,0 +1,574 @@ +import logging +import sys +from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast + +from pip._vendor.packaging.requirements import InvalidRequirement +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.version import Version + +from pip._internal.exceptions import ( + HashError, + InstallationSubprocessError, + InvalidInstalledPackage, + MetadataInconsistent, + MetadataInvalid, +) +from pip._internal.metadata import BaseDistribution +from pip._internal.models.link import Link, links_equivalent +from pip._internal.models.wheel import Wheel +from pip._internal.req.constructors import ( + install_req_from_editable, + install_req_from_line, +) +from pip._internal.req.req_install import InstallRequirement +from pip._internal.utils.direct_url_helpers import direct_url_from_link +from pip._internal.utils.misc import normalize_version_info + +from .base import Candidate, Requirement, format_name + +if TYPE_CHECKING: + from .factory import Factory + +logger = logging.getLogger(__name__) + +BaseCandidate = Union[ + "AlreadyInstalledCandidate", + "EditableCandidate", + "LinkCandidate", +] + +# Avoid conflicting with the PyPI package "Python". +REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "") + + +def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]: + """The runtime version of BaseCandidate.""" + base_candidate_classes = ( + AlreadyInstalledCandidate, + EditableCandidate, + LinkCandidate, + ) + if isinstance(candidate, base_candidate_classes): + return candidate + return None + + +def make_install_req_from_link( + link: Link, template: InstallRequirement +) -> InstallRequirement: + assert not template.editable, "template is editable" + if template.req: + line = str(template.req) + else: + line = link.url + ireq = install_req_from_line( + line, + user_supplied=template.user_supplied, + comes_from=template.comes_from, + use_pep517=template.use_pep517, + isolated=template.isolated, + constraint=template.constraint, + global_options=template.global_options, + hash_options=template.hash_options, + config_settings=template.config_settings, + ) + ireq.original_link = template.original_link + ireq.link = link + ireq.extras = template.extras + return ireq + + +def make_install_req_from_editable( + link: Link, template: InstallRequirement +) -> InstallRequirement: + assert template.editable, "template not editable" + ireq = install_req_from_editable( + link.url, + user_supplied=template.user_supplied, + comes_from=template.comes_from, + use_pep517=template.use_pep517, + isolated=template.isolated, + constraint=template.constraint, + permit_editable_wheels=template.permit_editable_wheels, + global_options=template.global_options, + hash_options=template.hash_options, + config_settings=template.config_settings, + ) + ireq.extras = template.extras + return ireq + + +def _make_install_req_from_dist( + dist: BaseDistribution, template: InstallRequirement +) -> InstallRequirement: + if template.req: + line = str(template.req) + elif template.link: + line = f"{dist.canonical_name} @ {template.link.url}" + else: + line = f"{dist.canonical_name}=={dist.version}" + ireq = install_req_from_line( + line, + user_supplied=template.user_supplied, + comes_from=template.comes_from, + use_pep517=template.use_pep517, + isolated=template.isolated, + constraint=template.constraint, + global_options=template.global_options, + hash_options=template.hash_options, + config_settings=template.config_settings, + ) + ireq.satisfied_by = dist + return ireq + + +class _InstallRequirementBackedCandidate(Candidate): + """A candidate backed by an ``InstallRequirement``. + + This represents a package request with the target not being already + in the environment, and needs to be fetched and installed. The backing + ``InstallRequirement`` is responsible for most of the leg work; this + class exposes appropriate information to the resolver. + + :param link: The link passed to the ``InstallRequirement``. The backing + ``InstallRequirement`` will use this link to fetch the distribution. + :param source_link: The link this candidate "originates" from. This is + different from ``link`` when the link is found in the wheel cache. + ``link`` would point to the wheel cache, while this points to the + found remote link (e.g. from pypi.org). + """ + + dist: BaseDistribution + is_installed = False + + def __init__( + self, + link: Link, + source_link: Link, + ireq: InstallRequirement, + factory: "Factory", + name: Optional[NormalizedName] = None, + version: Optional[Version] = None, + ) -> None: + self._link = link + self._source_link = source_link + self._factory = factory + self._ireq = ireq + self._name = name + self._version = version + self.dist = self._prepare() + self._hash: Optional[int] = None + + def __str__(self) -> str: + return f"{self.name} {self.version}" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({str(self._link)!r})" + + def __hash__(self) -> int: + if self._hash is not None: + return self._hash + + self._hash = hash((self.__class__, self._link)) + return self._hash + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return links_equivalent(self._link, other._link) + return False + + @property + def source_link(self) -> Optional[Link]: + return self._source_link + + @property + def project_name(self) -> NormalizedName: + """The normalised name of the project the candidate refers to""" + if self._name is None: + self._name = self.dist.canonical_name + return self._name + + @property + def name(self) -> str: + return self.project_name + + @property + def version(self) -> Version: + if self._version is None: + self._version = self.dist.version + return self._version + + def format_for_error(self) -> str: + return ( + f"{self.name} {self.version} " + f"(from {self._link.file_path if self._link.is_file else self._link})" + ) + + def _prepare_distribution(self) -> BaseDistribution: + raise NotImplementedError("Override in subclass") + + def _check_metadata_consistency(self, dist: BaseDistribution) -> None: + """Check for consistency of project name and version of dist.""" + if self._name is not None and self._name != dist.canonical_name: + raise MetadataInconsistent( + self._ireq, + "name", + self._name, + dist.canonical_name, + ) + if self._version is not None and self._version != dist.version: + raise MetadataInconsistent( + self._ireq, + "version", + str(self._version), + str(dist.version), + ) + # check dependencies are valid + # TODO performance: this means we iterate the dependencies at least twice, + # we may want to cache parsed Requires-Dist + try: + list(dist.iter_dependencies(list(dist.iter_provided_extras()))) + except InvalidRequirement as e: + raise MetadataInvalid(self._ireq, str(e)) + + def _prepare(self) -> BaseDistribution: + try: + dist = self._prepare_distribution() + except HashError as e: + # Provide HashError the underlying ireq that caused it. This + # provides context for the resulting error message to show the + # offending line to the user. + e.req = self._ireq + raise + except InstallationSubprocessError as exc: + # The output has been presented already, so don't duplicate it. + exc.context = "See above for output." + raise + + self._check_metadata_consistency(dist) + return dist + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + requires = self.dist.iter_dependencies() if with_requires else () + for r in requires: + yield from self._factory.make_requirements_from_spec(str(r), self._ireq) + yield self._factory.make_requires_python_requirement(self.dist.requires_python) + + def get_install_requirement(self) -> Optional[InstallRequirement]: + return self._ireq + + +class LinkCandidate(_InstallRequirementBackedCandidate): + is_editable = False + + def __init__( + self, + link: Link, + template: InstallRequirement, + factory: "Factory", + name: Optional[NormalizedName] = None, + version: Optional[Version] = None, + ) -> None: + source_link = link + cache_entry = factory.get_wheel_cache_entry(source_link, name) + if cache_entry is not None: + logger.debug("Using cached wheel link: %s", cache_entry.link) + link = cache_entry.link + ireq = make_install_req_from_link(link, template) + assert ireq.link == link + if ireq.link.is_wheel and not ireq.link.is_file: + wheel = Wheel(ireq.link.filename) + wheel_name = canonicalize_name(wheel.name) + assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel" + # Version may not be present for PEP 508 direct URLs + if version is not None: + wheel_version = Version(wheel.version) + assert ( + version == wheel_version + ), f"{version!r} != {wheel_version!r} for wheel {name}" + + if cache_entry is not None: + assert ireq.link.is_wheel + assert ireq.link.is_file + if cache_entry.persistent and template.link is template.original_link: + ireq.cached_wheel_source_link = source_link + if cache_entry.origin is not None: + ireq.download_info = cache_entry.origin + else: + # Legacy cache entry that does not have origin.json. + # download_info may miss the archive_info.hashes field. + ireq.download_info = direct_url_from_link( + source_link, link_is_in_wheel_cache=cache_entry.persistent + ) + + super().__init__( + link=link, + source_link=source_link, + ireq=ireq, + factory=factory, + name=name, + version=version, + ) + + def _prepare_distribution(self) -> BaseDistribution: + preparer = self._factory.preparer + return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True) + + +class EditableCandidate(_InstallRequirementBackedCandidate): + is_editable = True + + def __init__( + self, + link: Link, + template: InstallRequirement, + factory: "Factory", + name: Optional[NormalizedName] = None, + version: Optional[Version] = None, + ) -> None: + super().__init__( + link=link, + source_link=link, + ireq=make_install_req_from_editable(link, template), + factory=factory, + name=name, + version=version, + ) + + def _prepare_distribution(self) -> BaseDistribution: + return self._factory.preparer.prepare_editable_requirement(self._ireq) + + +class AlreadyInstalledCandidate(Candidate): + is_installed = True + source_link = None + + def __init__( + self, + dist: BaseDistribution, + template: InstallRequirement, + factory: "Factory", + ) -> None: + self.dist = dist + self._ireq = _make_install_req_from_dist(dist, template) + self._factory = factory + self._version = None + + # This is just logging some messages, so we can do it eagerly. + # The returned dist would be exactly the same as self.dist because we + # set satisfied_by in _make_install_req_from_dist. + # TODO: Supply reason based on force_reinstall and upgrade_strategy. + skip_reason = "already satisfied" + factory.preparer.prepare_installed_requirement(self._ireq, skip_reason) + + def __str__(self) -> str: + return str(self.dist) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.dist!r})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AlreadyInstalledCandidate): + return NotImplemented + return self.name == other.name and self.version == other.version + + def __hash__(self) -> int: + return hash((self.name, self.version)) + + @property + def project_name(self) -> NormalizedName: + return self.dist.canonical_name + + @property + def name(self) -> str: + return self.project_name + + @property + def version(self) -> Version: + if self._version is None: + self._version = self.dist.version + return self._version + + @property + def is_editable(self) -> bool: + return self.dist.editable + + def format_for_error(self) -> str: + return f"{self.name} {self.version} (Installed)" + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + if not with_requires: + return + + try: + for r in self.dist.iter_dependencies(): + yield from self._factory.make_requirements_from_spec(str(r), self._ireq) + except InvalidRequirement as exc: + raise InvalidInstalledPackage(dist=self.dist, invalid_exc=exc) from None + + def get_install_requirement(self) -> Optional[InstallRequirement]: + return None + + +class ExtrasCandidate(Candidate): + """A candidate that has 'extras', indicating additional dependencies. + + Requirements can be for a project with dependencies, something like + foo[extra]. The extras don't affect the project/version being installed + directly, but indicate that we need additional dependencies. We model that + by having an artificial ExtrasCandidate that wraps the "base" candidate. + + The ExtrasCandidate differs from the base in the following ways: + + 1. It has a unique name, of the form foo[extra]. This causes the resolver + to treat it as a separate node in the dependency graph. + 2. When we're getting the candidate's dependencies, + a) We specify that we want the extra dependencies as well. + b) We add a dependency on the base candidate. + See below for why this is needed. + 3. We return None for the underlying InstallRequirement, as the base + candidate will provide it, and we don't want to end up with duplicates. + + The dependency on the base candidate is needed so that the resolver can't + decide that it should recommend foo[extra1] version 1.0 and foo[extra2] + version 2.0. Having those candidates depend on foo=1.0 and foo=2.0 + respectively forces the resolver to recognise that this is a conflict. + """ + + def __init__( + self, + base: BaseCandidate, + extras: FrozenSet[str], + *, + comes_from: Optional[InstallRequirement] = None, + ) -> None: + """ + :param comes_from: the InstallRequirement that led to this candidate if it + differs from the base's InstallRequirement. This will often be the + case in the sense that this candidate's requirement has the extras + while the base's does not. Unlike the InstallRequirement backed + candidates, this requirement is used solely for reporting purposes, + it does not do any leg work. + """ + self.base = base + self.extras = frozenset(canonicalize_name(e) for e in extras) + self._comes_from = comes_from if comes_from is not None else self.base._ireq + + def __str__(self) -> str: + name, rest = str(self.base).split(" ", 1) + return "{}[{}] {}".format(name, ",".join(self.extras), rest) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(base={self.base!r}, extras={self.extras!r})" + + def __hash__(self) -> int: + return hash((self.base, self.extras)) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return self.base == other.base and self.extras == other.extras + return False + + @property + def project_name(self) -> NormalizedName: + return self.base.project_name + + @property + def name(self) -> str: + """The normalised name of the project the candidate refers to""" + return format_name(self.base.project_name, self.extras) + + @property + def version(self) -> Version: + return self.base.version + + def format_for_error(self) -> str: + return "{} [{}]".format( + self.base.format_for_error(), ", ".join(sorted(self.extras)) + ) + + @property + def is_installed(self) -> bool: + return self.base.is_installed + + @property + def is_editable(self) -> bool: + return self.base.is_editable + + @property + def source_link(self) -> Optional[Link]: + return self.base.source_link + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + factory = self.base._factory + + # Add a dependency on the exact base + # (See note 2b in the class docstring) + yield factory.make_requirement_from_candidate(self.base) + if not with_requires: + return + + # The user may have specified extras that the candidate doesn't + # support. We ignore any unsupported extras here. + valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras()) + invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras()) + for extra in sorted(invalid_extras): + logger.warning( + "%s %s does not provide the extra '%s'", + self.base.name, + self.version, + extra, + ) + + for r in self.base.dist.iter_dependencies(valid_extras): + yield from factory.make_requirements_from_spec( + str(r), + self._comes_from, + valid_extras, + ) + + def get_install_requirement(self) -> Optional[InstallRequirement]: + # We don't return anything here, because we always + # depend on the base candidate, and we'll get the + # install requirement from that. + return None + + +class RequiresPythonCandidate(Candidate): + is_installed = False + source_link = None + + def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None: + if py_version_info is not None: + version_info = normalize_version_info(py_version_info) + else: + version_info = sys.version_info[:3] + self._version = Version(".".join(str(c) for c in version_info)) + + # We don't need to implement __eq__() and __ne__() since there is always + # only one RequiresPythonCandidate in a resolution, i.e. the host Python. + # The built-in object.__eq__() and object.__ne__() do exactly what we want. + + def __str__(self) -> str: + return f"Python {self._version}" + + @property + def project_name(self) -> NormalizedName: + return REQUIRES_PYTHON_IDENTIFIER + + @property + def name(self) -> str: + return REQUIRES_PYTHON_IDENTIFIER + + @property + def version(self) -> Version: + return self._version + + def format_for_error(self) -> str: + return f"Python {self.version}" + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + return () + + def get_install_requirement(self) -> Optional[InstallRequirement]: + return None diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/factory.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/factory.py new file mode 100644 index 000000000..6c273eb88 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/factory.py @@ -0,0 +1,823 @@ +import contextlib +import functools +import logging +from typing import ( + TYPE_CHECKING, + Callable, + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Mapping, + NamedTuple, + Optional, + Protocol, + Sequence, + Set, + Tuple, + TypeVar, + cast, +) + +from pip._vendor.packaging.requirements import InvalidRequirement +from pip._vendor.packaging.specifiers import SpecifierSet +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.version import InvalidVersion, Version +from pip._vendor.resolvelib import ResolutionImpossible + +from pip._internal.cache import CacheEntry, WheelCache +from pip._internal.exceptions import ( + DistributionNotFound, + InstallationError, + InvalidInstalledPackage, + MetadataInconsistent, + MetadataInvalid, + UnsupportedPythonVersion, + UnsupportedWheel, +) +from pip._internal.index.package_finder import PackageFinder +from pip._internal.metadata import BaseDistribution, get_default_environment +from pip._internal.models.link import Link +from pip._internal.models.wheel import Wheel +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req.constructors import ( + install_req_drop_extras, + install_req_from_link_and_ireq, +) +from pip._internal.req.req_install import ( + InstallRequirement, + check_invalid_constraint_type, +) +from pip._internal.resolution.base import InstallRequirementProvider +from pip._internal.utils.compatibility_tags import get_supported +from pip._internal.utils.hashes import Hashes +from pip._internal.utils.packaging import get_requirement +from pip._internal.utils.virtualenv import running_under_virtualenv + +from .base import Candidate, Constraint, Requirement +from .candidates import ( + AlreadyInstalledCandidate, + BaseCandidate, + EditableCandidate, + ExtrasCandidate, + LinkCandidate, + RequiresPythonCandidate, + as_base_candidate, +) +from .found_candidates import FoundCandidates, IndexCandidateInfo +from .requirements import ( + ExplicitRequirement, + RequiresPythonRequirement, + SpecifierRequirement, + SpecifierWithoutExtrasRequirement, + UnsatisfiableRequirement, +) + +if TYPE_CHECKING: + + class ConflictCause(Protocol): + requirement: RequiresPythonRequirement + parent: Candidate + + +logger = logging.getLogger(__name__) + +C = TypeVar("C") +Cache = Dict[Link, C] + + +class CollectedRootRequirements(NamedTuple): + requirements: List[Requirement] + constraints: Dict[str, Constraint] + user_requested: Dict[str, int] + + +class Factory: + def __init__( + self, + finder: PackageFinder, + preparer: RequirementPreparer, + make_install_req: InstallRequirementProvider, + wheel_cache: Optional[WheelCache], + use_user_site: bool, + force_reinstall: bool, + ignore_installed: bool, + ignore_requires_python: bool, + py_version_info: Optional[Tuple[int, ...]] = None, + ) -> None: + self._finder = finder + self.preparer = preparer + self._wheel_cache = wheel_cache + self._python_candidate = RequiresPythonCandidate(py_version_info) + self._make_install_req_from_spec = make_install_req + self._use_user_site = use_user_site + self._force_reinstall = force_reinstall + self._ignore_requires_python = ignore_requires_python + + self._build_failures: Cache[InstallationError] = {} + self._link_candidate_cache: Cache[LinkCandidate] = {} + self._editable_candidate_cache: Cache[EditableCandidate] = {} + self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {} + self._extras_candidate_cache: Dict[ + Tuple[int, FrozenSet[NormalizedName]], ExtrasCandidate + ] = {} + self._supported_tags_cache = get_supported() + + if not ignore_installed: + env = get_default_environment() + self._installed_dists = { + dist.canonical_name: dist + for dist in env.iter_installed_distributions(local_only=False) + } + else: + self._installed_dists = {} + + @property + def force_reinstall(self) -> bool: + return self._force_reinstall + + def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None: + if not link.is_wheel: + return + wheel = Wheel(link.filename) + if wheel.supported(self._finder.target_python.get_unsorted_tags()): + return + msg = f"{link.filename} is not a supported wheel on this platform." + raise UnsupportedWheel(msg) + + def _make_extras_candidate( + self, + base: BaseCandidate, + extras: FrozenSet[str], + *, + comes_from: Optional[InstallRequirement] = None, + ) -> ExtrasCandidate: + cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras)) + try: + candidate = self._extras_candidate_cache[cache_key] + except KeyError: + candidate = ExtrasCandidate(base, extras, comes_from=comes_from) + self._extras_candidate_cache[cache_key] = candidate + return candidate + + def _make_candidate_from_dist( + self, + dist: BaseDistribution, + extras: FrozenSet[str], + template: InstallRequirement, + ) -> Candidate: + try: + base = self._installed_candidate_cache[dist.canonical_name] + except KeyError: + base = AlreadyInstalledCandidate(dist, template, factory=self) + self._installed_candidate_cache[dist.canonical_name] = base + if not extras: + return base + return self._make_extras_candidate(base, extras, comes_from=template) + + def _make_candidate_from_link( + self, + link: Link, + extras: FrozenSet[str], + template: InstallRequirement, + name: Optional[NormalizedName], + version: Optional[Version], + ) -> Optional[Candidate]: + base: Optional[BaseCandidate] = self._make_base_candidate_from_link( + link, template, name, version + ) + if not extras or base is None: + return base + return self._make_extras_candidate(base, extras, comes_from=template) + + def _make_base_candidate_from_link( + self, + link: Link, + template: InstallRequirement, + name: Optional[NormalizedName], + version: Optional[Version], + ) -> Optional[BaseCandidate]: + # TODO: Check already installed candidate, and use it if the link and + # editable flag match. + + if link in self._build_failures: + # We already tried this candidate before, and it does not build. + # Don't bother trying again. + return None + + if template.editable: + if link not in self._editable_candidate_cache: + try: + self._editable_candidate_cache[link] = EditableCandidate( + link, + template, + factory=self, + name=name, + version=version, + ) + except (MetadataInconsistent, MetadataInvalid) as e: + logger.info( + "Discarding [blue underline]%s[/]: [yellow]%s[reset]", + link, + e, + extra={"markup": True}, + ) + self._build_failures[link] = e + return None + + return self._editable_candidate_cache[link] + else: + if link not in self._link_candidate_cache: + try: + self._link_candidate_cache[link] = LinkCandidate( + link, + template, + factory=self, + name=name, + version=version, + ) + except MetadataInconsistent as e: + logger.info( + "Discarding [blue underline]%s[/]: [yellow]%s[reset]", + link, + e, + extra={"markup": True}, + ) + self._build_failures[link] = e + return None + return self._link_candidate_cache[link] + + def _iter_found_candidates( + self, + ireqs: Sequence[InstallRequirement], + specifier: SpecifierSet, + hashes: Hashes, + prefers_installed: bool, + incompatible_ids: Set[int], + ) -> Iterable[Candidate]: + if not ireqs: + return () + + # The InstallRequirement implementation requires us to give it a + # "template". Here we just choose the first requirement to represent + # all of them. + # Hopefully the Project model can correct this mismatch in the future. + template = ireqs[0] + assert template.req, "Candidates found on index must be PEP 508" + name = canonicalize_name(template.req.name) + + extras: FrozenSet[str] = frozenset() + for ireq in ireqs: + assert ireq.req, "Candidates found on index must be PEP 508" + specifier &= ireq.req.specifier + hashes &= ireq.hashes(trust_internet=False) + extras |= frozenset(ireq.extras) + + def _get_installed_candidate() -> Optional[Candidate]: + """Get the candidate for the currently-installed version.""" + # If --force-reinstall is set, we want the version from the index + # instead, so we "pretend" there is nothing installed. + if self._force_reinstall: + return None + try: + installed_dist = self._installed_dists[name] + except KeyError: + return None + + try: + # Don't use the installed distribution if its version + # does not fit the current dependency graph. + if not specifier.contains(installed_dist.version, prereleases=True): + return None + except InvalidVersion as e: + raise InvalidInstalledPackage(dist=installed_dist, invalid_exc=e) + + candidate = self._make_candidate_from_dist( + dist=installed_dist, + extras=extras, + template=template, + ) + # The candidate is a known incompatibility. Don't use it. + if id(candidate) in incompatible_ids: + return None + return candidate + + def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]: + result = self._finder.find_best_candidate( + project_name=name, + specifier=specifier, + hashes=hashes, + ) + icans = result.applicable_candidates + + # PEP 592: Yanked releases are ignored unless the specifier + # explicitly pins a version (via '==' or '===') that can be + # solely satisfied by a yanked release. + all_yanked = all(ican.link.is_yanked for ican in icans) + + def is_pinned(specifier: SpecifierSet) -> bool: + for sp in specifier: + if sp.operator == "===": + return True + if sp.operator != "==": + continue + if sp.version.endswith(".*"): + continue + return True + return False + + pinned = is_pinned(specifier) + + # PackageFinder returns earlier versions first, so we reverse. + for ican in reversed(icans): + if not (all_yanked and pinned) and ican.link.is_yanked: + continue + func = functools.partial( + self._make_candidate_from_link, + link=ican.link, + extras=extras, + template=template, + name=name, + version=ican.version, + ) + yield ican.version, func + + return FoundCandidates( + iter_index_candidate_infos, + _get_installed_candidate(), + prefers_installed, + incompatible_ids, + ) + + def _iter_explicit_candidates_from_base( + self, + base_requirements: Iterable[Requirement], + extras: FrozenSet[str], + ) -> Iterator[Candidate]: + """Produce explicit candidates from the base given an extra-ed package. + + :param base_requirements: Requirements known to the resolver. The + requirements are guaranteed to not have extras. + :param extras: The extras to inject into the explicit requirements' + candidates. + """ + for req in base_requirements: + lookup_cand, _ = req.get_candidate_lookup() + if lookup_cand is None: # Not explicit. + continue + # We've stripped extras from the identifier, and should always + # get a BaseCandidate here, unless there's a bug elsewhere. + base_cand = as_base_candidate(lookup_cand) + assert base_cand is not None, "no extras here" + yield self._make_extras_candidate(base_cand, extras) + + def _iter_candidates_from_constraints( + self, + identifier: str, + constraint: Constraint, + template: InstallRequirement, + ) -> Iterator[Candidate]: + """Produce explicit candidates from constraints. + + This creates "fake" InstallRequirement objects that are basically clones + of what "should" be the template, but with original_link set to link. + """ + for link in constraint.links: + self._fail_if_link_is_unsupported_wheel(link) + candidate = self._make_base_candidate_from_link( + link, + template=install_req_from_link_and_ireq(link, template), + name=canonicalize_name(identifier), + version=None, + ) + if candidate: + yield candidate + + def find_candidates( + self, + identifier: str, + requirements: Mapping[str, Iterable[Requirement]], + incompatibilities: Mapping[str, Iterator[Candidate]], + constraint: Constraint, + prefers_installed: bool, + is_satisfied_by: Callable[[Requirement, Candidate], bool], + ) -> Iterable[Candidate]: + # Collect basic lookup information from the requirements. + explicit_candidates: Set[Candidate] = set() + ireqs: List[InstallRequirement] = [] + for req in requirements[identifier]: + cand, ireq = req.get_candidate_lookup() + if cand is not None: + explicit_candidates.add(cand) + if ireq is not None: + ireqs.append(ireq) + + # If the current identifier contains extras, add requires and explicit + # candidates from entries from extra-less identifier. + with contextlib.suppress(InvalidRequirement): + parsed_requirement = get_requirement(identifier) + if parsed_requirement.name != identifier: + explicit_candidates.update( + self._iter_explicit_candidates_from_base( + requirements.get(parsed_requirement.name, ()), + frozenset(parsed_requirement.extras), + ), + ) + for req in requirements.get(parsed_requirement.name, []): + _, ireq = req.get_candidate_lookup() + if ireq is not None: + ireqs.append(ireq) + + # Add explicit candidates from constraints. We only do this if there are + # known ireqs, which represent requirements not already explicit. If + # there are no ireqs, we're constraining already-explicit requirements, + # which is handled later when we return the explicit candidates. + if ireqs: + try: + explicit_candidates.update( + self._iter_candidates_from_constraints( + identifier, + constraint, + template=ireqs[0], + ), + ) + except UnsupportedWheel: + # If we're constrained to install a wheel incompatible with the + # target architecture, no candidates will ever be valid. + return () + + # Since we cache all the candidates, incompatibility identification + # can be made quicker by comparing only the id() values. + incompat_ids = {id(c) for c in incompatibilities.get(identifier, ())} + + # If none of the requirements want an explicit candidate, we can ask + # the finder for candidates. + if not explicit_candidates: + return self._iter_found_candidates( + ireqs, + constraint.specifier, + constraint.hashes, + prefers_installed, + incompat_ids, + ) + + return ( + c + for c in explicit_candidates + if id(c) not in incompat_ids + and constraint.is_satisfied_by(c) + and all(is_satisfied_by(req, c) for req in requirements[identifier]) + ) + + def _make_requirements_from_install_req( + self, ireq: InstallRequirement, requested_extras: Iterable[str] + ) -> Iterator[Requirement]: + """ + Returns requirement objects associated with the given InstallRequirement. In + most cases this will be a single object but the following special cases exist: + - the InstallRequirement has markers that do not apply -> result is empty + - the InstallRequirement has both a constraint (or link) and extras + -> result is split in two requirement objects: one with the constraint + (or link) and one with the extra. This allows centralized constraint + handling for the base, resulting in fewer candidate rejections. + """ + if not ireq.match_markers(requested_extras): + logger.info( + "Ignoring %s: markers '%s' don't match your environment", + ireq.name, + ireq.markers, + ) + elif not ireq.link: + if ireq.extras and ireq.req is not None and ireq.req.specifier: + yield SpecifierWithoutExtrasRequirement(ireq) + yield SpecifierRequirement(ireq) + else: + self._fail_if_link_is_unsupported_wheel(ireq.link) + # Always make the link candidate for the base requirement to make it + # available to `find_candidates` for explicit candidate lookup for any + # set of extras. + # The extras are required separately via a second requirement. + cand = self._make_base_candidate_from_link( + ireq.link, + template=install_req_drop_extras(ireq) if ireq.extras else ireq, + name=canonicalize_name(ireq.name) if ireq.name else None, + version=None, + ) + if cand is None: + # There's no way we can satisfy a URL requirement if the underlying + # candidate fails to build. An unnamed URL must be user-supplied, so + # we fail eagerly. If the URL is named, an unsatisfiable requirement + # can make the resolver do the right thing, either backtrack (and + # maybe find some other requirement that's buildable) or raise a + # ResolutionImpossible eventually. + if not ireq.name: + raise self._build_failures[ireq.link] + yield UnsatisfiableRequirement(canonicalize_name(ireq.name)) + else: + # require the base from the link + yield self.make_requirement_from_candidate(cand) + if ireq.extras: + # require the extras on top of the base candidate + yield self.make_requirement_from_candidate( + self._make_extras_candidate(cand, frozenset(ireq.extras)) + ) + + def collect_root_requirements( + self, root_ireqs: List[InstallRequirement] + ) -> CollectedRootRequirements: + collected = CollectedRootRequirements([], {}, {}) + for i, ireq in enumerate(root_ireqs): + if ireq.constraint: + # Ensure we only accept valid constraints + problem = check_invalid_constraint_type(ireq) + if problem: + raise InstallationError(problem) + if not ireq.match_markers(): + continue + assert ireq.name, "Constraint must be named" + name = canonicalize_name(ireq.name) + if name in collected.constraints: + collected.constraints[name] &= ireq + else: + collected.constraints[name] = Constraint.from_ireq(ireq) + else: + reqs = list( + self._make_requirements_from_install_req( + ireq, + requested_extras=(), + ) + ) + if not reqs: + continue + template = reqs[0] + if ireq.user_supplied and template.name not in collected.user_requested: + collected.user_requested[template.name] = i + collected.requirements.extend(reqs) + # Put requirements with extras at the end of the root requires. This does not + # affect resolvelib's picking preference but it does affect its initial criteria + # population: by putting extras at the end we enable the candidate finder to + # present resolvelib with a smaller set of candidates to resolvelib, already + # taking into account any non-transient constraints on the associated base. This + # means resolvelib will have fewer candidates to visit and reject. + # Python's list sort is stable, meaning relative order is kept for objects with + # the same key. + collected.requirements.sort(key=lambda r: r.name != r.project_name) + return collected + + def make_requirement_from_candidate( + self, candidate: Candidate + ) -> ExplicitRequirement: + return ExplicitRequirement(candidate) + + def make_requirements_from_spec( + self, + specifier: str, + comes_from: Optional[InstallRequirement], + requested_extras: Iterable[str] = (), + ) -> Iterator[Requirement]: + """ + Returns requirement objects associated with the given specifier. In most cases + this will be a single object but the following special cases exist: + - the specifier has markers that do not apply -> result is empty + - the specifier has both a constraint and extras -> result is split + in two requirement objects: one with the constraint and one with the + extra. This allows centralized constraint handling for the base, + resulting in fewer candidate rejections. + """ + ireq = self._make_install_req_from_spec(specifier, comes_from) + return self._make_requirements_from_install_req(ireq, requested_extras) + + def make_requires_python_requirement( + self, + specifier: SpecifierSet, + ) -> Optional[Requirement]: + if self._ignore_requires_python: + return None + # Don't bother creating a dependency for an empty Requires-Python. + if not str(specifier): + return None + return RequiresPythonRequirement(specifier, self._python_candidate) + + def get_wheel_cache_entry( + self, link: Link, name: Optional[str] + ) -> Optional[CacheEntry]: + """Look up the link in the wheel cache. + + If ``preparer.require_hashes`` is True, don't use the wheel cache, + because cached wheels, always built locally, have different hashes + than the files downloaded from the index server and thus throw false + hash mismatches. Furthermore, cached wheels at present have + nondeterministic contents due to file modification times. + """ + if self._wheel_cache is None: + return None + return self._wheel_cache.get_cache_entry( + link=link, + package_name=name, + supported_tags=self._supported_tags_cache, + ) + + def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]: + # TODO: Are there more cases this needs to return True? Editable? + dist = self._installed_dists.get(candidate.project_name) + if dist is None: # Not installed, no uninstallation required. + return None + + # We're installing into global site. The current installation must + # be uninstalled, no matter it's in global or user site, because the + # user site installation has precedence over global. + if not self._use_user_site: + return dist + + # We're installing into user site. Remove the user site installation. + if dist.in_usersite: + return dist + + # We're installing into user site, but the installed incompatible + # package is in global site. We can't uninstall that, and would let + # the new user installation to "shadow" it. But shadowing won't work + # in virtual environments, so we error out. + if running_under_virtualenv() and dist.in_site_packages: + message = ( + f"Will not install to the user site because it will lack " + f"sys.path precedence to {dist.raw_name} in {dist.location}" + ) + raise InstallationError(message) + return None + + def _report_requires_python_error( + self, causes: Sequence["ConflictCause"] + ) -> UnsupportedPythonVersion: + assert causes, "Requires-Python error reported with no cause" + + version = self._python_candidate.version + + if len(causes) == 1: + specifier = str(causes[0].requirement.specifier) + message = ( + f"Package {causes[0].parent.name!r} requires a different " + f"Python: {version} not in {specifier!r}" + ) + return UnsupportedPythonVersion(message) + + message = f"Packages require a different Python. {version} not in:" + for cause in causes: + package = cause.parent.format_for_error() + specifier = str(cause.requirement.specifier) + message += f"\n{specifier!r} (required by {package})" + return UnsupportedPythonVersion(message) + + def _report_single_requirement_conflict( + self, req: Requirement, parent: Optional[Candidate] + ) -> DistributionNotFound: + if parent is None: + req_disp = str(req) + else: + req_disp = f"{req} (from {parent.name})" + + cands = self._finder.find_all_candidates(req.project_name) + skipped_by_requires_python = self._finder.requires_python_skipped_reasons() + + versions_set: Set[Version] = set() + yanked_versions_set: Set[Version] = set() + for c in cands: + is_yanked = c.link.is_yanked if c.link else False + if is_yanked: + yanked_versions_set.add(c.version) + else: + versions_set.add(c.version) + + versions = [str(v) for v in sorted(versions_set)] + yanked_versions = [str(v) for v in sorted(yanked_versions_set)] + + if yanked_versions: + # Saying "version X is yanked" isn't entirely accurate. + # https://github.com/pypa/pip/issues/11745#issuecomment-1402805842 + logger.critical( + "Ignored the following yanked versions: %s", + ", ".join(yanked_versions) or "none", + ) + if skipped_by_requires_python: + logger.critical( + "Ignored the following versions that require a different python " + "version: %s", + "; ".join(skipped_by_requires_python) or "none", + ) + logger.critical( + "Could not find a version that satisfies the requirement %s " + "(from versions: %s)", + req_disp, + ", ".join(versions) or "none", + ) + if str(req) == "requirements.txt": + logger.info( + "HINT: You are attempting to install a package literally " + 'named "requirements.txt" (which cannot exist). Consider ' + "using the '-r' flag to install the packages listed in " + "requirements.txt" + ) + + return DistributionNotFound(f"No matching distribution found for {req}") + + def get_installation_error( + self, + e: "ResolutionImpossible[Requirement, Candidate]", + constraints: Dict[str, Constraint], + ) -> InstallationError: + assert e.causes, "Installation error reported with no cause" + + # If one of the things we can't solve is "we need Python X.Y", + # that is what we report. + requires_python_causes = [ + cause + for cause in e.causes + if isinstance(cause.requirement, RequiresPythonRequirement) + and not cause.requirement.is_satisfied_by(self._python_candidate) + ] + if requires_python_causes: + # The comprehension above makes sure all Requirement instances are + # RequiresPythonRequirement, so let's cast for convenience. + return self._report_requires_python_error( + cast("Sequence[ConflictCause]", requires_python_causes), + ) + + # Otherwise, we have a set of causes which can't all be satisfied + # at once. + + # The simplest case is when we have *one* cause that can't be + # satisfied. We just report that case. + if len(e.causes) == 1: + req, parent = e.causes[0] + if req.name not in constraints: + return self._report_single_requirement_conflict(req, parent) + + # OK, we now have a list of requirements that can't all be + # satisfied at once. + + # A couple of formatting helpers + def text_join(parts: List[str]) -> str: + if len(parts) == 1: + return parts[0] + + return ", ".join(parts[:-1]) + " and " + parts[-1] + + def describe_trigger(parent: Candidate) -> str: + ireq = parent.get_install_requirement() + if not ireq or not ireq.comes_from: + return f"{parent.name}=={parent.version}" + if isinstance(ireq.comes_from, InstallRequirement): + return str(ireq.comes_from.name) + return str(ireq.comes_from) + + triggers = set() + for req, parent in e.causes: + if parent is None: + # This is a root requirement, so we can report it directly + trigger = req.format_for_error() + else: + trigger = describe_trigger(parent) + triggers.add(trigger) + + if triggers: + info = text_join(sorted(triggers)) + else: + info = "the requested packages" + + msg = ( + f"Cannot install {info} because these package versions " + "have conflicting dependencies." + ) + logger.critical(msg) + msg = "\nThe conflict is caused by:" + + relevant_constraints = set() + for req, parent in e.causes: + if req.name in constraints: + relevant_constraints.add(req.name) + msg = msg + "\n " + if parent: + msg = msg + f"{parent.name} {parent.version} depends on " + else: + msg = msg + "The user requested " + msg = msg + req.format_for_error() + for key in relevant_constraints: + spec = constraints[key].specifier + msg += f"\n The user requested (constraint) {key}{spec}" + + msg = ( + msg + + "\n\n" + + "To fix this you could try to:\n" + + "1. loosen the range of package versions you've specified\n" + + "2. remove package versions to allow pip to attempt to solve " + + "the dependency conflict\n" + ) + + logger.info(msg) + + return DistributionNotFound( + "ResolutionImpossible: for help visit " + "https://pip.pypa.io/en/latest/topics/dependency-resolution/" + "#dealing-with-dependency-conflicts" + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py new file mode 100644 index 000000000..a1d57e0f4 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py @@ -0,0 +1,174 @@ +"""Utilities to lazily create and visit candidates found. + +Creating and visiting a candidate is a *very* costly operation. It involves +fetching, extracting, potentially building modules from source, and verifying +distribution metadata. It is therefore crucial for performance to keep +everything here lazy all the way down, so we only touch candidates that we +absolutely need, and not "download the world" when we only need one version of +something. +""" + +import functools +import logging +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Set, Tuple + +from pip._vendor.packaging.version import _BaseVersion + +from pip._internal.exceptions import MetadataInvalid + +from .base import Candidate + +logger = logging.getLogger(__name__) + +IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]] + +if TYPE_CHECKING: + SequenceCandidate = Sequence[Candidate] +else: + # For compatibility: Python before 3.9 does not support using [] on the + # Sequence class. + # + # >>> from collections.abc import Sequence + # >>> Sequence[str] + # Traceback (most recent call last): + # File "", line 1, in + # TypeError: 'ABCMeta' object is not subscriptable + # + # TODO: Remove this block after dropping Python 3.8 support. + SequenceCandidate = Sequence + + +def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]: + """Iterator for ``FoundCandidates``. + + This iterator is used when the package is not already installed. Candidates + from index come later in their normal ordering. + """ + versions_found: Set[_BaseVersion] = set() + for version, func in infos: + if version in versions_found: + continue + try: + candidate = func() + except MetadataInvalid as e: + logger.warning( + "Ignoring version %s of %s since it has invalid metadata:\n" + "%s\n" + "Please use pip<24.1 if you need to use this version.", + version, + e.ireq.name, + e, + ) + # Mark version as found to avoid trying other candidates with the same + # version, since they most likely have invalid metadata as well. + versions_found.add(version) + else: + if candidate is None: + continue + yield candidate + versions_found.add(version) + + +def _iter_built_with_prepended( + installed: Candidate, infos: Iterator[IndexCandidateInfo] +) -> Iterator[Candidate]: + """Iterator for ``FoundCandidates``. + + This iterator is used when the resolver prefers the already-installed + candidate and NOT to upgrade. The installed candidate is therefore + always yielded first, and candidates from index come later in their + normal ordering, except skipped when the version is already installed. + """ + yield installed + versions_found: Set[_BaseVersion] = {installed.version} + for version, func in infos: + if version in versions_found: + continue + candidate = func() + if candidate is None: + continue + yield candidate + versions_found.add(version) + + +def _iter_built_with_inserted( + installed: Candidate, infos: Iterator[IndexCandidateInfo] +) -> Iterator[Candidate]: + """Iterator for ``FoundCandidates``. + + This iterator is used when the resolver prefers to upgrade an + already-installed package. Candidates from index are returned in their + normal ordering, except replaced when the version is already installed. + + The implementation iterates through and yields other candidates, inserting + the installed candidate exactly once before we start yielding older or + equivalent candidates, or after all other candidates if they are all newer. + """ + versions_found: Set[_BaseVersion] = set() + for version, func in infos: + if version in versions_found: + continue + # If the installed candidate is better, yield it first. + if installed.version >= version: + yield installed + versions_found.add(installed.version) + candidate = func() + if candidate is None: + continue + yield candidate + versions_found.add(version) + + # If the installed candidate is older than all other candidates. + if installed.version not in versions_found: + yield installed + + +class FoundCandidates(SequenceCandidate): + """A lazy sequence to provide candidates to the resolver. + + The intended usage is to return this from `find_matches()` so the resolver + can iterate through the sequence multiple times, but only access the index + page when remote packages are actually needed. This improve performances + when suitable candidates are already installed on disk. + """ + + def __init__( + self, + get_infos: Callable[[], Iterator[IndexCandidateInfo]], + installed: Optional[Candidate], + prefers_installed: bool, + incompatible_ids: Set[int], + ): + self._get_infos = get_infos + self._installed = installed + self._prefers_installed = prefers_installed + self._incompatible_ids = incompatible_ids + + def __getitem__(self, index: Any) -> Any: + # Implemented to satisfy the ABC check. This is not needed by the + # resolver, and should not be used by the provider either (for + # performance reasons). + raise NotImplementedError("don't do this") + + def __iter__(self) -> Iterator[Candidate]: + infos = self._get_infos() + if not self._installed: + iterator = _iter_built(infos) + elif self._prefers_installed: + iterator = _iter_built_with_prepended(self._installed, infos) + else: + iterator = _iter_built_with_inserted(self._installed, infos) + return (c for c in iterator if id(c) not in self._incompatible_ids) + + def __len__(self) -> int: + # Implemented to satisfy the ABC check. This is not needed by the + # resolver, and should not be used by the provider either (for + # performance reasons). + raise NotImplementedError("don't do this") + + @functools.lru_cache(maxsize=1) + def __bool__(self) -> bool: + if self._prefers_installed and self._installed: + return True + return any(self) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/provider.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/provider.py new file mode 100644 index 000000000..fb0dd85f1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/provider.py @@ -0,0 +1,258 @@ +import collections +import math +from functools import lru_cache +from typing import ( + TYPE_CHECKING, + Dict, + Iterable, + Iterator, + Mapping, + Sequence, + TypeVar, + Union, +) + +from pip._vendor.resolvelib.providers import AbstractProvider + +from .base import Candidate, Constraint, Requirement +from .candidates import REQUIRES_PYTHON_IDENTIFIER +from .factory import Factory + +if TYPE_CHECKING: + from pip._vendor.resolvelib.providers import Preference + from pip._vendor.resolvelib.resolvers import RequirementInformation + + PreferenceInformation = RequirementInformation[Requirement, Candidate] + + _ProviderBase = AbstractProvider[Requirement, Candidate, str] +else: + _ProviderBase = AbstractProvider + +# Notes on the relationship between the provider, the factory, and the +# candidate and requirement classes. +# +# The provider is a direct implementation of the resolvelib class. Its role +# is to deliver the API that resolvelib expects. +# +# Rather than work with completely abstract "requirement" and "candidate" +# concepts as resolvelib does, pip has concrete classes implementing these two +# ideas. The API of Requirement and Candidate objects are defined in the base +# classes, but essentially map fairly directly to the equivalent provider +# methods. In particular, `find_matches` and `is_satisfied_by` are +# requirement methods, and `get_dependencies` is a candidate method. +# +# The factory is the interface to pip's internal mechanisms. It is stateless, +# and is created by the resolver and held as a property of the provider. It is +# responsible for creating Requirement and Candidate objects, and provides +# services to those objects (access to pip's finder and preparer). + + +D = TypeVar("D") +V = TypeVar("V") + + +def _get_with_identifier( + mapping: Mapping[str, V], + identifier: str, + default: D, +) -> Union[D, V]: + """Get item from a package name lookup mapping with a resolver identifier. + + This extra logic is needed when the target mapping is keyed by package + name, which cannot be directly looked up with an identifier (which may + contain requested extras). Additional logic is added to also look up a value + by "cleaning up" the extras from the identifier. + """ + if identifier in mapping: + return mapping[identifier] + # HACK: Theoretically we should check whether this identifier is a valid + # "NAME[EXTRAS]" format, and parse out the name part with packaging or + # some regular expression. But since pip's resolver only spits out three + # kinds of identifiers: normalized PEP 503 names, normalized names plus + # extras, and Requires-Python, we can cheat a bit here. + name, open_bracket, _ = identifier.partition("[") + if open_bracket and name in mapping: + return mapping[name] + return default + + +class PipProvider(_ProviderBase): + """Pip's provider implementation for resolvelib. + + :params constraints: A mapping of constraints specified by the user. Keys + are canonicalized project names. + :params ignore_dependencies: Whether the user specified ``--no-deps``. + :params upgrade_strategy: The user-specified upgrade strategy. + :params user_requested: A set of canonicalized package names that the user + supplied for pip to install/upgrade. + """ + + def __init__( + self, + factory: Factory, + constraints: Dict[str, Constraint], + ignore_dependencies: bool, + upgrade_strategy: str, + user_requested: Dict[str, int], + ) -> None: + self._factory = factory + self._constraints = constraints + self._ignore_dependencies = ignore_dependencies + self._upgrade_strategy = upgrade_strategy + self._user_requested = user_requested + self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf) + + def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str: + return requirement_or_candidate.name + + def get_preference( + self, + identifier: str, + resolutions: Mapping[str, Candidate], + candidates: Mapping[str, Iterator[Candidate]], + information: Mapping[str, Iterable["PreferenceInformation"]], + backtrack_causes: Sequence["PreferenceInformation"], + ) -> "Preference": + """Produce a sort key for given requirement based on preference. + + The lower the return value is, the more preferred this group of + arguments is. + + Currently pip considers the following in order: + + * Prefer if any of the known requirements is "direct", e.g. points to an + explicit URL. + * If equal, prefer if any requirement is "pinned", i.e. contains + operator ``===`` or ``==``. + * If equal, calculate an approximate "depth" and resolve requirements + closer to the user-specified requirements first. If the depth cannot + by determined (eg: due to no matching parents), it is considered + infinite. + * Order user-specified requirements by the order they are specified. + * If equal, prefers "non-free" requirements, i.e. contains at least one + operator, such as ``>=`` or ``<``. + * If equal, order alphabetically for consistency (helps debuggability). + """ + try: + next(iter(information[identifier])) + except StopIteration: + # There is no information for this identifier, so there's no known + # candidates. + has_information = False + else: + has_information = True + + if has_information: + lookups = (r.get_candidate_lookup() for r, _ in information[identifier]) + candidate, ireqs = zip(*lookups) + else: + candidate, ireqs = None, () + + operators = [ + specifier.operator + for specifier_set in (ireq.specifier for ireq in ireqs if ireq) + for specifier in specifier_set + ] + + direct = candidate is not None + pinned = any(op[:2] == "==" for op in operators) + unfree = bool(operators) + + try: + requested_order: Union[int, float] = self._user_requested[identifier] + except KeyError: + requested_order = math.inf + if has_information: + parent_depths = ( + self._known_depths[parent.name] if parent is not None else 0.0 + for _, parent in information[identifier] + ) + inferred_depth = min(d for d in parent_depths) + 1.0 + else: + inferred_depth = math.inf + else: + inferred_depth = 1.0 + self._known_depths[identifier] = inferred_depth + + requested_order = self._user_requested.get(identifier, math.inf) + + # Requires-Python has only one candidate and the check is basically + # free, so we always do it first to avoid needless work if it fails. + requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER + + # Prefer the causes of backtracking on the assumption that the problem + # resolving the dependency tree is related to the failures that caused + # the backtracking + backtrack_cause = self.is_backtrack_cause(identifier, backtrack_causes) + + return ( + not requires_python, + not direct, + not pinned, + not backtrack_cause, + inferred_depth, + requested_order, + not unfree, + identifier, + ) + + def find_matches( + self, + identifier: str, + requirements: Mapping[str, Iterator[Requirement]], + incompatibilities: Mapping[str, Iterator[Candidate]], + ) -> Iterable[Candidate]: + def _eligible_for_upgrade(identifier: str) -> bool: + """Are upgrades allowed for this project? + + This checks the upgrade strategy, and whether the project was one + that the user specified in the command line, in order to decide + whether we should upgrade if there's a newer version available. + + (Note that we don't need access to the `--upgrade` flag, because + an upgrade strategy of "to-satisfy-only" means that `--upgrade` + was not specified). + """ + if self._upgrade_strategy == "eager": + return True + elif self._upgrade_strategy == "only-if-needed": + user_order = _get_with_identifier( + self._user_requested, + identifier, + default=None, + ) + return user_order is not None + return False + + constraint = _get_with_identifier( + self._constraints, + identifier, + default=Constraint.empty(), + ) + return self._factory.find_candidates( + identifier=identifier, + requirements=requirements, + constraint=constraint, + prefers_installed=(not _eligible_for_upgrade(identifier)), + incompatibilities=incompatibilities, + is_satisfied_by=self.is_satisfied_by, + ) + + @lru_cache(maxsize=None) + def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool: + return requirement.is_satisfied_by(candidate) + + def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: + with_requires = not self._ignore_dependencies + return [r for r in candidate.iter_dependencies(with_requires) if r is not None] + + @staticmethod + def is_backtrack_cause( + identifier: str, backtrack_causes: Sequence["PreferenceInformation"] + ) -> bool: + for backtrack_cause in backtrack_causes: + if identifier == backtrack_cause.requirement.name: + return True + if backtrack_cause.parent and identifier == backtrack_cause.parent.name: + return True + return False diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/reporter.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/reporter.py new file mode 100644 index 000000000..0594569d8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/reporter.py @@ -0,0 +1,81 @@ +from collections import defaultdict +from logging import getLogger +from typing import Any, DefaultDict + +from pip._vendor.resolvelib.reporters import BaseReporter + +from .base import Candidate, Requirement + +logger = getLogger(__name__) + + +class PipReporter(BaseReporter): + def __init__(self) -> None: + self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int) + + self._messages_at_reject_count = { + 1: ( + "pip is looking at multiple versions of {package_name} to " + "determine which version is compatible with other " + "requirements. This could take a while." + ), + 8: ( + "pip is still looking at multiple versions of {package_name} to " + "determine which version is compatible with other " + "requirements. This could take a while." + ), + 13: ( + "This is taking longer than usual. You might need to provide " + "the dependency resolver with stricter constraints to reduce " + "runtime. See https://pip.pypa.io/warnings/backtracking for " + "guidance. If you want to abort this run, press Ctrl + C." + ), + } + + def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None: + self.reject_count_by_package[candidate.name] += 1 + + count = self.reject_count_by_package[candidate.name] + if count not in self._messages_at_reject_count: + return + + message = self._messages_at_reject_count[count] + logger.info("INFO: %s", message.format(package_name=candidate.name)) + + msg = "Will try a different candidate, due to conflict:" + for req_info in criterion.information: + req, parent = req_info.requirement, req_info.parent + # Inspired by Factory.get_installation_error + msg += "\n " + if parent: + msg += f"{parent.name} {parent.version} depends on " + else: + msg += "The user requested " + msg += req.format_for_error() + logger.debug(msg) + + +class PipDebuggingReporter(BaseReporter): + """A reporter that does an info log for every event it sees.""" + + def starting(self) -> None: + logger.info("Reporter.starting()") + + def starting_round(self, index: int) -> None: + logger.info("Reporter.starting_round(%r)", index) + + def ending_round(self, index: int, state: Any) -> None: + logger.info("Reporter.ending_round(%r, state)", index) + logger.debug("Reporter.ending_round(%r, %r)", index, state) + + def ending(self, state: Any) -> None: + logger.info("Reporter.ending(%r)", state) + + def adding_requirement(self, requirement: Requirement, parent: Candidate) -> None: + logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent) + + def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None: + logger.info("Reporter.rejecting_candidate(%r, %r)", criterion, candidate) + + def pinning(self, candidate: Candidate) -> None: + logger.info("Reporter.pinning(%r)", candidate) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/requirements.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/requirements.py new file mode 100644 index 000000000..b04f41b21 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/requirements.py @@ -0,0 +1,245 @@ +from typing import Any, Optional + +from pip._vendor.packaging.specifiers import SpecifierSet +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name + +from pip._internal.req.constructors import install_req_drop_extras +from pip._internal.req.req_install import InstallRequirement + +from .base import Candidate, CandidateLookup, Requirement, format_name + + +class ExplicitRequirement(Requirement): + def __init__(self, candidate: Candidate) -> None: + self.candidate = candidate + + def __str__(self) -> str: + return str(self.candidate) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.candidate!r})" + + def __hash__(self) -> int: + return hash(self.candidate) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, ExplicitRequirement): + return False + return self.candidate == other.candidate + + @property + def project_name(self) -> NormalizedName: + # No need to canonicalize - the candidate did this + return self.candidate.project_name + + @property + def name(self) -> str: + # No need to canonicalize - the candidate did this + return self.candidate.name + + def format_for_error(self) -> str: + return self.candidate.format_for_error() + + def get_candidate_lookup(self) -> CandidateLookup: + return self.candidate, None + + def is_satisfied_by(self, candidate: Candidate) -> bool: + return candidate == self.candidate + + +class SpecifierRequirement(Requirement): + def __init__(self, ireq: InstallRequirement) -> None: + assert ireq.link is None, "This is a link, not a specifier" + self._ireq = ireq + self._equal_cache: Optional[str] = None + self._hash: Optional[int] = None + self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras) + + @property + def _equal(self) -> str: + if self._equal_cache is not None: + return self._equal_cache + + self._equal_cache = str(self._ireq) + return self._equal_cache + + def __str__(self) -> str: + return str(self._ireq.req) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({str(self._ireq.req)!r})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SpecifierRequirement): + return NotImplemented + return self._equal == other._equal + + def __hash__(self) -> int: + if self._hash is not None: + return self._hash + + self._hash = hash(self._equal) + return self._hash + + @property + def project_name(self) -> NormalizedName: + assert self._ireq.req, "Specifier-backed ireq is always PEP 508" + return canonicalize_name(self._ireq.req.name) + + @property + def name(self) -> str: + return format_name(self.project_name, self._extras) + + def format_for_error(self) -> str: + # Convert comma-separated specifiers into "A, B, ..., F and G" + # This makes the specifier a bit more "human readable", without + # risking a change in meaning. (Hopefully! Not all edge cases have + # been checked) + parts = [s.strip() for s in str(self).split(",")] + if len(parts) == 0: + return "" + elif len(parts) == 1: + return parts[0] + + return ", ".join(parts[:-1]) + " and " + parts[-1] + + def get_candidate_lookup(self) -> CandidateLookup: + return None, self._ireq + + def is_satisfied_by(self, candidate: Candidate) -> bool: + assert candidate.name == self.name, ( + f"Internal issue: Candidate is not for this requirement " + f"{candidate.name} vs {self.name}" + ) + # We can safely always allow prereleases here since PackageFinder + # already implements the prerelease logic, and would have filtered out + # prerelease candidates if the user does not expect them. + assert self._ireq.req, "Specifier-backed ireq is always PEP 508" + spec = self._ireq.req.specifier + return spec.contains(candidate.version, prereleases=True) + + +class SpecifierWithoutExtrasRequirement(SpecifierRequirement): + """ + Requirement backed by an install requirement on a base package. + Trims extras from its install requirement if there are any. + """ + + def __init__(self, ireq: InstallRequirement) -> None: + assert ireq.link is None, "This is a link, not a specifier" + self._ireq = install_req_drop_extras(ireq) + self._equal_cache: Optional[str] = None + self._hash: Optional[int] = None + self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras) + + @property + def _equal(self) -> str: + if self._equal_cache is not None: + return self._equal_cache + + self._equal_cache = str(self._ireq) + return self._equal_cache + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SpecifierWithoutExtrasRequirement): + return NotImplemented + return self._equal == other._equal + + def __hash__(self) -> int: + if self._hash is not None: + return self._hash + + self._hash = hash(self._equal) + return self._hash + + +class RequiresPythonRequirement(Requirement): + """A requirement representing Requires-Python metadata.""" + + def __init__(self, specifier: SpecifierSet, match: Candidate) -> None: + self.specifier = specifier + self._specifier_string = str(specifier) # for faster __eq__ + self._hash: Optional[int] = None + self._candidate = match + + def __str__(self) -> str: + return f"Python {self.specifier}" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({str(self.specifier)!r})" + + def __hash__(self) -> int: + if self._hash is not None: + return self._hash + + self._hash = hash((self._specifier_string, self._candidate)) + return self._hash + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, RequiresPythonRequirement): + return False + return ( + self._specifier_string == other._specifier_string + and self._candidate == other._candidate + ) + + @property + def project_name(self) -> NormalizedName: + return self._candidate.project_name + + @property + def name(self) -> str: + return self._candidate.name + + def format_for_error(self) -> str: + return str(self) + + def get_candidate_lookup(self) -> CandidateLookup: + if self.specifier.contains(self._candidate.version, prereleases=True): + return self._candidate, None + return None, None + + def is_satisfied_by(self, candidate: Candidate) -> bool: + assert candidate.name == self._candidate.name, "Not Python candidate" + # We can safely always allow prereleases here since PackageFinder + # already implements the prerelease logic, and would have filtered out + # prerelease candidates if the user does not expect them. + return self.specifier.contains(candidate.version, prereleases=True) + + +class UnsatisfiableRequirement(Requirement): + """A requirement that cannot be satisfied.""" + + def __init__(self, name: NormalizedName) -> None: + self._name = name + + def __str__(self) -> str: + return f"{self._name} (unavailable)" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({str(self._name)!r})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UnsatisfiableRequirement): + return NotImplemented + return self._name == other._name + + def __hash__(self) -> int: + return hash(self._name) + + @property + def project_name(self) -> NormalizedName: + return self._name + + @property + def name(self) -> str: + return self._name + + def format_for_error(self) -> str: + return str(self) + + def get_candidate_lookup(self) -> CandidateLookup: + return None, None + + def is_satisfied_by(self, candidate: Candidate) -> bool: + return False diff --git a/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/resolver.py b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/resolver.py new file mode 100644 index 000000000..c12beef0b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/resolution/resolvelib/resolver.py @@ -0,0 +1,317 @@ +import contextlib +import functools +import logging +import os +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast + +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible +from pip._vendor.resolvelib import Resolver as RLResolver +from pip._vendor.resolvelib.structs import DirectedGraph + +from pip._internal.cache import WheelCache +from pip._internal.index.package_finder import PackageFinder +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req.constructors import install_req_extend_extras +from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_set import RequirementSet +from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider +from pip._internal.resolution.resolvelib.provider import PipProvider +from pip._internal.resolution.resolvelib.reporter import ( + PipDebuggingReporter, + PipReporter, +) +from pip._internal.utils.packaging import get_requirement + +from .base import Candidate, Requirement +from .factory import Factory + +if TYPE_CHECKING: + from pip._vendor.resolvelib.resolvers import Result as RLResult + + Result = RLResult[Requirement, Candidate, str] + + +logger = logging.getLogger(__name__) + + +class Resolver(BaseResolver): + _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"} + + def __init__( + self, + preparer: RequirementPreparer, + finder: PackageFinder, + wheel_cache: Optional[WheelCache], + make_install_req: InstallRequirementProvider, + use_user_site: bool, + ignore_dependencies: bool, + ignore_installed: bool, + ignore_requires_python: bool, + force_reinstall: bool, + upgrade_strategy: str, + py_version_info: Optional[Tuple[int, ...]] = None, + ): + super().__init__() + assert upgrade_strategy in self._allowed_strategies + + self.factory = Factory( + finder=finder, + preparer=preparer, + make_install_req=make_install_req, + wheel_cache=wheel_cache, + use_user_site=use_user_site, + force_reinstall=force_reinstall, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + py_version_info=py_version_info, + ) + self.ignore_dependencies = ignore_dependencies + self.upgrade_strategy = upgrade_strategy + self._result: Optional[Result] = None + + def resolve( + self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + ) -> RequirementSet: + collected = self.factory.collect_root_requirements(root_reqs) + provider = PipProvider( + factory=self.factory, + constraints=collected.constraints, + ignore_dependencies=self.ignore_dependencies, + upgrade_strategy=self.upgrade_strategy, + user_requested=collected.user_requested, + ) + if "PIP_RESOLVER_DEBUG" in os.environ: + reporter: BaseReporter = PipDebuggingReporter() + else: + reporter = PipReporter() + resolver: RLResolver[Requirement, Candidate, str] = RLResolver( + provider, + reporter, + ) + + try: + limit_how_complex_resolution_can_be = 200000 + result = self._result = resolver.resolve( + collected.requirements, max_rounds=limit_how_complex_resolution_can_be + ) + + except ResolutionImpossible as e: + error = self.factory.get_installation_error( + cast("ResolutionImpossible[Requirement, Candidate]", e), + collected.constraints, + ) + raise error from e + + req_set = RequirementSet(check_supported_wheels=check_supported_wheels) + # process candidates with extras last to ensure their base equivalent is + # already in the req_set if appropriate. + # Python's sort is stable so using a binary key function keeps relative order + # within both subsets. + for candidate in sorted( + result.mapping.values(), key=lambda c: c.name != c.project_name + ): + ireq = candidate.get_install_requirement() + if ireq is None: + if candidate.name != candidate.project_name: + # extend existing req's extras + with contextlib.suppress(KeyError): + req = req_set.get_requirement(candidate.project_name) + req_set.add_named_requirement( + install_req_extend_extras( + req, get_requirement(candidate.name).extras + ) + ) + continue + + # Check if there is already an installation under the same name, + # and set a flag for later stages to uninstall it, if needed. + installed_dist = self.factory.get_dist_to_uninstall(candidate) + if installed_dist is None: + # There is no existing installation -- nothing to uninstall. + ireq.should_reinstall = False + elif self.factory.force_reinstall: + # The --force-reinstall flag is set -- reinstall. + ireq.should_reinstall = True + elif installed_dist.version != candidate.version: + # The installation is different in version -- reinstall. + ireq.should_reinstall = True + elif candidate.is_editable or installed_dist.editable: + # The incoming distribution is editable, or different in + # editable-ness to installation -- reinstall. + ireq.should_reinstall = True + elif candidate.source_link and candidate.source_link.is_file: + # The incoming distribution is under file:// + if candidate.source_link.is_wheel: + # is a local wheel -- do nothing. + logger.info( + "%s is already installed with the same version as the " + "provided wheel. Use --force-reinstall to force an " + "installation of the wheel.", + ireq.name, + ) + continue + + # is a local sdist or path -- reinstall + ireq.should_reinstall = True + else: + continue + + link = candidate.source_link + if link and link.is_yanked: + # The reason can contain non-ASCII characters, Unicode + # is required for Python 2. + msg = ( + "The candidate selected for download or install is a " + "yanked version: {name!r} candidate (version {version} " + "at {link})\nReason for being yanked: {reason}" + ).format( + name=candidate.name, + version=candidate.version, + link=link, + reason=link.yanked_reason or "", + ) + logger.warning(msg) + + req_set.add_named_requirement(ireq) + + reqs = req_set.all_requirements + self.factory.preparer.prepare_linked_requirements_more(reqs) + for req in reqs: + req.prepared = True + req.needs_more_preparation = False + return req_set + + def get_installation_order( + self, req_set: RequirementSet + ) -> List[InstallRequirement]: + """Get order for installation of requirements in RequirementSet. + + The returned list contains a requirement before another that depends on + it. This helps ensure that the environment is kept consistent as they + get installed one-by-one. + + The current implementation creates a topological ordering of the + dependency graph, giving more weight to packages with less + or no dependencies, while breaking any cycles in the graph at + arbitrary points. We make no guarantees about where the cycle + would be broken, other than it *would* be broken. + """ + assert self._result is not None, "must call resolve() first" + + if not req_set.requirements: + # Nothing is left to install, so we do not need an order. + return [] + + graph = self._result.graph + weights = get_topological_weights(graph, set(req_set.requirements.keys())) + + sorted_items = sorted( + req_set.requirements.items(), + key=functools.partial(_req_set_item_sorter, weights=weights), + reverse=True, + ) + return [ireq for _, ireq in sorted_items] + + +def get_topological_weights( + graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str] +) -> Dict[Optional[str], int]: + """Assign weights to each node based on how "deep" they are. + + This implementation may change at any point in the future without prior + notice. + + We first simplify the dependency graph by pruning any leaves and giving them + the highest weight: a package without any dependencies should be installed + first. This is done again and again in the same way, giving ever less weight + to the newly found leaves. The loop stops when no leaves are left: all + remaining packages have at least one dependency left in the graph. + + Then we continue with the remaining graph, by taking the length for the + longest path to any node from root, ignoring any paths that contain a single + node twice (i.e. cycles). This is done through a depth-first search through + the graph, while keeping track of the path to the node. + + Cycles in the graph result would result in node being revisited while also + being on its own path. In this case, take no action. This helps ensure we + don't get stuck in a cycle. + + When assigning weight, the longer path (i.e. larger length) is preferred. + + We are only interested in the weights of packages that are in the + requirement_keys. + """ + path: Set[Optional[str]] = set() + weights: Dict[Optional[str], int] = {} + + def visit(node: Optional[str]) -> None: + if node in path: + # We hit a cycle, so we'll break it here. + return + + # Time to visit the children! + path.add(node) + for child in graph.iter_children(node): + visit(child) + path.remove(node) + + if node not in requirement_keys: + return + + last_known_parent_count = weights.get(node, 0) + weights[node] = max(last_known_parent_count, len(path)) + + # Simplify the graph, pruning leaves that have no dependencies. + # This is needed for large graphs (say over 200 packages) because the + # `visit` function is exponentially slower then, taking minutes. + # See https://github.com/pypa/pip/issues/10557 + # We will loop until we explicitly break the loop. + while True: + leaves = set() + for key in graph: + if key is None: + continue + for _child in graph.iter_children(key): + # This means we have at least one child + break + else: + # No child. + leaves.add(key) + if not leaves: + # We are done simplifying. + break + # Calculate the weight for the leaves. + weight = len(graph) - 1 + for leaf in leaves: + if leaf not in requirement_keys: + continue + weights[leaf] = weight + # Remove the leaves from the graph, making it simpler. + for leaf in leaves: + graph.remove(leaf) + + # Visit the remaining graph. + # `None` is guaranteed to be the root node by resolvelib. + visit(None) + + # Sanity check: all requirement keys should be in the weights, + # and no other keys should be in the weights. + difference = set(weights.keys()).difference(requirement_keys) + assert not difference, difference + + return weights + + +def _req_set_item_sorter( + item: Tuple[str, InstallRequirement], + weights: Dict[Optional[str], int], +) -> Tuple[int, str]: + """Key function used to sort install requirements for installation. + + Based on the "weight" mapping calculated in ``get_installation_order()``. + The canonical package name is returned as the second member as a tie- + breaker to ensure the result is predictable, which is useful in tests. + """ + name = canonicalize_name(item[0]) + return weights[name], name diff --git a/venv/lib/python3.8/site-packages/pip/_internal/self_outdated_check.py b/venv/lib/python3.8/site-packages/pip/_internal/self_outdated_check.py new file mode 100644 index 000000000..2e0e3df35 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/self_outdated_check.py @@ -0,0 +1,252 @@ +import datetime +import functools +import hashlib +import json +import logging +import optparse +import os.path +import sys +from dataclasses import dataclass +from typing import Any, Callable, Dict, Optional + +from pip._vendor.packaging.version import Version +from pip._vendor.packaging.version import parse as parse_version +from pip._vendor.rich.console import Group +from pip._vendor.rich.markup import escape +from pip._vendor.rich.text import Text + +from pip._internal.index.collector import LinkCollector +from pip._internal.index.package_finder import PackageFinder +from pip._internal.metadata import get_default_environment +from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.network.session import PipSession +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.entrypoints import ( + get_best_invocation_for_this_pip, + get_best_invocation_for_this_python, +) +from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace +from pip._internal.utils.misc import ( + ExternallyManagedEnvironment, + check_externally_managed, + ensure_dir, +) + +_WEEK = datetime.timedelta(days=7) + +logger = logging.getLogger(__name__) + + +def _get_statefile_name(key: str) -> str: + key_bytes = key.encode() + name = hashlib.sha224(key_bytes).hexdigest() + return name + + +def _convert_date(isodate: str) -> datetime.datetime: + """Convert an ISO format string to a date. + + Handles the format 2020-01-22T14:24:01Z (trailing Z) + which is not supported by older versions of fromisoformat. + """ + return datetime.datetime.fromisoformat(isodate.replace("Z", "+00:00")) + + +class SelfCheckState: + def __init__(self, cache_dir: str) -> None: + self._state: Dict[str, Any] = {} + self._statefile_path = None + + # Try to load the existing state + if cache_dir: + self._statefile_path = os.path.join( + cache_dir, "selfcheck", _get_statefile_name(self.key) + ) + try: + with open(self._statefile_path, encoding="utf-8") as statefile: + self._state = json.load(statefile) + except (OSError, ValueError, KeyError): + # Explicitly suppressing exceptions, since we don't want to + # error out if the cache file is invalid. + pass + + @property + def key(self) -> str: + return sys.prefix + + def get(self, current_time: datetime.datetime) -> Optional[str]: + """Check if we have a not-outdated version loaded already.""" + if not self._state: + return None + + if "last_check" not in self._state: + return None + + if "pypi_version" not in self._state: + return None + + # Determine if we need to refresh the state + last_check = _convert_date(self._state["last_check"]) + time_since_last_check = current_time - last_check + if time_since_last_check > _WEEK: + return None + + return self._state["pypi_version"] + + def set(self, pypi_version: str, current_time: datetime.datetime) -> None: + # If we do not have a path to cache in, don't bother saving. + if not self._statefile_path: + return + + # Check to make sure that we own the directory + if not check_path_owner(os.path.dirname(self._statefile_path)): + return + + # Now that we've ensured the directory is owned by this user, we'll go + # ahead and make sure that all our directories are created. + ensure_dir(os.path.dirname(self._statefile_path)) + + state = { + # Include the key so it's easy to tell which pip wrote the + # file. + "key": self.key, + "last_check": current_time.isoformat(), + "pypi_version": pypi_version, + } + + text = json.dumps(state, sort_keys=True, separators=(",", ":")) + + with adjacent_tmp_file(self._statefile_path) as f: + f.write(text.encode()) + + try: + # Since we have a prefix-specific state file, we can just + # overwrite whatever is there, no need to check. + replace(f.name, self._statefile_path) + except OSError: + # Best effort. + pass + + +@dataclass +class UpgradePrompt: + old: str + new: str + + def __rich__(self) -> Group: + if WINDOWS: + pip_cmd = f"{get_best_invocation_for_this_python()} -m pip" + else: + pip_cmd = get_best_invocation_for_this_pip() + + notice = "[bold][[reset][blue]notice[reset][bold]][reset]" + return Group( + Text(), + Text.from_markup( + f"{notice} A new release of pip is available: " + f"[red]{self.old}[reset] -> [green]{self.new}[reset]" + ), + Text.from_markup( + f"{notice} To update, run: " + f"[green]{escape(pip_cmd)} install --upgrade pip" + ), + ) + + +def was_installed_by_pip(pkg: str) -> bool: + """Checks whether pkg was installed by pip + + This is used not to display the upgrade message when pip is in fact + installed by system package manager, such as dnf on Fedora. + """ + dist = get_default_environment().get_distribution(pkg) + return dist is not None and "pip" == dist.installer + + +def _get_current_remote_pip_version( + session: PipSession, options: optparse.Values +) -> Optional[str]: + # Lets use PackageFinder to see what the latest pip version is + link_collector = LinkCollector.create( + session, + options=options, + suppress_no_index=True, + ) + + # Pass allow_yanked=False so we don't suggest upgrading to a + # yanked version. + selection_prefs = SelectionPreferences( + allow_yanked=False, + allow_all_prereleases=False, # Explicitly set to False + ) + + finder = PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + ) + best_candidate = finder.find_best_candidate("pip").best_candidate + if best_candidate is None: + return None + + return str(best_candidate.version) + + +def _self_version_check_logic( + *, + state: SelfCheckState, + current_time: datetime.datetime, + local_version: Version, + get_remote_version: Callable[[], Optional[str]], +) -> Optional[UpgradePrompt]: + remote_version_str = state.get(current_time) + if remote_version_str is None: + remote_version_str = get_remote_version() + if remote_version_str is None: + logger.debug("No remote pip version found") + return None + state.set(remote_version_str, current_time) + + remote_version = parse_version(remote_version_str) + logger.debug("Remote version of pip: %s", remote_version) + logger.debug("Local version of pip: %s", local_version) + + pip_installed_by_pip = was_installed_by_pip("pip") + logger.debug("Was pip installed by pip? %s", pip_installed_by_pip) + if not pip_installed_by_pip: + return None # Only suggest upgrade if pip is installed by pip. + + local_version_is_older = ( + local_version < remote_version + and local_version.base_version != remote_version.base_version + ) + if local_version_is_older: + return UpgradePrompt(old=str(local_version), new=remote_version_str) + + return None + + +def pip_self_version_check(session: PipSession, options: optparse.Values) -> None: + """Check for an update for pip. + + Limit the frequency of checks to once per week. State is stored either in + the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix + of the pip script path. + """ + installed_dist = get_default_environment().get_distribution("pip") + if not installed_dist: + return + try: + check_externally_managed() + except ExternallyManagedEnvironment: + return + + upgrade_prompt = _self_version_check_logic( + state=SelfCheckState(cache_dir=options.cache_dir), + current_time=datetime.datetime.now(datetime.timezone.utc), + local_version=installed_dist.version, + get_remote_version=functools.partial( + _get_current_remote_pip_version, session, options + ), + ) + if upgrade_prompt is not None: + logger.warning("%s", upgrade_prompt, extra={"rich": True}) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4da055daa179711029cfb3220b5481463a2a5579 GIT binary patch literal 212 zcmYk0u?oU45QbB55TOs^klxV6LBzpPT*Nt~_OwUSBqV8}zLBrs>{Gb<2yRX$Am|VO zciew>aECmfv8dY}DAi}ezp~h@qx~eJW_vczc30+7{KI>M#&igH!In_L1_xBecBlhv zW#z$+p)s1Y~=8Qvu+n_5eb6QBtS1ns~oAPJz!LoP*97@1FYg#mkqt)E;hubz}v0kcsRg+k9t^GC?-T{a_tsxqX@omV$dfSg-A) zCBAfhkKezsgZn|=tmK2`zyaNI@E};hw-aQV*5<1J*Cor^9>rZ9ideCZ6r=t?GjYx( z>q#+Wr@r*NqNw@zy68Bq0au*$=JdYgEEbxnQIZJ71EvM*a5kdHi%<{3n4KhHQf!B@ z<}&u9lOjuHfYTZ9C?P#_ad*|9Ea*QW={)v35ohN-4ulNjK8yJUmrhqCAy-WFpzo~T zmRyOE>~eKt%n&2Z2L3sbLqy8UZj1KgfQ4E?z7Anpz!PZTad*tRBIaK(C5D{*frNn% zfJSn`2F3L#VWBcubVpJ`N(4?nF~*9XGl2XE+`zCFc6kgGB~PRXMqNN8xyEkxxjUx}D`MYbixu%jGjkIP}G6hcSJ2VC;bnDwO(u!91ma!!bJ z2fpm{0=e$TV+P1cjOb`TjL9J%E+%6KlyFc%eNSIdUz(U&d`t+ zU<3VAh>YHg0g7Oe7Dzk2k&K}-&G$e6GNbX7cQr{N@}7tgK5`BD6NIL`5X=^~{0_>? z_>zVd1q4$K+$I3Vh%~CxEj93AI|?fW40^-Zp{1ESk{nhVfe(RwB4k?5CZzuf0u}I1 zl{FjNt%I^xWp0!0d5srtrCMQ+%T2sLF0cc)wbR&dF;IBrdh3uiwpqD;$o}ZoD+N~l zde5sio2=nE?(SaAtzylsZ`JlIZv8oX2EKZu#cJ-Z+k((mgOS3iD7Ol}ZMIwWwss)7 z{LHPnt;2$|?Y8P9bGzZOGTSSAEq80bR`%H5zPHzCR-wKEv30k;?LkX*w_0x%p%rVa z`T{Skxl^vyNR?CGhxH!W$F>@Kho1X&#JJj{H#_@Rl%vPTG`z#uu6Hi z{G6=wSOZc#hsaXv*uhSfmPlV2|647$Q71RH8ugZk*8;rsT65L|w^=Q)vgbBos*NPLxpTmuh}Tm69=PHGYzO_Jlf2C*)5w&WJNEOX#fUr6}x7Nj@w7zNA9U zpCs7!`y8xcQktIyrI8LJRcfa#zHN?}WIS1J$0F`Teth;jaw3D9Wy|}x-|>{pHJ*Ni z2W_3&*BPDFxhv~+=EjDvw*Myp;xK#;;Yz^H{U{93Wa9L!@ELUs)ToeX{#;VGMv)Nx z)2B~Cr}V~{7~6(rV|{&d)9ilp^8G~LtoMXiFR=BFFV|nr7;Y`TpP9&xW8(aO8INPb zx&~enaLLxXVn{*z2}ui^|I3lo7~{>QaSCu9$MpBpzTKu=PsML8O_m8?EkvXyOE{3i zc(O{TUV8@nBs=tz$%6&7k(K$`u1U=Fb`PPJE4XdoO0(mUdB2k#L}{|)V?1a}+Zx$% zitM;vxTUm7MOK_zKuE<~E`4)oI1eR%$h`z;51H;$$E8HvA z%i1gZ%D%=#d}B-R^3y9!$U*kc!1g+GW9ckWeqz0{53KJVo6YS_{EszGjzQW8-2v0d!`~Fzksai9jD+$ z;0%`;n>I{yV8_R^{xB_|qNg4=)XuM!pUzqn< zEl^2^^kV^H{abdqJxuB*s7CoY;%EkS%x<$7(?FNNVudLX)|v$PIi{gtrm>v``4SJe z;yZ1MZ|vjxkN?)FN@QZ4ziRGg&gW7?$C? z74_kr3~5ls&>&!alDG4=V`uD7?T7Y?Eq{Zx%?FeF?KUR+c6+jHeuq(qmU(QMZa8_U z5U&VPLnqPQfoZgpyySfxjewW>r0I92WR1uLljwMs)P*PcKVWDdMf@rC21N}r_gk2g P&F6Bq^AXI-y}6uK~!b|f+8kGi{b zAVIg}T=W5qB0jc{&~u-H*PikO1$yetQgUU$3+!-rxHCKR%{Tj^-w!MtfBqF`|2UTQ zH*I!@i<_r7)sHBIHL?T~cFsl&zfNu!&d4d;k;^RMjylgQ(GlJ|OL)LudvBeQ4_)wu ze`AZT*n8)W_Jl2h8z%Ne4|jpEFRie@`s1Wlx+=02@VuJMvT_y#r!dV*P<ƅhio z$y25Hw31xUfxn{DAfs!L<4VC{6h+}HUVxrgA_{`6%yhDGco^~mJ@GN{Nt)*%aD{Gw zr{$7Y=uGmW5_OI@>UpZop=~hH4D@+>Q}6*%u!f=CINz1a#+{@}fA|Ph6VmUF4sM>} zR9~S03-Pi>wqPR%Tq`6gqE zaFD|_v&!j5W@%( zQxT?J=5~w*spOI~xrf>bL$MvSCYuwSiVQUZukH8F+PYZP0-InOf8(#&yW@Pw!m`R6M=_RMmoQPl|hXFYkIrS(U+((Jg~fgNrENZm!FWSxyRGX1y;2sl&KE6 pZM<{y3YlvqHKRe@m_K-B_nVfs-x8Hn4~6S@*@5di9|s4W{{W5#h;;w} literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed2ee8577534b0a5d1e9b0a798a1e213530a2759 GIT binary patch literal 1643 zcmZ8hy>8?-5Ee!4u2x>Jor80i04azdjga#SI0He7Ll7i^tAm?3%{Bs8q<87nABL3w zAO|?ywSI#u&gH#A>O6(oRC$D?%8*(+wh58c4CnWoA%EN6jyxQH{F$h?L(lum5)a1! zi63$578l{oJwb$@lR0s_KlgE`IW2;DKK=(y4%A4)r+59{SzIplxA5aBs&LQYi3rG zoyk&EAO+)fEfi>0RHDfxGZoX7lK^QNDA^g$8>txw8D&*bbE6h0c2a>8${GM!8dj^i z_psnv&LVUvnJNt)sj6h?9_@lW2W2F?(`htcqU9xU(eOOKe96{Q)~Z}O%SLLWSJ2IlRD=HLoaN` zvEPj_`H~m18}`X&#Adj8ji>G2SU@T_tZUvh?T>O^(q7Z_KI!v=^ouG`ne| zC8Qb8Rx(9rF#&7plLuqm{=G<6RUs33CD%&k$uF2&UeyIAn7rW&oz$0Tu-vZ?_D?{{ zg_8RQc(#_1AP8qkt`-}SgW30qMojl>Jho^iScoLG$V18Vq%kVj$tI{-eF-7D-^HnY zTs-3FWeBZ{{Sd6b>7_WjZW=!k;@VTI}l^dhHwklzt3a|CRu25gkFV zY3spvjz4Je|JJ{zjvBV~mLPt?(cvc_@b-AHh09g+dos(a@2k>+YGQ>*F<&@c81chA#; zXO&L-dO7#j*B+j^qI>=>nUH`6Bp_4Y@gE^$QMyOw8zd|;f~-;0_^sEHcZi0ervY}3 zk7&^w>T>Ufg2TBg4`G5z@t~vnQg`89gE~h&2s=+gi~R>V+IIRs8~6gruW@P%t4AUd z`HuIfu^)wegmU*Ge`O0~0~J&RH*|?*LXHc)=UfvfJHYf_x5S>ez4434E zhslkO;B&Nkc#M(SIJ$AsFaHKIlZsE9%h-*qJuT}E9Ehg^e5;<;clU`6{VghMi@Am88CEwULkbM(m>k=0+KtjwR9Tuj zM=r*0XfEx`1Qxq)3oqF$?O#x!$mye}+JL)IU9hOCyf2}xh_n76Fjq9!ec|i=+5N#j VHrigyAPh-JCjNx(27A-J@qghloyPzG literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/compat.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/compat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a145b85a7d31e50037abd498f08ac0aa001ef95 GIT binary patch literal 1975 zcmZ8iO>f&a7$zy%mKDWK)AVa=H?76i8fdHnS+_15ihZO5vITZfZ%FHlAd9puo0bHU zN|wO6tiA3RtOJtc{z6YX>{sx*Q~$ya>t4!n(2WAgmmlxf!-v$5%gZi;^6JkZ3F`>` zWd?7T8VtUKD*u3nq8_4{5{`SM?5&|MWwe!SLr3XHnMt`LG#MVReJdp=dX-RuS`hxirM39^y-B4u2a%Q&Fe?s zcU3VM_-Yg>pC*F=6Vgx8Gm)jr-#t{LEcN$cA<0tdM=AAl9;rblrY=pS5=p;MW~ucW zAVKXW)?~Y*y&+R!9;s1SBvda+MS0Q>^T|-^+TM11tFzaIomIh9A=21rE(!s`f%*+p z`7ty{4FC~wbd2_p3EM^rPh4>eBlA5&DS2v+!Qy0U9b=4U7*ey_0)y@k;KbYRhP%%? zyN}!5?oQLzb|@v+OWV^t6Y8-Lnb6nrByWZLETx%f6@o*o8!bqS3v=L2B1}M~{5uj$ zApd<3jIt>U*b$o~j0an6pYbf80%XBX)R#eC;B9v5QS_&gD}Aksp1B% z5|4;wm}wGi8|+0D2C>0mQ7jNDP)l(8ASeKn;^#zsqVNfs!DwtjwZBHE0zsOYx<55O;vO0e>R z^Wd|M2e*NbaUl&+L-Ana{-^ivZ_MrQ@+f8$Xr4%aP^7UTtl{|_R-YEkSJ^r1V+aP; zzuJCk=w62KU3r}M<`+edvVv0|jyzRG#QC8=%>3veIy}o}Q$&db8=Y>+u1$Y$#L_Zp zAN)s)?EFmlag^RsexLb*MF57$RJ)z9-TAh&v(wo-3jU+w zrdJXqQc7r&No{92OSRJppKNv7JI}-gIC5PXNEInvm8!g)m6&Ie3Bx5_O{4){+vSeh zSq4pOzDfpEJA;HXqgUR8dE{%XZSPk4Hug&`F^6pd$HCTUW z{EH-3&5Ev_abG)v$*d4D(+*4Hj3((&SDCq@a?WKNj4XFWGp(6s|OOavQfClbRv5|U}_AW5|q^F)(W zL8R{)r|(14d`H*9kY;fh>PzP#w-&yn_|RCc@oYE*z{Eu}vgD!ERdtw~8S|tX6Tnq4 zQHQ5_2lg_doiI$JDTB$X(S{kjpJlv!M8E`)$byHcJX-CY{R7}>0>*u0OxDj6FOw7I zM6|Q`J!lt5B$PaWE30keN0{S!3q8XIVaTJMFQyqSID2FsD|rtZ+j8*y^N4G?R%3bP Uri)$U;tiQYiU_X$<1M@PzZiNPbN~PV literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba93300cd22ea8514279c9c651c3d795ec171da1 GIT binary patch literal 4602 zcmdT{Nt4^g6-GBe5W`tDt452BOq4_{Ye-pAl!;1F5?d)RQ5Z#5Tq%}ZFotN(K!Z&U zP@0j+RH{ZP+Xo+Vwo)~7)L+OszXI2s^b_JN%J&*vCHa(G0v4Lxue)Ere#`eBemgr` z(eV7`uXXr)-|sJ+O#(V+R6lN)|&%ub%HkUEr7N*K|A3sf_73E1wWiK?0HMz zz3i=EejR(t-YUjx-bsv4d2eDJ&hNKN-uhGBJLR2zQfi*|?BSe}aky&88E09L{mj#n zch-9Yd(L?LBdz+K$Cuv|k&tdCY&Y`kN3qH*Wc9mRrA0GCz*oATQnRB!Q?-<+HaBvfViPSjZGJxsHALe5=nY z5Kaa@`AjQOtNNoI62xy6qXj?8giNGB>qKrS@}+Ug!#?9#KTe$_aI6BV)$w9s{esw2{ohbI=Y!&jQbP0{iHMG!?;mX#ooX227 zF{Bg&uZ}mpgC^6CSXVn{J=SIN{VqG=$J#yPORdLyde6x8Ew^+VR?z0&7en}U@; zu`;7u>Kcdqrgq4BW@L7?9sGAqrP7HbNiGZT5*>LC&5qX9J~`84o_3^tp>;tvdB@=fWpp7U8RFT*S1bl+(_BRthc|4DQ@XQR<}V&^I1^r0yB@?XH%M=wGuTiq<@1i4AQl`_qJs#^OE|W*o0~f_ z?j&ih!&q__Jga3TfuwuUqWiJLpC93G+HGYWH&8tCX*zY__a?CwX(6tSorTPElcJr* zM7oGZV-;qw1^g;y>+jaI$>ar4CzSmEV^_Hj*_#H1enZk7;#!lR%uStDSbo9w`idf%BG(3 ztT|^wjyfj3%}Se?o3K&|1oA`$OZU-HQG@t90ze(JdwLOdUGDKcE^l=WPv0|T8&Qd5 z*wxpxuCaz#We6HjO5>F16m}WdRji(v>k`nJjhs1zvC%=`N1gpk7vH{egJLr`_qVP( zSFhxTe|78X_XSj`iDTcf>L0c(Ir?aHDrx$Y8I%})Zq+O zF3AhzB{Wb6xouuaGiWNMWUPW!k}oKs2MW7Ngdb6}Ma{_7M!H#Xc0@MN&oEE^4$J!< z2Sn)qELP#bh3lA8W*Q{bS2FN4f?;N{_BKZ8;_TVG}JYDhen?H43Vtm9f&q%F=*FVD5elW!_PRukG4Yy4Z2sPCH2w%S?DS zmFPCAC0n?yUAu2UJ55B(Z~K_{ql%5^)Ba5QX|19N*8uQtIM{Fvyio}I(awN!fsV;` z1@R8;jmwueY#g$2`O?M(wV#-R(1;^Zv+rWv;OIww5ZK$ocGI*Il7R6*T08DY`{(a` zP#YIufOgmVG9QfHaxjf2A=BKyzDq$+;W_pOQekKAVGXc!ZxW>L%7jAg-^N~>d458Wv%$m!>Ss2rX)z0 zl(^K*#}qLHIaRW%?~A-l-!Oh4ipo~iv&*!VKJ#9xEL1Kl)J)J^Xq}`V)6xjxt?Oaz zbpmmNGD1q(#Q-`k^9wT-{bXsGo|WZU`@{kn>IG+}W-|WonQ4uw-#Tp+E=;M1|5Inrl|R^^i5^wO6+9qK!5kpFxq7 z`~o#!;g*V)l8W&uNEvIMXEe_*Reu*u%f8=Tl6Rp68S>!s(W(F61k%@ndw~Jvs*s1 zneRC&qL&TJ%QCbK%)CUoP97&Tb*QjKC<6j7utdo9l{td@PD1&dzmx3!01|FsZ@|iT za0Khe0bG(F)Go?#O1*dM@nZcs0PGB`Q&8O=g2X0AY0d6|a gY34ic?WulRw|^Y6th^i=WNOT0lPGeVE_OC literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed6e71fec4ff952d377fbf3562d0a5295b25fec6 GIT binary patch literal 3271 zcmaJ@-EP~+6()zjmgU%qf0`{4l<212YO$8KyFgn6X^=Edw*j`!Vw3E)LJNY@j3mYs z$(n<1(M6YLa+N2MDKdFeS@Yz+V7CEtRzJ#@Ni}{Kj(bs%y-U{wY7$U z=dXY3#3z>w;~&&nepb-AgIE0>;)%hHuE9;t60>VoU)E*#T8TBbyY|@WI%Bu%j=ioo z_Pf4GbM0hh+~_u{wv()mo82|E-R`B&4DNCN%;3IQKef6WoJE&~Ew1pDTL#|}7!{iX z_MRa;u@1Q@wuB>Gv>dVdp1~P!Joor2Z=N~bZS>ZjoBR@AKeM|#=rO+WoMBBh_bs_q zt-k!+UDXqKdap^zIX*^0w_px}UA3QAW$H%!S zohK=VTW0CxkyHYW)i691QeiXmXgiPo2sHZylu^wgVZvN@+Knl@|#jci7|3Jvjp-2xqN!*)H`LzAB zj*7L|&GC0M6j&PPoiI+dkbtIB=r~cGB^cZJv7`{(AR8HkZ&v+sJIrJ}jIk(GqwHwz zh?ZA+W1$qbE`2yc3^NJn2XO+gjYa9gi6YHPn?_2TdZjCcE@awwvHSVsp+WCGys8T^ zwYhPyB5!Ei0DQmF#?+XaQ#Q4x_S89bO(Xq8n^RM>QzrMc^}^=nOLOK<-51W(cxmGQ zEAOu1203bf@?D_NUZBukvR}1axsLP8MyyoP>nD*?H2_|c4PpPVTp0@eIod*&-U0?m zfXT}wu8`Oyu|;AV0ytC)E?g{e_YFkA5|&pnqG;IgebYC$%!bu4n@nEANXwKv_`DO2 z4hAdCeuRcLo`NGZ-9_tqm2-#0s(u!^tc#AMWP=g{@p0gMao-l z>7U=PvZ<=e2)7|pDgbI&R+kj6G+Q#Mir>nvoj6rGN|JECUs#*}5tLLuguym!-*ip+ zeYA0NngbCbCPX+$qG9O}^aAQ2QX-Y5<*F2U67?%;D7|5p3iwXGkG1j!i8r7jNazd- zuL_zoG^V(t2P@f*Hne$aA~4vgdEm?tDw_QX)b?jKU0AwaSEnmt{@5r z{0lqi_b73@F}7dGg&LljC4_7Y6UnSN{BDd5am%2sGiF4j0E2 zIUMyrLWk_C?$O1`59nG2X)*3Wf4GPao#zcQM+Zk0N(iAt1OV_L@CQW_R34|(FHXfI zO47j~PB}15fC&$HtontbL<%fw?Lm&w+T2`GZp|Z6l+iJ>|Iz&L5d4A$O@PY5Jy_) z>Sm`ijP0S5+9Iwp9B$o>BTA} z9w^u5MMsDlPOCckF3P7mw>B{(r5lK#zAWk%Yhen8c4EO7H8sdR$7q>tPtBX{PIP7xkkBua*{SxjGS3F0IsE z^7KVY_HWg7=WPl*bqm5p3ii!wtcjm(ZP?8fMAR#0HjtPB3?#n)YHnTHK<|~!>XCZ% EzvVfLZ2$lO literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e81b2ba87fbd5c61d380ce4cb21c9498c71c55f GIT binary patch literal 2123 zcmZuyPj4GV6yMpIU9bNoPDq=!D%7GBWTDsL{2t;bjdt)bc5o_(+H*emXdGnk1dw$q#)*N_# z`?D994afNl2aAsngPZWGXCS!4N#dlWPYCL6;-<7uQ`Tpx*Y{Gt?^`=fDrwLUtj>~Z zTI<)W?j`lK(Qgn(FwqoE?!TbC!h@%*zXW5(t1nz$<8>G>^A!;YBARC%(Sn(!7mP3S z)>E&)!W$2q&g$Qgjng3}_^Qxxo<&KAnffg`9K{pyWwx1{>YZ4Mp?)Y6Qvuc5D&Gth zDiFu+#n~hH){97wLY;?WnZS@vhN?qNjoV-}8kR+=FuoLeEVH4D`_3N*Uxep2yy|xl zrE}mMI>adnC+p7C-EyW>lhWCCHQjUNozg{pn>`2UK=a^Bt;K)t{OElO5!n}W{|fj^ z>u>GWE08~w^q@M^58(>3b93+7o~xed`V;b2L__|B{+vNXW8J}slFDC8mwVhlXqNO4 zvcz3W7a*oDe$ zqLGavH8Orw6e8o&L(|eNr657L@Xw{prD-f~YS?xjXB{T7pk*EX>kCPn>ZXjgFn#rl zBj1JTqrKiJPeo7cibt_XdUwP`Bzb`@y?fC>^@<%m%Cn8)%EqP?Vi1cBEu-NhA$t>% zO?pW@n7dr*UhAn?i;V)Nqb&iUanTFoObZFM*&FLPQN1@s6^=ww2&uZoj=8j8j#OBP z;_B7Q;oK<2rjP<4LzvVDARIR!0b!&;Typ-kPaC8LCFn0Sa8JLu4CuM0Y0M1I?^291-q4JYEZZ6$U$S7@q{Myit1Unw|Bgm29;n>h9dPQWA)+=%8e}TGH)WSt&hE_L04Hz=}RV?l%q{ z`3!tuUESQn-RE=MnWA-RS}m*c=dwEI{W7qgPJ?+ws|+~X$8K>F1?YSC0c5;E+%%5C{JPS@oDA#ZwVa^u!1ul;zcOH5u4lW zE6?B-Yv=@g#0|m-9HX%wnKiULj)4zj9Uc?OG|=}rky$NRQd{z!S|sP2m}SUFd><w@v8yo;6>O=-)B&<3^KoOuve(_AR6u&fC_$e Rb-jhmeGTBipCHIr{{m0!D;59% literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ff1e56851b2d86d7b40310d0e758adda6e3be43 GIT binary patch literal 2419 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c24d5b1bbfb301b0d8ab914ff81a07921b16b78b GIT binary patch literal 962 zcmY*XOK%e~5cWQ@c{DGost<|7UUSH<)LuZU5T&GQrK+GNg=#O`O*~1gdLQySX}c;X z_!Wo)9Qg~JIr0nk%Bg>W6XR_JiKY28{$|EA{_KlJqegK3`027=Iw8N+W_cO7d5M;O zMz|o9c!X+H=US+Hx~6=C8=>i$=$l@NmOKk|C{t^!?+~ca@;;#zue!ojk*h&ztb4T; zehvA$x3$4%`ZA_(e@|PYtkou(pk?oj~EAemw?TpaV)|> zvN&>T4>&jJXnAQcOE{oYNfXW_+{(;!Zl=7WXB9^V!Wm|}$jD*l)P_+(&jGMpHHidyJ|&532RxsACBAAMz59 zZ$RYh2j_>!C+~VIGmDDKFi_v`L^RR=leyD41Q#;6X29LoFb5tdAw<$W4ThHqr+QxzGh6nV?{gD8q+@eNbo7bOe{OF>ydU1gfXIOfF&g;Yz> gQsK&e7}FVt7fSwg9lmjo3E+0+Hwc%K!iX literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd0d0dc48338af3648aa8636260f3fa00ca9eb8f GIT binary patch literal 1780 zcmb7F&2QsG6rT^qoe>lcN|Be+EkCPgXEQE&0YJq&Y_lY;hWVf8GG7-jZ*D5byQ)Jp9it)8>yanc5O z8fzXm-2#uxEM_x%K$e+a+63=bin}P-{u_2hoq#C!td?0-#<{YePU@TmSUH@@2`>{J zvVLKh9K$WP!ANwCYER;81k2&sr{^X|_`^KwC;lbl0#@~fVs>;Lq=y1Rp>u2mx@;DhY9$vEYGK7OKLyp>?gd&sdu!h(1kU?rz zhf!8a#sR5tBC}kC=2-1|s`lS>Kt}^bz#zi6z;2+a$=Bs!(0mC4i7s%8$Aq_1(nYfa zNp5ILU*jt@hf)~3{NM#VyD+=4UZWq#6}}=bPBnnQeG z%^rQrvm)kGKP!M565~{J{&K7l^N!9v7LLu}gl0D(P;;M1hT@1YEaXwi% z1KQgimXk==&+1GHN2;+bi~oq`E+EEjFip6{J{aV9*un&R*u@Un#rwp?CSn723j%en zf&FI~%(hbuN<&u&zP3Kh5clXQ-xgY6$!6t2od5P75AWHByZejN=0v4V` zliH##)x>((BV5OC(5nBjR+^#pLm24ZWR!zJIeS_+DV?K%e(L6D&3<_bdXSZ$GEj7p zL1U65w%NKo&xdQ()nZ@j=BP!p=uW~vfI@w+YIK4BYLq1-Sa1+ea;ev5MJ=bRXtP*W z`bjJnd*xL%p49#zhECEpZ*?<9Tw|bY(@+9z8V5Awos9-_3qR$q6!Sgg3SHmGaX<$a z;rC%2cpSbzi0h`U9AFVzFV}E!s*T3W>|^9&z%^-WyO<8E*H!MrqDm(@`&>hAJ_9an Nv`hEwz5Tt1{{l|Q%mDxZ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..183f01d2bffec321d2323f6ab0b4f1e6c94b6dca GIT binary patch literal 5629 zcmbVQO>^7E8QujzkfJDBmJ~&@69;j=%GMT>I8HOJ>&CI2Sox4s%W)EpT^J&kB1Dj& z7N8}u)Ftwaotbtz?X~HQG`-Y+p{E}DD{$?pf1rmPl0NS)Ns+QW(*n2{Tm@~8Wp2yR?MbVv6`7mrfFB~ zX10=T<|;W(b8#7`nrEEHN>+8?2xmT`VyYk8M z%13GLgP`Tfo#h*;bv39aX>K_Y(pw8fYJD2SD6d2}MbeS2G5ERhkm$O?Or)r>{92xI%^cZP5I znFpCl4n~8qP==`*+p%MI9e-6)|^XKt=M$BTo=TTnp7g3%=EyF*7@=5;%l%G?xv#A}%pn1LM zpThHblxP+&`3_3Yiv^TvmM{CKQC{rN@fH77luq~tHt_Y-CYy=4>s@xXq}Of>=_GY8 zan?FP=*Ld86DC1B6i!`LbMPi=RfXfVe5YC$)kci!qUm_+UVt6SJGdIHXsdO0J&Jti zz87{xoL7%mN=9nyZcP@HRHAU>Sqo9=la843AA&?sDN@27m{9$j@V_j^clXZbA!{;kij%g1rS{tor z6?Z&`WS#Zc-NG){Yx$$GX|1xyjDSnsOPI%4dNbt>NtIRd{Z_diHAPu;MI#Vl`Kq`t z!l>O8t)%>^w-%S%J4roioo$~xTa!Yp1>$TXy=p_q@_o^|Uk-z{ewA}e@0H^q5og%OA#vgD#|kht#B z#W1RRVSEXhK?}HY24gj2_+BIv;iowlzDqVNWl}2^VNK4U!!+A}nwr>$)M|*G_;B%Z zfvV&N1D&L4D5A_*JjbVv1>QY&WHK;eCITL%1dp;YbkPFnHmRF6Bnj*Bgzp(~F@Y!a zcs&0IsCmTWQ$74^>}{C)mO|Z`JI1$c*L;Nc8)hRb-`_m*kUiuzAk8-($8+a1a^cc@`%d)uonHHZx+2AL5w>Un< z$6RmRs2fq9Hcrt3b(SCk!su|O4Zd&C_U!RznOp5P+Qei%cwcds)4uQV+l;eK!-sP| z<|}YPGZd|Xk+XfjJ_^N&i|e8#w%hX3Me0%`3NG<_e-XNwTa7xcWW+57jo=k#+-Yii z{scW>wTHVn+{&~t+Rp_ViI{{L-Pxy%gH9*vpk|loE6szIT-{t7lGZ=CC{OTHh9vhR zuH0bzfZsYhFd8-x%e!SAmz}M;AV(s0ch&>2$q3^)K@8$l{=5XE5kOOOT_hz_FX(MN zkmo2FGMNU(u^@KUZ2PT=yfFEtXhGre-wq@?#DhaF*CU@0ovQe}UhA!@owIuWW!nKlHIfS^9KY>)-#neZ$ngpT{gFSY=5$6P&*ZO z&F(i>D0(_7N|H$sd-^dH0Tu;{zll?mQPTmHD(ys}NSgw=VbJlt#9Pu47GcS`=>@TH z$d&^kwUuSk~f2@W~8*^G1#rc+r?fKF7N!$q*u5dRluqg{G1)1Wx@W z<(~)|_4N*=0FRWmVmuZa! zB*?2ZUZn@)YfNXxElp$JK{dT(ySw<*{h(U#bF1wo^(U848!+_pGbj*)``Cg%EI=O; zf5ET5M94eOe$8&OzhYbH|0`&87{^#i4Y@|WM{UuP*U)}IQy(HP|A$4;w0zW6jL!hC zXDoy2J)t>wkKF9mpby#Oi;ZkTO7g6p( zTcq7X1!LYlb7Td}>R^DA!_Mdl0bP@W(gv+XQK-4+P4smXSrCF$!F40N90ZL+0jS@N znSacC$t(n%5y{6k%pS=(5VZDVOTI<}lM0frQ$mq#fcDe$;0W4n*A<(t8{?G{UP|YT zIJSEGgvp30VxiL zFc7rnc1<4~(L+$H!8w%JDMIcLTNvN-3#53g4IFiV5mH7q=UKBwIq5B~p?_ z<;#?m=vl5UK^ZdHNlhG+8okS=&iOteKJ6T@qI*j*-ih`(Le+-h+hB@5Gguo$O1V z6-k~l=0`+eX8%@4goN!Oz9+^y3lL&{e<;^pz8baOpf3isz9znRk6yn`Un5Pr2TMfN91_%##pP)yd_%yj$ z-TAQ%7@oKfb&LD}wVzFS)U&3%MMeiD(zb`9#gxR@(xv1Vlw6_Y5S0p$1nNKG9!QqC zBU93{ZbIIowqHSf*!~INi7c~K0BACSljvRm0nBxV*;)RTr I$(zOKzy44gfdBvi literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/logging.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/logging.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c53173e54a043e9daf03ef9881ecdfd7ce7eb95 GIT binary patch literal 9957 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 AegFUf literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/misc.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/misc.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e38e3ad2db94b69bde897249b6a1206d46b856fe GIT binary patch literal 23033 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0539bde46922036c5ce3e897a42605d3ae65234e GIT binary patch literal 2121 zcmaJ?OK;mo5av?Ui;*lRsq^S_;X_@a5!J#i&>BV2AZdXBfm6ipg+W5C$dN>gukJ4G z$Z${2wf7!!u#f!b+t^4(~;WyHz-y~YEmbB8g-%efM zHESoil6L$KvA~Jy&^@*NH6Uj_SU)HJRj>}OMUANWI{}p4wx9vmqt?X6ulJ(%%Ua|{ zS6(=$#NRNQ9i!QWPSkyAM{7~f$ZkLv)}nRXxf)%2;rKTrvS; zC_uFy6*A5iDXA}AJemS?K7G#13!Bv%a%vgBE4YtNR$!FLm% zd-#a2a5_QA=Plle>^)0bXXL~>C%@U!KC4BwGe_2C^mWpr~Hpcqstn{)+)=NfA}1>-EeIBLS! zp?B%_KxzPKKA~|Y1?5n15C~J8P0?;;%^1)wrR*T*gEY>}9P<3a9+f#oSJ2 zBo@NE4B3;N?Wd#ug)bF+J9M?lIzr~I)W^)8V~X~Q?-;8swO1wJG_fhzNS@fzja@*N|aiwYAv{^TZ`Yq6aO3! zXL$-kIEHx)$?!fLLXsC6g5g6p7Q^b)px7QvIKViD0Ty*Q2R_7-IUFYOc&W1OeKQoX zgh7E~HU&Hy7eg!s2^_gJEM=VFo)KdDy<(v@Oj3ileAa;o(|md@+aa!fgYfrpFSf7` zX%?b>fPteC-oyP6OL{f*x8{xE3F;#^POMW+baMRly*$HnWhk-s-Z=934SQ)mJ^&OS z#d1bN7S8k?+Df1B5AbCmtEStyRJ|$43->i-pW>!4v~BSZ^)Xx3L3e?7JGvsY&V;li zG4v&ojbug=pIfI$?tYEk$EeVE%p7{2QO3uywz;4c3Ys1azgZSB9?>mx;}*?W3Kcu= zW(&&8OlTfvc{X54F=JVo0*^x^7E(9FBw<~0&=gHr2vLOrnnehZ!CS%B9YYDcWXTm# zCX&W@Dmh{cL6iIlI5tX%mc-bfp>R@Wp>C#HF~VLDhfJdJ3C~kH8Wr;?!YA^Qhag5H zkM8CY&^(hk;R*cDa%t|>ZAxH7I#|H;0LSwo!*I}~ZnuGscQm5876dSz21%UFm%A06 zk9$v^-5vOBaQwyKTW|36b47xFo$H@6t_x1t9FSAxSk+B5nDvjeOsD5k)?L9S5Ezud zL`jwKHs8eW{w>uuA?JC5zY$43o$3<$1VySL7lmHv`rtJ^>85UO_cxF$9#S)la_+54 z({zH0`Qn38sz3`4Aldu3CY?AqZ4MyM#OKgqMM?a10nJs+=); zY-nZ**93DZZi9d+`PO%79+e4vubC!3#;Ha0-z|HcbZpmlYp&g}Tw?#bcGD%--2VWW C#%4DF literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/retry.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/retry.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3379f57c21504004d8cd63e87be604ed28b0ee6 GIT binary patch literal 1624 zcmZuxPj4JG6t_LIlilqmN&%Htfba=vQFce77gSXh5(NnfRh1Gb>}BPQ-)4t+{$P8v z-6*+`O38^&&;uO%36MDRDdx(FufPHEo@bkF3-gnD6kAts`TCK0E3Wa6ST=@iK%3G>)&72QdfGe~sjf=_zT z)d}`}xPi5UwGX|SxWkV4W=wOv^*qvD-Fp^K2Ac1)VgE0*%pMIzGpNDNrdhQpJ=mt> z3_y+Q?&r!Fb!ed3I#|{4t+GSj^d2j#iv1e0h95Ni@i1!QrOJJ?>wHyD)eIq~8jM;s zi3Pu~WhThZxS%wv3xa?eh!;ut8xc!?{#JF5Qp$U z%VqB8Vb!Suq_Rhg0!r@!7&wXK zqdl3I(xE3y?Z$z`8tu&>zJlDIsl{E+^K2f_a&YJ-V`K)(!Ct^3U%!cu`4m%J>vWpar7o387nrXu4R>T*ubS;Jnl7!XYtZi@8Ao(koCv`O zdc=Qr-K^zaQu>jw(?3$by> zkhl7`P~U8=ZQa3%OHeS;<#KR(>;EaCZ|Pxa%>^S-aB++NE-l?8af?JS&QVhUM^ZwP9W_*IQ}r!_+vJNW=TJf314G`=zI>J*-OC>_2&&JyWx u-axA9%uJ|M?dmXQmeb literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46a6db2d7feb641ee486f131b8f08df86173bdbb GIT binary patch literal 3843 zcmZ`+TXQ2v6`q@9Su5LnBh)5=bVLPP;E_m$0?O>B2w@i%7B97SUQ7jfq;5$Qk7kDM zmTzbupi;c?5B6dAasL9({E8{wc;X*WB&p;(-J_ecnenvxa{BZ+-?_Cv+SsT&c>ejX zPJD2~asESt<)??$5#dU=HyvqF-?(?-5 z&QXou7V9Dq6|sKDx#NgBUZ48B@~pzEeC^Z9(KS$*^|D{!Tv|=W!x-wzs#<1lQnr^ET?UE(IvsFw(q%j^@;$3Al$k0l04$sT%Gu52K)F+1i^d!pk+ zwfo?EOkB<~dBVbXmC)glMlI)C*J&RP<^4}{X)adaZ&9)n5-AU1%HueH2g5K+lZ&}&Ew9p&nc8K5j9lPEr17Pw8xggEK}_~(j(%cwAz`iw-%@n!!#O;<=qw>W`k#DjCPibbisiE z;K70oZ81ja<`><5#);pcEk(q4tUe-9VnM!iYV&S1(OEciB)*JwnR^!SUOl2|qJQBH zEB^6w%W9|9w0Nu}z7}JAZjwKHY6$?HiI~^UAgdtxB;m zKY=VzYz~vG7bV@yWM)-trdb!+Qv$4X5D$wRle9M(41^TCn@w~+(M44vXk^+CD7lvp zlDI?PhxpV3G}`&Z9lB40sV`rf1|0pLIv54q)82D`>b!7=o|$pwkEg-Zu6pv9pzvq3 zs31XTGRkvNn#?8(%3=M8s!!XZzL!TbD)Rz4 z2cfZt^o0D59wll66bc9Rdq}jFsZ9;#?p5&i*aw`)w!$&H#7bOOcCA9Z#~u#Yp`5_R zs1_8uiseEos*_a6B(tWNAfFlW$*0Tnzh;~zW)Uw_?1G3X)SbRj4!OX_^9(ZwkCp+{L7 z=I@0ADMT+8p_V9?LUvA(z&c6XD@opK|E!~8EkavM1-8a{r;EHQBv9;_3fx(QW7lLs zn6|h>m8HA1LjKbwA>W0B>LwaztL6r7-Mxv{_9Sj~&a#5VhG94lqAiqb9jC@#U0e|4 zIyQWByzF3AJ5y)sa+iDm@J*cf9I-R>ZPZL{tOSUN%KqU?&%$aDz1E{UMiQE%#TC~8sx`c^%88)QX=E<5REB@A(mk*{HD<)Bw7^7>Up3=5;e z$JnoEj}t6ZXt?qY@m*U|fLyhx=28se^CBSsEH)4THYbvgSz970nTJ%PEprwBgu%2T z8_0VN?flKvF4+}oy0B|?TIEpKs9OcaPcSAclXa`ee9p>9UmbGdG!m5W?m*22)LKp|XS1w%OEWi>S6OthBZUdt;sKTrs9N-pM( zV*TeTDDrh^Cf}guO=@0cfH$uipcKuRV1!+YwmRz#4@RK(i)yvu1#SbrWPUyQ7S>yV zF-Xx6!y$rrkeL`VNyPXU`PA&8B1E4lJU>9k_!q(LJ@uzP_e?SIr>^`yM));AVugy! ze#gERun>&10`p{5w|_pYb!A{~ddk`Jb@T@am+mC!3gS z`TGwZw6059Dr#DsYpO9tRbS+|n@S2JNxDi>u&8puks48cmByV~HfYjEzX*ExkH}cf z1f+3l1Ee`+Dcnuqo0zZN9cO%!i1*2n)gv@D*x+_0aJ?^UKXhEYpOb~^#!l7G8gF=R b6))P)8n;aUiw4YSX6*d`8gJ1&zR$k`hVl)~ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78af98427723edbba1b6a8074cb4513b86774cf5 GIT binary patch literal 5658 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 Ef2HgRZU6uP literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f67400130740ddeb10d607768995786e9679f65 GIT binary patch literal 8278 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13870897f9b0a2f93033a6fd3ebbed3c18eeb327 GIT binary patch literal 8057 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 J9}J?6>>m`xd>8-# literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f22387ad82bc02e58135a2e659be7503588fba3 GIT binary patch literal 3296 zcmaJ@-EJGl6`t8$E-6Z){1+i<9dzIZP-%!{5GQ~UBxwyx!s~P+Wb3yg}@Y zMZN@2ua#q#c>}ELrM1GZfOVs^uJUVOy~gPdXl@G*@{AhK0Cw1W;qUw4k2gs-hN8Exnd~!IxxE)_5#>ts<#QIN+&(MK zQEYm{cD>Hv+8s*GhAg>_93rjuR$Q=w<0hq(-;x#A0!7 zg{EKluxJsw6^iOWnAjNgAEFk(W83`jb!V83L`O`-z7$F4zBmv`mg6To592-E$&bu1 zOI!J!R$mFRCq>IBfK8~*0W9B19IBmM=AB5UMkt6tXKZAmJEiL|wVfXo z*CRDfQ<)B;ams~?@RJQh)Psgp)FQO6z>Z|Kup_4pF_hC6o&|e!9&9DzWC}LmsfD== z-pjybJ(z0*XNS@ZS$>qqYkG=0x?W;Ew*XMCI*c{Kt3vQ=hOq0QuLZlX-9!xH-cdBe zWEDoay@v{McxCSt9@@PxLC266B(EF#g23+S3%M|MRR_ zXAh9%EHgu)4y6|Dre{I7t6#c@*DplOQ4v7-)l1(!wQ|vSj3+{1eg6_Vm?Wp3@t*~p zo_Qx^@{Xw-gP?H#@7_Oep3q}@8lI8U>KXYH@yL7RkK_^gH}rRG?=ke!)qt4VeqBAL z1b-MLASx29-Rlo*^0~Bz+%y`J3wR1?#A3}7kPeph z;hqZZ67rE%0L`|tRIDS$(!i8)!daRb20Y-MxYrX}GeDB{Wg;9$CiU5xlgiliTX#WF zY9j4r9B9AQTqwLu7eOAIVG%sbWa`*c1&E3u%SBoQ`1PW$A@_~-sHlFnv#C_3fO9ME z^AT(*!c_~}pNpC$-D?q~eKa@De&j7!KqHArn+9UYy)#M)2e~^Y_sJKxPpI)vgJXI|PrPFf#w{pz?44G4a0Z27-&4$u z{)+Jscw3FTKx&(~?CDQL>nZDJ3bhiHo1N1bU^mFR290-dnrfxaMNjr6sHYjmh8+$? zdineqG6h4Z$9bM<$as^nJz?|vKx(;{Kr~f0Vn2~SPiNtM%0^@04H7AqspCPGB-tUv z*?tdkf8V)@8vkL%`aV}jMOODoAk^y_!VXRO$k&2FZXiT{WlR;}81Ggss z*mz0QlA~)guh$qCI)`F-`3?aK{BYbe@<6oNJ*~$hd@x9|Jt#mIVxcXRr9TLr#Q~%j zvTjqLTG|yMY-}J@Q7yMt{5tw%YvbYP_cx=RCp){Fe~5NI+1-q` z?`?c~@8iv#=+WlKn~#f%Et^GUpt5le1#bZ}!KF!nH^qRouckzrMQ#goh35{8)TqE@ zF{Cv=px*z2O5oLKovzXvRhTk8d_BET)Lxze-Ug!-HIz2r1kWmGm;~-=h$#a?XHN&G zRZI_Cvc7f9hcMnsvzGJe{Nbc+o|{`tJO@$I5DqKmh&$9zM-O{g7)ewF(5`GCdx$$0 zt+{GzGl&bQ#SlM?AfyC}ewh`Ppy#8BnK4a)lFyfv%2MWj`7_QpYw9Y1qpm?yRJnxH zz(^ETI9xc@!)1$FWc4eG)O8#dy8VK}E#d2El<^2=7B^>sv(Kj|I6L`bm~Tb1(e8Zl zO2I`*b21pN(vW)8`!NXAIy^lWX|SQflrne|Iw*r`$(3H?LH~8*V;Q!mP;c`4X}yTq zUX~>yPS;-oKC8C!@ihrT z8hZFwEwGxaMHLecs9>X0cuJ@>cwy^L5t^eMUm?Yn4^6QERpg<|P>2)EhxY#7J_*yS6-AC;Mi1s)UA0gtJK~sbDbH6KIow~$q P)EWzo%IZqv%|_$@70#cP literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b66567b2d9377589da047a85c574dd83fbba0d6 GIT binary patch literal 4482 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 Md1q!~W_Eu1zZzq^CIA2c literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/_jaraco_text.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/_jaraco_text.py new file mode 100644 index 000000000..6ccf53b7a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/_jaraco_text.py @@ -0,0 +1,109 @@ +"""Functions brought over from jaraco.text. + +These functions are not supposed to be used within `pip._internal`. These are +helper functions brought over from `jaraco.text` to enable vendoring newer +copies of `pkg_resources` without having to vendor `jaraco.text` and its entire +dependency cone; something that our vendoring setup is not currently capable of +handling. + +License reproduced from original source below: + +Copyright Jason R. Coombs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +import functools +import itertools + + +def _nonblank(str): + return str and not str.startswith("#") + + +@functools.singledispatch +def yield_lines(iterable): + r""" + Yield valid lines of a string or iterable. + + >>> list(yield_lines('')) + [] + >>> list(yield_lines(['foo', 'bar'])) + ['foo', 'bar'] + >>> list(yield_lines('foo\nbar')) + ['foo', 'bar'] + >>> list(yield_lines('\nfoo\n#bar\nbaz #comment')) + ['foo', 'baz #comment'] + >>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n'])) + ['foo', 'bar', 'baz', 'bing'] + """ + return itertools.chain.from_iterable(map(yield_lines, iterable)) + + +@yield_lines.register(str) +def _(text): + return filter(_nonblank, map(str.strip, text.splitlines())) + + +def drop_comment(line): + """ + Drop comments. + + >>> drop_comment('foo # bar') + 'foo' + + A hash without a space may be in a URL. + + >>> drop_comment('http://example.com/foo#bar') + 'http://example.com/foo#bar' + """ + return line.partition(" #")[0] + + +def join_continuation(lines): + r""" + Join lines continued by a trailing backslash. + + >>> list(join_continuation(['foo \\', 'bar', 'baz'])) + ['foobar', 'baz'] + >>> list(join_continuation(['foo \\', 'bar', 'baz'])) + ['foobar', 'baz'] + >>> list(join_continuation(['foo \\', 'bar \\', 'baz'])) + ['foobarbaz'] + + Not sure why, but... + The character preceding the backslash is also elided. + + >>> list(join_continuation(['goo\\', 'dly'])) + ['godly'] + + A terrible idea, but... + If no line is available to continue, suppress the lines. + + >>> list(join_continuation(['foo', 'bar\\', 'baz\\'])) + ['foo'] + """ + lines = iter(lines) + for item in lines: + while item.endswith("\\"): + try: + item = item[:-2].strip() + next(lines) + except StopIteration: + return + yield item diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/_log.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/_log.py new file mode 100644 index 000000000..92c4c6a19 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/_log.py @@ -0,0 +1,38 @@ +"""Customize logging + +Defines custom logger class for the `logger.verbose(...)` method. + +init_logging() must be called before any other modules that call logging.getLogger. +""" + +import logging +from typing import Any, cast + +# custom log level for `--verbose` output +# between DEBUG and INFO +VERBOSE = 15 + + +class VerboseLogger(logging.Logger): + """Custom Logger, defining a verbose log-level + + VERBOSE is between INFO and DEBUG. + """ + + def verbose(self, msg: str, *args: Any, **kwargs: Any) -> None: + return self.log(VERBOSE, msg, *args, **kwargs) + + +def getLogger(name: str) -> VerboseLogger: + """logging.getLogger, but ensures our VerboseLogger class is returned""" + return cast(VerboseLogger, logging.getLogger(name)) + + +def init_logging() -> None: + """Register our VerboseLogger and VERBOSE log level. + + Should be called before any calls to getLogger(), + i.e. in pip._internal.__init__ + """ + logging.setLoggerClass(VerboseLogger) + logging.addLevelName(VERBOSE, "VERBOSE") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/appdirs.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/appdirs.py new file mode 100644 index 000000000..16933bf8a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/appdirs.py @@ -0,0 +1,52 @@ +""" +This code wraps the vendored appdirs module to so the return values are +compatible for the current pip code base. + +The intention is to rewrite current usages gradually, keeping the tests pass, +and eventually drop this after all usages are changed. +""" + +import os +import sys +from typing import List + +from pip._vendor import platformdirs as _appdirs + + +def user_cache_dir(appname: str) -> str: + return _appdirs.user_cache_dir(appname, appauthor=False) + + +def _macos_user_config_dir(appname: str, roaming: bool = True) -> str: + # Use ~/Application Support/pip, if the directory exists. + path = _appdirs.user_data_dir(appname, appauthor=False, roaming=roaming) + if os.path.isdir(path): + return path + + # Use a Linux-like ~/.config/pip, by default. + linux_like_path = "~/.config/" + if appname: + linux_like_path = os.path.join(linux_like_path, appname) + + return os.path.expanduser(linux_like_path) + + +def user_config_dir(appname: str, roaming: bool = True) -> str: + if sys.platform == "darwin": + return _macos_user_config_dir(appname, roaming) + + return _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming) + + +# for the discussion regarding site_config_dir locations +# see +def site_config_dirs(appname: str) -> List[str]: + if sys.platform == "darwin": + return [_appdirs.site_data_dir(appname, appauthor=False, multipath=True)] + + dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True) + if sys.platform == "win32": + return [dirval] + + # Unix-y system. Look in /etc as well. + return dirval.split(os.pathsep) + ["/etc"] diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/compat.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/compat.py new file mode 100644 index 000000000..d8b54e4ee --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/compat.py @@ -0,0 +1,79 @@ +"""Stuff that differs in different Python versions and platform +distributions.""" + +import importlib.resources +import logging +import os +import sys +from typing import IO + +__all__ = ["get_path_uid", "stdlib_pkgs", "WINDOWS"] + + +logger = logging.getLogger(__name__) + + +def has_tls() -> bool: + try: + import _ssl # noqa: F401 # ignore unused + + return True + except ImportError: + pass + + from pip._vendor.urllib3.util import IS_PYOPENSSL + + return IS_PYOPENSSL + + +def get_path_uid(path: str) -> int: + """ + Return path's uid. + + Does not follow symlinks: + https://github.com/pypa/pip/pull/935#discussion_r5307003 + + Placed this function in compat due to differences on AIX and + Jython, that should eventually go away. + + :raises OSError: When path is a symlink or can't be read. + """ + if hasattr(os, "O_NOFOLLOW"): + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW) + file_uid = os.fstat(fd).st_uid + os.close(fd) + else: # AIX and Jython + # WARNING: time of check vulnerability, but best we can do w/o NOFOLLOW + if not os.path.islink(path): + # older versions of Jython don't have `os.fstat` + file_uid = os.stat(path).st_uid + else: + # raise OSError for parity with os.O_NOFOLLOW above + raise OSError(f"{path} is a symlink; Will not return uid for symlinks") + return file_uid + + +# The importlib.resources.open_text function was deprecated in 3.11 with suggested +# replacement we use below. +if sys.version_info < (3, 11): + open_text_resource = importlib.resources.open_text +else: + + def open_text_resource( + package: str, resource: str, encoding: str = "utf-8", errors: str = "strict" + ) -> IO[str]: + return (importlib.resources.files(package) / resource).open( + "r", encoding=encoding, errors=errors + ) + + +# packages in the stdlib that may have installation metadata, but should not be +# considered 'installed'. this theoretically could be determined based on +# dist.location (py27:`sysconfig.get_paths()['stdlib']`, +# py26:sysconfig.get_config_vars('LIBDEST')), but fear platform variation may +# make this ineffective, so hard-coding +stdlib_pkgs = {"python", "wsgiref", "argparse"} + + +# windows detection, covers cpython and ironpython +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/compatibility_tags.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/compatibility_tags.py new file mode 100644 index 000000000..2e7b7450d --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/compatibility_tags.py @@ -0,0 +1,188 @@ +"""Generate and work with PEP 425 Compatibility Tags. +""" + +import re +from typing import List, Optional, Tuple + +from pip._vendor.packaging.tags import ( + PythonVersion, + Tag, + compatible_tags, + cpython_tags, + generic_tags, + interpreter_name, + interpreter_version, + ios_platforms, + mac_platforms, +) + +_apple_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)") + + +def version_info_to_nodot(version_info: Tuple[int, ...]) -> str: + # Only use up to the first two numbers. + return "".join(map(str, version_info[:2])) + + +def _mac_platforms(arch: str) -> List[str]: + match = _apple_arch_pat.match(arch) + if match: + name, major, minor, actual_arch = match.groups() + mac_version = (int(major), int(minor)) + arches = [ + # Since we have always only checked that the platform starts + # with "macosx", for backwards-compatibility we extract the + # actual prefix provided by the user in case they provided + # something like "macosxcustom_". It may be good to remove + # this as undocumented or deprecate it in the future. + "{}_{}".format(name, arch[len("macosx_") :]) + for arch in mac_platforms(mac_version, actual_arch) + ] + else: + # arch pattern didn't match (?!) + arches = [arch] + return arches + + +def _ios_platforms(arch: str) -> List[str]: + match = _apple_arch_pat.match(arch) + if match: + name, major, minor, actual_multiarch = match.groups() + ios_version = (int(major), int(minor)) + arches = [ + # Since we have always only checked that the platform starts + # with "ios", for backwards-compatibility we extract the + # actual prefix provided by the user in case they provided + # something like "ioscustom_". It may be good to remove + # this as undocumented or deprecate it in the future. + "{}_{}".format(name, arch[len("ios_") :]) + for arch in ios_platforms(ios_version, actual_multiarch) + ] + else: + # arch pattern didn't match (?!) + arches = [arch] + return arches + + +def _custom_manylinux_platforms(arch: str) -> List[str]: + arches = [arch] + arch_prefix, arch_sep, arch_suffix = arch.partition("_") + if arch_prefix == "manylinux2014": + # manylinux1/manylinux2010 wheels run on most manylinux2014 systems + # with the exception of wheels depending on ncurses. PEP 599 states + # manylinux1/manylinux2010 wheels should be considered + # manylinux2014 wheels: + # https://www.python.org/dev/peps/pep-0599/#backwards-compatibility-with-manylinux2010-wheels + if arch_suffix in {"i686", "x86_64"}: + arches.append("manylinux2010" + arch_sep + arch_suffix) + arches.append("manylinux1" + arch_sep + arch_suffix) + elif arch_prefix == "manylinux2010": + # manylinux1 wheels run on most manylinux2010 systems with the + # exception of wheels depending on ncurses. PEP 571 states + # manylinux1 wheels should be considered manylinux2010 wheels: + # https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels + arches.append("manylinux1" + arch_sep + arch_suffix) + return arches + + +def _get_custom_platforms(arch: str) -> List[str]: + arch_prefix, arch_sep, arch_suffix = arch.partition("_") + if arch.startswith("macosx"): + arches = _mac_platforms(arch) + elif arch.startswith("ios"): + arches = _ios_platforms(arch) + elif arch_prefix in ["manylinux2014", "manylinux2010"]: + arches = _custom_manylinux_platforms(arch) + else: + arches = [arch] + return arches + + +def _expand_allowed_platforms(platforms: Optional[List[str]]) -> Optional[List[str]]: + if not platforms: + return None + + seen = set() + result = [] + + for p in platforms: + if p in seen: + continue + additions = [c for c in _get_custom_platforms(p) if c not in seen] + seen.update(additions) + result.extend(additions) + + return result + + +def _get_python_version(version: str) -> PythonVersion: + if len(version) > 1: + return int(version[0]), int(version[1:]) + else: + return (int(version[0]),) + + +def _get_custom_interpreter( + implementation: Optional[str] = None, version: Optional[str] = None +) -> str: + if implementation is None: + implementation = interpreter_name() + if version is None: + version = interpreter_version() + return f"{implementation}{version}" + + +def get_supported( + version: Optional[str] = None, + platforms: Optional[List[str]] = None, + impl: Optional[str] = None, + abis: Optional[List[str]] = None, +) -> List[Tag]: + """Return a list of supported tags for each version specified in + `versions`. + + :param version: a string version, of the form "33" or "32", + or None. The version will be assumed to support our ABI. + :param platform: specify a list of platforms you want valid + tags for, or None. If None, use the local system platform. + :param impl: specify the exact implementation you want valid + tags for, or None. If None, use the local interpreter impl. + :param abis: specify a list of abis you want valid + tags for, or None. If None, use the local interpreter abi. + """ + supported: List[Tag] = [] + + python_version: Optional[PythonVersion] = None + if version is not None: + python_version = _get_python_version(version) + + interpreter = _get_custom_interpreter(impl, version) + + platforms = _expand_allowed_platforms(platforms) + + is_cpython = (impl or interpreter_name()) == "cp" + if is_cpython: + supported.extend( + cpython_tags( + python_version=python_version, + abis=abis, + platforms=platforms, + ) + ) + else: + supported.extend( + generic_tags( + interpreter=interpreter, + abis=abis, + platforms=platforms, + ) + ) + supported.extend( + compatible_tags( + python_version=python_version, + interpreter=interpreter, + platforms=platforms, + ) + ) + + return supported diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/datetime.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/datetime.py new file mode 100644 index 000000000..8668b3b0e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/datetime.py @@ -0,0 +1,11 @@ +"""For when pip wants to check the date or time. +""" + +import datetime + + +def today_is_later_than(year: int, month: int, day: int) -> bool: + today = datetime.date.today() + given = datetime.date(year, month, day) + + return today > given diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/deprecation.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/deprecation.py new file mode 100644 index 000000000..0911147e7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/deprecation.py @@ -0,0 +1,124 @@ +""" +A module that implements tooling to enable easy warnings about deprecations. +""" + +import logging +import warnings +from typing import Any, Optional, TextIO, Type, Union + +from pip._vendor.packaging.version import parse + +from pip import __version__ as current_version # NOTE: tests patch this name. + +DEPRECATION_MSG_PREFIX = "DEPRECATION: " + + +class PipDeprecationWarning(Warning): + pass + + +_original_showwarning: Any = None + + +# Warnings <-> Logging Integration +def _showwarning( + message: Union[Warning, str], + category: Type[Warning], + filename: str, + lineno: int, + file: Optional[TextIO] = None, + line: Optional[str] = None, +) -> None: + if file is not None: + if _original_showwarning is not None: + _original_showwarning(message, category, filename, lineno, file, line) + elif issubclass(category, PipDeprecationWarning): + # We use a specially named logger which will handle all of the + # deprecation messages for pip. + logger = logging.getLogger("pip._internal.deprecations") + logger.warning(message) + else: + _original_showwarning(message, category, filename, lineno, file, line) + + +def install_warning_logger() -> None: + # Enable our Deprecation Warnings + warnings.simplefilter("default", PipDeprecationWarning, append=True) + + global _original_showwarning + + if _original_showwarning is None: + _original_showwarning = warnings.showwarning + warnings.showwarning = _showwarning + + +def deprecated( + *, + reason: str, + replacement: Optional[str], + gone_in: Optional[str], + feature_flag: Optional[str] = None, + issue: Optional[int] = None, +) -> None: + """Helper to deprecate existing functionality. + + reason: + Textual reason shown to the user about why this functionality has + been deprecated. Should be a complete sentence. + replacement: + Textual suggestion shown to the user about what alternative + functionality they can use. + gone_in: + The version of pip does this functionality should get removed in. + Raises an error if pip's current version is greater than or equal to + this. + feature_flag: + Command-line flag of the form --use-feature={feature_flag} for testing + upcoming functionality. + issue: + Issue number on the tracker that would serve as a useful place for + users to find related discussion and provide feedback. + """ + + # Determine whether or not the feature is already gone in this version. + is_gone = gone_in is not None and parse(current_version) >= parse(gone_in) + + message_parts = [ + (reason, f"{DEPRECATION_MSG_PREFIX}{{}}"), + ( + gone_in, + ( + "pip {} will enforce this behaviour change." + if not is_gone + else "Since pip {}, this is no longer supported." + ), + ), + ( + replacement, + "A possible replacement is {}.", + ), + ( + feature_flag, + ( + "You can use the flag --use-feature={} to test the upcoming behaviour." + if not is_gone + else None + ), + ), + ( + issue, + "Discussion can be found at https://github.com/pypa/pip/issues/{}", + ), + ] + + message = " ".join( + format_str.format(value) + for value, format_str in message_parts + if format_str is not None and value is not None + ) + + # Raise as an error if this behaviour is deprecated. + if is_gone: + raise PipDeprecationWarning(message) + + warnings.warn(message, category=PipDeprecationWarning, stacklevel=2) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/direct_url_helpers.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/direct_url_helpers.py new file mode 100644 index 000000000..66020d396 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/direct_url_helpers.py @@ -0,0 +1,87 @@ +from typing import Optional + +from pip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo +from pip._internal.models.link import Link +from pip._internal.utils.urls import path_to_url +from pip._internal.vcs import vcs + + +def direct_url_as_pep440_direct_reference(direct_url: DirectUrl, name: str) -> str: + """Convert a DirectUrl to a pip requirement string.""" + direct_url.validate() # if invalid, this is a pip bug + requirement = name + " @ " + fragments = [] + if isinstance(direct_url.info, VcsInfo): + requirement += ( + f"{direct_url.info.vcs}+{direct_url.url}@{direct_url.info.commit_id}" + ) + elif isinstance(direct_url.info, ArchiveInfo): + requirement += direct_url.url + if direct_url.info.hash: + fragments.append(direct_url.info.hash) + else: + assert isinstance(direct_url.info, DirInfo) + requirement += direct_url.url + if direct_url.subdirectory: + fragments.append("subdirectory=" + direct_url.subdirectory) + if fragments: + requirement += "#" + "&".join(fragments) + return requirement + + +def direct_url_for_editable(source_dir: str) -> DirectUrl: + return DirectUrl( + url=path_to_url(source_dir), + info=DirInfo(editable=True), + ) + + +def direct_url_from_link( + link: Link, source_dir: Optional[str] = None, link_is_in_wheel_cache: bool = False +) -> DirectUrl: + if link.is_vcs: + vcs_backend = vcs.get_backend_for_scheme(link.scheme) + assert vcs_backend + url, requested_revision, _ = vcs_backend.get_url_rev_and_auth( + link.url_without_fragment + ) + # For VCS links, we need to find out and add commit_id. + if link_is_in_wheel_cache: + # If the requested VCS link corresponds to a cached + # wheel, it means the requested revision was an + # immutable commit hash, otherwise it would not have + # been cached. In that case we don't have a source_dir + # with the VCS checkout. + assert requested_revision + commit_id = requested_revision + else: + # If the wheel was not in cache, it means we have + # had to checkout from VCS to build and we have a source_dir + # which we can inspect to find out the commit id. + assert source_dir + commit_id = vcs_backend.get_revision(source_dir) + return DirectUrl( + url=url, + info=VcsInfo( + vcs=vcs_backend.name, + commit_id=commit_id, + requested_revision=requested_revision, + ), + subdirectory=link.subdirectory_fragment, + ) + elif link.is_existing_dir(): + return DirectUrl( + url=link.url_without_fragment, + info=DirInfo(), + subdirectory=link.subdirectory_fragment, + ) + else: + hash = None + hash_name = link.hash_name + if hash_name: + hash = f"{hash_name}={link.hash}" + return DirectUrl( + url=link.url_without_fragment, + info=ArchiveInfo(hash=hash), + subdirectory=link.subdirectory_fragment, + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/egg_link.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/egg_link.py new file mode 100644 index 000000000..4a384a636 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/egg_link.py @@ -0,0 +1,80 @@ +import os +import re +import sys +from typing import List, Optional + +from pip._internal.locations import site_packages, user_site +from pip._internal.utils.virtualenv import ( + running_under_virtualenv, + virtualenv_no_global, +) + +__all__ = [ + "egg_link_path_from_sys_path", + "egg_link_path_from_location", +] + + +def _egg_link_names(raw_name: str) -> List[str]: + """ + Convert a Name metadata value to a .egg-link name, by applying + the same substitution as pkg_resources's safe_name function. + Note: we cannot use canonicalize_name because it has a different logic. + + We also look for the raw name (without normalization) as setuptools 69 changed + the way it names .egg-link files (https://github.com/pypa/setuptools/issues/4167). + """ + return [ + re.sub("[^A-Za-z0-9.]+", "-", raw_name) + ".egg-link", + f"{raw_name}.egg-link", + ] + + +def egg_link_path_from_sys_path(raw_name: str) -> Optional[str]: + """ + Look for a .egg-link file for project name, by walking sys.path. + """ + egg_link_names = _egg_link_names(raw_name) + for path_item in sys.path: + for egg_link_name in egg_link_names: + egg_link = os.path.join(path_item, egg_link_name) + if os.path.isfile(egg_link): + return egg_link + return None + + +def egg_link_path_from_location(raw_name: str) -> Optional[str]: + """ + Return the path for the .egg-link file if it exists, otherwise, None. + + There's 3 scenarios: + 1) not in a virtualenv + try to find in site.USER_SITE, then site_packages + 2) in a no-global virtualenv + try to find in site_packages + 3) in a yes-global virtualenv + try to find in site_packages, then site.USER_SITE + (don't look in global location) + + For #1 and #3, there could be odd cases, where there's an egg-link in 2 + locations. + + This method will just return the first one found. + """ + sites: List[str] = [] + if running_under_virtualenv(): + sites.append(site_packages) + if not virtualenv_no_global() and user_site: + sites.append(user_site) + else: + if user_site: + sites.append(user_site) + sites.append(site_packages) + + egg_link_names = _egg_link_names(raw_name) + for site in sites: + for egg_link_name in egg_link_names: + egglink = os.path.join(site, egg_link_name) + if os.path.isfile(egglink): + return egglink + return None diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/entrypoints.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/entrypoints.py new file mode 100644 index 000000000..150136938 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/entrypoints.py @@ -0,0 +1,84 @@ +import itertools +import os +import shutil +import sys +from typing import List, Optional + +from pip._internal.cli.main import main +from pip._internal.utils.compat import WINDOWS + +_EXECUTABLE_NAMES = [ + "pip", + f"pip{sys.version_info.major}", + f"pip{sys.version_info.major}.{sys.version_info.minor}", +] +if WINDOWS: + _allowed_extensions = {"", ".exe"} + _EXECUTABLE_NAMES = [ + "".join(parts) + for parts in itertools.product(_EXECUTABLE_NAMES, _allowed_extensions) + ] + + +def _wrapper(args: Optional[List[str]] = None) -> int: + """Central wrapper for all old entrypoints. + + Historically pip has had several entrypoints defined. Because of issues + arising from PATH, sys.path, multiple Pythons, their interactions, and most + of them having a pip installed, users suffer every time an entrypoint gets + moved. + + To alleviate this pain, and provide a mechanism for warning users and + directing them to an appropriate place for help, we now define all of + our old entrypoints as wrappers for the current one. + """ + sys.stderr.write( + "WARNING: pip is being invoked by an old script wrapper. This will " + "fail in a future version of pip.\n" + "Please see https://github.com/pypa/pip/issues/5599 for advice on " + "fixing the underlying issue.\n" + "To avoid this problem you can invoke Python with '-m pip' instead of " + "running pip directly.\n" + ) + return main(args) + + +def get_best_invocation_for_this_pip() -> str: + """Try to figure out the best way to invoke pip in the current environment.""" + binary_directory = "Scripts" if WINDOWS else "bin" + binary_prefix = os.path.join(sys.prefix, binary_directory) + + # Try to use pip[X[.Y]] names, if those executables for this environment are + # the first on PATH with that name. + path_parts = os.path.normcase(os.environ.get("PATH", "")).split(os.pathsep) + exe_are_in_PATH = os.path.normcase(binary_prefix) in path_parts + if exe_are_in_PATH: + for exe_name in _EXECUTABLE_NAMES: + found_executable = shutil.which(exe_name) + binary_executable = os.path.join(binary_prefix, exe_name) + if ( + found_executable + and os.path.exists(binary_executable) + and os.path.samefile( + found_executable, + binary_executable, + ) + ): + return exe_name + + # Use the `-m` invocation, if there's no "nice" invocation. + return f"{get_best_invocation_for_this_python()} -m pip" + + +def get_best_invocation_for_this_python() -> str: + """Try to figure out the best way to invoke the current Python.""" + exe = sys.executable + exe_name = os.path.basename(exe) + + # Try to use the basename, if it's the first executable. + found_executable = shutil.which(exe_name) + if found_executable and os.path.samefile(found_executable, exe): + return exe_name + + # Use the full executable name, because we couldn't find something simpler. + return exe diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/filesystem.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/filesystem.py new file mode 100644 index 000000000..22e356cdd --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/filesystem.py @@ -0,0 +1,149 @@ +import fnmatch +import os +import os.path +import random +import sys +from contextlib import contextmanager +from tempfile import NamedTemporaryFile +from typing import Any, BinaryIO, Generator, List, Union, cast + +from pip._internal.utils.compat import get_path_uid +from pip._internal.utils.misc import format_size +from pip._internal.utils.retry import retry + + +def check_path_owner(path: str) -> bool: + # If we don't have a way to check the effective uid of this process, then + # we'll just assume that we own the directory. + if sys.platform == "win32" or not hasattr(os, "geteuid"): + return True + + assert os.path.isabs(path) + + previous = None + while path != previous: + if os.path.lexists(path): + # Check if path is writable by current user. + if os.geteuid() == 0: + # Special handling for root user in order to handle properly + # cases where users use sudo without -H flag. + try: + path_uid = get_path_uid(path) + except OSError: + return False + return path_uid == 0 + else: + return os.access(path, os.W_OK) + else: + previous, path = path, os.path.dirname(path) + return False # assume we don't own the path + + +@contextmanager +def adjacent_tmp_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]: + """Return a file-like object pointing to a tmp file next to path. + + The file is created securely and is ensured to be written to disk + after the context reaches its end. + + kwargs will be passed to tempfile.NamedTemporaryFile to control + the way the temporary file will be opened. + """ + with NamedTemporaryFile( + delete=False, + dir=os.path.dirname(path), + prefix=os.path.basename(path), + suffix=".tmp", + **kwargs, + ) as f: + result = cast(BinaryIO, f) + try: + yield result + finally: + result.flush() + os.fsync(result.fileno()) + + +replace = retry(stop_after_delay=1, wait=0.25)(os.replace) + + +# test_writable_dir and _test_writable_dir_win are copied from Flit, +# with the author's agreement to also place them under pip's license. +def test_writable_dir(path: str) -> bool: + """Check if a directory is writable. + + Uses os.access() on POSIX, tries creating files on Windows. + """ + # If the directory doesn't exist, find the closest parent that does. + while not os.path.isdir(path): + parent = os.path.dirname(path) + if parent == path: + break # Should never get here, but infinite loops are bad + path = parent + + if os.name == "posix": + return os.access(path, os.W_OK) + + return _test_writable_dir_win(path) + + +def _test_writable_dir_win(path: str) -> bool: + # os.access doesn't work on Windows: http://bugs.python.org/issue2528 + # and we can't use tempfile: http://bugs.python.org/issue22107 + basename = "accesstest_deleteme_fishfingers_custard_" + alphabet = "abcdefghijklmnopqrstuvwxyz0123456789" + for _ in range(10): + name = basename + "".join(random.choice(alphabet) for _ in range(6)) + file = os.path.join(path, name) + try: + fd = os.open(file, os.O_RDWR | os.O_CREAT | os.O_EXCL) + except FileExistsError: + pass + except PermissionError: + # This could be because there's a directory with the same name. + # But it's highly unlikely there's a directory called that, + # so we'll assume it's because the parent dir is not writable. + # This could as well be because the parent dir is not readable, + # due to non-privileged user access. + return False + else: + os.close(fd) + os.unlink(file) + return True + + # This should never be reached + raise OSError("Unexpected condition testing for writable directory") + + +def find_files(path: str, pattern: str) -> List[str]: + """Returns a list of absolute paths of files beneath path, recursively, + with filenames which match the UNIX-style shell glob pattern.""" + result: List[str] = [] + for root, _, files in os.walk(path): + matches = fnmatch.filter(files, pattern) + result.extend(os.path.join(root, f) for f in matches) + return result + + +def file_size(path: str) -> Union[int, float]: + # If it's a symlink, return 0. + if os.path.islink(path): + return 0 + return os.path.getsize(path) + + +def format_file_size(path: str) -> str: + return format_size(file_size(path)) + + +def directory_size(path: str) -> Union[int, float]: + size = 0.0 + for root, _dirs, files in os.walk(path): + for filename in files: + file_path = os.path.join(root, filename) + size += file_size(file_path) + return size + + +def format_directory_size(path: str) -> str: + return format_size(directory_size(path)) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/filetypes.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/filetypes.py new file mode 100644 index 000000000..594857017 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/filetypes.py @@ -0,0 +1,27 @@ +"""Filetype information. +""" + +from typing import Tuple + +from pip._internal.utils.misc import splitext + +WHEEL_EXTENSION = ".whl" +BZ2_EXTENSIONS: Tuple[str, ...] = (".tar.bz2", ".tbz") +XZ_EXTENSIONS: Tuple[str, ...] = ( + ".tar.xz", + ".txz", + ".tlz", + ".tar.lz", + ".tar.lzma", +) +ZIP_EXTENSIONS: Tuple[str, ...] = (".zip", WHEEL_EXTENSION) +TAR_EXTENSIONS: Tuple[str, ...] = (".tar.gz", ".tgz", ".tar") +ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS + + +def is_archive_file(name: str) -> bool: + """Return True if `name` is a considered as an archive file.""" + ext = splitext(name)[1].lower() + if ext in ARCHIVE_EXTENSIONS: + return True + return False diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/glibc.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/glibc.py new file mode 100644 index 000000000..998868ff2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/glibc.py @@ -0,0 +1,101 @@ +import os +import sys +from typing import Optional, Tuple + + +def glibc_version_string() -> Optional[str]: + "Returns glibc version string, or None if not using glibc." + return glibc_version_string_confstr() or glibc_version_string_ctypes() + + +def glibc_version_string_confstr() -> Optional[str]: + "Primary implementation of glibc_version_string using os.confstr." + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module: + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 + if sys.platform == "win32": + return None + try: + gnu_libc_version = os.confstr("CS_GNU_LIBC_VERSION") + if gnu_libc_version is None: + return None + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17": + _, version = gnu_libc_version.split() + except (AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def glibc_version_string_ctypes() -> Optional[str]: + "Fallback implementation of glibc_version_string using ctypes." + + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can't proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# platform.libc_ver regularly returns completely nonsensical glibc +# versions. E.g. on my computer, platform says: +# +# ~$ python2.7 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.7') +# ~$ python3.5 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.9') +# +# But the truth is: +# +# ~$ ldd --version +# ldd (Debian GLIBC 2.22-11) 2.22 +# +# This is unfortunate, because it means that the linehaul data on libc +# versions that was generated by pip 8.1.2 and earlier is useless and +# misleading. Solution: instead of using platform, use our code that actually +# works. +def libc_ver() -> Tuple[str, str]: + """Try to determine the glibc version + + Returns a tuple of strings (lib, version) which default to empty strings + in case the lookup fails. + """ + glibc_version = glibc_version_string() + if glibc_version is None: + return ("", "") + else: + return ("glibc", glibc_version) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/hashes.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/hashes.py new file mode 100644 index 000000000..535e94fca --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/hashes.py @@ -0,0 +1,147 @@ +import hashlib +from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, NoReturn, Optional + +from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError +from pip._internal.utils.misc import read_chunks + +if TYPE_CHECKING: + from hashlib import _Hash + + +# The recommended hash algo of the moment. Change this whenever the state of +# the art changes; it won't hurt backward compatibility. +FAVORITE_HASH = "sha256" + + +# Names of hashlib algorithms allowed by the --hash option and ``pip hash`` +# Currently, those are the ones at least as collision-resistant as sha256. +STRONG_HASHES = ["sha256", "sha384", "sha512"] + + +class Hashes: + """A wrapper that builds multiple hashes at once and checks them against + known-good values + + """ + + def __init__(self, hashes: Optional[Dict[str, List[str]]] = None) -> None: + """ + :param hashes: A dict of algorithm names pointing to lists of allowed + hex digests + """ + allowed = {} + if hashes is not None: + for alg, keys in hashes.items(): + # Make sure values are always sorted (to ease equality checks) + allowed[alg] = [k.lower() for k in sorted(keys)] + self._allowed = allowed + + def __and__(self, other: "Hashes") -> "Hashes": + if not isinstance(other, Hashes): + return NotImplemented + + # If either of the Hashes object is entirely empty (i.e. no hash + # specified at all), all hashes from the other object are allowed. + if not other: + return self + if not self: + return other + + # Otherwise only hashes that present in both objects are allowed. + new = {} + for alg, values in other._allowed.items(): + if alg not in self._allowed: + continue + new[alg] = [v for v in values if v in self._allowed[alg]] + return Hashes(new) + + @property + def digest_count(self) -> int: + return sum(len(digests) for digests in self._allowed.values()) + + def is_hash_allowed(self, hash_name: str, hex_digest: str) -> bool: + """Return whether the given hex digest is allowed.""" + return hex_digest in self._allowed.get(hash_name, []) + + def check_against_chunks(self, chunks: Iterable[bytes]) -> None: + """Check good hashes against ones built from iterable of chunks of + data. + + Raise HashMismatch if none match. + + """ + gots = {} + for hash_name in self._allowed.keys(): + try: + gots[hash_name] = hashlib.new(hash_name) + except (ValueError, TypeError): + raise InstallationError(f"Unknown hash name: {hash_name}") + + for chunk in chunks: + for hash in gots.values(): + hash.update(chunk) + + for hash_name, got in gots.items(): + if got.hexdigest() in self._allowed[hash_name]: + return + self._raise(gots) + + def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn": + raise HashMismatch(self._allowed, gots) + + def check_against_file(self, file: BinaryIO) -> None: + """Check good hashes against a file-like object + + Raise HashMismatch if none match. + + """ + return self.check_against_chunks(read_chunks(file)) + + def check_against_path(self, path: str) -> None: + with open(path, "rb") as file: + return self.check_against_file(file) + + def has_one_of(self, hashes: Dict[str, str]) -> bool: + """Return whether any of the given hashes are allowed.""" + for hash_name, hex_digest in hashes.items(): + if self.is_hash_allowed(hash_name, hex_digest): + return True + return False + + def __bool__(self) -> bool: + """Return whether I know any known-good hashes.""" + return bool(self._allowed) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Hashes): + return NotImplemented + return self._allowed == other._allowed + + def __hash__(self) -> int: + return hash( + ",".join( + sorted( + ":".join((alg, digest)) + for alg, digest_list in self._allowed.items() + for digest in digest_list + ) + ) + ) + + +class MissingHashes(Hashes): + """A workalike for Hashes used when we're missing a hash for a requirement + + It computes the actual hash of the requirement and raises a HashMissing + exception showing it to the user. + + """ + + def __init__(self) -> None: + """Don't offer the ``hashes`` kwarg.""" + # Pass our favorite hash in to generate a "gotten hash". With the + # empty list, it will never match, so an error will always raise. + super().__init__(hashes={FAVORITE_HASH: []}) + + def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn": + raise HashMissing(gots[FAVORITE_HASH].hexdigest()) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/logging.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/logging.py new file mode 100644 index 000000000..62035fc40 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/logging.py @@ -0,0 +1,354 @@ +import contextlib +import errno +import logging +import logging.handlers +import os +import sys +import threading +from dataclasses import dataclass +from io import TextIOWrapper +from logging import Filter +from typing import Any, ClassVar, Generator, List, Optional, TextIO, Type + +from pip._vendor.rich.console import ( + Console, + ConsoleOptions, + ConsoleRenderable, + RenderableType, + RenderResult, + RichCast, +) +from pip._vendor.rich.highlighter import NullHighlighter +from pip._vendor.rich.logging import RichHandler +from pip._vendor.rich.segment import Segment +from pip._vendor.rich.style import Style + +from pip._internal.utils._log import VERBOSE, getLogger +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX +from pip._internal.utils.misc import ensure_dir + +_log_state = threading.local() +subprocess_logger = getLogger("pip.subprocessor") + + +class BrokenStdoutLoggingError(Exception): + """ + Raised if BrokenPipeError occurs for the stdout stream while logging. + """ + + +def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool: + if exc_class is BrokenPipeError: + return True + + # On Windows, a broken pipe can show up as EINVAL rather than EPIPE: + # https://bugs.python.org/issue19612 + # https://bugs.python.org/issue30418 + if not WINDOWS: + return False + + return isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE) + + +@contextlib.contextmanager +def indent_log(num: int = 2) -> Generator[None, None, None]: + """ + A context manager which will cause the log output to be indented for any + log messages emitted inside it. + """ + # For thread-safety + _log_state.indentation = get_indentation() + _log_state.indentation += num + try: + yield + finally: + _log_state.indentation -= num + + +def get_indentation() -> int: + return getattr(_log_state, "indentation", 0) + + +class IndentingFormatter(logging.Formatter): + default_time_format = "%Y-%m-%dT%H:%M:%S" + + def __init__( + self, + *args: Any, + add_timestamp: bool = False, + **kwargs: Any, + ) -> None: + """ + A logging.Formatter that obeys the indent_log() context manager. + + :param add_timestamp: A bool indicating output lines should be prefixed + with their record's timestamp. + """ + self.add_timestamp = add_timestamp + super().__init__(*args, **kwargs) + + def get_message_start(self, formatted: str, levelno: int) -> str: + """ + Return the start of the formatted log message (not counting the + prefix to add to each line). + """ + if levelno < logging.WARNING: + return "" + if formatted.startswith(DEPRECATION_MSG_PREFIX): + # Then the message already has a prefix. We don't want it to + # look like "WARNING: DEPRECATION: ...." + return "" + if levelno < logging.ERROR: + return "WARNING: " + + return "ERROR: " + + def format(self, record: logging.LogRecord) -> str: + """ + Calls the standard formatter, but will indent all of the log message + lines by our current indentation level. + """ + formatted = super().format(record) + message_start = self.get_message_start(formatted, record.levelno) + formatted = message_start + formatted + + prefix = "" + if self.add_timestamp: + prefix = f"{self.formatTime(record)} " + prefix += " " * get_indentation() + formatted = "".join([prefix + line for line in formatted.splitlines(True)]) + return formatted + + +@dataclass +class IndentedRenderable: + renderable: RenderableType + indent: int + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + segments = console.render(self.renderable, options) + lines = Segment.split_lines(segments) + for line in lines: + yield Segment(" " * self.indent) + yield from line + yield Segment("\n") + + +class PipConsole(Console): + def on_broken_pipe(self) -> None: + # Reraise the original exception, rich 13.8.0+ exits by default + # instead, preventing our handler from firing. + raise BrokenPipeError() from None + + +class RichPipStreamHandler(RichHandler): + KEYWORDS: ClassVar[Optional[List[str]]] = [] + + def __init__(self, stream: Optional[TextIO], no_color: bool) -> None: + super().__init__( + console=PipConsole(file=stream, no_color=no_color, soft_wrap=True), + show_time=False, + show_level=False, + show_path=False, + highlighter=NullHighlighter(), + ) + + # Our custom override on Rich's logger, to make things work as we need them to. + def emit(self, record: logging.LogRecord) -> None: + style: Optional[Style] = None + + # If we are given a diagnostic error to present, present it with indentation. + if getattr(record, "rich", False): + assert isinstance(record.args, tuple) + (rich_renderable,) = record.args + assert isinstance( + rich_renderable, (ConsoleRenderable, RichCast, str) + ), f"{rich_renderable} is not rich-console-renderable" + + renderable: RenderableType = IndentedRenderable( + rich_renderable, indent=get_indentation() + ) + else: + message = self.format(record) + renderable = self.render_message(record, message) + if record.levelno is not None: + if record.levelno >= logging.ERROR: + style = Style(color="red") + elif record.levelno >= logging.WARNING: + style = Style(color="yellow") + + try: + self.console.print(renderable, overflow="ignore", crop=False, style=style) + except Exception: + self.handleError(record) + + def handleError(self, record: logging.LogRecord) -> None: + """Called when logging is unable to log some output.""" + + exc_class, exc = sys.exc_info()[:2] + # If a broken pipe occurred while calling write() or flush() on the + # stdout stream in logging's Handler.emit(), then raise our special + # exception so we can handle it in main() instead of logging the + # broken pipe error and continuing. + if ( + exc_class + and exc + and self.console.file is sys.stdout + and _is_broken_pipe_error(exc_class, exc) + ): + raise BrokenStdoutLoggingError() + + return super().handleError(record) + + +class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler): + def _open(self) -> TextIOWrapper: + ensure_dir(os.path.dirname(self.baseFilename)) + return super()._open() + + +class MaxLevelFilter(Filter): + def __init__(self, level: int) -> None: + self.level = level + + def filter(self, record: logging.LogRecord) -> bool: + return record.levelno < self.level + + +class ExcludeLoggerFilter(Filter): + """ + A logging Filter that excludes records from a logger (or its children). + """ + + def filter(self, record: logging.LogRecord) -> bool: + # The base Filter class allows only records from a logger (or its + # children). + return not super().filter(record) + + +def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int: + """Configures and sets up all of the logging + + Returns the requested logging level, as its integer value. + """ + + # Determine the level to be logging at. + if verbosity >= 2: + level_number = logging.DEBUG + elif verbosity == 1: + level_number = VERBOSE + elif verbosity == -1: + level_number = logging.WARNING + elif verbosity == -2: + level_number = logging.ERROR + elif verbosity <= -3: + level_number = logging.CRITICAL + else: + level_number = logging.INFO + + level = logging.getLevelName(level_number) + + # The "root" logger should match the "console" level *unless* we also need + # to log to a user log file. + include_user_log = user_log_file is not None + if include_user_log: + additional_log_file = user_log_file + root_level = "DEBUG" + else: + additional_log_file = "/dev/null" + root_level = level + + # Disable any logging besides WARNING unless we have DEBUG level logging + # enabled for vendored libraries. + vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG" + + # Shorthands for clarity + log_streams = { + "stdout": "ext://sys.stdout", + "stderr": "ext://sys.stderr", + } + handler_classes = { + "stream": "pip._internal.utils.logging.RichPipStreamHandler", + "file": "pip._internal.utils.logging.BetterRotatingFileHandler", + } + handlers = ["console", "console_errors", "console_subprocess"] + ( + ["user_log"] if include_user_log else [] + ) + + logging.config.dictConfig( + { + "version": 1, + "disable_existing_loggers": False, + "filters": { + "exclude_warnings": { + "()": "pip._internal.utils.logging.MaxLevelFilter", + "level": logging.WARNING, + }, + "restrict_to_subprocess": { + "()": "logging.Filter", + "name": subprocess_logger.name, + }, + "exclude_subprocess": { + "()": "pip._internal.utils.logging.ExcludeLoggerFilter", + "name": subprocess_logger.name, + }, + }, + "formatters": { + "indent": { + "()": IndentingFormatter, + "format": "%(message)s", + }, + "indent_with_timestamp": { + "()": IndentingFormatter, + "format": "%(message)s", + "add_timestamp": True, + }, + }, + "handlers": { + "console": { + "level": level, + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stdout"], + "filters": ["exclude_subprocess", "exclude_warnings"], + "formatter": "indent", + }, + "console_errors": { + "level": "WARNING", + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stderr"], + "filters": ["exclude_subprocess"], + "formatter": "indent", + }, + # A handler responsible for logging to the console messages + # from the "subprocessor" logger. + "console_subprocess": { + "level": level, + "class": handler_classes["stream"], + "stream": log_streams["stderr"], + "no_color": no_color, + "filters": ["restrict_to_subprocess"], + "formatter": "indent", + }, + "user_log": { + "level": "DEBUG", + "class": handler_classes["file"], + "filename": additional_log_file, + "encoding": "utf-8", + "delay": True, + "formatter": "indent_with_timestamp", + }, + }, + "root": { + "level": root_level, + "handlers": handlers, + }, + "loggers": {"pip._vendor": {"level": vendored_log_level}}, + } + ) + + return level_number diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/misc.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/misc.py new file mode 100644 index 000000000..44f6a05fb --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/misc.py @@ -0,0 +1,773 @@ +import errno +import getpass +import hashlib +import logging +import os +import posixpath +import shutil +import stat +import sys +import sysconfig +import urllib.parse +from dataclasses import dataclass +from functools import partial +from io import StringIO +from itertools import filterfalse, tee, zip_longest +from pathlib import Path +from types import FunctionType, TracebackType +from typing import ( + Any, + BinaryIO, + Callable, + Generator, + Iterable, + Iterator, + List, + Mapping, + Optional, + Sequence, + TextIO, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +from pip._vendor.packaging.requirements import Requirement +from pip._vendor.pyproject_hooks import BuildBackendHookCaller + +from pip import __version__ +from pip._internal.exceptions import CommandError, ExternallyManagedEnvironment +from pip._internal.locations import get_major_minor_version +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.retry import retry +from pip._internal.utils.virtualenv import running_under_virtualenv + +__all__ = [ + "rmtree", + "display_path", + "backup_dir", + "ask", + "splitext", + "format_size", + "is_installable_dir", + "normalize_path", + "renames", + "get_prog", + "ensure_dir", + "remove_auth_from_url", + "check_externally_managed", + "ConfiguredBuildBackendHookCaller", +] + +logger = logging.getLogger(__name__) + +T = TypeVar("T") +ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType] +VersionInfo = Tuple[int, int, int] +NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]] +OnExc = Callable[[FunctionType, Path, BaseException], Any] +OnErr = Callable[[FunctionType, Path, ExcInfo], Any] + +FILE_CHUNK_SIZE = 1024 * 1024 + + +def get_pip_version() -> str: + pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") + pip_pkg_dir = os.path.abspath(pip_pkg_dir) + + return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})" + + +def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]: + """ + Convert a tuple of ints representing a Python version to one of length + three. + + :param py_version_info: a tuple of ints representing a Python version, + or None to specify no version. The tuple can have any length. + + :return: a tuple of length three if `py_version_info` is non-None. + Otherwise, return `py_version_info` unchanged (i.e. None). + """ + if len(py_version_info) < 3: + py_version_info += (3 - len(py_version_info)) * (0,) + elif len(py_version_info) > 3: + py_version_info = py_version_info[:3] + + return cast("VersionInfo", py_version_info) + + +def ensure_dir(path: str) -> None: + """os.path.makedirs without EEXIST.""" + try: + os.makedirs(path) + except OSError as e: + # Windows can raise spurious ENOTEMPTY errors. See #6426. + if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY: + raise + + +def get_prog() -> str: + try: + prog = os.path.basename(sys.argv[0]) + if prog in ("__main__.py", "-c"): + return f"{sys.executable} -m pip" + else: + return prog + except (AttributeError, TypeError, IndexError): + pass + return "pip" + + +# Retry every half second for up to 3 seconds +@retry(stop_after_delay=3, wait=0.5) +def rmtree( + dir: str, ignore_errors: bool = False, onexc: Optional[OnExc] = None +) -> None: + if ignore_errors: + onexc = _onerror_ignore + if onexc is None: + onexc = _onerror_reraise + handler: OnErr = partial(rmtree_errorhandler, onexc=onexc) + if sys.version_info >= (3, 12): + # See https://docs.python.org/3.12/whatsnew/3.12.html#shutil. + shutil.rmtree(dir, onexc=handler) # type: ignore + else: + shutil.rmtree(dir, onerror=handler) # type: ignore + + +def _onerror_ignore(*_args: Any) -> None: + pass + + +def _onerror_reraise(*_args: Any) -> None: + raise # noqa: PLE0704 - Bare exception used to reraise existing exception + + +def rmtree_errorhandler( + func: FunctionType, + path: Path, + exc_info: Union[ExcInfo, BaseException], + *, + onexc: OnExc = _onerror_reraise, +) -> None: + """ + `rmtree` error handler to 'force' a file remove (i.e. like `rm -f`). + + * If a file is readonly then it's write flag is set and operation is + retried. + + * `onerror` is the original callback from `rmtree(... onerror=onerror)` + that is chained at the end if the "rm -f" still fails. + """ + try: + st_mode = os.stat(path).st_mode + except OSError: + # it's equivalent to os.path.exists + return + + if not st_mode & stat.S_IWRITE: + # convert to read/write + try: + os.chmod(path, st_mode | stat.S_IWRITE) + except OSError: + pass + else: + # use the original function to repeat the operation + try: + func(path) + return + except OSError: + pass + + if not isinstance(exc_info, BaseException): + _, exc_info, _ = exc_info + onexc(func, path, exc_info) + + +def display_path(path: str) -> str: + """Gives the display value for a given path, making it relative to cwd + if possible.""" + path = os.path.normcase(os.path.abspath(path)) + if path.startswith(os.getcwd() + os.path.sep): + path = "." + path[len(os.getcwd()) :] + return path + + +def backup_dir(dir: str, ext: str = ".bak") -> str: + """Figure out the name of a directory to back up the given dir to + (adding .bak, .bak2, etc)""" + n = 1 + extension = ext + while os.path.exists(dir + extension): + n += 1 + extension = ext + str(n) + return dir + extension + + +def ask_path_exists(message: str, options: Iterable[str]) -> str: + for action in os.environ.get("PIP_EXISTS_ACTION", "").split(): + if action in options: + return action + return ask(message, options) + + +def _check_no_input(message: str) -> None: + """Raise an error if no input is allowed.""" + if os.environ.get("PIP_NO_INPUT"): + raise Exception( + f"No input was expected ($PIP_NO_INPUT set); question: {message}" + ) + + +def ask(message: str, options: Iterable[str]) -> str: + """Ask the message interactively, with the given possible responses""" + while 1: + _check_no_input(message) + response = input(message) + response = response.strip().lower() + if response not in options: + print( + "Your response ({!r}) was not one of the expected responses: " + "{}".format(response, ", ".join(options)) + ) + else: + return response + + +def ask_input(message: str) -> str: + """Ask for input interactively.""" + _check_no_input(message) + return input(message) + + +def ask_password(message: str) -> str: + """Ask for a password interactively.""" + _check_no_input(message) + return getpass.getpass(message) + + +def strtobool(val: str) -> int: + """Convert a string representation of truth to true (1) or false (0). + + 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. + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return 1 + elif val in ("n", "no", "f", "false", "off", "0"): + return 0 + else: + raise ValueError(f"invalid truth value {val!r}") + + +def format_size(bytes: float) -> str: + if bytes > 1000 * 1000: + return f"{bytes / 1000.0 / 1000:.1f} MB" + elif bytes > 10 * 1000: + return f"{int(bytes / 1000)} kB" + elif bytes > 1000: + return f"{bytes / 1000.0:.1f} kB" + else: + return f"{int(bytes)} bytes" + + +def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]: + """Return a list of formatted rows and a list of column sizes. + + For example:: + + >>> tabulate([['foobar', 2000], [0xdeadbeef]]) + (['foobar 2000', '3735928559'], [10, 4]) + """ + rows = [tuple(map(str, row)) for row in rows] + sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue="")] + table = [" ".join(map(str.ljust, row, sizes)).rstrip() for row in rows] + return table, sizes + + +def is_installable_dir(path: str) -> bool: + """Is path is a directory containing pyproject.toml or setup.py? + + If pyproject.toml exists, this is a PEP 517 project. Otherwise we look for + a legacy setuptools layout by identifying setup.py. We don't check for the + setup.cfg because using it without setup.py is only available for PEP 517 + projects, which are already covered by the pyproject.toml check. + """ + if not os.path.isdir(path): + return False + if os.path.isfile(os.path.join(path, "pyproject.toml")): + return True + if os.path.isfile(os.path.join(path, "setup.py")): + return True + return False + + +def read_chunks( + file: BinaryIO, size: int = FILE_CHUNK_SIZE +) -> Generator[bytes, None, None]: + """Yield pieces of data from a file-like object until EOF.""" + while True: + chunk = file.read(size) + if not chunk: + break + yield chunk + + +def normalize_path(path: str, resolve_symlinks: bool = True) -> str: + """ + Convert a path to its canonical, case-normalized, absolute version. + + """ + path = os.path.expanduser(path) + if resolve_symlinks: + path = os.path.realpath(path) + else: + path = os.path.abspath(path) + return os.path.normcase(path) + + +def splitext(path: str) -> Tuple[str, str]: + """Like os.path.splitext, but take off .tar too""" + base, ext = posixpath.splitext(path) + if base.lower().endswith(".tar"): + ext = base[-4:] + ext + base = base[:-4] + return base, ext + + +def renames(old: str, new: str) -> None: + """Like os.renames(), but handles renaming across devices.""" + # Implementation borrowed from os.renames(). + head, tail = os.path.split(new) + if head and tail and not os.path.exists(head): + os.makedirs(head) + + shutil.move(old, new) + + head, tail = os.path.split(old) + if head and tail: + try: + os.removedirs(head) + except OSError: + pass + + +def is_local(path: str) -> bool: + """ + Return True if path is within sys.prefix, if we're running in a virtualenv. + + If we're not in a virtualenv, all paths are considered "local." + + Caution: this function assumes the head of path has been normalized + with normalize_path. + """ + if not running_under_virtualenv(): + return True + return path.startswith(normalize_path(sys.prefix)) + + +def write_output(msg: Any, *args: Any) -> None: + logger.info(msg, *args) + + +class StreamWrapper(StringIO): + orig_stream: TextIO + + @classmethod + def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper": + ret = cls() + ret.orig_stream = orig_stream + return ret + + # compileall.compile_dir() needs stdout.encoding to print to stdout + # type ignore is because TextIOBase.encoding is writeable + @property + def encoding(self) -> str: # type: ignore + return self.orig_stream.encoding + + +# Simulates an enum +def enum(*sequential: Any, **named: Any) -> Type[Any]: + enums = dict(zip(sequential, range(len(sequential))), **named) + reverse = {value: key for key, value in enums.items()} + enums["reverse_mapping"] = reverse + return type("Enum", (), enums) + + +def build_netloc(host: str, port: Optional[int]) -> str: + """ + Build a netloc from a host-port pair + """ + if port is None: + return host + if ":" in host: + # Only wrap host with square brackets when it is IPv6 + host = f"[{host}]" + return f"{host}:{port}" + + +def build_url_from_netloc(netloc: str, scheme: str = "https") -> str: + """ + Build a full URL from a netloc. + """ + if netloc.count(":") >= 2 and "@" not in netloc and "[" not in netloc: + # It must be a bare IPv6 address, so wrap it with brackets. + netloc = f"[{netloc}]" + return f"{scheme}://{netloc}" + + +def parse_netloc(netloc: str) -> Tuple[Optional[str], Optional[int]]: + """ + Return the host-port pair from a netloc. + """ + url = build_url_from_netloc(netloc) + parsed = urllib.parse.urlparse(url) + return parsed.hostname, parsed.port + + +def split_auth_from_netloc(netloc: str) -> NetlocTuple: + """ + Parse out and remove the auth information from a netloc. + + Returns: (netloc, (username, password)). + """ + if "@" not in netloc: + return netloc, (None, None) + + # Split from the right because that's how urllib.parse.urlsplit() + # behaves if more than one @ is present (which can be checked using + # the password attribute of urlsplit()'s return value). + auth, netloc = netloc.rsplit("@", 1) + pw: Optional[str] = None + if ":" in auth: + # Split from the left because that's how urllib.parse.urlsplit() + # behaves if more than one : is present (which again can be checked + # using the password attribute of the return value) + user, pw = auth.split(":", 1) + else: + user, pw = auth, None + + user = urllib.parse.unquote(user) + if pw is not None: + pw = urllib.parse.unquote(pw) + + return netloc, (user, pw) + + +def redact_netloc(netloc: str) -> str: + """ + Replace the sensitive data in a netloc with "****", if it exists. + + For example: + - "user:pass@example.com" returns "user:****@example.com" + - "accesstoken@example.com" returns "****@example.com" + """ + netloc, (user, password) = split_auth_from_netloc(netloc) + if user is None: + return netloc + if password is None: + user = "****" + password = "" + else: + user = urllib.parse.quote(user) + password = ":****" + return f"{user}{password}@{netloc}" + + +def _transform_url( + url: str, transform_netloc: Callable[[str], Tuple[Any, ...]] +) -> Tuple[str, NetlocTuple]: + """Transform and replace netloc in a url. + + transform_netloc is a function taking the netloc and returning a + tuple. The first element of this tuple is the new netloc. The + entire tuple is returned. + + Returns a tuple containing the transformed url as item 0 and the + original tuple returned by transform_netloc as item 1. + """ + purl = urllib.parse.urlsplit(url) + netloc_tuple = transform_netloc(purl.netloc) + # stripped url + url_pieces = (purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment) + surl = urllib.parse.urlunsplit(url_pieces) + return surl, cast("NetlocTuple", netloc_tuple) + + +def _get_netloc(netloc: str) -> NetlocTuple: + return split_auth_from_netloc(netloc) + + +def _redact_netloc(netloc: str) -> Tuple[str]: + return (redact_netloc(netloc),) + + +def split_auth_netloc_from_url( + url: str, +) -> Tuple[str, str, Tuple[Optional[str], Optional[str]]]: + """ + Parse a url into separate netloc, auth, and url with no auth. + + Returns: (url_without_auth, netloc, (username, password)) + """ + url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc) + return url_without_auth, netloc, auth + + +def remove_auth_from_url(url: str) -> str: + """Return a copy of url with 'username:password@' removed.""" + # username/pass params are passed to subversion through flags + # and are not recognized in the url. + return _transform_url(url, _get_netloc)[0] + + +def redact_auth_from_url(url: str) -> str: + """Replace the password in a given url with ****.""" + return _transform_url(url, _redact_netloc)[0] + + +def redact_auth_from_requirement(req: Requirement) -> str: + """Replace the password in a given requirement url with ****.""" + if not req.url: + return str(req) + return str(req).replace(req.url, redact_auth_from_url(req.url)) + + +@dataclass(frozen=True) +class HiddenText: + secret: str + redacted: str + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self.redacted + + # This is useful for testing. + def __eq__(self, other: Any) -> bool: + if type(self) is not type(other): + return False + + # The string being used for redaction doesn't also have to match, + # just the raw, original string. + return self.secret == other.secret + + +def hide_value(value: str) -> HiddenText: + return HiddenText(value, redacted="****") + + +def hide_url(url: str) -> HiddenText: + redacted = redact_auth_from_url(url) + return HiddenText(url, redacted=redacted) + + +def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None: + """Protection of pip.exe from modification on Windows + + On Windows, any operation modifying pip should be run as: + python -m pip ... + """ + pip_names = [ + "pip", + f"pip{sys.version_info.major}", + f"pip{sys.version_info.major}.{sys.version_info.minor}", + ] + + # See https://github.com/pypa/pip/issues/1299 for more discussion + should_show_use_python_msg = ( + modifying_pip and WINDOWS and os.path.basename(sys.argv[0]) in pip_names + ) + + if should_show_use_python_msg: + new_command = [sys.executable, "-m", "pip"] + sys.argv[1:] + raise CommandError( + "To modify pip, please run the following command:\n{}".format( + " ".join(new_command) + ) + ) + + +def check_externally_managed() -> None: + """Check whether the current environment is externally managed. + + If the ``EXTERNALLY-MANAGED`` config file is found, the current environment + is considered externally managed, and an ExternallyManagedEnvironment is + raised. + """ + if running_under_virtualenv(): + return + marker = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") + if not os.path.isfile(marker): + return + raise ExternallyManagedEnvironment.from_config(marker) + + +def is_console_interactive() -> bool: + """Is this console interactive?""" + return sys.stdin is not None and sys.stdin.isatty() + + +def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]: + """Return (hash, length) for path using hashlib.sha256()""" + + h = hashlib.sha256() + length = 0 + with open(path, "rb") as f: + for block in read_chunks(f, size=blocksize): + length += len(block) + h.update(block) + return h, length + + +def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]: + """ + Return paired elements. + + For example: + s -> (s0, s1), (s2, s3), (s4, s5), ... + """ + iterable = iter(iterable) + return zip_longest(iterable, iterable) + + +def partition( + pred: Callable[[T], bool], iterable: Iterable[T] +) -> Tuple[Iterable[T], Iterable[T]]: + """ + Use a predicate to partition entries into false entries and true entries, + like + + partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 + """ + t1, t2 = tee(iterable) + return filterfalse(pred, t1), filter(pred, t2) + + +class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller): + def __init__( + self, + config_holder: Any, + source_dir: str, + build_backend: str, + backend_path: Optional[str] = None, + runner: Optional[Callable[..., None]] = None, + python_executable: Optional[str] = None, + ): + super().__init__( + source_dir, build_backend, backend_path, runner, python_executable + ) + self.config_holder = config_holder + + def build_wheel( + self, + wheel_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + metadata_directory: Optional[str] = None, + ) -> str: + cs = self.config_holder.config_settings + return super().build_wheel( + wheel_directory, config_settings=cs, metadata_directory=metadata_directory + ) + + def build_sdist( + self, + sdist_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + ) -> str: + cs = self.config_holder.config_settings + return super().build_sdist(sdist_directory, config_settings=cs) + + def build_editable( + self, + wheel_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + metadata_directory: Optional[str] = None, + ) -> str: + cs = self.config_holder.config_settings + return super().build_editable( + wheel_directory, config_settings=cs, metadata_directory=metadata_directory + ) + + def get_requires_for_build_wheel( + self, config_settings: Optional[Mapping[str, Any]] = None + ) -> Sequence[str]: + cs = self.config_holder.config_settings + return super().get_requires_for_build_wheel(config_settings=cs) + + def get_requires_for_build_sdist( + self, config_settings: Optional[Mapping[str, Any]] = None + ) -> Sequence[str]: + cs = self.config_holder.config_settings + return super().get_requires_for_build_sdist(config_settings=cs) + + def get_requires_for_build_editable( + self, config_settings: Optional[Mapping[str, Any]] = None + ) -> Sequence[str]: + cs = self.config_holder.config_settings + return super().get_requires_for_build_editable(config_settings=cs) + + def prepare_metadata_for_build_wheel( + self, + metadata_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + _allow_fallback: bool = True, + ) -> str: + cs = self.config_holder.config_settings + return super().prepare_metadata_for_build_wheel( + metadata_directory=metadata_directory, + config_settings=cs, + _allow_fallback=_allow_fallback, + ) + + def prepare_metadata_for_build_editable( + self, + metadata_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + _allow_fallback: bool = True, + ) -> Optional[str]: + cs = self.config_holder.config_settings + return super().prepare_metadata_for_build_editable( + metadata_directory=metadata_directory, + config_settings=cs, + _allow_fallback=_allow_fallback, + ) + + +def warn_if_run_as_root() -> None: + """Output a warning for sudo users on Unix. + + In a virtual environment, sudo pip still writes to virtualenv. + On Windows, users may run pip as Administrator without issues. + This warning only applies to Unix root users outside of virtualenv. + """ + if running_under_virtualenv(): + return + if not hasattr(os, "getuid"): + return + # On Windows, there are no "system managed" Python packages. Installing as + # Administrator via pip is the correct way of updating system environments. + # + # We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform + # checks: https://mypy.readthedocs.io/en/stable/common_issues.html + if sys.platform == "win32" or sys.platform == "cygwin": + return + + if os.getuid() != 0: + return + + logger.warning( + "Running pip as the 'root' user can result in broken permissions and " + "conflicting behaviour with the system package manager, possibly " + "rendering your system unusable. " + "It is recommended to use a virtual environment instead: " + "https://pip.pypa.io/warnings/venv. " + "Use the --root-user-action option if you know what you are doing and " + "want to suppress this warning." + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/packaging.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/packaging.py new file mode 100644 index 000000000..caad70f7f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/packaging.py @@ -0,0 +1,58 @@ +import functools +import logging +import re +from typing import NewType, Optional, Tuple, cast + +from pip._vendor.packaging import specifiers, version +from pip._vendor.packaging.requirements import Requirement + +NormalizedExtra = NewType("NormalizedExtra", str) + +logger = logging.getLogger(__name__) + + +@functools.lru_cache(maxsize=32) +def check_requires_python( + requires_python: Optional[str], version_info: Tuple[int, ...] +) -> bool: + """ + Check if the given Python version matches a "Requires-Python" specifier. + + :param version_info: A 3-tuple of ints representing a Python + major-minor-micro version to check (e.g. `sys.version_info[:3]`). + + :return: `True` if the given Python version satisfies the requirement. + Otherwise, return `False`. + + :raises InvalidSpecifier: If `requires_python` has an invalid format. + """ + if requires_python is None: + # The package provides no information + return True + requires_python_specifier = specifiers.SpecifierSet(requires_python) + + python_version = version.parse(".".join(map(str, version_info))) + return python_version in requires_python_specifier + + +@functools.lru_cache(maxsize=2048) +def get_requirement(req_string: str) -> Requirement: + """Construct a packaging.Requirement object with caching""" + # Parsing requirement strings is expensive, and is also expected to happen + # with a low diversity of different arguments (at least relative the number + # constructed). This method adds a cache to requirement object creation to + # minimize repeated parsing of the same string to construct equivalent + # Requirement objects. + return Requirement(req_string) + + +def safe_extra(extra: str) -> NormalizedExtra: + """Convert an arbitrary string to a standard 'extra' name + + Any runs of non-alphanumeric characters are replaced with a single '_', + and the result is always lowercased. + + This function is duplicated from ``pkg_resources``. Note that this is not + the same to either ``canonicalize_name`` or ``_egg_link_name``. + """ + return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower()) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/retry.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/retry.py new file mode 100644 index 000000000..abfe07286 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/retry.py @@ -0,0 +1,42 @@ +import functools +from time import perf_counter, sleep +from typing import Callable, TypeVar + +from pip._vendor.typing_extensions import ParamSpec + +T = TypeVar("T") +P = ParamSpec("P") + + +def retry( + wait: float, stop_after_delay: float +) -> Callable[[Callable[P, T]], Callable[P, T]]: + """Decorator to automatically retry a function on error. + + If the function raises, the function is recalled with the same arguments + until it returns or the time limit is reached. When the time limit is + surpassed, the last exception raised is reraised. + + :param wait: The time to wait after an error before retrying, in seconds. + :param stop_after_delay: The time limit after which retries will cease, + in seconds. + """ + + def wrapper(func: Callable[P, T]) -> Callable[P, T]: + + @functools.wraps(func) + def retry_wrapped(*args: P.args, **kwargs: P.kwargs) -> T: + # The performance counter is monotonic on all platforms we care + # about and has much better resolution than time.monotonic(). + start_time = perf_counter() + while True: + try: + return func(*args, **kwargs) + except Exception: + if perf_counter() - start_time > stop_after_delay: + raise + sleep(wait) + + return retry_wrapped + + return wrapper diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/setuptools_build.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/setuptools_build.py new file mode 100644 index 000000000..96d1b2460 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/setuptools_build.py @@ -0,0 +1,146 @@ +import sys +import textwrap +from typing import List, Optional, Sequence + +# Shim to wrap setup.py invocation with setuptools +# Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on +# Windows are correctly handled (it should be "C:\\Users" not "C:\Users"). +_SETUPTOOLS_SHIM = textwrap.dedent( + """ + exec(compile(''' + # This is -- a caller that pip uses to run setup.py + # + # - It imports setuptools before invoking setup.py, to enable projects that directly + # import from `distutils.core` to work with newer packaging standards. + # - It provides a clear error message when setuptools is not installed. + # - It sets `sys.argv[0]` to the underlying `setup.py`, when invoking `setup.py` so + # setuptools doesn't think the script is `-c`. This avoids the following warning: + # manifest_maker: standard file '-c' not found". + # - It generates a shim setup.py, for handling setup.cfg-only projects. + import os, sys, tokenize + + try: + import setuptools + except ImportError as error: + print( + "ERROR: Can not execute `setup.py` since setuptools is not available in " + "the build environment.", + file=sys.stderr, + ) + sys.exit(1) + + __file__ = %r + sys.argv[0] = __file__ + + if os.path.exists(__file__): + filename = __file__ + with tokenize.open(__file__) as f: + setup_py_code = f.read() + else: + filename = "" + setup_py_code = "from setuptools import setup; setup()" + + exec(compile(setup_py_code, filename, "exec")) + ''' % ({!r},), "", "exec")) + """ +).rstrip() + + +def make_setuptools_shim_args( + setup_py_path: str, + global_options: Optional[Sequence[str]] = None, + no_user_config: bool = False, + unbuffered_output: bool = False, +) -> List[str]: + """ + Get setuptools command arguments with shim wrapped setup file invocation. + + :param setup_py_path: The path to setup.py to be wrapped. + :param global_options: Additional global options. + :param no_user_config: If True, disables personal user configuration. + :param unbuffered_output: If True, adds the unbuffered switch to the + argument list. + """ + args = [sys.executable] + if unbuffered_output: + args += ["-u"] + args += ["-c", _SETUPTOOLS_SHIM.format(setup_py_path)] + if global_options: + args += global_options + if no_user_config: + args += ["--no-user-cfg"] + return args + + +def make_setuptools_bdist_wheel_args( + setup_py_path: str, + global_options: Sequence[str], + build_options: Sequence[str], + destination_dir: str, +) -> List[str]: + # NOTE: Eventually, we'd want to also -S to the flags here, when we're + # isolating. Currently, it breaks Python in virtualenvs, because it + # relies on site.py to find parts of the standard library outside the + # virtualenv. + args = make_setuptools_shim_args( + setup_py_path, global_options=global_options, unbuffered_output=True + ) + args += ["bdist_wheel", "-d", destination_dir] + args += build_options + return args + + +def make_setuptools_clean_args( + setup_py_path: str, + global_options: Sequence[str], +) -> List[str]: + args = make_setuptools_shim_args( + setup_py_path, global_options=global_options, unbuffered_output=True + ) + args += ["clean", "--all"] + return args + + +def make_setuptools_develop_args( + setup_py_path: str, + *, + global_options: Sequence[str], + no_user_config: bool, + prefix: Optional[str], + home: Optional[str], + use_user_site: bool, +) -> List[str]: + assert not (use_user_site and prefix) + + args = make_setuptools_shim_args( + setup_py_path, + global_options=global_options, + no_user_config=no_user_config, + ) + + args += ["develop", "--no-deps"] + + if prefix: + args += ["--prefix", prefix] + if home is not None: + args += ["--install-dir", home] + + if use_user_site: + args += ["--user", "--prefix="] + + return args + + +def make_setuptools_egg_info_args( + setup_py_path: str, + egg_info_dir: Optional[str], + no_user_config: bool, +) -> List[str]: + args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config) + + args += ["egg_info"] + + if egg_info_dir: + args += ["--egg-base", egg_info_dir] + + return args diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/subprocess.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/subprocess.py new file mode 100644 index 000000000..cb2e23f00 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/subprocess.py @@ -0,0 +1,245 @@ +import logging +import os +import shlex +import subprocess +from typing import Any, Callable, Iterable, List, Literal, Mapping, Optional, Union + +from pip._vendor.rich.markup import escape + +from pip._internal.cli.spinners import SpinnerInterface, open_spinner +from pip._internal.exceptions import InstallationSubprocessError +from pip._internal.utils.logging import VERBOSE, subprocess_logger +from pip._internal.utils.misc import HiddenText + +CommandArgs = List[Union[str, HiddenText]] + + +def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs: + """ + Create a CommandArgs object. + """ + command_args: CommandArgs = [] + for arg in args: + # Check for list instead of CommandArgs since CommandArgs is + # only known during type-checking. + if isinstance(arg, list): + command_args.extend(arg) + else: + # Otherwise, arg is str or HiddenText. + command_args.append(arg) + + return command_args + + +def format_command_args(args: Union[List[str], CommandArgs]) -> str: + """ + Format command arguments for display. + """ + # For HiddenText arguments, display the redacted form by calling str(). + # Also, we don't apply str() to arguments that aren't HiddenText since + # this can trigger a UnicodeDecodeError in Python 2 if the argument + # has type unicode and includes a non-ascii character. (The type + # checker doesn't ensure the annotations are correct in all cases.) + return " ".join( + shlex.quote(str(arg)) if isinstance(arg, HiddenText) else shlex.quote(arg) + for arg in args + ) + + +def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]: + """ + Return the arguments in their raw, unredacted form. + """ + return [arg.secret if isinstance(arg, HiddenText) else arg for arg in args] + + +def call_subprocess( + cmd: Union[List[str], CommandArgs], + show_stdout: bool = False, + cwd: Optional[str] = None, + on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise", + extra_ok_returncodes: Optional[Iterable[int]] = None, + extra_environ: Optional[Mapping[str, Any]] = None, + unset_environ: Optional[Iterable[str]] = None, + spinner: Optional[SpinnerInterface] = None, + log_failed_cmd: Optional[bool] = True, + stdout_only: Optional[bool] = False, + *, + command_desc: str, +) -> str: + """ + Args: + show_stdout: if true, use INFO to log the subprocess's stderr and + stdout streams. Otherwise, use DEBUG. Defaults to False. + extra_ok_returncodes: an iterable of integer return codes that are + acceptable, in addition to 0. Defaults to None, which means []. + unset_environ: an iterable of environment variable names to unset + prior to calling subprocess.Popen(). + log_failed_cmd: if false, failed commands are not logged, only raised. + stdout_only: if true, return only stdout, else return both. When true, + logging of both stdout and stderr occurs when the subprocess has + terminated, else logging occurs as subprocess output is produced. + """ + if extra_ok_returncodes is None: + extra_ok_returncodes = [] + if unset_environ is None: + unset_environ = [] + # Most places in pip use show_stdout=False. What this means is-- + # + # - We connect the child's output (combined stderr and stdout) to a + # single pipe, which we read. + # - We log this output to stderr at DEBUG level as it is received. + # - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't + # requested), then we show a spinner so the user can still see the + # subprocess is in progress. + # - If the subprocess exits with an error, we log the output to stderr + # at ERROR level if it hasn't already been displayed to the console + # (e.g. if --verbose logging wasn't enabled). This way we don't log + # the output to the console twice. + # + # If show_stdout=True, then the above is still done, but with DEBUG + # replaced by INFO. + if show_stdout: + # Then log the subprocess output at INFO level. + log_subprocess: Callable[..., None] = subprocess_logger.info + used_level = logging.INFO + else: + # Then log the subprocess output using VERBOSE. This also ensures + # it will be logged to the log file (aka user_log), if enabled. + log_subprocess = subprocess_logger.verbose + used_level = VERBOSE + + # Whether the subprocess will be visible in the console. + showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level + + # Only use the spinner if we're not showing the subprocess output + # and we have a spinner. + use_spinner = not showing_subprocess and spinner is not None + + log_subprocess("Running command %s", command_desc) + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + for name in unset_environ: + env.pop(name, None) + try: + proc = subprocess.Popen( + # Convert HiddenText objects to the underlying str. + reveal_command_args(cmd), + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT if not stdout_only else subprocess.PIPE, + cwd=cwd, + env=env, + errors="backslashreplace", + ) + except Exception as exc: + if log_failed_cmd: + subprocess_logger.critical( + "Error %s while executing command %s", + exc, + command_desc, + ) + raise + all_output = [] + if not stdout_only: + assert proc.stdout + assert proc.stdin + proc.stdin.close() + # In this mode, stdout and stderr are in the same pipe. + while True: + line: str = proc.stdout.readline() + if not line: + break + line = line.rstrip() + all_output.append(line + "\n") + + # Show the line immediately. + log_subprocess(line) + # Update the spinner. + if use_spinner: + assert spinner + spinner.spin() + try: + proc.wait() + finally: + if proc.stdout: + proc.stdout.close() + output = "".join(all_output) + else: + # In this mode, stdout and stderr are in different pipes. + # We must use communicate() which is the only safe way to read both. + out, err = proc.communicate() + # log line by line to preserve pip log indenting + for out_line in out.splitlines(): + log_subprocess(out_line) + all_output.append(out) + for err_line in err.splitlines(): + log_subprocess(err_line) + all_output.append(err) + output = out + + proc_had_error = proc.returncode and proc.returncode not in extra_ok_returncodes + if use_spinner: + assert spinner + if proc_had_error: + spinner.finish("error") + else: + spinner.finish("done") + if proc_had_error: + if on_returncode == "raise": + error = InstallationSubprocessError( + command_description=command_desc, + exit_code=proc.returncode, + output_lines=all_output if not showing_subprocess else None, + ) + if log_failed_cmd: + subprocess_logger.error("%s", error, extra={"rich": True}) + subprocess_logger.verbose( + "[bold magenta]full command[/]: [blue]%s[/]", + escape(format_command_args(cmd)), + extra={"markup": True}, + ) + subprocess_logger.verbose( + "[bold magenta]cwd[/]: %s", + escape(cwd or "[inherit]"), + extra={"markup": True}, + ) + + raise error + elif on_returncode == "warn": + subprocess_logger.warning( + 'Command "%s" had error code %s in %s', + command_desc, + proc.returncode, + cwd, + ) + elif on_returncode == "ignore": + pass + else: + raise ValueError(f"Invalid value: on_returncode={on_returncode!r}") + return output + + +def runner_with_spinner_message(message: str) -> Callable[..., None]: + """Provide a subprocess_runner that shows a spinner message. + + Intended for use with for BuildBackendHookCaller. Thus, the runner has + an API that matches what's expected by BuildBackendHookCaller.subprocess_runner. + """ + + def runner( + cmd: List[str], + cwd: Optional[str] = None, + extra_environ: Optional[Mapping[str, Any]] = None, + ) -> None: + with open_spinner(message) as spinner: + call_subprocess( + cmd, + command_desc=message, + cwd=cwd, + extra_environ=extra_environ, + spinner=spinner, + ) + + return runner diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/temp_dir.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/temp_dir.py new file mode 100644 index 000000000..06668e8ab --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/temp_dir.py @@ -0,0 +1,296 @@ +import errno +import itertools +import logging +import os.path +import tempfile +import traceback +from contextlib import ExitStack, contextmanager +from pathlib import Path +from typing import ( + Any, + Callable, + Dict, + Generator, + List, + Optional, + TypeVar, + Union, +) + +from pip._internal.utils.misc import enum, rmtree + +logger = logging.getLogger(__name__) + +_T = TypeVar("_T", bound="TempDirectory") + + +# Kinds of temporary directories. Only needed for ones that are +# globally-managed. +tempdir_kinds = enum( + BUILD_ENV="build-env", + EPHEM_WHEEL_CACHE="ephem-wheel-cache", + REQ_BUILD="req-build", +) + + +_tempdir_manager: Optional[ExitStack] = None + + +@contextmanager +def global_tempdir_manager() -> Generator[None, None, None]: + global _tempdir_manager + with ExitStack() as stack: + old_tempdir_manager, _tempdir_manager = _tempdir_manager, stack + try: + yield + finally: + _tempdir_manager = old_tempdir_manager + + +class TempDirectoryTypeRegistry: + """Manages temp directory behavior""" + + def __init__(self) -> None: + self._should_delete: Dict[str, bool] = {} + + def set_delete(self, kind: str, value: bool) -> None: + """Indicate whether a TempDirectory of the given kind should be + auto-deleted. + """ + self._should_delete[kind] = value + + def get_delete(self, kind: str) -> bool: + """Get configured auto-delete flag for a given TempDirectory type, + default True. + """ + return self._should_delete.get(kind, True) + + +_tempdir_registry: Optional[TempDirectoryTypeRegistry] = None + + +@contextmanager +def tempdir_registry() -> Generator[TempDirectoryTypeRegistry, None, None]: + """Provides a scoped global tempdir registry that can be used to dictate + whether directories should be deleted. + """ + global _tempdir_registry + old_tempdir_registry = _tempdir_registry + _tempdir_registry = TempDirectoryTypeRegistry() + try: + yield _tempdir_registry + finally: + _tempdir_registry = old_tempdir_registry + + +class _Default: + pass + + +_default = _Default() + + +class TempDirectory: + """Helper class that owns and cleans up a temporary directory. + + This class can be used as a context manager or as an OO representation of a + temporary directory. + + Attributes: + path + Location to the created temporary directory + delete + Whether the directory should be deleted when exiting + (when used as a contextmanager) + + Methods: + cleanup() + Deletes the temporary directory + + When used as a context manager, if the delete attribute is True, on + exiting the context the temporary directory is deleted. + """ + + def __init__( + self, + path: Optional[str] = None, + delete: Union[bool, None, _Default] = _default, + kind: str = "temp", + globally_managed: bool = False, + ignore_cleanup_errors: bool = True, + ): + super().__init__() + + if delete is _default: + if path is not None: + # If we were given an explicit directory, resolve delete option + # now. + delete = False + else: + # Otherwise, we wait until cleanup and see what + # tempdir_registry says. + delete = None + + # The only time we specify path is in for editables where it + # is the value of the --src option. + if path is None: + path = self._create(kind) + + self._path = path + self._deleted = False + self.delete = delete + self.kind = kind + self.ignore_cleanup_errors = ignore_cleanup_errors + + if globally_managed: + assert _tempdir_manager is not None + _tempdir_manager.enter_context(self) + + @property + def path(self) -> str: + assert not self._deleted, f"Attempted to access deleted path: {self._path}" + return self._path + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.path!r}>" + + def __enter__(self: _T) -> _T: + return self + + def __exit__(self, exc: Any, value: Any, tb: Any) -> None: + if self.delete is not None: + delete = self.delete + elif _tempdir_registry: + delete = _tempdir_registry.get_delete(self.kind) + else: + delete = True + + if delete: + self.cleanup() + + def _create(self, kind: str) -> str: + """Create a temporary directory and store its path in self.path""" + # We realpath here because some systems have their default tmpdir + # symlinked to another directory. This tends to confuse build + # scripts, so we canonicalize the path by traversing potential + # symlinks here. + path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-")) + logger.debug("Created temporary directory: %s", path) + return path + + def cleanup(self) -> None: + """Remove the temporary directory created and reset state""" + self._deleted = True + if not os.path.exists(self._path): + return + + errors: List[BaseException] = [] + + def onerror( + func: Callable[..., Any], + path: Path, + exc_val: BaseException, + ) -> None: + """Log a warning for a `rmtree` error and continue""" + formatted_exc = "\n".join( + traceback.format_exception_only(type(exc_val), exc_val) + ) + formatted_exc = formatted_exc.rstrip() # remove trailing new line + if func in (os.unlink, os.remove, os.rmdir): + logger.debug( + "Failed to remove a temporary file '%s' due to %s.\n", + path, + formatted_exc, + ) + else: + logger.debug("%s failed with %s.", func.__qualname__, formatted_exc) + errors.append(exc_val) + + if self.ignore_cleanup_errors: + try: + # first try with @retry; retrying to handle ephemeral errors + rmtree(self._path, ignore_errors=False) + except OSError: + # last pass ignore/log all errors + rmtree(self._path, onexc=onerror) + if errors: + logger.warning( + "Failed to remove contents in a temporary directory '%s'.\n" + "You can safely remove it manually.", + self._path, + ) + else: + rmtree(self._path) + + +class AdjacentTempDirectory(TempDirectory): + """Helper class that creates a temporary directory adjacent to a real one. + + Attributes: + original + The original directory to create a temp directory for. + path + After calling create() or entering, contains the full + path to the temporary directory. + delete + Whether the directory should be deleted when exiting + (when used as a contextmanager) + + """ + + # The characters that may be used to name the temp directory + # We always prepend a ~ and then rotate through these until + # a usable name is found. + # pkg_resources raises a different error for .dist-info folder + # with leading '-' and invalid metadata + LEADING_CHARS = "-~.=%0123456789" + + def __init__(self, original: str, delete: Optional[bool] = None) -> None: + self.original = original.rstrip("/\\") + super().__init__(delete=delete) + + @classmethod + def _generate_names(cls, name: str) -> Generator[str, None, None]: + """Generates a series of temporary names. + + The algorithm replaces the leading characters in the name + with ones that are valid filesystem characters, but are not + valid package names (for both Python and pip definitions of + package). + """ + for i in range(1, len(name)): + for candidate in itertools.combinations_with_replacement( + cls.LEADING_CHARS, i - 1 + ): + new_name = "~" + "".join(candidate) + name[i:] + if new_name != name: + yield new_name + + # If we make it this far, we will have to make a longer name + for i in range(len(cls.LEADING_CHARS)): + for candidate in itertools.combinations_with_replacement( + cls.LEADING_CHARS, i + ): + new_name = "~" + "".join(candidate) + name + if new_name != name: + yield new_name + + def _create(self, kind: str) -> str: + root, name = os.path.split(self.original) + for candidate in self._generate_names(name): + path = os.path.join(root, candidate) + try: + os.mkdir(path) + except OSError as ex: + # Continue if the name exists already + if ex.errno != errno.EEXIST: + raise + else: + path = os.path.realpath(path) + break + else: + # Final fallback on the default behavior. + path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-")) + + logger.debug("Created temporary directory: %s", path) + return path diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/unpacking.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/unpacking.py new file mode 100644 index 000000000..87a6d19ab --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/unpacking.py @@ -0,0 +1,337 @@ +"""Utilities related archives. +""" + +import logging +import os +import shutil +import stat +import sys +import tarfile +import zipfile +from typing import Iterable, List, Optional +from zipfile import ZipInfo + +from pip._internal.exceptions import InstallationError +from pip._internal.utils.filetypes import ( + BZ2_EXTENSIONS, + TAR_EXTENSIONS, + XZ_EXTENSIONS, + ZIP_EXTENSIONS, +) +from pip._internal.utils.misc import ensure_dir + +logger = logging.getLogger(__name__) + + +SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS + +try: + import bz2 # noqa + + SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS +except ImportError: + logger.debug("bz2 module is not available") + +try: + # Only for Python 3.3+ + import lzma # noqa + + SUPPORTED_EXTENSIONS += XZ_EXTENSIONS +except ImportError: + logger.debug("lzma module is not available") + + +def current_umask() -> int: + """Get the current umask which involves having to set it temporarily.""" + mask = os.umask(0) + os.umask(mask) + return mask + + +def split_leading_dir(path: str) -> List[str]: + path = path.lstrip("/").lstrip("\\") + if "/" in path and ( + ("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path + ): + return path.split("/", 1) + elif "\\" in path: + return path.split("\\", 1) + else: + return [path, ""] + + +def has_leading_dir(paths: Iterable[str]) -> bool: + """Returns true if all the paths have the same leading path name + (i.e., everything is in one subdirectory in an archive)""" + common_prefix = None + for path in paths: + prefix, rest = split_leading_dir(path) + if not prefix: + return False + elif common_prefix is None: + common_prefix = prefix + elif prefix != common_prefix: + return False + return True + + +def is_within_directory(directory: str, target: str) -> bool: + """ + Return true if the absolute path of target is within the directory + """ + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) + + prefix = os.path.commonprefix([abs_directory, abs_target]) + return prefix == abs_directory + + +def _get_default_mode_plus_executable() -> int: + return 0o777 & ~current_umask() | 0o111 + + +def set_extracted_file_to_default_mode_plus_executable(path: str) -> None: + """ + Make file present at path have execute for user/group/world + (chmod +x) is no-op on windows per python docs + """ + os.chmod(path, _get_default_mode_plus_executable()) + + +def zip_item_is_executable(info: ZipInfo) -> bool: + mode = info.external_attr >> 16 + # if mode and regular file and any execute permissions for + # user/group/world? + return bool(mode and stat.S_ISREG(mode) and mode & 0o111) + + +def unzip_file(filename: str, location: str, flatten: bool = True) -> None: + """ + Unzip the file (with path `filename`) to the destination `location`. All + files are written based on system defaults and umask (i.e. permissions are + not preserved), except that regular file members with any execute + permissions (user, group, or world) have "chmod +x" applied after being + written. Note that for windows, any execute changes using os.chmod are + no-ops per the python docs. + """ + ensure_dir(location) + zipfp = open(filename, "rb") + try: + zip = zipfile.ZipFile(zipfp, allowZip64=True) + leading = has_leading_dir(zip.namelist()) and flatten + for info in zip.infolist(): + name = info.filename + fn = name + if leading: + fn = split_leading_dir(name)[1] + fn = os.path.join(location, fn) + dir = os.path.dirname(fn) + if not is_within_directory(location, fn): + message = ( + "The zip file ({}) has a file ({}) trying to install " + "outside target directory ({})" + ) + raise InstallationError(message.format(filename, fn, location)) + if fn.endswith("/") or fn.endswith("\\"): + # A directory + ensure_dir(fn) + else: + ensure_dir(dir) + # Don't use read() to avoid allocating an arbitrarily large + # chunk of memory for the file's content + fp = zip.open(name) + try: + with open(fn, "wb") as destfp: + shutil.copyfileobj(fp, destfp) + finally: + fp.close() + if zip_item_is_executable(info): + set_extracted_file_to_default_mode_plus_executable(fn) + finally: + zipfp.close() + + +def untar_file(filename: str, location: str) -> None: + """ + Untar the file (with path `filename`) to the destination `location`. + All files are written based on system defaults and umask (i.e. permissions + are not preserved), except that regular file members with any execute + permissions (user, group, or world) have "chmod +x" applied on top of the + default. Note that for windows, any execute changes using os.chmod are + no-ops per the python docs. + """ + ensure_dir(location) + if filename.lower().endswith(".gz") or filename.lower().endswith(".tgz"): + mode = "r:gz" + elif filename.lower().endswith(BZ2_EXTENSIONS): + mode = "r:bz2" + elif filename.lower().endswith(XZ_EXTENSIONS): + mode = "r:xz" + elif filename.lower().endswith(".tar"): + mode = "r" + else: + logger.warning( + "Cannot determine compression type for file %s", + filename, + ) + mode = "r:*" + + tar = tarfile.open(filename, mode, encoding="utf-8") # type: ignore + try: + leading = has_leading_dir([member.name for member in tar.getmembers()]) + + # PEP 706 added `tarfile.data_filter`, and made some other changes to + # Python's tarfile module (see below). The features were backported to + # security releases. + try: + data_filter = tarfile.data_filter + except AttributeError: + _untar_without_filter(filename, location, tar, leading) + else: + default_mode_plus_executable = _get_default_mode_plus_executable() + + if leading: + # Strip the leading directory from all files in the archive, + # including hardlink targets (which are relative to the + # unpack location). + for member in tar.getmembers(): + name_lead, name_rest = split_leading_dir(member.name) + member.name = name_rest + if member.islnk(): + lnk_lead, lnk_rest = split_leading_dir(member.linkname) + if lnk_lead == name_lead: + member.linkname = lnk_rest + + def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo: + orig_mode = member.mode + try: + try: + member = data_filter(member, location) + except tarfile.LinkOutsideDestinationError: + if sys.version_info[:3] in { + (3, 8, 17), + (3, 9, 17), + (3, 10, 12), + (3, 11, 4), + }: + # The tarfile filter in specific Python versions + # raises LinkOutsideDestinationError on valid input + # (https://github.com/python/cpython/issues/107845) + # Ignore the error there, but do use the + # more lax `tar_filter` + member = tarfile.tar_filter(member, location) + else: + raise + except tarfile.TarError as exc: + message = "Invalid member in the tar file {}: {}" + # Filter error messages mention the member name. + # No need to add it here. + raise InstallationError( + message.format( + filename, + exc, + ) + ) + if member.isfile() and orig_mode & 0o111: + member.mode = default_mode_plus_executable + else: + # See PEP 706 note above. + # The PEP changed this from `int` to `Optional[int]`, + # where None means "use the default". Mypy doesn't + # know this yet. + member.mode = None # type: ignore [assignment] + return member + + tar.extractall(location, filter=pip_filter) + + finally: + tar.close() + + +def _untar_without_filter( + filename: str, + location: str, + tar: tarfile.TarFile, + leading: bool, +) -> None: + """Fallback for Python without tarfile.data_filter""" + for member in tar.getmembers(): + fn = member.name + if leading: + fn = split_leading_dir(fn)[1] + path = os.path.join(location, fn) + if not is_within_directory(location, path): + message = ( + "The tar file ({}) has a file ({}) trying to install " + "outside target directory ({})" + ) + raise InstallationError(message.format(filename, path, location)) + if member.isdir(): + ensure_dir(path) + elif member.issym(): + try: + tar._extract_member(member, path) + except Exception as exc: + # Some corrupt tar files seem to produce this + # (specifically bad symlinks) + logger.warning( + "In the tar file %s the member %s is invalid: %s", + filename, + member.name, + exc, + ) + continue + else: + try: + fp = tar.extractfile(member) + except (KeyError, AttributeError) as exc: + # Some corrupt tar files seem to produce this + # (specifically bad symlinks) + logger.warning( + "In the tar file %s the member %s is invalid: %s", + filename, + member.name, + exc, + ) + continue + ensure_dir(os.path.dirname(path)) + assert fp is not None + with open(path, "wb") as destfp: + shutil.copyfileobj(fp, destfp) + fp.close() + # Update the timestamp (useful for cython compiled files) + tar.utime(member, path) + # member have any execute permissions for user/group/world? + if member.mode & 0o111: + set_extracted_file_to_default_mode_plus_executable(path) + + +def unpack_file( + filename: str, + location: str, + content_type: Optional[str] = None, +) -> None: + filename = os.path.realpath(filename) + if ( + content_type == "application/zip" + or filename.lower().endswith(ZIP_EXTENSIONS) + or zipfile.is_zipfile(filename) + ): + unzip_file(filename, location, flatten=not filename.endswith(".whl")) + elif ( + content_type == "application/x-gzip" + or tarfile.is_tarfile(filename) + or filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS) + ): + untar_file(filename, location) + else: + # FIXME: handle? + # FIXME: magic signatures? + logger.critical( + "Cannot unpack file %s (downloaded from %s, content-type: %s); " + "cannot detect archive format", + filename, + location, + content_type, + ) + raise InstallationError(f"Cannot determine archive format of {location}") diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/urls.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/urls.py new file mode 100644 index 000000000..9f34f882a --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/urls.py @@ -0,0 +1,55 @@ +import os +import string +import urllib.parse +import urllib.request + +from .compat import WINDOWS + + +def path_to_url(path: str) -> str: + """ + Convert a path to a file: URL. The path will be made absolute and have + quoted path parts. + """ + path = os.path.normpath(os.path.abspath(path)) + url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path)) + return url + + +def url_to_path(url: str) -> str: + """ + Convert a file: URL to a path. + """ + assert url.startswith( + "file:" + ), f"You can only turn file: urls into filenames (not {url!r})" + + _, netloc, path, _, _ = urllib.parse.urlsplit(url) + + if not netloc or netloc == "localhost": + # According to RFC 8089, same as empty authority. + netloc = "" + elif WINDOWS: + # If we have a UNC path, prepend UNC share notation. + netloc = "\\\\" + netloc + else: + raise ValueError( + f"non-local file URIs are not supported on this platform: {url!r}" + ) + + path = urllib.request.url2pathname(netloc + path) + + # On Windows, urlsplit parses the path as something like "/C:/Users/foo". + # This creates issues for path-related functions like io.open(), so we try + # to detect and strip the leading slash. + if ( + WINDOWS + and not netloc # Not UNC. + and len(path) >= 3 + and path[0] == "/" # Leading slash to strip. + and path[1] in string.ascii_letters # Drive letter. + and path[2:4] in (":", ":/") # Colon + end of string, or colon + absolute path. + ): + path = path[1:] + + return path diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/virtualenv.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/virtualenv.py new file mode 100644 index 000000000..882e36f5c --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/virtualenv.py @@ -0,0 +1,104 @@ +import logging +import os +import re +import site +import sys +from typing import List, Optional + +logger = logging.getLogger(__name__) +_INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile( + r"include-system-site-packages\s*=\s*(?Ptrue|false)" +) + + +def _running_under_venv() -> bool: + """Checks if sys.base_prefix and sys.prefix match. + + This handles PEP 405 compliant virtual environments. + """ + return sys.prefix != getattr(sys, "base_prefix", sys.prefix) + + +def _running_under_legacy_virtualenv() -> bool: + """Checks if sys.real_prefix is set. + + This handles virtual environments created with pypa's virtualenv. + """ + # pypa/virtualenv case + return hasattr(sys, "real_prefix") + + +def running_under_virtualenv() -> bool: + """True if we're running inside a virtual environment, False otherwise.""" + return _running_under_venv() or _running_under_legacy_virtualenv() + + +def _get_pyvenv_cfg_lines() -> Optional[List[str]]: + """Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines + + Returns None, if it could not read/access the file. + """ + pyvenv_cfg_file = os.path.join(sys.prefix, "pyvenv.cfg") + try: + # Although PEP 405 does not specify, the built-in venv module always + # writes with UTF-8. (pypa/pip#8717) + with open(pyvenv_cfg_file, encoding="utf-8") as f: + return f.read().splitlines() # avoids trailing newlines + except OSError: + return None + + +def _no_global_under_venv() -> bool: + """Check `{sys.prefix}/pyvenv.cfg` for system site-packages inclusion + + PEP 405 specifies that when system site-packages are not supposed to be + visible from a virtual environment, `pyvenv.cfg` must contain the following + line: + + include-system-site-packages = false + + Additionally, log a warning if accessing the file fails. + """ + cfg_lines = _get_pyvenv_cfg_lines() + if cfg_lines is None: + # We're not in a "sane" venv, so assume there is no system + # site-packages access (since that's PEP 405's default state). + logger.warning( + "Could not access 'pyvenv.cfg' despite a virtual environment " + "being active. Assuming global site-packages is not accessible " + "in this environment." + ) + return True + + for line in cfg_lines: + match = _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX.match(line) + if match is not None and match.group("value") == "false": + return True + return False + + +def _no_global_under_legacy_virtualenv() -> bool: + """Check if "no-global-site-packages.txt" exists beside site.py + + This mirrors logic in pypa/virtualenv for determining whether system + site-packages are visible in the virtual environment. + """ + site_mod_dir = os.path.dirname(os.path.abspath(site.__file__)) + no_global_site_packages_file = os.path.join( + site_mod_dir, + "no-global-site-packages.txt", + ) + return os.path.exists(no_global_site_packages_file) + + +def virtualenv_no_global() -> bool: + """Returns a boolean, whether running in venv with no system site-packages.""" + # PEP 405 compliance needs to be checked first since virtualenv >=20 would + # return True for both checks, but is only able to use the PEP 405 config. + if _running_under_venv(): + return _no_global_under_venv() + + if _running_under_legacy_virtualenv(): + return _no_global_under_legacy_virtualenv() + + return False diff --git a/venv/lib/python3.8/site-packages/pip/_internal/utils/wheel.py b/venv/lib/python3.8/site-packages/pip/_internal/utils/wheel.py new file mode 100644 index 000000000..f85aee8a3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/utils/wheel.py @@ -0,0 +1,134 @@ +"""Support functions for working with wheel files. +""" + +import logging +from email.message import Message +from email.parser import Parser +from typing import Tuple +from zipfile import BadZipFile, ZipFile + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.exceptions import UnsupportedWheel + +VERSION_COMPATIBLE = (1, 0) + + +logger = logging.getLogger(__name__) + + +def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]: + """Extract information from the provided wheel, ensuring it meets basic + standards. + + Returns the name of the .dist-info directory and the parsed WHEEL metadata. + """ + try: + info_dir = wheel_dist_info_dir(wheel_zip, name) + metadata = wheel_metadata(wheel_zip, info_dir) + version = wheel_version(metadata) + except UnsupportedWheel as e: + raise UnsupportedWheel(f"{name} has an invalid wheel, {e}") + + check_compatibility(version, name) + + return info_dir, metadata + + +def wheel_dist_info_dir(source: ZipFile, name: str) -> str: + """Returns the name of the contained .dist-info directory. + + Raises AssertionError or UnsupportedWheel if not found, >1 found, or + it doesn't match the provided name. + """ + # Zip file path separators must be / + subdirs = {p.split("/", 1)[0] for p in source.namelist()} + + info_dirs = [s for s in subdirs if s.endswith(".dist-info")] + + if not info_dirs: + raise UnsupportedWheel(".dist-info directory not found") + + if len(info_dirs) > 1: + raise UnsupportedWheel( + "multiple .dist-info directories found: {}".format(", ".join(info_dirs)) + ) + + info_dir = info_dirs[0] + + info_dir_name = canonicalize_name(info_dir) + canonical_name = canonicalize_name(name) + if not info_dir_name.startswith(canonical_name): + raise UnsupportedWheel( + f".dist-info directory {info_dir!r} does not start with {canonical_name!r}" + ) + + return info_dir + + +def read_wheel_metadata_file(source: ZipFile, path: str) -> bytes: + try: + return source.read(path) + # BadZipFile for general corruption, KeyError for missing entry, + # and RuntimeError for password-protected files + except (BadZipFile, KeyError, RuntimeError) as e: + raise UnsupportedWheel(f"could not read {path!r} file: {e!r}") + + +def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message: + """Return the WHEEL metadata of an extracted wheel, if possible. + Otherwise, raise UnsupportedWheel. + """ + path = f"{dist_info_dir}/WHEEL" + # Zip file path separators must be / + wheel_contents = read_wheel_metadata_file(source, path) + + try: + wheel_text = wheel_contents.decode() + except UnicodeDecodeError as e: + raise UnsupportedWheel(f"error decoding {path!r}: {e!r}") + + # FeedParser (used by Parser) does not raise any exceptions. The returned + # message may have .defects populated, but for backwards-compatibility we + # currently ignore them. + return Parser().parsestr(wheel_text) + + +def wheel_version(wheel_data: Message) -> Tuple[int, ...]: + """Given WHEEL metadata, return the parsed Wheel-Version. + Otherwise, raise UnsupportedWheel. + """ + version_text = wheel_data["Wheel-Version"] + if version_text is None: + raise UnsupportedWheel("WHEEL is missing Wheel-Version") + + version = version_text.strip() + + try: + return tuple(map(int, version.split("."))) + except ValueError: + raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}") + + +def check_compatibility(version: Tuple[int, ...], name: str) -> None: + """Raises errors or warns if called with an incompatible Wheel-Version. + + pip should refuse to install a Wheel-Version that's a major series + ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when + installing a version only minor version ahead (e.g 1.2 > 1.1). + + version: a 2-tuple representing a Wheel-Version (Major, Minor) + name: name of wheel or package to raise exception about + + :raises UnsupportedWheel: when an incompatible Wheel-Version is given + """ + if version[0] > VERSION_COMPATIBLE[0]: + raise UnsupportedWheel( + "{}'s Wheel-Version ({}) is not compatible with this version " + "of pip".format(name, ".".join(map(str, version))) + ) + elif version > VERSION_COMPATIBLE: + logger.warning( + "Installing from a newer Wheel-Version (%s)", + ".".join(map(str, version)), + ) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/__init__.py b/venv/lib/python3.8/site-packages/pip/_internal/vcs/__init__.py new file mode 100644 index 000000000..b6beddbe6 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/vcs/__init__.py @@ -0,0 +1,15 @@ +# Expose a limited set of classes and functions so callers outside of +# the vcs package don't need to import deeper than `pip._internal.vcs`. +# (The test directory may still need to import from a vcs sub-package.) +# Import all vcs modules to register each VCS in the VcsSupport object. +import pip._internal.vcs.bazaar +import pip._internal.vcs.git +import pip._internal.vcs.mercurial +import pip._internal.vcs.subversion # noqa: F401 +from pip._internal.vcs.versioncontrol import ( # noqa: F401 + RemoteNotFoundError, + RemoteNotValidError, + is_url, + make_vcs_requirement_url, + vcs, +) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06da3272c8eb8bbd4a9674065d8cf6c9a51c1957 GIT binary patch literal 535 zcmZWlJx{|h5Ote2O^Z4qq)ITbc1S#QVL*rhm{144hAfej9IaJ54i_hg{v-YZGrz(s z6MumTyF{o`&hmS{_wFqF9;ed@vGMUq`9q(OFPH3x)ySpYm4lq*Jwf8$j{7ZpEf03w z7endCz8u9P8N`7c$74B(Cq3JFPv+jYy@f32LvwfsQfXK!eWU6kzeZHJCq6O3ThZrL zQll_4$u^KQS(PBX)Eq%Vp?8jF#rc7mmAs4+UTDCAiO7mk%C?N5@vLKxy7r1|bKI3B zU{)h%!kl!?s!kigimSq$cHP#>RH0D`bkjr6!N9@Lfp6iR;MAQR{Jhe&l0ab#8xDeA zK?6dS?sxQ-r4=pT^jZ~Rxd@*TAmtEhWZ4FgHc&KF@U$&iL}#?(8p6`rYz4NJm-K%k e)J}vZHsv}=qVmlI=ThahfD8AoDzFgr{?;#sR;Bm= literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9cfe3dc28d50959d754b578dcc64e0552e9b516 GIT binary patch literal 3560 zcmZ`+-Etg96`r2Ion5Wu)t_bACbfV=SYm0R0tL8I6tRPoRJlZv7%rv)!)UvAccS@O zch5@E)anMgD%^w@z);eqUx8~bc!s{bqdEMc9jh}BaTu6<PDq8F?-h9|i&Q)N12rJs=8J#UQU3-ZtdMalv^fhM zZig;+LXW$lFB&2cO%ZHb+2*1J(scz}K+vI=^c6y*f9x z!LOOI4Kr3ty2dx3IQ%-lVP-bJvRo^?4$s`w{@u8WW2v3pN>=VRTJ7)3I3J9AjVeI* z-Emn?s^&SRss@otjq6!8#H<-2!l$`(Qcn#N<61|GeeFw8PGvq|bCxFBM!kos0yLb> ztdR}1?48Qql25H3lgk*FtLM%0X@6W~qAx13mx#3gq1YE`G08+;_8-K%sy{g_$3?z1 zd2eedh1gBRRw?7bo{;^0k?;4@WcQ5az3rd%RZ@zr38v$bz|v&WkCMC;63FZW8U0;j zn(fJRN5X?bn!g08f_-K;8jX^#Uy}4EB|7?hCnF zK!roO0x1KJWStc1(q)NXZ3L_ssgjDjB)+7>?lf*Vu@Y z@tqy*tAhj@K4sb&9B>%$yI5dMgPlm{%iCRqh&iC_f#v6@HAGppc&2bIb}+9O8J()p z>vIt2dL2V24w!NGYxl^VxqzN`%H$pBjC=>Jog%iihtE0?k=Zck55Cy13b$}`9(buP+*a9n`$N7$n00xqelHKNR^n^Pqu zE>ficrDhZjQjk$ppPNEK{us?Cs3rqj&I)S*_SKEKly-S5T-QNb3}U*&>)bVe?pNSc zK`=ptv(Y@5?3sPSPOT5E-#>&EoD&v&rscB*sZU*JKgHaB$l{A zE(YXv;tmY7fT~HFlxdO+1qYNenHW=prg6O%B`P9okUJ)n&kK1dDS=CRssV#PA(~=Q zyRf9vF3HmDOYPEvO9<3UkTsLr?F_l2Xq|+EX`pf>%c-bdxY)7U_lpSTmrm zjl5PSgG}IQ!?gnpO4?kmQ$x=Nxj-@;yj|q?sY}V+-+t^)+X0 z(PpXVnt-k0W@+QQ8MK_vAN~hKfNd)P literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/git.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/git.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06c4456331635128d681843cf5061304cafc001a GIT binary patch literal 12537 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 AZ2$lO literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c10af9b2d35fd6f61879f7d67292e196c999454 GIT binary patch literal 8540 zcmds6+m9R9d7m>g91gi$?p1q{SJGIPZP6BY<+x2;RaTjlnnY#J@zt4bDQNlK!#^(_^z@ z;@1qVhF!56nM$VNR2)WotT5YfD{doK$u;tod}FFI)hJX7dd&`}8^uboF;kh*<4ic) zn5)b+<}34!g~~!>v9j1WRXN2BVf2^8@)4`72(!P+9rRA?UKYLQbk9X^P4{x>o%xo* z^L*-|!KcLHp;b96PW8`;vquc?Vec8G!V_R(Y?dr#zZ1lXa=+6`f~e_-Ds#Kt3PmZS z^4|_P7tPz^L81ygh+Cn5P;L3ij#>)hYAtHUQ7EcGGZE6SCBdFh^Oz5sapH%ezlAj( zNVSA>K~nYG$xgK{qeitUk}#^3Of_{QYBc;Nzb?09RcQFT0zmZ)UhZDUfw!CWNG;qF zjVKYDQF1eCH~BZEjHJrn5_{>5V^#b>$QXCO5j7JTh05Bi#hvN44BtF|@els+{Kv1B z&Ua1`wl#(d5ikoV|8&{gu+y4eHg1QsbZ8U=aU;8Q=^_ z0JzU87B?$4w<;NKR~(TQuE_DsBa=Hk`_MjQmAsh3TtT?226r)^<9R(lt@pZo3UdWM zt>=pAoS4Ch88J&|(zb4{8uakuBkNP6&iD+UeOLhWIemimr@QCt7Iw`&GWD)`zMyw4 z;0{A*i-38GFFvyPDZZrNx-@o^Wxk^4mIrXtQ!D!2bkkLSTCcC_^>j7Ip98ctenzjH z=3l|Pa=67=er|9Nk3au#8WKArR`_`h>xD;*U*Io3v?0AGXJ6tkWA+U0NP8~w5=Li% z-=a8&(`WfBk23sKehIT)I*YmTBb%@D4a_}%V(v134Rhx|HEg5u0_6X7m4C0jwUJV)9jMiKCu)bh>T_PjDj`*ENl_0TT%hY~yv)vpV?wTf z=>b9xnkG--V2ubc{7|0&X@a=uaMm@pO|Zz^?3iV#DN$nbG?tF3B3Cd}bK4@RZuzmO z0(8}f9l40<;l0+z?)B70+{c9(^2z9>mm231X}E(&U+VAC;jCvSMxS-r5qrz{xPQm$ zSv|X#=^FRUuDzR;m$!|s%}s9p(7bBgFLv#|MQdbl6XTxU&vf;eTSr#U(KAjLRy@9g zy+%LVwT{>wvzP6;*vEEF`J--@o4dLAlf?Z2L*HIDdRB9-o0Wgo&GvJ&KmMDO?@aPt zv+Ew3J@bAsnd+K{Wal%TTX}ErxuwO?OLm0U@E-(?cEcMQKd&B14`$mVF^)ahS%I;) z4XfVthIASMlEk!4m4yt-K*V3LjZ#6GQLOBJKipMW*!`y85TpQ&2!6rVUc+owLk2b;jzlK$egKbS;lj|;M3!=L884S>Xp}p=zOqRQ zl#Lh1id9v9aI{*gq}y<8bCKm)fth&bS-~{TqUoZa zXT`y6XL&-zhEU*Eoz%}MCx$_3h*ZWb0zCLop9}!~#%IxV;X}arrNlVYlGZhkpazNA zWtx*_=g&tBxV;m^9w;kizy%+s|`?ua%A3*?X;vFEJiQ6q9)dEp8 z#lH%O+WAY;sgjj)?{)O$Z=re2RIXaBg?M4LnlgqoTYe4A+jwHqLc?{fqS-k+LF|ba zkc9!CpH}7}ku{;$0d7cOsGQYYr~~-L}3I^aV+1UNp0RWroW5f_wdAI`wVLy z;^nf=xd~h+UjtgZL#8Qr$gihyC!FcIfkzw1ZKG#)4Y^3dMt1gVGzQ)n<99G7>kF*& z$AUrq(Oc;9Ai|sYk=M{b-5$W_XahW{T+=$P(-eYvzKh*)0gYkX%!U@>F*_?`$e!4K z!gG=&5Gyq*x6$cpn@rjfe-(WzVfRc}U9eHJ&w?)52`n!Vg(krcJ3nCeZEbC_2iBJC zx6O@RSH4SYT|0F;zkh?Ap4W^LSVQQ;yV}8eEg9VtHO#?M!la-Z$pi1j*t-iE4V_kGz6n%i=kmR>?ra>gv&bR>iA>d*kI zX(&oE8cLEcWt03(lD(MPGzy$aL_=(h$z&4UT}^q{vKdU;ukDOu7HQ7xtd8;FT70P9!DOnLM|90%0DH2(?^ zAr86zJTGF+L5Ph+Dp}|+(%uuWWanLAdWz5TIcSf?5oYtn&#YcPSpwvI3K=4n`<%s> zlI0UnPid$tfQo$twda0lG(8)RU^o9clOaQ-h;+@^Mu60FdhSO?)BK*%b+PtIvI#p z%c$ML=v^AI#spsS7N-9KkB;vNA!Ut>wW_a*_ zy`m(GDBq{%Luy9(gN~H-mHu}8N~P0k1>yhtDEwa+4{9QH_wri=;n-kNIHawXd=Wim zM=fyHmIw$Wc{^nj6DUM$$yaEc*C~G(G)0VvfYN_8qa!_FKX8}`i-a$0J1YNyAGU=K z_~Zw4cuMaiharV>$p9l|k!nh|o5S*eTGV@prqyGR@?|qOEGGE5LCNC_s>KQwy(pRu-a-_CvfNehZCZ7EA<-@ZyW6ZQATCThc)! zwRsrcBHA_6%#s;4nfYrcL*Znet*}`(YdS2CQ=RAj2id2J4HuxdO#$1p!%C8MZJo(q zrU_=}rR(9oe-L{JLD~rPCScA#j3JEuCfjIKLFcXmWYf^it>WFdQ583_V-eQ%&dJbhAE*D3 z-mZ&-V3@<4tYdZ-#;_X#T1SfdRBlUFl#H6@#V6>%R+DE$p>UgVi`zT{UtxC5G_i~? zaOXW(|1{+1*&m|J)3=ULEP^kiT{cH)k4AlqlW(y)^GPOgv{xlBXv2Rj*+$~-=H-Lb zdwgUNOn42l%~SpwMB}?{fxu-<04S`dVXZAGe1Y{G6!Bm^k5Tn%VSv^3{3zD;|8ojkKhA#e2IxI*jCrqPr26$0s|}0Dozy8D1w> zE)On#^R}#jgIW<%C;J5}E4Ge=G@yC1gmROb8XA=&`A?s)$p$}+Z zaR7c$S5eu)SQUl~+PD@04k%U!lauN=3vU>Xi}^nTc1${J6m2B0usr4pTZF}#hqZA} zTA0bRPq`q{LM_7Q(b3||c5`x;dJtFSstDvLVKS-+5Floz{~5$=tlBWR^-vW4w{ix~ zJwsZbl1y*Mhj~MW@ACsBy(nDVhomDkzci9E->dmZ$GNvhUdP)HQ1O8Lz!pJzM!WtW z$~8=`+ynRIAE7~C%ia{#^u9!*W+(v|TmN^QOYLAjZ#$@iIIMH}m<8(-+rM0fD8(Vk zi3=2vkIN70T^V_vTQs{3$tv0Yjbk|)mLk%E264=bnqX%bqVf@q+o@#7Pf}BaY>fm5 zsW-43WAN=A5sOiA138=Lw-Bb(=uZos-_fahL}eASWkZ`2x3|dWp`w8b87?pqn4pRG zz^Ldt#@KtvsFl58L3y-2ouKEV^(dhTyCO9JoaA=WPn22C*+qs4dEX zVgG^iY06)4EC@~xqGNW>Pl(Q-YCe7t7E*^FI?0`)KyCn0<-pCY2&L?U)a8=C8^sYPfm230W%M&gbk;$O z!bsizYwITl(oCw=A`PXq3=-;Wc9w6@{{a+YDXXMYQnYh-UVD1WCtBB6nwZMfFmrCJKWP3p&c%{(4iZXY# z+K6}?|Fl*SiCcBU|O~-&3|E4&FwPll?ea&Pm_UYo%+R|dt!%q5Qy|A*F*U(Fz%2J7rN^_J6 zD--FclN_{YLK8((f#@-rNVk(9jMp1MTuZ4!lAop{WSn#zT%A3cd#6X>Ur=PM2^5Jn zCCL;h@8nLnF#d(;qJ>0gy?_!|7Va4f94N2FKo)l}rJL2A?gEnqJ2T{PFSvuw@Ho0RC{Yt4S& J_`Y%RzX5(nmBau5 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf6e49bda4d5fd787c59a72fc997908c4d9999a9 GIT binary patch literal 21084 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/bazaar.py b/venv/lib/python3.8/site-packages/pip/_internal/vcs/bazaar.py new file mode 100644 index 000000000..c754b7cc5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/vcs/bazaar.py @@ -0,0 +1,112 @@ +import logging +from typing import List, Optional, Tuple + +from pip._internal.utils.misc import HiddenText, display_path +from pip._internal.utils.subprocess import make_command +from pip._internal.utils.urls import path_to_url +from pip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RevOptions, + VersionControl, + vcs, +) + +logger = logging.getLogger(__name__) + + +class Bazaar(VersionControl): + name = "bzr" + dirname = ".bzr" + repo_name = "branch" + schemes = ( + "bzr+http", + "bzr+https", + "bzr+ssh", + "bzr+sftp", + "bzr+ftp", + "bzr+lp", + "bzr+file", + ) + + @staticmethod + def get_base_rev_args(rev: str) -> List[str]: + return ["-r", rev] + + def fetch_new( + self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int + ) -> None: + rev_display = rev_options.to_display() + logger.info( + "Checking out %s%s to %s", + url, + rev_display, + display_path(dest), + ) + if verbosity <= 0: + flags = ["--quiet"] + elif verbosity == 1: + flags = [] + else: + flags = [f"-{'v'*verbosity}"] + cmd_args = make_command( + "checkout", "--lightweight", *flags, rev_options.to_args(), url, dest + ) + self.run_command(cmd_args) + + def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + self.run_command(make_command("switch", url), cwd=dest) + + def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + output = self.run_command( + make_command("info"), show_stdout=False, stdout_only=True, cwd=dest + ) + if output.startswith("Standalone "): + # Older versions of pip used to create standalone branches. + # Convert the standalone branch to a checkout by calling "bzr bind". + cmd_args = make_command("bind", "-q", url) + self.run_command(cmd_args, cwd=dest) + + cmd_args = make_command("update", "-q", rev_options.to_args()) + self.run_command(cmd_args, cwd=dest) + + @classmethod + def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + # hotfix the URL scheme after removing bzr+ from bzr+ssh:// re-add it + url, rev, user_pass = super().get_url_rev_and_auth(url) + if url.startswith("ssh://"): + url = "bzr+" + url + return url, rev, user_pass + + @classmethod + def get_remote_url(cls, location: str) -> str: + urls = cls.run_command( + ["info"], show_stdout=False, stdout_only=True, cwd=location + ) + for line in urls.splitlines(): + line = line.strip() + for x in ("checkout of branch: ", "parent branch: "): + if line.startswith(x): + repo = line.split(x)[1] + if cls._is_local_repository(repo): + return path_to_url(repo) + return repo + raise RemoteNotFoundError + + @classmethod + def get_revision(cls, location: str) -> str: + revision = cls.run_command( + ["revno"], + show_stdout=False, + stdout_only=True, + cwd=location, + ) + return revision.splitlines()[-1] + + @classmethod + def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool: + """Always assume the versions don't match""" + return False + + +vcs.register(Bazaar) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/git.py b/venv/lib/python3.8/site-packages/pip/_internal/vcs/git.py new file mode 100644 index 000000000..0425debb3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/vcs/git.py @@ -0,0 +1,527 @@ +import logging +import os.path +import pathlib +import re +import urllib.parse +import urllib.request +from dataclasses import replace +from typing import List, Optional, Tuple + +from pip._internal.exceptions import BadCommand, InstallationError +from pip._internal.utils.misc import HiddenText, display_path, hide_url +from pip._internal.utils.subprocess import make_command +from pip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RemoteNotValidError, + RevOptions, + VersionControl, + find_path_to_project_root_from_repo_root, + vcs, +) + +urlsplit = urllib.parse.urlsplit +urlunsplit = urllib.parse.urlunsplit + + +logger = logging.getLogger(__name__) + + +GIT_VERSION_REGEX = re.compile( + r"^git version " # Prefix. + r"(\d+)" # Major. + r"\.(\d+)" # Dot, minor. + r"(?:\.(\d+))?" # Optional dot, patch. + r".*$" # Suffix, including any pre- and post-release segments we don't care about. +) + +HASH_REGEX = re.compile("^[a-fA-F0-9]{40}$") + +# SCP (Secure copy protocol) shorthand. e.g. 'git@example.com:foo/bar.git' +SCP_REGEX = re.compile( + r"""^ + # Optional user, e.g. 'git@' + (\w+@)? + # Server, e.g. 'github.com'. + ([^/:]+): + # The server-side path. e.g. 'user/project.git'. Must start with an + # alphanumeric character so as not to be confusable with a Windows paths + # like 'C:/foo/bar' or 'C:\foo\bar'. + (\w[^:]*) + $""", + re.VERBOSE, +) + + +def looks_like_hash(sha: str) -> bool: + return bool(HASH_REGEX.match(sha)) + + +class Git(VersionControl): + name = "git" + dirname = ".git" + repo_name = "clone" + schemes = ( + "git+http", + "git+https", + "git+ssh", + "git+git", + "git+file", + ) + # Prevent the user's environment variables from interfering with pip: + # https://github.com/pypa/pip/issues/1130 + unset_environ = ("GIT_DIR", "GIT_WORK_TREE") + default_arg_rev = "HEAD" + + @staticmethod + def get_base_rev_args(rev: str) -> List[str]: + return [rev] + + def is_immutable_rev_checkout(self, url: str, dest: str) -> bool: + _, rev_options = self.get_url_rev_options(hide_url(url)) + if not rev_options.rev: + return False + if not self.is_commit_id_equal(dest, rev_options.rev): + # the current commit is different from rev, + # which means rev was something else than a commit hash + return False + # return False in the rare case rev is both a commit hash + # and a tag or a branch; we don't want to cache in that case + # because that branch/tag could point to something else in the future + is_tag_or_branch = bool(self.get_revision_sha(dest, rev_options.rev)[0]) + return not is_tag_or_branch + + def get_git_version(self) -> Tuple[int, ...]: + version = self.run_command( + ["version"], + command_desc="git version", + show_stdout=False, + stdout_only=True, + ) + match = GIT_VERSION_REGEX.match(version) + if not match: + logger.warning("Can't parse git version: %s", version) + return () + return (int(match.group(1)), int(match.group(2))) + + @classmethod + def get_current_branch(cls, location: str) -> Optional[str]: + """ + Return the current branch, or None if HEAD isn't at a branch + (e.g. detached HEAD). + """ + # git-symbolic-ref exits with empty stdout if "HEAD" is a detached + # HEAD rather than a symbolic ref. In addition, the -q causes the + # command to exit with status code 1 instead of 128 in this case + # and to suppress the message to stderr. + args = ["symbolic-ref", "-q", "HEAD"] + output = cls.run_command( + args, + extra_ok_returncodes=(1,), + show_stdout=False, + stdout_only=True, + cwd=location, + ) + ref = output.strip() + + if ref.startswith("refs/heads/"): + return ref[len("refs/heads/") :] + + return None + + @classmethod + def get_revision_sha(cls, dest: str, rev: str) -> Tuple[Optional[str], bool]: + """ + Return (sha_or_none, is_branch), where sha_or_none is a commit hash + if the revision names a remote branch or tag, otherwise None. + + Args: + dest: the repository directory. + rev: the revision name. + """ + # Pass rev to pre-filter the list. + output = cls.run_command( + ["show-ref", rev], + cwd=dest, + show_stdout=False, + stdout_only=True, + on_returncode="ignore", + ) + refs = {} + # NOTE: We do not use splitlines here since that would split on other + # unicode separators, which can be maliciously used to install a + # different revision. + for line in output.strip().split("\n"): + line = line.rstrip("\r") + if not line: + continue + try: + ref_sha, ref_name = line.split(" ", maxsplit=2) + except ValueError: + # Include the offending line to simplify troubleshooting if + # this error ever occurs. + raise ValueError(f"unexpected show-ref line: {line!r}") + + refs[ref_name] = ref_sha + + branch_ref = f"refs/remotes/origin/{rev}" + tag_ref = f"refs/tags/{rev}" + + sha = refs.get(branch_ref) + if sha is not None: + return (sha, True) + + sha = refs.get(tag_ref) + + return (sha, False) + + @classmethod + def _should_fetch(cls, dest: str, rev: str) -> bool: + """ + Return true if rev is a ref or is a commit that we don't have locally. + + Branches and tags are not considered in this method because they are + assumed to be always available locally (which is a normal outcome of + ``git clone`` and ``git fetch --tags``). + """ + if rev.startswith("refs/"): + # Always fetch remote refs. + return True + + if not looks_like_hash(rev): + # Git fetch would fail with abbreviated commits. + return False + + if cls.has_commit(dest, rev): + # Don't fetch if we have the commit locally. + return False + + return True + + @classmethod + def resolve_revision( + cls, dest: str, url: HiddenText, rev_options: RevOptions + ) -> RevOptions: + """ + Resolve a revision to a new RevOptions object with the SHA1 of the + branch, tag, or ref if found. + + Args: + rev_options: a RevOptions object. + """ + rev = rev_options.arg_rev + # The arg_rev property's implementation for Git ensures that the + # rev return value is always non-None. + assert rev is not None + + sha, is_branch = cls.get_revision_sha(dest, rev) + + if sha is not None: + rev_options = rev_options.make_new(sha) + rev_options = replace(rev_options, branch_name=(rev if is_branch else None)) + + return rev_options + + # Do not show a warning for the common case of something that has + # the form of a Git commit hash. + if not looks_like_hash(rev): + logger.warning( + "Did not find branch or tag '%s', assuming revision or ref.", + rev, + ) + + if not cls._should_fetch(dest, rev): + return rev_options + + # fetch the requested revision + cls.run_command( + make_command("fetch", "-q", url, rev_options.to_args()), + cwd=dest, + ) + # Change the revision to the SHA of the ref we fetched + sha = cls.get_revision(dest, rev="FETCH_HEAD") + rev_options = rev_options.make_new(sha) + + return rev_options + + @classmethod + def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool: + """ + Return whether the current commit hash equals the given name. + + Args: + dest: the repository directory. + name: a string name. + """ + if not name: + # Then avoid an unnecessary subprocess call. + return False + + return cls.get_revision(dest) == name + + def fetch_new( + self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int + ) -> None: + rev_display = rev_options.to_display() + logger.info("Cloning %s%s to %s", url, rev_display, display_path(dest)) + if verbosity <= 0: + flags: Tuple[str, ...] = ("--quiet",) + elif verbosity == 1: + flags = () + else: + flags = ("--verbose", "--progress") + if self.get_git_version() >= (2, 17): + # Git added support for partial clone in 2.17 + # https://git-scm.com/docs/partial-clone + # Speeds up cloning by functioning without a complete copy of repository + self.run_command( + make_command( + "clone", + "--filter=blob:none", + *flags, + url, + dest, + ) + ) + else: + self.run_command(make_command("clone", *flags, url, dest)) + + if rev_options.rev: + # Then a specific revision was requested. + rev_options = self.resolve_revision(dest, url, rev_options) + branch_name = getattr(rev_options, "branch_name", None) + logger.debug("Rev options %s, branch_name %s", rev_options, branch_name) + if branch_name is None: + # Only do a checkout if the current commit id doesn't match + # the requested revision. + if not self.is_commit_id_equal(dest, rev_options.rev): + cmd_args = make_command( + "checkout", + "-q", + rev_options.to_args(), + ) + self.run_command(cmd_args, cwd=dest) + elif self.get_current_branch(dest) != branch_name: + # Then a specific branch was requested, and that branch + # is not yet checked out. + track_branch = f"origin/{branch_name}" + cmd_args = [ + "checkout", + "-b", + branch_name, + "--track", + track_branch, + ] + self.run_command(cmd_args, cwd=dest) + else: + sha = self.get_revision(dest) + rev_options = rev_options.make_new(sha) + + logger.info("Resolved %s to commit %s", url, rev_options.rev) + + #: repo may contain submodules + self.update_submodules(dest) + + def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + self.run_command( + make_command("config", "remote.origin.url", url), + cwd=dest, + ) + cmd_args = make_command("checkout", "-q", rev_options.to_args()) + self.run_command(cmd_args, cwd=dest) + + self.update_submodules(dest) + + def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + # First fetch changes from the default remote + if self.get_git_version() >= (1, 9): + # fetch tags in addition to everything else + self.run_command(["fetch", "-q", "--tags"], cwd=dest) + else: + self.run_command(["fetch", "-q"], cwd=dest) + # Then reset to wanted revision (maybe even origin/master) + rev_options = self.resolve_revision(dest, url, rev_options) + cmd_args = make_command("reset", "--hard", "-q", rev_options.to_args()) + self.run_command(cmd_args, cwd=dest) + #: update submodules + self.update_submodules(dest) + + @classmethod + def get_remote_url(cls, location: str) -> str: + """ + Return URL of the first remote encountered. + + Raises RemoteNotFoundError if the repository does not have a remote + url configured. + """ + # We need to pass 1 for extra_ok_returncodes since the command + # exits with return code 1 if there are no matching lines. + stdout = cls.run_command( + ["config", "--get-regexp", r"remote\..*\.url"], + extra_ok_returncodes=(1,), + show_stdout=False, + stdout_only=True, + cwd=location, + ) + remotes = stdout.splitlines() + try: + found_remote = remotes[0] + except IndexError: + raise RemoteNotFoundError + + for remote in remotes: + if remote.startswith("remote.origin.url "): + found_remote = remote + break + url = found_remote.split(" ")[1] + return cls._git_remote_to_pip_url(url.strip()) + + @staticmethod + def _git_remote_to_pip_url(url: str) -> str: + """ + Convert a remote url from what git uses to what pip accepts. + + There are 3 legal forms **url** may take: + + 1. A fully qualified url: ssh://git@example.com/foo/bar.git + 2. A local project.git folder: /path/to/bare/repository.git + 3. SCP shorthand for form 1: git@example.com:foo/bar.git + + Form 1 is output as-is. Form 2 must be converted to URI and form 3 must + be converted to form 1. + + See the corresponding test test_git_remote_url_to_pip() for examples of + sample inputs/outputs. + """ + if re.match(r"\w+://", url): + # This is already valid. Pass it though as-is. + return url + if os.path.exists(url): + # A local bare remote (git clone --mirror). + # Needs a file:// prefix. + return pathlib.PurePath(url).as_uri() + scp_match = SCP_REGEX.match(url) + if scp_match: + # Add an ssh:// prefix and replace the ':' with a '/'. + return scp_match.expand(r"ssh://\1\2/\3") + # Otherwise, bail out. + raise RemoteNotValidError(url) + + @classmethod + def has_commit(cls, location: str, rev: str) -> bool: + """ + Check if rev is a commit that is available in the local repository. + """ + try: + cls.run_command( + ["rev-parse", "-q", "--verify", "sha^" + rev], + cwd=location, + log_failed_cmd=False, + ) + except InstallationError: + return False + else: + return True + + @classmethod + def get_revision(cls, location: str, rev: Optional[str] = None) -> str: + if rev is None: + rev = "HEAD" + current_rev = cls.run_command( + ["rev-parse", rev], + show_stdout=False, + stdout_only=True, + cwd=location, + ) + return current_rev.strip() + + @classmethod + def get_subdirectory(cls, location: str) -> Optional[str]: + """ + Return the path to Python project root, relative to the repo root. + Return None if the project root is in the repo root. + """ + # find the repo root + git_dir = cls.run_command( + ["rev-parse", "--git-dir"], + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() + if not os.path.isabs(git_dir): + git_dir = os.path.join(location, git_dir) + repo_root = os.path.abspath(os.path.join(git_dir, "..")) + return find_path_to_project_root_from_repo_root(location, repo_root) + + @classmethod + def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + """ + Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'. + That's required because although they use SSH they sometimes don't + work with a ssh:// scheme (e.g. GitHub). But we need a scheme for + parsing. Hence we remove it again afterwards and return it as a stub. + """ + # Works around an apparent Git bug + # (see https://article.gmane.org/gmane.comp.version-control.git/146500) + scheme, netloc, path, query, fragment = urlsplit(url) + if scheme.endswith("file"): + initial_slashes = path[: -len(path.lstrip("/"))] + newpath = initial_slashes + urllib.request.url2pathname(path).replace( + "\\", "/" + ).lstrip("/") + after_plus = scheme.find("+") + 1 + url = scheme[:after_plus] + urlunsplit( + (scheme[after_plus:], netloc, newpath, query, fragment), + ) + + if "://" not in url: + assert "file:" not in url + url = url.replace("git+", "git+ssh://") + url, rev, user_pass = super().get_url_rev_and_auth(url) + url = url.replace("ssh://", "") + else: + url, rev, user_pass = super().get_url_rev_and_auth(url) + + return url, rev, user_pass + + @classmethod + def update_submodules(cls, location: str) -> None: + if not os.path.exists(os.path.join(location, ".gitmodules")): + return + cls.run_command( + ["submodule", "update", "--init", "--recursive", "-q"], + cwd=location, + ) + + @classmethod + def get_repository_root(cls, location: str) -> Optional[str]: + loc = super().get_repository_root(location) + if loc: + return loc + try: + r = cls.run_command( + ["rev-parse", "--show-toplevel"], + cwd=location, + show_stdout=False, + stdout_only=True, + on_returncode="raise", + log_failed_cmd=False, + ) + except BadCommand: + logger.debug( + "could not determine if %s is under git control " + "because git is not available", + location, + ) + return None + except InstallationError: + return None + return os.path.normpath(r.rstrip("\r\n")) + + @staticmethod + def should_add_vcs_url_prefix(repo_url: str) -> bool: + """In either https or ssh form, requirements must be prefixed with git+.""" + return True + + +vcs.register(Git) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/mercurial.py b/venv/lib/python3.8/site-packages/pip/_internal/vcs/mercurial.py new file mode 100644 index 000000000..c183d41d0 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/vcs/mercurial.py @@ -0,0 +1,163 @@ +import configparser +import logging +import os +from typing import List, Optional, Tuple + +from pip._internal.exceptions import BadCommand, InstallationError +from pip._internal.utils.misc import HiddenText, display_path +from pip._internal.utils.subprocess import make_command +from pip._internal.utils.urls import path_to_url +from pip._internal.vcs.versioncontrol import ( + RevOptions, + VersionControl, + find_path_to_project_root_from_repo_root, + vcs, +) + +logger = logging.getLogger(__name__) + + +class Mercurial(VersionControl): + name = "hg" + dirname = ".hg" + repo_name = "clone" + schemes = ( + "hg+file", + "hg+http", + "hg+https", + "hg+ssh", + "hg+static-http", + ) + + @staticmethod + def get_base_rev_args(rev: str) -> List[str]: + return [f"--rev={rev}"] + + def fetch_new( + self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int + ) -> None: + rev_display = rev_options.to_display() + logger.info( + "Cloning hg %s%s to %s", + url, + rev_display, + display_path(dest), + ) + if verbosity <= 0: + flags: Tuple[str, ...] = ("--quiet",) + elif verbosity == 1: + flags = () + elif verbosity == 2: + flags = ("--verbose",) + else: + flags = ("--verbose", "--debug") + self.run_command(make_command("clone", "--noupdate", *flags, url, dest)) + self.run_command( + make_command("update", *flags, rev_options.to_args()), + cwd=dest, + ) + + def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + repo_config = os.path.join(dest, self.dirname, "hgrc") + config = configparser.RawConfigParser() + try: + config.read(repo_config) + config.set("paths", "default", url.secret) + with open(repo_config, "w") as config_file: + config.write(config_file) + except (OSError, configparser.NoSectionError) as exc: + logger.warning("Could not switch Mercurial repository to %s: %s", url, exc) + else: + cmd_args = make_command("update", "-q", rev_options.to_args()) + self.run_command(cmd_args, cwd=dest) + + def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + self.run_command(["pull", "-q"], cwd=dest) + cmd_args = make_command("update", "-q", rev_options.to_args()) + self.run_command(cmd_args, cwd=dest) + + @classmethod + def get_remote_url(cls, location: str) -> str: + url = cls.run_command( + ["showconfig", "paths.default"], + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() + if cls._is_local_repository(url): + url = path_to_url(url) + return url.strip() + + @classmethod + def get_revision(cls, location: str) -> str: + """ + Return the repository-local changeset revision number, as an integer. + """ + current_revision = cls.run_command( + ["parents", "--template={rev}"], + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() + return current_revision + + @classmethod + def get_requirement_revision(cls, location: str) -> str: + """ + Return the changeset identification hash, as a 40-character + hexadecimal string + """ + current_rev_hash = cls.run_command( + ["parents", "--template={node}"], + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() + return current_rev_hash + + @classmethod + def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool: + """Always assume the versions don't match""" + return False + + @classmethod + def get_subdirectory(cls, location: str) -> Optional[str]: + """ + Return the path to Python project root, relative to the repo root. + Return None if the project root is in the repo root. + """ + # find the repo root + repo_root = cls.run_command( + ["root"], show_stdout=False, stdout_only=True, cwd=location + ).strip() + if not os.path.isabs(repo_root): + repo_root = os.path.abspath(os.path.join(location, repo_root)) + return find_path_to_project_root_from_repo_root(location, repo_root) + + @classmethod + def get_repository_root(cls, location: str) -> Optional[str]: + loc = super().get_repository_root(location) + if loc: + return loc + try: + r = cls.run_command( + ["root"], + cwd=location, + show_stdout=False, + stdout_only=True, + on_returncode="raise", + log_failed_cmd=False, + ) + except BadCommand: + logger.debug( + "could not determine if %s is under hg control " + "because hg is not available", + location, + ) + return None + except InstallationError: + return None + return os.path.normpath(r.rstrip("\r\n")) + + +vcs.register(Mercurial) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/subversion.py b/venv/lib/python3.8/site-packages/pip/_internal/vcs/subversion.py new file mode 100644 index 000000000..f359266d9 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/vcs/subversion.py @@ -0,0 +1,324 @@ +import logging +import os +import re +from typing import List, Optional, Tuple + +from pip._internal.utils.misc import ( + HiddenText, + display_path, + is_console_interactive, + is_installable_dir, + split_auth_from_netloc, +) +from pip._internal.utils.subprocess import CommandArgs, make_command +from pip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RevOptions, + VersionControl, + vcs, +) + +logger = logging.getLogger(__name__) + +_svn_xml_url_re = re.compile('url="([^"]+)"') +_svn_rev_re = re.compile(r'committed-rev="(\d+)"') +_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"') +_svn_info_xml_url_re = re.compile(r"(.*)") + + +class Subversion(VersionControl): + name = "svn" + dirname = ".svn" + repo_name = "checkout" + schemes = ("svn+ssh", "svn+http", "svn+https", "svn+svn", "svn+file") + + @classmethod + def should_add_vcs_url_prefix(cls, remote_url: str) -> bool: + return True + + @staticmethod + def get_base_rev_args(rev: str) -> List[str]: + return ["-r", rev] + + @classmethod + def get_revision(cls, location: str) -> str: + """ + Return the maximum revision for all files under a given location + """ + # Note: taken from setuptools.command.egg_info + revision = 0 + + for base, dirs, _ in os.walk(location): + if cls.dirname not in dirs: + dirs[:] = [] + continue # no sense walking uncontrolled subdirs + dirs.remove(cls.dirname) + entries_fn = os.path.join(base, cls.dirname, "entries") + if not os.path.exists(entries_fn): + # FIXME: should we warn? + continue + + dirurl, localrev = cls._get_svn_url_rev(base) + + if base == location: + assert dirurl is not None + base = dirurl + "/" # save the root url + elif not dirurl or not dirurl.startswith(base): + dirs[:] = [] + continue # not part of the same svn tree, skip it + revision = max(revision, localrev) + return str(revision) + + @classmethod + def get_netloc_and_auth( + cls, netloc: str, scheme: str + ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]: + """ + This override allows the auth information to be passed to svn via the + --username and --password options instead of via the URL. + """ + if scheme == "ssh": + # The --username and --password options can't be used for + # svn+ssh URLs, so keep the auth information in the URL. + return super().get_netloc_and_auth(netloc, scheme) + + return split_auth_from_netloc(netloc) + + @classmethod + def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + # hotfix the URL scheme after removing svn+ from svn+ssh:// re-add it + url, rev, user_pass = super().get_url_rev_and_auth(url) + if url.startswith("ssh://"): + url = "svn+" + url + return url, rev, user_pass + + @staticmethod + def make_rev_args( + username: Optional[str], password: Optional[HiddenText] + ) -> CommandArgs: + extra_args: CommandArgs = [] + if username: + extra_args += ["--username", username] + if password: + extra_args += ["--password", password] + + return extra_args + + @classmethod + def get_remote_url(cls, location: str) -> str: + # In cases where the source is in a subdirectory, we have to look up in + # the location until we find a valid project root. + orig_location = location + while not is_installable_dir(location): + last_location = location + location = os.path.dirname(location) + if location == last_location: + # We've traversed up to the root of the filesystem without + # finding a Python project. + logger.warning( + "Could not find Python project for directory %s (tried all " + "parent directories)", + orig_location, + ) + raise RemoteNotFoundError + + url, _rev = cls._get_svn_url_rev(location) + if url is None: + raise RemoteNotFoundError + + return url + + @classmethod + def _get_svn_url_rev(cls, location: str) -> Tuple[Optional[str], int]: + from pip._internal.exceptions import InstallationError + + entries_path = os.path.join(location, cls.dirname, "entries") + if os.path.exists(entries_path): + with open(entries_path) as f: + data = f.read() + else: # subversion >= 1.7 does not have the 'entries' file + data = "" + + url = None + if data.startswith("8") or data.startswith("9") or data.startswith("10"): + entries = list(map(str.splitlines, data.split("\n\x0c\n"))) + del entries[0][0] # get rid of the '8' + url = entries[0][3] + revs = [int(d[9]) for d in entries if len(d) > 9 and d[9]] + [0] + elif data.startswith("= 1.7 + # Note that using get_remote_call_options is not necessary here + # because `svn info` is being run against a local directory. + # We don't need to worry about making sure interactive mode + # is being used to prompt for passwords, because passwords + # are only potentially needed for remote server requests. + xml = cls.run_command( + ["info", "--xml", location], + show_stdout=False, + stdout_only=True, + ) + match = _svn_info_xml_url_re.search(xml) + assert match is not None + url = match.group(1) + revs = [int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml)] + except InstallationError: + url, revs = None, [] + + if revs: + rev = max(revs) + else: + rev = 0 + + return url, rev + + @classmethod + def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool: + """Always assume the versions don't match""" + return False + + def __init__(self, use_interactive: Optional[bool] = None) -> None: + if use_interactive is None: + use_interactive = is_console_interactive() + self.use_interactive = use_interactive + + # This member is used to cache the fetched version of the current + # ``svn`` client. + # Special value definitions: + # None: Not evaluated yet. + # Empty tuple: Could not parse version. + self._vcs_version: Optional[Tuple[int, ...]] = None + + super().__init__() + + def call_vcs_version(self) -> Tuple[int, ...]: + """Query the version of the currently installed Subversion client. + + :return: A tuple containing the parts of the version information or + ``()`` if the version returned from ``svn`` could not be parsed. + :raises: BadCommand: If ``svn`` is not installed. + """ + # Example versions: + # svn, version 1.10.3 (r1842928) + # compiled Feb 25 2019, 14:20:39 on x86_64-apple-darwin17.0.0 + # svn, version 1.7.14 (r1542130) + # compiled Mar 28 2018, 08:49:13 on x86_64-pc-linux-gnu + # svn, version 1.12.0-SlikSvn (SlikSvn/1.12.0) + # compiled May 28 2019, 13:44:56 on x86_64-microsoft-windows6.2 + version_prefix = "svn, version " + version = self.run_command(["--version"], show_stdout=False, stdout_only=True) + if not version.startswith(version_prefix): + return () + + version = version[len(version_prefix) :].split()[0] + version_list = version.partition("-")[0].split(".") + try: + parsed_version = tuple(map(int, version_list)) + except ValueError: + return () + + return parsed_version + + def get_vcs_version(self) -> Tuple[int, ...]: + """Return the version of the currently installed Subversion client. + + If the version of the Subversion client has already been queried, + a cached value will be used. + + :return: A tuple containing the parts of the version information or + ``()`` if the version returned from ``svn`` could not be parsed. + :raises: BadCommand: If ``svn`` is not installed. + """ + if self._vcs_version is not None: + # Use cached version, if available. + # If parsing the version failed previously (empty tuple), + # do not attempt to parse it again. + return self._vcs_version + + vcs_version = self.call_vcs_version() + self._vcs_version = vcs_version + return vcs_version + + def get_remote_call_options(self) -> CommandArgs: + """Return options to be used on calls to Subversion that contact the server. + + These options are applicable for the following ``svn`` subcommands used + in this class. + + - checkout + - switch + - update + + :return: A list of command line arguments to pass to ``svn``. + """ + if not self.use_interactive: + # --non-interactive switch is available since Subversion 0.14.4. + # Subversion < 1.8 runs in interactive mode by default. + return ["--non-interactive"] + + svn_version = self.get_vcs_version() + # By default, Subversion >= 1.8 runs in non-interactive mode if + # stdin is not a TTY. Since that is how pip invokes SVN, in + # call_subprocess(), pip must pass --force-interactive to ensure + # the user can be prompted for a password, if required. + # SVN added the --force-interactive option in SVN 1.8. Since + # e.g. RHEL/CentOS 7, which is supported until 2024, ships with + # SVN 1.7, pip should continue to support SVN 1.7. Therefore, pip + # can't safely add the option if the SVN version is < 1.8 (or unknown). + if svn_version >= (1, 8): + return ["--force-interactive"] + + return [] + + def fetch_new( + self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int + ) -> None: + rev_display = rev_options.to_display() + logger.info( + "Checking out %s%s to %s", + url, + rev_display, + display_path(dest), + ) + if verbosity <= 0: + flags = ["--quiet"] + else: + flags = [] + cmd_args = make_command( + "checkout", + *flags, + self.get_remote_call_options(), + rev_options.to_args(), + url, + dest, + ) + self.run_command(cmd_args) + + def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + cmd_args = make_command( + "switch", + self.get_remote_call_options(), + rev_options.to_args(), + url, + dest, + ) + self.run_command(cmd_args) + + def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + cmd_args = make_command( + "update", + self.get_remote_call_options(), + rev_options.to_args(), + dest, + ) + self.run_command(cmd_args) + + +vcs.register(Subversion) diff --git a/venv/lib/python3.8/site-packages/pip/_internal/vcs/versioncontrol.py b/venv/lib/python3.8/site-packages/pip/_internal/vcs/versioncontrol.py new file mode 100644 index 000000000..a4133165e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/vcs/versioncontrol.py @@ -0,0 +1,688 @@ +"""Handles all VCS (version control) support""" + +import logging +import os +import shutil +import sys +import urllib.parse +from dataclasses import dataclass, field +from typing import ( + Any, + Dict, + Iterable, + Iterator, + List, + Literal, + Mapping, + Optional, + Tuple, + Type, + Union, +) + +from pip._internal.cli.spinners import SpinnerInterface +from pip._internal.exceptions import BadCommand, InstallationError +from pip._internal.utils.misc import ( + HiddenText, + ask_path_exists, + backup_dir, + display_path, + hide_url, + hide_value, + is_installable_dir, + rmtree, +) +from pip._internal.utils.subprocess import ( + CommandArgs, + call_subprocess, + format_command_args, + make_command, +) + +__all__ = ["vcs"] + + +logger = logging.getLogger(__name__) + +AuthInfo = Tuple[Optional[str], Optional[str]] + + +def is_url(name: str) -> bool: + """ + Return true if the name looks like a URL. + """ + scheme = urllib.parse.urlsplit(name).scheme + if not scheme: + return False + return scheme in ["http", "https", "file", "ftp"] + vcs.all_schemes + + +def make_vcs_requirement_url( + repo_url: str, rev: str, project_name: str, subdir: Optional[str] = None +) -> str: + """ + Return the URL for a VCS requirement. + + Args: + repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+"). + project_name: the (unescaped) project name. + """ + egg_project_name = project_name.replace("-", "_") + req = f"{repo_url}@{rev}#egg={egg_project_name}" + if subdir: + req += f"&subdirectory={subdir}" + + return req + + +def find_path_to_project_root_from_repo_root( + location: str, repo_root: str +) -> Optional[str]: + """ + Find the the Python project's root by searching up the filesystem from + `location`. Return the path to project root relative to `repo_root`. + Return None if the project root is `repo_root`, or cannot be found. + """ + # find project root. + orig_location = location + while not is_installable_dir(location): + last_location = location + location = os.path.dirname(location) + if location == last_location: + # We've traversed up to the root of the filesystem without + # finding a Python project. + logger.warning( + "Could not find a Python project for directory %s (tried all " + "parent directories)", + orig_location, + ) + return None + + if os.path.samefile(repo_root, location): + return None + + return os.path.relpath(location, repo_root) + + +class RemoteNotFoundError(Exception): + pass + + +class RemoteNotValidError(Exception): + def __init__(self, url: str): + super().__init__(url) + self.url = url + + +@dataclass(frozen=True) +class RevOptions: + """ + Encapsulates a VCS-specific revision to install, along with any VCS + install options. + + Args: + vc_class: a VersionControl subclass. + rev: the name of the revision to install. + extra_args: a list of extra options. + """ + + vc_class: Type["VersionControl"] + rev: Optional[str] = None + extra_args: CommandArgs = field(default_factory=list) + branch_name: Optional[str] = None + + def __repr__(self) -> str: + return f"" + + @property + def arg_rev(self) -> Optional[str]: + if self.rev is None: + return self.vc_class.default_arg_rev + + return self.rev + + def to_args(self) -> CommandArgs: + """ + Return the VCS-specific command arguments. + """ + args: CommandArgs = [] + rev = self.arg_rev + if rev is not None: + args += self.vc_class.get_base_rev_args(rev) + args += self.extra_args + + return args + + def to_display(self) -> str: + if not self.rev: + return "" + + return f" (to revision {self.rev})" + + def make_new(self, rev: str) -> "RevOptions": + """ + Make a copy of the current instance, but with a new rev. + + Args: + rev: the name of the revision for the new object. + """ + return self.vc_class.make_rev_options(rev, extra_args=self.extra_args) + + +class VcsSupport: + _registry: Dict[str, "VersionControl"] = {} + schemes = ["ssh", "git", "hg", "bzr", "sftp", "svn"] + + def __init__(self) -> None: + # Register more schemes with urlparse for various version control + # systems + urllib.parse.uses_netloc.extend(self.schemes) + super().__init__() + + def __iter__(self) -> Iterator[str]: + return self._registry.__iter__() + + @property + def backends(self) -> List["VersionControl"]: + return list(self._registry.values()) + + @property + def dirnames(self) -> List[str]: + return [backend.dirname for backend in self.backends] + + @property + def all_schemes(self) -> List[str]: + schemes: List[str] = [] + for backend in self.backends: + schemes.extend(backend.schemes) + return schemes + + def register(self, cls: Type["VersionControl"]) -> None: + if not hasattr(cls, "name"): + logger.warning("Cannot register VCS %s", cls.__name__) + return + if cls.name not in self._registry: + self._registry[cls.name] = cls() + logger.debug("Registered VCS backend: %s", cls.name) + + def unregister(self, name: str) -> None: + if name in self._registry: + del self._registry[name] + + def get_backend_for_dir(self, location: str) -> Optional["VersionControl"]: + """ + Return a VersionControl object if a repository of that type is found + at the given directory. + """ + vcs_backends = {} + for vcs_backend in self._registry.values(): + repo_path = vcs_backend.get_repository_root(location) + if not repo_path: + continue + logger.debug("Determine that %s uses VCS: %s", location, vcs_backend.name) + vcs_backends[repo_path] = vcs_backend + + if not vcs_backends: + return None + + # Choose the VCS in the inner-most directory. Since all repository + # roots found here would be either `location` or one of its + # parents, the longest path should have the most path components, + # i.e. the backend representing the inner-most repository. + inner_most_repo_path = max(vcs_backends, key=len) + return vcs_backends[inner_most_repo_path] + + def get_backend_for_scheme(self, scheme: str) -> Optional["VersionControl"]: + """ + Return a VersionControl object or None. + """ + for vcs_backend in self._registry.values(): + if scheme in vcs_backend.schemes: + return vcs_backend + return None + + def get_backend(self, name: str) -> Optional["VersionControl"]: + """ + Return a VersionControl object or None. + """ + name = name.lower() + return self._registry.get(name) + + +vcs = VcsSupport() + + +class VersionControl: + name = "" + dirname = "" + repo_name = "" + # List of supported schemes for this Version Control + schemes: Tuple[str, ...] = () + # Iterable of environment variable names to pass to call_subprocess(). + unset_environ: Tuple[str, ...] = () + default_arg_rev: Optional[str] = None + + @classmethod + def should_add_vcs_url_prefix(cls, remote_url: str) -> bool: + """ + Return whether the vcs prefix (e.g. "git+") should be added to a + repository's remote url when used in a requirement. + """ + return not remote_url.lower().startswith(f"{cls.name}:") + + @classmethod + def get_subdirectory(cls, location: str) -> Optional[str]: + """ + Return the path to Python project root, relative to the repo root. + Return None if the project root is in the repo root. + """ + return None + + @classmethod + def get_requirement_revision(cls, repo_dir: str) -> str: + """ + Return the revision string that should be used in a requirement. + """ + return cls.get_revision(repo_dir) + + @classmethod + def get_src_requirement(cls, repo_dir: str, project_name: str) -> str: + """ + Return the requirement string to use to redownload the files + currently at the given repository directory. + + Args: + project_name: the (unescaped) project name. + + The return value has a form similar to the following: + + {repository_url}@{revision}#egg={project_name} + """ + repo_url = cls.get_remote_url(repo_dir) + + if cls.should_add_vcs_url_prefix(repo_url): + repo_url = f"{cls.name}+{repo_url}" + + revision = cls.get_requirement_revision(repo_dir) + subdir = cls.get_subdirectory(repo_dir) + req = make_vcs_requirement_url(repo_url, revision, project_name, subdir=subdir) + + return req + + @staticmethod + def get_base_rev_args(rev: str) -> List[str]: + """ + Return the base revision arguments for a vcs command. + + Args: + rev: the name of a revision to install. Cannot be None. + """ + raise NotImplementedError + + def is_immutable_rev_checkout(self, url: str, dest: str) -> bool: + """ + Return true if the commit hash checked out at dest matches + the revision in url. + + Always return False, if the VCS does not support immutable commit + hashes. + + This method does not check if there are local uncommitted changes + in dest after checkout, as pip currently has no use case for that. + """ + return False + + @classmethod + def make_rev_options( + cls, rev: Optional[str] = None, extra_args: Optional[CommandArgs] = None + ) -> RevOptions: + """ + Return a RevOptions object. + + Args: + rev: the name of a revision to install. + extra_args: a list of extra options. + """ + return RevOptions(cls, rev, extra_args=extra_args or []) + + @classmethod + def _is_local_repository(cls, repo: str) -> bool: + """ + posix absolute paths start with os.path.sep, + win32 ones start with drive (like c:\\folder) + """ + drive, tail = os.path.splitdrive(repo) + return repo.startswith(os.path.sep) or bool(drive) + + @classmethod + def get_netloc_and_auth( + cls, netloc: str, scheme: str + ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]: + """ + Parse the repository URL's netloc, and return the new netloc to use + along with auth information. + + Args: + netloc: the original repository URL netloc. + scheme: the repository URL's scheme without the vcs prefix. + + This is mainly for the Subversion class to override, so that auth + information can be provided via the --username and --password options + instead of through the URL. For other subclasses like Git without + such an option, auth information must stay in the URL. + + Returns: (netloc, (username, password)). + """ + return netloc, (None, None) + + @classmethod + def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + """ + Parse the repository URL to use, and return the URL, revision, + and auth info to use. + + Returns: (url, rev, (username, password)). + """ + scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) + if "+" not in scheme: + raise ValueError( + f"Sorry, {url!r} is a malformed VCS url. " + "The format is +://, " + "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp" + ) + # Remove the vcs prefix. + scheme = scheme.split("+", 1)[1] + netloc, user_pass = cls.get_netloc_and_auth(netloc, scheme) + rev = None + if "@" in path: + path, rev = path.rsplit("@", 1) + if not rev: + raise InstallationError( + f"The URL {url!r} has an empty revision (after @) " + "which is not supported. Include a revision after @ " + "or remove @ from the URL." + ) + url = urllib.parse.urlunsplit((scheme, netloc, path, query, "")) + return url, rev, user_pass + + @staticmethod + def make_rev_args( + username: Optional[str], password: Optional[HiddenText] + ) -> CommandArgs: + """ + Return the RevOptions "extra arguments" to use in obtain(). + """ + return [] + + def get_url_rev_options(self, url: HiddenText) -> Tuple[HiddenText, RevOptions]: + """ + Return the URL and RevOptions object to use in obtain(), + as a tuple (url, rev_options). + """ + secret_url, rev, user_pass = self.get_url_rev_and_auth(url.secret) + username, secret_password = user_pass + password: Optional[HiddenText] = None + if secret_password is not None: + password = hide_value(secret_password) + extra_args = self.make_rev_args(username, password) + rev_options = self.make_rev_options(rev, extra_args=extra_args) + + return hide_url(secret_url), rev_options + + @staticmethod + def normalize_url(url: str) -> str: + """ + Normalize a URL for comparison by unquoting it and removing any + trailing slash. + """ + return urllib.parse.unquote(url).rstrip("/") + + @classmethod + def compare_urls(cls, url1: str, url2: str) -> bool: + """ + Compare two repo URLs for identity, ignoring incidental differences. + """ + return cls.normalize_url(url1) == cls.normalize_url(url2) + + def fetch_new( + self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int + ) -> None: + """ + Fetch a revision from a repository, in the case that this is the + first fetch from the repository. + + Args: + dest: the directory to fetch the repository to. + rev_options: a RevOptions object. + verbosity: verbosity level. + """ + raise NotImplementedError + + def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + """ + Switch the repo at ``dest`` to point to ``URL``. + + Args: + rev_options: a RevOptions object. + """ + raise NotImplementedError + + def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: + """ + Update an already-existing repo to the given ``rev_options``. + + Args: + rev_options: a RevOptions object. + """ + raise NotImplementedError + + @classmethod + def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool: + """ + Return whether the id of the current commit equals the given name. + + Args: + dest: the repository directory. + name: a string name. + """ + raise NotImplementedError + + def obtain(self, dest: str, url: HiddenText, verbosity: int) -> None: + """ + Install or update in editable mode the package represented by this + VersionControl object. + + :param dest: the repository directory in which to install or update. + :param url: the repository URL starting with a vcs prefix. + :param verbosity: verbosity level. + """ + url, rev_options = self.get_url_rev_options(url) + + if not os.path.exists(dest): + self.fetch_new(dest, url, rev_options, verbosity=verbosity) + return + + rev_display = rev_options.to_display() + if self.is_repository_directory(dest): + existing_url = self.get_remote_url(dest) + if self.compare_urls(existing_url, url.secret): + logger.debug( + "%s in %s exists, and has correct URL (%s)", + self.repo_name.title(), + display_path(dest), + url, + ) + if not self.is_commit_id_equal(dest, rev_options.rev): + logger.info( + "Updating %s %s%s", + display_path(dest), + self.repo_name, + rev_display, + ) + self.update(dest, url, rev_options) + else: + logger.info("Skipping because already up-to-date.") + return + + logger.warning( + "%s %s in %s exists with URL %s", + self.name, + self.repo_name, + display_path(dest), + existing_url, + ) + prompt = ("(s)witch, (i)gnore, (w)ipe, (b)ackup ", ("s", "i", "w", "b")) + else: + logger.warning( + "Directory %s already exists, and is not a %s %s.", + dest, + self.name, + self.repo_name, + ) + # https://github.com/python/mypy/issues/1174 + prompt = ("(i)gnore, (w)ipe, (b)ackup ", ("i", "w", "b")) # type: ignore + + logger.warning( + "The plan is to install the %s repository %s", + self.name, + url, + ) + response = ask_path_exists(f"What to do? {prompt[0]}", prompt[1]) + + if response == "a": + sys.exit(-1) + + if response == "w": + logger.warning("Deleting %s", display_path(dest)) + rmtree(dest) + self.fetch_new(dest, url, rev_options, verbosity=verbosity) + return + + if response == "b": + dest_dir = backup_dir(dest) + logger.warning("Backing up %s to %s", display_path(dest), dest_dir) + shutil.move(dest, dest_dir) + self.fetch_new(dest, url, rev_options, verbosity=verbosity) + return + + # Do nothing if the response is "i". + if response == "s": + logger.info( + "Switching %s %s to %s%s", + self.repo_name, + display_path(dest), + url, + rev_display, + ) + self.switch(dest, url, rev_options) + + def unpack(self, location: str, url: HiddenText, verbosity: int) -> None: + """ + Clean up current location and download the url repository + (and vcs infos) into location + + :param url: the repository URL starting with a vcs prefix. + :param verbosity: verbosity level. + """ + if os.path.exists(location): + rmtree(location) + self.obtain(location, url=url, verbosity=verbosity) + + @classmethod + def get_remote_url(cls, location: str) -> str: + """ + Return the url used at location + + Raises RemoteNotFoundError if the repository does not have a remote + url configured. + """ + raise NotImplementedError + + @classmethod + def get_revision(cls, location: str) -> str: + """ + Return the current commit id of the files at the given location. + """ + raise NotImplementedError + + @classmethod + def run_command( + cls, + cmd: Union[List[str], CommandArgs], + show_stdout: bool = True, + cwd: Optional[str] = None, + on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise", + extra_ok_returncodes: Optional[Iterable[int]] = None, + command_desc: Optional[str] = None, + extra_environ: Optional[Mapping[str, Any]] = None, + spinner: Optional[SpinnerInterface] = None, + log_failed_cmd: bool = True, + stdout_only: bool = False, + ) -> str: + """ + Run a VCS subcommand + This is simply a wrapper around call_subprocess that adds the VCS + command name, and checks that the VCS is available + """ + cmd = make_command(cls.name, *cmd) + if command_desc is None: + command_desc = format_command_args(cmd) + try: + return call_subprocess( + cmd, + show_stdout, + cwd, + on_returncode=on_returncode, + extra_ok_returncodes=extra_ok_returncodes, + command_desc=command_desc, + extra_environ=extra_environ, + unset_environ=cls.unset_environ, + spinner=spinner, + log_failed_cmd=log_failed_cmd, + stdout_only=stdout_only, + ) + except NotADirectoryError: + raise BadCommand(f"Cannot find command {cls.name!r} - invalid PATH") + except FileNotFoundError: + # errno.ENOENT = no such file or directory + # In other words, the VCS executable isn't available + raise BadCommand( + f"Cannot find command {cls.name!r} - do you have " + f"{cls.name!r} installed and in your PATH?" + ) + except PermissionError: + # errno.EACCES = Permission denied + # This error occurs, for instance, when the command is installed + # only for another user. So, the current user don't have + # permission to call the other user command. + raise BadCommand( + f"No permission to execute {cls.name!r} - install it " + f"locally, globally (ask admin), or check your PATH. " + f"See possible solutions at " + f"https://pip.pypa.io/en/latest/reference/pip_freeze/" + f"#fixing-permission-denied." + ) + + @classmethod + def is_repository_directory(cls, path: str) -> bool: + """ + Return whether a directory path is a repository directory. + """ + logger.debug("Checking in %s for %s (%s)...", path, cls.dirname, cls.name) + return os.path.exists(os.path.join(path, cls.dirname)) + + @classmethod + def get_repository_root(cls, location: str) -> Optional[str]: + """ + Return the "root" (top-level) directory controlled by the vcs, + or `None` if the directory is not in any. + + It is meant to be overridden to implement smarter detection + mechanisms for specific vcs. + + This can do more than is_repository_directory() alone. For + example, the Git override checks that Git is actually available. + """ + if cls.is_repository_directory(location): + return location + return None diff --git a/venv/lib/python3.8/site-packages/pip/_internal/wheel_builder.py b/venv/lib/python3.8/site-packages/pip/_internal/wheel_builder.py new file mode 100644 index 000000000..93f8e1f5b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_internal/wheel_builder.py @@ -0,0 +1,354 @@ +"""Orchestrator for building wheels from InstallRequirements. +""" + +import logging +import os.path +import re +import shutil +from typing import Iterable, List, Optional, Tuple + +from pip._vendor.packaging.utils import canonicalize_name, canonicalize_version +from pip._vendor.packaging.version import InvalidVersion, Version + +from pip._internal.cache import WheelCache +from pip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel +from pip._internal.metadata import FilesystemWheel, get_wheel_distribution +from pip._internal.models.link import Link +from pip._internal.models.wheel import Wheel +from pip._internal.operations.build.wheel import build_wheel_pep517 +from pip._internal.operations.build.wheel_editable import build_wheel_editable +from pip._internal.operations.build.wheel_legacy import build_wheel_legacy +from pip._internal.req.req_install import InstallRequirement +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ensure_dir, hash_file +from pip._internal.utils.setuptools_build import make_setuptools_clean_args +from pip._internal.utils.subprocess import call_subprocess +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.urls import path_to_url +from pip._internal.vcs import vcs + +logger = logging.getLogger(__name__) + +_egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE) + +BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]] + + +def _contains_egg_info(s: str) -> bool: + """Determine whether the string looks like an egg_info. + + :param s: The string to parse. E.g. foo-2.1 + """ + return bool(_egg_info_re.search(s)) + + +def _should_build( + req: InstallRequirement, + need_wheel: bool, +) -> bool: + """Return whether an InstallRequirement should be built into a wheel.""" + if req.constraint: + # never build requirements that are merely constraints + return False + if req.is_wheel: + if need_wheel: + logger.info( + "Skipping %s, due to already being wheel.", + req.name, + ) + return False + + if need_wheel: + # i.e. pip wheel, not pip install + return True + + # From this point, this concerns the pip install command only + # (need_wheel=False). + + if not req.source_dir: + return False + + if req.editable: + # we only build PEP 660 editable requirements + return req.supports_pyproject_editable + + return True + + +def should_build_for_wheel_command( + req: InstallRequirement, +) -> bool: + return _should_build(req, need_wheel=True) + + +def should_build_for_install_command( + req: InstallRequirement, +) -> bool: + return _should_build(req, need_wheel=False) + + +def _should_cache( + req: InstallRequirement, +) -> Optional[bool]: + """ + Return whether a built InstallRequirement can be stored in the persistent + wheel cache, assuming the wheel cache is available, and _should_build() + has determined a wheel needs to be built. + """ + if req.editable or not req.source_dir: + # never cache editable requirements + return False + + if req.link and req.link.is_vcs: + # VCS checkout. Do not cache + # unless it points to an immutable commit hash. + assert not req.editable + assert req.source_dir + vcs_backend = vcs.get_backend_for_scheme(req.link.scheme) + assert vcs_backend + if vcs_backend.is_immutable_rev_checkout(req.link.url, req.source_dir): + return True + return False + + assert req.link + base, ext = req.link.splitext() + if _contains_egg_info(base): + return True + + # Otherwise, do not cache. + return False + + +def _get_cache_dir( + req: InstallRequirement, + wheel_cache: WheelCache, +) -> str: + """Return the persistent or temporary cache directory where the built + wheel need to be stored. + """ + cache_available = bool(wheel_cache.cache_dir) + assert req.link + if cache_available and _should_cache(req): + cache_dir = wheel_cache.get_path_for_link(req.link) + else: + cache_dir = wheel_cache.get_ephem_path_for_link(req.link) + return cache_dir + + +def _verify_one(req: InstallRequirement, wheel_path: str) -> None: + canonical_name = canonicalize_name(req.name or "") + w = Wheel(os.path.basename(wheel_path)) + if canonicalize_name(w.name) != canonical_name: + raise InvalidWheelFilename( + f"Wheel has unexpected file name: expected {canonical_name!r}, " + f"got {w.name!r}", + ) + dist = get_wheel_distribution(FilesystemWheel(wheel_path), canonical_name) + dist_verstr = str(dist.version) + if canonicalize_version(dist_verstr) != canonicalize_version(w.version): + raise InvalidWheelFilename( + f"Wheel has unexpected file name: expected {dist_verstr!r}, " + f"got {w.version!r}", + ) + metadata_version_value = dist.metadata_version + if metadata_version_value is None: + raise UnsupportedWheel("Missing Metadata-Version") + try: + metadata_version = Version(metadata_version_value) + except InvalidVersion: + msg = f"Invalid Metadata-Version: {metadata_version_value}" + raise UnsupportedWheel(msg) + if metadata_version >= Version("1.2") and not isinstance(dist.version, Version): + raise UnsupportedWheel( + f"Metadata 1.2 mandates PEP 440 version, but {dist_verstr!r} is not" + ) + + +def _build_one( + req: InstallRequirement, + output_dir: str, + verify: bool, + build_options: List[str], + global_options: List[str], + editable: bool, +) -> Optional[str]: + """Build one wheel. + + :return: The filename of the built wheel, or None if the build failed. + """ + artifact = "editable" if editable else "wheel" + try: + ensure_dir(output_dir) + except OSError as e: + logger.warning( + "Building %s for %s failed: %s", + artifact, + req.name, + e, + ) + return None + + # Install build deps into temporary directory (PEP 518) + with req.build_env: + wheel_path = _build_one_inside_env( + req, output_dir, build_options, global_options, editable + ) + if wheel_path and verify: + try: + _verify_one(req, wheel_path) + except (InvalidWheelFilename, UnsupportedWheel) as e: + logger.warning("Built %s for %s is invalid: %s", artifact, req.name, e) + return None + return wheel_path + + +def _build_one_inside_env( + req: InstallRequirement, + output_dir: str, + build_options: List[str], + global_options: List[str], + editable: bool, +) -> Optional[str]: + with TempDirectory(kind="wheel") as temp_dir: + assert req.name + if req.use_pep517: + assert req.metadata_directory + assert req.pep517_backend + if global_options: + logger.warning( + "Ignoring --global-option when building %s using PEP 517", req.name + ) + if build_options: + logger.warning( + "Ignoring --build-option when building %s using PEP 517", req.name + ) + if editable: + wheel_path = build_wheel_editable( + name=req.name, + backend=req.pep517_backend, + metadata_directory=req.metadata_directory, + tempd=temp_dir.path, + ) + else: + wheel_path = build_wheel_pep517( + name=req.name, + backend=req.pep517_backend, + metadata_directory=req.metadata_directory, + tempd=temp_dir.path, + ) + else: + wheel_path = build_wheel_legacy( + name=req.name, + setup_py_path=req.setup_py_path, + source_dir=req.unpacked_source_directory, + global_options=global_options, + build_options=build_options, + tempd=temp_dir.path, + ) + + if wheel_path is not None: + wheel_name = os.path.basename(wheel_path) + dest_path = os.path.join(output_dir, wheel_name) + try: + wheel_hash, length = hash_file(wheel_path) + shutil.move(wheel_path, dest_path) + logger.info( + "Created wheel for %s: filename=%s size=%d sha256=%s", + req.name, + wheel_name, + length, + wheel_hash.hexdigest(), + ) + logger.info("Stored in directory: %s", output_dir) + return dest_path + except Exception as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, + e, + ) + # Ignore return, we can't do anything else useful. + if not req.use_pep517: + _clean_one_legacy(req, global_options) + return None + + +def _clean_one_legacy(req: InstallRequirement, global_options: List[str]) -> bool: + clean_args = make_setuptools_clean_args( + req.setup_py_path, + global_options=global_options, + ) + + logger.info("Running setup.py clean for %s", req.name) + try: + call_subprocess( + clean_args, command_desc="python setup.py clean", cwd=req.source_dir + ) + return True + except Exception: + logger.error("Failed cleaning build dir for %s", req.name) + return False + + +def build( + requirements: Iterable[InstallRequirement], + wheel_cache: WheelCache, + verify: bool, + build_options: List[str], + global_options: List[str], +) -> BuildResult: + """Build wheels. + + :return: The list of InstallRequirement that succeeded to build and + the list of InstallRequirement that failed to build. + """ + if not requirements: + return [], [] + + # Build the wheels. + logger.info( + "Building wheels for collected packages: %s", + ", ".join(req.name for req in requirements), # type: ignore + ) + + with indent_log(): + build_successes, build_failures = [], [] + for req in requirements: + assert req.name + cache_dir = _get_cache_dir(req, wheel_cache) + wheel_file = _build_one( + req, + cache_dir, + verify, + build_options, + global_options, + req.editable and req.permit_editable_wheels, + ) + if wheel_file: + # Record the download origin in the cache + if req.download_info is not None: + # download_info is guaranteed to be set because when we build an + # InstallRequirement it has been through the preparer before, but + # let's be cautious. + wheel_cache.record_download_origin(cache_dir, req.download_info) + # Update the link for this. + req.link = Link(path_to_url(wheel_file)) + req.local_file_path = req.link.file_path + assert req.link.is_wheel + build_successes.append(req) + else: + build_failures.append(req) + + # notify success/failure + if build_successes: + logger.info( + "Successfully built %s", + " ".join([req.name for req in build_successes]), # type: ignore + ) + if build_failures: + logger.info( + "Failed to build %s", + " ".join([req.name for req in build_failures]), # type: ignore + ) + # Return a list of requirements that failed to build + return build_successes, build_failures diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/__init__.py new file mode 100644 index 000000000..561089ccc --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/__init__.py @@ -0,0 +1,116 @@ +""" +pip._vendor is for vendoring dependencies of pip to prevent needing pip to +depend on something external. + +Files inside of pip._vendor should be considered immutable and should only be +updated to versions from upstream. +""" +from __future__ import absolute_import + +import glob +import os.path +import sys + +# Downstream redistributors which have debundled our dependencies should also +# patch this value to be true. This will trigger the additional patching +# to cause things like "six" to be available as pip. +DEBUNDLED = False + +# By default, look in this directory for a bunch of .whl files which we will +# add to the beginning of sys.path before attempting to import anything. This +# is done to support downstream re-distributors like Debian and Fedora who +# wish to create their own Wheels for our dependencies to aid in debundling. +WHEEL_DIR = os.path.abspath(os.path.dirname(__file__)) + + +# Define a small helper function to alias our vendored modules to the real ones +# if the vendored ones do not exist. This idea of this was taken from +# https://github.com/kennethreitz/requests/pull/2567. +def vendored(modulename): + vendored_name = "{0}.{1}".format(__name__, modulename) + + try: + __import__(modulename, globals(), locals(), level=0) + except ImportError: + # We can just silently allow import failures to pass here. If we + # got to this point it means that ``import pip._vendor.whatever`` + # failed and so did ``import whatever``. Since we're importing this + # upfront in an attempt to alias imports, not erroring here will + # just mean we get a regular import error whenever pip *actually* + # tries to import one of these modules to use it, which actually + # gives us a better error message than we would have otherwise + # gotten. + pass + else: + sys.modules[vendored_name] = sys.modules[modulename] + base, head = vendored_name.rsplit(".", 1) + setattr(sys.modules[base], head, sys.modules[modulename]) + + +# If we're operating in a debundled setup, then we want to go ahead and trigger +# the aliasing of our vendored libraries as well as looking for wheels to add +# to our sys.path. This will cause all of this code to be a no-op typically +# however downstream redistributors can enable it in a consistent way across +# all platforms. +if DEBUNDLED: + # Actually look inside of WHEEL_DIR to find .whl files and add them to the + # front of our sys.path. + sys.path[:] = glob.glob(os.path.join(WHEEL_DIR, "*.whl")) + sys.path + + # Actually alias all of our vendored dependencies. + vendored("cachecontrol") + vendored("certifi") + vendored("distlib") + vendored("distro") + vendored("packaging") + vendored("packaging.version") + vendored("packaging.specifiers") + vendored("pkg_resources") + vendored("platformdirs") + vendored("progress") + vendored("pyproject_hooks") + vendored("requests") + vendored("requests.exceptions") + vendored("requests.packages") + vendored("requests.packages.urllib3") + vendored("requests.packages.urllib3._collections") + vendored("requests.packages.urllib3.connection") + vendored("requests.packages.urllib3.connectionpool") + vendored("requests.packages.urllib3.contrib") + vendored("requests.packages.urllib3.contrib.ntlmpool") + vendored("requests.packages.urllib3.contrib.pyopenssl") + vendored("requests.packages.urllib3.exceptions") + vendored("requests.packages.urllib3.fields") + vendored("requests.packages.urllib3.filepost") + vendored("requests.packages.urllib3.packages") + vendored("requests.packages.urllib3.packages.ordered_dict") + vendored("requests.packages.urllib3.packages.six") + vendored("requests.packages.urllib3.packages.ssl_match_hostname") + vendored("requests.packages.urllib3.packages.ssl_match_hostname." + "_implementation") + vendored("requests.packages.urllib3.poolmanager") + vendored("requests.packages.urllib3.request") + vendored("requests.packages.urllib3.response") + vendored("requests.packages.urllib3.util") + vendored("requests.packages.urllib3.util.connection") + vendored("requests.packages.urllib3.util.request") + vendored("requests.packages.urllib3.util.response") + vendored("requests.packages.urllib3.util.retry") + vendored("requests.packages.urllib3.util.ssl_") + vendored("requests.packages.urllib3.util.timeout") + vendored("requests.packages.urllib3.util.url") + vendored("resolvelib") + vendored("rich") + vendored("rich.console") + vendored("rich.highlighter") + vendored("rich.logging") + vendored("rich.markup") + vendored("rich.progress") + vendored("rich.segment") + vendored("rich.style") + vendored("rich.text") + vendored("rich.traceback") + if sys.version_info < (3, 11): + vendored("tomli") + vendored("truststore") + vendored("urllib3") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50d503bf3849c26f109ff300244413b5a77d1777 GIT binary patch literal 3073 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__init__.py new file mode 100644 index 000000000..21916243c --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__init__.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 + +"""CacheControl import Interface. + +Make it easy to import from cachecontrol without long namespaces. +""" + +__author__ = "Eric Larson" +__email__ = "eric@ionrock.org" +__version__ = "0.14.1" + +from pip._vendor.cachecontrol.adapter import CacheControlAdapter +from pip._vendor.cachecontrol.controller import CacheController +from pip._vendor.cachecontrol.wrapper import CacheControl + +__all__ = [ + "__author__", + "__email__", + "__version__", + "CacheControlAdapter", + "CacheController", + "CacheControl", +] + +import logging + +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1935236a831e712d31c9a94016f0b7415d65b497 GIT binary patch literal 801 zcmaJ;&5qMB5KhvxX_Ee|#2rQA)I$?i%Yg%0En1L3Dtkd(ahB>*z z+}vee?y+9pV?OH(zv}0G*Fk?E233#;CqEFwD$GOlhb&|fi`j^c%RwF?ZX)75hWKFw zg9L2r6=jxJ&+AD!9su)L+k8g7-eMu~)1S{a+Xmln*57LZ2K2fBfT+XQI6PptX_ zSs7I&MO#~(Ywx*TtF29hl4T-k1+~Twbs8Pww}uzVf|^>%!v$b)!<96u*rdvohr#po z@!D&E4#x2?$}Tz^i_nZGXT7X!1mJonobA%B0;nQ~I0IsaO8&pRltm$n^B!e;VE! zsx`t|N-ZBMTHb+BWyxjPgeBMo`e2$NA+4}@8&SsIQOU4<6W?uxI0s)HGi_&R&Ew~6 ztt!ai02>Y>djmTVN>?ClwxG*8(|eRl&h^!NWdN2O=GM?+117_{cbVYJGvq3Lnbq9F z9Dj5}OTbdDv$Hx`SDlOyF1aNn)%#}ns#0t#;JVeeeu~EL`z~&8idB=u`0{z(o+sKTBiJx|E!gV^eURI{^9$u&-uu}O?(hwx_4ysp z?((}v+vHoIZPuRKd5y?_Wuyl{Uf3Y8qpYM;q`D*eE44xD}EJUeFkT=KCbS}nurevNxpcJs4vTJ_bRNHC5Ir zyM`VpYi(bXOSCms1TCCEjdU_}qzAs*D}_1`=OWQA<`QQ!-Pqgz^7xtV9x5rK^m&wV zOraHVUdxb(JFm>Y61fRs{$2X>JQcoJinCZG{vJlm3k1tQh)znsxKQ&v+btgLzLY|o z#9~*;XnH23e=f3fKZ#Fjmq(+|{4!Qz7oa$cW&)1Jg&%?!&!s;_NT(H~emG6}sJPHw zC}B~Ab^1GyP6<2(G&_DB}hY8Vk*+br2&g}>y&jP%@>){t-5|}O27Jz|1CpHyHUk8ZI@81 zX-~CmVYK#4s1P>*gb%<_B3^`A)Fu{wUOUVK;5rsS_aW@aCM<8Eg?zK&HE3Z7MXrEh zD{@K!F^jWThz(g=*T^(`qS-fSrFN9CuWzm+@Y{U-PCK&f)aZkyg(PzB7* zrKN1V4_5TFamiL}?X0X*7hccwovoXwUsL(DYH@l6QjpA$sJ|%L2P=`9K!yMawZLvP<(3T}}S9@WIo?$rL(XB9ixqwSY zgrUSikv-!9KUX!wG|f>L1za5xvDIyu*ik*PhQh(%mStq>s~J10xoS8cJb160Ib&ub z_~iXg)x7|7VXijAT#5f(6}pDa$cQ&JW^m;~Rd|8jTFjVupQkxrB;pBHR1RRliNLvl RkNLJyW<96Zf%l$A{{sco)_wp0 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47837145106ffec9bcaab585618f0f47fb147bc3 GIT binary patch literal 4266 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 tpeOy@M{|!a?Pbv(5bQCTqit7FsGD0aULKRwF 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 L?@i6ni`oALbuE79 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2090a987cd58f485bb2b26d1a72adb42e7715aa5 GIT binary patch literal 3169 zcmai0O>^7E8O8!2NKq7{$c}3}uCsLxF%yaGG(Gf?=~yF6Nv9r8CQ_S*nG6hKmm(~D zb$2OQ8v4+&ul)x;NXPtzUfW-RYfm}%)=T@m3(A%?>=Ya<_v77X-}iYx7Jpb4-oyUpb=r(jBb|ci-u?exeh5tXhroyfBeTid+vd-WW!i z6bCXZ3l*u;y(E=AuUb3!?(5xf=d0bF-~Vp-W#w(>r&VhwO4DeJbyju0Iym_H8>vg6 z$e5t7K0XGI$e5Q0gU<4a^!WEwIMdP(+6@)>eC00cHwJTOd=aoNdh&9oD&BS|R z9isrUGS-RSVwMXY@kz;x@iQ44elktsDL+XdWu)ZtSgKPqP4YvoWF&Z! zn}SCcYCM}vq}t>W5Kg8t=VlriKEbIx(L7EIEg=s?!jjKrJTnqU#S8T?Z>HEIEm&OS zM&?HIVp8MO=>4``-3CHrj7rA9&`?|QQWeh=8~}BI!C{6}=_%=HHK?swY(JPQ); zktj~U#alh!94@(tOhlw;19T!0JebR?q;<0!EK*XMj&^R&nh?ctHO z_P)DYbCD=`uTZD7UnKfyYa9{(4|S1AlVq?uF`oe1f`9;sLL@$tV(aNsla&+w^eHcL z{v^p~&wHD-%GPybq9!dOQL|0pJed%Qz_4&&Nb4DR!`aj1a0-?83dOaJHF8I-v)wt< zGfUV>k$+-1VUwB&9L#Q7H0Vk8asoW&5U>!&6X)#L$ZOMyT zDXAuf%A!05N>)a(>DzhLL|&fVzs%?FzaO6>rheGu!y=bI@VWi3d>Ww^xRbJKU50EG zC~0OYkC9Dp9_mxh?@-R)N5`OuQLg{hojW0$GqmoU`kru*q5km9Q{&GLP)GtHQ)y(+ zR|Hbs374d97$Os@+sl`r-2LS%mny>qU(0l&s0yo9G(S3O1AqVi%V3Jo7|1hul*n}O z7@3|HWrlzrJc-77P$KP%e5?F?YoeqaCvwXuiWfCFmih4@O~wn6&-=d_=)}k^aC{UU zN^DKaK?o^PsDbqpWw$EQ0l9Hed-^4cY8WPYV#4qjP)G-89Iwj)c6M(G5&cWy%F{-v z284TV<>#p2s)Ir7(*DIme9~*3auc1oa}Kv$uotdja0Yux_kufA?_muv>tT2(aWO1P zOc++p#kl9G_aUQhQDaFY_AG_!271-%o2_B^5EGh$!C7guvyYZru>i&y{^GBHuCYQCmG=4*kA!a`#V{GTsZs+!dLTX}!y{lBAs-UjjJ-4>}9tmDEx(jFMa8ib~=&vE7iDrkntn0#uuFG0z z(euw9Ea%}WKz!YL3I=Qa7#*@*4bif5_X2J_a@AeozF_&McFse-u?v?u#y5>;4LcW* z97av#q4$!369m8<0YD!S$VI|r=dBB{x5!FGD*OvCeJBJd?x<&io5UZQ{Q{OwB22*svGa;HiB2BB;4(oG4U)>cR42bk);}a^%NFQhjQ9 zrQ7iuDe5zz(dK_rr}O`IZ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd20b76e07738da2a81433a01c11e4f779edfe3b GIT binary patch literal 5328 zcmb7I%WoUU8Q&M5qGVcf1mvbuKDwyEw!PSY`Wx9Q^U zMqbxzdfi+z*UdNcM#hT@-D0!YEj3Hsa`LVc#)TQna@0Qd4O#2jfZd6xJDmyQIMIO)lUS|S+_cC8 zEp*rwX^BcMA|0efst%G^K%m@CWH-;fRKip%ld7)=5S!ij@ZDSfjn!K>-g;yG z_0+u{@1%vbpw|oIyLfD6bz|e*_l4@=v0`v$DLBuTI9u9eOCPeO)hkPDSC($G*Vi_r z2Q>%B#!9gvTZbCbCFR{Tm#OC&4f0axzKmNA?QHa;I?3Lkqdbku(C*D?>yfRUd+xry zYY*+Ab;`PJeQvMUJhN=-#>uv%^H1HrZq*z)i@Kac@wp{|73;~zjZV@P4KWZ~p@aR3ZsKrVT z!@*sFsbR0-Ly9M|(F$4}(Mn=1lc>=VeHkhpwp6{hlUBroUMPhhw6&1)&`FV>Bx<%i1sP+OtXQNe^W*dKb>eqKz=aW0=4fnr7BVedQ$ISUYjjLk zQ4vp(e2?dVd>8G!X%~1AZ4YhRwBa#m=lBe;T}fwO4U||NTTE84;*&B= zGJ?B>}B>jiW#zzu@9M*!y+{&cpjZ)Hu}A9u*L#*KZrukEg7rA2IR)tu~4y|OzwwVaHf-l1yPi2j~}I2 z6mAK2;licKMzwVv+a(M zf^7>H3&AJL=!5}bQP>J~w8K=eGo?{-Ur2WGGV3ILsaO*6`efqZ%!LatvK4l2?Ax@1 z+ZqTuXYc{jdrCxY`E3~XC~OKN6F!Tv3@fFKr=L2& z5Ds3m2mij)BV3w;m>4)aEycD&-C;qz13PSwj_~lItiQ8y>k8ZG&~8@Wx%2i-oWypp zqgWWXqCOW)AtJC3rXiq{iCr?-sPJwaws)XGpr_T?4pasX&NgWrP>DhS<2*xjKC%h~ z@MxB_jobttqn#X_MP^?1C5E2s8%vB_Q+;n@@512OX?sr#PMMipgD&GI*HA$OL3~A2 zb%u_-I<)UmRB`rgn?_tS;tr88w@TwtZV%lMGazAnY&|WEOo-s~9W0p^M!QP$nI`fa zdNKGsZU#fmW7z;7Z>G68!pNw@H;#A|YB^-0Xy-+&qA0RkcG>alie0vgcGVdyOdG+- zuGjKu(f8w^D||mK`+hg!{fO!n-~Xr|MA?W@(C99|PdZYeJ4(jtA8+O)gXM0~Be&_j%It{R~eB4>L9M#PblNglY1B(e`qZdcGP+ z*D(6^KqC4HV1a;|gjxUzrS>eJLp#rp;S8#2%>0>KRSf8;$_s2oDunO7>C;T?_hAmNCqbk@25VN_&Us!LUi3ArHEN-M7eul zPrHsr)Y~Is;yfh8y6f^H#-Wpe%q6P38RQJJq)32{NkscfWc>_+=A03&?7`V7m8YR3 zpM&U>i;}8jN24y`mzyvRm93MM9i8>Tl`L~#@9PBoJ_PGS>Nh^a%^@zSI-6wmZg{t& z*(UNH<#xE5G5euiq@M2fq9evP!SNS(`e+-TJy@LD#wc{J*9u2mL0-lz`3e=}WLcE` zGKY8xqwnHUMHFxduj*DkNxdyxQ@(miOSPXL9vyCXJ&6>ywaHg--H_+dK1f` zJ#X3ze9^ea3E1*vT6sHAdM)8$JH*@3pAe#qhrD4jHs}^uM4YhE&c}o?h$NWu&Uw~` z<=H0Q2v`y`T4`mxlv$;(#%yx(PQcnS>6*d+ga2KwU#wp=lfd?H+O2*>>?nZi1}cez z%}8V#qX*30~KGOI0zw#ZXU7Dr(ZY-hm?klZ~g}r zN}MKvI$L=;PwbT=q@ccxL`ccf-E!qJ^%Kd04?9S+SB#toxq^C==jblisZYC@k? zqhG46y)zO|dzC0-cjhu&#x&)G$S zHY?=z?bFt7k;Zg+SmedC7BB5NkSGmH!@`y=|7kS!hNV;1ZkgM<_-IB~P|gm^`}V#Q z67?&uA4EqJE=}$p%ti!%FfHRrlK7g0RjtRAHNubIv#r{k$;oLRfk^jN`kaYa1YUo0 z$4A-;`jPgtL7^l?g>>$^QbN*8&@CwwDJgu*GgQzqAaWr|rEWKPfFg|3BHl3Crmwj1 z#@a3awY7~jf456LwV5n#|AZzJpJt7LL;sW?FqI!R~#$^P9+- z&f`mg>cm1RKMLFC1I$nUj+M@vlmD8!P&!6 z=?M9FI%80_AX6Ju63?GxS`*aADc0=#AP^f;at_iDagDR<;?#q`A;!OoiQf?8uRtWA z^@)jMqq7c3)>Hp4ytyiqBwNGBozq0wGeQeKMVrfx{U+61Drk?{cb^&24Z^72*I}g0 zH;{}a8tmlt4t>IsC6Xo-N^;-k0zTZ-M;~tLeHq~!&!x=wS4qYYOiJ{I=Npb@a*|Q+ sP?DSwyR>)}kiq-Kk4Y`{EQ$)wvxw8gpWam*&t1$_y}9SBi`C=*1I%LPxc~qF literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df0039b35ef9d44d982952874d05352ed0a1c6ed GIT binary patch literal 3305 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 Y&@_nKAV${2(lpxez^bQ%aA*GYH!Wt4VgLXD literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/_cmd.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/_cmd.py new file mode 100644 index 000000000..2c84208a5 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/_cmd.py @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import logging +from argparse import ArgumentParser +from typing import TYPE_CHECKING + +from pip._vendor import requests + +from pip._vendor.cachecontrol.adapter import CacheControlAdapter +from pip._vendor.cachecontrol.cache import DictCache +from pip._vendor.cachecontrol.controller import logger + +if TYPE_CHECKING: + from argparse import Namespace + + from pip._vendor.cachecontrol.controller import CacheController + + +def setup_logging() -> None: + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler() + logger.addHandler(handler) + + +def get_session() -> requests.Session: + adapter = CacheControlAdapter( + DictCache(), cache_etags=True, serializer=None, heuristic=None + ) + sess = requests.Session() + sess.mount("http://", adapter) + sess.mount("https://", adapter) + + sess.cache_controller = adapter.controller # type: ignore[attr-defined] + return sess + + +def get_args() -> Namespace: + parser = ArgumentParser() + parser.add_argument("url", help="The URL to try and cache") + return parser.parse_args() + + +def main() -> None: + args = get_args() + sess = get_session() + + # Make a request to get a response + resp = sess.get(args.url) + + # Turn on logging + setup_logging() + + # try setting the cache + cache_controller: CacheController = ( + sess.cache_controller # type: ignore[attr-defined] + ) + cache_controller.cache_response(resp.request, resp.raw) + + # Now try to get it + if cache_controller.cached_request(resp.request): + print("Cached!") + else: + print("Not cached :(") + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/adapter.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/adapter.py new file mode 100644 index 000000000..34a9eb827 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/adapter.py @@ -0,0 +1,161 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import functools +import types +import zlib +from typing import TYPE_CHECKING, Any, Collection, Mapping + +from pip._vendor.requests.adapters import HTTPAdapter + +from pip._vendor.cachecontrol.cache import DictCache +from pip._vendor.cachecontrol.controller import PERMANENT_REDIRECT_STATUSES, CacheController +from pip._vendor.cachecontrol.filewrapper import CallbackFileWrapper + +if TYPE_CHECKING: + from pip._vendor.requests import PreparedRequest, Response + from pip._vendor.urllib3 import HTTPResponse + + from pip._vendor.cachecontrol.cache import BaseCache + from pip._vendor.cachecontrol.heuristics import BaseHeuristic + from pip._vendor.cachecontrol.serialize import Serializer + + +class CacheControlAdapter(HTTPAdapter): + invalidating_methods = {"PUT", "PATCH", "DELETE"} + + def __init__( + self, + cache: BaseCache | None = None, + cache_etags: bool = True, + controller_class: type[CacheController] | None = None, + serializer: Serializer | None = None, + heuristic: BaseHeuristic | None = None, + cacheable_methods: Collection[str] | None = None, + *args: Any, + **kw: Any, + ) -> None: + super().__init__(*args, **kw) + self.cache = DictCache() if cache is None else cache + self.heuristic = heuristic + self.cacheable_methods = cacheable_methods or ("GET",) + + controller_factory = controller_class or CacheController + self.controller = controller_factory( + self.cache, cache_etags=cache_etags, serializer=serializer + ) + + def send( + self, + request: PreparedRequest, + stream: bool = False, + timeout: None | float | tuple[float, float] | tuple[float, None] = None, + verify: bool | str = True, + cert: (None | bytes | str | tuple[bytes | str, bytes | str]) = None, + proxies: Mapping[str, str] | None = None, + cacheable_methods: Collection[str] | None = None, + ) -> Response: + """ + Send a request. Use the request information to see if it + exists in the cache and cache the response if we need to and can. + """ + cacheable = cacheable_methods or self.cacheable_methods + if request.method in cacheable: + try: + cached_response = self.controller.cached_request(request) + except zlib.error: + cached_response = None + if cached_response: + return self.build_response(request, cached_response, from_cache=True) + + # check for etags and add headers if appropriate + request.headers.update(self.controller.conditional_headers(request)) + + resp = super().send(request, stream, timeout, verify, cert, proxies) + + return resp + + def build_response( # type: ignore[override] + self, + request: PreparedRequest, + response: HTTPResponse, + from_cache: bool = False, + cacheable_methods: Collection[str] | None = None, + ) -> Response: + """ + Build a response by making a request or using the cache. + + This will end up calling send and returning a potentially + cached response + """ + cacheable = cacheable_methods or self.cacheable_methods + if not from_cache and request.method in cacheable: + # Check for any heuristics that might update headers + # before trying to cache. + if self.heuristic: + response = self.heuristic.apply(response) + + # apply any expiration heuristics + if response.status == 304: + # We must have sent an ETag request. This could mean + # that we've been expired already or that we simply + # have an etag. In either case, we want to try and + # update the cache if that is the case. + cached_response = self.controller.update_cached_response( + request, response + ) + + if cached_response is not response: + from_cache = True + + # We are done with the server response, read a + # possible response body (compliant servers will + # not return one, but we cannot be 100% sure) and + # release the connection back to the pool. + response.read(decode_content=False) + response.release_conn() + + response = cached_response + + # We always cache the 301 responses + elif int(response.status) in PERMANENT_REDIRECT_STATUSES: + self.controller.cache_response(request, response) + else: + # Wrap the response file with a wrapper that will cache the + # response when the stream has been consumed. + response._fp = CallbackFileWrapper( # type: ignore[assignment] + response._fp, # type: ignore[arg-type] + functools.partial( + self.controller.cache_response, request, response + ), + ) + if response.chunked: + super_update_chunk_length = response._update_chunk_length + + def _update_chunk_length(self: HTTPResponse) -> None: + super_update_chunk_length() + if self.chunk_left == 0: + self._fp._close() # type: ignore[union-attr] + + response._update_chunk_length = types.MethodType( # type: ignore[method-assign] + _update_chunk_length, response + ) + + resp: Response = super().build_response(request, response) + + # See if we should invalidate the cache. + if request.method in self.invalidating_methods and resp.ok: + assert request.url is not None + cache_url = self.controller.cache_url(request.url) + self.cache.delete(cache_url) + + # Give the request a from_cache attr to let people use it + resp.from_cache = from_cache # type: ignore[attr-defined] + + return resp + + def close(self) -> None: + self.cache.close() + super().close() # type: ignore[no-untyped-call] diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/cache.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/cache.py new file mode 100644 index 000000000..91598e920 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/cache.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 + +""" +The cache object API for implementing caches. The default is a thread +safe in-memory dictionary. +""" + +from __future__ import annotations + +from threading import Lock +from typing import IO, TYPE_CHECKING, MutableMapping + +if TYPE_CHECKING: + from datetime import datetime + + +class BaseCache: + def get(self, key: str) -> bytes | None: + raise NotImplementedError() + + def set( + self, key: str, value: bytes, expires: int | datetime | None = None + ) -> None: + raise NotImplementedError() + + def delete(self, key: str) -> None: + raise NotImplementedError() + + def close(self) -> None: + pass + + +class DictCache(BaseCache): + def __init__(self, init_dict: MutableMapping[str, bytes] | None = None) -> None: + self.lock = Lock() + self.data = init_dict or {} + + def get(self, key: str) -> bytes | None: + return self.data.get(key, None) + + def set( + self, key: str, value: bytes, expires: int | datetime | None = None + ) -> None: + with self.lock: + self.data.update({key: value}) + + def delete(self, key: str) -> None: + with self.lock: + if key in self.data: + self.data.pop(key) + + +class SeparateBodyBaseCache(BaseCache): + """ + In this variant, the body is not stored mixed in with the metadata, but is + passed in (as a bytes-like object) in a separate call to ``set_body()``. + + That is, the expected interaction pattern is:: + + cache.set(key, serialized_metadata) + cache.set_body(key) + + Similarly, the body should be loaded separately via ``get_body()``. + """ + + def set_body(self, key: str, body: bytes) -> None: + raise NotImplementedError() + + def get_body(self, key: str) -> IO[bytes] | None: + """ + Return the body as file-like object. + """ + raise NotImplementedError() diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__init__.py new file mode 100644 index 000000000..24ff469ff --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 + +from pip._vendor.cachecontrol.caches.file_cache import FileCache, SeparateBodyFileCache +from pip._vendor.cachecontrol.caches.redis_cache import RedisCache + +__all__ = ["FileCache", "SeparateBodyFileCache", "RedisCache"] diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bef761c4901503158b5e2f8727b76813a25c0c9a GIT binary patch literal 446 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 R+r(WmrYaLFQ^l#7{{vCa&qn|N literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c5e28443d17d42032376c5df97c96babdfbebd2 GIT binary patch literal 1993 zcmZ`)PjBNy6rb_fiIXPTbldHAIUs{T!V--Sd*OhfqFwE-KoO#>1V)SHc&4pe$Ii}7 zy4%P-wFfSIgC3e=zXE4Ig|D3W3S8j5aiR@n8ELHd-n=*Sdw>0++ieqAzx^JhCk`Qh zAhWt`V0HIWb=;WQ&-8d8e1m04pew8kuChG&^Qc0$L{cGeg-!zLxdk^7+QWbU{X zwkUZ@c!M{u2ycq!B@5f!Js^JTPkhs-)mohAg^qPv|~rIOP^+*A}Y@Z7H(+LcOf)Av$GMc2eYd)sR9@Wu90o(2>Tyhv+d|B`rw=2{<6P zbm&uwGQ2^F?9ec`MD{@T*Dt|wF&2TCiPKbM!M->XSuq)lTnFF8BNa@}^>LB!OdjtX zN+Cw6*wHdhPK6B4M1B@z>1d(yc<@=EQZ05Sa2y{AFij^x1X{e1L4uJ7_*xcO&8R?P z5+ZXln4DK`6s38pqi7v)DdQcTZM+T2V0m6KrRA(+g5#CmA~@_+oXa&Z{ZC*R2u)x* zK$PT!+|ZKF=>d@_@GTRMiX91kYsoH%3QKWnRGnq69Q zq}h$Fos-6#Uf3l&X>xMv%H7gFaiO8;uSQzh+vLJAceF~X+of}}HYb-9ZiA-G`^%G( z)e@{4--4>C8hV!IhlSr+gh&ZpwNsVmO2>I3Bu2JkdGT7J3l*E{L~bM1lwy*_iKrYf z5pOC6L8-bL+BS<6)&W<3V6M3_zcv`sr^ zyYxM4wwe`q`u}U859Z6T&dsd4Qx6SzAFxZz`CSwrg6w11%UW1psfF4MTF%rW1+C@6 zlEyfh)OtYF@6X9TdD*{!hK0(sureum)#gy2zBPOPOk@)wy~9F!NhV^M=0{$fWu873 z9#p*Y(%g%&+TsxmxPkXV3Ga2PkK5j2CGiwT|YuyZ>ogyaB_(Rfq0Xm$iRzFy}a;pA-JataIFB`=+j%p z!O(;qB58W^-|dJn!DN6i&8V>MfP_OICSME3N*<3z6jkjg8W((;A>E0hSJODFPb3zD zX>f`4RmbKdq%g@V$u}6xG;rYntrUt}+Gl;6{JqudvtC2u+0b9Z_YS8ps^L9}7)`s> zozl?f6NrI?-zf>-he0`549Y>WGAL_C;y4H$iPKUx@g98OU8pMe^V-2LFbfK&TZi>& L$Ld%%?XZ6Unvu>7 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py new file mode 100644 index 000000000..81d2ef46c --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py @@ -0,0 +1,182 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import hashlib +import os +from textwrap import dedent +from typing import IO, TYPE_CHECKING +from pathlib import Path + +from pip._vendor.cachecontrol.cache import BaseCache, SeparateBodyBaseCache +from pip._vendor.cachecontrol.controller import CacheController + +if TYPE_CHECKING: + from datetime import datetime + + from filelock import BaseFileLock + + +def _secure_open_write(filename: str, fmode: int) -> IO[bytes]: + # We only want to write to this file, so open it in write only mode + flags = os.O_WRONLY + + # os.O_CREAT | os.O_EXCL will fail if the file already exists, so we only + # will open *new* files. + # We specify this because we want to ensure that the mode we pass is the + # mode of the file. + flags |= os.O_CREAT | os.O_EXCL + + # Do not follow symlinks to prevent someone from making a symlink that + # we follow and insecurely open a cache file. + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + + # On Windows we'll mark this file as binary + if hasattr(os, "O_BINARY"): + flags |= os.O_BINARY + + # Before we open our file, we want to delete any existing file that is + # there + try: + os.remove(filename) + except OSError: + # The file must not exist already, so we can just skip ahead to opening + pass + + # Open our file, the use of os.O_CREAT | os.O_EXCL will ensure that if a + # race condition happens between the os.remove and this line, that an + # error will be raised. Because we utilize a lockfile this should only + # happen if someone is attempting to attack us. + fd = os.open(filename, flags, fmode) + try: + return os.fdopen(fd, "wb") + + except: + # An error occurred wrapping our FD in a file object + os.close(fd) + raise + + +class _FileCacheMixin: + """Shared implementation for both FileCache variants.""" + + def __init__( + self, + directory: str | Path, + forever: bool = False, + filemode: int = 0o0600, + dirmode: int = 0o0700, + lock_class: type[BaseFileLock] | None = None, + ) -> None: + try: + if lock_class is None: + from filelock import FileLock + + lock_class = FileLock + except ImportError: + notice = dedent( + """ + NOTE: In order to use the FileCache you must have + filelock installed. You can install it via pip: + pip install cachecontrol[filecache] + """ + ) + raise ImportError(notice) + + self.directory = directory + self.forever = forever + self.filemode = filemode + self.dirmode = dirmode + self.lock_class = lock_class + + @staticmethod + def encode(x: str) -> str: + return hashlib.sha224(x.encode()).hexdigest() + + def _fn(self, name: str) -> str: + # NOTE: This method should not change as some may depend on it. + # See: https://github.com/ionrock/cachecontrol/issues/63 + hashed = self.encode(name) + parts = list(hashed[:5]) + [hashed] + return os.path.join(self.directory, *parts) + + def get(self, key: str) -> bytes | None: + name = self._fn(key) + try: + with open(name, "rb") as fh: + return fh.read() + + except FileNotFoundError: + return None + + def set( + self, key: str, value: bytes, expires: int | datetime | None = None + ) -> None: + name = self._fn(key) + self._write(name, value) + + def _write(self, path: str, data: bytes) -> None: + """ + Safely write the data to the given path. + """ + # Make sure the directory exists + try: + os.makedirs(os.path.dirname(path), self.dirmode) + except OSError: + pass + + with self.lock_class(path + ".lock"): + # Write our actual file + with _secure_open_write(path, self.filemode) as fh: + fh.write(data) + + def _delete(self, key: str, suffix: str) -> None: + name = self._fn(key) + suffix + if not self.forever: + try: + os.remove(name) + except FileNotFoundError: + pass + + +class FileCache(_FileCacheMixin, BaseCache): + """ + Traditional FileCache: body is stored in memory, so not suitable for large + downloads. + """ + + def delete(self, key: str) -> None: + self._delete(key, "") + + +class SeparateBodyFileCache(_FileCacheMixin, SeparateBodyBaseCache): + """ + Memory-efficient FileCache: body is stored in a separate file, reducing + peak memory usage. + """ + + def get_body(self, key: str) -> IO[bytes] | None: + name = self._fn(key) + ".body" + try: + return open(name, "rb") + except FileNotFoundError: + return None + + def set_body(self, key: str, body: bytes) -> None: + name = self._fn(key) + ".body" + self._write(name, body) + + def delete(self, key: str) -> None: + self._delete(key, "") + self._delete(key, ".body") + + +def url_to_file_path(url: str, filecache: FileCache) -> str: + """Return the file cache path based on the URL. + + This does not ensure the file exists! + """ + key = CacheController.cache_url(url) + return filecache._fn(key) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py new file mode 100644 index 000000000..f4f68c47b --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + + +from datetime import datetime, timezone +from typing import TYPE_CHECKING + +from pip._vendor.cachecontrol.cache import BaseCache + +if TYPE_CHECKING: + from redis import Redis + + +class RedisCache(BaseCache): + def __init__(self, conn: Redis[bytes]) -> None: + self.conn = conn + + def get(self, key: str) -> bytes | None: + return self.conn.get(key) + + def set( + self, key: str, value: bytes, expires: int | datetime | None = None + ) -> None: + if not expires: + self.conn.set(key, value) + elif isinstance(expires, datetime): + now_utc = datetime.now(timezone.utc) + if expires.tzinfo is None: + now_utc = now_utc.replace(tzinfo=None) + delta = expires - now_utc + self.conn.setex(key, int(delta.total_seconds()), value) + else: + self.conn.setex(key, expires, value) + + def delete(self, key: str) -> None: + self.conn.delete(key) + + def clear(self) -> None: + """Helper for clearing all the keys in a database. Use with + caution!""" + for key in self.conn.keys(): + self.conn.delete(key) + + def close(self) -> None: + """Redis uses connection pooling, no need to close the connection.""" + pass diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/controller.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/controller.py new file mode 100644 index 000000000..f0ff6e1be --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/controller.py @@ -0,0 +1,500 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 + +""" +The httplib2 algorithms ported for use with requests. +""" + +from __future__ import annotations + +import calendar +import logging +import re +import time +from email.utils import parsedate_tz +from typing import TYPE_CHECKING, Collection, Mapping + +from pip._vendor.requests.structures import CaseInsensitiveDict + +from pip._vendor.cachecontrol.cache import DictCache, SeparateBodyBaseCache +from pip._vendor.cachecontrol.serialize import Serializer + +if TYPE_CHECKING: + from typing import Literal + + from pip._vendor.requests import PreparedRequest + from pip._vendor.urllib3 import HTTPResponse + + from pip._vendor.cachecontrol.cache import BaseCache + +logger = logging.getLogger(__name__) + +URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") + +PERMANENT_REDIRECT_STATUSES = (301, 308) + + +def parse_uri(uri: str) -> tuple[str, str, str, str, str]: + """Parses a URI using the regex given in Appendix B of RFC 3986. + + (scheme, authority, path, query, fragment) = parse_uri(uri) + """ + match = URI.match(uri) + assert match is not None + groups = match.groups() + return (groups[1], groups[3], groups[4], groups[6], groups[8]) + + +class CacheController: + """An interface to see if request should cached or not.""" + + def __init__( + self, + cache: BaseCache | None = None, + cache_etags: bool = True, + serializer: Serializer | None = None, + status_codes: Collection[int] | None = None, + ): + self.cache = DictCache() if cache is None else cache + self.cache_etags = cache_etags + self.serializer = serializer or Serializer() + self.cacheable_status_codes = status_codes or (200, 203, 300, 301, 308) + + @classmethod + def _urlnorm(cls, uri: str) -> str: + """Normalize the URL to create a safe key for the cache""" + (scheme, authority, path, query, fragment) = parse_uri(uri) + if not scheme or not authority: + raise Exception("Only absolute URIs are allowed. uri = %s" % uri) + + scheme = scheme.lower() + authority = authority.lower() + + if not path: + path = "/" + + # Could do syntax based normalization of the URI before + # computing the digest. See Section 6.2.2 of Std 66. + request_uri = query and "?".join([path, query]) or path + defrag_uri = scheme + "://" + authority + request_uri + + return defrag_uri + + @classmethod + def cache_url(cls, uri: str) -> str: + return cls._urlnorm(uri) + + def parse_cache_control(self, headers: Mapping[str, str]) -> dict[str, int | None]: + known_directives = { + # https://tools.ietf.org/html/rfc7234#section-5.2 + "max-age": (int, True), + "max-stale": (int, False), + "min-fresh": (int, True), + "no-cache": (None, False), + "no-store": (None, False), + "no-transform": (None, False), + "only-if-cached": (None, False), + "must-revalidate": (None, False), + "public": (None, False), + "private": (None, False), + "proxy-revalidate": (None, False), + "s-maxage": (int, True), + } + + cc_headers = headers.get("cache-control", headers.get("Cache-Control", "")) + + retval: dict[str, int | None] = {} + + for cc_directive in cc_headers.split(","): + if not cc_directive.strip(): + continue + + parts = cc_directive.split("=", 1) + directive = parts[0].strip() + + try: + typ, required = known_directives[directive] + except KeyError: + logger.debug("Ignoring unknown cache-control directive: %s", directive) + continue + + if not typ or not required: + retval[directive] = None + if typ: + try: + retval[directive] = typ(parts[1].strip()) + except IndexError: + if required: + logger.debug( + "Missing value for cache-control " "directive: %s", + directive, + ) + except ValueError: + logger.debug( + "Invalid value for cache-control directive " "%s, must be %s", + directive, + typ.__name__, + ) + + return retval + + def _load_from_cache(self, request: PreparedRequest) -> HTTPResponse | None: + """ + Load a cached response, or return None if it's not available. + """ + # We do not support caching of partial content: so if the request contains a + # Range header then we don't want to load anything from the cache. + if "Range" in request.headers: + return None + + cache_url = request.url + assert cache_url is not None + cache_data = self.cache.get(cache_url) + if cache_data is None: + logger.debug("No cache entry available") + return None + + if isinstance(self.cache, SeparateBodyBaseCache): + body_file = self.cache.get_body(cache_url) + else: + body_file = None + + result = self.serializer.loads(request, cache_data, body_file) + if result is None: + logger.warning("Cache entry deserialization failed, entry ignored") + return result + + def cached_request(self, request: PreparedRequest) -> HTTPResponse | Literal[False]: + """ + Return a cached response if it exists in the cache, otherwise + return False. + """ + assert request.url is not None + cache_url = self.cache_url(request.url) + logger.debug('Looking up "%s" in the cache', cache_url) + cc = self.parse_cache_control(request.headers) + + # Bail out if the request insists on fresh data + if "no-cache" in cc: + logger.debug('Request header has "no-cache", cache bypassed') + return False + + if "max-age" in cc and cc["max-age"] == 0: + logger.debug('Request header has "max_age" as 0, cache bypassed') + return False + + # Check whether we can load the response from the cache: + resp = self._load_from_cache(request) + if not resp: + return False + + # If we have a cached permanent redirect, return it immediately. We + # don't need to test our response for other headers b/c it is + # intrinsically "cacheable" as it is Permanent. + # + # See: + # https://tools.ietf.org/html/rfc7231#section-6.4.2 + # + # Client can try to refresh the value by repeating the request + # with cache busting headers as usual (ie no-cache). + if int(resp.status) in PERMANENT_REDIRECT_STATUSES: + msg = ( + "Returning cached permanent redirect response " + "(ignoring date and etag information)" + ) + logger.debug(msg) + return resp + + headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(resp.headers) + if not headers or "date" not in headers: + if "etag" not in headers: + # Without date or etag, the cached response can never be used + # and should be deleted. + logger.debug("Purging cached response: no date or etag") + self.cache.delete(cache_url) + logger.debug("Ignoring cached response: no date") + return False + + now = time.time() + time_tuple = parsedate_tz(headers["date"]) + assert time_tuple is not None + date = calendar.timegm(time_tuple[:6]) + current_age = max(0, now - date) + logger.debug("Current age based on date: %i", current_age) + + # TODO: There is an assumption that the result will be a + # urllib3 response object. This may not be best since we + # could probably avoid instantiating or constructing the + # response until we know we need it. + resp_cc = self.parse_cache_control(headers) + + # determine freshness + freshness_lifetime = 0 + + # Check the max-age pragma in the cache control header + max_age = resp_cc.get("max-age") + if max_age is not None: + freshness_lifetime = max_age + logger.debug("Freshness lifetime from max-age: %i", freshness_lifetime) + + # If there isn't a max-age, check for an expires header + elif "expires" in headers: + expires = parsedate_tz(headers["expires"]) + if expires is not None: + expire_time = calendar.timegm(expires[:6]) - date + freshness_lifetime = max(0, expire_time) + logger.debug("Freshness lifetime from expires: %i", freshness_lifetime) + + # Determine if we are setting freshness limit in the + # request. Note, this overrides what was in the response. + max_age = cc.get("max-age") + if max_age is not None: + freshness_lifetime = max_age + logger.debug( + "Freshness lifetime from request max-age: %i", freshness_lifetime + ) + + min_fresh = cc.get("min-fresh") + if min_fresh is not None: + # adjust our current age by our min fresh + current_age += min_fresh + logger.debug("Adjusted current age from min-fresh: %i", current_age) + + # Return entry if it is fresh enough + if freshness_lifetime > current_age: + logger.debug('The response is "fresh", returning cached response') + logger.debug("%i > %i", freshness_lifetime, current_age) + return resp + + # we're not fresh. If we don't have an Etag, clear it out + if "etag" not in headers: + logger.debug('The cached response is "stale" with no etag, purging') + self.cache.delete(cache_url) + + # return the original handler + return False + + def conditional_headers(self, request: PreparedRequest) -> dict[str, str]: + resp = self._load_from_cache(request) + new_headers = {} + + if resp: + headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(resp.headers) + + if "etag" in headers: + new_headers["If-None-Match"] = headers["ETag"] + + if "last-modified" in headers: + new_headers["If-Modified-Since"] = headers["Last-Modified"] + + return new_headers + + def _cache_set( + self, + cache_url: str, + request: PreparedRequest, + response: HTTPResponse, + body: bytes | None = None, + expires_time: int | None = None, + ) -> None: + """ + Store the data in the cache. + """ + if isinstance(self.cache, SeparateBodyBaseCache): + # We pass in the body separately; just put a placeholder empty + # string in the metadata. + self.cache.set( + cache_url, + self.serializer.dumps(request, response, b""), + expires=expires_time, + ) + # body is None can happen when, for example, we're only updating + # headers, as is the case in update_cached_response(). + if body is not None: + self.cache.set_body(cache_url, body) + else: + self.cache.set( + cache_url, + self.serializer.dumps(request, response, body), + expires=expires_time, + ) + + def cache_response( + self, + request: PreparedRequest, + response: HTTPResponse, + body: bytes | None = None, + status_codes: Collection[int] | None = None, + ) -> None: + """ + Algorithm for caching requests. + + This assumes a requests Response object. + """ + # From httplib2: Don't cache 206's since we aren't going to + # handle byte range requests + cacheable_status_codes = status_codes or self.cacheable_status_codes + if response.status not in cacheable_status_codes: + logger.debug( + "Status code %s not in %s", response.status, cacheable_status_codes + ) + return + + response_headers: CaseInsensitiveDict[str] = CaseInsensitiveDict( + response.headers + ) + + if "date" in response_headers: + time_tuple = parsedate_tz(response_headers["date"]) + assert time_tuple is not None + date = calendar.timegm(time_tuple[:6]) + else: + date = 0 + + # If we've been given a body, our response has a Content-Length, that + # Content-Length is valid then we can check to see if the body we've + # been given matches the expected size, and if it doesn't we'll just + # skip trying to cache it. + if ( + body is not None + and "content-length" in response_headers + and response_headers["content-length"].isdigit() + and int(response_headers["content-length"]) != len(body) + ): + return + + cc_req = self.parse_cache_control(request.headers) + cc = self.parse_cache_control(response_headers) + + assert request.url is not None + cache_url = self.cache_url(request.url) + logger.debug('Updating cache with response from "%s"', cache_url) + + # Delete it from the cache if we happen to have it stored there + no_store = False + if "no-store" in cc: + no_store = True + logger.debug('Response header has "no-store"') + if "no-store" in cc_req: + no_store = True + logger.debug('Request header has "no-store"') + if no_store and self.cache.get(cache_url): + logger.debug('Purging existing cache entry to honor "no-store"') + self.cache.delete(cache_url) + if no_store: + return + + # https://tools.ietf.org/html/rfc7234#section-4.1: + # A Vary header field-value of "*" always fails to match. + # Storing such a response leads to a deserialization warning + # during cache lookup and is not allowed to ever be served, + # so storing it can be avoided. + if "*" in response_headers.get("vary", ""): + logger.debug('Response header has "Vary: *"') + return + + # If we've been given an etag, then keep the response + if self.cache_etags and "etag" in response_headers: + expires_time = 0 + if response_headers.get("expires"): + expires = parsedate_tz(response_headers["expires"]) + if expires is not None: + expires_time = calendar.timegm(expires[:6]) - date + + expires_time = max(expires_time, 14 * 86400) + + logger.debug(f"etag object cached for {expires_time} seconds") + logger.debug("Caching due to etag") + self._cache_set(cache_url, request, response, body, expires_time) + + # Add to the cache any permanent redirects. We do this before looking + # that the Date headers. + elif int(response.status) in PERMANENT_REDIRECT_STATUSES: + logger.debug("Caching permanent redirect") + self._cache_set(cache_url, request, response, b"") + + # Add to the cache if the response headers demand it. If there + # is no date header then we can't do anything about expiring + # the cache. + elif "date" in response_headers: + time_tuple = parsedate_tz(response_headers["date"]) + assert time_tuple is not None + date = calendar.timegm(time_tuple[:6]) + # cache when there is a max-age > 0 + max_age = cc.get("max-age") + if max_age is not None and max_age > 0: + logger.debug("Caching b/c date exists and max-age > 0") + expires_time = max_age + self._cache_set( + cache_url, + request, + response, + body, + expires_time, + ) + + # If the request can expire, it means we should cache it + # in the meantime. + elif "expires" in response_headers: + if response_headers["expires"]: + expires = parsedate_tz(response_headers["expires"]) + if expires is not None: + expires_time = calendar.timegm(expires[:6]) - date + else: + expires_time = None + + logger.debug( + "Caching b/c of expires header. expires in {} seconds".format( + expires_time + ) + ) + self._cache_set( + cache_url, + request, + response, + body, + expires_time, + ) + + def update_cached_response( + self, request: PreparedRequest, response: HTTPResponse + ) -> HTTPResponse: + """On a 304 we will get a new set of headers that we want to + update our cached value with, assuming we have one. + + This should only ever be called when we've sent an ETag and + gotten a 304 as the response. + """ + assert request.url is not None + cache_url = self.cache_url(request.url) + cached_response = self._load_from_cache(request) + + if not cached_response: + # we didn't have a cached response + return response + + # Lets update our headers with the headers from the new request: + # http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26#section-4.1 + # + # The server isn't supposed to send headers that would make + # the cached body invalid. But... just in case, we'll be sure + # to strip out ones we know that might be problmatic due to + # typical assumptions. + excluded_headers = ["content-length"] + + cached_response.headers.update( + { + k: v + for k, v in response.headers.items() + if k.lower() not in excluded_headers + } + ) + + # we want a 200 b/c we have content via the cache + cached_response.status = 200 + + # update our cache + self._cache_set(cache_url, request, cached_response) + + return cached_response diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/filewrapper.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/filewrapper.py new file mode 100644 index 000000000..37d2fa591 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/filewrapper.py @@ -0,0 +1,119 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import mmap +from tempfile import NamedTemporaryFile +from typing import TYPE_CHECKING, Any, Callable + +if TYPE_CHECKING: + from http.client import HTTPResponse + + +class CallbackFileWrapper: + """ + Small wrapper around a fp object which will tee everything read into a + buffer, and when that file is closed it will execute a callback with the + contents of that buffer. + + All attributes are proxied to the underlying file object. + + This class uses members with a double underscore (__) leading prefix so as + not to accidentally shadow an attribute. + + The data is stored in a temporary file until it is all available. As long + as the temporary files directory is disk-based (sometimes it's a + memory-backed-``tmpfs`` on Linux), data will be unloaded to disk if memory + pressure is high. For small files the disk usually won't be used at all, + it'll all be in the filesystem memory cache, so there should be no + performance impact. + """ + + def __init__( + self, fp: HTTPResponse, callback: Callable[[bytes], None] | None + ) -> None: + self.__buf = NamedTemporaryFile("rb+", delete=True) + self.__fp = fp + self.__callback = callback + + def __getattr__(self, name: str) -> Any: + # The vagaries of garbage collection means that self.__fp is + # not always set. By using __getattribute__ and the private + # name[0] allows looking up the attribute value and raising an + # AttributeError when it doesn't exist. This stop things from + # infinitely recursing calls to getattr in the case where + # self.__fp hasn't been set. + # + # [0] https://docs.python.org/2/reference/expressions.html#atom-identifiers + fp = self.__getattribute__("_CallbackFileWrapper__fp") + return getattr(fp, name) + + def __is_fp_closed(self) -> bool: + try: + return self.__fp.fp is None + + except AttributeError: + pass + + try: + closed: bool = self.__fp.closed + return closed + + except AttributeError: + pass + + # We just don't cache it then. + # TODO: Add some logging here... + return False + + def _close(self) -> None: + if self.__callback: + if self.__buf.tell() == 0: + # Empty file: + result = b"" + else: + # Return the data without actually loading it into memory, + # relying on Python's buffer API and mmap(). mmap() just gives + # a view directly into the filesystem's memory cache, so it + # doesn't result in duplicate memory use. + self.__buf.seek(0, 0) + result = memoryview( + mmap.mmap(self.__buf.fileno(), 0, access=mmap.ACCESS_READ) + ) + self.__callback(result) + + # We assign this to None here, because otherwise we can get into + # really tricky problems where the CPython interpreter dead locks + # because the callback is holding a reference to something which + # has a __del__ method. Setting this to None breaks the cycle + # and allows the garbage collector to do it's thing normally. + self.__callback = None + + # Closing the temporary file releases memory and frees disk space. + # Important when caching big files. + self.__buf.close() + + def read(self, amt: int | None = None) -> bytes: + data: bytes = self.__fp.read(amt) + if data: + # We may be dealing with b'', a sign that things are over: + # it's passed e.g. after we've already closed self.__buf. + self.__buf.write(data) + if self.__is_fp_closed(): + self._close() + + return data + + def _safe_read(self, amt: int) -> bytes: + data: bytes = self.__fp._safe_read(amt) # type: ignore[attr-defined] + if amt == 2 and data == b"\r\n": + # urllib executes this read to toss the CRLF at the end + # of the chunk. + return data + + self.__buf.write(data) + if self.__is_fp_closed(): + self._close() + + return data diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/heuristics.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/heuristics.py new file mode 100644 index 000000000..b778c4f3f --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/heuristics.py @@ -0,0 +1,157 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import calendar +import time +from datetime import datetime, timedelta, timezone +from email.utils import formatdate, parsedate, parsedate_tz +from typing import TYPE_CHECKING, Any, Mapping + +if TYPE_CHECKING: + from pip._vendor.urllib3 import HTTPResponse + +TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT" + + +def expire_after(delta: timedelta, date: datetime | None = None) -> datetime: + date = date or datetime.now(timezone.utc) + return date + delta + + +def datetime_to_header(dt: datetime) -> str: + return formatdate(calendar.timegm(dt.timetuple())) + + +class BaseHeuristic: + def warning(self, response: HTTPResponse) -> str | None: + """ + Return a valid 1xx warning header value describing the cache + adjustments. + + The response is provided too allow warnings like 113 + http://tools.ietf.org/html/rfc7234#section-5.5.4 where we need + to explicitly say response is over 24 hours old. + """ + return '110 - "Response is Stale"' + + def update_headers(self, response: HTTPResponse) -> dict[str, str]: + """Update the response headers with any new headers. + + NOTE: This SHOULD always include some Warning header to + signify that the response was cached by the client, not + by way of the provided headers. + """ + return {} + + def apply(self, response: HTTPResponse) -> HTTPResponse: + updated_headers = self.update_headers(response) + + if updated_headers: + response.headers.update(updated_headers) + warning_header_value = self.warning(response) + if warning_header_value is not None: + response.headers.update({"Warning": warning_header_value}) + + return response + + +class OneDayCache(BaseHeuristic): + """ + Cache the response by providing an expires 1 day in the + future. + """ + + def update_headers(self, response: HTTPResponse) -> dict[str, str]: + headers = {} + + if "expires" not in response.headers: + date = parsedate(response.headers["date"]) + expires = expire_after( + timedelta(days=1), + date=datetime(*date[:6], tzinfo=timezone.utc), # type: ignore[index,misc] + ) + headers["expires"] = datetime_to_header(expires) + headers["cache-control"] = "public" + return headers + + +class ExpiresAfter(BaseHeuristic): + """ + Cache **all** requests for a defined time period. + """ + + def __init__(self, **kw: Any) -> None: + self.delta = timedelta(**kw) + + def update_headers(self, response: HTTPResponse) -> dict[str, str]: + expires = expire_after(self.delta) + return {"expires": datetime_to_header(expires), "cache-control": "public"} + + def warning(self, response: HTTPResponse) -> str | None: + tmpl = "110 - Automatically cached for %s. Response might be stale" + return tmpl % self.delta + + +class LastModified(BaseHeuristic): + """ + If there is no Expires header already, fall back on Last-Modified + using the heuristic from + http://tools.ietf.org/html/rfc7234#section-4.2.2 + to calculate a reasonable value. + + Firefox also does something like this per + https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ + http://lxr.mozilla.org/mozilla-release/source/netwerk/protocol/http/nsHttpResponseHead.cpp#397 + Unlike mozilla we limit this to 24-hr. + """ + + cacheable_by_default_statuses = { + 200, + 203, + 204, + 206, + 300, + 301, + 404, + 405, + 410, + 414, + 501, + } + + def update_headers(self, resp: HTTPResponse) -> dict[str, str]: + headers: Mapping[str, str] = resp.headers + + if "expires" in headers: + return {} + + if "cache-control" in headers and headers["cache-control"] != "public": + return {} + + if resp.status not in self.cacheable_by_default_statuses: + return {} + + if "date" not in headers or "last-modified" not in headers: + return {} + + time_tuple = parsedate_tz(headers["date"]) + assert time_tuple is not None + date = calendar.timegm(time_tuple[:6]) + last_modified = parsedate(headers["last-modified"]) + if last_modified is None: + return {} + + now = time.time() + current_age = max(0, now - date) + delta = date - calendar.timegm(last_modified) + freshness_lifetime = max(0, min(delta / 10, 24 * 3600)) + if freshness_lifetime <= current_age: + return {} + + expires = date + freshness_lifetime + return {"expires": time.strftime(TIME_FMT, time.gmtime(expires))} + + def warning(self, resp: HTTPResponse) -> str | None: + return None diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/py.typed b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/serialize.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/serialize.py new file mode 100644 index 000000000..a49487a14 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/serialize.py @@ -0,0 +1,146 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import io +from typing import IO, TYPE_CHECKING, Any, Mapping, cast + +from pip._vendor import msgpack +from pip._vendor.requests.structures import CaseInsensitiveDict +from pip._vendor.urllib3 import HTTPResponse + +if TYPE_CHECKING: + from pip._vendor.requests import PreparedRequest + + +class Serializer: + serde_version = "4" + + def dumps( + self, + request: PreparedRequest, + response: HTTPResponse, + body: bytes | None = None, + ) -> bytes: + response_headers: CaseInsensitiveDict[str] = CaseInsensitiveDict( + response.headers + ) + + if body is None: + # When a body isn't passed in, we'll read the response. We + # also update the response with a new file handler to be + # sure it acts as though it was never read. + body = response.read(decode_content=False) + response._fp = io.BytesIO(body) # type: ignore[assignment] + response.length_remaining = len(body) + + data = { + "response": { + "body": body, # Empty bytestring if body is stored separately + "headers": {str(k): str(v) for k, v in response.headers.items()}, + "status": response.status, + "version": response.version, + "reason": str(response.reason), + "decode_content": response.decode_content, + } + } + + # Construct our vary headers + data["vary"] = {} + if "vary" in response_headers: + varied_headers = response_headers["vary"].split(",") + for header in varied_headers: + header = str(header).strip() + header_value = request.headers.get(header, None) + if header_value is not None: + header_value = str(header_value) + data["vary"][header] = header_value + + return b",".join([f"cc={self.serde_version}".encode(), self.serialize(data)]) + + def serialize(self, data: dict[str, Any]) -> bytes: + return cast(bytes, msgpack.dumps(data, use_bin_type=True)) + + def loads( + self, + request: PreparedRequest, + data: bytes, + body_file: IO[bytes] | None = None, + ) -> HTTPResponse | None: + # Short circuit if we've been given an empty set of data + if not data: + return None + + # Previous versions of this library supported other serialization + # formats, but these have all been removed. + if not data.startswith(f"cc={self.serde_version},".encode()): + return None + + data = data[5:] + return self._loads_v4(request, data, body_file) + + def prepare_response( + self, + request: PreparedRequest, + cached: Mapping[str, Any], + body_file: IO[bytes] | None = None, + ) -> HTTPResponse | None: + """Verify our vary headers match and construct a real urllib3 + HTTPResponse object. + """ + # Special case the '*' Vary value as it means we cannot actually + # determine if the cached response is suitable for this request. + # This case is also handled in the controller code when creating + # a cache entry, but is left here for backwards compatibility. + if "*" in cached.get("vary", {}): + return None + + # Ensure that the Vary headers for the cached response match our + # request + for header, value in cached.get("vary", {}).items(): + if request.headers.get(header, None) != value: + return None + + body_raw = cached["response"].pop("body") + + headers: CaseInsensitiveDict[str] = CaseInsensitiveDict( + data=cached["response"]["headers"] + ) + if headers.get("transfer-encoding", "") == "chunked": + headers.pop("transfer-encoding") + + cached["response"]["headers"] = headers + + try: + body: IO[bytes] + if body_file is None: + body = io.BytesIO(body_raw) + else: + body = body_file + except TypeError: + # This can happen if cachecontrol serialized to v1 format (pickle) + # using Python 2. A Python 2 str(byte string) will be unpickled as + # a Python 3 str (unicode string), which will cause the above to + # fail with: + # + # TypeError: 'str' does not support the buffer interface + body = io.BytesIO(body_raw.encode("utf8")) + + # Discard any `strict` parameter serialized by older version of cachecontrol. + cached["response"].pop("strict", None) + + return HTTPResponse(body=body, preload_content=False, **cached["response"]) + + def _loads_v4( + self, + request: PreparedRequest, + data: bytes, + body_file: IO[bytes] | None = None, + ) -> HTTPResponse | None: + try: + cached = msgpack.loads(data, raw=False) + except ValueError: + return None + + return self.prepare_response(request, cached, body_file) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/wrapper.py b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/wrapper.py new file mode 100644 index 000000000..f618bc363 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/cachecontrol/wrapper.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2015 Eric Larson +# +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +from typing import TYPE_CHECKING, Collection + +from pip._vendor.cachecontrol.adapter import CacheControlAdapter +from pip._vendor.cachecontrol.cache import DictCache + +if TYPE_CHECKING: + from pip._vendor import requests + + from pip._vendor.cachecontrol.cache import BaseCache + from pip._vendor.cachecontrol.controller import CacheController + from pip._vendor.cachecontrol.heuristics import BaseHeuristic + from pip._vendor.cachecontrol.serialize import Serializer + + +def CacheControl( + sess: requests.Session, + cache: BaseCache | None = None, + cache_etags: bool = True, + serializer: Serializer | None = None, + heuristic: BaseHeuristic | None = None, + controller_class: type[CacheController] | None = None, + adapter_class: type[CacheControlAdapter] | None = None, + cacheable_methods: Collection[str] | None = None, +) -> requests.Session: + cache = DictCache() if cache is None else cache + adapter_class = adapter_class or CacheControlAdapter + adapter = adapter_class( + cache, + cache_etags=cache_etags, + serializer=serializer, + heuristic=heuristic, + controller_class=controller_class, + cacheable_methods=cacheable_methods, + ) + sess.mount("http://", adapter) + sess.mount("https://", adapter) + + return sess diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__init__.py new file mode 100644 index 000000000..f61d77fa3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2024.08.30" diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__main__.py b/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__main__.py new file mode 100644 index 000000000..00376349e --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from pip._vendor.certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8fadfff9bd24a3d231d6b4d0d715139eea0f2005 GIT binary patch literal 325 zcmYk1u};G<5Qc3hp;1vK9)UGO;;3QhfDi*CVnHk!u#))NSaod6HbLnd@e0g5MOG$W zfeA-q=r7%OI^ErWx|hvnjeGC1dj? zVV8^vax-Ek zESg$Fty|+i&H-V|;$j=c@gE(vxkQZ7u^(Zo7jx&{G3+7l!m1JwEMT1EJ zsO3~5v-;>+FoO%6tt$J)t?Nb;?5EK5av?G+vagwF&$_%dojv*rWvyQc4xE&OuMM9! z6>!+XQbNfuVGU)YoHoDCCoNYSJ#Xq*jpHc-OeDk_^I{3e?RINk%E_T+oSg7hYKWCB v=QFUQQt`tL{BQ%$vMQH#mL+Oq!qch|s}jzrU8Hr}glxz{I;0^BXpj8>mFtV9 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acc3329bd84713fc3b546fb5be7a10729c8b79de GIT binary patch literal 2151 zcmah~Pj4GV6rY*>eAi(^SutaR6g46@5r~(p)5`{Ds)m~PcopG{>y=!JB zp@~v1t+-WufgWr)g^$3QPcc_c`3ivp@6Fmy(gd;Aym_;4X6N^Q?~k9%&V~e@KmP1w zKhG2L7cMS86ce(e{jT}MuT<-6CavSWGp4ayy zzh8+elyH^=FlIz(_Xm6i_E*QNkXK<<%YV1$YjFNoI6n*LXQO(5E}FY`zJBHW-1z*S zMb(HJcAk&sVQ%zp^%tTAO2mSg-@7fsE?XwiBCJ9_Z}la!I9oA^3}n;)vgp`I}@u~Ch5MAokNiyc6!kqBBMLKEy2Sw2Ygu0+(vQCgZA;2Xjz@bQSZlHA;lu7%GPNRRBzE54VlDu*Br;|tm8|dfaB;c)a!`u zL6vg%6$RJ4bvX-GSqEXfM5S4FN=+d0wBT93({ij2vVrCqDH0w7T74tBV49(@c@nR; zKyNV_z;sr|&bXF~6?pUCh(7`AI8+~N+g)C;glR{!p*s$dH}rL75+Yf{djdHJlL>Z& zj;Dl6XkEO-mn_vS&s4_6gPEd=0|{BU3|}!m zq)Z`|#0p6<*(VQT#5}P%&;~^lO<#}`Nc>mqm!;?Ki92K?@;zbXbMhTP`VEpD=ciC4 z&?eZrybGsVj&X~D$SpU<#UyBX@&mBQ4^bcp`4I@?@T*joeG$f!X3(aqo#9Sd1 z{!GZUN|82A_UI78J)+M`-8}({N7!Tm6jBa+utLeVK{XYg$vo)`>|)pgpr~A|IUtsH z--Lse(t}aLp*3~|2^h%#g;u@`n=XA8_e<+42KEOSlfW|h9@^dq(Yj+Qam None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] + + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("pip._vendor.certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return files("pip._vendor.certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +elif sys.version_info >= (3, 7): + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii") + +else: + import os + import types + from typing import Union + + Package = Union[types.ModuleType, str] + Resource = Union[str, "os.PathLike"] + + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict' + ) -> str: + with open(where(), encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where() -> str: + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + def contents() -> str: + return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii") diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/certifi/py.typed b/venv/lib/python3.8/site-packages/pip/_vendor/certifi/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__init__.py b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__init__.py new file mode 100644 index 000000000..bf0d6c6d3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__init__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2023 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import logging + +__version__ = '0.3.9' + + +class DistlibException(Exception): + pass + + +try: + from logging import NullHandler +except ImportError: # pragma: no cover + + class NullHandler(logging.Handler): + + def handle(self, record): + pass + + def emit(self, record): + pass + + def createLock(self): + self.lock = None + + +logger = logging.getLogger(__name__) +logger.addHandler(NullHandler()) diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b0c67b6c1a4628bc4fa5ead4cb4ad836654eb01 GIT binary patch literal 1085 zcmah|Pixdr5TBQ1lWevvi%3!MD8e3gBlKcHq+p8*We*lnb9tnB({0@R>q}bf;;sE4 z?Xh3Mv!CK!J^2+pIg|9yQU%}ghMAq;%x`AioOPZn&@N?b#qZPbu*@;U z1i2Esy8yzQe)ZqyhCn3>aLpCNa!a8km zT#-ATA%&(@C<&!X9jdO>P|CDO8!0E%sivm@ZmVX+7p*-KW-vX@vbS-eGSq*~gP~g- z^OVHhJd||~k=;F3)fe zEY?^IS={bdr}5z0XLv|T%}(F)ZQpMH8x4$?m9tq|%*;U2!swcInU=PHyV&h&v|uxM zmsh23cC;?F@vhgz^k>-YQV!H@VaDZzs7(+nb&dH~O~!xOeIl>8!kSC>Eyo@@mKcgP J>(NRe{s4w}=8XUV literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-38.pyc b/venv/lib/python3.8/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0baa129e28236386e89528b3f225a7a2adb49091 GIT binary patch literal 31819 zcmchA36LDud0uzVxwDJK;sywaY~mnymb(iOBtZ}YK!7+1h~xqUme+vbaCW+PG1xg4 zuX}Ke29YdK7AV__e8h4kj%wkG9n(1+sZyypv7JQaI4;Ll#ff4&?If13#H=_@Vmm(Q zSj_kRue)b@b}7luvB32C&j0=Izu)VZ)~qQQ`1?mcwYT=C_YC7Vc+>wkjGJTld0);N zhGRHp-DsF4)8w^Pw@Q|rQ>B!g)1|bWGo?&tM6R->>?=ldYua#giv@MwH2BZ4O4-+p zruC}fX0GNPW1Hcm7LCuEs);=5`cPx2G-MiXVcskayCbDFc$%q?HrAHL!g|-0)=TLd zN*i&`);Bdal{Pgtmo_)Hl(sasmbNywm9|ORT>ZYr_R{voj?#x3_m}Q(JWzU|VVCU2 zgQW)>J4-u}ChrWrWt4VEdAmxxa4yt$HFlSFH};hFG#)BF)Ofh`aAUkQ-YAxejfv7k z)FPc(wWBD(pl30 zMjg}`^;t`ON#oSBhO^1p{FdQtc0Y0_RXXdu>1=hjy=9i3bMAAtV}#FWjO&b{v>m{v z=Swe0Ne{&(y%(4CA?JRSbiX@;l3wV`_dq?}I9EC+`6k1BDRofibFHiA@q6(Z;P<@c z*v^Bf{hZE?I(ODDG%l8A(WjT(S!dTB>$55Ksykb{*qp@M-FW+Q=@rvJKlV5eq4tN| zS5W)QxPRCg$NjiFi~Ebx{$l;r#%rb5q#gIi?f9Z@hcn?kg8sf1*KE~GrBWyFUMgJz z(UQ|lJNrwH^O*BEN-R51I0tZkGb$NY1B$JKrOX=)p_ckSz2@sIUhzzb?2~i1n2p_5|7p!jb^DS zCGLz%{Bm64(Rxe2*Lj5cSkBX*v_OH5fdZw~@4EZ%7+04{OHIU*s?Ps@7aMdk%^6r)zb0_STYH%mn$i zsxMWP=LQ8_wVV1nhwH1YT2o$oOZA!`zLx7^E*M4v<)TRUx*KG!wOhU$Cd)W7q6g!r<7``U`WdK(WI)$o`iqAX8TXz=k373 zH@b#z-m%oiuCZ)6<~{Sa;itOB9TPyXX5P!R{rRbb#dJ`pd9|kJSDIC6SFPG|+#u^V z^|>tV1SCxF9YbwJ!SCJJd!^NI_qrW-vF6tIowD|9xiHCN(+Br@HQ$|D!t2U{i>$S!y=A0wT57LT^L(bQ zwi*D-^wKRgg4*wA{~(nZhFQQrc~%?>au?idZP)VwS%t>97><`NJRd^r%aGgb^6B}C=iO*2=Y<6o4D&(sb^~uNxK2&ERX=(-GS{xvoib~3 z6`(W1L>I%ixG$WUec}1@L>9iy)#ht;UndMN=PPy3#j7>5?oI#fxmQ$WX$cAPihWZ# z9`Zb}&uR@1Lp)G!Q|oXj7SwiLQ&dy;@i5B6S{?`^^#Bhx4-fLN6Nh3-eTdgY4>iUI zW-zQ zz@wRBD#(}1EUjD)3gvR6<+SU(9w`I4Ds}xvP^<5-4Ou1sWtMH>kT>Q2T~=5nsz-l> z385ya{|C5OFm40Uy%Dwo$Pdm84~xhljRd;H|Ncvl=MBLYx1}VD$>n3*+olT@1~YBPX3;08qK39DeDZ~GrdnE zom{E!=X*c5obP7tVFDu6*8V5ULr%dNzK7PCMmKxXxV-UpK7Nl<`TW{HME-&^;;dO7 z?xyaUTMTD(i{a<4j@+|u4|Ru@*L0B%Q+jP@=>?7*D3@KSR)NX9@>}+W(U-6Em05R%Ij3~a zDC6nCoCagWKuzN?6POd7Nt9wYD$QH=4a|>fMLC{bYueQw7TG6fF4`RPLv|-~>5b{j zk95*En^RLA>-7o!U;>xN@tel4R2&U5nyO~1t#;EF90=0NT~a}&-nxP5o8{DLIza(M zO!?l8ntvs*6ls`Ott^44A#uGz>PAumdLE>OU>D9lJ9F;BsT0R9o(eL8_r*eBIUpS| z`mbqJZkFSo1ZfZ=FUWxKH#38~?#%1JCh7*_Ztz_#1r8w7K_tRbLj_Kqv&3@9) zI7402DJ*CGoS(;YOoMrAvrM_>M4Zx6Uq#B{uGzJmk!?ntB9EtQ*f-Wv9IYGy9=`0V z+Walszf$q-38LJDJr3k^T(UC9o^L6@+pc&v*l}Am@o4T=JPR&feEwn!T*{wbaQ!9M zRV-kR=FOEb?u1=GbW~OnOOS|K6=mw8;NIGT%H1v^X!UpQE#1y>P*^mZQnk7 z-mW-~ay>76-E5&v)vCMX>o)dR(wn4%bl9$;86+-dt<)ArgZ%=0Ft z!Sn5=%=uokr0brIT1hIV;};#@Z7lh>?3Tj2{s}v&ArSKfyXm@^)fF2MvpoSBGx4rU zf#s%;x$j@GT^w9xpEz}4wtVjV?AdcO$De0Hd2r#>i!YtMaOz|*>b2*tf-W7e`!gU4 zGF5^UXv9pA3tt5V%-cq*Dep;XIuAv|VDC}3({jD$!@h21RBLPA-s8PzP1>CuADniN z66)1Cf)0a*sMeCIfzP2EQFUx@vVrzIWFB-Mgoh*BZaR=Y3NKgcZOtEp6wpVer+NZS z4N`TtDKtxcSZ*7YCG{AtAQ+~JVd`l<&W8lS3sPRC8Kf57TS4YJ3&n%>+yDVlMdbNT z{Ji}*82PlBPo>Qf{L4cm%Ho_(jhQ2$6`&&KDDSQHUn30xZTVV0E&0Lw*ItC=TXzXp z4g&Y8t<4h z?^u*LoLbd~l#y*OIT+NU6{J14J|AQkZ&cKRHvm#*s5NWI@XuMWP*$_EKJM3a^m?Y4 zQI8;rn&e>$haiW#TGet{y~#%eRo_A6nAh;^pW^4GambtLG*ms2zA`ti)T&n!(hLPU zN;B-F2U8VDxMRHqiN-gsn#Za~O8UeB1c^6%FQY6=~rsdU9q)IJgTyV~S z)LS6kr}cXfTH|UCl8)u)uMRmG5bPhvlLDlkobo~LhaILU+_Ow54^m1FvW*qzOZy|A zGUiPOQp;QUZu%rh^&M0F@LglUyq&rBC3!dOj70BlXPR04&R=uK0K*{TIDb2bvd!hu zE?biC=DS(c{&U^z^4hM*QQZtujo%)URAZ3IhR};bH`Ogje}AuAu)tdQyJ%edy=yQz``a;8)jNf*RIX;A;P4oE(5~~uDf;d<$PXwnc5Xf#qr}H4N35NqQDyDLVQ!_^ka9P zn4=z*J6?M35?IzPh{uq*ea@pGTWv!bft(IA12|rgf_i{7t{>!^cweo+qLQLaL=IUT zRz6Ou#slkCS8)h(EE~WLGMrIBD9?qCpJK5&79tl&!M8!0P1U^@I1mJC4u_w|&pU{N zF`CbgfICv!&YNRAnGA;RuYA6c`RcoQ=#?B#DjA+lESc;& zZ{<_@lpZqNCjR~+WB+NCkN^yU&!3F_$^lTH!Mna@$|$q_yZZXRE<{@l@m~z~>Myev zik^d25lS!z#|SmnSCx!GqBCqi5t&rPqxFicqYL4pYknRg%V$i8J61PETcxJAsbCmp zJ2xyeNYn5pz%HgkEEl1!jH1+c@X#ADfy7_Ivk5kjH5=x7vojJkkVUP60KKti-oEmB z2*5FVkcGwwkXC#KQm{!IL1|iszXQOo(DG8n%fugHAOpG&7mi6NEN0ax4OhN(6=F7o zv>g<2G;4Lz5dJ19bc@!T?Hsru>YQB(MH{;YP7izCEwTMjQW0CMoovQ5*yFA9w_paT zT(1P#k4T!Ja29feP9Nk>=nDwDA4SW89K;9@9ndV{Z9Gy3c<4d72bIs^1)O?@M}wpR zlA0Dm3+2O9d{PgLsl${fff(#3zgjsa4@O=QB3kib$K@Kk*s2NaTO&0XzlfS&OX z2BhFqYyjt-VX9>u6{#g;jJYbr4~l_RKUnLvVNqXza-a=GD;h&wkNVbl$1~I zV6#xWL7!57+J~@>r&)|)jyCqId2PuMjl`PiYzO;?mJ5S}54E3KrLQKbghNd&4oSa) zjJynns6v-%;8&a2=;89bC)Rk3DfkU;=m+qEA-W~RCL5$B^&?0P;+Q87>~X+&VDLhh z0jv)yfu^^vXl1k_YcY`w9C`}K7xgM{0v2G)fmK;e zixJFpa*J8-m^_7+qfR5?v0umx_vpP5+W95Oi!yT%%b0f7C#1_~E{IY;$ z>c>!M0=ALik~81?SsXeecde@+gm0TVhQzlJL$X`@2^~C*e0gtU3~rlF>bCVM3tB0x z`fufLr#@wT3T8wn>*R2k1`C0eAC`GcQNVr4$-hn8{g7jF>~3eA0yN}|*X>#hFfC$C z(%sA*l-f1bcfsVCSwLBM%2HtP^nR#|)Zw#qHyft>Nv7;(7vTS=9^Shy4p*K=w_=TL z76f?!CC7_)WTK<`*rq)XYNt*mx@LsDxPkG~yV7dcow!73c)cvNa@x>zHOco{ZJ01& zu7Uz;+h={KkJsApVS)NUX9c%xlPysft~hm=ZYJYGiFatJ2O-+NQMpBfDeog(M3cp^ zQ}J7>Ph~_6uzlBUQsB5yGzS*+TBF^tp|1KjYSmC7*B01h2icRj5#y+}5^}X-sKZk? zVL5a!u*sf1P8&QMD5SG!*L?em*mo-qrcbhU8m780F;M3fbYb>x36Q~4Xv`?zmWgH0 z;T=ddH0daP+`KA?M-5=~Y!7rD4l;}z;I`y0P3_x%;4wmc9{FKgs7Nt15m#WD(79kM z(DnH}sM9tKmuF=GK2sPi?2ES`G~5*Wt^rHam>7I@;cRr&tf*jMP^B5SvDSoft>)O( zEA&HhsF^)kG|HNRLmD6ea>0<%-*uEG2F_j^AJzoNt1Ub%_UF)y-Vf(_nn2=qp@h-E^~@H8LI1x??C*NpI4xuwdmnk;>eWR3M2 zrX`lu&Ypq!+%rh@=SvVg5f)K|wEZ`6(Sbw(CnYSHw@LCIM-NQ&;F*x*!H9PkPSwRB zb-+n2Lpr_-V+)LUDf&`A5t2hcy$tUSN`P<2dzn&)pY4JvZ8Jcm(wz}VC23q`Q2rs5 zpGWyWtjm|O^Xjt>JV9IFCqf8J7G_4XONKU3%Uwf#wVQ$Q&|FNZUk-Erw39=wf|F-1 zF)xNKgJ)^zNrR48GP)VfvmTDH5)-RY^ALKl&_fq+^?F5*NoV+@x5Zri(FfISZKhw} zZZ0-kH+r)X9>fm~K8y1lvOjvevn9qvSoa}sHV)~j-RadCzUa#*NZv_L0nOoM|GOBW zVEs!?iHkTXt|TpVWog`~_`z`BObv$g6feX5n45=75X4Yep86t-8Gc@54#}@5MHjO{ znv^UU<`Fo?z>^@=@D_qx=y#%xNsA~@dqLr=;KoCsOl6sy>TjczK6HgUzv+~9BmO$E z^(fv0ThpVkV2}-un4=<+(_di89lQi!%$8xjpSDJERe(h#%O|)8+crD9KM=?JDw|a) zRMZ)YDFg#%%4M3|VHsy%gVEzY`Ug6vrBsh?r0Tu2<$Pw>&d$F^_{>JuoO=1dHta)3oN zk*SyXFstqB2v-@3;TM4XFR8L>H=CT8YapViawM?zO}pF;9l!HO#65bY3`{q{E>1FQ z#Oxda7WCsok5OPsb5v1&TypPaSg!UqRoGT)MZwx~xzdF8DD2CF@iD7D&gVIFw1QbS zkY0TiFM-Mb5hwMlIKW)@@Aaeqz(?|2*xiOzTJs0&6b;V?=)%|t{ft!RNinikXY$X} zG0kZ$sa*XAs}|Mn*LfxNuN^22(W;@%COfiOky}>rOZ*Xcj^XG1eH?n0)qW=>R0CR7 zsT3HcEJADP!ZJ%ahwdt)ly}xRqc{&aYn?Hi3(h)cJ$T1nXDd>Sxnreu&Nk;hT(5UGINO~al6Ipb!NjFa&I68(cblCDot-#uadtVoao(!K zeM{S%JSM}q`U;v}`3m|P&&T6-L|yC!)oL$!J(npRn-G0L1|VO$V56Gmctwx}j6WX5 zrGQRUA&5Z0tJq|VQA4+0nU$410`h>Sxh?H<>yQ(hsqo`a7Vu1Lgw2WJU?Ctpw z9A_yricAhMZ>c?3hpU%zE?iG>1GM`F8PQr@u>*|-EfJx60#$euHZfNCf)v{Gr0MP< zs>kp5p=llp!BRX}QqF3P4#OZof9toY!but-hONUth+ODsMG2s&lJy}73!8!b0htB-IL*-uy$tFIQH zwIMFkSr>Jq=VBlzqQ3|(tWy^Be zjvHWhHug0jPLS}hq);~*k+gwkKYCciBW*!U2jkfWB}03kLoppILiq1B<68Dr{2&N7n- zC}5WeTImuCg1N!~Hyxwfb{jw&b4zK)JAFJ+eHqCI8YiB=sXm~N#6U#@61Wa})-&kR z0P8a%*|-X>eKqwN(9{S>X?30zeH@1%({9qxEcmpFPlM4GFmIO2``85PQU)3A`<=14 z8DWCIU{iuL;I5=C>c67&4{ixVM#S~^cpo$pI4*A!_{D*sD9J&Qs2bnsiG2WzsiP_= z{3RtMW@M(vm~k@>@Bxo%AMj#!z&kA?hKYF%$pW(hW={b}kE4jmZQLGYquCU3MUVGE zkF*eiEyRkR91^>xG3`Fa!eo4YJ1+M3cqJShQN6tap6r9nrd6w0F-q*Hplaj+xC-(Z zB8vKbRZ`GTEcDwIwNaUu+USIG%w<}3t!2}v;%i)mEPD^iwv0w+ z`$zYIZb3oy2$?9N`S$onAH7{vFCaUdCI5u&qZ?bj$O8xK>*9|{m*pdDvzQe`5gU6y zK`C{b1QrnyICL=+L8w0+XeJzJ*5@rfwTfov=@Sw2k6<4~foNd>1}FF=w1j~8$jBB( ztfqW0O;XP!a2?#>!=5Mm^DgbX zEbLsl^RNR7OY|;1dgyXr5HrR9c$8)A5T}PCR>L~=ztGla<9=G5H4&JnnPo*EIc(BL z(n9vJ7WORck=glhqDNuG4tpU36$O&Q+G*3u3LE0}TB{Aw5Ph4*%=}Z7`@#4&gn|%A zgShM$`r5ocZgc#4MZ<|}(r{rP@!&WXheHM<)!CkCy5N$49N?YS@!{&v*u2$dFQPjX zjM3hUal2A|^gMjIf~h6JNv47sj0k`8aAfY2afLQdV%5)h)yr&25@{(kK(qp0gXz8; zHzX-E#0e795s7=T*9URa0dI{Oa|8lIb7v?l?AOAA4_ww_LeGLnAqPZXn*824q#W@M zili!@Ajo(kXGU@y>|3ymp;tnQMZOT~)1%4KKcUI!8YEcVwb$4+dXi|v9D|w)OJ-7z zh?B0sgP28lAPBCMn6Gf+yLUb=&YG!4Y0g|m=R*tmq*-fh@Rl^QG= zq(R|SowhHC!8(%oD7ih%YmT{A7p_Mv3ID!it#5Jk`D+)`_aH7WtxD)^{U zkX+s*oHS9^aO%fMT|fc}h>c7xH2E0evo%73e##u0s0pV-Au<3?+H}!}42)__cU}ph zHG#fOBw*7Iz#6ncvG%M4*u1T?($Ng}LY2e5;Fjj8gCG)ExZE%iQgGO9-*=GG9j zQ-rLvxsN`@Gg%A;BkasHvO?}1$>$JvR4AnKX~rKh4!n@Yx{-7~4ePDGN+C@ypO?Ha zOs=a|5KUR;Oeza4t9p#I%H+ z4MNk>h&C%>WxuRnFO7xNoNXAKjch1dvU(s!K!VX zA8__NkKt@Pk2_D`{Gjt7M)Hta3)psw1Zij#5Jw@_JGqTHgalzShJy+N51^lz?}Q{N zq6B$0aiR5jIFHh52U(!L*MTyf0;nZs0hr|hFv}F^E|!>8&R6NiLgacn9|*W!Rcacp;-YVlJIR>QpE2K z$1dDEu}z1D59Kbj;hpN`uF*_Hw=JkMSB}O`b$DS>9BD3Wz6eG~kZ_-8n`O)a617^s`gy)smwW*-8*EHI(w49=&`#w2Aia3QK@hH#k`$waYA$o8Zbs<_qHHls zVJ8Mn0*5Fd7zD=fjeTwD*@B2K(O(u9ycb?^8W&=;$f*P5sj`+6k{%;F5V2t{q<|qA z2l#+<+JXFsRG?HiLyIF$+yO@qapJ+2gDPV|ZoD>W-zwUNj~qE_x13A0%SUc$?nnYl z_OgNWcq2(nw&yGm0kSkgM2d=145x)<3`WF3;zbJo1%iYst&xU%4JHfQ>}*MbA#PJb z(xqaQq{pPRSo?uBqkx58`to|}b_$F6;Izfu(EUym9$}x+GxRx@>&BxF;|V)`>$0Gg z^qXB~Z-oy*qkRL_i}@hzZCFS!1c&8fnHorVi2(N%9Vb`&$LzSsQE84o{D}kIj+>Pr z9_%u5in(8yoeVtL4+kDz@U97u_ym@{Kn0=w16T6q+GyD6?b2xodI-)&%!-UK@5WnB zQTO*3Q2q?8BOKy)LbI11MR;n|;3$&o|5QAR5**G2Rx6NM8+}k@{P%y}mtE2q02?&d zb$b1Yo=BfqrIJ3W?YLGucu3ApuDOo#l*>Q2N^iC%ak7_q#prNMWqLZe=p56?c#p5^w2Dsy z4kEQ-K&Ot%@*9xwNT+~GOt~JUn_2{tk^~Z&vBv>MMK3ke6PiMy&_Bf`8KJm~g#%oJ zLKGN)n+RlIA-Jfz8=z)fRhI=m)32U2JL~$vCM}#zNL2(@OwYQw>0h1;0Ktign6rZ# zof*VIv<98CK+jSD6|Oj+n>Nl^#V#p$CI!S3q(IymQy?dTJ`k-&Gi&w;I4M`;bh8NN z$aS;b9Angqna({xCsczpD90Bk?dw=N6nc>9^Z=I^Rm~m;gTq@cM=Xk(mUJ(ihvKGL zp3bb@M9-f+eNJ;+#ubq)lCSxy4Y!H1Pbc2W?gImyuCt-XrkPXJy&h>6L`z7;(kuh~7W&vJL$s zAA&S7S5TKwGMOvH1Bg9h`-Xw;0Sp6x>*+Cw;@kLy>KX`@seTJNR?}<1gOTh{*iQVY z*o^hJgfCY#gM^4b!Q!6X`7S1{Yzva=kkIIPl5CQGrW=(gmUR=c#dA;dQ2*+rZ7WtM zy`!zt8u*_hFI9%TtP9#J^Ym#7EF;l{EOw>S-k*f>HNS_5{V-n^7&dx>dD;9O_BF6T57{e=t zruN{qr@wE~ygwhfGLpw+nQSC*DnZxeZK5k*54ZPe5UC!1YfF!gV4=b%j>`nH|2I_H z*Ueb()%Ip4&Ga=cAx5HcQdOdT>;(t2il4Y!x)Fa8K#Si50wYMG)!84mH$-)?msQ)_ zKNDsI^MKQok^*yakZU{u_zbbZ#ApMQqqBKnghHmYqS4GCjoyI^Y4m#H2PnDVN21|J z!iLN6kS(s-?gVP;q(WkJAJoG{umNFVh%l9;qlr#z8tjCmVRPfZNPZXH?`QB>6Ft%W z>`?au&6zjD4IdyRc$0XJB|oq+U=KX-Fw4ns)ia4D@>%M6JkOH%acPi`m?PEfrXX&N zG0wY-9ORaVjccXmVQe3fa&k!VJ={-4gp$EjA8&5fxx+0iz(*zb_i^_Yonzq57bRF@ zsPZ|03Lw{N(fFxJdt%~rzc})eEI0@C)^Z5h*j_?XLM~2;10Rf=i2+?IZRnoD!OGv|%WO%OmK`=s*XV^88OEYbjB5{odmzr-K(OI_>Yu^Wby=l zp4j^^{}^niX$>Sz(3~TXD`jm1G)`FmFjE<{f|*Kb((HTz(*dH-_~NBW=dw(N>y#u# ze2nA2CHO|mm16g&tp$;9h`L)sR3}ZmLRyouB~TeupO`^&iVjD&5o+JJhYPS$f?u%g z9Yv}ejB$T13=)xqI%qzLDDZ3!SUDOMun#5jWMt|Pulbh&7xgPVB&nlFnFlx=qj+g# zC=^O0DbAg>{S*?ASs9VRArO!Yf!84Ki^w3*4}+iycuxYnFb;%)v3*d+bHrRgbax=R zV;MX)A8@fkf^^{sfCShH*o4mO=Ks#hj$q%UxaKqblk{gE z7hTiK*9-)Q3h@r3IJM56#n!3dw+_^uu65YxOoQ-0;{eI7V!b2T!7IiO+=us2TKELU z=QRAk*cS-T0S)Fl!gzk}`jhQw9bbFMA&H5O&pXkFe+ezo_OkRINZ6e$j(b#*J9zvu zUSTgX051#+70)R{EA`ye#sF+TAVr7apQSt_X3N0GK~(rdWc&z0w;2}(=3*LJA;UlO z@UEqyC2q9_%>ILh8I;foP%LvW>2E`5K9-2y!JTkGsMS!FVHLdx zo6EKFF1Y1g#4qTGfO*W_=uY&%bZ5^6y>L{5>p-bNW)W`=%1E=Py;Q3%!oIVxLM-{Z zV(Y>Ru{DzR$4TQ>kzOwmX6&{|`xE_>hIR_4UBy0etBJ4}!ytVf%UZ<2B35ecIN>-( z)0b2>7$Pr~ylhZVpP_`bwcnN%46L5O-TiT)0vv_k?H!Nvl zS}ogLOfYp4_fR~>S~Kuigjig+$7PF&#GWVIHl;Wq0?C}h)cg}PT)oG`%Qyt4qZy*W zqX(2f#=8XqMTk|F2Gv1RT-2)ssUoL~!E*pNAW;l6x6}lrO3Wr4!PyqA);NX`#wkdH zIC9TFpG$0TFY!QT=dWQR=gpMuIs?cTG=ox zfhl)cF-tH8WEcH<#k7knBPM8uBZz$rOhQs!PoyYs>R*Zf@J}UWJ;?@pO_$a|Q-QL4td-VhgNd=no4O*&`qpj;c zsI3JyMJM}YUuzSZXfMe(-QH)y_KNf$Q?_pwz>*8CUjIR@9%7Ru)!TiIPH3KSilRA7 zFTVc2^Mif)l*Ieo?KJ3fonYv0Pw`5`^WD6X8N(ed6t#Hl;)(+$aK$Y@f~+1F#=2-% ziNZecItoBN#w${7mE{#lm69p-ByX{AxS^in0r4OGQ!mCYnYP~zy!IG?EKdG)qlM9- zJpV=t+u-Wg|1$YZ{(-cWwMNzya)q_|0v-M%1)MWXA>}~O9pla!C$OniZJ{m3mtYv^ zP;<2C(^>z ztErjJgEJ7;F{M~bv>rL!nWE;ZL)f)%7Owfi*^{ScX3w5Jd+LHZf^yXi4n<3C;r0LE zVSp}ikH_yOHVDxN!Kc!r__T`Di9a^@7=GSP99Hw5bff)O;VH<;MfRGO(bO?Vp6=)UJ_8(>s3Mk zf;oObKsrSf{!ZZM>6K+fD2n#?;KvN1g}rXIT?f!Q1OEr!3$(C|wGUcn@H{QhN&{LL z$po}$ed2zmhC0r}kMNcN;@Vn6o#5dl4^;Ht>GO5!xOd7Qt7?8M_6t2&z!fwI3vL|P zhfLJcNS;>gGE`v|z38GIzW0a$+xr8oj3zY@11moweMl(r36P#d!U+975TwU|cu7zm z`?{c%u%vGilRZLMj#0ry=dA`XoGJ@XpV^a8f9d2&Tx)N zk7;IsMDZ*#eZ7e$E$rcDZ=#VV{0r9BH_`a4%F6ryEZiK!PaF!X@)mNGjL?_P_5>>W zq`p{U22q}Sy0E0^FTp{c!P9RrCP!C{$ z_o$w*nSLZ(xo@hfpp&{$s|^RK^RKcp0)})?;JIQjkU|f}x8tIJFkGl>-1r?Kdb|QV zDdpq}8^M(Fa)sTkOG9#nt$GloC*gbFZ*kqHi@l{0l&8b^)`nqxW6r3v7Qu1roH1t| z&g-4^&IX*ZLe$xW^G0X0vjyi(&Q>76Hhz4==)4%Kc(Y>g>~U_J8}r%Wf(7nCN*x*7 z0AOh&zOTTIUFO^MSa0sxMLT=m7=QZwVZ`1bQ1U3nHtbh~4cz%qM^7G|o+$3_jJ&}O zjb6X;ND=4G=o_z3_up@LZf1*t-78X^Q7i@Gwm{e|{T1Wd?l4Z}s*Y2M@?y)}I2D!x?#5EU-Cd-Y{YWy1 zYx!}V-p?-Q?jbD8NptB0986hkhzlMD;U}y2Hpiy82-C2PZXNh;zUvZ_U^CfrPjnk_v^4%WJJ4>){8MBGpDg4-B3={)gd$C)0U@tN8S zu=S!YW;&Zr$ij12C3L-eD9(G>wewLXByokWR%(JV{nz+Rq6~-6$}|K75hPO-)(i8PfRzk{JFrqJ|J&! z*MDhYnwYZ_Nevh>xPp@A4DNkF10N&OVAwD>%1VgN))jaa7Lq_K;yKBV;CgKA16pCy zo1KZ1dUM}Mdw`ppCRKVnFsm?4X?5^h)mD8P-0BqoEHcH2yxAj#A~k&k@3*rDtQ7wE z9V=ScH&qj@%prYjl>ZuTx&F{m{WAUS+F4{@DFH}b=Nh589CUf~W|%mEFBK2*jX z!TX9xB=sJiW+AhO<-iX+kf!M%MXoQ!zwVWR>1ABa;~mJax6G-dM)=&a}>#2kkt$N zgW;3sW}}3`nzJ(}Prcf^J$d%ov%R+|EP9Zg@!(z*Iw`hMd<6s3UNq&$Q7}Kxg$dCd zm)7U~Kh4YXCXY8?0+tOeW7lv^?s1PD&fzzqmxO_$bTEzJ>nVtnh182UI;4RA z1aGoFHe?cl&*-{vmp5Qgy7MOqdKpSPk;z+3y@~G#a974@F&!XKoLUH6(J+yVG=@u+ zwEn^)#IKa_qZDCWc!+pnpXBF*5T_24u{UjBlzoDv2<+~Kow1S2ZzURs{gLSa1lhTT zEz!UrDj^AB5BFQh6UJ({RYBa&G>KflGE zaRMrQdvI_*1o?_rmJE$(G`LSM(0rv}9i^NpZeG=}{v<1+omx4ph+E5Y;SOx&JmBM! zhMLELbO6+=s^gIO889s*!k;?BG;gv53PXw#kFYFlvs>hCorj)eBv*n_)UbXBvrPm% zD2Zuq;V%DDU}Rs*X0gXtI==63%Iv_NO1EjY) zX7{ir?MfTtptnL!d0t(eL%qQWtPiB7dhc;S5+Mb1Q0X+qleJKaC?nh5LJ0>q@D%5i zY%t{tN#v?yeo78zXNbS~@X`xL^gKS#of}nT@P*M9cY|MuqlP2jg~O*#R2^T;Q^S11 zCBBNtrPlB;%EMY7XiSk$$jP_zkbb}Ze%y9GXEcibNZkFrdVmL;hX;As$-^!j5YL2< zh9Pe-bhA-cOI2QGUTnMgU`+-e;%mEVH

fb{@3)0Vw{-a)gZ341- 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 F{}0HoUL literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/mssql.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/mssql.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9ee1276228c568e41b4f507c490089b662d7d32 GIT binary patch literal 10716 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 b^T$WW^I84S6t07~GUI8q)KkEfQ`dh3C0}WZ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/mysql.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/mysql.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d5840c11423808b7fd20641c43480609b07a825 GIT binary patch literal 9933 zcmbta+ix7#d7t~vUbtLQ6h+zee^8Z%RR(nOsGT(@+A>%a}m5V!$6Wu*k3a?M8CN(r@*>x3*h#xG=k9jiWq6`izBqE%u? z-BXRCRTTI!_qoPt>$Jd+yJs2`)`Y-MxMv&ZtaHHA>?CG2X-&$~+Y&p)iuWW|bjEg6 zYYOG(*lCncJ5wl^P(H&ZP@Zt|D4$39EIWttIZ?iV@+6x=dCIwf_UD}w&H+|p=XX?K zg)TPS#YSR`U0~1OlXm3zXI(_^^GVADyU3>5%zc%;z+Swk?a0kFv)A$x_r3iukvef@rl{fNDBPqSXcd+GY=L|0}sOs2e!k4l1jzgU0ME73Z36tKp$I6l3$wTBYR%sK~?> z=W0R+ja*+zJM2Y*uIsihhgB?wfw zwteF#OY`$fb8$1Lm*;tNa}Goec%|kgtL!h)Y8JfpnkUw5-w^Xo=31rA>Ayzeg6#hz z5J$3PCP78WmdX@MbM(5wRHofln9hvb%9?UmvJ94D=DK2~*aXYqmt{FN#PZ@EMu~m} zln$^FcJO}T=hB+YM%kfzLzcIzZi13l#*aoc^E$yx4}|}g_IS5;aJXBKCb}*0 z=P<}q-?+t9Y*(a}o7m(+lo9eE7&glGac^XL39m=_LH3O_;_g_uY)&>K8gf3Z`9aNF z7n~U#?iTmFd7#_S9ek+L3Ys;q%AJPe1<_cy$6$R5OfEVUx*a+-%El*=!JVMRz3Onx zk^&Y0hj3@fqWv05Adnu&p%$ih6@MJK665@GpzcU@EtEqrX{d%qmL#<4{p9+Wn zR{0V(Fw~i}k@i0hr3ZRw5YB%Z8lHl_+OER?4fRHlqEYa^=*#bfI@&R&=zT9#y?mGv z_%FhYr!XZM=hvNan2J$<6KZ%X9cD$(--cO~a^a9@`+YcsQl4pFq<1kV?|1Ro{@3wH zc#6016#Zen+G1X2|GZFJ=${$NMa-=b&kd#N{@I~?xqp5rm-}ak^2hyiMA_Rj%VMa} z?9uXfVm4_p8!b#R{R@rO!6b;LQ7)zU1#B3fCP0kEX9&DV;CTWs0IV#co8Y>X=X^q} z*n25TB`TI*qV6vfNCA{Gd<+lkm_5jlq)TfId{+y$_iZHA+6o*N>3(a|;Zd$4Y)C98 z(S%UVq(khA>oz}en7!8Ws;n+f*=>ExQARsq?l%c2rp4lP~r!K{Se@x9Hni$>Q;Q;wjWB5 zyqVk0hBM=|osF8~&dfV^9Jjei;x@BXS@mZ&w}abF@8af57uUGstk#^1upiY8htJ$` zygM^)Z8hog()6n{J}l71O*B{59e<|cI*rv@b%rr_MrhmV&29c8%z>um{{}!B(PTwZ zo@h$^R30lSLsn!wRlwfwf3%G9wUNuodFrjAck;`t#uG!stthI%6{S=9Pwk0OG@dAm zfzga_QU8r*^xiDsO%r`hyeV+;rYO;yr!unWD`9^0%_r@#uJM@e=LCKN>nJIaUKO_W z6_l!E)jJpAq;+x2{uGcv3gtkqD?17w57ZE@86PJh2(%qiFLFmH9?IpC5oKzA4OY71 zRh^jghv#lP)s3DZ=i?ZYzYY)$GnjaI4_&LS@L6hHB=8o2c>)Ur-UT4r?Hd4+HY#gM z`$%tvdq#>hGG!$-<~PB9VxhFK&P6yac8(T$97qVKU?bf;8p?Hv7QA5sM;x({z9I3q z(V?Wmr4jy2Wo@nIK@cMo0!1}`WX{95uGHXxsqrcVtBX&IRrHqf28Q}4+`fo#3{%d~ zrhnU>?k(s02y0cx7@doh3~3Ls82I>7sMMvp94K{l!{q0&BuzlwKOd^NH70K;{5-j` zp$4UhC3ky&j8-xk*8}50iu@uN4HYIpx(h5+H&nc%@-MqzF!6;iyM5JN^{$Q)^icnM ziRr(J`)g3BW1&hAJ=4qxwFjA9X;-;xBxN-zr$U4OcbM{j$5Qx?@gU30T{%p}&kdFT zd$%5D1v8=+!c;K>_-V<=y>dKqGLLxvLW$?fZFTa6$;Gz*@nkqT5lN5a$@Yn<>GP%H zRMRWAHW_ww`uv-vsW)H$XzHU<>CK51J_;gp+PL;GdiJ-qm5*L5l%@dqWPDGF+Hj}^msQc#p{ z6&;E!uF>*xLCy)qXFS$4QfheW8q}L6lh|kEOssw-QkH0k6a#y#8SP?Eg7%-*fXt*Z zJJKdXX8r|8AnizZ^#E?sjyx~j`q^D0H0laJx|PRMMNR!KxD)PIs;)tL-VGIUuSoxe z`UC1K90XZH|5QDN{^#$Sp^5%zLI3xk*+1NRUyQ477#zX+R(`9vmB%~~jW&b`95yHt#xd*Rw9(jqos;EKOh#E!IXfTlKj!gdgE^QYh^%v?#By5{1 zp2Rf5j=-M`u3@7?s68*HTONkd+rF=o> zB4b82;UG=^D3F>IO1DPt%6}<6fN|TE>vAanjk2YIYe;}gidD1`%s|W8+*xx$eyEO~ zIkx@UdNk7V$Zut~*R-1=rQttRB4zpoUd0d(<=EEs^g-qgR;vaieV5N*a>880*?E$Y z+CTp8JxN%SICA<8w^>D)d3m}w+W&W{r#egRvhUzZxr6oaH3GK@beF_yC?X}+zZT2n z4gyxJ)H1Ix+*rAOb#A4ka|=DA9I_P-_d!kMcr3FCv2h*L{0SPV7;Dy!T1Q3%LbtX7 zDeF#*O8KPTUfrgButxe?lRC&e8ngym+px5%*AXf)`vscGzCzVUX7Uq!a#JLzROPV& zrxj2ikxht^8LMN}gmN~N{?Z>4uD&mna^=NxaxIoi!;xu=@ZGkfjBPiXtmQhubGE(J zs<^~xC}nM%)!>Xn8`#zXxbL2ijD|?2+3?Ld_#0^w;fML#Uenud_*Nly%lJA17;^Xn z;kXO0k(0m0KOn#ekX4F?l9;0ExUOyYX(Qow67_fyfZ`aRLK72)hYXMbV`ONiA<6sx z{_B!InjXoTbLLs`KFP?yMe=%uXX%lExe8&piT7 z0-FQ`i}rw+HAll0+E>yBrp&4Q)GWwT#3lRWo_1%EW(f1i-=i$peO)Lz8g3?1y z3VH*<=@qA& z#M*F7zffRMl~A%(=ugRp=EH}TavUA&B6Q$6yhqGLUO4uz`%O$@7ESxkL~N}fm!MRD zehWk?5mBT@vA6A`LPhn5w9}n$l;rY5eeX8V7<`LBZyS0XbOjCb^aWZjc;*j+cu(yH ztp%6tQ@q>V4J1XxUWna5F4ABo9Yx&FhWoi(cEy5a<=pvj}FE*Rh09kGFA4+$1gV-{)8&$@2x z2^QzxnJvG)Fmr8r{_5ff#ku9Bo7c+4Cj9d_Uti?TR;$LHVsP6jc9L~PQYF)+e2>w% z*k-|FV$p&Idsco3Ym0Jo$oy42w!k^u8FK%vY|tbpIu3}5ZV;h89Qr-zx)1AKC&r>! zTFMdY_I^-RG7`zk=q+6# zMkbE#GO|q4%ily*8KLnWJ=PN&feFE+t{#0^0Rk09W&RuiA7CI^0k(qoFG=rpz8c^p za0kqh=#2JjS3Op{Q)93$MUe$5#!fLFlyzuT1HXvln<7$Xn@tY?r?|Qu^A3(64wjn% zDPocCb>dxps75K+bCKWeoMv#5sp_L5Eh8%0B8g?(Ly^HEu6HtSU4FtCR3g!L==x(*-AM#@zyw-255i?d5B3)eeRg7R6voxicLQk-8{oV~fULgn22_2u_F z6-aJo@##3*C%E_k6}!rqk5F-&KyRM~ZAKH?#EGdS5DkeQdGwYvMb66^)qFCdD3ADE z)I7E8^x^NZ_y7qi5CE#B4wX`eLa9TY)S*JYFp|Zq<@PWZo9GC!Y$*B*7(Y(r3T4+- zai#1t*No&)yyIXo3f1E=gq|22<2y+LrwA!lxZxDL{WXz92zg8rkwqjwMEo9U-||1- zk2oZMf;fvnp3NBvkANEzz8D}8$dnai(gcryOd}vptWm)V@gM&gzEH|NlO|l~%Fod0 z|3(cQXCVijP~%rb4GL@$20@@ldDT98e8c~OJ~ct$4uO*d$ee*!g(N*|Utb{| z6FqcZM=cSY0(za2SCkG*S`7%LS#Ils%$8QZxwO}ucSIcwP7p^Uo zR~FmZj!2S>W+B4Ys2+uqiF1b7nND103e9i=%cImLZ&n@O7gB2F=wzVNW{t+_BHN9R zXze={x8(?f#&3cWe~g$o1t9((6naiv@Xxe`$AL(uLh(DCX$|2*PtO^+;Cb5KHEhKV{42?KwR&bS%nmdqw|{!wfiA;|bP>nd zvo4+OZ%_I~N=_#RhmNv~K{Hdtg>!8mZU^}aei1=avF3r!H8#+de3ABZv1VSGeQRlf z9ySHYxc`=EvP z&ZJx_#75~IP+z$B*63oKNqW!0u}~+GZ;iem=Q;;#3pnXTVyf3K%Fp^Z<)YY5xJ=fe zJ2*ZF>^<3hew99Vn84=%k-5no$~kh8$D)5_5C_9J=B594unvHh@&7k;=EFb5YY3ux z29;dxC=)vYv?qIbH5%&}@4*v_-xK?iGMD0W9}yEGvH4gtk;a#E8fhfK)-dVxG-K2L zSM>dtDCS*j;qVtxF8$k*#BTsZ1{_Xgf5llhCno2_<(zbpm4R4RVn&L?sWdf_XNe;Y z>{P6HN#FA!0(k;-vdqbCFa6EYSaQOw)IS{8xgQ(qwh` z5dx%ft&_cpOeYG(pN>z-#5~Avu}1nj0iC93oy6EWw5K8dw-<2~WgXtr7Dw`%wODtb zq8StkZ~|8dkZT%yg&|?&9|$`bD}P~Zh~RyDDm0PsJj^%Ztixp@w7&|FQ%r1?_$h#N sKkKI;_t-v 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 E0B-~dg#Z8m literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/postgresql.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/postgresql.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edef3be9c0635bd8fc711f151e4dda9fc81991a6 GIT binary patch literal 21733 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/sqlite.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/ddl/__pycache__/sqlite.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c32165b67e81c8608fbdff1018c51d76cfc952ce GIT binary patch literal 5973 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 LOL=u-D6jqp|EM`h literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/_autogen.py b/venv/lib/python3.8/site-packages/alembic/ddl/_autogen.py new file mode 100644 index 000000000..74715b18a --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/_autogen.py @@ -0,0 +1,329 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from typing import Any +from typing import ClassVar +from typing import Dict +from typing import Generic +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy.sql.schema import Constraint +from sqlalchemy.sql.schema import ForeignKeyConstraint +from sqlalchemy.sql.schema import Index +from sqlalchemy.sql.schema import UniqueConstraint +from typing_extensions import TypeGuard + +from .. import util +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + + from alembic.autogenerate.api import AutogenContext + from alembic.ddl.impl import DefaultImpl + +CompareConstraintType = Union[Constraint, Index] + +_C = TypeVar("_C", bound=CompareConstraintType) + +_clsreg: Dict[str, Type[_constraint_sig]] = {} + + +class ComparisonResult(NamedTuple): + status: Literal["equal", "different", "skip"] + message: str + + @property + def is_equal(self) -> bool: + return self.status == "equal" + + @property + def is_different(self) -> bool: + return self.status == "different" + + @property + def is_skip(self) -> bool: + return self.status == "skip" + + @classmethod + def Equal(cls) -> ComparisonResult: + """the constraints are equal.""" + return cls("equal", "The two constraints are equal") + + @classmethod + def Different(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult: + """the constraints are different for the provided reason(s).""" + return cls("different", ", ".join(util.to_list(reason))) + + @classmethod + def Skip(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult: + """the constraint cannot be compared for the provided reason(s). + + The message is logged, but the constraints will be otherwise + considered equal, meaning that no migration command will be + generated. + """ + return cls("skip", ", ".join(util.to_list(reason))) + + +class _constraint_sig(Generic[_C]): + const: _C + + _sig: Tuple[Any, ...] + name: Optional[sqla_compat._ConstraintNameDefined] + + impl: DefaultImpl + + _is_index: ClassVar[bool] = False + _is_fk: ClassVar[bool] = False + _is_uq: ClassVar[bool] = False + + _is_metadata: bool + + def __init_subclass__(cls) -> None: + cls._register() + + @classmethod + def _register(cls): + raise NotImplementedError() + + def __init__( + self, is_metadata: bool, impl: DefaultImpl, const: _C + ) -> None: + raise NotImplementedError() + + def compare_to_reflected( + self, other: _constraint_sig[Any] + ) -> ComparisonResult: + assert self.impl is other.impl + assert self._is_metadata + assert not other._is_metadata + + return self._compare_to_reflected(other) + + def _compare_to_reflected( + self, other: _constraint_sig[_C] + ) -> ComparisonResult: + raise NotImplementedError() + + @classmethod + def from_constraint( + cls, is_metadata: bool, impl: DefaultImpl, constraint: _C + ) -> _constraint_sig[_C]: + # these could be cached by constraint/impl, however, if the + # constraint is modified in place, then the sig is wrong. the mysql + # impl currently does this, and if we fixed that we can't be sure + # someone else might do it too, so play it safe. + sig = _clsreg[constraint.__visit_name__](is_metadata, impl, constraint) + return sig + + def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]: + return sqla_compat._get_constraint_final_name( + self.const, context.dialect + ) + + @util.memoized_property + def is_named(self): + return sqla_compat._constraint_is_named(self.const, self.impl.dialect) + + @util.memoized_property + def unnamed(self) -> Tuple[Any, ...]: + return self._sig + + @util.memoized_property + def unnamed_no_options(self) -> Tuple[Any, ...]: + raise NotImplementedError() + + @util.memoized_property + def _full_sig(self) -> Tuple[Any, ...]: + return (self.name,) + self.unnamed + + def __eq__(self, other) -> bool: + return self._full_sig == other._full_sig + + def __ne__(self, other) -> bool: + return self._full_sig != other._full_sig + + def __hash__(self) -> int: + return hash(self._full_sig) + + +class _uq_constraint_sig(_constraint_sig[UniqueConstraint]): + _is_uq = True + + @classmethod + def _register(cls) -> None: + _clsreg["unique_constraint"] = cls + + is_unique = True + + def __init__( + self, + is_metadata: bool, + impl: DefaultImpl, + const: UniqueConstraint, + ) -> None: + self.impl = impl + self.const = const + self.name = sqla_compat.constraint_name_or_none(const.name) + self._sig = tuple(sorted([col.name for col in const.columns])) + self._is_metadata = is_metadata + + @property + def column_names(self) -> Tuple[str, ...]: + return tuple([col.name for col in self.const.columns]) + + def _compare_to_reflected( + self, other: _constraint_sig[_C] + ) -> ComparisonResult: + assert self._is_metadata + metadata_obj = self + conn_obj = other + + assert is_uq_sig(conn_obj) + return self.impl.compare_unique_constraint( + metadata_obj.const, conn_obj.const + ) + + +class _ix_constraint_sig(_constraint_sig[Index]): + _is_index = True + + name: sqla_compat._ConstraintName + + @classmethod + def _register(cls) -> None: + _clsreg["index"] = cls + + def __init__( + self, is_metadata: bool, impl: DefaultImpl, const: Index + ) -> None: + self.impl = impl + self.const = const + self.name = const.name + self.is_unique = bool(const.unique) + self._is_metadata = is_metadata + + def _compare_to_reflected( + self, other: _constraint_sig[_C] + ) -> ComparisonResult: + assert self._is_metadata + metadata_obj = self + conn_obj = other + + assert is_index_sig(conn_obj) + return self.impl.compare_indexes(metadata_obj.const, conn_obj.const) + + @util.memoized_property + def has_expressions(self): + return sqla_compat.is_expression_index(self.const) + + @util.memoized_property + def column_names(self) -> Tuple[str, ...]: + return tuple([col.name for col in self.const.columns]) + + @util.memoized_property + def column_names_optional(self) -> Tuple[Optional[str], ...]: + return tuple( + [getattr(col, "name", None) for col in self.const.expressions] + ) + + @util.memoized_property + def is_named(self): + return True + + @util.memoized_property + def unnamed(self): + return (self.is_unique,) + self.column_names_optional + + +class _fk_constraint_sig(_constraint_sig[ForeignKeyConstraint]): + _is_fk = True + + @classmethod + def _register(cls) -> None: + _clsreg["foreign_key_constraint"] = cls + + def __init__( + self, + is_metadata: bool, + impl: DefaultImpl, + const: ForeignKeyConstraint, + ) -> None: + self._is_metadata = is_metadata + + self.impl = impl + self.const = const + + self.name = sqla_compat.constraint_name_or_none(const.name) + + ( + self.source_schema, + self.source_table, + self.source_columns, + self.target_schema, + self.target_table, + self.target_columns, + onupdate, + ondelete, + deferrable, + initially, + ) = sqla_compat._fk_spec(const) + + self._sig: Tuple[Any, ...] = ( + self.source_schema, + self.source_table, + tuple(self.source_columns), + self.target_schema, + self.target_table, + tuple(self.target_columns), + ) + ( + ( + (None if onupdate.lower() == "no action" else onupdate.lower()) + if onupdate + else None + ), + ( + (None if ondelete.lower() == "no action" else ondelete.lower()) + if ondelete + else None + ), + # convert initially + deferrable into one three-state value + ( + "initially_deferrable" + if initially and initially.lower() == "deferred" + else "deferrable" if deferrable else "not deferrable" + ), + ) + + @util.memoized_property + def unnamed_no_options(self): + return ( + self.source_schema, + self.source_table, + tuple(self.source_columns), + self.target_schema, + self.target_table, + tuple(self.target_columns), + ) + + +def is_index_sig(sig: _constraint_sig) -> TypeGuard[_ix_constraint_sig]: + return sig._is_index + + +def is_uq_sig(sig: _constraint_sig) -> TypeGuard[_uq_constraint_sig]: + return sig._is_uq + + +def is_fk_sig(sig: _constraint_sig) -> TypeGuard[_fk_constraint_sig]: + return sig._is_fk diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/base.py b/venv/lib/python3.8/site-packages/alembic/ddl/base.py new file mode 100644 index 000000000..6fbe95245 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/base.py @@ -0,0 +1,336 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import functools +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import exc +from sqlalchemy import Integer +from sqlalchemy import types as sqltypes +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.schema import Column +from sqlalchemy.schema import DDLElement +from sqlalchemy.sql.elements import quoted_name + +from ..util.sqla_compat import _columns_for_constraint # noqa +from ..util.sqla_compat import _find_columns # noqa +from ..util.sqla_compat import _fk_spec # noqa +from ..util.sqla_compat import _is_type_bound # noqa +from ..util.sqla_compat import _table_for_constraint # noqa + +if TYPE_CHECKING: + from typing import Any + + from sqlalchemy.sql.compiler import Compiled + from sqlalchemy.sql.compiler import DDLCompiler + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.functions import Function + from sqlalchemy.sql.schema import FetchedValue + from sqlalchemy.sql.type_api import TypeEngine + + from .impl import DefaultImpl + from ..util.sqla_compat import Computed + from ..util.sqla_compat import Identity + +_ServerDefault = Union["TextClause", "FetchedValue", "Function[Any]", str] + + +class AlterTable(DDLElement): + """Represent an ALTER TABLE statement. + + Only the string name and optional schema name of the table + is required, not a full Table object. + + """ + + def __init__( + self, + table_name: str, + schema: Optional[Union[quoted_name, str]] = None, + ) -> None: + self.table_name = table_name + self.schema = schema + + +class RenameTable(AlterTable): + def __init__( + self, + old_table_name: str, + new_table_name: Union[quoted_name, str], + schema: Optional[Union[quoted_name, str]] = None, + ) -> None: + super().__init__(old_table_name, schema=schema) + self.new_table_name = new_table_name + + +class AlterColumn(AlterTable): + def __init__( + self, + name: str, + column_name: str, + schema: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_nullable: Optional[bool] = None, + existing_server_default: Optional[_ServerDefault] = None, + existing_comment: Optional[str] = None, + ) -> None: + super().__init__(name, schema=schema) + self.column_name = column_name + self.existing_type = ( + sqltypes.to_instance(existing_type) + if existing_type is not None + else None + ) + self.existing_nullable = existing_nullable + self.existing_server_default = existing_server_default + self.existing_comment = existing_comment + + +class ColumnNullable(AlterColumn): + def __init__( + self, name: str, column_name: str, nullable: bool, **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.nullable = nullable + + +class ColumnType(AlterColumn): + def __init__( + self, name: str, column_name: str, type_: TypeEngine, **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.type_ = sqltypes.to_instance(type_) + + +class ColumnName(AlterColumn): + def __init__( + self, name: str, column_name: str, newname: str, **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.newname = newname + + +class ColumnDefault(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + default: Optional[_ServerDefault], + **kw, + ) -> None: + super().__init__(name, column_name, **kw) + self.default = default + + +class ComputedColumnDefault(AlterColumn): + def __init__( + self, name: str, column_name: str, default: Optional[Computed], **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.default = default + + +class IdentityColumnDefault(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + default: Optional[Identity], + impl: DefaultImpl, + **kw, + ) -> None: + super().__init__(name, column_name, **kw) + self.default = default + self.impl = impl + + +class AddColumn(AlterTable): + def __init__( + self, + name: str, + column: Column[Any], + schema: Optional[Union[quoted_name, str]] = None, + ) -> None: + super().__init__(name, schema=schema) + self.column = column + + +class DropColumn(AlterTable): + def __init__( + self, name: str, column: Column[Any], schema: Optional[str] = None + ) -> None: + super().__init__(name, schema=schema) + self.column = column + + +class ColumnComment(AlterColumn): + def __init__( + self, name: str, column_name: str, comment: Optional[str], **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.comment = comment + + +@compiles(RenameTable) +def visit_rename_table( + element: RenameTable, compiler: DDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, element.schema), + ) + + +@compiles(AddColumn) +def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + add_column(compiler, element.column, **kw), + ) + + +@compiles(DropColumn) +def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + drop_column(compiler, element.column.name, **kw), + ) + + +@compiles(ColumnNullable) +def visit_column_nullable( + element: ColumnNullable, compiler: DDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "DROP NOT NULL" if element.nullable else "SET NOT NULL", + ) + + +@compiles(ColumnType) +def visit_column_type(element: ColumnType, compiler: DDLCompiler, **kw) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "TYPE %s" % format_type(compiler, element.type_), + ) + + +@compiles(ColumnName) +def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str: + return "%s RENAME %s TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +@compiles(ColumnDefault) +def visit_column_default( + element: ColumnDefault, compiler: DDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ( + "SET DEFAULT %s" % format_server_default(compiler, element.default) + if element.default is not None + else "DROP DEFAULT" + ), + ) + + +@compiles(ComputedColumnDefault) +def visit_computed_column( + element: ComputedColumnDefault, compiler: DDLCompiler, **kw +): + raise exc.CompileError( + 'Adding or removing a "computed" construct, e.g. GENERATED ' + "ALWAYS AS, to or from an existing column is not supported." + ) + + +@compiles(IdentityColumnDefault) +def visit_identity_column( + element: IdentityColumnDefault, compiler: DDLCompiler, **kw +): + raise exc.CompileError( + 'Adding, removing or modifying an "identity" construct, ' + "e.g. GENERATED AS IDENTITY, to or from an existing " + "column is not supported in this dialect." + ) + + +def quote_dotted( + name: Union[quoted_name, str], quote: functools.partial +) -> Union[quoted_name, str]: + """quote the elements of a dotted name""" + + if isinstance(name, quoted_name): + return quote(name) + result = ".".join([quote(x) for x in name.split(".")]) + return result + + +def format_table_name( + compiler: Compiled, + name: Union[quoted_name, str], + schema: Optional[Union[quoted_name, str]], +) -> Union[quoted_name, str]: + quote = functools.partial(compiler.preparer.quote) + if schema: + return quote_dotted(schema, quote) + "." + quote(name) + else: + return quote(name) + + +def format_column_name( + compiler: DDLCompiler, name: Optional[Union[quoted_name, str]] +) -> Union[quoted_name, str]: + return compiler.preparer.quote(name) # type: ignore[arg-type] + + +def format_server_default( + compiler: DDLCompiler, + default: Optional[_ServerDefault], +) -> str: + return compiler.get_column_default_string( + Column("x", Integer, server_default=default) + ) + + +def format_type(compiler: DDLCompiler, type_: TypeEngine) -> str: + return compiler.dialect.type_compiler.process(type_) + + +def alter_table( + compiler: DDLCompiler, + name: str, + schema: Optional[str], +) -> str: + return "ALTER TABLE %s" % format_table_name(compiler, name, schema) + + +def drop_column(compiler: DDLCompiler, name: str, **kw) -> str: + return "DROP COLUMN %s" % format_column_name(compiler, name) + + +def alter_column(compiler: DDLCompiler, name: str) -> str: + return "ALTER COLUMN %s" % format_column_name(compiler, name) + + +def add_column(compiler: DDLCompiler, column: Column[Any], **kw) -> str: + text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw) + + const = " ".join( + compiler.process(constraint) for constraint in column.constraints + ) + if const: + text += " " + const + + return text diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/impl.py b/venv/lib/python3.8/site-packages/alembic/ddl/impl.py new file mode 100644 index 000000000..2609a62de --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/impl.py @@ -0,0 +1,886 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import logging +import re +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import List +from typing import Mapping +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import cast +from sqlalchemy import Column +from sqlalchemy import MetaData +from sqlalchemy import PrimaryKeyConstraint +from sqlalchemy import schema +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import text + +from . import _autogen +from . import base +from ._autogen import _constraint_sig as _constraint_sig +from ._autogen import ComparisonResult as ComparisonResult +from .. import util +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + from typing import TextIO + + from sqlalchemy.engine import Connection + from sqlalchemy.engine import Dialect + from sqlalchemy.engine.cursor import CursorResult + from sqlalchemy.engine.reflection import Inspector + from sqlalchemy.sql import ClauseElement + from sqlalchemy.sql import Executable + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.selectable import TableClause + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefault + from ..autogenerate.api import AutogenContext + from ..operations.batch import ApplyBatchImpl + from ..operations.batch import BatchOperationsImpl + +log = logging.getLogger(__name__) + + +class ImplMeta(type): + def __init__( + cls, + classname: str, + bases: Tuple[Type[DefaultImpl]], + dict_: Dict[str, Any], + ): + newtype = type.__init__(cls, classname, bases, dict_) + if "__dialect__" in dict_: + _impls[dict_["__dialect__"]] = cls # type: ignore[assignment] + return newtype + + +_impls: Dict[str, Type[DefaultImpl]] = {} + + +class DefaultImpl(metaclass=ImplMeta): + """Provide the entrypoint for major migration operations, + including database-specific behavioral variances. + + While individual SQL/DDL constructs already provide + for database-specific implementations, variances here + allow for entirely different sequences of operations + to take place for a particular migration, such as + SQL Server's special 'IDENTITY INSERT' step for + bulk inserts. + + """ + + __dialect__ = "default" + + transactional_ddl = False + command_terminator = ";" + type_synonyms: Tuple[Set[str], ...] = ({"NUMERIC", "DECIMAL"},) + type_arg_extract: Sequence[str] = () + # These attributes are deprecated in SQLAlchemy via #10247. They need to + # be ignored to support older version that did not use dialect kwargs. + # They only apply to Oracle and are replaced by oracle_order, + # oracle_on_null + identity_attrs_ignore: Tuple[str, ...] = ("order", "on_null") + + def __init__( + self, + dialect: Dialect, + connection: Optional[Connection], + as_sql: bool, + transactional_ddl: Optional[bool], + output_buffer: Optional[TextIO], + context_opts: Dict[str, Any], + ) -> None: + self.dialect = dialect + self.connection = connection + self.as_sql = as_sql + self.literal_binds = context_opts.get("literal_binds", False) + + self.output_buffer = output_buffer + self.memo: dict = {} + self.context_opts = context_opts + if transactional_ddl is not None: + self.transactional_ddl = transactional_ddl + + if self.literal_binds: + if not self.as_sql: + raise util.CommandError( + "Can't use literal_binds setting without as_sql mode" + ) + + @classmethod + def get_by_dialect(cls, dialect: Dialect) -> Type[DefaultImpl]: + return _impls[dialect.name] + + def static_output(self, text: str) -> None: + assert self.output_buffer is not None + self.output_buffer.write(text + "\n\n") + self.output_buffer.flush() + + def version_table_impl( + self, + *, + version_table: str, + version_table_schema: Optional[str], + version_table_pk: bool, + **kw: Any, + ) -> Table: + """Generate a :class:`.Table` object which will be used as the + structure for the Alembic version table. + + Third party dialects may override this hook to provide an alternate + structure for this :class:`.Table`; requirements are only that it + be named based on the ``version_table`` parameter and contains + at least a single string-holding column named ``version_num``. + + .. versionadded:: 1.14 + + """ + vt = Table( + version_table, + MetaData(), + Column("version_num", String(32), nullable=False), + schema=version_table_schema, + ) + if version_table_pk: + vt.append_constraint( + PrimaryKeyConstraint( + "version_num", name=f"{version_table}_pkc" + ) + ) + + return vt + + def requires_recreate_in_batch( + self, batch_op: BatchOperationsImpl + ) -> bool: + """Return True if the given :class:`.BatchOperationsImpl` + would need the table to be recreated and copied in order to + proceed. + + Normally, only returns True on SQLite when operations other + than add_column are present. + + """ + return False + + def prep_table_for_batch( + self, batch_impl: ApplyBatchImpl, table: Table + ) -> None: + """perform any operations needed on a table before a new + one is created to replace it in batch mode. + + the PG dialect uses this to drop constraints on the table + before the new one uses those same names. + + """ + + @property + def bind(self) -> Optional[Connection]: + return self.connection + + def _exec( + self, + construct: Union[Executable, str], + execution_options: Optional[Mapping[str, Any]] = None, + multiparams: Optional[Sequence[Mapping[str, Any]]] = None, + params: Mapping[str, Any] = util.immutabledict(), + ) -> Optional[CursorResult]: + if isinstance(construct, str): + construct = text(construct) + if self.as_sql: + if multiparams is not None or params: + raise TypeError("SQL parameters not allowed with as_sql") + + compile_kw: dict[str, Any] + if self.literal_binds and not isinstance( + construct, schema.DDLElement + ): + compile_kw = dict(compile_kwargs={"literal_binds": True}) + else: + compile_kw = {} + + if TYPE_CHECKING: + assert isinstance(construct, ClauseElement) + compiled = construct.compile(dialect=self.dialect, **compile_kw) + self.static_output( + str(compiled).replace("\t", " ").strip() + + self.command_terminator + ) + return None + else: + conn = self.connection + assert conn is not None + if execution_options: + conn = conn.execution_options(**execution_options) + + if params and multiparams is not None: + raise TypeError( + "Can't send params and multiparams at the same time" + ) + + if multiparams: + return conn.execute(construct, multiparams) + else: + return conn.execute(construct, params) + + def execute( + self, + sql: Union[Executable, str], + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + self._exec(sql, execution_options) + + def alter_column( + self, + table_name: str, + column_name: str, + nullable: Optional[bool] = None, + server_default: Union[_ServerDefault, Literal[False]] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + autoincrement: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + existing_comment: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Optional[_ServerDefault] = None, + existing_nullable: Optional[bool] = None, + existing_autoincrement: Optional[bool] = None, + **kw: Any, + ) -> None: + if autoincrement is not None or existing_autoincrement is not None: + util.warn( + "autoincrement and existing_autoincrement " + "only make sense for MySQL", + stacklevel=3, + ) + if nullable is not None: + self._exec( + base.ColumnNullable( + table_name, + column_name, + nullable, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + ) + ) + if server_default is not False: + kw = {} + cls_: Type[ + Union[ + base.ComputedColumnDefault, + base.IdentityColumnDefault, + base.ColumnDefault, + ] + ] + if sqla_compat._server_default_is_computed( + server_default, existing_server_default + ): + cls_ = base.ComputedColumnDefault + elif sqla_compat._server_default_is_identity( + server_default, existing_server_default + ): + cls_ = base.IdentityColumnDefault + kw["impl"] = self + else: + cls_ = base.ColumnDefault + self._exec( + cls_( + table_name, + column_name, + server_default, # type:ignore[arg-type] + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + **kw, + ) + ) + if type_ is not None: + self._exec( + base.ColumnType( + table_name, + column_name, + type_, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + ) + ) + + if comment is not False: + self._exec( + base.ColumnComment( + table_name, + column_name, + comment, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + ) + ) + + # do the new name last ;) + if name is not None: + self._exec( + base.ColumnName( + table_name, + column_name, + name, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + ) + ) + + def add_column( + self, + table_name: str, + column: Column[Any], + schema: Optional[Union[str, quoted_name]] = None, + ) -> None: + self._exec(base.AddColumn(table_name, column, schema=schema)) + + def drop_column( + self, + table_name: str, + column: Column[Any], + schema: Optional[str] = None, + **kw, + ) -> None: + self._exec(base.DropColumn(table_name, column, schema=schema)) + + def add_constraint(self, const: Any) -> None: + if const._create_rule is None or const._create_rule(self): + self._exec(schema.AddConstraint(const)) + + def drop_constraint(self, const: Constraint) -> None: + self._exec(schema.DropConstraint(const)) + + def rename_table( + self, + old_table_name: str, + new_table_name: Union[str, quoted_name], + schema: Optional[Union[str, quoted_name]] = None, + ) -> None: + self._exec( + base.RenameTable(old_table_name, new_table_name, schema=schema) + ) + + def create_table(self, table: Table, **kw: Any) -> None: + table.dispatch.before_create( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + self._exec(schema.CreateTable(table, **kw)) + table.dispatch.after_create( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + for index in table.indexes: + self._exec(schema.CreateIndex(index)) + + with_comment = ( + self.dialect.supports_comments and not self.dialect.inline_comments + ) + comment = table.comment + if comment and with_comment: + self.create_table_comment(table) + + for column in table.columns: + comment = column.comment + if comment and with_comment: + self.create_column_comment(column) + + def drop_table(self, table: Table, **kw: Any) -> None: + table.dispatch.before_drop( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + self._exec(schema.DropTable(table, **kw)) + table.dispatch.after_drop( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + + def create_index(self, index: Index, **kw: Any) -> None: + self._exec(schema.CreateIndex(index, **kw)) + + def create_table_comment(self, table: Table) -> None: + self._exec(schema.SetTableComment(table)) + + def drop_table_comment(self, table: Table) -> None: + self._exec(schema.DropTableComment(table)) + + def create_column_comment(self, column: ColumnElement[Any]) -> None: + self._exec(schema.SetColumnComment(column)) + + def drop_index(self, index: Index, **kw: Any) -> None: + self._exec(schema.DropIndex(index, **kw)) + + def bulk_insert( + self, + table: Union[TableClause, Table], + rows: List[dict], + multiinsert: bool = True, + ) -> None: + if not isinstance(rows, list): + raise TypeError("List expected") + elif rows and not isinstance(rows[0], dict): + raise TypeError("List of dictionaries expected") + if self.as_sql: + for row in rows: + self._exec( + sqla_compat._insert_inline(table).values( + **{ + k: ( + sqla_compat._literal_bindparam( + k, v, type_=table.c[k].type + ) + if not isinstance( + v, sqla_compat._literal_bindparam + ) + else v + ) + for k, v in row.items() + } + ) + ) + else: + if rows: + if multiinsert: + self._exec( + sqla_compat._insert_inline(table), multiparams=rows + ) + else: + for row in rows: + self._exec( + sqla_compat._insert_inline(table).values(**row) + ) + + def _tokenize_column_type(self, column: Column) -> Params: + definition: str + definition = self.dialect.type_compiler.process(column.type).lower() + + # tokenize the SQLAlchemy-generated version of a type, so that + # the two can be compared. + # + # examples: + # NUMERIC(10, 5) + # TIMESTAMP WITH TIMEZONE + # INTEGER UNSIGNED + # INTEGER (10) UNSIGNED + # INTEGER(10) UNSIGNED + # varchar character set utf8 + # + + tokens: List[str] = re.findall(r"[\w\-_]+|\(.+?\)", definition) + + term_tokens: List[str] = [] + paren_term = None + + for token in tokens: + if re.match(r"^\(.*\)$", token): + paren_term = token + else: + term_tokens.append(token) + + params = Params(term_tokens[0], term_tokens[1:], [], {}) + + if paren_term: + term: str + for term in re.findall("[^(),]+", paren_term): + if "=" in term: + key, val = term.split("=") + params.kwargs[key.strip()] = val.strip() + else: + params.args.append(term.strip()) + + return params + + def _column_types_match( + self, inspector_params: Params, metadata_params: Params + ) -> bool: + if inspector_params.token0 == metadata_params.token0: + return True + + synonyms = [{t.lower() for t in batch} for batch in self.type_synonyms] + inspector_all_terms = " ".join( + [inspector_params.token0] + inspector_params.tokens + ) + metadata_all_terms = " ".join( + [metadata_params.token0] + metadata_params.tokens + ) + + for batch in synonyms: + if {inspector_all_terms, metadata_all_terms}.issubset(batch) or { + inspector_params.token0, + metadata_params.token0, + }.issubset(batch): + return True + return False + + def _column_args_match( + self, inspected_params: Params, meta_params: Params + ) -> bool: + """We want to compare column parameters. However, we only want + to compare parameters that are set. If they both have `collation`, + we want to make sure they are the same. However, if only one + specifies it, dont flag it for being less specific + """ + + if ( + len(meta_params.tokens) == len(inspected_params.tokens) + and meta_params.tokens != inspected_params.tokens + ): + return False + + if ( + len(meta_params.args) == len(inspected_params.args) + and meta_params.args != inspected_params.args + ): + return False + + insp = " ".join(inspected_params.tokens).lower() + meta = " ".join(meta_params.tokens).lower() + + for reg in self.type_arg_extract: + mi = re.search(reg, insp) + mm = re.search(reg, meta) + + if mi and mm and mi.group(1) != mm.group(1): + return False + + return True + + def compare_type( + self, inspector_column: Column[Any], metadata_column: Column + ) -> bool: + """Returns True if there ARE differences between the types of the two + columns. Takes impl.type_synonyms into account between retrospected + and metadata types + """ + inspector_params = self._tokenize_column_type(inspector_column) + metadata_params = self._tokenize_column_type(metadata_column) + + if not self._column_types_match(inspector_params, metadata_params): + return True + if not self._column_args_match(inspector_params, metadata_params): + return True + return False + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + return rendered_inspector_default != rendered_metadata_default + + def correct_for_autogen_constraints( + self, + conn_uniques: Set[UniqueConstraint], + conn_indexes: Set[Index], + metadata_unique_constraints: Set[UniqueConstraint], + metadata_indexes: Set[Index], + ) -> None: + pass + + def cast_for_batch_migrate(self, existing, existing_transfer, new_type): + if existing.type._type_affinity is not new_type._type_affinity: + existing_transfer["expr"] = cast( + existing_transfer["expr"], new_type + ) + + def render_ddl_sql_expr( + self, expr: ClauseElement, is_server_default: bool = False, **kw: Any + ) -> str: + """Render a SQL expression that is typically a server default, + index expression, etc. + + """ + + compile_kw = {"literal_binds": True, "include_table": False} + + return str( + expr.compile(dialect=self.dialect, compile_kwargs=compile_kw) + ) + + def _compat_autogen_column_reflect(self, inspector: Inspector) -> Callable: + return self.autogen_column_reflect + + def correct_for_autogen_foreignkeys( + self, + conn_fks: Set[ForeignKeyConstraint], + metadata_fks: Set[ForeignKeyConstraint], + ) -> None: + pass + + def autogen_column_reflect(self, inspector, table, column_info): + """A hook that is attached to the 'column_reflect' event for when + a Table is reflected from the database during the autogenerate + process. + + Dialects can elect to modify the information gathered here. + + """ + + def start_migrations(self) -> None: + """A hook called when :meth:`.EnvironmentContext.run_migrations` + is called. + + Implementations can set up per-migration-run state here. + + """ + + def emit_begin(self) -> None: + """Emit the string ``BEGIN``, or the backend-specific + equivalent, on the current connection context. + + This is used in offline mode and typically + via :meth:`.EnvironmentContext.begin_transaction`. + + """ + self.static_output("BEGIN" + self.command_terminator) + + def emit_commit(self) -> None: + """Emit the string ``COMMIT``, or the backend-specific + equivalent, on the current connection context. + + This is used in offline mode and typically + via :meth:`.EnvironmentContext.begin_transaction`. + + """ + self.static_output("COMMIT" + self.command_terminator) + + def render_type( + self, type_obj: TypeEngine, autogen_context: AutogenContext + ) -> Union[str, Literal[False]]: + return False + + def _compare_identity_default(self, metadata_identity, inspector_identity): + # ignored contains the attributes that were not considered + # because assumed to their default values in the db. + diff, ignored = _compare_identity_options( + metadata_identity, + inspector_identity, + sqla_compat.Identity(), + skip={"always"}, + ) + + meta_always = getattr(metadata_identity, "always", None) + inspector_always = getattr(inspector_identity, "always", None) + # None and False are the same in this comparison + if bool(meta_always) != bool(inspector_always): + diff.add("always") + + diff.difference_update(self.identity_attrs_ignore) + + # returns 3 values: + return ( + # different identity attributes + diff, + # ignored identity attributes + ignored, + # if the two identity should be considered different + bool(diff) or bool(metadata_identity) != bool(inspector_identity), + ) + + def _compare_index_unique( + self, metadata_index: Index, reflected_index: Index + ) -> Optional[str]: + conn_unique = bool(reflected_index.unique) + meta_unique = bool(metadata_index.unique) + if conn_unique != meta_unique: + return f"unique={conn_unique} to unique={meta_unique}" + else: + return None + + def _create_metadata_constraint_sig( + self, constraint: _autogen._C, **opts: Any + ) -> _constraint_sig[_autogen._C]: + return _constraint_sig.from_constraint(True, self, constraint, **opts) + + def _create_reflected_constraint_sig( + self, constraint: _autogen._C, **opts: Any + ) -> _constraint_sig[_autogen._C]: + return _constraint_sig.from_constraint(False, self, constraint, **opts) + + def compare_indexes( + self, + metadata_index: Index, + reflected_index: Index, + ) -> ComparisonResult: + """Compare two indexes by comparing the signature generated by + ``create_index_sig``. + + This method returns a ``ComparisonResult``. + """ + msg: List[str] = [] + unique_msg = self._compare_index_unique( + metadata_index, reflected_index + ) + if unique_msg: + msg.append(unique_msg) + m_sig = self._create_metadata_constraint_sig(metadata_index) + r_sig = self._create_reflected_constraint_sig(reflected_index) + + assert _autogen.is_index_sig(m_sig) + assert _autogen.is_index_sig(r_sig) + + # The assumption is that the index have no expression + for sig in m_sig, r_sig: + if sig.has_expressions: + log.warning( + "Generating approximate signature for index %s. " + "The dialect " + "implementation should either skip expression indexes " + "or provide a custom implementation.", + sig.const, + ) + + if m_sig.column_names != r_sig.column_names: + msg.append( + f"expression {r_sig.column_names} to {m_sig.column_names}" + ) + + if msg: + return ComparisonResult.Different(msg) + else: + return ComparisonResult.Equal() + + def compare_unique_constraint( + self, + metadata_constraint: UniqueConstraint, + reflected_constraint: UniqueConstraint, + ) -> ComparisonResult: + """Compare two unique constraints by comparing the two signatures. + + The arguments are two tuples that contain the unique constraint and + the signatures generated by ``create_unique_constraint_sig``. + + This method returns a ``ComparisonResult``. + """ + metadata_tup = self._create_metadata_constraint_sig( + metadata_constraint + ) + reflected_tup = self._create_reflected_constraint_sig( + reflected_constraint + ) + + meta_sig = metadata_tup.unnamed + conn_sig = reflected_tup.unnamed + if conn_sig != meta_sig: + return ComparisonResult.Different( + f"expression {conn_sig} to {meta_sig}" + ) + else: + return ComparisonResult.Equal() + + def _skip_functional_indexes(self, metadata_indexes, conn_indexes): + conn_indexes_by_name = {c.name: c for c in conn_indexes} + + for idx in list(metadata_indexes): + if idx.name in conn_indexes_by_name: + continue + iex = sqla_compat.is_expression_index(idx) + if iex: + util.warn( + "autogenerate skipping metadata-specified " + "expression-based index " + f"{idx.name!r}; dialect {self.__dialect__!r} under " + f"SQLAlchemy {sqla_compat.sqlalchemy_version} can't " + "reflect these indexes so they can't be compared" + ) + metadata_indexes.discard(idx) + + def adjust_reflected_dialect_options( + self, reflected_object: Dict[str, Any], kind: str + ) -> Dict[str, Any]: + return reflected_object.get("dialect_options", {}) + + +class Params(NamedTuple): + token0: str + tokens: List[str] + args: List[str] + kwargs: Dict[str, str] + + +def _compare_identity_options( + metadata_io: Union[schema.Identity, schema.Sequence, None], + inspector_io: Union[schema.Identity, schema.Sequence, None], + default_io: Union[schema.Identity, schema.Sequence], + skip: Set[str], +): + # this can be used for identity or sequence compare. + # default_io is an instance of IdentityOption with all attributes to the + # default value. + meta_d = sqla_compat._get_identity_options_dict(metadata_io) + insp_d = sqla_compat._get_identity_options_dict(inspector_io) + + diff = set() + ignored_attr = set() + + def check_dicts( + meta_dict: Mapping[str, Any], + insp_dict: Mapping[str, Any], + default_dict: Mapping[str, Any], + attrs: Iterable[str], + ): + for attr in set(attrs).difference(skip): + meta_value = meta_dict.get(attr) + insp_value = insp_dict.get(attr) + if insp_value != meta_value: + default_value = default_dict.get(attr) + if meta_value == default_value: + ignored_attr.add(attr) + else: + diff.add(attr) + + check_dicts( + meta_d, + insp_d, + sqla_compat._get_identity_options_dict(default_io), + set(meta_d).union(insp_d), + ) + if sqla_compat.identity_has_dialect_kwargs: + # use only the dialect kwargs in inspector_io since metadata_io + # can have options for many backends + check_dicts( + getattr(metadata_io, "dialect_kwargs", {}), + getattr(inspector_io, "dialect_kwargs", {}), + default_io.dialect_kwargs, # type: ignore[union-attr] + getattr(inspector_io, "dialect_kwargs", {}), + ) + + return diff, ignored_attr diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/mssql.py b/venv/lib/python3.8/site-packages/alembic/ddl/mssql.py new file mode 100644 index 000000000..baa43d5e7 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/mssql.py @@ -0,0 +1,419 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import types as sqltypes +from sqlalchemy.schema import Column +from sqlalchemy.schema import CreateIndex +from sqlalchemy.sql.base import Executable +from sqlalchemy.sql.elements import ClauseElement + +from .base import AddColumn +from .base import alter_column +from .base import alter_table +from .base import ColumnDefault +from .base import ColumnName +from .base import ColumnNullable +from .base import ColumnType +from .base import format_column_name +from .base import format_server_default +from .base import format_table_name +from .base import format_type +from .base import RenameTable +from .impl import DefaultImpl +from .. import util +from ..util import sqla_compat +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.dialects.mssql.base import MSDDLCompiler + from sqlalchemy.dialects.mssql.base import MSSQLCompiler + from sqlalchemy.engine.cursor import CursorResult + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.selectable import TableClause + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefault + + +class MSSQLImpl(DefaultImpl): + __dialect__ = "mssql" + transactional_ddl = True + batch_separator = "GO" + + type_synonyms = DefaultImpl.type_synonyms + ({"VARCHAR", "NVARCHAR"},) + identity_attrs_ignore = DefaultImpl.identity_attrs_ignore + ( + "minvalue", + "maxvalue", + "nominvalue", + "nomaxvalue", + "cycle", + "cache", + ) + + def __init__(self, *arg, **kw) -> None: + super().__init__(*arg, **kw) + self.batch_separator = self.context_opts.get( + "mssql_batch_separator", self.batch_separator + ) + + def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]: + result = super()._exec(construct, *args, **kw) + if self.as_sql and self.batch_separator: + self.static_output(self.batch_separator) + return result + + def emit_begin(self) -> None: + self.static_output("BEGIN TRANSACTION" + self.command_terminator) + + def emit_commit(self) -> None: + super().emit_commit() + if self.as_sql and self.batch_separator: + self.static_output(self.batch_separator) + + def alter_column( # type:ignore[override] + self, + table_name: str, + column_name: str, + nullable: Optional[bool] = None, + server_default: Optional[ + Union[_ServerDefault, Literal[False]] + ] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Optional[_ServerDefault] = None, + existing_nullable: Optional[bool] = None, + **kw: Any, + ) -> None: + if nullable is not None: + if type_ is not None: + # the NULL/NOT NULL alter will handle + # the type alteration + existing_type = type_ + type_ = None + elif existing_type is None: + raise util.CommandError( + "MS-SQL ALTER COLUMN operations " + "with NULL or NOT NULL require the " + "existing_type or a new type_ be passed." + ) + elif existing_nullable is not None and type_ is not None: + nullable = existing_nullable + + # the NULL/NOT NULL alter will handle + # the type alteration + existing_type = type_ + type_ = None + + elif type_ is not None: + util.warn( + "MS-SQL ALTER COLUMN operations that specify type_= " + "should also specify a nullable= or " + "existing_nullable= argument to avoid implicit conversion " + "of NOT NULL columns to NULL." + ) + + used_default = False + if sqla_compat._server_default_is_identity( + server_default, existing_server_default + ) or sqla_compat._server_default_is_computed( + server_default, existing_server_default + ): + used_default = True + kw["server_default"] = server_default + kw["existing_server_default"] = existing_server_default + + super().alter_column( + table_name, + column_name, + nullable=nullable, + type_=type_, + schema=schema, + existing_type=existing_type, + existing_nullable=existing_nullable, + **kw, + ) + + if server_default is not False and used_default is False: + if existing_server_default is not False or server_default is None: + self._exec( + _ExecDropConstraint( + table_name, + column_name, + "sys.default_constraints", + schema, + ) + ) + if server_default is not None: + super().alter_column( + table_name, + column_name, + schema=schema, + server_default=server_default, + ) + + if name is not None: + super().alter_column( + table_name, column_name, schema=schema, name=name + ) + + def create_index(self, index: Index, **kw: Any) -> None: + # this likely defaults to None if not present, so get() + # should normally not return the default value. being + # defensive in any case + mssql_include = index.kwargs.get("mssql_include", None) or () + assert index.table is not None + for col in mssql_include: + if col not in index.table.c: + index.table.append_column(Column(col, sqltypes.NullType)) + self._exec(CreateIndex(index, **kw)) + + def bulk_insert( # type:ignore[override] + self, table: Union[TableClause, Table], rows: List[dict], **kw: Any + ) -> None: + if self.as_sql: + self._exec( + "SET IDENTITY_INSERT %s ON" + % self.dialect.identifier_preparer.format_table(table) + ) + super().bulk_insert(table, rows, **kw) + self._exec( + "SET IDENTITY_INSERT %s OFF" + % self.dialect.identifier_preparer.format_table(table) + ) + else: + super().bulk_insert(table, rows, **kw) + + def drop_column( + self, + table_name: str, + column: Column[Any], + schema: Optional[str] = None, + **kw, + ) -> None: + drop_default = kw.pop("mssql_drop_default", False) + if drop_default: + self._exec( + _ExecDropConstraint( + table_name, column, "sys.default_constraints", schema + ) + ) + drop_check = kw.pop("mssql_drop_check", False) + if drop_check: + self._exec( + _ExecDropConstraint( + table_name, column, "sys.check_constraints", schema + ) + ) + drop_fks = kw.pop("mssql_drop_foreign_key", False) + if drop_fks: + self._exec(_ExecDropFKConstraint(table_name, column, schema)) + super().drop_column(table_name, column, schema=schema, **kw) + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + if rendered_metadata_default is not None: + rendered_metadata_default = re.sub( + r"[\(\) \"\']", "", rendered_metadata_default + ) + + if rendered_inspector_default is not None: + # SQL Server collapses whitespace and adds arbitrary parenthesis + # within expressions. our only option is collapse all of it + + rendered_inspector_default = re.sub( + r"[\(\) \"\']", "", rendered_inspector_default + ) + + return rendered_inspector_default != rendered_metadata_default + + def _compare_identity_default(self, metadata_identity, inspector_identity): + diff, ignored, is_alter = super()._compare_identity_default( + metadata_identity, inspector_identity + ) + + if ( + metadata_identity is None + and inspector_identity is not None + and not diff + and inspector_identity.column is not None + and inspector_identity.column.primary_key + ): + # mssql reflect primary keys with autoincrement as identity + # columns. if no different attributes are present ignore them + is_alter = False + + return diff, ignored, is_alter + + def adjust_reflected_dialect_options( + self, reflected_object: Dict[str, Any], kind: str + ) -> Dict[str, Any]: + options: Dict[str, Any] + options = reflected_object.get("dialect_options", {}).copy() + if not options.get("mssql_include"): + options.pop("mssql_include", None) + if not options.get("mssql_clustered"): + options.pop("mssql_clustered", None) + return options + + +class _ExecDropConstraint(Executable, ClauseElement): + inherit_cache = False + + def __init__( + self, + tname: str, + colname: Union[Column[Any], str], + type_: str, + schema: Optional[str], + ) -> None: + self.tname = tname + self.colname = colname + self.type_ = type_ + self.schema = schema + + +class _ExecDropFKConstraint(Executable, ClauseElement): + inherit_cache = False + + def __init__( + self, tname: str, colname: Column[Any], schema: Optional[str] + ) -> None: + self.tname = tname + self.colname = colname + self.schema = schema + + +@compiles(_ExecDropConstraint, "mssql") +def _exec_drop_col_constraint( + element: _ExecDropConstraint, compiler: MSSQLCompiler, **kw +) -> str: + schema, tname, colname, type_ = ( + element.schema, + element.tname, + element.colname, + element.type_, + ) + # from http://www.mssqltips.com/sqlservertip/1425/\ + # working-with-default-constraints-in-sql-server/ + return """declare @const_name varchar(256) +select @const_name = QUOTENAME([name]) from %(type)s +where parent_object_id = object_id('%(schema_dot)s%(tname)s') +and col_name(parent_object_id, parent_column_id) = '%(colname)s' +exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % { + "type": type_, + "tname": tname, + "colname": colname, + "tname_quoted": format_table_name(compiler, tname, schema), + "schema_dot": schema + "." if schema else "", + } + + +@compiles(_ExecDropFKConstraint, "mssql") +def _exec_drop_col_fk_constraint( + element: _ExecDropFKConstraint, compiler: MSSQLCompiler, **kw +) -> str: + schema, tname, colname = element.schema, element.tname, element.colname + + return """declare @const_name varchar(256) +select @const_name = QUOTENAME([name]) from +sys.foreign_keys fk join sys.foreign_key_columns fkc +on fk.object_id=fkc.constraint_object_id +where fkc.parent_object_id = object_id('%(schema_dot)s%(tname)s') +and col_name(fkc.parent_object_id, fkc.parent_column_id) = '%(colname)s' +exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % { + "tname": tname, + "colname": colname, + "tname_quoted": format_table_name(compiler, tname, schema), + "schema_dot": schema + "." if schema else "", + } + + +@compiles(AddColumn, "mssql") +def visit_add_column(element: AddColumn, compiler: MSDDLCompiler, **kw) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + mssql_add_column(compiler, element.column, **kw), + ) + + +def mssql_add_column( + compiler: MSDDLCompiler, column: Column[Any], **kw +) -> str: + return "ADD %s" % compiler.get_column_specification(column, **kw) + + +@compiles(ColumnNullable, "mssql") +def visit_column_nullable( + element: ColumnNullable, compiler: MSDDLCompiler, **kw +) -> str: + return "%s %s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + format_type(compiler, element.existing_type), # type: ignore[arg-type] + "NULL" if element.nullable else "NOT NULL", + ) + + +@compiles(ColumnDefault, "mssql") +def visit_column_default( + element: ColumnDefault, compiler: MSDDLCompiler, **kw +) -> str: + # TODO: there can also be a named constraint + # with ADD CONSTRAINT here + return "%s ADD DEFAULT %s FOR %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_server_default(compiler, element.default), + format_column_name(compiler, element.column_name), + ) + + +@compiles(ColumnName, "mssql") +def visit_rename_column( + element: ColumnName, compiler: MSDDLCompiler, **kw +) -> str: + return "EXEC sp_rename '%s.%s', %s, 'COLUMN'" % ( + format_table_name(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +@compiles(ColumnType, "mssql") +def visit_column_type( + element: ColumnType, compiler: MSDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + format_type(compiler, element.type_), + ) + + +@compiles(RenameTable, "mssql") +def visit_rename_table( + element: RenameTable, compiler: MSDDLCompiler, **kw +) -> str: + return "EXEC sp_rename '%s', %s" % ( + format_table_name(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/mysql.py b/venv/lib/python3.8/site-packages/alembic/ddl/mysql.py new file mode 100644 index 000000000..3482f672d --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/mysql.py @@ -0,0 +1,492 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import schema +from sqlalchemy import types as sqltypes + +from .base import alter_table +from .base import AlterColumn +from .base import ColumnDefault +from .base import ColumnName +from .base import ColumnNullable +from .base import ColumnType +from .base import format_column_name +from .base import format_server_default +from .impl import DefaultImpl +from .. import util +from ..util import sqla_compat +from ..util.sqla_compat import _is_mariadb +from ..util.sqla_compat import _is_type_bound +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.dialects.mysql.base import MySQLDDLCompiler + from sqlalchemy.sql.ddl import DropConstraint + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefault + + +class MySQLImpl(DefaultImpl): + __dialect__ = "mysql" + + transactional_ddl = False + type_synonyms = DefaultImpl.type_synonyms + ( + {"BOOL", "TINYINT"}, + {"JSON", "LONGTEXT"}, + ) + type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"] + + def alter_column( # type:ignore[override] + self, + table_name: str, + column_name: str, + nullable: Optional[bool] = None, + server_default: Union[_ServerDefault, Literal[False]] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Optional[_ServerDefault] = None, + existing_nullable: Optional[bool] = None, + autoincrement: Optional[bool] = None, + existing_autoincrement: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + existing_comment: Optional[str] = None, + **kw: Any, + ) -> None: + if sqla_compat._server_default_is_identity( + server_default, existing_server_default + ) or sqla_compat._server_default_is_computed( + server_default, existing_server_default + ): + # modifying computed or identity columns is not supported + # the default will raise + super().alter_column( + table_name, + column_name, + nullable=nullable, + type_=type_, + schema=schema, + existing_type=existing_type, + existing_nullable=existing_nullable, + server_default=server_default, + existing_server_default=existing_server_default, + **kw, + ) + if name is not None or self._is_mysql_allowed_functional_default( + type_ if type_ is not None else existing_type, server_default + ): + self._exec( + MySQLChangeColumn( + table_name, + column_name, + schema=schema, + newname=name if name is not None else column_name, + nullable=( + nullable + if nullable is not None + else ( + existing_nullable + if existing_nullable is not None + else True + ) + ), + type_=type_ if type_ is not None else existing_type, + default=( + server_default + if server_default is not False + else existing_server_default + ), + autoincrement=( + autoincrement + if autoincrement is not None + else existing_autoincrement + ), + comment=( + comment if comment is not False else existing_comment + ), + ) + ) + elif ( + nullable is not None + or type_ is not None + or autoincrement is not None + or comment is not False + ): + self._exec( + MySQLModifyColumn( + table_name, + column_name, + schema=schema, + newname=name if name is not None else column_name, + nullable=( + nullable + if nullable is not None + else ( + existing_nullable + if existing_nullable is not None + else True + ) + ), + type_=type_ if type_ is not None else existing_type, + default=( + server_default + if server_default is not False + else existing_server_default + ), + autoincrement=( + autoincrement + if autoincrement is not None + else existing_autoincrement + ), + comment=( + comment if comment is not False else existing_comment + ), + ) + ) + elif server_default is not False: + self._exec( + MySQLAlterDefault( + table_name, column_name, server_default, schema=schema + ) + ) + + def drop_constraint( + self, + const: Constraint, + ) -> None: + if isinstance(const, schema.CheckConstraint) and _is_type_bound(const): + return + + super().drop_constraint(const) + + def _is_mysql_allowed_functional_default( + self, + type_: Optional[TypeEngine], + server_default: Union[_ServerDefault, Literal[False]], + ) -> bool: + return ( + type_ is not None + and type_._type_affinity is sqltypes.DateTime + and server_default is not None + ) + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + # partially a workaround for SQLAlchemy issue #3023; if the + # column were created without "NOT NULL", MySQL may have added + # an implicit default of '0' which we need to skip + # TODO: this is not really covered anymore ? + if ( + metadata_column.type._type_affinity is sqltypes.Integer + and inspector_column.primary_key + and not inspector_column.autoincrement + and not rendered_metadata_default + and rendered_inspector_default == "'0'" + ): + return False + elif ( + rendered_inspector_default + and inspector_column.type._type_affinity is sqltypes.Integer + ): + rendered_inspector_default = ( + re.sub(r"^'|'$", "", rendered_inspector_default) + if rendered_inspector_default is not None + else None + ) + return rendered_inspector_default != rendered_metadata_default + elif ( + rendered_metadata_default + and metadata_column.type._type_affinity is sqltypes.String + ): + metadata_default = re.sub(r"^'|'$", "", rendered_metadata_default) + return rendered_inspector_default != f"'{metadata_default}'" + elif rendered_inspector_default and rendered_metadata_default: + # adjust for "function()" vs. "FUNCTION" as can occur particularly + # for the CURRENT_TIMESTAMP function on newer MariaDB versions + + # SQLAlchemy MySQL dialect bundles ON UPDATE into the server + # default; adjust for this possibly being present. + onupdate_ins = re.match( + r"(.*) (on update.*?)(?:\(\))?$", + rendered_inspector_default.lower(), + ) + onupdate_met = re.match( + r"(.*) (on update.*?)(?:\(\))?$", + rendered_metadata_default.lower(), + ) + + if onupdate_ins: + if not onupdate_met: + return True + elif onupdate_ins.group(2) != onupdate_met.group(2): + return True + + rendered_inspector_default = onupdate_ins.group(1) + rendered_metadata_default = onupdate_met.group(1) + + return re.sub( + r"(.*?)(?:\(\))?$", r"\1", rendered_inspector_default.lower() + ) != re.sub( + r"(.*?)(?:\(\))?$", r"\1", rendered_metadata_default.lower() + ) + else: + return rendered_inspector_default != rendered_metadata_default + + def correct_for_autogen_constraints( + self, + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + ): + # TODO: if SQLA 1.0, make use of "duplicates_index" + # metadata + removed = set() + for idx in list(conn_indexes): + if idx.unique: + continue + # MySQL puts implicit indexes on FK columns, even if + # composite and even if MyISAM, so can't check this too easily. + # the name of the index may be the column name or it may + # be the name of the FK constraint. + for col in idx.columns: + if idx.name == col.name: + conn_indexes.remove(idx) + removed.add(idx.name) + break + for fk in col.foreign_keys: + if fk.name == idx.name: + conn_indexes.remove(idx) + removed.add(idx.name) + break + if idx.name in removed: + break + + # then remove indexes from the "metadata_indexes" + # that we've removed from reflected, otherwise they come out + # as adds (see #202) + for idx in list(metadata_indexes): + if idx.name in removed: + metadata_indexes.remove(idx) + + def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks): + conn_fk_by_sig = { + self._create_reflected_constraint_sig(fk).unnamed_no_options: fk + for fk in conn_fks + } + metadata_fk_by_sig = { + self._create_metadata_constraint_sig(fk).unnamed_no_options: fk + for fk in metadata_fks + } + + for sig in set(conn_fk_by_sig).intersection(metadata_fk_by_sig): + mdfk = metadata_fk_by_sig[sig] + cnfk = conn_fk_by_sig[sig] + # MySQL considers RESTRICT to be the default and doesn't + # report on it. if the model has explicit RESTRICT and + # the conn FK has None, set it to RESTRICT + if ( + mdfk.ondelete is not None + and mdfk.ondelete.lower() == "restrict" + and cnfk.ondelete is None + ): + cnfk.ondelete = "RESTRICT" + if ( + mdfk.onupdate is not None + and mdfk.onupdate.lower() == "restrict" + and cnfk.onupdate is None + ): + cnfk.onupdate = "RESTRICT" + + +class MariaDBImpl(MySQLImpl): + __dialect__ = "mariadb" + + +class MySQLAlterDefault(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + default: _ServerDefault, + schema: Optional[str] = None, + ) -> None: + super(AlterColumn, self).__init__(name, schema=schema) + self.column_name = column_name + self.default = default + + +class MySQLChangeColumn(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + schema: Optional[str] = None, + newname: Optional[str] = None, + type_: Optional[TypeEngine] = None, + nullable: Optional[bool] = None, + default: Optional[Union[_ServerDefault, Literal[False]]] = False, + autoincrement: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + ) -> None: + super(AlterColumn, self).__init__(name, schema=schema) + self.column_name = column_name + self.nullable = nullable + self.newname = newname + self.default = default + self.autoincrement = autoincrement + self.comment = comment + if type_ is None: + raise util.CommandError( + "All MySQL CHANGE/MODIFY COLUMN operations " + "require the existing type." + ) + + self.type_ = sqltypes.to_instance(type_) + + +class MySQLModifyColumn(MySQLChangeColumn): + pass + + +@compiles(ColumnNullable, "mysql", "mariadb") +@compiles(ColumnName, "mysql", "mariadb") +@compiles(ColumnDefault, "mysql", "mariadb") +@compiles(ColumnType, "mysql", "mariadb") +def _mysql_doesnt_support_individual(element, compiler, **kw): + raise NotImplementedError( + "Individual alter column constructs not supported by MySQL" + ) + + +@compiles(MySQLAlterDefault, "mysql", "mariadb") +def _mysql_alter_default( + element: MySQLAlterDefault, compiler: MySQLDDLCompiler, **kw +) -> str: + return "%s ALTER COLUMN %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + ( + "SET DEFAULT %s" % format_server_default(compiler, element.default) + if element.default is not None + else "DROP DEFAULT" + ), + ) + + +@compiles(MySQLModifyColumn, "mysql", "mariadb") +def _mysql_modify_column( + element: MySQLModifyColumn, compiler: MySQLDDLCompiler, **kw +) -> str: + return "%s MODIFY %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + _mysql_colspec( + compiler, + nullable=element.nullable, + server_default=element.default, + type_=element.type_, + autoincrement=element.autoincrement, + comment=element.comment, + ), + ) + + +@compiles(MySQLChangeColumn, "mysql", "mariadb") +def _mysql_change_column( + element: MySQLChangeColumn, compiler: MySQLDDLCompiler, **kw +) -> str: + return "%s CHANGE %s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + _mysql_colspec( + compiler, + nullable=element.nullable, + server_default=element.default, + type_=element.type_, + autoincrement=element.autoincrement, + comment=element.comment, + ), + ) + + +def _mysql_colspec( + compiler: MySQLDDLCompiler, + nullable: Optional[bool], + server_default: Optional[Union[_ServerDefault, Literal[False]]], + type_: TypeEngine, + autoincrement: Optional[bool], + comment: Optional[Union[str, Literal[False]]], +) -> str: + spec = "%s %s" % ( + compiler.dialect.type_compiler.process(type_), + "NULL" if nullable else "NOT NULL", + ) + if autoincrement: + spec += " AUTO_INCREMENT" + if server_default is not False and server_default is not None: + spec += " DEFAULT %s" % format_server_default(compiler, server_default) + if comment: + spec += " COMMENT %s" % compiler.sql_compiler.render_literal_value( + comment, sqltypes.String() + ) + + return spec + + +@compiles(schema.DropConstraint, "mysql", "mariadb") +def _mysql_drop_constraint( + element: DropConstraint, compiler: MySQLDDLCompiler, **kw +) -> str: + """Redefine SQLAlchemy's drop constraint to + raise errors for invalid constraint type.""" + + constraint = element.element + if isinstance( + constraint, + ( + schema.ForeignKeyConstraint, + schema.PrimaryKeyConstraint, + schema.UniqueConstraint, + ), + ): + assert not kw + return compiler.visit_drop_constraint(element) + elif isinstance(constraint, schema.CheckConstraint): + # note that SQLAlchemy as of 1.2 does not yet support + # DROP CONSTRAINT for MySQL/MariaDB, so we implement fully + # here. + if _is_mariadb(compiler.dialect): + return "ALTER TABLE %s DROP CONSTRAINT %s" % ( + compiler.preparer.format_table(constraint.table), + compiler.preparer.format_constraint(constraint), + ) + else: + return "ALTER TABLE %s DROP CHECK %s" % ( + compiler.preparer.format_table(constraint.table), + compiler.preparer.format_constraint(constraint), + ) + else: + raise NotImplementedError( + "No generic 'DROP CONSTRAINT' in MySQL - " + "please specify constraint type" + ) diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/oracle.py b/venv/lib/python3.8/site-packages/alembic/ddl/oracle.py new file mode 100644 index 000000000..eac99124f --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/oracle.py @@ -0,0 +1,202 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING + +from sqlalchemy.sql import sqltypes + +from .base import AddColumn +from .base import alter_table +from .base import ColumnComment +from .base import ColumnDefault +from .base import ColumnName +from .base import ColumnNullable +from .base import ColumnType +from .base import format_column_name +from .base import format_server_default +from .base import format_table_name +from .base import format_type +from .base import IdentityColumnDefault +from .base import RenameTable +from .impl import DefaultImpl +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from sqlalchemy.dialects.oracle.base import OracleDDLCompiler + from sqlalchemy.engine.cursor import CursorResult + from sqlalchemy.sql.schema import Column + + +class OracleImpl(DefaultImpl): + __dialect__ = "oracle" + transactional_ddl = False + batch_separator = "/" + command_terminator = "" + type_synonyms = DefaultImpl.type_synonyms + ( + {"VARCHAR", "VARCHAR2"}, + {"BIGINT", "INTEGER", "SMALLINT", "DECIMAL", "NUMERIC", "NUMBER"}, + {"DOUBLE", "FLOAT", "DOUBLE_PRECISION"}, + ) + identity_attrs_ignore = () + + def __init__(self, *arg, **kw) -> None: + super().__init__(*arg, **kw) + self.batch_separator = self.context_opts.get( + "oracle_batch_separator", self.batch_separator + ) + + def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]: + result = super()._exec(construct, *args, **kw) + if self.as_sql and self.batch_separator: + self.static_output(self.batch_separator) + return result + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + if rendered_metadata_default is not None: + rendered_metadata_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_metadata_default + ) + + rendered_metadata_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default + ) + + if rendered_inspector_default is not None: + rendered_inspector_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_inspector_default + ) + + rendered_inspector_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default + ) + + rendered_inspector_default = rendered_inspector_default.strip() + return rendered_inspector_default != rendered_metadata_default + + def emit_begin(self) -> None: + self._exec("SET TRANSACTION READ WRITE") + + def emit_commit(self) -> None: + self._exec("COMMIT") + + +@compiles(AddColumn, "oracle") +def visit_add_column( + element: AddColumn, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + add_column(compiler, element.column, **kw), + ) + + +@compiles(ColumnNullable, "oracle") +def visit_column_nullable( + element: ColumnNullable, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "NULL" if element.nullable else "NOT NULL", + ) + + +@compiles(ColumnType, "oracle") +def visit_column_type( + element: ColumnType, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "%s" % format_type(compiler, element.type_), + ) + + +@compiles(ColumnName, "oracle") +def visit_column_name( + element: ColumnName, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s RENAME COLUMN %s TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +@compiles(ColumnDefault, "oracle") +def visit_column_default( + element: ColumnDefault, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ( + "DEFAULT %s" % format_server_default(compiler, element.default) + if element.default is not None + else "DEFAULT NULL" + ), + ) + + +@compiles(ColumnComment, "oracle") +def visit_column_comment( + element: ColumnComment, compiler: OracleDDLCompiler, **kw +) -> str: + ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}" + + comment = compiler.sql_compiler.render_literal_value( + (element.comment if element.comment is not None else ""), + sqltypes.String(), + ) + + return ddl.format( + table_name=element.table_name, + column_name=element.column_name, + comment=comment, + ) + + +@compiles(RenameTable, "oracle") +def visit_rename_table( + element: RenameTable, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) + + +def alter_column(compiler: OracleDDLCompiler, name: str) -> str: + return "MODIFY %s" % format_column_name(compiler, name) + + +def add_column(compiler: OracleDDLCompiler, column: Column[Any], **kw) -> str: + return "ADD %s" % compiler.get_column_specification(column, **kw) + + +@compiles(IdentityColumnDefault, "oracle") +def visit_identity_column( + element: IdentityColumnDefault, compiler: OracleDDLCompiler, **kw +): + text = "%s %s " % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ) + if element.default is None: + # drop identity + text += "DROP IDENTITY" + return text + else: + text += compiler.visit_identity_column(element.default) + return text diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/postgresql.py b/venv/lib/python3.8/site-packages/alembic/ddl/postgresql.py new file mode 100644 index 000000000..60aa15366 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/postgresql.py @@ -0,0 +1,850 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import logging +import re +from typing import Any +from typing import cast +from typing import Dict +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import literal_column +from sqlalchemy import Numeric +from sqlalchemy import text +from sqlalchemy import types as sqltypes +from sqlalchemy.dialects.postgresql import BIGINT +from sqlalchemy.dialects.postgresql import ExcludeConstraint +from sqlalchemy.dialects.postgresql import INTEGER +from sqlalchemy.schema import CreateIndex +from sqlalchemy.sql.elements import ColumnClause +from sqlalchemy.sql.elements import TextClause +from sqlalchemy.sql.functions import FunctionElement +from sqlalchemy.types import NULLTYPE + +from .base import alter_column +from .base import alter_table +from .base import AlterColumn +from .base import ColumnComment +from .base import format_column_name +from .base import format_table_name +from .base import format_type +from .base import IdentityColumnDefault +from .base import RenameTable +from .impl import ComparisonResult +from .impl import DefaultImpl +from .. import util +from ..autogenerate import render +from ..operations import ops +from ..operations import schemaobj +from ..operations.base import BatchOperations +from ..operations.base import Operations +from ..util import sqla_compat +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy import Index + from sqlalchemy import UniqueConstraint + from sqlalchemy.dialects.postgresql.array import ARRAY + from sqlalchemy.dialects.postgresql.base import PGDDLCompiler + from sqlalchemy.dialects.postgresql.hstore import HSTORE + from sqlalchemy.dialects.postgresql.json import JSON + from sqlalchemy.dialects.postgresql.json import JSONB + from sqlalchemy.sql.elements import ClauseElement + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefault + from ..autogenerate.api import AutogenContext + from ..autogenerate.render import _f_name + from ..runtime.migration import MigrationContext + + +log = logging.getLogger(__name__) + + +class PostgresqlImpl(DefaultImpl): + __dialect__ = "postgresql" + transactional_ddl = True + type_synonyms = DefaultImpl.type_synonyms + ( + {"FLOAT", "DOUBLE PRECISION"}, + ) + + def create_index(self, index: Index, **kw: Any) -> None: + # this likely defaults to None if not present, so get() + # should normally not return the default value. being + # defensive in any case + postgresql_include = index.kwargs.get("postgresql_include", None) or () + for col in postgresql_include: + if col not in index.table.c: # type: ignore[union-attr] + index.table.append_column( # type: ignore[union-attr] + Column(col, sqltypes.NullType) + ) + self._exec(CreateIndex(index, **kw)) + + def prep_table_for_batch(self, batch_impl, table): + for constraint in table.constraints: + if ( + constraint.name is not None + and constraint.name in batch_impl.named_constraints + ): + self.drop_constraint(constraint) + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + # don't do defaults for SERIAL columns + if ( + metadata_column.primary_key + and metadata_column is metadata_column.table._autoincrement_column + ): + return False + + conn_col_default = rendered_inspector_default + + defaults_equal = conn_col_default == rendered_metadata_default + if defaults_equal: + return False + + if None in ( + conn_col_default, + rendered_metadata_default, + metadata_column.server_default, + ): + return not defaults_equal + + metadata_default = metadata_column.server_default.arg + + if isinstance(metadata_default, str): + if not isinstance(inspector_column.type, (Numeric, Float)): + metadata_default = re.sub(r"^'|'$", "", metadata_default) + metadata_default = f"'{metadata_default}'" + + metadata_default = literal_column(metadata_default) + + # run a real compare against the server + conn = self.connection + assert conn is not None + return not conn.scalar( + sqla_compat._select( + literal_column(conn_col_default) == metadata_default + ) + ) + + def alter_column( # type:ignore[override] + self, + table_name: str, + column_name: str, + nullable: Optional[bool] = None, + server_default: Union[_ServerDefault, Literal[False]] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + autoincrement: Optional[bool] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Optional[_ServerDefault] = None, + existing_nullable: Optional[bool] = None, + existing_autoincrement: Optional[bool] = None, + **kw: Any, + ) -> None: + using = kw.pop("postgresql_using", None) + + if using is not None and type_ is None: + raise util.CommandError( + "postgresql_using must be used with the type_ parameter" + ) + + if type_ is not None: + self._exec( + PostgresqlColumnType( + table_name, + column_name, + type_, + schema=schema, + using=using, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + ) + ) + + super().alter_column( + table_name, + column_name, + nullable=nullable, + server_default=server_default, + name=name, + schema=schema, + autoincrement=autoincrement, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_autoincrement=existing_autoincrement, + **kw, + ) + + def autogen_column_reflect(self, inspector, table, column_info): + if column_info.get("default") and isinstance( + column_info["type"], (INTEGER, BIGINT) + ): + seq_match = re.match( + r"nextval\('(.+?)'::regclass\)", column_info["default"] + ) + if seq_match: + info = sqla_compat._exec_on_inspector( + inspector, + text( + "select c.relname, a.attname " + "from pg_class as c join " + "pg_depend d on d.objid=c.oid and " + "d.classid='pg_class'::regclass and " + "d.refclassid='pg_class'::regclass " + "join pg_class t on t.oid=d.refobjid " + "join pg_attribute a on a.attrelid=t.oid and " + "a.attnum=d.refobjsubid " + "where c.relkind='S' and " + "c.oid=cast(:seqname as regclass)" + ), + seqname=seq_match.group(1), + ).first() + if info: + seqname, colname = info + if colname == column_info["name"]: + log.info( + "Detected sequence named '%s' as " + "owned by integer column '%s(%s)', " + "assuming SERIAL and omitting", + seqname, + table.name, + colname, + ) + # sequence, and the owner is this column, + # its a SERIAL - whack it! + del column_info["default"] + + def correct_for_autogen_constraints( + self, + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + ): + doubled_constraints = { + index + for index in conn_indexes + if index.info.get("duplicates_constraint") + } + + for ix in doubled_constraints: + conn_indexes.remove(ix) + + if not sqla_compat.sqla_2: + self._skip_functional_indexes(metadata_indexes, conn_indexes) + + # pg behavior regarding modifiers + # | # | compiled sql | returned sql | regexp. group is removed | + # | - | ---------------- | -----------------| ------------------------ | + # | 1 | nulls first | nulls first | - | + # | 2 | nulls last | | (? str: + expr = expr.lower().replace('"', "").replace("'", "") + if index.table is not None: + # should not be needed, since include_table=False is in compile + expr = expr.replace(f"{index.table.name.lower()}.", "") + + if "::" in expr: + # strip :: cast. types can have spaces in them + expr = re.sub(r"(::[\w ]+\w)", "", expr) + + while expr and expr[0] == "(" and expr[-1] == ")": + expr = expr[1:-1] + + # NOTE: when parsing the connection expression this cleanup could + # be skipped + for rs in self._default_modifiers_re: + if match := rs.search(expr): + start, end = match.span(1) + expr = expr[:start] + expr[end:] + break + + while expr and expr[0] == "(" and expr[-1] == ")": + expr = expr[1:-1] + + # strip casts + cast_re = re.compile(r"cast\s*\(") + if cast_re.match(expr): + expr = cast_re.sub("", expr) + # remove the as type + expr = re.sub(r"as\s+[^)]+\)", "", expr) + # remove spaces + expr = expr.replace(" ", "") + return expr + + def _dialect_options( + self, item: Union[Index, UniqueConstraint] + ) -> Tuple[Any, ...]: + # only the positive case is returned by sqlalchemy reflection so + # None and False are threated the same + if item.dialect_kwargs.get("postgresql_nulls_not_distinct"): + return ("nulls_not_distinct",) + return () + + def compare_indexes( + self, + metadata_index: Index, + reflected_index: Index, + ) -> ComparisonResult: + msg = [] + unique_msg = self._compare_index_unique( + metadata_index, reflected_index + ) + if unique_msg: + msg.append(unique_msg) + m_exprs = metadata_index.expressions + r_exprs = reflected_index.expressions + if len(m_exprs) != len(r_exprs): + msg.append(f"expression number {len(r_exprs)} to {len(m_exprs)}") + if msg: + # no point going further, return early + return ComparisonResult.Different(msg) + skip = [] + for pos, (m_e, r_e) in enumerate(zip(m_exprs, r_exprs), 1): + m_compile = self._compile_element(m_e) + m_text = self._cleanup_index_expr(metadata_index, m_compile) + # print(f"META ORIG: {m_compile!r} CLEANUP: {m_text!r}") + r_compile = self._compile_element(r_e) + r_text = self._cleanup_index_expr(metadata_index, r_compile) + # print(f"CONN ORIG: {r_compile!r} CLEANUP: {r_text!r}") + if m_text == r_text: + continue # expressions these are equal + elif m_compile.strip().endswith("_ops") and ( + " " in m_compile or ")" in m_compile # is an expression + ): + skip.append( + f"expression #{pos} {m_compile!r} detected " + "as including operator clause." + ) + util.warn( + f"Expression #{pos} {m_compile!r} in index " + f"{reflected_index.name!r} detected to include " + "an operator clause. Expression compare cannot proceed. " + "Please move the operator clause to the " + "``postgresql_ops`` dict to enable proper compare " + "of the index expressions: " + "https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#operator-classes", # noqa: E501 + ) + else: + msg.append(f"expression #{pos} {r_compile!r} to {m_compile!r}") + + m_options = self._dialect_options(metadata_index) + r_options = self._dialect_options(reflected_index) + if m_options != r_options: + msg.extend(f"options {r_options} to {m_options}") + + if msg: + return ComparisonResult.Different(msg) + elif skip: + # if there are other changes detected don't skip the index + return ComparisonResult.Skip(skip) + else: + return ComparisonResult.Equal() + + def compare_unique_constraint( + self, + metadata_constraint: UniqueConstraint, + reflected_constraint: UniqueConstraint, + ) -> ComparisonResult: + metadata_tup = self._create_metadata_constraint_sig( + metadata_constraint + ) + reflected_tup = self._create_reflected_constraint_sig( + reflected_constraint + ) + + meta_sig = metadata_tup.unnamed + conn_sig = reflected_tup.unnamed + if conn_sig != meta_sig: + return ComparisonResult.Different( + f"expression {conn_sig} to {meta_sig}" + ) + + metadata_do = self._dialect_options(metadata_tup.const) + conn_do = self._dialect_options(reflected_tup.const) + if metadata_do != conn_do: + return ComparisonResult.Different( + f"expression {conn_do} to {metadata_do}" + ) + + return ComparisonResult.Equal() + + def adjust_reflected_dialect_options( + self, reflected_options: Dict[str, Any], kind: str + ) -> Dict[str, Any]: + options: Dict[str, Any] + options = reflected_options.get("dialect_options", {}).copy() + if not options.get("postgresql_include"): + options.pop("postgresql_include", None) + return options + + def _compile_element(self, element: Union[ClauseElement, str]) -> str: + if isinstance(element, str): + return element + return element.compile( + dialect=self.dialect, + compile_kwargs={"literal_binds": True, "include_table": False}, + ).string + + def render_ddl_sql_expr( + self, + expr: ClauseElement, + is_server_default: bool = False, + is_index: bool = False, + **kw: Any, + ) -> str: + """Render a SQL expression that is typically a server default, + index expression, etc. + + """ + + # apply self_group to index expressions; + # see https://github.com/sqlalchemy/sqlalchemy/blob/ + # 82fa95cfce070fab401d020c6e6e4a6a96cc2578/ + # lib/sqlalchemy/dialects/postgresql/base.py#L2261 + if is_index and not isinstance(expr, ColumnClause): + expr = expr.self_group() + + return super().render_ddl_sql_expr( + expr, is_server_default=is_server_default, is_index=is_index, **kw + ) + + def render_type( + self, type_: TypeEngine, autogen_context: AutogenContext + ) -> Union[str, Literal[False]]: + mod = type(type_).__module__ + if not mod.startswith("sqlalchemy.dialects.postgresql"): + return False + + if hasattr(self, "_render_%s_type" % type_.__visit_name__): + meth = getattr(self, "_render_%s_type" % type_.__visit_name__) + return meth(type_, autogen_context) + + return False + + def _render_HSTORE_type( + self, type_: HSTORE, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "text_type", r"(.+?\(.*text_type=)" + ), + ) + + def _render_ARRAY_type( + self, type_: ARRAY, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "item_type", r"(.+?\()" + ), + ) + + def _render_JSON_type( + self, type_: JSON, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)" + ), + ) + + def _render_JSONB_type( + self, type_: JSONB, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)" + ), + ) + + +class PostgresqlColumnType(AlterColumn): + def __init__( + self, name: str, column_name: str, type_: TypeEngine, **kw + ) -> None: + using = kw.pop("using", None) + super().__init__(name, column_name, **kw) + self.type_ = sqltypes.to_instance(type_) + self.using = using + + +@compiles(RenameTable, "postgresql") +def visit_rename_table( + element: RenameTable, compiler: PGDDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) + + +@compiles(PostgresqlColumnType, "postgresql") +def visit_column_type( + element: PostgresqlColumnType, compiler: PGDDLCompiler, **kw +) -> str: + return "%s %s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "TYPE %s" % format_type(compiler, element.type_), + "USING %s" % element.using if element.using else "", + ) + + +@compiles(ColumnComment, "postgresql") +def visit_column_comment( + element: ColumnComment, compiler: PGDDLCompiler, **kw +) -> str: + ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}" + comment = ( + compiler.sql_compiler.render_literal_value( + element.comment, sqltypes.String() + ) + if element.comment is not None + else "NULL" + ) + + return ddl.format( + table_name=format_table_name( + compiler, element.table_name, element.schema + ), + column_name=format_column_name(compiler, element.column_name), + comment=comment, + ) + + +@compiles(IdentityColumnDefault, "postgresql") +def visit_identity_column( + element: IdentityColumnDefault, compiler: PGDDLCompiler, **kw +): + text = "%s %s " % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ) + if element.default is None: + # drop identity + text += "DROP IDENTITY" + return text + elif element.existing_server_default is None: + # add identity options + text += "ADD " + text += compiler.visit_identity_column(element.default) + return text + else: + # alter identity + diff, _, _ = element.impl._compare_identity_default( + element.default, element.existing_server_default + ) + identity = element.default + for attr in sorted(diff): + if attr == "always": + text += "SET GENERATED %s " % ( + "ALWAYS" if identity.always else "BY DEFAULT" + ) + else: + text += "SET %s " % compiler.get_identity_options( + sqla_compat.Identity(**{attr: getattr(identity, attr)}) + ) + return text + + +@Operations.register_operation("create_exclude_constraint") +@BatchOperations.register_operation( + "create_exclude_constraint", "batch_create_exclude_constraint" +) +@ops.AddConstraintOp.register_add_constraint("exclude_constraint") +class CreateExcludeConstraintOp(ops.AddConstraintOp): + """Represent a create exclude constraint operation.""" + + constraint_type = "exclude" + + def __init__( + self, + constraint_name: sqla_compat._ConstraintName, + table_name: Union[str, quoted_name], + elements: Union[ + Sequence[Tuple[str, str]], + Sequence[Tuple[ColumnClause[Any], str]], + ], + where: Optional[Union[ColumnElement[bool], str]] = None, + schema: Optional[str] = None, + _orig_constraint: Optional[ExcludeConstraint] = None, + **kw, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.elements = elements + self.where = where + self.schema = schema + self._orig_constraint = _orig_constraint + self.kw = kw + + @classmethod + def from_constraint( # type:ignore[override] + cls, constraint: ExcludeConstraint + ) -> CreateExcludeConstraintOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + return cls( + constraint.name, + constraint_table.name, + [ # type: ignore + (expr, op) for expr, name, op in constraint._render_exprs + ], + where=cast("ColumnElement[bool] | None", constraint.where), + schema=constraint_table.schema, + _orig_constraint=constraint, + deferrable=constraint.deferrable, + initially=constraint.initially, + using=constraint.using, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> ExcludeConstraint: + if self._orig_constraint is not None: + return self._orig_constraint + schema_obj = schemaobj.SchemaObjects(migration_context) + t = schema_obj.table(self.table_name, schema=self.schema) + excl = ExcludeConstraint( + *self.elements, + name=self.constraint_name, + where=self.where, + **self.kw, + ) + for ( + expr, + name, + oper, + ) in excl._render_exprs: + t.append_column(Column(name, NULLTYPE)) + t.append_constraint(excl) + return excl + + @classmethod + def create_exclude_constraint( + cls, + operations: Operations, + constraint_name: str, + table_name: str, + *elements: Any, + **kw: Any, + ) -> Optional[Table]: + """Issue an alter to create an EXCLUDE constraint using the + current migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + e.g.:: + + from alembic import op + + op.create_exclude_constraint( + "user_excl", + "user", + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), + ) + + Note that the expressions work the same way as that of + the ``ExcludeConstraint`` object itself; if plain strings are + passed, quoting rules must be applied manually. + + :param name: Name of the constraint. + :param table_name: String name of the source table. + :param elements: exclude conditions. + :param where: SQL expression or SQL string with optional WHERE + clause. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. + + """ + op = cls(constraint_name, table_name, elements, **kw) + return operations.invoke(op) + + @classmethod + def batch_create_exclude_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + *elements: Any, + **kw: Any, + ) -> Optional[Table]: + """Issue a "create exclude constraint" instruction using the + current batch migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + .. seealso:: + + :meth:`.Operations.create_exclude_constraint` + + """ + kw["schema"] = operations.impl.schema + op = cls(constraint_name, operations.impl.table_name, elements, **kw) + return operations.invoke(op) + + +@render.renderers.dispatch_for(CreateExcludeConstraintOp) +def _add_exclude_constraint( + autogen_context: AutogenContext, op: CreateExcludeConstraintOp +) -> str: + return _exclude_constraint(op.to_constraint(), autogen_context, alter=True) + + +@render._constraint_renderers.dispatch_for(ExcludeConstraint) +def _render_inline_exclude_constraint( + constraint: ExcludeConstraint, + autogen_context: AutogenContext, + namespace_metadata: MetaData, +) -> str: + rendered = render._user_defined_render( + "exclude", constraint, autogen_context + ) + if rendered is not False: + return rendered + + return _exclude_constraint(constraint, autogen_context, False) + + +def _postgresql_autogenerate_prefix(autogen_context: AutogenContext) -> str: + imports = autogen_context.imports + if imports is not None: + imports.add("from sqlalchemy.dialects import postgresql") + return "postgresql." + + +def _exclude_constraint( + constraint: ExcludeConstraint, + autogen_context: AutogenContext, + alter: bool, +) -> str: + opts: List[Tuple[str, Union[quoted_name, str, _f_name, None]]] = [] + + has_batch = autogen_context._has_batch + + if constraint.deferrable: + opts.append(("deferrable", str(constraint.deferrable))) + if constraint.initially: + opts.append(("initially", str(constraint.initially))) + if constraint.using: + opts.append(("using", str(constraint.using))) + if not has_batch and alter and constraint.table.schema: + opts.append(("schema", render._ident(constraint.table.schema))) + if not alter and constraint.name: + opts.append( + ("name", render._render_gen_name(autogen_context, constraint.name)) + ) + + def do_expr_where_opts(): + args = [ + "(%s, %r)" + % ( + _render_potential_column( + sqltext, # type:ignore[arg-type] + autogen_context, + ), + opstring, + ) + for sqltext, name, opstring in constraint._render_exprs + ] + if constraint.where is not None: + args.append( + "where=%s" + % render._render_potential_expr( + constraint.where, autogen_context + ) + ) + args.extend(["%s=%r" % (k, v) for k, v in opts]) + return args + + if alter: + args = [ + repr(render._render_gen_name(autogen_context, constraint.name)) + ] + if not has_batch: + args += [repr(render._ident(constraint.table.name))] + args.extend(do_expr_where_opts()) + return "%(prefix)screate_exclude_constraint(%(args)s)" % { + "prefix": render._alembic_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + else: + args = do_expr_where_opts() + return "%(prefix)sExcludeConstraint(%(args)s)" % { + "prefix": _postgresql_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + + +def _render_potential_column( + value: Union[ + ColumnClause[Any], Column[Any], TextClause, FunctionElement[Any] + ], + autogen_context: AutogenContext, +) -> str: + if isinstance(value, ColumnClause): + if value.is_literal: + # like literal_column("int8range(from, to)") in ExcludeConstraint + template = "%(prefix)sliteral_column(%(name)r)" + else: + template = "%(prefix)scolumn(%(name)r)" + + return template % { + "prefix": render._sqlalchemy_autogenerate_prefix(autogen_context), + "name": value.name, + } + else: + return render._render_potential_expr( + value, + autogen_context, + wrap_in_text=isinstance(value, (TextClause, FunctionElement)), + ) diff --git a/venv/lib/python3.8/site-packages/alembic/ddl/sqlite.py b/venv/lib/python3.8/site-packages/alembic/ddl/sqlite.py new file mode 100644 index 000000000..fce5c2396 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/ddl/sqlite.py @@ -0,0 +1,236 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Dict +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import cast +from sqlalchemy import JSON +from sqlalchemy import schema +from sqlalchemy import sql + +from .base import alter_table +from .base import ColumnName +from .base import format_column_name +from .base import format_table_name +from .base import RenameTable +from .impl import DefaultImpl +from .. import util +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from sqlalchemy.engine.reflection import Inspector + from sqlalchemy.sql.compiler import DDLCompiler + from sqlalchemy.sql.elements import Cast + from sqlalchemy.sql.elements import ClauseElement + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.type_api import TypeEngine + + from ..operations.batch import BatchOperationsImpl + + +class SQLiteImpl(DefaultImpl): + __dialect__ = "sqlite" + + transactional_ddl = False + """SQLite supports transactional DDL, but pysqlite does not: + see: http://bugs.python.org/issue10740 + """ + + def requires_recreate_in_batch( + self, batch_op: BatchOperationsImpl + ) -> bool: + """Return True if the given :class:`.BatchOperationsImpl` + would need the table to be recreated and copied in order to + proceed. + + Normally, only returns True on SQLite when operations other + than add_column are present. + + """ + for op in batch_op.batch: + if op[0] == "add_column": + col = op[1][1] + if isinstance( + col.server_default, schema.DefaultClause + ) and isinstance(col.server_default.arg, sql.ClauseElement): + return True + elif ( + isinstance(col.server_default, util.sqla_compat.Computed) + and col.server_default.persisted + ): + return True + elif op[0] not in ("create_index", "drop_index"): + return True + else: + return False + + def add_constraint(self, const: Constraint): + # attempt to distinguish between an + # auto-gen constraint and an explicit one + if const._create_rule is None: + raise NotImplementedError( + "No support for ALTER of constraints in SQLite dialect. " + "Please refer to the batch mode feature which allows for " + "SQLite migrations using a copy-and-move strategy." + ) + elif const._create_rule(self): + util.warn( + "Skipping unsupported ALTER for " + "creation of implicit constraint. " + "Please refer to the batch mode feature which allows for " + "SQLite migrations using a copy-and-move strategy." + ) + + def drop_constraint(self, const: Constraint): + if const._create_rule is None: + raise NotImplementedError( + "No support for ALTER of constraints in SQLite dialect. " + "Please refer to the batch mode feature which allows for " + "SQLite migrations using a copy-and-move strategy." + ) + + def compare_server_default( + self, + inspector_column: Column[Any], + metadata_column: Column[Any], + rendered_metadata_default: Optional[str], + rendered_inspector_default: Optional[str], + ) -> bool: + if rendered_metadata_default is not None: + rendered_metadata_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_metadata_default + ) + + rendered_metadata_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default + ) + + if rendered_inspector_default is not None: + rendered_inspector_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_inspector_default + ) + + rendered_inspector_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default + ) + + return rendered_inspector_default != rendered_metadata_default + + def _guess_if_default_is_unparenthesized_sql_expr( + self, expr: Optional[str] + ) -> bool: + """Determine if a server default is a SQL expression or a constant. + + There are too many assertions that expect server defaults to round-trip + identically without parenthesis added so we will add parens only in + very specific cases. + + """ + if not expr: + return False + elif re.match(r"^[0-9\.]$", expr): + return False + elif re.match(r"^'.+'$", expr): + return False + elif re.match(r"^\(.+\)$", expr): + return False + else: + return True + + def autogen_column_reflect( + self, + inspector: Inspector, + table: Table, + column_info: Dict[str, Any], + ) -> None: + # SQLite expression defaults require parenthesis when sent + # as DDL + if self._guess_if_default_is_unparenthesized_sql_expr( + column_info.get("default", None) + ): + column_info["default"] = "(%s)" % (column_info["default"],) + + def render_ddl_sql_expr( + self, expr: ClauseElement, is_server_default: bool = False, **kw + ) -> str: + # SQLite expression defaults require parenthesis when sent + # as DDL + str_expr = super().render_ddl_sql_expr( + expr, is_server_default=is_server_default, **kw + ) + + if ( + is_server_default + and self._guess_if_default_is_unparenthesized_sql_expr(str_expr) + ): + str_expr = "(%s)" % (str_expr,) + return str_expr + + def cast_for_batch_migrate( + self, + existing: Column[Any], + existing_transfer: Dict[str, Union[TypeEngine, Cast]], + new_type: TypeEngine, + ) -> None: + if ( + existing.type._type_affinity is not new_type._type_affinity + and not isinstance(new_type, JSON) + ): + existing_transfer["expr"] = cast( + existing_transfer["expr"], new_type + ) + + def correct_for_autogen_constraints( + self, + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + ): + self._skip_functional_indexes(metadata_indexes, conn_indexes) + + +@compiles(RenameTable, "sqlite") +def visit_rename_table( + element: RenameTable, compiler: DDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) + + +@compiles(ColumnName, "sqlite") +def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str: + return "%s RENAME COLUMN %s TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +# @compiles(AddColumn, 'sqlite') +# def visit_add_column(element, compiler, **kw): +# return "%s %s" % ( +# alter_table(compiler, element.table_name, element.schema), +# add_column(compiler, element.column, **kw) +# ) + + +# def add_column(compiler, column, **kw): +# text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw) +# need to modify SQLAlchemy so that the CHECK associated with a Boolean +# or Enum gets placed as part of the column constraints, not the Table +# see ticket 98 +# for const in column.constraints: +# text += compiler.process(AddConstraint(const)) +# return text diff --git a/venv/lib/python3.8/site-packages/alembic/environment.py b/venv/lib/python3.8/site-packages/alembic/environment.py new file mode 100644 index 000000000..adfc93eb0 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/environment.py @@ -0,0 +1 @@ +from .runtime.environment import * # noqa diff --git a/venv/lib/python3.8/site-packages/alembic/migration.py b/venv/lib/python3.8/site-packages/alembic/migration.py new file mode 100644 index 000000000..02626e2cf --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/migration.py @@ -0,0 +1 @@ +from .runtime.migration import * # noqa diff --git a/venv/lib/python3.8/site-packages/alembic/op.py b/venv/lib/python3.8/site-packages/alembic/op.py new file mode 100644 index 000000000..f3f5fac0c --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/op.py @@ -0,0 +1,5 @@ +from .operations.base import Operations + +# create proxy functions for +# each method on the Operations class. +Operations.create_module_class_proxy(globals(), locals()) diff --git a/venv/lib/python3.8/site-packages/alembic/op.pyi b/venv/lib/python3.8/site-packages/alembic/op.pyi new file mode 100644 index 000000000..920444696 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/op.pyi @@ -0,0 +1,1338 @@ +# ### this file stubs are generated by tools/write_pyi.py - do not edit ### +# ### imports are manually managed +from __future__ import annotations + +from contextlib import contextmanager +from typing import Any +from typing import Awaitable +from typing import Callable +from typing import Dict +from typing import Iterator +from typing import List +from typing import Literal +from typing import Mapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +if TYPE_CHECKING: + from sqlalchemy.engine import Connection + from sqlalchemy.sql import Executable + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import conv + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.expression import TableClause + from sqlalchemy.sql.functions import Function + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Computed + from sqlalchemy.sql.schema import Identity + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.type_api import TypeEngine + from sqlalchemy.util import immutabledict + + from .operations.base import BatchOperations + from .operations.ops import AddColumnOp + from .operations.ops import AddConstraintOp + from .operations.ops import AlterColumnOp + from .operations.ops import AlterTableOp + from .operations.ops import BulkInsertOp + from .operations.ops import CreateIndexOp + from .operations.ops import CreateTableCommentOp + from .operations.ops import CreateTableOp + from .operations.ops import DropColumnOp + from .operations.ops import DropConstraintOp + from .operations.ops import DropIndexOp + from .operations.ops import DropTableCommentOp + from .operations.ops import DropTableOp + from .operations.ops import ExecuteSQLOp + from .operations.ops import MigrateOperation + from .runtime.migration import MigrationContext + from .util.sqla_compat import _literal_bindparam + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=Callable[..., Any]) + +### end imports ### + +def add_column( + table_name: str, column: Column[Any], *, schema: Optional[str] = None +) -> None: + """Issue an "add column" instruction using the current + migration context. + + e.g.:: + + from alembic import op + from sqlalchemy import Column, String + + op.add_column("organization", Column("name", String())) + + The :meth:`.Operations.add_column` method typically corresponds + to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope + of this command, the column's name, datatype, nullability, + and optional server-generated defaults may be indicated. + + .. note:: + + With the exception of NOT NULL constraints or single-column FOREIGN + KEY constraints, other kinds of constraints such as PRIMARY KEY, + UNIQUE or CHECK constraints **cannot** be generated using this + method; for these constraints, refer to operations such as + :meth:`.Operations.create_primary_key` and + :meth:`.Operations.create_check_constraint`. In particular, the + following :class:`~sqlalchemy.schema.Column` parameters are + **ignored**: + + * :paramref:`~sqlalchemy.schema.Column.primary_key` - SQL databases + typically do not support an ALTER operation that can add + individual columns one at a time to an existing primary key + constraint, therefore it's less ambiguous to use the + :meth:`.Operations.create_primary_key` method, which assumes no + existing primary key constraint is present. + * :paramref:`~sqlalchemy.schema.Column.unique` - use the + :meth:`.Operations.create_unique_constraint` method + * :paramref:`~sqlalchemy.schema.Column.index` - use the + :meth:`.Operations.create_index` method + + + The provided :class:`~sqlalchemy.schema.Column` object may include a + :class:`~sqlalchemy.schema.ForeignKey` constraint directive, + referencing a remote table name. For this specific type of constraint, + Alembic will automatically emit a second ALTER statement in order to + add the single-column FOREIGN KEY constraint separately:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + ) + + The column argument passed to :meth:`.Operations.add_column` is a + :class:`~sqlalchemy.schema.Column` construct, used in the same way it's + used in SQLAlchemy. In particular, values or functions to be indicated + as producing the column's default value on the database side are + specified using the ``server_default`` parameter, and not ``default`` + which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the column add + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + :param table_name: String name of the parent table. + :param column: a :class:`sqlalchemy.schema.Column` object + representing the new column. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def alter_column( + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Union[str, Literal[False], None] = False, + server_default: Any = False, + new_column_name: Optional[str] = None, + type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_type: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_server_default: Union[ + str, bool, Identity, Computed, None + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + **kw: Any, +) -> None: + r"""Issue an "alter column" instruction using the + current migration context. + + Generally, only that aspect of the column which + is being changed, i.e. name, type, nullability, + default, needs to be specified. Multiple changes + can also be specified at once and the backend should + "do the right thing", emitting each change either + separately or together as the backend allows. + + MySQL has special requirements here, since MySQL + cannot ALTER a column without a full specification. + When producing MySQL-compatible migration files, + it is recommended that the ``existing_type``, + ``existing_server_default``, and ``existing_nullable`` + parameters be present, if not being altered. + + Type changes which are against the SQLAlchemy + "schema" types :class:`~sqlalchemy.types.Boolean` + and :class:`~sqlalchemy.types.Enum` may also + add or drop constraints which accompany those + types on backends that don't support them natively. + The ``existing_type`` argument is + used in this case to identify and remove a previous + constraint that was bound to the type object. + + :param table_name: string name of the target table. + :param column_name: string name of the target column, + as it exists before the operation begins. + :param nullable: Optional; specify ``True`` or ``False`` + to alter the column's nullability. + :param server_default: Optional; specify a string + SQL expression, :func:`~sqlalchemy.sql.expression.text`, + or :class:`~sqlalchemy.schema.DefaultClause` to indicate + an alteration to the column's default value. + Set to ``None`` to have the default removed. + :param comment: optional string text of a new comment to add to the + column. + :param new_column_name: Optional; specify a string name here to + indicate the new name within a column rename operation. + :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine` + type object to specify a change to the column's type. + For SQLAlchemy types that also indicate a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`), + the constraint is also generated. + :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column; + currently understood by the MySQL dialect. + :param existing_type: Optional; a + :class:`~sqlalchemy.types.TypeEngine` + type object to specify the previous type. This + is required for all MySQL column alter operations that + don't otherwise specify a new type, as well as for + when nullability is being changed on a SQL Server + column. It is also used if the type is a so-called + SQLAlchemy "schema" type which may define a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, + :class:`~sqlalchemy.types.Enum`), + so that the constraint can be dropped. + :param existing_server_default: Optional; The existing + default value of the column. Required on MySQL if + an existing default is not being changed; else MySQL + removes the default. + :param existing_nullable: Optional; the existing nullability + of the column. Required on MySQL if the existing nullability + is not being changed; else MySQL sets this to NULL. + :param existing_autoincrement: Optional; the existing autoincrement + of the column. Used for MySQL's system of altering a column + that specifies ``AUTO_INCREMENT``. + :param existing_comment: string text of the existing comment on the + column to be maintained. Required on MySQL if the existing comment + on the column is not being changed. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param postgresql_using: String argument which will indicate a + SQL expression to render within the Postgresql-specific USING clause + within ALTER COLUMN. This string is taken directly as raw SQL which + must explicitly include any necessary quoting or escaping of tokens + within the expression. + + """ + +@contextmanager +def batch_alter_table( + table_name: str, + schema: Optional[str] = None, + recreate: Literal["auto", "always", "never"] = "auto", + partial_reordering: Optional[Tuple[Any, ...]] = None, + copy_from: Optional[Table] = None, + table_args: Tuple[Any, ...] = (), + table_kwargs: Mapping[str, Any] = immutabledict({}), + reflect_args: Tuple[Any, ...] = (), + reflect_kwargs: Mapping[str, Any] = immutabledict({}), + naming_convention: Optional[Dict[str, str]] = None, +) -> Iterator[BatchOperations]: + """Invoke a series of per-table migrations in batch. + + Batch mode allows a series of operations specific to a table + to be syntactically grouped together, and allows for alternate + modes of table migration, in particular the "recreate" style of + migration required by SQLite. + + "recreate" style is as follows: + + 1. A new table is created with the new specification, based on the + migration directives within the batch, using a temporary name. + + 2. the data copied from the existing table to the new table. + + 3. the existing table is dropped. + + 4. the new table is renamed to the existing table name. + + The directive by default will only use "recreate" style on the + SQLite backend, and only if directives are present which require + this form, e.g. anything other than ``add_column()``. The batch + operation on other backends will proceed using standard ALTER TABLE + operations. + + The method is used as a context manager, which returns an instance + of :class:`.BatchOperations`; this object is the same as + :class:`.Operations` except that table names and schema names + are omitted. E.g.:: + + with op.batch_alter_table("some_table") as batch_op: + batch_op.add_column(Column("foo", Integer)) + batch_op.drop_column("bar") + + The operations within the context manager are invoked at once + when the context is ended. When run against SQLite, if the + migrations include operations not supported by SQLite's ALTER TABLE, + the entire table will be copied to a new one with the new + specification, moving all data across as well. + + The copy operation by default uses reflection to retrieve the current + structure of the table, and therefore :meth:`.batch_alter_table` + in this mode requires that the migration is run in "online" mode. + The ``copy_from`` parameter may be passed which refers to an existing + :class:`.Table` object, which will bypass this reflection step. + + .. note:: The table copy operation will currently not copy + CHECK constraints, and may not copy UNIQUE constraints that are + unnamed, as is possible on SQLite. See the section + :ref:`sqlite_batch_constraints` for workarounds. + + :param table_name: name of table + :param schema: optional schema name. + :param recreate: under what circumstances the table should be + recreated. At its default of ``"auto"``, the SQLite dialect will + recreate the table if any operations other than ``add_column()``, + ``create_index()``, or ``drop_index()`` are + present. Other options include ``"always"`` and ``"never"``. + :param copy_from: optional :class:`~sqlalchemy.schema.Table` object + that will act as the structure of the table being copied. If omitted, + table reflection is used to retrieve the structure of the table. + + .. seealso:: + + :ref:`batch_offline_mode` + + :paramref:`~.Operations.batch_alter_table.reflect_args` + + :paramref:`~.Operations.batch_alter_table.reflect_kwargs` + + :param reflect_args: a sequence of additional positional arguments that + will be applied to the table structure being reflected / copied; + this may be used to pass column and constraint overrides to the + table that will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param reflect_kwargs: a dictionary of additional keyword arguments + that will be applied to the table structure being copied; this may be + used to pass additional table and reflection options to the table that + will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param table_args: a sequence of additional positional arguments that + will be applied to the new :class:`~sqlalchemy.schema.Table` when + created, in addition to those copied from the source table. + This may be used to provide additional constraints such as CHECK + constraints that may not be reflected. + :param table_kwargs: a dictionary of additional keyword arguments + that will be applied to the new :class:`~sqlalchemy.schema.Table` + when created, in addition to those copied from the source table. + This may be used to provide for additional table options that may + not be reflected. + :param naming_convention: a naming convention dictionary of the form + described at :ref:`autogen_naming_conventions` which will be applied + to the :class:`~sqlalchemy.schema.MetaData` during the reflection + process. This is typically required if one wants to drop SQLite + constraints, as these constraints will not have names when + reflected on this backend. Requires SQLAlchemy **0.9.4** or greater. + + .. seealso:: + + :ref:`dropping_sqlite_foreign_keys` + + :param partial_reordering: a list of tuples, each suggesting a desired + ordering of two or more columns in the newly created table. Requires + that :paramref:`.batch_alter_table.recreate` is set to ``"always"``. + Examples, given a table with columns "a", "b", "c", and "d": + + Specify the order of all columns:: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("c", "d", "a", "b")], + ) as batch_op: + pass + + Ensure "d" appears before "c", and "b", appears before "a":: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("d", "c"), ("b", "a")], + ) as batch_op: + pass + + The ordering of columns not included in the partial_reordering + set is undefined. Therefore it is best to specify the complete + ordering of all columns for best results. + + .. note:: batch mode requires SQLAlchemy 0.8 or above. + + .. seealso:: + + :ref:`batch_migrations` + + """ + +def bulk_insert( + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, +) -> None: + """Issue a "bulk insert" operation using the current + migration context. + + This provides a means of representing an INSERT of multiple rows + which works equally well in the context of executing on a live + connection as well as that of generating a SQL script. In the + case of a SQL script, the values are rendered inline into the + statement. + + e.g.:: + + from alembic import op + from datetime import date + from sqlalchemy.sql import table, column + from sqlalchemy import String, Integer, Date + + # Create an ad-hoc table to use for the insert statement. + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), + ) + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], + ) + + When using --sql mode, some datatypes may not render inline + automatically, such as dates and other special types. When this + issue is present, :meth:`.Operations.inline_literal` may be used:: + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, + ], + multiinsert=False, + ) + + When using :meth:`.Operations.inline_literal` in conjunction with + :meth:`.Operations.bulk_insert`, in order for the statement to work + in "online" (e.g. non --sql) mode, the + :paramref:`~.Operations.bulk_insert.multiinsert` + flag should be set to ``False``, which will have the effect of + individual INSERT statements being emitted to the database, each + with a distinct VALUES clause, so that the "inline" values can + still be rendered, rather than attempting to pass the values + as bound parameters. + + :param table: a table object which represents the target of the INSERT. + + :param rows: a list of dictionaries indicating rows. + + :param multiinsert: when at its default of True and --sql mode is not + enabled, the INSERT statement will be executed using + "executemany()" style, where all elements in the list of + dictionaries are passed as bound parameters in a single + list. Setting this to False results in individual INSERT + statements being emitted per parameter set, and is needed + in those cases where non-literal values are present in the + parameter sets. + + """ + +def create_check_constraint( + constraint_name: Optional[str], + table_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + *, + schema: Optional[str] = None, + **kw: Any, +) -> None: + """Issue a "create check constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + from sqlalchemy.sql import column, func + + op.create_check_constraint( + "ck_user_name_len", + "user", + func.len(column("name")) > 5, + ) + + CHECK constraints are usually against a SQL expression, so ad-hoc + table metadata is usually needed. The function will convert the given + arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound + to an anonymous table in order to emit the CREATE statement. + + :param name: Name of the check constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param condition: SQL expression that's the condition of the + constraint. Can be a string or SQLAlchemy expression language + structure. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def create_exclude_constraint( + constraint_name: str, table_name: str, *elements: Any, **kw: Any +) -> Optional[Table]: + """Issue an alter to create an EXCLUDE constraint using the + current migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + e.g.:: + + from alembic import op + + op.create_exclude_constraint( + "user_excl", + "user", + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), + ) + + Note that the expressions work the same way as that of + the ``ExcludeConstraint`` object itself; if plain strings are + passed, quoting rules must be applied manually. + + :param name: Name of the constraint. + :param table_name: String name of the source table. + :param elements: exclude conditions. + :param where: SQL expression or SQL string with optional WHERE + clause. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. + + """ + +def create_foreign_key( + constraint_name: Optional[str], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + **dialect_kw: Any, +) -> None: + """Issue a "create foreign key" instruction using the + current migration context. + + e.g.:: + + from alembic import op + + op.create_foreign_key( + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.ForeignKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param source_table: String name of the source table. + :param referent_table: String name of the destination table. + :param local_cols: a list of string column names in the + source table. + :param remote_cols: a list of string column names in the + remote table. + :param onupdate: Optional string. If set, emit ON UPDATE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param ondelete: Optional string. If set, emit ON DELETE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param deferrable: optional bool. If set, emit DEFERRABLE or NOT + DEFERRABLE when issuing DDL for this constraint. + :param source_schema: Optional schema name of the source table. + :param referent_schema: Optional schema name of the destination table. + + """ + +def create_index( + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, Function[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, +) -> None: + r"""Issue a "create index" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_index("ik_test", "t1", ["foo", "bar"]) + + Functional indexes can be produced by using the + :func:`sqlalchemy.sql.expression.text` construct:: + + from alembic import op + from sqlalchemy import text + + op.create_index("ik_test", "t1", [text("lower(foo)")]) + + :param index_name: name of the index. + :param table_name: name of the owning table. + :param columns: a list consisting of string column names and/or + :func:`~sqlalchemy.sql.expression.text` constructs. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param unique: If True, create a unique index. + + :param quote: Force quoting of this column's name on or off, + corresponding to ``True`` or ``False``. When left at its default + of ``None``, the column identifier will be quoted according to + whether the name is case sensitive (identifiers with at least one + upper case character are treated as case sensitive), or if it's a + reserved word. This flag is only needed to force quoting of a + reserved word which is not known by the SQLAlchemy dialect. + + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + +def create_primary_key( + constraint_name: Optional[str], + table_name: str, + columns: List[str], + *, + schema: Optional[str] = None, +) -> None: + """Issue a "create primary key" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_primary_key("pk_my_table", "my_table", ["id", "version"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.PrimaryKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions` + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the target table. + :param columns: a list of string column names to be applied to the + primary key constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def create_table( + table_name: str, + *columns: SchemaItem, + if_not_exists: Optional[bool] = None, + **kw: Any, +) -> Table: + r"""Issue a "create table" instruction using the current migration + context. + + This directive receives an argument list similar to that of the + traditional :class:`sqlalchemy.schema.Table` construct, but without the + metadata:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + Note that :meth:`.create_table` accepts + :class:`~sqlalchemy.schema.Column` + constructs directly from the SQLAlchemy library. In particular, + default values to be created on the database side are + specified using the ``server_default`` parameter, and not + ``default`` which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the "timestamp" column + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + The function also returns a newly created + :class:`~sqlalchemy.schema.Table` object, corresponding to the table + specification given, which is suitable for + immediate SQL operations, in particular + :meth:`.Operations.bulk_insert`:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + account_table = op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + op.bulk_insert( + account_table, + [ + {"name": "A1", "description": "account 1"}, + {"name": "A2", "description": "account 2"}, + ], + ) + + :param table_name: Name of the table + :param \*columns: collection of :class:`~sqlalchemy.schema.Column` + objects within + the table, as well as optional :class:`~sqlalchemy.schema.Constraint` + objects + and :class:`~.sqlalchemy.schema.Index` objects. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + :return: the :class:`~sqlalchemy.schema.Table` object corresponding + to the parameters given. + + """ + +def create_table_comment( + table_name: str, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, +) -> None: + """Emit a COMMENT ON operation to set the comment for a table. + + :param table_name: string name of the target table. + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + .. seealso:: + + :meth:`.Operations.drop_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + +def create_unique_constraint( + constraint_name: Optional[str], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, +) -> Any: + """Issue a "create unique constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + op.create_unique_constraint("uq_user_name", "user", ["name"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.UniqueConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param name: Name of the unique constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param columns: a list of string column names in the + source table. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def drop_column( + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + **kw: Any, +) -> None: + """Issue a "drop column" instruction using the current + migration context. + + e.g.:: + + drop_column("organization", "account_id") + + :param table_name: name of table + :param column_name: name of column + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param mssql_drop_check: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the CHECK constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.check_constraints, + then exec's a separate DROP CONSTRAINT for that constraint. + :param mssql_drop_default: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the DEFAULT constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.default_constraints, + then exec's a separate DROP CONSTRAINT for that default. + :param mssql_drop_foreign_key: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop a single FOREIGN KEY constraint on the column using a + SQL-script-compatible + block that selects into a @variable from + sys.foreign_keys/sys.foreign_key_columns, + then exec's a separate DROP CONSTRAINT for that default. Only + works if the column has exactly one FK constraint which refers to + it, at the moment. + + """ + +def drop_constraint( + constraint_name: str, + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, +) -> None: + r"""Drop a constraint of the given name, typically via DROP CONSTRAINT. + + :param constraint_name: name of the constraint. + :param table_name: table name. + :param type\_: optional, required on MySQL. can be + 'foreignkey', 'primary', 'unique', or 'check'. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def drop_index( + index_name: str, + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, +) -> None: + r"""Issue a "drop index" instruction using the current + migration context. + + e.g.:: + + drop_index("accounts") + + :param index_name: name of the index. + :param table_name: name of the owning table. Some + backends such as Microsoft SQL Server require this. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + :param if_exists: If True, adds IF EXISTS operator when + dropping the index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + +def drop_table( + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, +) -> None: + r"""Issue a "drop table" instruction using the current + migration context. + + + e.g.:: + + drop_table("accounts") + + :param table_name: Name of the table + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + """ + +def drop_table_comment( + table_name: str, + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, +) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table. + + :param table_name: string name of the target table. + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + .. seealso:: + + :meth:`.Operations.create_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + +def execute( + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, +) -> None: + r"""Execute the given SQL using the current migration context. + + The given SQL can be a plain string, e.g.:: + + op.execute("INSERT INTO table (foo) VALUES ('some value')") + + Or it can be any kind of Core SQL Expression construct, such as + below where we use an update construct:: + + from sqlalchemy.sql import table, column + from sqlalchemy import String + from alembic import op + + account = table("account", column("name", String)) + op.execute( + account.update() + .where(account.c.name == op.inline_literal("account 1")) + .values({"name": op.inline_literal("account 2")}) + ) + + Above, we made use of the SQLAlchemy + :func:`sqlalchemy.sql.expression.table` and + :func:`sqlalchemy.sql.expression.column` constructs to make a brief, + ad-hoc table construct just for our UPDATE statement. A full + :class:`~sqlalchemy.schema.Table` construct of course works perfectly + fine as well, though note it's a recommended practice to at least + ensure the definition of a table is self-contained within the migration + script, rather than imported from a module that may break compatibility + with older migrations. + + In a SQL script context, the statement is emitted directly to the + output stream. There is *no* return result, however, as this + function is oriented towards generating a change script + that can run in "offline" mode. Additionally, parameterized + statements are discouraged here, as they *will not work* in offline + mode. Above, we use :meth:`.inline_literal` where parameters are + to be used. + + For full interaction with a connected database where parameters can + also be used normally, use the "bind" available from the context:: + + from alembic import op + + connection = op.get_bind() + + connection.execute( + account.update() + .where(account.c.name == "account 1") + .values({"name": "account 2"}) + ) + + Additionally, when passing the statement as a plain string, it is first + coerced into a :func:`sqlalchemy.sql.expression.text` construct + before being passed along. In the less likely case that the + literal SQL string contains a colon, it must be escaped with a + backslash, as:: + + op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')") + + + :param sqltext: Any legal SQLAlchemy expression, including: + + * a string + * a :func:`sqlalchemy.sql.expression.text` construct. + * a :func:`sqlalchemy.sql.expression.insert` construct. + * a :func:`sqlalchemy.sql.expression.update` construct. + * a :func:`sqlalchemy.sql.expression.delete` construct. + * Any "executable" described in SQLAlchemy Core documentation, + noting that no result set is returned. + + .. note:: when passing a plain string, the statement is coerced into + a :func:`sqlalchemy.sql.expression.text` construct. This construct + considers symbols with colons, e.g. ``:foo`` to be bound parameters. + To avoid this, ensure that colon symbols are escaped, e.g. + ``\:foo``. + + :param execution_options: Optional dictionary of + execution options, will be passed to + :meth:`sqlalchemy.engine.Connection.execution_options`. + """ + +def f(name: str) -> conv: + """Indicate a string name that has already had a naming convention + applied to it. + + This feature combines with the SQLAlchemy ``naming_convention`` feature + to disambiguate constraint names that have already had naming + conventions applied to them, versus those that have not. This is + necessary in the case that the ``"%(constraint_name)s"`` token + is used within a naming convention, so that it can be identified + that this particular name should remain fixed. + + If the :meth:`.Operations.f` is used on a constraint, the naming + convention will not take effect:: + + op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x"))) + + Above, the CHECK constraint generated will have the name + ``ck_bool_t_x`` regardless of whether or not a naming convention is + in use. + + Alternatively, if a naming convention is in use, and 'f' is not used, + names will be converted along conventions. If the ``target_metadata`` + contains the naming convention + ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the + output of the following: + + op.add_column("t", "x", Boolean(name="x")) + + will be:: + + CONSTRAINT ck_bool_t_x CHECK (x in (1, 0))) + + The function is rendered in the output of autogenerate when + a particular constraint name is already converted. + + """ + +def get_bind() -> Connection: + """Return the current 'bind'. + + Under normal circumstances, this is the + :class:`~sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + In a SQL script context, this value is ``None``. [TODO: verify this] + + """ + +def get_context() -> MigrationContext: + """Return the :class:`.MigrationContext` object that's + currently in use. + + """ + +def implementation_for(op_cls: Any) -> Callable[[_C], _C]: + """Register an implementation for a given :class:`.MigrateOperation`. + + This is part of the operation extensibility API. + + .. seealso:: + + :ref:`operation_plugins` - example of use + + """ + +def inline_literal( + value: Union[str, int], type_: Optional[TypeEngine[Any]] = None +) -> _literal_bindparam: + r"""Produce an 'inline literal' expression, suitable for + using in an INSERT, UPDATE, or DELETE statement. + + When using Alembic in "offline" mode, CRUD operations + aren't compatible with SQLAlchemy's default behavior surrounding + literal values, + which is that they are converted into bound values and passed + separately into the ``execute()`` method of the DBAPI cursor. + An offline SQL + script needs to have these rendered inline. While it should + always be noted that inline literal values are an **enormous** + security hole in an application that handles untrusted input, + a schema migration is not run in this context, so + literals are safe to render inline, with the caveat that + advanced types like dates may not be supported directly + by SQLAlchemy. + + See :meth:`.Operations.execute` for an example usage of + :meth:`.Operations.inline_literal`. + + The environment can also be configured to attempt to render + "literal" values inline automatically, for those simple types + that are supported by the dialect; see + :paramref:`.EnvironmentContext.configure.literal_binds` for this + more recently added feature. + + :param value: The value to render. Strings, integers, and simple + numerics should be supported. Other types like boolean, + dates, etc. may or may not be supported yet by various + backends. + :param type\_: optional - a :class:`sqlalchemy.types.TypeEngine` + subclass stating the type of this value. In SQLAlchemy + expressions, this is usually derived automatically + from the Python type of the value itself, as well as + based on the context in which the value is used. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.literal_binds` + + """ + +@overload +def invoke(operation: CreateTableOp) -> Table: ... +@overload +def invoke( + operation: Union[ + AddConstraintOp, + DropConstraintOp, + CreateIndexOp, + DropIndexOp, + AddColumnOp, + AlterColumnOp, + AlterTableOp, + CreateTableCommentOp, + DropTableCommentOp, + DropColumnOp, + BulkInsertOp, + DropTableOp, + ExecuteSQLOp, + ] +) -> None: ... +@overload +def invoke(operation: MigrateOperation) -> Any: + """Given a :class:`.MigrateOperation`, invoke it in terms of + this :class:`.Operations` instance. + + """ + +def register_operation( + name: str, sourcename: Optional[str] = None +) -> Callable[[Type[_T]], Type[_T]]: + """Register a new operation for this class. + + This method is normally used to add new operations + to the :class:`.Operations` class, and possibly the + :class:`.BatchOperations` class as well. All Alembic migration + operations are implemented via this system, however the system + is also available as a public API to facilitate adding custom + operations. + + .. seealso:: + + :ref:`operation_plugins` + + + """ + +def rename_table( + old_table_name: str, new_table_name: str, *, schema: Optional[str] = None +) -> None: + """Emit an ALTER TABLE to rename a table. + + :param old_table_name: old name. + :param new_table_name: new name. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def run_async( + async_function: Callable[..., Awaitable[_T]], *args: Any, **kw_args: Any +) -> _T: + """Invoke the given asynchronous callable, passing an asynchronous + :class:`~sqlalchemy.ext.asyncio.AsyncConnection` as the first + argument. + + This method allows calling async functions from within the + synchronous ``upgrade()`` or ``downgrade()`` alembic migration + method. + + The async connection passed to the callable shares the same + transaction as the connection running in the migration context. + + Any additional arg or kw_arg passed to this function are passed + to the provided async function. + + .. versionadded: 1.11 + + .. note:: + + This method can be called only when alembic is called using + an async dialect. + """ diff --git a/venv/lib/python3.8/site-packages/alembic/operations/__init__.py b/venv/lib/python3.8/site-packages/alembic/operations/__init__.py new file mode 100644 index 000000000..26197cbe8 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/operations/__init__.py @@ -0,0 +1,15 @@ +from . import toimpl +from .base import AbstractOperations +from .base import BatchOperations +from .base import Operations +from .ops import MigrateOperation +from .ops import MigrationScript + + +__all__ = [ + "AbstractOperations", + "Operations", + "BatchOperations", + "MigrateOperation", + "MigrationScript", +] diff --git a/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..413aad7b2bb8880bb6c71989bd1aa97c5f60a217 GIT binary patch literal 502 zcmY+ByH3L}6o&0wnl!YcV&EM*q#n93AOwh!%TTdoiE=r#R_(;HO$EFW@4-`KW#ScJ z;W(`dwdG&mIsWaVV?U0^3BfV>VElp-^67&ABgEhmm&s6SQd2=B%_w#32~YZ&-?NV# zWI@jXa+rlZhhiY3Eb4iH9A`0dRLAwOPU@p&m<{XEf=tI>Se;C1H?S?2N+6wFmB#9# zvJVP$VR_pa%pPACwptyel7nb+$Cqe_ebOD;=J1L|rMa@&$70&`rUNGfClSj1H0g-; zkh-8O3~Y_P$GJPdRp!s#MR{Hbk>~G3kK7ebzF*j?mB8Q))*J-8fenaONoXv)ElR`G zt6jBCs^;mF1}HhCuFExOwt;5D1TS}%`Rt4tZXs2;T`a+{LVzrJ#oGOgusr7tw|PEO ZuU&jD+xl66`$=L>QATt>1CQ?J&o4#ZfZG57 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/base.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43d9240f6a95742236dd2371852f3c3bbae5b72d GIT binary patch literal 71986 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/batch.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/batch.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1bc534c8eb75979961f9437aa7d553196ccddea6 GIT binary patch literal 19208 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/ops.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/ops.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c4d81fa34823efd3db26c09ec1a5c020625eb0c GIT binary patch literal 84033 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%0Aiq`aLr7^Z~*#Y%RcZMUR)A_BX{4Evg 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 b>BvRyw3(W3-n*^Vtxw+t!$BdK2u6MYD^YEp literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/schemaobj.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/schemaobj.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b3c0438d505ce8896eb7981dcff4c79b078a7b6 GIT binary patch literal 8436 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/toimpl.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/operations/__pycache__/toimpl.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9534a9374e688033fe132540af2a2ce8cc1174ed GIT binary patch literal 5740 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/operations/base.py b/venv/lib/python3.8/site-packages/alembic/operations/base.py new file mode 100644 index 000000000..9b52fa6f2 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/operations/base.py @@ -0,0 +1,1907 @@ +# mypy: allow-untyped-calls + +from __future__ import annotations + +from contextlib import contextmanager +import re +import textwrap +from typing import Any +from typing import Awaitable +from typing import Callable +from typing import Dict +from typing import Iterator +from typing import List # noqa +from typing import Mapping +from typing import NoReturn +from typing import Optional +from typing import overload +from typing import Sequence # noqa +from typing import Tuple +from typing import Type # noqa +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy.sql.elements import conv + +from . import batch +from . import schemaobj +from .. import util +from ..util import sqla_compat +from ..util.compat import formatannotation_fwdref +from ..util.compat import inspect_formatargspec +from ..util.compat import inspect_getfullargspec +from ..util.sqla_compat import _literal_bindparam + + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy import Table + from sqlalchemy.engine import Connection + from sqlalchemy.sql import Executable + from sqlalchemy.sql.expression import ColumnElement + from sqlalchemy.sql.expression import TableClause + from sqlalchemy.sql.expression import TextClause + from sqlalchemy.sql.functions import Function + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Computed + from sqlalchemy.sql.schema import Identity + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.types import TypeEngine + + from .batch import BatchOperationsImpl + from .ops import AddColumnOp + from .ops import AddConstraintOp + from .ops import AlterColumnOp + from .ops import AlterTableOp + from .ops import BulkInsertOp + from .ops import CreateIndexOp + from .ops import CreateTableCommentOp + from .ops import CreateTableOp + from .ops import DropColumnOp + from .ops import DropConstraintOp + from .ops import DropIndexOp + from .ops import DropTableCommentOp + from .ops import DropTableOp + from .ops import ExecuteSQLOp + from .ops import MigrateOperation + from ..ddl import DefaultImpl + from ..runtime.migration import MigrationContext +__all__ = ("Operations", "BatchOperations") +_T = TypeVar("_T") + +_C = TypeVar("_C", bound=Callable[..., Any]) + + +class AbstractOperations(util.ModuleClsProxy): + """Base class for Operations and BatchOperations. + + .. versionadded:: 1.11.0 + + """ + + impl: Union[DefaultImpl, BatchOperationsImpl] + _to_impl = util.Dispatcher() + + def __init__( + self, + migration_context: MigrationContext, + impl: Optional[BatchOperationsImpl] = None, + ) -> None: + """Construct a new :class:`.Operations` + + :param migration_context: a :class:`.MigrationContext` + instance. + + """ + self.migration_context = migration_context + if impl is None: + self.impl = migration_context.impl + else: + self.impl = impl + + self.schema_obj = schemaobj.SchemaObjects(migration_context) + + @classmethod + def register_operation( + cls, name: str, sourcename: Optional[str] = None + ) -> Callable[[Type[_T]], Type[_T]]: + """Register a new operation for this class. + + This method is normally used to add new operations + to the :class:`.Operations` class, and possibly the + :class:`.BatchOperations` class as well. All Alembic migration + operations are implemented via this system, however the system + is also available as a public API to facilitate adding custom + operations. + + .. seealso:: + + :ref:`operation_plugins` + + + """ + + def register(op_cls: Type[_T]) -> Type[_T]: + if sourcename is None: + fn = getattr(op_cls, name) + source_name = fn.__name__ + else: + fn = getattr(op_cls, sourcename) + source_name = fn.__name__ + + spec = inspect_getfullargspec(fn) + + name_args = spec[0] + assert name_args[0:2] == ["cls", "operations"] + + name_args[0:2] = ["self"] + + args = inspect_formatargspec( + *spec, formatannotation=formatannotation_fwdref + ) + num_defaults = len(spec[3]) if spec[3] else 0 + + defaulted_vals: Tuple[Any, ...] + + if num_defaults: + defaulted_vals = tuple(name_args[0 - num_defaults :]) + else: + defaulted_vals = () + + defaulted_vals += tuple(spec[4]) + # here, we are using formatargspec in a different way in order + # to get a string that will re-apply incoming arguments to a new + # function call + + apply_kw = inspect_formatargspec( + name_args + spec[4], + spec[1], + spec[2], + defaulted_vals, + formatvalue=lambda x: "=" + x, + formatannotation=formatannotation_fwdref, + ) + + args = re.sub( + r'[_]?ForwardRef\(([\'"].+?[\'"])\)', + lambda m: m.group(1), + args, + ) + + func_text = textwrap.dedent( + """\ + def %(name)s%(args)s: + %(doc)r + return op_cls.%(source_name)s%(apply_kw)s + """ + % { + "name": name, + "source_name": source_name, + "args": args, + "apply_kw": apply_kw, + "doc": fn.__doc__, + } + ) + + globals_ = dict(globals()) + globals_.update({"op_cls": op_cls}) + lcl: Dict[str, Any] = {} + + exec(func_text, globals_, lcl) + setattr(cls, name, lcl[name]) + fn.__func__.__doc__ = ( + "This method is proxied on " + "the :class:`.%s` class, via the :meth:`.%s.%s` method." + % (cls.__name__, cls.__name__, name) + ) + if hasattr(fn, "_legacy_translations"): + lcl[name]._legacy_translations = fn._legacy_translations + return op_cls + + return register + + @classmethod + def implementation_for(cls, op_cls: Any) -> Callable[[_C], _C]: + """Register an implementation for a given :class:`.MigrateOperation`. + + This is part of the operation extensibility API. + + .. seealso:: + + :ref:`operation_plugins` - example of use + + """ + + def decorate(fn: _C) -> _C: + cls._to_impl.dispatch_for(op_cls)(fn) + return fn + + return decorate + + @classmethod + @contextmanager + def context( + cls, migration_context: MigrationContext + ) -> Iterator[Operations]: + op = Operations(migration_context) + op._install_proxy() + yield op + op._remove_proxy() + + @contextmanager + def batch_alter_table( + self, + table_name: str, + schema: Optional[str] = None, + recreate: Literal["auto", "always", "never"] = "auto", + partial_reordering: Optional[Tuple[Any, ...]] = None, + copy_from: Optional[Table] = None, + table_args: Tuple[Any, ...] = (), + table_kwargs: Mapping[str, Any] = util.immutabledict(), + reflect_args: Tuple[Any, ...] = (), + reflect_kwargs: Mapping[str, Any] = util.immutabledict(), + naming_convention: Optional[Dict[str, str]] = None, + ) -> Iterator[BatchOperations]: + """Invoke a series of per-table migrations in batch. + + Batch mode allows a series of operations specific to a table + to be syntactically grouped together, and allows for alternate + modes of table migration, in particular the "recreate" style of + migration required by SQLite. + + "recreate" style is as follows: + + 1. A new table is created with the new specification, based on the + migration directives within the batch, using a temporary name. + + 2. the data copied from the existing table to the new table. + + 3. the existing table is dropped. + + 4. the new table is renamed to the existing table name. + + The directive by default will only use "recreate" style on the + SQLite backend, and only if directives are present which require + this form, e.g. anything other than ``add_column()``. The batch + operation on other backends will proceed using standard ALTER TABLE + operations. + + The method is used as a context manager, which returns an instance + of :class:`.BatchOperations`; this object is the same as + :class:`.Operations` except that table names and schema names + are omitted. E.g.:: + + with op.batch_alter_table("some_table") as batch_op: + batch_op.add_column(Column("foo", Integer)) + batch_op.drop_column("bar") + + The operations within the context manager are invoked at once + when the context is ended. When run against SQLite, if the + migrations include operations not supported by SQLite's ALTER TABLE, + the entire table will be copied to a new one with the new + specification, moving all data across as well. + + The copy operation by default uses reflection to retrieve the current + structure of the table, and therefore :meth:`.batch_alter_table` + in this mode requires that the migration is run in "online" mode. + The ``copy_from`` parameter may be passed which refers to an existing + :class:`.Table` object, which will bypass this reflection step. + + .. note:: The table copy operation will currently not copy + CHECK constraints, and may not copy UNIQUE constraints that are + unnamed, as is possible on SQLite. See the section + :ref:`sqlite_batch_constraints` for workarounds. + + :param table_name: name of table + :param schema: optional schema name. + :param recreate: under what circumstances the table should be + recreated. At its default of ``"auto"``, the SQLite dialect will + recreate the table if any operations other than ``add_column()``, + ``create_index()``, or ``drop_index()`` are + present. Other options include ``"always"`` and ``"never"``. + :param copy_from: optional :class:`~sqlalchemy.schema.Table` object + that will act as the structure of the table being copied. If omitted, + table reflection is used to retrieve the structure of the table. + + .. seealso:: + + :ref:`batch_offline_mode` + + :paramref:`~.Operations.batch_alter_table.reflect_args` + + :paramref:`~.Operations.batch_alter_table.reflect_kwargs` + + :param reflect_args: a sequence of additional positional arguments that + will be applied to the table structure being reflected / copied; + this may be used to pass column and constraint overrides to the + table that will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param reflect_kwargs: a dictionary of additional keyword arguments + that will be applied to the table structure being copied; this may be + used to pass additional table and reflection options to the table that + will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param table_args: a sequence of additional positional arguments that + will be applied to the new :class:`~sqlalchemy.schema.Table` when + created, in addition to those copied from the source table. + This may be used to provide additional constraints such as CHECK + constraints that may not be reflected. + :param table_kwargs: a dictionary of additional keyword arguments + that will be applied to the new :class:`~sqlalchemy.schema.Table` + when created, in addition to those copied from the source table. + This may be used to provide for additional table options that may + not be reflected. + :param naming_convention: a naming convention dictionary of the form + described at :ref:`autogen_naming_conventions` which will be applied + to the :class:`~sqlalchemy.schema.MetaData` during the reflection + process. This is typically required if one wants to drop SQLite + constraints, as these constraints will not have names when + reflected on this backend. Requires SQLAlchemy **0.9.4** or greater. + + .. seealso:: + + :ref:`dropping_sqlite_foreign_keys` + + :param partial_reordering: a list of tuples, each suggesting a desired + ordering of two or more columns in the newly created table. Requires + that :paramref:`.batch_alter_table.recreate` is set to ``"always"``. + Examples, given a table with columns "a", "b", "c", and "d": + + Specify the order of all columns:: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("c", "d", "a", "b")], + ) as batch_op: + pass + + Ensure "d" appears before "c", and "b", appears before "a":: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("d", "c"), ("b", "a")], + ) as batch_op: + pass + + The ordering of columns not included in the partial_reordering + set is undefined. Therefore it is best to specify the complete + ordering of all columns for best results. + + .. note:: batch mode requires SQLAlchemy 0.8 or above. + + .. seealso:: + + :ref:`batch_migrations` + + """ + impl = batch.BatchOperationsImpl( + self, + table_name, + schema, + recreate, + copy_from, + table_args, + table_kwargs, + reflect_args, + reflect_kwargs, + naming_convention, + partial_reordering, + ) + batch_op = BatchOperations(self.migration_context, impl=impl) + yield batch_op + impl.flush() + + def get_context(self) -> MigrationContext: + """Return the :class:`.MigrationContext` object that's + currently in use. + + """ + + return self.migration_context + + @overload + def invoke(self, operation: CreateTableOp) -> Table: ... + + @overload + def invoke( + self, + operation: Union[ + AddConstraintOp, + DropConstraintOp, + CreateIndexOp, + DropIndexOp, + AddColumnOp, + AlterColumnOp, + AlterTableOp, + CreateTableCommentOp, + DropTableCommentOp, + DropColumnOp, + BulkInsertOp, + DropTableOp, + ExecuteSQLOp, + ], + ) -> None: ... + + @overload + def invoke(self, operation: MigrateOperation) -> Any: ... + + def invoke(self, operation: MigrateOperation) -> Any: + """Given a :class:`.MigrateOperation`, invoke it in terms of + this :class:`.Operations` instance. + + """ + fn = self._to_impl.dispatch( + operation, self.migration_context.impl.__dialect__ + ) + return fn(self, operation) + + def f(self, name: str) -> conv: + """Indicate a string name that has already had a naming convention + applied to it. + + This feature combines with the SQLAlchemy ``naming_convention`` feature + to disambiguate constraint names that have already had naming + conventions applied to them, versus those that have not. This is + necessary in the case that the ``"%(constraint_name)s"`` token + is used within a naming convention, so that it can be identified + that this particular name should remain fixed. + + If the :meth:`.Operations.f` is used on a constraint, the naming + convention will not take effect:: + + op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x"))) + + Above, the CHECK constraint generated will have the name + ``ck_bool_t_x`` regardless of whether or not a naming convention is + in use. + + Alternatively, if a naming convention is in use, and 'f' is not used, + names will be converted along conventions. If the ``target_metadata`` + contains the naming convention + ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the + output of the following: + + op.add_column("t", "x", Boolean(name="x")) + + will be:: + + CONSTRAINT ck_bool_t_x CHECK (x in (1, 0))) + + The function is rendered in the output of autogenerate when + a particular constraint name is already converted. + + """ + return conv(name) + + def inline_literal( + self, value: Union[str, int], type_: Optional[TypeEngine[Any]] = None + ) -> _literal_bindparam: + r"""Produce an 'inline literal' expression, suitable for + using in an INSERT, UPDATE, or DELETE statement. + + When using Alembic in "offline" mode, CRUD operations + aren't compatible with SQLAlchemy's default behavior surrounding + literal values, + which is that they are converted into bound values and passed + separately into the ``execute()`` method of the DBAPI cursor. + An offline SQL + script needs to have these rendered inline. While it should + always be noted that inline literal values are an **enormous** + security hole in an application that handles untrusted input, + a schema migration is not run in this context, so + literals are safe to render inline, with the caveat that + advanced types like dates may not be supported directly + by SQLAlchemy. + + See :meth:`.Operations.execute` for an example usage of + :meth:`.Operations.inline_literal`. + + The environment can also be configured to attempt to render + "literal" values inline automatically, for those simple types + that are supported by the dialect; see + :paramref:`.EnvironmentContext.configure.literal_binds` for this + more recently added feature. + + :param value: The value to render. Strings, integers, and simple + numerics should be supported. Other types like boolean, + dates, etc. may or may not be supported yet by various + backends. + :param type\_: optional - a :class:`sqlalchemy.types.TypeEngine` + subclass stating the type of this value. In SQLAlchemy + expressions, this is usually derived automatically + from the Python type of the value itself, as well as + based on the context in which the value is used. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.literal_binds` + + """ + return sqla_compat._literal_bindparam(None, value, type_=type_) + + def get_bind(self) -> Connection: + """Return the current 'bind'. + + Under normal circumstances, this is the + :class:`~sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + In a SQL script context, this value is ``None``. [TODO: verify this] + + """ + return self.migration_context.impl.bind # type: ignore[return-value] + + def run_async( + self, + async_function: Callable[..., Awaitable[_T]], + *args: Any, + **kw_args: Any, + ) -> _T: + """Invoke the given asynchronous callable, passing an asynchronous + :class:`~sqlalchemy.ext.asyncio.AsyncConnection` as the first + argument. + + This method allows calling async functions from within the + synchronous ``upgrade()`` or ``downgrade()`` alembic migration + method. + + The async connection passed to the callable shares the same + transaction as the connection running in the migration context. + + Any additional arg or kw_arg passed to this function are passed + to the provided async function. + + .. versionadded: 1.11 + + .. note:: + + This method can be called only when alembic is called using + an async dialect. + """ + if not sqla_compat.sqla_14_18: + raise NotImplementedError("SQLAlchemy 1.4.18+ required") + sync_conn = self.get_bind() + if sync_conn is None: + raise NotImplementedError("Cannot call run_async in SQL mode") + if not sync_conn.dialect.is_async: + raise ValueError("Cannot call run_async with a sync engine") + from sqlalchemy.ext.asyncio import AsyncConnection + from sqlalchemy.util import await_only + + async_conn = AsyncConnection._retrieve_proxy_for_target(sync_conn) + return await_only(async_function(async_conn, *args, **kw_args)) + + +class Operations(AbstractOperations): + """Define high level migration operations. + + Each operation corresponds to some schema migration operation, + executed against a particular :class:`.MigrationContext` + which in turn represents connectivity to a database, + or a file output stream. + + While :class:`.Operations` is normally configured as + part of the :meth:`.EnvironmentContext.run_migrations` + method called from an ``env.py`` script, a standalone + :class:`.Operations` instance can be + made for use cases external to regular Alembic + migrations by passing in a :class:`.MigrationContext`:: + + from alembic.migration import MigrationContext + from alembic.operations import Operations + + conn = myengine.connect() + ctx = MigrationContext.configure(conn) + op = Operations(ctx) + + op.alter_column("t", "c", nullable=True) + + Note that as of 0.8, most of the methods on this class are produced + dynamically using the :meth:`.Operations.register_operation` + method. + + """ + + if TYPE_CHECKING: + # START STUB FUNCTIONS: op_cls + # ### the following stubs are generated by tools/write_pyi.py ### + # ### do not edit ### + + def add_column( + self, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + ) -> None: + """Issue an "add column" instruction using the current + migration context. + + e.g.:: + + from alembic import op + from sqlalchemy import Column, String + + op.add_column("organization", Column("name", String())) + + The :meth:`.Operations.add_column` method typically corresponds + to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope + of this command, the column's name, datatype, nullability, + and optional server-generated defaults may be indicated. + + .. note:: + + With the exception of NOT NULL constraints or single-column FOREIGN + KEY constraints, other kinds of constraints such as PRIMARY KEY, + UNIQUE or CHECK constraints **cannot** be generated using this + method; for these constraints, refer to operations such as + :meth:`.Operations.create_primary_key` and + :meth:`.Operations.create_check_constraint`. In particular, the + following :class:`~sqlalchemy.schema.Column` parameters are + **ignored**: + + * :paramref:`~sqlalchemy.schema.Column.primary_key` - SQL databases + typically do not support an ALTER operation that can add + individual columns one at a time to an existing primary key + constraint, therefore it's less ambiguous to use the + :meth:`.Operations.create_primary_key` method, which assumes no + existing primary key constraint is present. + * :paramref:`~sqlalchemy.schema.Column.unique` - use the + :meth:`.Operations.create_unique_constraint` method + * :paramref:`~sqlalchemy.schema.Column.index` - use the + :meth:`.Operations.create_index` method + + + The provided :class:`~sqlalchemy.schema.Column` object may include a + :class:`~sqlalchemy.schema.ForeignKey` constraint directive, + referencing a remote table name. For this specific type of constraint, + Alembic will automatically emit a second ALTER statement in order to + add the single-column FOREIGN KEY constraint separately:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + ) + + The column argument passed to :meth:`.Operations.add_column` is a + :class:`~sqlalchemy.schema.Column` construct, used in the same way it's + used in SQLAlchemy. In particular, values or functions to be indicated + as producing the column's default value on the database side are + specified using the ``server_default`` parameter, and not ``default`` + which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the column add + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + :param table_name: String name of the parent table. + :param column: a :class:`sqlalchemy.schema.Column` object + representing the new column. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def alter_column( + self, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Union[str, Literal[False], None] = False, + server_default: Any = False, + new_column_name: Optional[str] = None, + type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_type: Union[ + TypeEngine[Any], Type[TypeEngine[Any]], None + ] = None, + existing_server_default: Union[ + str, bool, Identity, Computed, None + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + r"""Issue an "alter column" instruction using the + current migration context. + + Generally, only that aspect of the column which + is being changed, i.e. name, type, nullability, + default, needs to be specified. Multiple changes + can also be specified at once and the backend should + "do the right thing", emitting each change either + separately or together as the backend allows. + + MySQL has special requirements here, since MySQL + cannot ALTER a column without a full specification. + When producing MySQL-compatible migration files, + it is recommended that the ``existing_type``, + ``existing_server_default``, and ``existing_nullable`` + parameters be present, if not being altered. + + Type changes which are against the SQLAlchemy + "schema" types :class:`~sqlalchemy.types.Boolean` + and :class:`~sqlalchemy.types.Enum` may also + add or drop constraints which accompany those + types on backends that don't support them natively. + The ``existing_type`` argument is + used in this case to identify and remove a previous + constraint that was bound to the type object. + + :param table_name: string name of the target table. + :param column_name: string name of the target column, + as it exists before the operation begins. + :param nullable: Optional; specify ``True`` or ``False`` + to alter the column's nullability. + :param server_default: Optional; specify a string + SQL expression, :func:`~sqlalchemy.sql.expression.text`, + or :class:`~sqlalchemy.schema.DefaultClause` to indicate + an alteration to the column's default value. + Set to ``None`` to have the default removed. + :param comment: optional string text of a new comment to add to the + column. + :param new_column_name: Optional; specify a string name here to + indicate the new name within a column rename operation. + :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine` + type object to specify a change to the column's type. + For SQLAlchemy types that also indicate a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`), + the constraint is also generated. + :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column; + currently understood by the MySQL dialect. + :param existing_type: Optional; a + :class:`~sqlalchemy.types.TypeEngine` + type object to specify the previous type. This + is required for all MySQL column alter operations that + don't otherwise specify a new type, as well as for + when nullability is being changed on a SQL Server + column. It is also used if the type is a so-called + SQLAlchemy "schema" type which may define a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, + :class:`~sqlalchemy.types.Enum`), + so that the constraint can be dropped. + :param existing_server_default: Optional; The existing + default value of the column. Required on MySQL if + an existing default is not being changed; else MySQL + removes the default. + :param existing_nullable: Optional; the existing nullability + of the column. Required on MySQL if the existing nullability + is not being changed; else MySQL sets this to NULL. + :param existing_autoincrement: Optional; the existing autoincrement + of the column. Used for MySQL's system of altering a column + that specifies ``AUTO_INCREMENT``. + :param existing_comment: string text of the existing comment on the + column to be maintained. Required on MySQL if the existing comment + on the column is not being changed. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param postgresql_using: String argument which will indicate a + SQL expression to render within the Postgresql-specific USING clause + within ALTER COLUMN. This string is taken directly as raw SQL which + must explicitly include any necessary quoting or escaping of tokens + within the expression. + + """ # noqa: E501 + ... + + def bulk_insert( + self, + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, + ) -> None: + """Issue a "bulk insert" operation using the current + migration context. + + This provides a means of representing an INSERT of multiple rows + which works equally well in the context of executing on a live + connection as well as that of generating a SQL script. In the + case of a SQL script, the values are rendered inline into the + statement. + + e.g.:: + + from alembic import op + from datetime import date + from sqlalchemy.sql import table, column + from sqlalchemy import String, Integer, Date + + # Create an ad-hoc table to use for the insert statement. + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), + ) + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], + ) + + When using --sql mode, some datatypes may not render inline + automatically, such as dates and other special types. When this + issue is present, :meth:`.Operations.inline_literal` may be used:: + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, + ], + multiinsert=False, + ) + + When using :meth:`.Operations.inline_literal` in conjunction with + :meth:`.Operations.bulk_insert`, in order for the statement to work + in "online" (e.g. non --sql) mode, the + :paramref:`~.Operations.bulk_insert.multiinsert` + flag should be set to ``False``, which will have the effect of + individual INSERT statements being emitted to the database, each + with a distinct VALUES clause, so that the "inline" values can + still be rendered, rather than attempting to pass the values + as bound parameters. + + :param table: a table object which represents the target of the INSERT. + + :param rows: a list of dictionaries indicating rows. + + :param multiinsert: when at its default of True and --sql mode is not + enabled, the INSERT statement will be executed using + "executemany()" style, where all elements in the list of + dictionaries are passed as bound parameters in a single + list. Setting this to False results in individual INSERT + statements being emitted per parameter set, and is needed + in those cases where non-literal values are present in the + parameter sets. + + """ # noqa: E501 + ... + + def create_check_constraint( + self, + constraint_name: Optional[str], + table_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + from sqlalchemy.sql import column, func + + op.create_check_constraint( + "ck_user_name_len", + "user", + func.len(column("name")) > 5, + ) + + CHECK constraints are usually against a SQL expression, so ad-hoc + table metadata is usually needed. The function will convert the given + arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound + to an anonymous table in order to emit the CREATE statement. + + :param name: Name of the check constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param condition: SQL expression that's the condition of the + constraint. Can be a string or SQLAlchemy expression language + structure. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def create_exclude_constraint( + self, + constraint_name: str, + table_name: str, + *elements: Any, + **kw: Any, + ) -> Optional[Table]: + """Issue an alter to create an EXCLUDE constraint using the + current migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + e.g.:: + + from alembic import op + + op.create_exclude_constraint( + "user_excl", + "user", + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), + ) + + Note that the expressions work the same way as that of + the ``ExcludeConstraint`` object itself; if plain strings are + passed, quoting rules must be applied manually. + + :param name: Name of the constraint. + :param table_name: String name of the source table. + :param elements: exclude conditions. + :param where: SQL expression or SQL string with optional WHERE + clause. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. + + """ # noqa: E501 + ... + + def create_foreign_key( + self, + constraint_name: Optional[str], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current migration context. + + e.g.:: + + from alembic import op + + op.create_foreign_key( + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.ForeignKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param source_table: String name of the source table. + :param referent_table: String name of the destination table. + :param local_cols: a list of string column names in the + source table. + :param remote_cols: a list of string column names in the + remote table. + :param onupdate: Optional string. If set, emit ON UPDATE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param ondelete: Optional string. If set, emit ON DELETE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param deferrable: optional bool. If set, emit DEFERRABLE or NOT + DEFERRABLE when issuing DDL for this constraint. + :param source_schema: Optional schema name of the source table. + :param referent_schema: Optional schema name of the destination table. + + """ # noqa: E501 + ... + + def create_index( + self, + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, Function[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "create index" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_index("ik_test", "t1", ["foo", "bar"]) + + Functional indexes can be produced by using the + :func:`sqlalchemy.sql.expression.text` construct:: + + from alembic import op + from sqlalchemy import text + + op.create_index("ik_test", "t1", [text("lower(foo)")]) + + :param index_name: name of the index. + :param table_name: name of the owning table. + :param columns: a list consisting of string column names and/or + :func:`~sqlalchemy.sql.expression.text` constructs. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param unique: If True, create a unique index. + + :param quote: Force quoting of this column's name on or off, + corresponding to ``True`` or ``False``. When left at its default + of ``None``, the column identifier will be quoted according to + whether the name is case sensitive (identifiers with at least one + upper case character are treated as case sensitive), or if it's a + reserved word. This flag is only needed to force quoting of a + reserved word which is not known by the SQLAlchemy dialect. + + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ # noqa: E501 + ... + + def create_primary_key( + self, + constraint_name: Optional[str], + table_name: str, + columns: List[str], + *, + schema: Optional[str] = None, + ) -> None: + """Issue a "create primary key" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_primary_key("pk_my_table", "my_table", ["id", "version"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.PrimaryKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions` + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the target table. + :param columns: a list of string column names to be applied to the + primary key constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def create_table( + self, + table_name: str, + *columns: SchemaItem, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> Table: + r"""Issue a "create table" instruction using the current migration + context. + + This directive receives an argument list similar to that of the + traditional :class:`sqlalchemy.schema.Table` construct, but without the + metadata:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + Note that :meth:`.create_table` accepts + :class:`~sqlalchemy.schema.Column` + constructs directly from the SQLAlchemy library. In particular, + default values to be created on the database side are + specified using the ``server_default`` parameter, and not + ``default`` which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the "timestamp" column + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + The function also returns a newly created + :class:`~sqlalchemy.schema.Table` object, corresponding to the table + specification given, which is suitable for + immediate SQL operations, in particular + :meth:`.Operations.bulk_insert`:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + account_table = op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + op.bulk_insert( + account_table, + [ + {"name": "A1", "description": "account 1"}, + {"name": "A2", "description": "account 2"}, + ], + ) + + :param table_name: Name of the table + :param \*columns: collection of :class:`~sqlalchemy.schema.Column` + objects within + the table, as well as optional :class:`~sqlalchemy.schema.Constraint` + objects + and :class:`~.sqlalchemy.schema.Index` objects. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + :return: the :class:`~sqlalchemy.schema.Table` object corresponding + to the parameters given. + + """ # noqa: E501 + ... + + def create_table_comment( + self, + table_name: str, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table. + + :param table_name: string name of the target table. + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + .. seealso:: + + :meth:`.Operations.drop_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ # noqa: E501 + ... + + def create_unique_constraint( + self, + constraint_name: Optional[str], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> Any: + """Issue a "create unique constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + op.create_unique_constraint("uq_user_name", "user", ["name"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.UniqueConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param name: Name of the unique constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param columns: a list of string column names in the + source table. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def drop_column( + self, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "drop column" instruction using the current + migration context. + + e.g.:: + + drop_column("organization", "account_id") + + :param table_name: name of table + :param column_name: name of column + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param mssql_drop_check: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the CHECK constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.check_constraints, + then exec's a separate DROP CONSTRAINT for that constraint. + :param mssql_drop_default: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the DEFAULT constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.default_constraints, + then exec's a separate DROP CONSTRAINT for that default. + :param mssql_drop_foreign_key: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop a single FOREIGN KEY constraint on the column using a + SQL-script-compatible + block that selects into a @variable from + sys.foreign_keys/sys.foreign_key_columns, + then exec's a separate DROP CONSTRAINT for that default. Only + works if the column has exactly one FK constraint which refers to + it, at the moment. + + """ # noqa: E501 + ... + + def drop_constraint( + self, + constraint_name: str, + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, + ) -> None: + r"""Drop a constraint of the given name, typically via DROP CONSTRAINT. + + :param constraint_name: name of the constraint. + :param table_name: table name. + :param type\_: optional, required on MySQL. can be + 'foreignkey', 'primary', 'unique', or 'check'. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def drop_index( + self, + index_name: str, + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop index" instruction using the current + migration context. + + e.g.:: + + drop_index("accounts") + + :param index_name: name of the index. + :param table_name: name of the owning table. Some + backends such as Microsoft SQL Server require this. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + :param if_exists: If True, adds IF EXISTS operator when + dropping the index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ # noqa: E501 + ... + + def drop_table( + self, + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop table" instruction using the current + migration context. + + + e.g.:: + + drop_table("accounts") + + :param table_name: Name of the table + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + """ # noqa: E501 + ... + + def drop_table_comment( + self, + table_name: str, + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table. + + :param table_name: string name of the target table. + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + .. seealso:: + + :meth:`.Operations.create_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ # noqa: E501 + ... + + def execute( + self, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + r"""Execute the given SQL using the current migration context. + + The given SQL can be a plain string, e.g.:: + + op.execute("INSERT INTO table (foo) VALUES ('some value')") + + Or it can be any kind of Core SQL Expression construct, such as + below where we use an update construct:: + + from sqlalchemy.sql import table, column + from sqlalchemy import String + from alembic import op + + account = table("account", column("name", String)) + op.execute( + account.update() + .where(account.c.name == op.inline_literal("account 1")) + .values({"name": op.inline_literal("account 2")}) + ) + + Above, we made use of the SQLAlchemy + :func:`sqlalchemy.sql.expression.table` and + :func:`sqlalchemy.sql.expression.column` constructs to make a brief, + ad-hoc table construct just for our UPDATE statement. A full + :class:`~sqlalchemy.schema.Table` construct of course works perfectly + fine as well, though note it's a recommended practice to at least + ensure the definition of a table is self-contained within the migration + script, rather than imported from a module that may break compatibility + with older migrations. + + In a SQL script context, the statement is emitted directly to the + output stream. There is *no* return result, however, as this + function is oriented towards generating a change script + that can run in "offline" mode. Additionally, parameterized + statements are discouraged here, as they *will not work* in offline + mode. Above, we use :meth:`.inline_literal` where parameters are + to be used. + + For full interaction with a connected database where parameters can + also be used normally, use the "bind" available from the context:: + + from alembic import op + + connection = op.get_bind() + + connection.execute( + account.update() + .where(account.c.name == "account 1") + .values({"name": "account 2"}) + ) + + Additionally, when passing the statement as a plain string, it is first + coerced into a :func:`sqlalchemy.sql.expression.text` construct + before being passed along. In the less likely case that the + literal SQL string contains a colon, it must be escaped with a + backslash, as:: + + op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')") + + + :param sqltext: Any legal SQLAlchemy expression, including: + + * a string + * a :func:`sqlalchemy.sql.expression.text` construct. + * a :func:`sqlalchemy.sql.expression.insert` construct. + * a :func:`sqlalchemy.sql.expression.update` construct. + * a :func:`sqlalchemy.sql.expression.delete` construct. + * Any "executable" described in SQLAlchemy Core documentation, + noting that no result set is returned. + + .. note:: when passing a plain string, the statement is coerced into + a :func:`sqlalchemy.sql.expression.text` construct. This construct + considers symbols with colons, e.g. ``:foo`` to be bound parameters. + To avoid this, ensure that colon symbols are escaped, e.g. + ``\:foo``. + + :param execution_options: Optional dictionary of + execution options, will be passed to + :meth:`sqlalchemy.engine.Connection.execution_options`. + """ # noqa: E501 + ... + + def rename_table( + self, + old_table_name: str, + new_table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + """Emit an ALTER TABLE to rename a table. + + :param old_table_name: old name. + :param new_table_name: new name. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + # END STUB FUNCTIONS: op_cls + + +class BatchOperations(AbstractOperations): + """Modifies the interface :class:`.Operations` for batch mode. + + This basically omits the ``table_name`` and ``schema`` parameters + from associated methods, as these are a given when running under batch + mode. + + .. seealso:: + + :meth:`.Operations.batch_alter_table` + + Note that as of 0.8, most of the methods on this class are produced + dynamically using the :meth:`.Operations.register_operation` + method. + + """ + + impl: BatchOperationsImpl + + def _noop(self, operation: Any) -> NoReturn: + raise NotImplementedError( + "The %s method does not apply to a batch table alter operation." + % operation + ) + + if TYPE_CHECKING: + # START STUB FUNCTIONS: batch_op + # ### the following stubs are generated by tools/write_pyi.py ### + # ### do not edit ### + + def add_column( + self, + column: Column[Any], + *, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + ) -> None: + """Issue an "add column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.add_column` + + """ # noqa: E501 + ... + + def alter_column( + self, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Union[str, Literal[False], None] = False, + server_default: Any = False, + new_column_name: Optional[str] = None, + type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_type: Union[ + TypeEngine[Any], Type[TypeEngine[Any]], None + ] = None, + existing_server_default: Union[ + str, bool, Identity, Computed, None + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue an "alter column" instruction using the current + batch migration context. + + Parameters are the same as that of :meth:`.Operations.alter_column`, + as well as the following option(s): + + :param insert_before: String name of an existing column which this + column should be placed before, when creating the new table. + + :param insert_after: String name of an existing column which this + column should be placed after, when creating the new table. If + both :paramref:`.BatchOperations.alter_column.insert_before` + and :paramref:`.BatchOperations.alter_column.insert_after` are + omitted, the column is inserted after the last existing column + in the table. + + .. seealso:: + + :meth:`.Operations.alter_column` + + + """ # noqa: E501 + ... + + def create_check_constraint( + self, + constraint_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_check_constraint` + + """ # noqa: E501 + ... + + def create_exclude_constraint( + self, constraint_name: str, *elements: Any, **kw: Any + ) -> Optional[Table]: + """Issue a "create exclude constraint" instruction using the + current batch migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + .. seealso:: + + :meth:`.Operations.create_exclude_constraint` + + """ # noqa: E501 + ... + + def create_foreign_key( + self, + constraint_name: Optional[str], + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + referent_schema: Optional[str] = None, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``source_schema`` + arguments from the call. + + e.g.:: + + with batch_alter_table("address") as batch_op: + batch_op.create_foreign_key( + "fk_user_address", + "user", + ["user_id"], + ["id"], + ) + + .. seealso:: + + :meth:`.Operations.create_foreign_key` + + """ # noqa: E501 + ... + + def create_index( + self, index_name: str, columns: List[str], **kw: Any + ) -> None: + """Issue a "create index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.create_index` + + """ # noqa: E501 + ... + + def create_primary_key( + self, constraint_name: Optional[str], columns: List[str] + ) -> None: + """Issue a "create primary key" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_primary_key` + + """ # noqa: E501 + ... + + def create_table_comment( + self, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table + using the current batch migration context. + + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + """ # noqa: E501 + ... + + def create_unique_constraint( + self, constraint_name: str, columns: Sequence[str], **kw: Any + ) -> Any: + """Issue a "create unique constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_unique_constraint` + + """ # noqa: E501 + ... + + def drop_column(self, column_name: str, **kw: Any) -> None: + """Issue a "drop column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_column` + + """ # noqa: E501 + ... + + def drop_constraint( + self, constraint_name: str, type_: Optional[str] = None + ) -> None: + """Issue a "drop constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.drop_constraint` + + """ # noqa: E501 + ... + + def drop_index(self, index_name: str, **kw: Any) -> None: + """Issue a "drop index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_index` + + """ # noqa: E501 + ... + + def drop_table_comment( + self, *, existing_comment: Optional[str] = None + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table using the current + batch operations context. + + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + """ # noqa: E501 + ... + + def execute( + self, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + """Execute the given SQL using the current migration context. + + .. seealso:: + + :meth:`.Operations.execute` + + """ # noqa: E501 + ... + + # END STUB FUNCTIONS: batch_op diff --git a/venv/lib/python3.8/site-packages/alembic/operations/batch.py b/venv/lib/python3.8/site-packages/alembic/operations/batch.py new file mode 100644 index 000000000..fd7ab9903 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/operations/batch.py @@ -0,0 +1,717 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import CheckConstraint +from sqlalchemy import Column +from sqlalchemy import ForeignKeyConstraint +from sqlalchemy import Index +from sqlalchemy import MetaData +from sqlalchemy import PrimaryKeyConstraint +from sqlalchemy import schema as sql_schema +from sqlalchemy import Table +from sqlalchemy import types as sqltypes +from sqlalchemy.sql.schema import SchemaEventTarget +from sqlalchemy.util import OrderedDict +from sqlalchemy.util import topological + +from ..util import exc +from ..util.sqla_compat import _columns_for_constraint +from ..util.sqla_compat import _copy +from ..util.sqla_compat import _copy_expression +from ..util.sqla_compat import _ensure_scope_for_ddl +from ..util.sqla_compat import _fk_is_self_referential +from ..util.sqla_compat import _idx_table_bound_expressions +from ..util.sqla_compat import _insert_inline +from ..util.sqla_compat import _is_type_bound +from ..util.sqla_compat import _remove_column_from_collection +from ..util.sqla_compat import _resolve_for_variant +from ..util.sqla_compat import _select +from ..util.sqla_compat import constraint_name_defined +from ..util.sqla_compat import constraint_name_string + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.engine import Dialect + from sqlalchemy.sql.elements import ColumnClause + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.functions import Function + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.type_api import TypeEngine + + from ..ddl.impl import DefaultImpl + + +class BatchOperationsImpl: + def __init__( + self, + operations, + table_name, + schema, + recreate, + copy_from, + table_args, + table_kwargs, + reflect_args, + reflect_kwargs, + naming_convention, + partial_reordering, + ): + self.operations = operations + self.table_name = table_name + self.schema = schema + if recreate not in ("auto", "always", "never"): + raise ValueError( + "recreate may be one of 'auto', 'always', or 'never'." + ) + self.recreate = recreate + self.copy_from = copy_from + self.table_args = table_args + self.table_kwargs = dict(table_kwargs) + self.reflect_args = reflect_args + self.reflect_kwargs = dict(reflect_kwargs) + self.reflect_kwargs.setdefault( + "listeners", list(self.reflect_kwargs.get("listeners", ())) + ) + self.reflect_kwargs["listeners"].append( + ("column_reflect", operations.impl.autogen_column_reflect) + ) + self.naming_convention = naming_convention + self.partial_reordering = partial_reordering + self.batch = [] + + @property + def dialect(self) -> Dialect: + return self.operations.impl.dialect + + @property + def impl(self) -> DefaultImpl: + return self.operations.impl + + def _should_recreate(self) -> bool: + if self.recreate == "auto": + return self.operations.impl.requires_recreate_in_batch(self) + elif self.recreate == "always": + return True + else: + return False + + def flush(self) -> None: + should_recreate = self._should_recreate() + + with _ensure_scope_for_ddl(self.impl.connection): + if not should_recreate: + for opname, arg, kw in self.batch: + fn = getattr(self.operations.impl, opname) + fn(*arg, **kw) + else: + if self.naming_convention: + m1 = MetaData(naming_convention=self.naming_convention) + else: + m1 = MetaData() + + if self.copy_from is not None: + existing_table = self.copy_from + reflected = False + else: + if self.operations.migration_context.as_sql: + raise exc.CommandError( + f"This operation cannot proceed in --sql mode; " + f"batch mode with dialect " + f"{self.operations.migration_context.dialect.name} " # noqa: E501 + f"requires a live database connection with which " + f'to reflect the table "{self.table_name}". ' + f"To generate a batch SQL migration script using " + "table " + '"move and copy", a complete Table object ' + f'should be passed to the "copy_from" argument ' + "of the batch_alter_table() method so that table " + "reflection can be skipped." + ) + + existing_table = Table( + self.table_name, + m1, + schema=self.schema, + autoload_with=self.operations.get_bind(), + *self.reflect_args, + **self.reflect_kwargs, + ) + reflected = True + + batch_impl = ApplyBatchImpl( + self.impl, + existing_table, + self.table_args, + self.table_kwargs, + reflected, + partial_reordering=self.partial_reordering, + ) + for opname, arg, kw in self.batch: + fn = getattr(batch_impl, opname) + fn(*arg, **kw) + + batch_impl._create(self.impl) + + def alter_column(self, *arg, **kw) -> None: + self.batch.append(("alter_column", arg, kw)) + + def add_column(self, *arg, **kw) -> None: + if ( + "insert_before" in kw or "insert_after" in kw + ) and not self._should_recreate(): + raise exc.CommandError( + "Can't specify insert_before or insert_after when using " + "ALTER; please specify recreate='always'" + ) + self.batch.append(("add_column", arg, kw)) + + def drop_column(self, *arg, **kw) -> None: + self.batch.append(("drop_column", arg, kw)) + + def add_constraint(self, const: Constraint) -> None: + self.batch.append(("add_constraint", (const,), {})) + + def drop_constraint(self, const: Constraint) -> None: + self.batch.append(("drop_constraint", (const,), {})) + + def rename_table(self, *arg, **kw): + self.batch.append(("rename_table", arg, kw)) + + def create_index(self, idx: Index, **kw: Any) -> None: + self.batch.append(("create_index", (idx,), kw)) + + def drop_index(self, idx: Index, **kw: Any) -> None: + self.batch.append(("drop_index", (idx,), kw)) + + def create_table_comment(self, table): + self.batch.append(("create_table_comment", (table,), {})) + + def drop_table_comment(self, table): + self.batch.append(("drop_table_comment", (table,), {})) + + def create_table(self, table): + raise NotImplementedError("Can't create table in batch mode") + + def drop_table(self, table): + raise NotImplementedError("Can't drop table in batch mode") + + def create_column_comment(self, column): + self.batch.append(("create_column_comment", (column,), {})) + + +class ApplyBatchImpl: + def __init__( + self, + impl: DefaultImpl, + table: Table, + table_args: tuple, + table_kwargs: Dict[str, Any], + reflected: bool, + partial_reordering: tuple = (), + ) -> None: + self.impl = impl + self.table = table # this is a Table object + self.table_args = table_args + self.table_kwargs = table_kwargs + self.temp_table_name = self._calc_temp_name(table.name) + self.new_table: Optional[Table] = None + + self.partial_reordering = partial_reordering # tuple of tuples + self.add_col_ordering: Tuple[ + Tuple[str, str], ... + ] = () # tuple of tuples + + self.column_transfers = OrderedDict( + (c.name, {"expr": c}) for c in self.table.c + ) + self.existing_ordering = list(self.column_transfers) + + self.reflected = reflected + self._grab_table_elements() + + @classmethod + def _calc_temp_name(cls, tablename: Union[quoted_name, str]) -> str: + return ("_alembic_tmp_%s" % tablename)[0:50] + + def _grab_table_elements(self) -> None: + schema = self.table.schema + self.columns: Dict[str, Column[Any]] = OrderedDict() + for c in self.table.c: + c_copy = _copy(c, schema=schema) + c_copy.unique = c_copy.index = False + # ensure that the type object was copied, + # as we may need to modify it in-place + if isinstance(c.type, SchemaEventTarget): + assert c_copy.type is not c.type + self.columns[c.name] = c_copy + self.named_constraints: Dict[str, Constraint] = {} + self.unnamed_constraints = [] + self.col_named_constraints = {} + self.indexes: Dict[str, Index] = {} + self.new_indexes: Dict[str, Index] = {} + + for const in self.table.constraints: + if _is_type_bound(const): + continue + elif ( + self.reflected + and isinstance(const, CheckConstraint) + and not const.name + ): + # TODO: we are skipping unnamed reflected CheckConstraint + # because + # we have no way to determine _is_type_bound() for these. + pass + elif constraint_name_string(const.name): + self.named_constraints[const.name] = const + else: + self.unnamed_constraints.append(const) + + if not self.reflected: + for col in self.table.c: + for const in col.constraints: + if const.name: + self.col_named_constraints[const.name] = (col, const) + + for idx in self.table.indexes: + self.indexes[idx.name] = idx # type: ignore[index] + + for k in self.table.kwargs: + self.table_kwargs.setdefault(k, self.table.kwargs[k]) + + def _adjust_self_columns_for_partial_reordering(self) -> None: + pairs = set() + + col_by_idx = list(self.columns) + + if self.partial_reordering: + for tuple_ in self.partial_reordering: + for index, elem in enumerate(tuple_): + if index > 0: + pairs.add((tuple_[index - 1], elem)) + else: + for index, elem in enumerate(self.existing_ordering): + if index > 0: + pairs.add((col_by_idx[index - 1], elem)) + + pairs.update(self.add_col_ordering) + + # this can happen if some columns were dropped and not removed + # from existing_ordering. this should be prevented already, but + # conservatively making sure this didn't happen + pairs_list = [p for p in pairs if p[0] != p[1]] + + sorted_ = list( + topological.sort(pairs_list, col_by_idx, deterministic_order=True) + ) + self.columns = OrderedDict((k, self.columns[k]) for k in sorted_) + self.column_transfers = OrderedDict( + (k, self.column_transfers[k]) for k in sorted_ + ) + + def _transfer_elements_to_new_table(self) -> None: + assert self.new_table is None, "Can only create new table once" + + m = MetaData() + schema = self.table.schema + + if self.partial_reordering or self.add_col_ordering: + self._adjust_self_columns_for_partial_reordering() + + self.new_table = new_table = Table( + self.temp_table_name, + m, + *(list(self.columns.values()) + list(self.table_args)), + schema=schema, + **self.table_kwargs, + ) + + for const in ( + list(self.named_constraints.values()) + self.unnamed_constraints + ): + const_columns = {c.key for c in _columns_for_constraint(const)} + + if not const_columns.issubset(self.column_transfers): + continue + + const_copy: Constraint + if isinstance(const, ForeignKeyConstraint): + if _fk_is_self_referential(const): + # for self-referential constraint, refer to the + # *original* table name, and not _alembic_batch_temp. + # This is consistent with how we're handling + # FK constraints from other tables; we assume SQLite + # no foreign keys just keeps the names unchanged, so + # when we rename back, they match again. + const_copy = _copy( + const, schema=schema, target_table=self.table + ) + else: + # "target_table" for ForeignKeyConstraint.copy() is + # only used if the FK is detected as being + # self-referential, which we are handling above. + const_copy = _copy(const, schema=schema) + else: + const_copy = _copy( + const, schema=schema, target_table=new_table + ) + if isinstance(const, ForeignKeyConstraint): + self._setup_referent(m, const) + new_table.append_constraint(const_copy) + + def _gather_indexes_from_both_tables(self) -> List[Index]: + assert self.new_table is not None + idx: List[Index] = [] + + for idx_existing in self.indexes.values(): + # this is a lift-and-move from Table.to_metadata + + if idx_existing._column_flag: + continue + + idx_copy = Index( + idx_existing.name, + unique=idx_existing.unique, + *[ + _copy_expression(expr, self.new_table) + for expr in _idx_table_bound_expressions(idx_existing) + ], + _table=self.new_table, + **idx_existing.kwargs, + ) + idx.append(idx_copy) + + for index in self.new_indexes.values(): + idx.append( + Index( + index.name, + unique=index.unique, + *[self.new_table.c[col] for col in index.columns.keys()], + **index.kwargs, + ) + ) + return idx + + def _setup_referent( + self, metadata: MetaData, constraint: ForeignKeyConstraint + ) -> None: + spec = constraint.elements[0]._get_colspec() + parts = spec.split(".") + tname = parts[-2] + if len(parts) == 3: + referent_schema = parts[0] + else: + referent_schema = None + + if tname != self.temp_table_name: + key = sql_schema._get_table_key(tname, referent_schema) + + def colspec(elem: Any): + return elem._get_colspec() + + if key in metadata.tables: + t = metadata.tables[key] + for elem in constraint.elements: + colname = colspec(elem).split(".")[-1] + if colname not in t.c: + t.append_column(Column(colname, sqltypes.NULLTYPE)) + else: + Table( + tname, + metadata, + *[ + Column(n, sqltypes.NULLTYPE) + for n in [ + colspec(elem).split(".")[-1] + for elem in constraint.elements + ] + ], + schema=referent_schema, + ) + + def _create(self, op_impl: DefaultImpl) -> None: + self._transfer_elements_to_new_table() + + op_impl.prep_table_for_batch(self, self.table) + assert self.new_table is not None + op_impl.create_table(self.new_table) + + try: + op_impl._exec( + _insert_inline(self.new_table).from_select( + list( + k + for k, transfer in self.column_transfers.items() + if "expr" in transfer + ), + _select( + *[ + transfer["expr"] + for transfer in self.column_transfers.values() + if "expr" in transfer + ] + ), + ) + ) + op_impl.drop_table(self.table) + except: + op_impl.drop_table(self.new_table) + raise + else: + op_impl.rename_table( + self.temp_table_name, self.table.name, schema=self.table.schema + ) + self.new_table.name = self.table.name + try: + for idx in self._gather_indexes_from_both_tables(): + op_impl.create_index(idx) + finally: + self.new_table.name = self.temp_table_name + + def alter_column( + self, + table_name: str, + column_name: str, + nullable: Optional[bool] = None, + server_default: Optional[Union[Function[Any], str, bool]] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + autoincrement: Optional[Union[bool, Literal["auto"]]] = None, + comment: Union[str, Literal[False]] = False, + **kw, + ) -> None: + existing = self.columns[column_name] + existing_transfer: Dict[str, Any] = self.column_transfers[column_name] + if name is not None and name != column_name: + # note that we don't change '.key' - we keep referring + # to the renamed column by its old key in _create(). neat! + existing.name = name + existing_transfer["name"] = name + + existing_type = kw.get("existing_type", None) + if existing_type: + resolved_existing_type = _resolve_for_variant( + kw["existing_type"], self.impl.dialect + ) + + # pop named constraints for Boolean/Enum for rename + if ( + isinstance(resolved_existing_type, SchemaEventTarget) + and resolved_existing_type.name # type:ignore[attr-defined] # noqa E501 + ): + self.named_constraints.pop( + resolved_existing_type.name, # type:ignore[attr-defined] # noqa E501 + None, + ) + + if type_ is not None: + type_ = sqltypes.to_instance(type_) + # old type is being discarded so turn off eventing + # rules. Alternatively we can + # erase the events set up by this type, but this is simpler. + # we also ignore the drop_constraint that will come here from + # Operations.implementation_for(alter_column) + + if isinstance(existing.type, SchemaEventTarget): + existing.type._create_events = ( # type:ignore[attr-defined] + existing.type.create_constraint # type:ignore[attr-defined] # noqa + ) = False + + self.impl.cast_for_batch_migrate( + existing, existing_transfer, type_ + ) + + existing.type = type_ + + # we *dont* however set events for the new type, because + # alter_column is invoked from + # Operations.implementation_for(alter_column) which already + # will emit an add_constraint() + + if nullable is not None: + existing.nullable = nullable + if server_default is not False: + if server_default is None: + existing.server_default = None + else: + sql_schema.DefaultClause( + server_default # type: ignore[arg-type] + )._set_parent(existing) + if autoincrement is not None: + existing.autoincrement = bool(autoincrement) + + if comment is not False: + existing.comment = comment + + def _setup_dependencies_for_add_column( + self, + colname: str, + insert_before: Optional[str], + insert_after: Optional[str], + ) -> None: + index_cols = self.existing_ordering + col_indexes = {name: i for i, name in enumerate(index_cols)} + + if not self.partial_reordering: + if insert_after: + if not insert_before: + if insert_after in col_indexes: + # insert after an existing column + idx = col_indexes[insert_after] + 1 + if idx < len(index_cols): + insert_before = index_cols[idx] + else: + # insert after a column that is also new + insert_before = dict(self.add_col_ordering)[ + insert_after + ] + if insert_before: + if not insert_after: + if insert_before in col_indexes: + # insert before an existing column + idx = col_indexes[insert_before] - 1 + if idx >= 0: + insert_after = index_cols[idx] + else: + # insert before a column that is also new + insert_after = { + b: a for a, b in self.add_col_ordering + }[insert_before] + + if insert_before: + self.add_col_ordering += ((colname, insert_before),) + if insert_after: + self.add_col_ordering += ((insert_after, colname),) + + if ( + not self.partial_reordering + and not insert_before + and not insert_after + and col_indexes + ): + self.add_col_ordering += ((index_cols[-1], colname),) + + def add_column( + self, + table_name: str, + column: Column[Any], + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + **kw, + ) -> None: + self._setup_dependencies_for_add_column( + column.name, insert_before, insert_after + ) + # we copy the column because operations.add_column() + # gives us a Column that is part of a Table already. + self.columns[column.name] = _copy(column, schema=self.table.schema) + self.column_transfers[column.name] = {} + + def drop_column( + self, + table_name: str, + column: Union[ColumnClause[Any], Column[Any]], + **kw, + ) -> None: + if column.name in self.table.primary_key.columns: + _remove_column_from_collection( + self.table.primary_key.columns, column + ) + del self.columns[column.name] + del self.column_transfers[column.name] + self.existing_ordering.remove(column.name) + + # pop named constraints for Boolean/Enum for rename + if ( + "existing_type" in kw + and isinstance(kw["existing_type"], SchemaEventTarget) + and kw["existing_type"].name # type:ignore[attr-defined] + ): + self.named_constraints.pop( + kw["existing_type"].name, None # type:ignore[attr-defined] + ) + + def create_column_comment(self, column): + """the batch table creation function will issue create_column_comment + on the real "impl" as part of the create table process. + + That is, the Column object will have the comment on it already, + so when it is received by add_column() it will be a normal part of + the CREATE TABLE and doesn't need an extra step here. + + """ + + def create_table_comment(self, table): + """the batch table creation function will issue create_table_comment + on the real "impl" as part of the create table process. + + """ + + def drop_table_comment(self, table): + """the batch table creation function will issue drop_table_comment + on the real "impl" as part of the create table process. + + """ + + def add_constraint(self, const: Constraint) -> None: + if not constraint_name_defined(const.name): + raise ValueError("Constraint must have a name") + if isinstance(const, sql_schema.PrimaryKeyConstraint): + if self.table.primary_key in self.unnamed_constraints: + self.unnamed_constraints.remove(self.table.primary_key) + + if constraint_name_string(const.name): + self.named_constraints[const.name] = const + else: + self.unnamed_constraints.append(const) + + def drop_constraint(self, const: Constraint) -> None: + if not const.name: + raise ValueError("Constraint must have a name") + try: + if const.name in self.col_named_constraints: + col, const = self.col_named_constraints.pop(const.name) + + for col_const in list(self.columns[col.name].constraints): + if col_const.name == const.name: + self.columns[col.name].constraints.remove(col_const) + elif constraint_name_string(const.name): + const = self.named_constraints.pop(const.name) + elif const in self.unnamed_constraints: + self.unnamed_constraints.remove(const) + + except KeyError: + if _is_type_bound(const): + # type-bound constraints are only included in the new + # table via their type object in any case, so ignore the + # drop_constraint() that comes here via the + # Operations.implementation_for(alter_column) + return + raise ValueError("No such constraint: '%s'" % const.name) + else: + if isinstance(const, PrimaryKeyConstraint): + for col in const.columns: + self.columns[col.name].primary_key = False + + def create_index(self, idx: Index) -> None: + self.new_indexes[idx.name] = idx # type: ignore[index] + + def drop_index(self, idx: Index) -> None: + try: + del self.indexes[idx.name] # type: ignore[arg-type] + except KeyError: + raise ValueError("No such index: '%s'" % idx.name) + + def rename_table(self, *arg, **kw): + raise NotImplementedError("TODO") diff --git a/venv/lib/python3.8/site-packages/alembic/operations/ops.py b/venv/lib/python3.8/site-packages/alembic/operations/ops.py new file mode 100644 index 000000000..60b856a8f --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/operations/ops.py @@ -0,0 +1,2800 @@ +from __future__ import annotations + +from abc import abstractmethod +import re +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import FrozenSet +from typing import Iterator +from typing import List +from typing import MutableMapping +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy.types import NULLTYPE + +from . import schemaobj +from .base import BatchOperations +from .base import Operations +from .. import util +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.sql import Executable + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import conv + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.functions import Function + from sqlalchemy.sql.schema import CheckConstraint + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Computed + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import Identity + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import PrimaryKeyConstraint + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.selectable import TableClause + from sqlalchemy.sql.type_api import TypeEngine + + from ..autogenerate.rewriter import Rewriter + from ..runtime.migration import MigrationContext + from ..script.revision import _RevIdType + +_T = TypeVar("_T", bound=Any) +_AC = TypeVar("_AC", bound="AddConstraintOp") + + +class MigrateOperation: + """base class for migration command and organization objects. + + This system is part of the operation extensibility API. + + .. seealso:: + + :ref:`operation_objects` + + :ref:`operation_plugins` + + :ref:`customizing_revision` + + """ + + @util.memoized_property + def info(self) -> Dict[Any, Any]: + """A dictionary that may be used to store arbitrary information + along with this :class:`.MigrateOperation` object. + + """ + return {} + + _mutations: FrozenSet[Rewriter] = frozenset() + + def reverse(self) -> MigrateOperation: + raise NotImplementedError + + def to_diff_tuple(self) -> Tuple[Any, ...]: + raise NotImplementedError + + +class AddConstraintOp(MigrateOperation): + """Represent an add constraint operation.""" + + add_constraint_ops = util.Dispatcher() + + @property + def constraint_type(self) -> str: + raise NotImplementedError() + + @classmethod + def register_add_constraint( + cls, type_: str + ) -> Callable[[Type[_AC]], Type[_AC]]: + def go(klass: Type[_AC]) -> Type[_AC]: + cls.add_constraint_ops.dispatch_for(type_)(klass.from_constraint) + return klass + + return go + + @classmethod + def from_constraint(cls, constraint: Constraint) -> AddConstraintOp: + return cls.add_constraint_ops.dispatch(constraint.__visit_name__)( # type: ignore[no-any-return] # noqa: E501 + constraint + ) + + @abstractmethod + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> Constraint: + pass + + def reverse(self) -> DropConstraintOp: + return DropConstraintOp.from_constraint(self.to_constraint()) + + def to_diff_tuple(self) -> Tuple[str, Constraint]: + return ("add_constraint", self.to_constraint()) + + +@Operations.register_operation("drop_constraint") +@BatchOperations.register_operation("drop_constraint", "batch_drop_constraint") +class DropConstraintOp(MigrateOperation): + """Represent a drop constraint operation.""" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, + _reverse: Optional[AddConstraintOp] = None, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.constraint_type = type_ + self.schema = schema + self._reverse = _reverse + + def reverse(self) -> AddConstraintOp: + return AddConstraintOp.from_constraint(self.to_constraint()) + + def to_diff_tuple( + self, + ) -> Tuple[str, SchemaItem]: + if self.constraint_type == "foreignkey": + return ("remove_fk", self.to_constraint()) + else: + return ("remove_constraint", self.to_constraint()) + + @classmethod + def from_constraint(cls, constraint: Constraint) -> DropConstraintOp: + types = { + "unique_constraint": "unique", + "foreign_key_constraint": "foreignkey", + "primary_key_constraint": "primary", + "check_constraint": "check", + "column_check_constraint": "check", + "table_or_column_check_constraint": "check", + } + + constraint_table = sqla_compat._table_for_constraint(constraint) + return cls( + sqla_compat.constraint_name_or_none(constraint.name), + constraint_table.name, + schema=constraint_table.schema, + type_=types.get(constraint.__visit_name__), + _reverse=AddConstraintOp.from_constraint(constraint), + ) + + def to_constraint(self) -> Constraint: + if self._reverse is not None: + constraint = self._reverse.to_constraint() + constraint.name = self.constraint_name + constraint_table = sqla_compat._table_for_constraint(constraint) + constraint_table.name = self.table_name + constraint_table.schema = self.schema + + return constraint + else: + raise ValueError( + "constraint cannot be produced; " + "original constraint is not present" + ) + + @classmethod + def drop_constraint( + cls, + operations: Operations, + constraint_name: str, + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, + ) -> None: + r"""Drop a constraint of the given name, typically via DROP CONSTRAINT. + + :param constraint_name: name of the constraint. + :param table_name: table name. + :param type\_: optional, required on MySQL. can be + 'foreignkey', 'primary', 'unique', or 'check'. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + + op = cls(constraint_name, table_name, type_=type_, schema=schema) + return operations.invoke(op) + + @classmethod + def batch_drop_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + type_: Optional[str] = None, + ) -> None: + """Issue a "drop constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.drop_constraint` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + type_=type_, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_primary_key") +@BatchOperations.register_operation( + "create_primary_key", "batch_create_primary_key" +) +@AddConstraintOp.register_add_constraint("primary_key_constraint") +class CreatePrimaryKeyOp(AddConstraintOp): + """Represent a create primary key operation.""" + + constraint_type = "primarykey" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.columns = columns + self.schema = schema + self.kw = kw + + @classmethod + def from_constraint(cls, constraint: Constraint) -> CreatePrimaryKeyOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + pk_constraint = cast("PrimaryKeyConstraint", constraint) + return cls( + sqla_compat.constraint_name_or_none(pk_constraint.name), + constraint_table.name, + pk_constraint.columns.keys(), + schema=constraint_table.schema, + **pk_constraint.dialect_kwargs, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> PrimaryKeyConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.primary_key_constraint( + self.constraint_name, + self.table_name, + self.columns, + schema=self.schema, + **self.kw, + ) + + @classmethod + def create_primary_key( + cls, + operations: Operations, + constraint_name: Optional[str], + table_name: str, + columns: List[str], + *, + schema: Optional[str] = None, + ) -> None: + """Issue a "create primary key" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_primary_key("pk_my_table", "my_table", ["id", "version"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.PrimaryKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions` + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the target table. + :param columns: a list of string column names to be applied to the + primary key constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + op = cls(constraint_name, table_name, columns, schema=schema) + return operations.invoke(op) + + @classmethod + def batch_create_primary_key( + cls, + operations: BatchOperations, + constraint_name: Optional[str], + columns: List[str], + ) -> None: + """Issue a "create primary key" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_primary_key` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + columns, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_unique_constraint") +@BatchOperations.register_operation( + "create_unique_constraint", "batch_create_unique_constraint" +) +@AddConstraintOp.register_add_constraint("unique_constraint") +class CreateUniqueConstraintOp(AddConstraintOp): + """Represent a create unique constraint operation.""" + + constraint_type = "unique" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.columns = columns + self.schema = schema + self.kw = kw + + @classmethod + def from_constraint( + cls, constraint: Constraint + ) -> CreateUniqueConstraintOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + + uq_constraint = cast("UniqueConstraint", constraint) + + kw: Dict[str, Any] = {} + if uq_constraint.deferrable: + kw["deferrable"] = uq_constraint.deferrable + if uq_constraint.initially: + kw["initially"] = uq_constraint.initially + kw.update(uq_constraint.dialect_kwargs) + return cls( + sqla_compat.constraint_name_or_none(uq_constraint.name), + constraint_table.name, + [c.name for c in uq_constraint.columns], + schema=constraint_table.schema, + **kw, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> UniqueConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.unique_constraint( + self.constraint_name, + self.table_name, + self.columns, + schema=self.schema, + **self.kw, + ) + + @classmethod + def create_unique_constraint( + cls, + operations: Operations, + constraint_name: Optional[str], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> Any: + """Issue a "create unique constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + op.create_unique_constraint("uq_user_name", "user", ["name"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.UniqueConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param name: Name of the unique constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param columns: a list of string column names in the + source table. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + + op = cls(constraint_name, table_name, columns, schema=schema, **kw) + return operations.invoke(op) + + @classmethod + def batch_create_unique_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + columns: Sequence[str], + **kw: Any, + ) -> Any: + """Issue a "create unique constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_unique_constraint` + + """ + kw["schema"] = operations.impl.schema + op = cls(constraint_name, operations.impl.table_name, columns, **kw) + return operations.invoke(op) + + +@Operations.register_operation("create_foreign_key") +@BatchOperations.register_operation( + "create_foreign_key", "batch_create_foreign_key" +) +@AddConstraintOp.register_add_constraint("foreign_key_constraint") +class CreateForeignKeyOp(AddConstraintOp): + """Represent a create foreign key constraint operation.""" + + constraint_type = "foreignkey" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.source_table = source_table + self.referent_table = referent_table + self.local_cols = local_cols + self.remote_cols = remote_cols + self.kw = kw + + def to_diff_tuple(self) -> Tuple[str, ForeignKeyConstraint]: + return ("add_fk", self.to_constraint()) + + @classmethod + def from_constraint(cls, constraint: Constraint) -> CreateForeignKeyOp: + fk_constraint = cast("ForeignKeyConstraint", constraint) + kw: Dict[str, Any] = {} + if fk_constraint.onupdate: + kw["onupdate"] = fk_constraint.onupdate + if fk_constraint.ondelete: + kw["ondelete"] = fk_constraint.ondelete + if fk_constraint.initially: + kw["initially"] = fk_constraint.initially + if fk_constraint.deferrable: + kw["deferrable"] = fk_constraint.deferrable + if fk_constraint.use_alter: + kw["use_alter"] = fk_constraint.use_alter + if fk_constraint.match: + kw["match"] = fk_constraint.match + + ( + source_schema, + source_table, + source_columns, + target_schema, + target_table, + target_columns, + onupdate, + ondelete, + deferrable, + initially, + ) = sqla_compat._fk_spec(fk_constraint) + + kw["source_schema"] = source_schema + kw["referent_schema"] = target_schema + kw.update(fk_constraint.dialect_kwargs) + return cls( + sqla_compat.constraint_name_or_none(fk_constraint.name), + source_table, + target_table, + source_columns, + target_columns, + **kw, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> ForeignKeyConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.foreign_key_constraint( + self.constraint_name, + self.source_table, + self.referent_table, + self.local_cols, + self.remote_cols, + **self.kw, + ) + + @classmethod + def create_foreign_key( + cls, + operations: Operations, + constraint_name: Optional[str], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current migration context. + + e.g.:: + + from alembic import op + + op.create_foreign_key( + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.ForeignKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param source_table: String name of the source table. + :param referent_table: String name of the destination table. + :param local_cols: a list of string column names in the + source table. + :param remote_cols: a list of string column names in the + remote table. + :param onupdate: Optional string. If set, emit ON UPDATE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param ondelete: Optional string. If set, emit ON DELETE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param deferrable: optional bool. If set, emit DEFERRABLE or NOT + DEFERRABLE when issuing DDL for this constraint. + :param source_schema: Optional schema name of the source table. + :param referent_schema: Optional schema name of the destination table. + + """ + + op = cls( + constraint_name, + source_table, + referent_table, + local_cols, + remote_cols, + onupdate=onupdate, + ondelete=ondelete, + deferrable=deferrable, + source_schema=source_schema, + referent_schema=referent_schema, + initially=initially, + match=match, + **dialect_kw, + ) + return operations.invoke(op) + + @classmethod + def batch_create_foreign_key( + cls, + operations: BatchOperations, + constraint_name: Optional[str], + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + referent_schema: Optional[str] = None, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``source_schema`` + arguments from the call. + + e.g.:: + + with batch_alter_table("address") as batch_op: + batch_op.create_foreign_key( + "fk_user_address", + "user", + ["user_id"], + ["id"], + ) + + .. seealso:: + + :meth:`.Operations.create_foreign_key` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + referent_table, + local_cols, + remote_cols, + onupdate=onupdate, + ondelete=ondelete, + deferrable=deferrable, + source_schema=operations.impl.schema, + referent_schema=referent_schema, + initially=initially, + match=match, + **dialect_kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_check_constraint") +@BatchOperations.register_operation( + "create_check_constraint", "batch_create_check_constraint" +) +@AddConstraintOp.register_add_constraint("check_constraint") +@AddConstraintOp.register_add_constraint("table_or_column_check_constraint") +@AddConstraintOp.register_add_constraint("column_check_constraint") +class CreateCheckConstraintOp(AddConstraintOp): + """Represent a create check constraint operation.""" + + constraint_type = "check" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + condition: Union[str, TextClause, ColumnElement[Any]], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.condition = condition + self.schema = schema + self.kw = kw + + @classmethod + def from_constraint( + cls, constraint: Constraint + ) -> CreateCheckConstraintOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + + ck_constraint = cast("CheckConstraint", constraint) + return cls( + sqla_compat.constraint_name_or_none(ck_constraint.name), + constraint_table.name, + cast("ColumnElement[Any]", ck_constraint.sqltext), + schema=constraint_table.schema, + **ck_constraint.dialect_kwargs, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> CheckConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.check_constraint( + self.constraint_name, + self.table_name, + self.condition, + schema=self.schema, + **self.kw, + ) + + @classmethod + def create_check_constraint( + cls, + operations: Operations, + constraint_name: Optional[str], + table_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + from sqlalchemy.sql import column, func + + op.create_check_constraint( + "ck_user_name_len", + "user", + func.len(column("name")) > 5, + ) + + CHECK constraints are usually against a SQL expression, so ad-hoc + table metadata is usually needed. The function will convert the given + arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound + to an anonymous table in order to emit the CREATE statement. + + :param name: Name of the check constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param condition: SQL expression that's the condition of the + constraint. Can be a string or SQLAlchemy expression language + structure. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + op = cls(constraint_name, table_name, condition, schema=schema, **kw) + return operations.invoke(op) + + @classmethod + def batch_create_check_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_check_constraint` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + condition, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_index") +@BatchOperations.register_operation("create_index", "batch_create_index") +class CreateIndexOp(MigrateOperation): + """Represent a create index operation.""" + + def __init__( + self, + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, ColumnElement[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + self.index_name = index_name + self.table_name = table_name + self.columns = columns + self.schema = schema + self.unique = unique + self.if_not_exists = if_not_exists + self.kw = kw + + def reverse(self) -> DropIndexOp: + return DropIndexOp.from_index(self.to_index()) + + def to_diff_tuple(self) -> Tuple[str, Index]: + return ("add_index", self.to_index()) + + @classmethod + def from_index(cls, index: Index) -> CreateIndexOp: + assert index.table is not None + return cls( + index.name, + index.table.name, + index.expressions, + schema=index.table.schema, + unique=index.unique, + **index.kwargs, + ) + + def to_index( + self, migration_context: Optional[MigrationContext] = None + ) -> Index: + schema_obj = schemaobj.SchemaObjects(migration_context) + + idx = schema_obj.index( + self.index_name, + self.table_name, + self.columns, + schema=self.schema, + unique=self.unique, + **self.kw, + ) + return idx + + @classmethod + def create_index( + cls, + operations: Operations, + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, Function[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "create index" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_index("ik_test", "t1", ["foo", "bar"]) + + Functional indexes can be produced by using the + :func:`sqlalchemy.sql.expression.text` construct:: + + from alembic import op + from sqlalchemy import text + + op.create_index("ik_test", "t1", [text("lower(foo)")]) + + :param index_name: name of the index. + :param table_name: name of the owning table. + :param columns: a list consisting of string column names and/or + :func:`~sqlalchemy.sql.expression.text` constructs. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param unique: If True, create a unique index. + + :param quote: Force quoting of this column's name on or off, + corresponding to ``True`` or ``False``. When left at its default + of ``None``, the column identifier will be quoted according to + whether the name is case sensitive (identifiers with at least one + upper case character are treated as case sensitive), or if it's a + reserved word. This flag is only needed to force quoting of a + reserved word which is not known by the SQLAlchemy dialect. + + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + op = cls( + index_name, + table_name, + columns, + schema=schema, + unique=unique, + if_not_exists=if_not_exists, + **kw, + ) + return operations.invoke(op) + + @classmethod + def batch_create_index( + cls, + operations: BatchOperations, + index_name: str, + columns: List[str], + **kw: Any, + ) -> None: + """Issue a "create index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.create_index` + + """ + + op = cls( + index_name, + operations.impl.table_name, + columns, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("drop_index") +@BatchOperations.register_operation("drop_index", "batch_drop_index") +class DropIndexOp(MigrateOperation): + """Represent a drop index operation.""" + + def __init__( + self, + index_name: Union[quoted_name, str, conv], + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + _reverse: Optional[CreateIndexOp] = None, + **kw: Any, + ) -> None: + self.index_name = index_name + self.table_name = table_name + self.schema = schema + self.if_exists = if_exists + self._reverse = _reverse + self.kw = kw + + def to_diff_tuple(self) -> Tuple[str, Index]: + return ("remove_index", self.to_index()) + + def reverse(self) -> CreateIndexOp: + return CreateIndexOp.from_index(self.to_index()) + + @classmethod + def from_index(cls, index: Index) -> DropIndexOp: + assert index.table is not None + return cls( + index.name, # type: ignore[arg-type] + table_name=index.table.name, + schema=index.table.schema, + _reverse=CreateIndexOp.from_index(index), + unique=index.unique, + **index.kwargs, + ) + + def to_index( + self, migration_context: Optional[MigrationContext] = None + ) -> Index: + schema_obj = schemaobj.SchemaObjects(migration_context) + + # need a dummy column name here since SQLAlchemy + # 0.7.6 and further raises on Index with no columns + return schema_obj.index( + self.index_name, + self.table_name, + self._reverse.columns if self._reverse else ["x"], + schema=self.schema, + **self.kw, + ) + + @classmethod + def drop_index( + cls, + operations: Operations, + index_name: str, + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop index" instruction using the current + migration context. + + e.g.:: + + drop_index("accounts") + + :param index_name: name of the index. + :param table_name: name of the owning table. Some + backends such as Microsoft SQL Server require this. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + :param if_exists: If True, adds IF EXISTS operator when + dropping the index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + op = cls( + index_name, + table_name=table_name, + schema=schema, + if_exists=if_exists, + **kw, + ) + return operations.invoke(op) + + @classmethod + def batch_drop_index( + cls, operations: BatchOperations, index_name: str, **kw: Any + ) -> None: + """Issue a "drop index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_index` + + """ + + op = cls( + index_name, + table_name=operations.impl.table_name, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_table") +class CreateTableOp(MigrateOperation): + """Represent a create table operation.""" + + def __init__( + self, + table_name: str, + columns: Sequence[SchemaItem], + *, + schema: Optional[str] = None, + if_not_exists: Optional[bool] = None, + _namespace_metadata: Optional[MetaData] = None, + _constraints_included: bool = False, + **kw: Any, + ) -> None: + self.table_name = table_name + self.columns = columns + self.schema = schema + self.if_not_exists = if_not_exists + self.info = kw.pop("info", {}) + self.comment = kw.pop("comment", None) + self.prefixes = kw.pop("prefixes", None) + self.kw = kw + self._namespace_metadata = _namespace_metadata + self._constraints_included = _constraints_included + + def reverse(self) -> DropTableOp: + return DropTableOp.from_table( + self.to_table(), _namespace_metadata=self._namespace_metadata + ) + + def to_diff_tuple(self) -> Tuple[str, Table]: + return ("add_table", self.to_table()) + + @classmethod + def from_table( + cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None + ) -> CreateTableOp: + if _namespace_metadata is None: + _namespace_metadata = table.metadata + + return cls( + table.name, + list(table.c) + list(table.constraints), + schema=table.schema, + _namespace_metadata=_namespace_metadata, + # given a Table() object, this Table will contain full Index() + # and UniqueConstraint objects already constructed in response to + # each unique=True / index=True flag on a Column. Carry this + # state along so that when we re-convert back into a Table, we + # skip unique=True/index=True so that these constraints are + # not doubled up. see #844 #848 + _constraints_included=True, + comment=table.comment, + info=dict(table.info), + prefixes=list(table._prefixes), + **table.kwargs, + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.table( + self.table_name, + *self.columns, + schema=self.schema, + prefixes=list(self.prefixes) if self.prefixes else [], + comment=self.comment, + info=self.info.copy() if self.info else {}, + _constraints_included=self._constraints_included, + **self.kw, + ) + + @classmethod + def create_table( + cls, + operations: Operations, + table_name: str, + *columns: SchemaItem, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> Table: + r"""Issue a "create table" instruction using the current migration + context. + + This directive receives an argument list similar to that of the + traditional :class:`sqlalchemy.schema.Table` construct, but without the + metadata:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + Note that :meth:`.create_table` accepts + :class:`~sqlalchemy.schema.Column` + constructs directly from the SQLAlchemy library. In particular, + default values to be created on the database side are + specified using the ``server_default`` parameter, and not + ``default`` which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the "timestamp" column + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + The function also returns a newly created + :class:`~sqlalchemy.schema.Table` object, corresponding to the table + specification given, which is suitable for + immediate SQL operations, in particular + :meth:`.Operations.bulk_insert`:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + account_table = op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + op.bulk_insert( + account_table, + [ + {"name": "A1", "description": "account 1"}, + {"name": "A2", "description": "account 2"}, + ], + ) + + :param table_name: Name of the table + :param \*columns: collection of :class:`~sqlalchemy.schema.Column` + objects within + the table, as well as optional :class:`~sqlalchemy.schema.Constraint` + objects + and :class:`~.sqlalchemy.schema.Index` objects. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + :return: the :class:`~sqlalchemy.schema.Table` object corresponding + to the parameters given. + + """ + op = cls(table_name, columns, if_not_exists=if_not_exists, **kw) + return operations.invoke(op) + + +@Operations.register_operation("drop_table") +class DropTableOp(MigrateOperation): + """Represent a drop table operation.""" + + def __init__( + self, + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + table_kw: Optional[MutableMapping[Any, Any]] = None, + _reverse: Optional[CreateTableOp] = None, + ) -> None: + self.table_name = table_name + self.schema = schema + self.if_exists = if_exists + self.table_kw = table_kw or {} + self.comment = self.table_kw.pop("comment", None) + self.info = self.table_kw.pop("info", None) + self.prefixes = self.table_kw.pop("prefixes", None) + self._reverse = _reverse + + def to_diff_tuple(self) -> Tuple[str, Table]: + return ("remove_table", self.to_table()) + + def reverse(self) -> CreateTableOp: + return CreateTableOp.from_table(self.to_table()) + + @classmethod + def from_table( + cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None + ) -> DropTableOp: + return cls( + table.name, + schema=table.schema, + table_kw={ + "comment": table.comment, + "info": dict(table.info), + "prefixes": list(table._prefixes), + **table.kwargs, + }, + _reverse=CreateTableOp.from_table( + table, _namespace_metadata=_namespace_metadata + ), + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + if self._reverse: + cols_and_constraints = self._reverse.columns + else: + cols_and_constraints = [] + + schema_obj = schemaobj.SchemaObjects(migration_context) + t = schema_obj.table( + self.table_name, + *cols_and_constraints, + comment=self.comment, + info=self.info.copy() if self.info else {}, + prefixes=list(self.prefixes) if self.prefixes else [], + schema=self.schema, + _constraints_included=( + self._reverse._constraints_included if self._reverse else False + ), + **self.table_kw, + ) + return t + + @classmethod + def drop_table( + cls, + operations: Operations, + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop table" instruction using the current + migration context. + + + e.g.:: + + drop_table("accounts") + + :param table_name: Name of the table + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + """ + op = cls(table_name, schema=schema, if_exists=if_exists, table_kw=kw) + operations.invoke(op) + + +class AlterTableOp(MigrateOperation): + """Represent an alter table operation.""" + + def __init__( + self, + table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + self.table_name = table_name + self.schema = schema + + +@Operations.register_operation("rename_table") +class RenameTableOp(AlterTableOp): + """Represent a rename table operation.""" + + def __init__( + self, + old_table_name: str, + new_table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + super().__init__(old_table_name, schema=schema) + self.new_table_name = new_table_name + + @classmethod + def rename_table( + cls, + operations: Operations, + old_table_name: str, + new_table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + """Emit an ALTER TABLE to rename a table. + + :param old_table_name: old name. + :param new_table_name: new name. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + op = cls(old_table_name, new_table_name, schema=schema) + return operations.invoke(op) + + +@Operations.register_operation("create_table_comment") +@BatchOperations.register_operation( + "create_table_comment", "batch_create_table_comment" +) +class CreateTableCommentOp(AlterTableOp): + """Represent a COMMENT ON `table` operation.""" + + def __init__( + self, + table_name: str, + comment: Optional[str], + *, + schema: Optional[str] = None, + existing_comment: Optional[str] = None, + ) -> None: + self.table_name = table_name + self.comment = comment + self.existing_comment = existing_comment + self.schema = schema + + @classmethod + def create_table_comment( + cls, + operations: Operations, + table_name: str, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table. + + :param table_name: string name of the target table. + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + .. seealso:: + + :meth:`.Operations.drop_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + + op = cls( + table_name, + comment, + existing_comment=existing_comment, + schema=schema, + ) + return operations.invoke(op) + + @classmethod + def batch_create_table_comment( + cls, + operations: BatchOperations, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table + using the current batch migration context. + + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + """ + + op = cls( + operations.impl.table_name, + comment, + existing_comment=existing_comment, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + def reverse(self) -> Union[CreateTableCommentOp, DropTableCommentOp]: + """Reverses the COMMENT ON operation against a table.""" + if self.existing_comment is None: + return DropTableCommentOp( + self.table_name, + existing_comment=self.comment, + schema=self.schema, + ) + else: + return CreateTableCommentOp( + self.table_name, + self.existing_comment, + existing_comment=self.comment, + schema=self.schema, + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.table( + self.table_name, schema=self.schema, comment=self.comment + ) + + def to_diff_tuple(self) -> Tuple[Any, ...]: + return ("add_table_comment", self.to_table(), self.existing_comment) + + +@Operations.register_operation("drop_table_comment") +@BatchOperations.register_operation( + "drop_table_comment", "batch_drop_table_comment" +) +class DropTableCommentOp(AlterTableOp): + """Represent an operation to remove the comment from a table.""" + + def __init__( + self, + table_name: str, + *, + schema: Optional[str] = None, + existing_comment: Optional[str] = None, + ) -> None: + self.table_name = table_name + self.existing_comment = existing_comment + self.schema = schema + + @classmethod + def drop_table_comment( + cls, + operations: Operations, + table_name: str, + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table. + + :param table_name: string name of the target table. + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + .. seealso:: + + :meth:`.Operations.create_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + + op = cls(table_name, existing_comment=existing_comment, schema=schema) + return operations.invoke(op) + + @classmethod + def batch_drop_table_comment( + cls, + operations: BatchOperations, + *, + existing_comment: Optional[str] = None, + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table using the current + batch operations context. + + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + """ + + op = cls( + operations.impl.table_name, + existing_comment=existing_comment, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + def reverse(self) -> CreateTableCommentOp: + """Reverses the COMMENT ON operation against a table.""" + return CreateTableCommentOp( + self.table_name, self.existing_comment, schema=self.schema + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.table(self.table_name, schema=self.schema) + + def to_diff_tuple(self) -> Tuple[Any, ...]: + return ("remove_table_comment", self.to_table()) + + +@Operations.register_operation("alter_column") +@BatchOperations.register_operation("alter_column", "batch_alter_column") +class AlterColumnOp(AlterTableOp): + """Represent an alter column operation.""" + + def __init__( + self, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + existing_type: Optional[Any] = None, + existing_server_default: Any = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + modify_nullable: Optional[bool] = None, + modify_comment: Optional[Union[str, Literal[False]]] = False, + modify_server_default: Any = False, + modify_name: Optional[str] = None, + modify_type: Optional[Any] = None, + **kw: Any, + ) -> None: + super().__init__(table_name, schema=schema) + self.column_name = column_name + self.existing_type = existing_type + self.existing_server_default = existing_server_default + self.existing_nullable = existing_nullable + self.existing_comment = existing_comment + self.modify_nullable = modify_nullable + self.modify_comment = modify_comment + self.modify_server_default = modify_server_default + self.modify_name = modify_name + self.modify_type = modify_type + self.kw = kw + + def to_diff_tuple(self) -> Any: + col_diff = [] + schema, tname, cname = self.schema, self.table_name, self.column_name + + if self.modify_type is not None: + col_diff.append( + ( + "modify_type", + schema, + tname, + cname, + { + "existing_nullable": self.existing_nullable, + "existing_server_default": ( + self.existing_server_default + ), + "existing_comment": self.existing_comment, + }, + self.existing_type, + self.modify_type, + ) + ) + + if self.modify_nullable is not None: + col_diff.append( + ( + "modify_nullable", + schema, + tname, + cname, + { + "existing_type": self.existing_type, + "existing_server_default": ( + self.existing_server_default + ), + "existing_comment": self.existing_comment, + }, + self.existing_nullable, + self.modify_nullable, + ) + ) + + if self.modify_server_default is not False: + col_diff.append( + ( + "modify_default", + schema, + tname, + cname, + { + "existing_nullable": self.existing_nullable, + "existing_type": self.existing_type, + "existing_comment": self.existing_comment, + }, + self.existing_server_default, + self.modify_server_default, + ) + ) + + if self.modify_comment is not False: + col_diff.append( + ( + "modify_comment", + schema, + tname, + cname, + { + "existing_nullable": self.existing_nullable, + "existing_type": self.existing_type, + "existing_server_default": ( + self.existing_server_default + ), + }, + self.existing_comment, + self.modify_comment, + ) + ) + + return col_diff + + def has_changes(self) -> bool: + hc1 = ( + self.modify_nullable is not None + or self.modify_server_default is not False + or self.modify_type is not None + or self.modify_comment is not False + ) + if hc1: + return True + for kw in self.kw: + if kw.startswith("modify_"): + return True + else: + return False + + def reverse(self) -> AlterColumnOp: + kw = self.kw.copy() + kw["existing_type"] = self.existing_type + kw["existing_nullable"] = self.existing_nullable + kw["existing_server_default"] = self.existing_server_default + kw["existing_comment"] = self.existing_comment + if self.modify_type is not None: + kw["modify_type"] = self.modify_type + if self.modify_nullable is not None: + kw["modify_nullable"] = self.modify_nullable + if self.modify_server_default is not False: + kw["modify_server_default"] = self.modify_server_default + if self.modify_comment is not False: + kw["modify_comment"] = self.modify_comment + + # TODO: make this a little simpler + all_keys = { + m.group(1) + for m in [re.match(r"^(?:existing_|modify_)(.+)$", k) for k in kw] + if m + } + + for k in all_keys: + if "modify_%s" % k in kw: + swap = kw["existing_%s" % k] + kw["existing_%s" % k] = kw["modify_%s" % k] + kw["modify_%s" % k] = swap + + return self.__class__( + self.table_name, self.column_name, schema=self.schema, **kw + ) + + @classmethod + def alter_column( + cls, + operations: Operations, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + server_default: Any = False, + new_column_name: Optional[str] = None, + type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None, + existing_type: Optional[ + Union[TypeEngine[Any], Type[TypeEngine[Any]]] + ] = None, + existing_server_default: Optional[ + Union[str, bool, Identity, Computed] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + r"""Issue an "alter column" instruction using the + current migration context. + + Generally, only that aspect of the column which + is being changed, i.e. name, type, nullability, + default, needs to be specified. Multiple changes + can also be specified at once and the backend should + "do the right thing", emitting each change either + separately or together as the backend allows. + + MySQL has special requirements here, since MySQL + cannot ALTER a column without a full specification. + When producing MySQL-compatible migration files, + it is recommended that the ``existing_type``, + ``existing_server_default``, and ``existing_nullable`` + parameters be present, if not being altered. + + Type changes which are against the SQLAlchemy + "schema" types :class:`~sqlalchemy.types.Boolean` + and :class:`~sqlalchemy.types.Enum` may also + add or drop constraints which accompany those + types on backends that don't support them natively. + The ``existing_type`` argument is + used in this case to identify and remove a previous + constraint that was bound to the type object. + + :param table_name: string name of the target table. + :param column_name: string name of the target column, + as it exists before the operation begins. + :param nullable: Optional; specify ``True`` or ``False`` + to alter the column's nullability. + :param server_default: Optional; specify a string + SQL expression, :func:`~sqlalchemy.sql.expression.text`, + or :class:`~sqlalchemy.schema.DefaultClause` to indicate + an alteration to the column's default value. + Set to ``None`` to have the default removed. + :param comment: optional string text of a new comment to add to the + column. + :param new_column_name: Optional; specify a string name here to + indicate the new name within a column rename operation. + :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine` + type object to specify a change to the column's type. + For SQLAlchemy types that also indicate a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`), + the constraint is also generated. + :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column; + currently understood by the MySQL dialect. + :param existing_type: Optional; a + :class:`~sqlalchemy.types.TypeEngine` + type object to specify the previous type. This + is required for all MySQL column alter operations that + don't otherwise specify a new type, as well as for + when nullability is being changed on a SQL Server + column. It is also used if the type is a so-called + SQLAlchemy "schema" type which may define a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, + :class:`~sqlalchemy.types.Enum`), + so that the constraint can be dropped. + :param existing_server_default: Optional; The existing + default value of the column. Required on MySQL if + an existing default is not being changed; else MySQL + removes the default. + :param existing_nullable: Optional; the existing nullability + of the column. Required on MySQL if the existing nullability + is not being changed; else MySQL sets this to NULL. + :param existing_autoincrement: Optional; the existing autoincrement + of the column. Used for MySQL's system of altering a column + that specifies ``AUTO_INCREMENT``. + :param existing_comment: string text of the existing comment on the + column to be maintained. Required on MySQL if the existing comment + on the column is not being changed. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param postgresql_using: String argument which will indicate a + SQL expression to render within the Postgresql-specific USING clause + within ALTER COLUMN. This string is taken directly as raw SQL which + must explicitly include any necessary quoting or escaping of tokens + within the expression. + + """ + + alt = cls( + table_name, + column_name, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + modify_name=new_column_name, + modify_type=type_, + modify_server_default=server_default, + modify_nullable=nullable, + modify_comment=comment, + **kw, + ) + + return operations.invoke(alt) + + @classmethod + def batch_alter_column( + cls, + operations: BatchOperations, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + server_default: Any = False, + new_column_name: Optional[str] = None, + type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None, + existing_type: Optional[ + Union[TypeEngine[Any], Type[TypeEngine[Any]]] + ] = None, + existing_server_default: Optional[ + Union[str, bool, Identity, Computed] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue an "alter column" instruction using the current + batch migration context. + + Parameters are the same as that of :meth:`.Operations.alter_column`, + as well as the following option(s): + + :param insert_before: String name of an existing column which this + column should be placed before, when creating the new table. + + :param insert_after: String name of an existing column which this + column should be placed after, when creating the new table. If + both :paramref:`.BatchOperations.alter_column.insert_before` + and :paramref:`.BatchOperations.alter_column.insert_after` are + omitted, the column is inserted after the last existing column + in the table. + + .. seealso:: + + :meth:`.Operations.alter_column` + + + """ + alt = cls( + operations.impl.table_name, + column_name, + schema=operations.impl.schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + modify_name=new_column_name, + modify_type=type_, + modify_server_default=server_default, + modify_nullable=nullable, + modify_comment=comment, + insert_before=insert_before, + insert_after=insert_after, + **kw, + ) + + return operations.invoke(alt) + + +@Operations.register_operation("add_column") +@BatchOperations.register_operation("add_column", "batch_add_column") +class AddColumnOp(AlterTableOp): + """Represent an add column operation.""" + + def __init__( + self, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + super().__init__(table_name, schema=schema) + self.column = column + self.kw = kw + + def reverse(self) -> DropColumnOp: + return DropColumnOp.from_column_and_tablename( + self.schema, self.table_name, self.column + ) + + def to_diff_tuple( + self, + ) -> Tuple[str, Optional[str], str, Column[Any]]: + return ("add_column", self.schema, self.table_name, self.column) + + def to_column(self) -> Column[Any]: + return self.column + + @classmethod + def from_column(cls, col: Column[Any]) -> AddColumnOp: + return cls(col.table.name, col, schema=col.table.schema) + + @classmethod + def from_column_and_tablename( + cls, + schema: Optional[str], + tname: str, + col: Column[Any], + ) -> AddColumnOp: + return cls(tname, col, schema=schema) + + @classmethod + def add_column( + cls, + operations: Operations, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + ) -> None: + """Issue an "add column" instruction using the current + migration context. + + e.g.:: + + from alembic import op + from sqlalchemy import Column, String + + op.add_column("organization", Column("name", String())) + + The :meth:`.Operations.add_column` method typically corresponds + to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope + of this command, the column's name, datatype, nullability, + and optional server-generated defaults may be indicated. + + .. note:: + + With the exception of NOT NULL constraints or single-column FOREIGN + KEY constraints, other kinds of constraints such as PRIMARY KEY, + UNIQUE or CHECK constraints **cannot** be generated using this + method; for these constraints, refer to operations such as + :meth:`.Operations.create_primary_key` and + :meth:`.Operations.create_check_constraint`. In particular, the + following :class:`~sqlalchemy.schema.Column` parameters are + **ignored**: + + * :paramref:`~sqlalchemy.schema.Column.primary_key` - SQL databases + typically do not support an ALTER operation that can add + individual columns one at a time to an existing primary key + constraint, therefore it's less ambiguous to use the + :meth:`.Operations.create_primary_key` method, which assumes no + existing primary key constraint is present. + * :paramref:`~sqlalchemy.schema.Column.unique` - use the + :meth:`.Operations.create_unique_constraint` method + * :paramref:`~sqlalchemy.schema.Column.index` - use the + :meth:`.Operations.create_index` method + + + The provided :class:`~sqlalchemy.schema.Column` object may include a + :class:`~sqlalchemy.schema.ForeignKey` constraint directive, + referencing a remote table name. For this specific type of constraint, + Alembic will automatically emit a second ALTER statement in order to + add the single-column FOREIGN KEY constraint separately:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + ) + + The column argument passed to :meth:`.Operations.add_column` is a + :class:`~sqlalchemy.schema.Column` construct, used in the same way it's + used in SQLAlchemy. In particular, values or functions to be indicated + as producing the column's default value on the database side are + specified using the ``server_default`` parameter, and not ``default`` + which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the column add + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + :param table_name: String name of the parent table. + :param column: a :class:`sqlalchemy.schema.Column` object + representing the new column. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + + op = cls(table_name, column, schema=schema) + return operations.invoke(op) + + @classmethod + def batch_add_column( + cls, + operations: BatchOperations, + column: Column[Any], + *, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + ) -> None: + """Issue an "add column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.add_column` + + """ + + kw = {} + if insert_before: + kw["insert_before"] = insert_before + if insert_after: + kw["insert_after"] = insert_after + + op = cls( + operations.impl.table_name, + column, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("drop_column") +@BatchOperations.register_operation("drop_column", "batch_drop_column") +class DropColumnOp(AlterTableOp): + """Represent a drop column operation.""" + + def __init__( + self, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + _reverse: Optional[AddColumnOp] = None, + **kw: Any, + ) -> None: + super().__init__(table_name, schema=schema) + self.column_name = column_name + self.kw = kw + self._reverse = _reverse + + def to_diff_tuple( + self, + ) -> Tuple[str, Optional[str], str, Column[Any]]: + return ( + "remove_column", + self.schema, + self.table_name, + self.to_column(), + ) + + def reverse(self) -> AddColumnOp: + if self._reverse is None: + raise ValueError( + "operation is not reversible; " + "original column is not present" + ) + + return AddColumnOp.from_column_and_tablename( + self.schema, self.table_name, self._reverse.column + ) + + @classmethod + def from_column_and_tablename( + cls, + schema: Optional[str], + tname: str, + col: Column[Any], + ) -> DropColumnOp: + return cls( + tname, + col.name, + schema=schema, + _reverse=AddColumnOp.from_column_and_tablename(schema, tname, col), + ) + + def to_column( + self, migration_context: Optional[MigrationContext] = None + ) -> Column[Any]: + if self._reverse is not None: + return self._reverse.column + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.column(self.column_name, NULLTYPE) + + @classmethod + def drop_column( + cls, + operations: Operations, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "drop column" instruction using the current + migration context. + + e.g.:: + + drop_column("organization", "account_id") + + :param table_name: name of table + :param column_name: name of column + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param mssql_drop_check: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the CHECK constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.check_constraints, + then exec's a separate DROP CONSTRAINT for that constraint. + :param mssql_drop_default: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the DEFAULT constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.default_constraints, + then exec's a separate DROP CONSTRAINT for that default. + :param mssql_drop_foreign_key: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop a single FOREIGN KEY constraint on the column using a + SQL-script-compatible + block that selects into a @variable from + sys.foreign_keys/sys.foreign_key_columns, + then exec's a separate DROP CONSTRAINT for that default. Only + works if the column has exactly one FK constraint which refers to + it, at the moment. + + """ + + op = cls(table_name, column_name, schema=schema, **kw) + return operations.invoke(op) + + @classmethod + def batch_drop_column( + cls, operations: BatchOperations, column_name: str, **kw: Any + ) -> None: + """Issue a "drop column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_column` + + """ + op = cls( + operations.impl.table_name, + column_name, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("bulk_insert") +class BulkInsertOp(MigrateOperation): + """Represent a bulk insert operation.""" + + def __init__( + self, + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, + ) -> None: + self.table = table + self.rows = rows + self.multiinsert = multiinsert + + @classmethod + def bulk_insert( + cls, + operations: Operations, + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, + ) -> None: + """Issue a "bulk insert" operation using the current + migration context. + + This provides a means of representing an INSERT of multiple rows + which works equally well in the context of executing on a live + connection as well as that of generating a SQL script. In the + case of a SQL script, the values are rendered inline into the + statement. + + e.g.:: + + from alembic import op + from datetime import date + from sqlalchemy.sql import table, column + from sqlalchemy import String, Integer, Date + + # Create an ad-hoc table to use for the insert statement. + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), + ) + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], + ) + + When using --sql mode, some datatypes may not render inline + automatically, such as dates and other special types. When this + issue is present, :meth:`.Operations.inline_literal` may be used:: + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, + ], + multiinsert=False, + ) + + When using :meth:`.Operations.inline_literal` in conjunction with + :meth:`.Operations.bulk_insert`, in order for the statement to work + in "online" (e.g. non --sql) mode, the + :paramref:`~.Operations.bulk_insert.multiinsert` + flag should be set to ``False``, which will have the effect of + individual INSERT statements being emitted to the database, each + with a distinct VALUES clause, so that the "inline" values can + still be rendered, rather than attempting to pass the values + as bound parameters. + + :param table: a table object which represents the target of the INSERT. + + :param rows: a list of dictionaries indicating rows. + + :param multiinsert: when at its default of True and --sql mode is not + enabled, the INSERT statement will be executed using + "executemany()" style, where all elements in the list of + dictionaries are passed as bound parameters in a single + list. Setting this to False results in individual INSERT + statements being emitted per parameter set, and is needed + in those cases where non-literal values are present in the + parameter sets. + + """ + + op = cls(table, rows, multiinsert=multiinsert) + operations.invoke(op) + + +@Operations.register_operation("execute") +@BatchOperations.register_operation("execute", "batch_execute") +class ExecuteSQLOp(MigrateOperation): + """Represent an execute SQL operation.""" + + def __init__( + self, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + self.sqltext = sqltext + self.execution_options = execution_options + + @classmethod + def execute( + cls, + operations: Operations, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + r"""Execute the given SQL using the current migration context. + + The given SQL can be a plain string, e.g.:: + + op.execute("INSERT INTO table (foo) VALUES ('some value')") + + Or it can be any kind of Core SQL Expression construct, such as + below where we use an update construct:: + + from sqlalchemy.sql import table, column + from sqlalchemy import String + from alembic import op + + account = table("account", column("name", String)) + op.execute( + account.update() + .where(account.c.name == op.inline_literal("account 1")) + .values({"name": op.inline_literal("account 2")}) + ) + + Above, we made use of the SQLAlchemy + :func:`sqlalchemy.sql.expression.table` and + :func:`sqlalchemy.sql.expression.column` constructs to make a brief, + ad-hoc table construct just for our UPDATE statement. A full + :class:`~sqlalchemy.schema.Table` construct of course works perfectly + fine as well, though note it's a recommended practice to at least + ensure the definition of a table is self-contained within the migration + script, rather than imported from a module that may break compatibility + with older migrations. + + In a SQL script context, the statement is emitted directly to the + output stream. There is *no* return result, however, as this + function is oriented towards generating a change script + that can run in "offline" mode. Additionally, parameterized + statements are discouraged here, as they *will not work* in offline + mode. Above, we use :meth:`.inline_literal` where parameters are + to be used. + + For full interaction with a connected database where parameters can + also be used normally, use the "bind" available from the context:: + + from alembic import op + + connection = op.get_bind() + + connection.execute( + account.update() + .where(account.c.name == "account 1") + .values({"name": "account 2"}) + ) + + Additionally, when passing the statement as a plain string, it is first + coerced into a :func:`sqlalchemy.sql.expression.text` construct + before being passed along. In the less likely case that the + literal SQL string contains a colon, it must be escaped with a + backslash, as:: + + op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')") + + + :param sqltext: Any legal SQLAlchemy expression, including: + + * a string + * a :func:`sqlalchemy.sql.expression.text` construct. + * a :func:`sqlalchemy.sql.expression.insert` construct. + * a :func:`sqlalchemy.sql.expression.update` construct. + * a :func:`sqlalchemy.sql.expression.delete` construct. + * Any "executable" described in SQLAlchemy Core documentation, + noting that no result set is returned. + + .. note:: when passing a plain string, the statement is coerced into + a :func:`sqlalchemy.sql.expression.text` construct. This construct + considers symbols with colons, e.g. ``:foo`` to be bound parameters. + To avoid this, ensure that colon symbols are escaped, e.g. + ``\:foo``. + + :param execution_options: Optional dictionary of + execution options, will be passed to + :meth:`sqlalchemy.engine.Connection.execution_options`. + """ + op = cls(sqltext, execution_options=execution_options) + return operations.invoke(op) + + @classmethod + def batch_execute( + cls, + operations: Operations, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + """Execute the given SQL using the current migration context. + + .. seealso:: + + :meth:`.Operations.execute` + + """ + return cls.execute( + operations, sqltext, execution_options=execution_options + ) + + def to_diff_tuple(self) -> Tuple[str, Union[Executable, str]]: + return ("execute", self.sqltext) + + +class OpContainer(MigrateOperation): + """Represent a sequence of operations operation.""" + + def __init__(self, ops: Sequence[MigrateOperation] = ()) -> None: + self.ops = list(ops) + + def is_empty(self) -> bool: + return not self.ops + + def as_diffs(self) -> Any: + return list(OpContainer._ops_as_diffs(self)) + + @classmethod + def _ops_as_diffs( + cls, migrations: OpContainer + ) -> Iterator[Tuple[Any, ...]]: + for op in migrations.ops: + if hasattr(op, "ops"): + yield from cls._ops_as_diffs(cast("OpContainer", op)) + else: + yield op.to_diff_tuple() + + +class ModifyTableOps(OpContainer): + """Contains a sequence of operations that all apply to a single Table.""" + + def __init__( + self, + table_name: str, + ops: Sequence[MigrateOperation], + *, + schema: Optional[str] = None, + ) -> None: + super().__init__(ops) + self.table_name = table_name + self.schema = schema + + def reverse(self) -> ModifyTableOps: + return ModifyTableOps( + self.table_name, + ops=list(reversed([op.reverse() for op in self.ops])), + schema=self.schema, + ) + + +class UpgradeOps(OpContainer): + """contains a sequence of operations that would apply to the + 'upgrade' stream of a script. + + .. seealso:: + + :ref:`customizing_revision` + + """ + + def __init__( + self, + ops: Sequence[MigrateOperation] = (), + upgrade_token: str = "upgrades", + ) -> None: + super().__init__(ops=ops) + self.upgrade_token = upgrade_token + + def reverse_into(self, downgrade_ops: DowngradeOps) -> DowngradeOps: + downgrade_ops.ops[:] = list( + reversed([op.reverse() for op in self.ops]) + ) + return downgrade_ops + + def reverse(self) -> DowngradeOps: + return self.reverse_into(DowngradeOps(ops=[])) + + +class DowngradeOps(OpContainer): + """contains a sequence of operations that would apply to the + 'downgrade' stream of a script. + + .. seealso:: + + :ref:`customizing_revision` + + """ + + def __init__( + self, + ops: Sequence[MigrateOperation] = (), + downgrade_token: str = "downgrades", + ) -> None: + super().__init__(ops=ops) + self.downgrade_token = downgrade_token + + def reverse(self) -> UpgradeOps: + return UpgradeOps( + ops=list(reversed([op.reverse() for op in self.ops])) + ) + + +class MigrationScript(MigrateOperation): + """represents a migration script. + + E.g. when autogenerate encounters this object, this corresponds to the + production of an actual script file. + + A normal :class:`.MigrationScript` object would contain a single + :class:`.UpgradeOps` and a single :class:`.DowngradeOps` directive. + These are accessible via the ``.upgrade_ops`` and ``.downgrade_ops`` + attributes. + + In the case of an autogenerate operation that runs multiple times, + such as the multiple database example in the "multidb" template, + the ``.upgrade_ops`` and ``.downgrade_ops`` attributes are disabled, + and instead these objects should be accessed via the ``.upgrade_ops_list`` + and ``.downgrade_ops_list`` list-based attributes. These latter + attributes are always available at the very least as single-element lists. + + .. seealso:: + + :ref:`customizing_revision` + + """ + + _needs_render: Optional[bool] + _upgrade_ops: List[UpgradeOps] + _downgrade_ops: List[DowngradeOps] + + def __init__( + self, + rev_id: Optional[str], + upgrade_ops: UpgradeOps, + downgrade_ops: DowngradeOps, + *, + message: Optional[str] = None, + imports: Set[str] = set(), + head: Optional[str] = None, + splice: Optional[bool] = None, + branch_label: Optional[_RevIdType] = None, + version_path: Optional[str] = None, + depends_on: Optional[_RevIdType] = None, + ) -> None: + self.rev_id = rev_id + self.message = message + self.imports = imports + self.head = head + self.splice = splice + self.branch_label = branch_label + self.version_path = version_path + self.depends_on = depends_on + self.upgrade_ops = upgrade_ops + self.downgrade_ops = downgrade_ops + + @property + def upgrade_ops(self) -> Optional[UpgradeOps]: + """An instance of :class:`.UpgradeOps`. + + .. seealso:: + + :attr:`.MigrationScript.upgrade_ops_list` + """ + if len(self._upgrade_ops) > 1: + raise ValueError( + "This MigrationScript instance has a multiple-entry " + "list for UpgradeOps; please use the " + "upgrade_ops_list attribute." + ) + elif not self._upgrade_ops: + return None + else: + return self._upgrade_ops[0] + + @upgrade_ops.setter + def upgrade_ops( + self, upgrade_ops: Union[UpgradeOps, List[UpgradeOps]] + ) -> None: + self._upgrade_ops = util.to_list(upgrade_ops) + for elem in self._upgrade_ops: + assert isinstance(elem, UpgradeOps) + + @property + def downgrade_ops(self) -> Optional[DowngradeOps]: + """An instance of :class:`.DowngradeOps`. + + .. seealso:: + + :attr:`.MigrationScript.downgrade_ops_list` + """ + if len(self._downgrade_ops) > 1: + raise ValueError( + "This MigrationScript instance has a multiple-entry " + "list for DowngradeOps; please use the " + "downgrade_ops_list attribute." + ) + elif not self._downgrade_ops: + return None + else: + return self._downgrade_ops[0] + + @downgrade_ops.setter + def downgrade_ops( + self, downgrade_ops: Union[DowngradeOps, List[DowngradeOps]] + ) -> None: + self._downgrade_ops = util.to_list(downgrade_ops) + for elem in self._downgrade_ops: + assert isinstance(elem, DowngradeOps) + + @property + def upgrade_ops_list(self) -> List[UpgradeOps]: + """A list of :class:`.UpgradeOps` instances. + + This is used in place of the :attr:`.MigrationScript.upgrade_ops` + attribute when dealing with a revision operation that does + multiple autogenerate passes. + + """ + return self._upgrade_ops + + @property + def downgrade_ops_list(self) -> List[DowngradeOps]: + """A list of :class:`.DowngradeOps` instances. + + This is used in place of the :attr:`.MigrationScript.downgrade_ops` + attribute when dealing with a revision operation that does + multiple autogenerate passes. + + """ + return self._downgrade_ops diff --git a/venv/lib/python3.8/site-packages/alembic/operations/schemaobj.py b/venv/lib/python3.8/site-packages/alembic/operations/schemaobj.py new file mode 100644 index 000000000..59c1002f1 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/operations/schemaobj.py @@ -0,0 +1,290 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import schema as sa_schema +from sqlalchemy.sql.schema import Column +from sqlalchemy.sql.schema import Constraint +from sqlalchemy.sql.schema import Index +from sqlalchemy.types import Integer +from sqlalchemy.types import NULLTYPE + +from .. import util +from ..util import sqla_compat + +if TYPE_CHECKING: + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.schema import CheckConstraint + from sqlalchemy.sql.schema import ForeignKey + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import PrimaryKeyConstraint + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.type_api import TypeEngine + + from ..runtime.migration import MigrationContext + + +class SchemaObjects: + def __init__( + self, migration_context: Optional[MigrationContext] = None + ) -> None: + self.migration_context = migration_context + + def primary_key_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + cols: Sequence[str], + schema: Optional[str] = None, + **dialect_kw, + ) -> PrimaryKeyConstraint: + m = self.metadata() + columns = [sa_schema.Column(n, NULLTYPE) for n in cols] + t = sa_schema.Table(table_name, m, *columns, schema=schema) + # SQLAlchemy primary key constraint name arg is wrongly typed on + # the SQLAlchemy side through 2.0.5 at least + p = sa_schema.PrimaryKeyConstraint( + *[t.c[n] for n in cols], name=name, **dialect_kw # type: ignore + ) + return p + + def foreign_key_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + source: str, + referent: str, + local_cols: List[str], + remote_cols: List[str], + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + **dialect_kw, + ) -> ForeignKeyConstraint: + m = self.metadata() + if source == referent and source_schema == referent_schema: + t1_cols = local_cols + remote_cols + else: + t1_cols = local_cols + sa_schema.Table( + referent, + m, + *[sa_schema.Column(n, NULLTYPE) for n in remote_cols], + schema=referent_schema, + ) + + t1 = sa_schema.Table( + source, + m, + *[ + sa_schema.Column(n, NULLTYPE) + for n in util.unique_list(t1_cols) + ], + schema=source_schema, + ) + + tname = ( + "%s.%s" % (referent_schema, referent) + if referent_schema + else referent + ) + + dialect_kw["match"] = match + + f = sa_schema.ForeignKeyConstraint( + local_cols, + ["%s.%s" % (tname, n) for n in remote_cols], + name=name, + onupdate=onupdate, + ondelete=ondelete, + deferrable=deferrable, + initially=initially, + **dialect_kw, + ) + t1.append_constraint(f) + + return f + + def unique_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + source: str, + local_cols: Sequence[str], + schema: Optional[str] = None, + **kw, + ) -> UniqueConstraint: + t = sa_schema.Table( + source, + self.metadata(), + *[sa_schema.Column(n, NULLTYPE) for n in local_cols], + schema=schema, + ) + kw["name"] = name + uq = sa_schema.UniqueConstraint(*[t.c[n] for n in local_cols], **kw) + # TODO: need event tests to ensure the event + # is fired off here + t.append_constraint(uq) + return uq + + def check_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + source: str, + condition: Union[str, TextClause, ColumnElement[Any]], + schema: Optional[str] = None, + **kw, + ) -> Union[CheckConstraint]: + t = sa_schema.Table( + source, + self.metadata(), + sa_schema.Column("x", Integer), + schema=schema, + ) + ck = sa_schema.CheckConstraint(condition, name=name, **kw) + t.append_constraint(ck) + return ck + + def generic_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + type_: Optional[str], + schema: Optional[str] = None, + **kw, + ) -> Any: + t = self.table(table_name, schema=schema) + types: Dict[Optional[str], Any] = { + "foreignkey": lambda name: sa_schema.ForeignKeyConstraint( + [], [], name=name + ), + "primary": sa_schema.PrimaryKeyConstraint, + "unique": sa_schema.UniqueConstraint, + "check": lambda name: sa_schema.CheckConstraint("", name=name), + None: sa_schema.Constraint, + } + try: + const = types[type_] + except KeyError as ke: + raise TypeError( + "'type' can be one of %s" + % ", ".join(sorted(repr(x) for x in types)) + ) from ke + else: + const = const(name=name) + t.append_constraint(const) + return const + + def metadata(self) -> MetaData: + kw = {} + if ( + self.migration_context is not None + and "target_metadata" in self.migration_context.opts + ): + mt = self.migration_context.opts["target_metadata"] + if hasattr(mt, "naming_convention"): + kw["naming_convention"] = mt.naming_convention + return sa_schema.MetaData(**kw) + + def table(self, name: str, *columns, **kw) -> Table: + m = self.metadata() + + cols = [ + sqla_compat._copy(c) if c.table is not None else c + for c in columns + if isinstance(c, Column) + ] + # these flags have already added their UniqueConstraint / + # Index objects to the table, so flip them off here. + # SQLAlchemy tometadata() avoids this instead by preserving the + # flags and skipping the constraints that have _type_bound on them, + # but for a migration we'd rather list out the constraints + # explicitly. + _constraints_included = kw.pop("_constraints_included", False) + if _constraints_included: + for c in cols: + c.unique = c.index = False + + t = sa_schema.Table(name, m, *cols, **kw) + + constraints = [ + ( + sqla_compat._copy(elem, target_table=t) + if getattr(elem, "parent", None) is not t + and getattr(elem, "parent", None) is not None + else elem + ) + for elem in columns + if isinstance(elem, (Constraint, Index)) + ] + + for const in constraints: + t.append_constraint(const) + + for f in t.foreign_keys: + self._ensure_table_for_fk(m, f) + return t + + def column(self, name: str, type_: TypeEngine, **kw) -> Column: + return sa_schema.Column(name, type_, **kw) + + def index( + self, + name: Optional[str], + tablename: Optional[str], + columns: Sequence[Union[str, TextClause, ColumnElement[Any]]], + schema: Optional[str] = None, + **kw, + ) -> Index: + t = sa_schema.Table( + tablename or "no_table", + self.metadata(), + schema=schema, + ) + kw["_table"] = t + idx = sa_schema.Index( + name, + *[util.sqla_compat._textual_index_column(t, n) for n in columns], + **kw, + ) + return idx + + def _parse_table_key(self, table_key: str) -> Tuple[Optional[str], str]: + if "." in table_key: + tokens = table_key.split(".") + sname: Optional[str] = ".".join(tokens[0:-1]) + tname = tokens[-1] + else: + tname = table_key + sname = None + return (sname, tname) + + def _ensure_table_for_fk(self, metadata: MetaData, fk: ForeignKey) -> None: + """create a placeholder Table object for the referent of a + ForeignKey. + + """ + if isinstance(fk._colspec, str): + table_key, cname = fk._colspec.rsplit(".", 1) + sname, tname = self._parse_table_key(table_key) + if table_key not in metadata.tables: + rel_t = sa_schema.Table(tname, metadata, schema=sname) + else: + rel_t = metadata.tables[table_key] + if cname not in rel_t.c: + rel_t.append_column(sa_schema.Column(cname, NULLTYPE)) diff --git a/venv/lib/python3.8/site-packages/alembic/operations/toimpl.py b/venv/lib/python3.8/site-packages/alembic/operations/toimpl.py new file mode 100644 index 000000000..4b960049c --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/operations/toimpl.py @@ -0,0 +1,238 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from typing import TYPE_CHECKING + +from sqlalchemy import schema as sa_schema + +from . import ops +from .base import Operations +from ..util.sqla_compat import _copy +from ..util.sqla_compat import sqla_14 + +if TYPE_CHECKING: + from sqlalchemy.sql.schema import Table + + +@Operations.implementation_for(ops.AlterColumnOp) +def alter_column( + operations: "Operations", operation: "ops.AlterColumnOp" +) -> None: + compiler = operations.impl.dialect.statement_compiler( + operations.impl.dialect, None + ) + + existing_type = operation.existing_type + existing_nullable = operation.existing_nullable + existing_server_default = operation.existing_server_default + type_ = operation.modify_type + column_name = operation.column_name + table_name = operation.table_name + schema = operation.schema + server_default = operation.modify_server_default + new_column_name = operation.modify_name + nullable = operation.modify_nullable + comment = operation.modify_comment + existing_comment = operation.existing_comment + + def _count_constraint(constraint): + return not isinstance(constraint, sa_schema.PrimaryKeyConstraint) and ( + not constraint._create_rule or constraint._create_rule(compiler) + ) + + if existing_type and type_: + t = operations.schema_obj.table( + table_name, + sa_schema.Column(column_name, existing_type), + schema=schema, + ) + for constraint in t.constraints: + if _count_constraint(constraint): + operations.impl.drop_constraint(constraint) + + operations.impl.alter_column( + table_name, + column_name, + nullable=nullable, + server_default=server_default, + name=new_column_name, + type_=type_, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + comment=comment, + existing_comment=existing_comment, + **operation.kw, + ) + + if type_: + t = operations.schema_obj.table( + table_name, + operations.schema_obj.column(column_name, type_), + schema=schema, + ) + for constraint in t.constraints: + if _count_constraint(constraint): + operations.impl.add_constraint(constraint) + + +@Operations.implementation_for(ops.DropTableOp) +def drop_table(operations: "Operations", operation: "ops.DropTableOp") -> None: + kw = {} + if operation.if_exists is not None: + if not sqla_14: + raise NotImplementedError("SQLAlchemy 1.4+ required") + + kw["if_exists"] = operation.if_exists + operations.impl.drop_table( + operation.to_table(operations.migration_context), **kw + ) + + +@Operations.implementation_for(ops.DropColumnOp) +def drop_column( + operations: "Operations", operation: "ops.DropColumnOp" +) -> None: + column = operation.to_column(operations.migration_context) + operations.impl.drop_column( + operation.table_name, column, schema=operation.schema, **operation.kw + ) + + +@Operations.implementation_for(ops.CreateIndexOp) +def create_index( + operations: "Operations", operation: "ops.CreateIndexOp" +) -> None: + idx = operation.to_index(operations.migration_context) + kw = {} + if operation.if_not_exists is not None: + if not sqla_14: + raise NotImplementedError("SQLAlchemy 1.4+ required") + + kw["if_not_exists"] = operation.if_not_exists + operations.impl.create_index(idx, **kw) + + +@Operations.implementation_for(ops.DropIndexOp) +def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None: + kw = {} + if operation.if_exists is not None: + if not sqla_14: + raise NotImplementedError("SQLAlchemy 1.4+ required") + + kw["if_exists"] = operation.if_exists + + operations.impl.drop_index( + operation.to_index(operations.migration_context), + **kw, + ) + + +@Operations.implementation_for(ops.CreateTableOp) +def create_table( + operations: "Operations", operation: "ops.CreateTableOp" +) -> "Table": + kw = {} + if operation.if_not_exists is not None: + if not sqla_14: + raise NotImplementedError("SQLAlchemy 1.4+ required") + + kw["if_not_exists"] = operation.if_not_exists + table = operation.to_table(operations.migration_context) + operations.impl.create_table(table, **kw) + return table + + +@Operations.implementation_for(ops.RenameTableOp) +def rename_table( + operations: "Operations", operation: "ops.RenameTableOp" +) -> None: + operations.impl.rename_table( + operation.table_name, operation.new_table_name, schema=operation.schema + ) + + +@Operations.implementation_for(ops.CreateTableCommentOp) +def create_table_comment( + operations: "Operations", operation: "ops.CreateTableCommentOp" +) -> None: + table = operation.to_table(operations.migration_context) + operations.impl.create_table_comment(table) + + +@Operations.implementation_for(ops.DropTableCommentOp) +def drop_table_comment( + operations: "Operations", operation: "ops.DropTableCommentOp" +) -> None: + table = operation.to_table(operations.migration_context) + operations.impl.drop_table_comment(table) + + +@Operations.implementation_for(ops.AddColumnOp) +def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None: + table_name = operation.table_name + column = operation.column + schema = operation.schema + kw = operation.kw + + if column.table is not None: + column = _copy(column) + + t = operations.schema_obj.table(table_name, column, schema=schema) + operations.impl.add_column(table_name, column, schema=schema, **kw) + + for constraint in t.constraints: + if not isinstance(constraint, sa_schema.PrimaryKeyConstraint): + operations.impl.add_constraint(constraint) + for index in t.indexes: + operations.impl.create_index(index) + + with_comment = ( + operations.impl.dialect.supports_comments + and not operations.impl.dialect.inline_comments + ) + comment = column.comment + if comment and with_comment: + operations.impl.create_column_comment(column) + + +@Operations.implementation_for(ops.AddConstraintOp) +def create_constraint( + operations: "Operations", operation: "ops.AddConstraintOp" +) -> None: + operations.impl.add_constraint( + operation.to_constraint(operations.migration_context) + ) + + +@Operations.implementation_for(ops.DropConstraintOp) +def drop_constraint( + operations: "Operations", operation: "ops.DropConstraintOp" +) -> None: + operations.impl.drop_constraint( + operations.schema_obj.generic_constraint( + operation.constraint_name, + operation.table_name, + operation.constraint_type, + schema=operation.schema, + ) + ) + + +@Operations.implementation_for(ops.BulkInsertOp) +def bulk_insert( + operations: "Operations", operation: "ops.BulkInsertOp" +) -> None: + operations.impl.bulk_insert( # type: ignore[union-attr] + operation.table, operation.rows, multiinsert=operation.multiinsert + ) + + +@Operations.implementation_for(ops.ExecuteSQLOp) +def execute_sql( + operations: "Operations", operation: "ops.ExecuteSQLOp" +) -> None: + operations.migration_context.impl.execute( + operation.sqltext, execution_options=operation.execution_options + ) diff --git a/venv/lib/python3.8/site-packages/alembic/py.typed b/venv/lib/python3.8/site-packages/alembic/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/alembic/runtime/__init__.py b/venv/lib/python3.8/site-packages/alembic/runtime/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8178415327feb33b98f2bd01eaf45e03b0ed4cf GIT binary patch literal 208 zcmWIL<>g`k0)Zp?nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o5j>KO;XkRX?>V zH9IpkN8cs2EHx*;AU8FyMBgVdsaU_DvLquvPq)BWH?1f&H7PSyx1=aBIXksTzbrMc zOg|?x397_c&qBXAvm{lw04SZ9o?5J*n3I~Dl$oqwRGL?k3A7_VJ~J<~BtBlRpz;=n SO>TZlX-=vg$T^>Zm;nIV%Q&_G literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/environment.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/environment.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e432713c642f6718ede5a93168bd171f9cf2449 GIT binary patch literal 40044 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/migration.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/runtime/__pycache__/migration.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0f7037bf9e768a28544d45de3baa89145925f17 GIT binary patch literal 39551 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 None: + r"""Construct a new :class:`.EnvironmentContext`. + + :param config: a :class:`.Config` instance. + :param script: a :class:`.ScriptDirectory` instance. + :param \**kw: keyword options that will be ultimately + passed along to the :class:`.MigrationContext` when + :meth:`.EnvironmentContext.configure` is called. + + """ + self.config = config + self.script = script + self.context_opts = kw + + def __enter__(self) -> EnvironmentContext: + """Establish a context which provides a + :class:`.EnvironmentContext` object to + env.py scripts. + + The :class:`.EnvironmentContext` will + be made available as ``from alembic import context``. + + """ + self._install_proxy() + return self + + def __exit__(self, *arg: Any, **kw: Any) -> None: + self._remove_proxy() + + def is_offline_mode(self) -> bool: + """Return True if the current migrations environment + is running in "offline mode". + + This is ``True`` or ``False`` depending + on the ``--sql`` flag passed. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + return self.context_opts.get("as_sql", False) # type: ignore[no-any-return] # noqa: E501 + + def is_transactional_ddl(self) -> bool: + """Return True if the context is configured to expect a + transactional DDL capable backend. + + This defaults to the type of database in use, and + can be overridden by the ``transactional_ddl`` argument + to :meth:`.configure` + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + return self.get_context().impl.transactional_ddl + + def requires_connection(self) -> bool: + return not self.is_offline_mode() + + def get_head_revision(self) -> _RevNumber: + """Return the hex identifier of the 'head' script revision. + + If the script directory has multiple heads, this + method raises a :class:`.CommandError`; + :meth:`.EnvironmentContext.get_head_revisions` should be preferred. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: :meth:`.EnvironmentContext.get_head_revisions` + + """ + return self.script.as_revision_number("head") + + def get_head_revisions(self) -> _RevNumber: + """Return the hex identifier of the 'heads' script revision(s). + + This returns a tuple containing the version number of all + heads in the script directory. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + return self.script.as_revision_number("heads") + + def get_starting_revision_argument(self) -> _RevNumber: + """Return the 'starting revision' argument, + if the revision was passed using ``start:end``. + + This is only meaningful in "offline" mode. + Returns ``None`` if no value is available + or was configured. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + if self._migration_context is not None: + return self.script.as_revision_number( + self.get_context()._start_from_rev + ) + elif "starting_rev" in self.context_opts: + return self.script.as_revision_number( + self.context_opts["starting_rev"] + ) + else: + # this should raise only in the case that a command + # is being run where the "starting rev" is never applicable; + # this is to catch scripts which rely upon this in + # non-sql mode or similar + raise util.CommandError( + "No starting revision argument is available." + ) + + def get_revision_argument(self) -> _RevNumber: + """Get the 'destination' revision argument. + + This is typically the argument passed to the + ``upgrade`` or ``downgrade`` command. + + If it was specified as ``head``, the actual + version number is returned; if specified + as ``base``, ``None`` is returned. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + return self.script.as_revision_number( + self.context_opts["destination_rev"] + ) + + def get_tag_argument(self) -> Optional[str]: + """Return the value passed for the ``--tag`` argument, if any. + + The ``--tag`` argument is not used directly by Alembic, + but is available for custom ``env.py`` configurations that + wish to use it; particularly for offline generation scripts + that wish to generate tagged filenames. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_x_argument` - a newer and more + open ended system of extending ``env.py`` scripts via the command + line. + + """ + return self.context_opts.get("tag", None) # type: ignore[no-any-return] # noqa: E501 + + @overload + def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]: ... + + @overload + def get_x_argument( + self, as_dictionary: Literal[True] + ) -> Dict[str, str]: ... + + @overload + def get_x_argument( + self, as_dictionary: bool = ... + ) -> Union[List[str], Dict[str, str]]: ... + + def get_x_argument( + self, as_dictionary: bool = False + ) -> Union[List[str], Dict[str, str]]: + """Return the value(s) passed for the ``-x`` argument, if any. + + The ``-x`` argument is an open ended flag that allows any user-defined + value or values to be passed on the command line, then available + here for consumption by a custom ``env.py`` script. + + The return value is a list, returned directly from the ``argparse`` + structure. If ``as_dictionary=True`` is passed, the ``x`` arguments + are parsed using ``key=value`` format into a dictionary that is + then returned. If there is no ``=`` in the argument, value is an empty + string. + + .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when + arguments are passed without the ``=`` symbol. + + For example, to support passing a database URL on the command line, + the standard ``env.py`` script can be modified like this:: + + cmd_line_url = context.get_x_argument( + as_dictionary=True).get('dbname') + if cmd_line_url: + engine = create_engine(cmd_line_url) + else: + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + This then takes effect by running the ``alembic`` script as:: + + alembic -x dbname=postgresql://user:pass@host/dbname upgrade head + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_tag_argument` + + :attr:`.Config.cmd_opts` + + """ + if self.config.cmd_opts is not None: + value = self.config.cmd_opts.x or [] + else: + value = [] + if as_dictionary: + dict_value = {} + for arg in value: + x_key, _, x_value = arg.partition("=") + dict_value[x_key] = x_value + value = dict_value + + return value + + def configure( + self, + connection: Optional[Connection] = None, + url: Optional[Union[str, URL]] = None, + dialect_name: Optional[str] = None, + dialect_opts: Optional[Dict[str, Any]] = None, + transactional_ddl: Optional[bool] = None, + transaction_per_migration: bool = False, + output_buffer: Optional[TextIO] = None, + starting_rev: Optional[str] = None, + tag: Optional[str] = None, + template_args: Optional[Dict[str, Any]] = None, + render_as_batch: bool = False, + target_metadata: Union[MetaData, Sequence[MetaData], None] = None, + include_name: Optional[IncludeNameFn] = None, + include_object: Optional[IncludeObjectFn] = None, + include_schemas: bool = False, + process_revision_directives: Optional[ + ProcessRevisionDirectiveFn + ] = None, + compare_type: Union[bool, CompareType] = True, + compare_server_default: Union[bool, CompareServerDefault] = False, + render_item: Optional[RenderItemFn] = None, + literal_binds: bool = False, + upgrade_token: str = "upgrades", + downgrade_token: str = "downgrades", + alembic_module_prefix: str = "op.", + sqlalchemy_module_prefix: str = "sa.", + user_module_prefix: Optional[str] = None, + on_version_apply: Optional[OnVersionApplyFn] = None, + **kw: Any, + ) -> None: + """Configure a :class:`.MigrationContext` within this + :class:`.EnvironmentContext` which will provide database + connectivity and other configuration to a series of + migration scripts. + + Many methods on :class:`.EnvironmentContext` require that + this method has been called in order to function, as they + ultimately need to have database access or at least access + to the dialect in use. Those which do are documented as such. + + The important thing needed by :meth:`.configure` is a + means to determine what kind of database dialect is in use. + An actual connection to that database is needed only if + the :class:`.MigrationContext` is to be used in + "online" mode. + + If the :meth:`.is_offline_mode` function returns ``True``, + then no connection is needed here. Otherwise, the + ``connection`` parameter should be present as an + instance of :class:`sqlalchemy.engine.Connection`. + + This function is typically called from the ``env.py`` + script within a migration environment. It can be called + multiple times for an invocation. The most recent + :class:`~sqlalchemy.engine.Connection` + for which it was called is the one that will be operated upon + by the next call to :meth:`.run_migrations`. + + General parameters: + + :param connection: a :class:`~sqlalchemy.engine.Connection` + to use + for SQL execution in "online" mode. When present, is also + used to determine the type of dialect in use. + :param url: a string database url, or a + :class:`sqlalchemy.engine.url.URL` object. + The type of dialect to be used will be derived from this if + ``connection`` is not passed. + :param dialect_name: string name of a dialect, such as + "postgresql", "mssql", etc. + The type of dialect to be used will be derived from this if + ``connection`` and ``url`` are not passed. + :param dialect_opts: dictionary of options to be passed to dialect + constructor. + :param transactional_ddl: Force the usage of "transactional" + DDL on or off; + this otherwise defaults to whether or not the dialect in + use supports it. + :param transaction_per_migration: if True, nest each migration script + in a transaction rather than the full series of migrations to + run. + :param output_buffer: a file-like object that will be used + for textual output + when the ``--sql`` option is used to generate SQL scripts. + Defaults to + ``sys.stdout`` if not passed here and also not present on + the :class:`.Config` + object. The value here overrides that of the :class:`.Config` + object. + :param output_encoding: when using ``--sql`` to generate SQL + scripts, apply this encoding to the string output. + :param literal_binds: when using ``--sql`` to generate SQL + scripts, pass through the ``literal_binds`` flag to the compiler + so that any literal values that would ordinarily be bound + parameters are converted to plain strings. + + .. warning:: Dialects can typically only handle simple datatypes + like strings and numbers for auto-literal generation. Datatypes + like dates, intervals, and others may still require manual + formatting, typically using :meth:`.Operations.inline_literal`. + + .. note:: the ``literal_binds`` flag is ignored on SQLAlchemy + versions prior to 0.8 where this feature is not supported. + + .. seealso:: + + :meth:`.Operations.inline_literal` + + :param starting_rev: Override the "starting revision" argument + when using ``--sql`` mode. + :param tag: a string tag for usage by custom ``env.py`` scripts. + Set via the ``--tag`` option, can be overridden here. + :param template_args: dictionary of template arguments which + will be added to the template argument environment when + running the "revision" command. Note that the script environment + is only run within the "revision" command if the --autogenerate + option is used, or if the option "revision_environment=true" + is present in the alembic.ini file. + + :param version_table: The name of the Alembic version table. + The default is ``'alembic_version'``. + :param version_table_schema: Optional schema to place version + table within. + :param version_table_pk: boolean, whether the Alembic version table + should use a primary key constraint for the "value" column; this + only takes effect when the table is first created. + Defaults to True; setting to False should not be necessary and is + here for backwards compatibility reasons. + :param on_version_apply: a callable or collection of callables to be + run for each migration step. + The callables will be run in the order they are given, once for + each migration step, after the respective operation has been + applied but before its transaction is finalized. + Each callable accepts no positional arguments and the following + keyword arguments: + + * ``ctx``: the :class:`.MigrationContext` running the migration, + * ``step``: a :class:`.MigrationInfo` representing the + step currently being applied, + * ``heads``: a collection of version strings representing the + current heads, + * ``run_args``: the ``**kwargs`` passed to :meth:`.run_migrations`. + + Parameters specific to the autogenerate feature, when + ``alembic revision`` is run with the ``--autogenerate`` feature: + + :param target_metadata: a :class:`sqlalchemy.schema.MetaData` + object, or a sequence of :class:`~sqlalchemy.schema.MetaData` + objects, that will be consulted during autogeneration. + The tables present in each :class:`~sqlalchemy.schema.MetaData` + will be compared against + what is locally available on the target + :class:`~sqlalchemy.engine.Connection` + to produce candidate upgrade/downgrade operations. + :param compare_type: Indicates type comparison behavior during + an autogenerate + operation. Defaults to ``True`` turning on type comparison, which + has good accuracy on most backends. See :ref:`compare_types` + for an example as well as information on other type + comparison options. Set to ``False`` which disables type + comparison. A callable can also be passed to provide custom type + comparison, see :ref:`compare_types` for additional details. + + .. versionchanged:: 1.12.0 The default value of + :paramref:`.EnvironmentContext.configure.compare_type` has been + changed to ``True``. + + .. seealso:: + + :ref:`compare_types` + + :paramref:`.EnvironmentContext.configure.compare_server_default` + + :param compare_server_default: Indicates server default comparison + behavior during + an autogenerate operation. Defaults to ``False`` which disables + server default + comparison. Set to ``True`` to turn on server default comparison, + which has + varied accuracy depending on backend. + + To customize server default comparison behavior, a callable may + be specified + which can filter server default comparisons during an + autogenerate operation. + defaults during an autogenerate operation. The format of this + callable is:: + + def my_compare_server_default(context, inspected_column, + metadata_column, inspected_default, metadata_default, + rendered_metadata_default): + # return True if the defaults are different, + # False if not, or None to allow the default implementation + # to compare these defaults + return None + + context.configure( + # ... + compare_server_default = my_compare_server_default + ) + + ``inspected_column`` is a dictionary structure as returned by + :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas + ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from + the local model environment. + + A return value of ``None`` indicates to allow default server default + comparison + to proceed. Note that some backends such as Postgresql actually + execute + the two defaults on the database side to compare for equivalence. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.compare_type` + + :param include_name: A callable function which is given + the chance to return ``True`` or ``False`` for any database reflected + object based on its name, including database schema names when + the :paramref:`.EnvironmentContext.configure.include_schemas` flag + is set to ``True``. + + The function accepts the following positional arguments: + + * ``name``: the name of the object, such as schema name or table name. + Will be ``None`` when indicating the default schema name of the + database connection. + * ``type``: a string describing the type of object; currently + ``"schema"``, ``"table"``, ``"column"``, ``"index"``, + ``"unique_constraint"``, or ``"foreign_key_constraint"`` + * ``parent_names``: a dictionary of "parent" object names, that are + relative to the name being given. Keys in this dictionary may + include: ``"schema_name"``, ``"table_name"`` or + ``"schema_qualified_table_name"``. + + E.g.:: + + def include_name(name, type_, parent_names): + if type_ == "schema": + return name in ["schema_one", "schema_two"] + else: + return True + + context.configure( + # ... + include_schemas = True, + include_name = include_name + ) + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_object` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + + :param include_object: A callable function which is given + the chance to return ``True`` or ``False`` for any object, + indicating if the given object should be considered in the + autogenerate sweep. + + The function accepts the following positional arguments: + + * ``object``: a :class:`~sqlalchemy.schema.SchemaItem` object such + as a :class:`~sqlalchemy.schema.Table`, + :class:`~sqlalchemy.schema.Column`, + :class:`~sqlalchemy.schema.Index` + :class:`~sqlalchemy.schema.UniqueConstraint`, + or :class:`~sqlalchemy.schema.ForeignKeyConstraint` object + * ``name``: the name of the object. This is typically available + via ``object.name``. + * ``type``: a string describing the type of object; currently + ``"table"``, ``"column"``, ``"index"``, ``"unique_constraint"``, + or ``"foreign_key_constraint"`` + * ``reflected``: ``True`` if the given object was produced based on + table reflection, ``False`` if it's from a local :class:`.MetaData` + object. + * ``compare_to``: the object being compared against, if available, + else ``None``. + + E.g.:: + + def include_object(object, name, type_, reflected, compare_to): + if (type_ == "column" and + not reflected and + object.info.get("skip_autogenerate", False)): + return False + else: + return True + + context.configure( + # ... + include_object = include_object + ) + + For the use case of omitting specific schemas from a target database + when :paramref:`.EnvironmentContext.configure.include_schemas` is + set to ``True``, the :attr:`~sqlalchemy.schema.Table.schema` + attribute can be checked for each :class:`~sqlalchemy.schema.Table` + object passed to the hook, however it is much more efficient + to filter on schemas before reflection of objects takes place + using the :paramref:`.EnvironmentContext.configure.include_name` + hook. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + :param render_as_batch: if True, commands which alter elements + within a table will be placed under a ``with batch_alter_table():`` + directive, so that batch migrations will take place. + + .. seealso:: + + :ref:`batch_migrations` + + :param include_schemas: If True, autogenerate will scan across + all schemas located by the SQLAlchemy + :meth:`~sqlalchemy.engine.reflection.Inspector.get_schema_names` + method, and include all differences in tables found across all + those schemas. When using this option, you may want to also + use the :paramref:`.EnvironmentContext.configure.include_name` + parameter to specify a callable which + can filter the tables/schemas that get included. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_object` + + :param render_item: Callable that can be used to override how + any schema item, i.e. column, constraint, type, + etc., is rendered for autogenerate. The callable receives a + string describing the type of object, the object, and + the autogen context. If it returns False, the + default rendering method will be used. If it returns None, + the item will not be rendered in the context of a Table + construct, that is, can be used to skip columns or constraints + within op.create_table():: + + def my_render_column(type_, col, autogen_context): + if type_ == "column" and isinstance(col, MySpecialCol): + return repr(col) + else: + return False + + context.configure( + # ... + render_item = my_render_column + ) + + Available values for the type string include: ``"column"``, + ``"primary_key"``, ``"foreign_key"``, ``"unique"``, ``"check"``, + ``"type"``, ``"server_default"``. + + .. seealso:: + + :ref:`autogen_render_types` + + :param upgrade_token: When autogenerate completes, the text of the + candidate upgrade operations will be present in this template + variable when ``script.py.mako`` is rendered. Defaults to + ``upgrades``. + :param downgrade_token: When autogenerate completes, the text of the + candidate downgrade operations will be present in this + template variable when ``script.py.mako`` is rendered. Defaults to + ``downgrades``. + + :param alembic_module_prefix: When autogenerate refers to Alembic + :mod:`alembic.operations` constructs, this prefix will be used + (i.e. ``op.create_table``) Defaults to "``op.``". + Can be ``None`` to indicate no prefix. + + :param sqlalchemy_module_prefix: When autogenerate refers to + SQLAlchemy + :class:`~sqlalchemy.schema.Column` or type classes, this prefix + will be used + (i.e. ``sa.Column("somename", sa.Integer)``) Defaults to "``sa.``". + Can be ``None`` to indicate no prefix. + Note that when dialect-specific types are rendered, autogenerate + will render them using the dialect module name, i.e. ``mssql.BIT()``, + ``postgresql.UUID()``. + + :param user_module_prefix: When autogenerate refers to a SQLAlchemy + type (e.g. :class:`.TypeEngine`) where the module name is not + under the ``sqlalchemy`` namespace, this prefix will be used + within autogenerate. If left at its default of + ``None``, the ``__module__`` attribute of the type is used to + render the import module. It's a good practice to set this + and to have all custom types be available from a fixed module space, + in order to future-proof migration files against reorganizations + in modules. + + .. seealso:: + + :ref:`autogen_module_prefix` + + :param process_revision_directives: a callable function that will + be passed a structure representing the end result of an autogenerate + or plain "revision" operation, which can be manipulated to affect + how the ``alembic revision`` command ultimately outputs new + revision scripts. The structure of the callable is:: + + def process_revision_directives(context, revision, directives): + pass + + The ``directives`` parameter is a Python list containing + a single :class:`.MigrationScript` directive, which represents + the revision file to be generated. This list as well as its + contents may be freely modified to produce any set of commands. + The section :ref:`customizing_revision` shows an example of + doing this. The ``context`` parameter is the + :class:`.MigrationContext` in use, + and ``revision`` is a tuple of revision identifiers representing the + current revision of the database. + + The callable is invoked at all times when the ``--autogenerate`` + option is passed to ``alembic revision``. If ``--autogenerate`` + is not passed, the callable is invoked only if the + ``revision_environment`` variable is set to True in the Alembic + configuration, in which case the given ``directives`` collection + will contain empty :class:`.UpgradeOps` and :class:`.DowngradeOps` + collections for ``.upgrade_ops`` and ``.downgrade_ops``. The + ``--autogenerate`` option itself can be inferred by inspecting + ``context.config.cmd_opts.autogenerate``. + + The callable function may optionally be an instance of + a :class:`.Rewriter` object. This is a helper object that + assists in the production of autogenerate-stream rewriter functions. + + .. seealso:: + + :ref:`customizing_revision` + + :ref:`autogen_rewriter` + + :paramref:`.command.revision.process_revision_directives` + + Parameters specific to individual backends: + + :param mssql_batch_separator: The "batch separator" which will + be placed between each statement when generating offline SQL Server + migrations. Defaults to ``GO``. Note this is in addition to the + customary semicolon ``;`` at the end of each statement; SQL Server + considers the "batch separator" to denote the end of an + individual statement execution, and cannot group certain + dependent operations in one step. + :param oracle_batch_separator: The "batch separator" which will + be placed between each statement when generating offline + Oracle migrations. Defaults to ``/``. Oracle doesn't add a + semicolon between statements like most other backends. + + """ + opts = self.context_opts + if transactional_ddl is not None: + opts["transactional_ddl"] = transactional_ddl + if output_buffer is not None: + opts["output_buffer"] = output_buffer + elif self.config.output_buffer is not None: + opts["output_buffer"] = self.config.output_buffer + if starting_rev: + opts["starting_rev"] = starting_rev + if tag: + opts["tag"] = tag + if template_args and "template_args" in opts: + opts["template_args"].update(template_args) + opts["transaction_per_migration"] = transaction_per_migration + opts["target_metadata"] = target_metadata + opts["include_name"] = include_name + opts["include_object"] = include_object + opts["include_schemas"] = include_schemas + opts["render_as_batch"] = render_as_batch + opts["upgrade_token"] = upgrade_token + opts["downgrade_token"] = downgrade_token + opts["sqlalchemy_module_prefix"] = sqlalchemy_module_prefix + opts["alembic_module_prefix"] = alembic_module_prefix + opts["user_module_prefix"] = user_module_prefix + opts["literal_binds"] = literal_binds + opts["process_revision_directives"] = process_revision_directives + opts["on_version_apply"] = util.to_tuple(on_version_apply, default=()) + + if render_item is not None: + opts["render_item"] = render_item + opts["compare_type"] = compare_type + if compare_server_default is not None: + opts["compare_server_default"] = compare_server_default + opts["script"] = self.script + + opts.update(kw) + + self._migration_context = MigrationContext.configure( + connection=connection, + url=url, + dialect_name=dialect_name, + environment_context=self, + dialect_opts=dialect_opts, + opts=opts, + ) + + def run_migrations(self, **kw: Any) -> None: + """Run migrations as determined by the current command line + configuration + as well as versioning information present (or not) in the current + database connection (if one is present). + + The function accepts optional ``**kw`` arguments. If these are + passed, they are sent directly to the ``upgrade()`` and + ``downgrade()`` + functions within each target revision file. By modifying the + ``script.py.mako`` file so that the ``upgrade()`` and ``downgrade()`` + functions accept arguments, parameters can be passed here so that + contextual information, usually information to identify a particular + database in use, can be passed from a custom ``env.py`` script + to the migration functions. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + assert self._migration_context is not None + with Operations.context(self._migration_context): + self.get_context().run_migrations(**kw) + + def execute( + self, + sql: Union[Executable, str], + execution_options: Optional[Dict[str, Any]] = None, + ) -> None: + """Execute the given SQL using the current change context. + + The behavior of :meth:`.execute` is the same + as that of :meth:`.Operations.execute`. Please see that + function's documentation for full detail including + caveats and limitations. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + self.get_context().execute(sql, execution_options=execution_options) + + def static_output(self, text: str) -> None: + """Emit text directly to the "offline" SQL stream. + + Typically this is for emitting comments that + start with --. The statement is not treated + as a SQL execution, no ; or batch separator + is added, etc. + + """ + self.get_context().impl.static_output(text) + + def begin_transaction( + self, + ) -> Union[_ProxyTransaction, ContextManager[None]]: + """Return a context manager that will + enclose an operation within a "transaction", + as defined by the environment's offline + and transactional DDL settings. + + e.g.:: + + with context.begin_transaction(): + context.run_migrations() + + :meth:`.begin_transaction` is intended to + "do the right thing" regardless of + calling context: + + * If :meth:`.is_transactional_ddl` is ``False``, + returns a "do nothing" context manager + which otherwise produces no transactional + state or directives. + * If :meth:`.is_offline_mode` is ``True``, + returns a context manager that will + invoke the :meth:`.DefaultImpl.emit_begin` + and :meth:`.DefaultImpl.emit_commit` + methods, which will produce the string + directives ``BEGIN`` and ``COMMIT`` on + the output stream, as rendered by the + target backend (e.g. SQL Server would + emit ``BEGIN TRANSACTION``). + * Otherwise, calls :meth:`sqlalchemy.engine.Connection.begin` + on the current online connection, which + returns a :class:`sqlalchemy.engine.Transaction` + object. This object demarcates a real + transaction and is itself a context manager, + which will roll back if an exception + is raised. + + Note that a custom ``env.py`` script which + has more specific transactional needs can of course + manipulate the :class:`~sqlalchemy.engine.Connection` + directly to produce transactional state in "online" + mode. + + """ + + return self.get_context().begin_transaction() + + def get_context(self) -> MigrationContext: + """Return the current :class:`.MigrationContext` object. + + If :meth:`.EnvironmentContext.configure` has not been + called yet, raises an exception. + + """ + + if self._migration_context is None: + raise Exception("No context has been configured yet.") + return self._migration_context + + def get_bind(self) -> Connection: + """Return the current 'bind'. + + In "online" mode, this is the + :class:`sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + return self.get_context().bind # type: ignore[return-value] + + def get_impl(self) -> DefaultImpl: + return self.get_context().impl diff --git a/venv/lib/python3.8/site-packages/alembic/runtime/migration.py b/venv/lib/python3.8/site-packages/alembic/runtime/migration.py new file mode 100644 index 000000000..28f01c3b3 --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/runtime/migration.py @@ -0,0 +1,1391 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from contextlib import contextmanager +from contextlib import nullcontext +import logging +import sys +from typing import Any +from typing import Callable +from typing import cast +from typing import Collection +from typing import ContextManager +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import Column +from sqlalchemy import literal_column +from sqlalchemy.engine import Engine +from sqlalchemy.engine import url as sqla_url +from sqlalchemy.engine.strategies import MockEngineStrategy + +from .. import ddl +from .. import util +from ..util import sqla_compat +from ..util.compat import EncodedIO +from ..util.sqla_compat import _select + +if TYPE_CHECKING: + from sqlalchemy.engine import Dialect + from sqlalchemy.engine import URL + from sqlalchemy.engine.base import Connection + from sqlalchemy.engine.base import Transaction + from sqlalchemy.engine.mock import MockConnection + from sqlalchemy.sql import Executable + + from .environment import EnvironmentContext + from ..config import Config + from ..script.base import Script + from ..script.base import ScriptDirectory + from ..script.revision import _RevisionOrBase + from ..script.revision import Revision + from ..script.revision import RevisionMap + +log = logging.getLogger(__name__) + + +class _ProxyTransaction: + def __init__(self, migration_context: MigrationContext) -> None: + self.migration_context = migration_context + + @property + def _proxied_transaction(self) -> Optional[Transaction]: + return self.migration_context._transaction + + def rollback(self) -> None: + t = self._proxied_transaction + assert t is not None + t.rollback() + self.migration_context._transaction = None + + def commit(self) -> None: + t = self._proxied_transaction + assert t is not None + t.commit() + self.migration_context._transaction = None + + def __enter__(self) -> _ProxyTransaction: + return self + + def __exit__(self, type_: Any, value: Any, traceback: Any) -> None: + if self._proxied_transaction is not None: + self._proxied_transaction.__exit__(type_, value, traceback) + self.migration_context._transaction = None + + +class MigrationContext: + """Represent the database state made available to a migration + script. + + :class:`.MigrationContext` is the front end to an actual + database connection, or alternatively a string output + stream given a particular database dialect, + from an Alembic perspective. + + When inside the ``env.py`` script, the :class:`.MigrationContext` + is available via the + :meth:`.EnvironmentContext.get_context` method, + which is available at ``alembic.context``:: + + # from within env.py script + from alembic import context + + migration_context = context.get_context() + + For usage outside of an ``env.py`` script, such as for + utility routines that want to check the current version + in the database, the :meth:`.MigrationContext.configure` + method to create new :class:`.MigrationContext` objects. + For example, to get at the current revision in the + database using :meth:`.MigrationContext.get_current_revision`:: + + # in any application, outside of an env.py script + from alembic.migration import MigrationContext + from sqlalchemy import create_engine + + engine = create_engine("postgresql://mydatabase") + conn = engine.connect() + + context = MigrationContext.configure(conn) + current_rev = context.get_current_revision() + + The above context can also be used to produce + Alembic migration operations with an :class:`.Operations` + instance:: + + # in any application, outside of the normal Alembic environment + from alembic.operations import Operations + + op = Operations(context) + op.alter_column("mytable", "somecolumn", nullable=True) + + """ + + def __init__( + self, + dialect: Dialect, + connection: Optional[Connection], + opts: Dict[str, Any], + environment_context: Optional[EnvironmentContext] = None, + ) -> None: + self.environment_context = environment_context + self.opts = opts + self.dialect = dialect + self.script: Optional[ScriptDirectory] = opts.get("script") + as_sql: bool = opts.get("as_sql", False) + transactional_ddl = opts.get("transactional_ddl") + self._transaction_per_migration = opts.get( + "transaction_per_migration", False + ) + self.on_version_apply_callbacks = opts.get("on_version_apply", ()) + self._transaction: Optional[Transaction] = None + + if as_sql: + self.connection = cast( + Optional["Connection"], self._stdout_connection(connection) + ) + assert self.connection is not None + self._in_external_transaction = False + else: + self.connection = connection + self._in_external_transaction = ( + sqla_compat._get_connection_in_transaction(connection) + ) + + self._migrations_fn: Optional[ + Callable[..., Iterable[RevisionStep]] + ] = opts.get("fn") + self.as_sql = as_sql + + self.purge = opts.get("purge", False) + + if "output_encoding" in opts: + self.output_buffer = EncodedIO( + opts.get("output_buffer") + or sys.stdout, # type:ignore[arg-type] + opts["output_encoding"], + ) + else: + self.output_buffer = opts.get("output_buffer", sys.stdout) + + self._user_compare_type = opts.get("compare_type", True) + self._user_compare_server_default = opts.get( + "compare_server_default", False + ) + self.version_table = version_table = opts.get( + "version_table", "alembic_version" + ) + self.version_table_schema = version_table_schema = opts.get( + "version_table_schema", None + ) + + self._start_from_rev: Optional[str] = opts.get("starting_rev") + self.impl = ddl.DefaultImpl.get_by_dialect(dialect)( + dialect, + self.connection, + self.as_sql, + transactional_ddl, + self.output_buffer, + opts, + ) + + self._version = self.impl.version_table_impl( + version_table=version_table, + version_table_schema=version_table_schema, + version_table_pk=opts.get("version_table_pk", True), + ) + + log.info("Context impl %s.", self.impl.__class__.__name__) + if self.as_sql: + log.info("Generating static SQL") + log.info( + "Will assume %s DDL.", + ( + "transactional" + if self.impl.transactional_ddl + else "non-transactional" + ), + ) + + @classmethod + def configure( + cls, + connection: Optional[Connection] = None, + url: Optional[Union[str, URL]] = None, + dialect_name: Optional[str] = None, + dialect: Optional[Dialect] = None, + environment_context: Optional[EnvironmentContext] = None, + dialect_opts: Optional[Dict[str, str]] = None, + opts: Optional[Any] = None, + ) -> MigrationContext: + """Create a new :class:`.MigrationContext`. + + This is a factory method usually called + by :meth:`.EnvironmentContext.configure`. + + :param connection: a :class:`~sqlalchemy.engine.Connection` + to use for SQL execution in "online" mode. When present, + is also used to determine the type of dialect in use. + :param url: a string database url, or a + :class:`sqlalchemy.engine.url.URL` object. + The type of dialect to be used will be derived from this if + ``connection`` is not passed. + :param dialect_name: string name of a dialect, such as + "postgresql", "mssql", etc. The type of dialect to be used will be + derived from this if ``connection`` and ``url`` are not passed. + :param opts: dictionary of options. Most other options + accepted by :meth:`.EnvironmentContext.configure` are passed via + this dictionary. + + """ + if opts is None: + opts = {} + if dialect_opts is None: + dialect_opts = {} + + if connection: + if isinstance(connection, Engine): + raise util.CommandError( + "'connection' argument to configure() is expected " + "to be a sqlalchemy.engine.Connection instance, " + "got %r" % connection, + ) + + dialect = connection.dialect + elif url: + url_obj = sqla_url.make_url(url) + dialect = url_obj.get_dialect()(**dialect_opts) + elif dialect_name: + url_obj = sqla_url.make_url("%s://" % dialect_name) + dialect = url_obj.get_dialect()(**dialect_opts) + elif not dialect: + raise Exception("Connection, url, or dialect_name is required.") + assert dialect is not None + return MigrationContext(dialect, connection, opts, environment_context) + + @contextmanager + def autocommit_block(self) -> Iterator[None]: + """Enter an "autocommit" block, for databases that support AUTOCOMMIT + isolation levels. + + This special directive is intended to support the occasional database + DDL or system operation that specifically has to be run outside of + any kind of transaction block. The PostgreSQL database platform + is the most common target for this style of operation, as many + of its DDL operations must be run outside of transaction blocks, even + though the database overall supports transactional DDL. + + The method is used as a context manager within a migration script, by + calling on :meth:`.Operations.get_context` to retrieve the + :class:`.MigrationContext`, then invoking + :meth:`.MigrationContext.autocommit_block` using the ``with:`` + statement:: + + def upgrade(): + with op.get_context().autocommit_block(): + op.execute("ALTER TYPE mood ADD VALUE 'soso'") + + Above, a PostgreSQL "ALTER TYPE..ADD VALUE" directive is emitted, + which must be run outside of a transaction block at the database level. + The :meth:`.MigrationContext.autocommit_block` method makes use of the + SQLAlchemy ``AUTOCOMMIT`` isolation level setting, which against the + psycogp2 DBAPI corresponds to the ``connection.autocommit`` setting, + to ensure that the database driver is not inside of a DBAPI level + transaction block. + + .. warning:: + + As is necessary, **the database transaction preceding the block is + unconditionally committed**. This means that the run of migrations + preceding the operation will be committed, before the overall + migration operation is complete. + + It is recommended that when an application includes migrations with + "autocommit" blocks, that + :paramref:`.EnvironmentContext.transaction_per_migration` be used + so that the calling environment is tuned to expect short per-file + migrations whether or not one of them has an autocommit block. + + + """ + _in_connection_transaction = self._in_connection_transaction() + + if self.impl.transactional_ddl and self.as_sql: + self.impl.emit_commit() + + elif _in_connection_transaction: + assert self._transaction is not None + + self._transaction.commit() + self._transaction = None + + if not self.as_sql: + assert self.connection is not None + current_level = self.connection.get_isolation_level() + base_connection = self.connection + + # in 1.3 and 1.4 non-future mode, the connection gets switched + # out. we can use the base connection with the new mode + # except that it will not know it's in "autocommit" and will + # emit deprecation warnings when an autocommit action takes + # place. + self.connection = self.impl.connection = ( + base_connection.execution_options(isolation_level="AUTOCOMMIT") + ) + + # sqlalchemy future mode will "autobegin" in any case, so take + # control of that "transaction" here + fake_trans: Optional[Transaction] = self.connection.begin() + else: + fake_trans = None + try: + yield + finally: + if not self.as_sql: + assert self.connection is not None + if fake_trans is not None: + fake_trans.commit() + self.connection.execution_options( + isolation_level=current_level + ) + self.connection = self.impl.connection = base_connection + + if self.impl.transactional_ddl and self.as_sql: + self.impl.emit_begin() + + elif _in_connection_transaction: + assert self.connection is not None + self._transaction = self.connection.begin() + + def begin_transaction( + self, _per_migration: bool = False + ) -> Union[_ProxyTransaction, ContextManager[None]]: + """Begin a logical transaction for migration operations. + + This method is used within an ``env.py`` script to demarcate where + the outer "transaction" for a series of migrations begins. Example:: + + def run_migrations_online(): + connectable = create_engine(...) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + Above, :meth:`.MigrationContext.begin_transaction` is used to demarcate + where the outer logical transaction occurs around the + :meth:`.MigrationContext.run_migrations` operation. + + A "Logical" transaction means that the operation may or may not + correspond to a real database transaction. If the target database + supports transactional DDL (or + :paramref:`.EnvironmentContext.configure.transactional_ddl` is true), + the :paramref:`.EnvironmentContext.configure.transaction_per_migration` + flag is not set, and the migration is against a real database + connection (as opposed to using "offline" ``--sql`` mode), a real + transaction will be started. If ``--sql`` mode is in effect, the + operation would instead correspond to a string such as "BEGIN" being + emitted to the string output. + + The returned object is a Python context manager that should only be + used in the context of a ``with:`` statement as indicated above. + The object has no other guaranteed API features present. + + .. seealso:: + + :meth:`.MigrationContext.autocommit_block` + + """ + + if self._in_external_transaction: + return nullcontext() + + if self.impl.transactional_ddl: + transaction_now = _per_migration == self._transaction_per_migration + else: + transaction_now = _per_migration is True + + if not transaction_now: + return nullcontext() + + elif not self.impl.transactional_ddl: + assert _per_migration + + if self.as_sql: + return nullcontext() + else: + # track our own notion of a "transaction block", which must be + # committed when complete. Don't rely upon whether or not the + # SQLAlchemy connection reports as "in transaction"; this + # because SQLAlchemy future connection features autobegin + # behavior, so it may already be in a transaction from our + # emitting of queries like "has_version_table", etc. While we + # could track these operations as well, that leaves open the + # possibility of new operations or other things happening in + # the user environment that still may be triggering + # "autobegin". + + in_transaction = self._transaction is not None + + if in_transaction: + return nullcontext() + else: + assert self.connection is not None + self._transaction = ( + sqla_compat._safe_begin_connection_transaction( + self.connection + ) + ) + return _ProxyTransaction(self) + elif self.as_sql: + + @contextmanager + def begin_commit(): + self.impl.emit_begin() + yield + self.impl.emit_commit() + + return begin_commit() + else: + assert self.connection is not None + self._transaction = sqla_compat._safe_begin_connection_transaction( + self.connection + ) + return _ProxyTransaction(self) + + def get_current_revision(self) -> Optional[str]: + """Return the current revision, usually that which is present + in the ``alembic_version`` table in the database. + + This method intends to be used only for a migration stream that + does not contain unmerged branches in the target database; + if there are multiple branches present, an exception is raised. + The :meth:`.MigrationContext.get_current_heads` should be preferred + over this method going forward in order to be compatible with + branch migration support. + + If this :class:`.MigrationContext` was configured in "offline" + mode, that is with ``as_sql=True``, the ``starting_rev`` + parameter is returned instead, if any. + + """ + heads = self.get_current_heads() + if len(heads) == 0: + return None + elif len(heads) > 1: + raise util.CommandError( + "Version table '%s' has more than one head present; " + "please use get_current_heads()" % self.version_table + ) + else: + return heads[0] + + def get_current_heads(self) -> Tuple[str, ...]: + """Return a tuple of the current 'head versions' that are represented + in the target database. + + For a migration stream without branches, this will be a single + value, synonymous with that of + :meth:`.MigrationContext.get_current_revision`. However when multiple + unmerged branches exist within the target database, the returned tuple + will contain a value for each head. + + If this :class:`.MigrationContext` was configured in "offline" + mode, that is with ``as_sql=True``, the ``starting_rev`` + parameter is returned in a one-length tuple. + + If no version table is present, or if there are no revisions + present, an empty tuple is returned. + + """ + if self.as_sql: + start_from_rev: Any = self._start_from_rev + if start_from_rev == "base": + start_from_rev = None + elif start_from_rev is not None and self.script: + start_from_rev = [ + self.script.get_revision(sfr).revision + for sfr in util.to_list(start_from_rev) + if sfr not in (None, "base") + ] + return util.to_tuple(start_from_rev, default=()) + else: + if self._start_from_rev: + raise util.CommandError( + "Can't specify current_rev to context " + "when using a database connection" + ) + if not self._has_version_table(): + return () + assert self.connection is not None + return tuple( + row[0] + for row in self.connection.execute( + _select(self._version.c.version_num) + ) + ) + + def _ensure_version_table(self, purge: bool = False) -> None: + with sqla_compat._ensure_scope_for_ddl(self.connection): + assert self.connection is not None + self._version.create(self.connection, checkfirst=True) + if purge: + assert self.connection is not None + self.connection.execute(self._version.delete()) + + def _has_version_table(self) -> bool: + assert self.connection is not None + return sqla_compat._connectable_has_table( + self.connection, self.version_table, self.version_table_schema + ) + + def stamp(self, script_directory: ScriptDirectory, revision: str) -> None: + """Stamp the version table with a specific revision. + + This method calculates those branches to which the given revision + can apply, and updates those branches as though they were migrated + towards that revision (either up or down). If no current branches + include the revision, it is added as a new branch head. + + """ + heads = self.get_current_heads() + if not self.as_sql and not heads: + self._ensure_version_table() + head_maintainer = HeadMaintainer(self, heads) + for step in script_directory._stamp_revs(revision, heads): + head_maintainer.update_to_step(step) + + def run_migrations(self, **kw: Any) -> None: + r"""Run the migration scripts established for this + :class:`.MigrationContext`, if any. + + The commands in :mod:`alembic.command` will set up a function + that is ultimately passed to the :class:`.MigrationContext` + as the ``fn`` argument. This function represents the "work" + that will be done when :meth:`.MigrationContext.run_migrations` + is called, typically from within the ``env.py`` script of the + migration environment. The "work function" then provides an iterable + of version callables and other version information which + in the case of the ``upgrade`` or ``downgrade`` commands are the + list of version scripts to invoke. Other commands yield nothing, + in the case that a command wants to run some other operation + against the database such as the ``current`` or ``stamp`` commands. + + :param \**kw: keyword arguments here will be passed to each + migration callable, that is the ``upgrade()`` or ``downgrade()`` + method within revision scripts. + + """ + self.impl.start_migrations() + + heads: Tuple[str, ...] + if self.purge: + if self.as_sql: + raise util.CommandError("Can't use --purge with --sql mode") + self._ensure_version_table(purge=True) + heads = () + else: + heads = self.get_current_heads() + + dont_mutate = self.opts.get("dont_mutate", False) + + if not self.as_sql and not heads and not dont_mutate: + self._ensure_version_table() + + head_maintainer = HeadMaintainer(self, heads) + + assert self._migrations_fn is not None + for step in self._migrations_fn(heads, self): + with self.begin_transaction(_per_migration=True): + if self.as_sql and not head_maintainer.heads: + # for offline mode, include a CREATE TABLE from + # the base + assert self.connection is not None + self._version.create(self.connection) + log.info("Running %s", step) + if self.as_sql: + self.impl.static_output( + "-- Running %s" % (step.short_log,) + ) + step.migration_fn(**kw) + + # previously, we wouldn't stamp per migration + # if we were in a transaction, however given the more + # complex model that involves any number of inserts + # and row-targeted updates and deletes, it's simpler for now + # just to run the operations on every version + head_maintainer.update_to_step(step) + for callback in self.on_version_apply_callbacks: + callback( + ctx=self, + step=step.info, + heads=set(head_maintainer.heads), + run_args=kw, + ) + + if self.as_sql and not head_maintainer.heads: + assert self.connection is not None + self._version.drop(self.connection) + + def _in_connection_transaction(self) -> bool: + try: + meth = self.connection.in_transaction # type:ignore[union-attr] + except AttributeError: + return False + else: + return meth() + + def execute( + self, + sql: Union[Executable, str], + execution_options: Optional[Dict[str, Any]] = None, + ) -> None: + """Execute a SQL construct or string statement. + + The underlying execution mechanics are used, that is + if this is "offline mode" the SQL is written to the + output buffer, otherwise the SQL is emitted on + the current SQLAlchemy connection. + + """ + self.impl._exec(sql, execution_options) + + def _stdout_connection( + self, connection: Optional[Connection] + ) -> MockConnection: + def dump(construct, *multiparams, **params): + self.impl._exec(construct) + + return MockEngineStrategy.MockConnection(self.dialect, dump) + + @property + def bind(self) -> Optional[Connection]: + """Return the current "bind". + + In online mode, this is an instance of + :class:`sqlalchemy.engine.Connection`, and is suitable + for ad-hoc execution of any kind of usage described + in SQLAlchemy Core documentation as well as + for usage with the :meth:`sqlalchemy.schema.Table.create` + and :meth:`sqlalchemy.schema.MetaData.create_all` methods + of :class:`~sqlalchemy.schema.Table`, + :class:`~sqlalchemy.schema.MetaData`. + + Note that when "standard output" mode is enabled, + this bind will be a "mock" connection handler that cannot + return results and is only appropriate for a very limited + subset of commands. + + """ + return self.connection + + @property + def config(self) -> Optional[Config]: + """Return the :class:`.Config` used by the current environment, + if any.""" + + if self.environment_context: + return self.environment_context.config + else: + return None + + def _compare_type( + self, inspector_column: Column[Any], metadata_column: Column + ) -> bool: + if self._user_compare_type is False: + return False + + if callable(self._user_compare_type): + user_value = self._user_compare_type( + self, + inspector_column, + metadata_column, + inspector_column.type, + metadata_column.type, + ) + if user_value is not None: + return user_value + + return self.impl.compare_type(inspector_column, metadata_column) + + def _compare_server_default( + self, + inspector_column: Column[Any], + metadata_column: Column[Any], + rendered_metadata_default: Optional[str], + rendered_column_default: Optional[str], + ) -> bool: + if self._user_compare_server_default is False: + return False + + if callable(self._user_compare_server_default): + user_value = self._user_compare_server_default( + self, + inspector_column, + metadata_column, + rendered_column_default, + metadata_column.server_default, + rendered_metadata_default, + ) + if user_value is not None: + return user_value + + return self.impl.compare_server_default( + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_column_default, + ) + + +class HeadMaintainer: + def __init__(self, context: MigrationContext, heads: Any) -> None: + self.context = context + self.heads = set(heads) + + def _insert_version(self, version: str) -> None: + assert version not in self.heads + self.heads.add(version) + + self.context.impl._exec( + self.context._version.insert().values( + version_num=literal_column("'%s'" % version) + ) + ) + + def _delete_version(self, version: str) -> None: + self.heads.remove(version) + + ret = self.context.impl._exec( + self.context._version.delete().where( + self.context._version.c.version_num + == literal_column("'%s'" % version) + ) + ) + + if ( + not self.context.as_sql + and self.context.dialect.supports_sane_rowcount + and ret is not None + and ret.rowcount != 1 + ): + raise util.CommandError( + "Online migration expected to match one " + "row when deleting '%s' in '%s'; " + "%d found" + % (version, self.context.version_table, ret.rowcount) + ) + + def _update_version(self, from_: str, to_: str) -> None: + assert to_ not in self.heads + self.heads.remove(from_) + self.heads.add(to_) + + ret = self.context.impl._exec( + self.context._version.update() + .values(version_num=literal_column("'%s'" % to_)) + .where( + self.context._version.c.version_num + == literal_column("'%s'" % from_) + ) + ) + + if ( + not self.context.as_sql + and self.context.dialect.supports_sane_rowcount + and ret is not None + and ret.rowcount != 1 + ): + raise util.CommandError( + "Online migration expected to match one " + "row when updating '%s' to '%s' in '%s'; " + "%d found" + % (from_, to_, self.context.version_table, ret.rowcount) + ) + + def update_to_step(self, step: Union[RevisionStep, StampStep]) -> None: + if step.should_delete_branch(self.heads): + vers = step.delete_version_num + log.debug("branch delete %s", vers) + self._delete_version(vers) + elif step.should_create_branch(self.heads): + vers = step.insert_version_num + log.debug("new branch insert %s", vers) + self._insert_version(vers) + elif step.should_merge_branches(self.heads): + # delete revs, update from rev, update to rev + ( + delete_revs, + update_from_rev, + update_to_rev, + ) = step.merge_branch_idents(self.heads) + log.debug( + "merge, delete %s, update %s to %s", + delete_revs, + update_from_rev, + update_to_rev, + ) + for delrev in delete_revs: + self._delete_version(delrev) + self._update_version(update_from_rev, update_to_rev) + elif step.should_unmerge_branches(self.heads): + ( + update_from_rev, + update_to_rev, + insert_revs, + ) = step.unmerge_branch_idents(self.heads) + log.debug( + "unmerge, insert %s, update %s to %s", + insert_revs, + update_from_rev, + update_to_rev, + ) + for insrev in insert_revs: + self._insert_version(insrev) + self._update_version(update_from_rev, update_to_rev) + else: + from_, to_ = step.update_version_num(self.heads) + log.debug("update %s to %s", from_, to_) + self._update_version(from_, to_) + + +class MigrationInfo: + """Exposes information about a migration step to a callback listener. + + The :class:`.MigrationInfo` object is available exclusively for the + benefit of the :paramref:`.EnvironmentContext.on_version_apply` + callback hook. + + """ + + is_upgrade: bool + """True/False: indicates whether this operation ascends or descends the + version tree.""" + + is_stamp: bool + """True/False: indicates whether this operation is a stamp (i.e. whether + it results in any actual database operations).""" + + up_revision_id: Optional[str] + """Version string corresponding to :attr:`.Revision.revision`. + + In the case of a stamp operation, it is advised to use the + :attr:`.MigrationInfo.up_revision_ids` tuple as a stamp operation can + make a single movement from one or more branches down to a single + branchpoint, in which case there will be multiple "up" revisions. + + .. seealso:: + + :attr:`.MigrationInfo.up_revision_ids` + + """ + + up_revision_ids: Tuple[str, ...] + """Tuple of version strings corresponding to :attr:`.Revision.revision`. + + In the majority of cases, this tuple will be a single value, synonymous + with the scalar value of :attr:`.MigrationInfo.up_revision_id`. + It can be multiple revision identifiers only in the case of an + ``alembic stamp`` operation which is moving downwards from multiple + branches down to their common branch point. + + """ + + down_revision_ids: Tuple[str, ...] + """Tuple of strings representing the base revisions of this migration step. + + If empty, this represents a root revision; otherwise, the first item + corresponds to :attr:`.Revision.down_revision`, and the rest are inferred + from dependencies. + """ + + revision_map: RevisionMap + """The revision map inside of which this operation occurs.""" + + def __init__( + self, + revision_map: RevisionMap, + is_upgrade: bool, + is_stamp: bool, + up_revisions: Union[str, Tuple[str, ...]], + down_revisions: Union[str, Tuple[str, ...]], + ) -> None: + self.revision_map = revision_map + self.is_upgrade = is_upgrade + self.is_stamp = is_stamp + self.up_revision_ids = util.to_tuple(up_revisions, default=()) + if self.up_revision_ids: + self.up_revision_id = self.up_revision_ids[0] + else: + # this should never be the case with + # "upgrade", "downgrade", or "stamp" as we are always + # measuring movement in terms of at least one upgrade version + self.up_revision_id = None + self.down_revision_ids = util.to_tuple(down_revisions, default=()) + + @property + def is_migration(self) -> bool: + """True/False: indicates whether this operation is a migration. + + At present this is true if and only the migration is not a stamp. + If other operation types are added in the future, both this attribute + and :attr:`~.MigrationInfo.is_stamp` will be false. + """ + return not self.is_stamp + + @property + def source_revision_ids(self) -> Tuple[str, ...]: + """Active revisions before this migration step is applied.""" + return ( + self.down_revision_ids if self.is_upgrade else self.up_revision_ids + ) + + @property + def destination_revision_ids(self) -> Tuple[str, ...]: + """Active revisions after this migration step is applied.""" + return ( + self.up_revision_ids if self.is_upgrade else self.down_revision_ids + ) + + @property + def up_revision(self) -> Optional[Revision]: + """Get :attr:`~.MigrationInfo.up_revision_id` as + a :class:`.Revision`. + + """ + return self.revision_map.get_revision(self.up_revision_id) + + @property + def up_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~.MigrationInfo.up_revision_ids` as a + :class:`.Revision`.""" + return self.revision_map.get_revisions(self.up_revision_ids) + + @property + def down_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~.MigrationInfo.down_revision_ids` as a tuple of + :class:`Revisions <.Revision>`.""" + return self.revision_map.get_revisions(self.down_revision_ids) + + @property + def source_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~MigrationInfo.source_revision_ids` as a tuple of + :class:`Revisions <.Revision>`.""" + return self.revision_map.get_revisions(self.source_revision_ids) + + @property + def destination_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~MigrationInfo.destination_revision_ids` as a tuple of + :class:`Revisions <.Revision>`.""" + return self.revision_map.get_revisions(self.destination_revision_ids) + + +class MigrationStep: + from_revisions_no_deps: Tuple[str, ...] + to_revisions_no_deps: Tuple[str, ...] + is_upgrade: bool + migration_fn: Any + + if TYPE_CHECKING: + + @property + def doc(self) -> Optional[str]: ... + + @property + def name(self) -> str: + return self.migration_fn.__name__ + + @classmethod + def upgrade_from_script( + cls, revision_map: RevisionMap, script: Script + ) -> RevisionStep: + return RevisionStep(revision_map, script, True) + + @classmethod + def downgrade_from_script( + cls, revision_map: RevisionMap, script: Script + ) -> RevisionStep: + return RevisionStep(revision_map, script, False) + + @property + def is_downgrade(self) -> bool: + return not self.is_upgrade + + @property + def short_log(self) -> str: + return "%s %s -> %s" % ( + self.name, + util.format_as_comma(self.from_revisions_no_deps), + util.format_as_comma(self.to_revisions_no_deps), + ) + + def __str__(self): + if self.doc: + return "%s %s -> %s, %s" % ( + self.name, + util.format_as_comma(self.from_revisions_no_deps), + util.format_as_comma(self.to_revisions_no_deps), + self.doc, + ) + else: + return self.short_log + + +class RevisionStep(MigrationStep): + def __init__( + self, revision_map: RevisionMap, revision: Script, is_upgrade: bool + ) -> None: + self.revision_map = revision_map + self.revision = revision + self.is_upgrade = is_upgrade + if is_upgrade: + self.migration_fn = revision.module.upgrade + else: + self.migration_fn = revision.module.downgrade + + def __repr__(self): + return "RevisionStep(%r, is_upgrade=%r)" % ( + self.revision.revision, + self.is_upgrade, + ) + + def __eq__(self, other: object) -> bool: + return ( + isinstance(other, RevisionStep) + and other.revision == self.revision + and self.is_upgrade == other.is_upgrade + ) + + @property + def doc(self) -> Optional[str]: + return self.revision.doc + + @property + def from_revisions(self) -> Tuple[str, ...]: + if self.is_upgrade: + return self.revision._normalized_down_revisions + else: + return (self.revision.revision,) + + @property + def from_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + if self.is_upgrade: + return self.revision._versioned_down_revisions + else: + return (self.revision.revision,) + + @property + def to_revisions(self) -> Tuple[str, ...]: + if self.is_upgrade: + return (self.revision.revision,) + else: + return self.revision._normalized_down_revisions + + @property + def to_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + if self.is_upgrade: + return (self.revision.revision,) + else: + return self.revision._versioned_down_revisions + + @property + def _has_scalar_down_revision(self) -> bool: + return len(self.revision._normalized_down_revisions) == 1 + + def should_delete_branch(self, heads: Set[str]) -> bool: + """A delete is when we are a. in a downgrade and b. + we are going to the "base" or we are going to a version that + is implied as a dependency on another version that is remaining. + + """ + if not self.is_downgrade: + return False + + if self.revision.revision not in heads: + return False + + downrevs = self.revision._normalized_down_revisions + + if not downrevs: + # is a base + return True + else: + # determine what the ultimate "to_revisions" for an + # unmerge would be. If there are none, then we're a delete. + to_revisions = self._unmerge_to_revisions(heads) + return not to_revisions + + def merge_branch_idents( + self, heads: Set[str] + ) -> Tuple[List[str], str, str]: + other_heads = set(heads).difference(self.from_revisions) + + if other_heads: + ancestors = { + r.revision + for r in self.revision_map._get_ancestor_nodes( + self.revision_map.get_revisions(other_heads), check=False + ) + } + from_revisions = list( + set(self.from_revisions).difference(ancestors) + ) + else: + from_revisions = list(self.from_revisions) + + return ( + # delete revs, update from rev, update to rev + list(from_revisions[0:-1]), + from_revisions[-1], + self.to_revisions[0], + ) + + def _unmerge_to_revisions(self, heads: Set[str]) -> Tuple[str, ...]: + other_heads = set(heads).difference([self.revision.revision]) + if other_heads: + ancestors = { + r.revision + for r in self.revision_map._get_ancestor_nodes( + self.revision_map.get_revisions(other_heads), check=False + ) + } + return tuple(set(self.to_revisions).difference(ancestors)) + else: + # for each revision we plan to return, compute its ancestors + # (excluding self), and remove those from the final output since + # they are already accounted for. + ancestors = { + r.revision + for to_revision in self.to_revisions + for r in self.revision_map._get_ancestor_nodes( + self.revision_map.get_revisions(to_revision), check=False + ) + if r.revision != to_revision + } + return tuple(set(self.to_revisions).difference(ancestors)) + + def unmerge_branch_idents( + self, heads: Set[str] + ) -> Tuple[str, str, Tuple[str, ...]]: + to_revisions = self._unmerge_to_revisions(heads) + + return ( + # update from rev, update to rev, insert revs + self.from_revisions[0], + to_revisions[-1], + to_revisions[0:-1], + ) + + def should_create_branch(self, heads: Set[str]) -> bool: + if not self.is_upgrade: + return False + + downrevs = self.revision._normalized_down_revisions + + if not downrevs: + # is a base + return True + else: + # none of our downrevs are present, so... + # we have to insert our version. This is true whether + # or not there is only one downrev, or multiple (in the latter + # case, we're a merge point.) + if not heads.intersection(downrevs): + return True + else: + return False + + def should_merge_branches(self, heads: Set[str]) -> bool: + if not self.is_upgrade: + return False + + downrevs = self.revision._normalized_down_revisions + + if len(downrevs) > 1 and len(heads.intersection(downrevs)) > 1: + return True + + return False + + def should_unmerge_branches(self, heads: Set[str]) -> bool: + if not self.is_downgrade: + return False + + downrevs = self.revision._normalized_down_revisions + + if self.revision.revision in heads and len(downrevs) > 1: + return True + + return False + + def update_version_num(self, heads: Set[str]) -> Tuple[str, str]: + if not self._has_scalar_down_revision: + downrev = heads.intersection( + self.revision._normalized_down_revisions + ) + assert ( + len(downrev) == 1 + ), "Can't do an UPDATE because downrevision is ambiguous" + down_revision = list(downrev)[0] + else: + down_revision = self.revision._normalized_down_revisions[0] + + if self.is_upgrade: + return down_revision, self.revision.revision + else: + return self.revision.revision, down_revision + + @property + def delete_version_num(self) -> str: + return self.revision.revision + + @property + def insert_version_num(self) -> str: + return self.revision.revision + + @property + def info(self) -> MigrationInfo: + return MigrationInfo( + revision_map=self.revision_map, + up_revisions=self.revision.revision, + down_revisions=self.revision._normalized_down_revisions, + is_upgrade=self.is_upgrade, + is_stamp=False, + ) + + +class StampStep(MigrationStep): + def __init__( + self, + from_: Optional[Union[str, Collection[str]]], + to_: Optional[Union[str, Collection[str]]], + is_upgrade: bool, + branch_move: bool, + revision_map: Optional[RevisionMap] = None, + ) -> None: + self.from_: Tuple[str, ...] = util.to_tuple(from_, default=()) + self.to_: Tuple[str, ...] = util.to_tuple(to_, default=()) + self.is_upgrade = is_upgrade + self.branch_move = branch_move + self.migration_fn = self.stamp_revision + self.revision_map = revision_map + + doc: Optional[str] = None + + def stamp_revision(self, **kw: Any) -> None: + return None + + def __eq__(self, other): + return ( + isinstance(other, StampStep) + and other.from_revisions == self.from_revisions + and other.to_revisions == self.to_revisions + and other.branch_move == self.branch_move + and self.is_upgrade == other.is_upgrade + ) + + @property + def from_revisions(self): + return self.from_ + + @property + def to_revisions(self) -> Tuple[str, ...]: + return self.to_ + + @property + def from_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + return self.from_ + + @property + def to_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + return self.to_ + + @property + def delete_version_num(self) -> str: + assert len(self.from_) == 1 + return self.from_[0] + + @property + def insert_version_num(self) -> str: + assert len(self.to_) == 1 + return self.to_[0] + + def update_version_num(self, heads: Set[str]) -> Tuple[str, str]: + assert len(self.from_) == 1 + assert len(self.to_) == 1 + return self.from_[0], self.to_[0] + + def merge_branch_idents( + self, heads: Union[Set[str], List[str]] + ) -> Union[Tuple[List[Any], str, str], Tuple[List[str], str, str]]: + return ( + # delete revs, update from rev, update to rev + list(self.from_[0:-1]), + self.from_[-1], + self.to_[0], + ) + + def unmerge_branch_idents( + self, heads: Set[str] + ) -> Tuple[str, str, List[str]]: + return ( + # update from rev, update to rev, insert revs + self.from_[0], + self.to_[-1], + list(self.to_[0:-1]), + ) + + def should_delete_branch(self, heads: Set[str]) -> bool: + # TODO: we probably need to look for self.to_ inside of heads, + # in a similar manner as should_create_branch, however we have + # no tests for this yet (stamp downgrades w/ branches) + return self.is_downgrade and self.branch_move + + def should_create_branch(self, heads: Set[str]) -> Union[Set[str], bool]: + return ( + self.is_upgrade + and (self.branch_move or set(self.from_).difference(heads)) + and set(self.to_).difference(heads) + ) + + def should_merge_branches(self, heads: Set[str]) -> bool: + return len(self.from_) > 1 + + def should_unmerge_branches(self, heads: Set[str]) -> bool: + return len(self.to_) > 1 + + @property + def info(self) -> MigrationInfo: + up, down = ( + (self.to_, self.from_) + if self.is_upgrade + else (self.from_, self.to_) + ) + assert self.revision_map is not None + return MigrationInfo( + revision_map=self.revision_map, + up_revisions=up, + down_revisions=down, + is_upgrade=self.is_upgrade, + is_stamp=True, + ) diff --git a/venv/lib/python3.8/site-packages/alembic/script/__init__.py b/venv/lib/python3.8/site-packages/alembic/script/__init__.py new file mode 100644 index 000000000..d78f3f1dc --- /dev/null +++ b/venv/lib/python3.8/site-packages/alembic/script/__init__.py @@ -0,0 +1,4 @@ +from .base import Script +from .base import ScriptDirectory + +__all__ = ["ScriptDirectory", "Script"] diff --git a/venv/lib/python3.8/site-packages/alembic/script/__pycache__/__init__.cpython-38.pyc b/venv/lib/python3.8/site-packages/alembic/script/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db1eb0e799120138719fc4dbd3c0673cce2ff6ee GIT binary patch literal 309 zcmYk1F;2uV5Jhb#1cY`4hoDK3onjjbgebN|G%K1kmJ<)V);fu0n@HhC+=El3rQ!-y zi~|KD&G-ABFKhg~USBe*>klxR&d+52mmu>%9d1Z8)7-L-*PJI7#G)3&na&TQ&h*)y zmBkmQXvybd-+JRB(bX^QjK?+(e)1V5e0ut+%;&sO0T;L2&*xGqYo&Z=zDT(w-w}>O zhk$Q*GH79kBig}r*hhF&O#nB=7aB@m=7$(AWvmQ0C~Yzt;YO14*Gmdl+1vEX7C zyfYxh^+HZX#;-<}T)T;qFe%`=X_V%)P1?FmlboKDx^0@eX?jkQrqeWSoYT{|J$)Xh zNobqw_x*Qf_5sjVa@xh&yLax~_xsR8gP$xO9!n$) z?}(X5I0?sSC6s#-`(wo=uU{HCjE`OQ=_@|&$@@oTkm%lT?v z%A{I_%|wR3q_b(h2^5#L?ijrf$a4e>qIJrdvEx?_27 zb+5#CwDv6DS-n%@I}yLDdY6%Kx4&U1h1zztb}ipsy&G@K&Tho*sx9$7i0`ZJlTvrI z_AlR4y+`7E5kF8pAn`j}2bb@y-YfCD5TCA28;S0t?ww0BsQrgAcXzic%lB39Lym-F zBYuDNek0M|C2wX2-puMZA4# zzN^XVyPf2zL}m6Hgr$lR6zc7E$FKX%PTRv{snKcs?#upiykpY1rSE%)@5 z6&Fv*$JRlYkKgPwzN_kfM=|X~O-aiRpwB^AtA3MBx2b1&V~tJRwAreCX7X6EjDX>R38Wv0>VRNi->yXUq!4s^>i zFV+t&969u!Gxt|m=s-95(&hWR<%2WIy;SjP2WPz2>iLS-&ClBR^=968)V{yc9qY$? zFCA@g`3FB^czBHHO;imhQ8gW-nshD4bdrwcq!8lQO*v^dy_C6XF65jH{<2Qa z$>X=+6!DvO#+=gif-~-vucfM4mw6_x8_uLNbc4vpA6`h^(yvx~r z&8m*MC1($|&K=iHXRmXoYQ+z(vzLef$K@ObIv(6!To^uZ4c^gU`k>__f@5J+V)ct@wKj=J!=N-|C(*kT&QnO) zi${Qb6nc2t zc^W zgKi!@st3t|E~w73UDuN_7fwcVd8MO#dt^$bf=xw$q=B34u!zP~anA7Ndu^RQQyF