summaryrefslogtreecommitdiffstats
path: root/third_party/python/virtualenv
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/virtualenv')
-rw-r--r--third_party/python/virtualenv/README_MOZILLA10
-rw-r--r--third_party/python/virtualenv/__main__.py168
-rw-r--r--third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/LICENSE.txt23
-rw-r--r--third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/METADATA264
-rw-r--r--third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py608
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/__init__.py6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/__init__.py1473
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/helpers.py274
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/LICENSE7
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/METADATA259
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/RECORD9
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/top_level.txt2
-rw-r--r--third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser.py61
-rw-r--r--third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/LICENSE.txt122
-rw-r--r--third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/METADATA70
-rw-r--r--third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2.py518
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/METADATA24
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/RECORD26
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/WHEEL5
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py23
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py41
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py764
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.cfg84
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py786
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py2607
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py1120
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py1339
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py516
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py1302
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py393
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py131
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py1056
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py355
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py419
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t32.exebin0 -> 96768 bytes
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t64.exebin0 -> 105984 bytes
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py1761
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py736
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w32.exebin0 -> 90112 bytes
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w64.exebin0 -> 99840 bytes
-rw-r--r--third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py1018
-rw-r--r--third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/LICENSE24
-rw-r--r--third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/METADATA156
-rw-r--r--third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/WHEEL5
-rw-r--r--third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py451
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/LICENSE13
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/METADATA65
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/RECORD21
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/__init__.py541
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/_compat.py143
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/LICENSE13
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/METADATA73
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/RECORD7
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/__init__.py644
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/_compat.py152
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/LICENSE13
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/METADATA94
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/RECORD7
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/WHEEL5
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/__init__.py631
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/_compat.py75
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/LICENSE13
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/METADATA49
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/RECORD39
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/__init__.py36
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_compat.py23
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py2.py270
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py3.py312
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/abc.py58
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/version.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/LICENSE13
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/METADATA41
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/RECORD42
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/__init__.py53
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_common.py120
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_compat.py139
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py2.py107
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py3.py164
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/abc.py142
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/py.typed0
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/readers.py123
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/trees.py6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/LICENSE13
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/METADATA41
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/RECORD42
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/__init__.py53
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_common.py120
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_compat.py139
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py2.py107
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py3.py160
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/abc.py142
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/py.typed0
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/readers.py123
-rw-r--r--third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/trees.py6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/DESCRIPTION.rst61
-rw-r--r--third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/METADATA88
-rw-r--r--third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/RECORD7
-rw-r--r--third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/metadata.json1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2/__init__.py1809
-rw-r--r--third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/LICENSE.txt27
-rw-r--r--third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/METADATA238
-rw-r--r--third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/WHEEL5
-rw-r--r--third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/top_level.txt2
-rw-r--r--third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir.py693
-rw-r--r--third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/LICENSE18
-rw-r--r--third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/METADATA49
-rw-r--r--third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six.py982
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/LICENSE254
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/METADATA41
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/WHEEL5
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing.py2422
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/LICENSE254
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/METADATA50
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/WHEEL5
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing.py2550
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/LICENSE7
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/METADATA49
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp.py286
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/LICENSE19
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/METADATA54
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/RECORD6
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/WHEEL5
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp.py314
-rw-r--r--third_party/python/virtualenv/distributions.json83
-rw-r--r--third_party/python/virtualenv/modules.json314
-rw-r--r--third_party/python/virtualenv/virtualenv-20.2.2.dist-info/LICENSE20
-rw-r--r--third_party/python/virtualenv/virtualenv-20.2.2.dist-info/METADATA92
-rw-r--r--third_party/python/virtualenv/virtualenv-20.2.2.dist-info/RECORD122
-rw-r--r--third_party/python/virtualenv/virtualenv-20.2.2.dist-info/WHEEL6
-rw-r--r--third_party/python/virtualenv/virtualenv-20.2.2.dist-info/entry_points.txt32
-rw-r--r--third_party/python/virtualenv/virtualenv-20.2.2.dist-info/top_level.txt1
-rw-r--r--third_party/python/virtualenv/virtualenv-20.2.2.dist-info/zip-safe1
-rw-r--r--third_party/python/virtualenv/virtualenv.py55
-rw-r--r--third_party/python/virtualenv/virtualenv/__init__.py10
-rw-r--r--third_party/python/virtualenv/virtualenv/__main__.py77
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/__init__.py19
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/activator.py44
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/bash/__init__.py13
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/bash/activate.sh87
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/batch/__init__.py23
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/batch/activate.bat40
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/batch/deactivate.bat19
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/batch/pydoc.bat1
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/cshell/__init__.py14
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/cshell/activate.csh55
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/fish/__init__.py10
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/fish/activate.fish100
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/powershell/__init__.py10
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/powershell/activate.ps160
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/python/__init__.py35
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/python/activate_this.py32
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/via_template.py67
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/xonsh/__init__.py14
-rw-r--r--third_party/python/virtualenv/virtualenv/activation/xonsh/activate.xsh46
-rw-r--r--third_party/python/virtualenv/virtualenv/app_data/__init__.py57
-rw-r--r--third_party/python/virtualenv/virtualenv/app_data/base.py95
-rw-r--r--third_party/python/virtualenv/virtualenv/app_data/na.py66
-rw-r--r--third_party/python/virtualenv/virtualenv/app_data/read_only.py34
-rw-r--r--third_party/python/virtualenv/virtualenv/app_data/via_disk_folder.py177
-rw-r--r--third_party/python/virtualenv/virtualenv/app_data/via_tempdir.py27
-rw-r--r--third_party/python/virtualenv/virtualenv/config/__init__.py1
-rw-r--r--third_party/python/virtualenv/virtualenv/config/cli/__init__.py1
-rw-r--r--third_party/python/virtualenv/virtualenv/config/cli/parser.py120
-rw-r--r--third_party/python/virtualenv/virtualenv/config/convert.py98
-rw-r--r--third_party/python/virtualenv/virtualenv/config/env_var.py29
-rw-r--r--third_party/python/virtualenv/virtualenv/config/ini.py83
-rw-r--r--third_party/python/virtualenv/virtualenv/create/__init__.py1
-rw-r--r--third_party/python/virtualenv/virtualenv/create/creator.py238
-rw-r--r--third_party/python/virtualenv/virtualenv/create/debug.py110
-rw-r--r--third_party/python/virtualenv/virtualenv/create/describe.py117
-rw-r--r--third_party/python/virtualenv/virtualenv/create/pyenv_cfg.py61
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/_virtualenv.py130
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/api.py112
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/builtin_way.py17
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/__init__.py1
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/common.py65
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py102
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py84
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py298
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/common.py53
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py121
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py63
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/python2.py111
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/site.py164
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/ref.py172
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/via_global_self_do.py114
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/store.py26
-rw-r--r--third_party/python/virtualenv/virtualenv/create/via_global_ref/venv.py83
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/__init__.py1
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/builtin.py163
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/cached_py_info.py148
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/discover.py46
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/py_info.py490
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/py_spec.py122
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/windows/__init__.py28
-rw-r--r--third_party/python/virtualenv/virtualenv/discovery/windows/pep514.py161
-rw-r--r--third_party/python/virtualenv/virtualenv/info.py65
-rw-r--r--third_party/python/virtualenv/virtualenv/report.py57
-rw-r--r--third_party/python/virtualenv/virtualenv/run/__init__.py151
-rw-r--r--third_party/python/virtualenv/virtualenv/run/plugin/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/run/plugin/activators.py53
-rw-r--r--third_party/python/virtualenv/virtualenv/run/plugin/base.py58
-rw-r--r--third_party/python/virtualenv/virtualenv/run/plugin/creators.py77
-rw-r--r--third_party/python/virtualenv/virtualenv/run/plugin/discovery.py32
-rw-r--r--third_party/python/virtualenv/virtualenv/run/plugin/seeders.py35
-rw-r--r--third_party/python/virtualenv/virtualenv/run/session.py91
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/__init__.py1
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/base_embed.py118
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/pip_invoke.py56
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/__init__.py0
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/base.py158
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/copy.py35
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/symlink.py61
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/via_app_data.py139
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/seeder.py39
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/__init__.py11
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/acquire.py120
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/bundle.py49
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/__init__.py62
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whlbin0 -> 1360957 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whlbin0 -> 1518513 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whlbin0 -> 583228 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whlbin0 -> 583493 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whlbin0 -> 785194 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whlbin0 -> 785164 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whlbin0 -> 21556 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whlbin0 -> 34788 bytes
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/periodic_update.py367
-rw-r--r--third_party/python/virtualenv/virtualenv/seed/wheels/util.py116
-rw-r--r--third_party/python/virtualenv/virtualenv/util/__init__.py11
-rw-r--r--third_party/python/virtualenv/virtualenv/util/error.py13
-rw-r--r--third_party/python/virtualenv/virtualenv/util/lock.py168
-rw-r--r--third_party/python/virtualenv/virtualenv/util/path/__init__.py16
-rw-r--r--third_party/python/virtualenv/virtualenv/util/path/_pathlib/__init__.py63
-rw-r--r--third_party/python/virtualenv/virtualenv/util/path/_pathlib/via_os_path.py148
-rw-r--r--third_party/python/virtualenv/virtualenv/util/path/_permission.py32
-rw-r--r--third_party/python/virtualenv/virtualenv/util/path/_sync.py98
-rw-r--r--third_party/python/virtualenv/virtualenv/util/six.py50
-rw-r--r--third_party/python/virtualenv/virtualenv/util/subprocess/__init__.py40
-rw-r--r--third_party/python/virtualenv/virtualenv/util/subprocess/_win_subprocess.py175
-rw-r--r--third_party/python/virtualenv/virtualenv/util/zipapp.py33
-rw-r--r--third_party/python/virtualenv/virtualenv/version.py3
283 files changed, 43139 insertions, 0 deletions
diff --git a/third_party/python/virtualenv/README_MOZILLA b/third_party/python/virtualenv/README_MOZILLA
new file mode 100644
index 0000000000..6f40472d9e
--- /dev/null
+++ b/third_party/python/virtualenv/README_MOZILLA
@@ -0,0 +1,10 @@
+The contents of this directory are extracted from virtualenv.pyz as downloaded
+from https://bootstrap.pypa.io/virtualenv.pyz.
+
+The virtualenv.py script is a wrapper that should be preserved and allows to
+use the extracted virtualenv.pyz as-is.
+
+It is not vendored via `mach vendor` because that would require a larger wrapper
+script that handles the same things as the zipapp already does, picking the right
+dependencies to add to the PYTHONPATH depending on the python version.
+
diff --git a/third_party/python/virtualenv/__main__.py b/third_party/python/virtualenv/__main__.py
new file mode 100644
index 0000000000..abf90c7a84
--- /dev/null
+++ b/third_party/python/virtualenv/__main__.py
@@ -0,0 +1,168 @@
+import json
+import os
+import sys
+import zipfile
+
+ABS_HERE = os.path.abspath(os.path.dirname(__file__))
+NEW_IMPORT_SYSTEM = sys.version_info[0] == 3
+
+
+class VersionPlatformSelect(object):
+ def __init__(self):
+ self.archive = ABS_HERE
+ self._zip_file = zipfile.ZipFile(ABS_HERE, "r")
+ self.modules = self._load("modules.json")
+ self.distributions = self._load("distributions.json")
+ self.__cache = {}
+
+ def _load(self, of_file):
+ version = ".".join(str(i) for i in sys.version_info[0:2])
+ per_version = json.loads(self.get_data(of_file).decode("utf-8"))
+ all_platforms = per_version[version] if version in per_version else per_version["3.9"]
+ content = all_platforms.get("==any", {}) # start will all platforms
+ not_us = "!={}".format(sys.platform)
+ for key, value in all_platforms.items(): # now override that with not platform
+ if key.startswith("!=") and key != not_us:
+ content.update(value)
+ content.update(all_platforms.get("=={}".format(sys.platform), {})) # and finish it off with our platform
+ return content
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._zip_file.close()
+
+ def find_mod(self, fullname):
+ if fullname in self.modules:
+ result = self.modules[fullname]
+ return result
+
+ def get_filename(self, fullname):
+ zip_path = self.find_mod(fullname)
+ return None if zip_path is None else os.path.join(ABS_HERE, zip_path)
+
+ def get_data(self, filename):
+ if filename.startswith(ABS_HERE):
+ # keep paths relative from the zipfile
+ filename = filename[len(ABS_HERE) + 1 :]
+ filename = filename.lstrip(os.sep)
+ if sys.platform == "win32":
+ # paths within the zipfile is always /, fixup on Windows to transform \ to /
+ filename = "/".join(filename.split(os.sep))
+ with self._zip_file.open(filename) as file_handler:
+ return file_handler.read()
+
+ def find_distributions(self, context):
+ dist_class = versioned_distribution_class()
+ name = context.name
+ if name in self.distributions:
+ result = dist_class(file_loader=self.get_data, dist_path=self.distributions[name])
+ yield result
+
+ def __repr__(self):
+ return "{}(path={})".format(self.__class__.__name__, ABS_HERE)
+
+ def _register_distutils_finder(self):
+ if "distlib" not in self.modules:
+ return
+
+ class DistlibFinder(object):
+ def __init__(self, path, loader):
+ self.path = path
+ self.loader = loader
+
+ def find(self, name):
+ class Resource(object):
+ def __init__(self, content):
+ self.bytes = content
+
+ full_path = os.path.join(self.path, name)
+ return Resource(self.loader.get_data(full_path))
+
+ # noinspection PyPackageRequirements
+ from distlib.resources import register_finder
+
+ register_finder(self, lambda module: DistlibFinder(os.path.dirname(module.__file__), self))
+
+
+_VER_DISTRIBUTION_CLASS = None
+
+
+def versioned_distribution_class():
+ global _VER_DISTRIBUTION_CLASS
+ if _VER_DISTRIBUTION_CLASS is None:
+ if sys.version_info >= (3, 8):
+ # noinspection PyCompatibility
+ from importlib.metadata import Distribution
+ else:
+ # noinspection PyUnresolvedReferences
+ from importlib_metadata import Distribution
+
+ class VersionedDistribution(Distribution):
+ def __init__(self, file_loader, dist_path):
+ self.file_loader = file_loader
+ self.dist_path = dist_path
+
+ def read_text(self, filename):
+ return self.file_loader(self.locate_file(filename)).decode("utf-8")
+
+ def locate_file(self, path):
+ return os.path.join(self.dist_path, path)
+
+ _VER_DISTRIBUTION_CLASS = VersionedDistribution
+ return _VER_DISTRIBUTION_CLASS
+
+
+if NEW_IMPORT_SYSTEM:
+ # noinspection PyCompatibility
+ # noinspection PyCompatibility
+ from importlib.abc import SourceLoader
+ from importlib.util import spec_from_file_location
+
+ class VersionedFindLoad(VersionPlatformSelect, SourceLoader):
+ def find_spec(self, fullname, path, target=None):
+ zip_path = self.find_mod(fullname)
+ if zip_path is not None:
+ spec = spec_from_file_location(name=fullname, loader=self)
+ return spec
+
+ def module_repr(self, module):
+ raise NotImplementedError
+
+
+else:
+ # noinspection PyDeprecation
+ from imp import new_module
+
+ class VersionedFindLoad(VersionPlatformSelect):
+ def find_module(self, fullname, path=None):
+ return self if self.find_mod(fullname) else None
+
+ def load_module(self, fullname):
+ filename = self.get_filename(fullname)
+ code = self.get_data(filename)
+ mod = sys.modules.setdefault(fullname, new_module(fullname))
+ mod.__file__ = filename
+ mod.__loader__ = self
+ is_package = filename.endswith("__init__.py")
+ if is_package:
+ mod.__path__ = [os.path.dirname(filename)]
+ mod.__package__ = fullname
+ else:
+ mod.__package__ = fullname.rpartition(".")[0]
+ exec(code, mod.__dict__)
+ return mod
+
+
+def run():
+ with VersionedFindLoad() as finder:
+ sys.meta_path.insert(0, finder)
+ finder._register_distutils_finder()
+ from virtualenv.__main__ import run as run_virtualenv
+
+ run_virtualenv()
+
+
+if __name__ == "__main__":
+ run()
diff --git a/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/LICENSE.txt b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/LICENSE.txt
new file mode 100644
index 0000000000..107c61405e
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/LICENSE.txt
@@ -0,0 +1,23 @@
+# This is the MIT license
+
+Copyright (c) 2010 ActiveState Software Inc.
+
+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/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/METADATA
new file mode 100644
index 0000000000..f950731044
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/METADATA
@@ -0,0 +1,264 @@
+Metadata-Version: 2.1
+Name: appdirs
+Version: 1.4.4
+Summary: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir".
+Home-page: http://github.com/ActiveState/appdirs
+Author: Trent Mick
+Author-email: trentm@gmail.com
+Maintainer: Jeff Rouse
+Maintainer-email: jr@its.to
+License: MIT
+Keywords: application directory log cache user
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+
+
+.. image:: https://secure.travis-ci.org/ActiveState/appdirs.png
+ :target: http://travis-ci.org/ActiveState/appdirs
+
+the problem
+===========
+
+What directory should your app use for storing user data? If running on Mac OS X, you
+should use::
+
+ ~/Library/Application Support/<AppName>
+
+If on Windows (at least English Win XP) that should be::
+
+ C:\Documents and Settings\<User>\Application Data\Local Settings\<AppAuthor>\<AppName>
+
+or possibly::
+
+ C:\Documents and Settings\<User>\Application Data\<AppAuthor>\<AppName>
+
+for `roaming profiles <http://bit.ly/9yl3b6>`_ but that is another story.
+
+On Linux (and other Unices) the dir, according to the `XDG
+spec <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_, is::
+
+ ~/.local/share/<AppName>
+
+
+``appdirs`` to the rescue
+=========================
+
+This kind of thing is what the ``appdirs`` module is for. ``appdirs`` will
+help you choose an appropriate:
+
+- user data dir (``user_data_dir``)
+- user config dir (``user_config_dir``)
+- user cache dir (``user_cache_dir``)
+- site data dir (``site_data_dir``)
+- site config dir (``site_config_dir``)
+- user log dir (``user_log_dir``)
+
+and also:
+
+- is a single module so other Python packages can include their own private copy
+- is slightly opinionated on the directory names used. Look for "OPINION" in
+ documentation and code for when an opinion is being applied.
+
+
+some example output
+===================
+
+On Mac OS X::
+
+ >>> from appdirs import *
+ >>> appname = "SuperApp"
+ >>> appauthor = "Acme"
+ >>> user_data_dir(appname, appauthor)
+ '/Users/trentm/Library/Application Support/SuperApp'
+ >>> site_data_dir(appname, appauthor)
+ '/Library/Application Support/SuperApp'
+ >>> user_cache_dir(appname, appauthor)
+ '/Users/trentm/Library/Caches/SuperApp'
+ >>> user_log_dir(appname, appauthor)
+ '/Users/trentm/Library/Logs/SuperApp'
+
+On Windows 7::
+
+ >>> from appdirs import *
+ >>> appname = "SuperApp"
+ >>> appauthor = "Acme"
+ >>> user_data_dir(appname, appauthor)
+ 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp'
+ >>> user_data_dir(appname, appauthor, roaming=True)
+ 'C:\\Users\\trentm\\AppData\\Roaming\\Acme\\SuperApp'
+ >>> user_cache_dir(appname, appauthor)
+ 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Cache'
+ >>> user_log_dir(appname, appauthor)
+ 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Logs'
+
+On Linux::
+
+ >>> from appdirs import *
+ >>> appname = "SuperApp"
+ >>> appauthor = "Acme"
+ >>> user_data_dir(appname, appauthor)
+ '/home/trentm/.local/share/SuperApp
+ >>> site_data_dir(appname, appauthor)
+ '/usr/local/share/SuperApp'
+ >>> site_data_dir(appname, appauthor, multipath=True)
+ '/usr/local/share/SuperApp:/usr/share/SuperApp'
+ >>> user_cache_dir(appname, appauthor)
+ '/home/trentm/.cache/SuperApp'
+ >>> user_log_dir(appname, appauthor)
+ '/home/trentm/.cache/SuperApp/log'
+ >>> user_config_dir(appname)
+ '/home/trentm/.config/SuperApp'
+ >>> site_config_dir(appname)
+ '/etc/xdg/SuperApp'
+ >>> os.environ['XDG_CONFIG_DIRS'] = '/etc:/usr/local/etc'
+ >>> site_config_dir(appname, multipath=True)
+ '/etc/SuperApp:/usr/local/etc/SuperApp'
+
+
+``AppDirs`` for convenience
+===========================
+
+::
+
+ >>> from appdirs import AppDirs
+ >>> dirs = AppDirs("SuperApp", "Acme")
+ >>> dirs.user_data_dir
+ '/Users/trentm/Library/Application Support/SuperApp'
+ >>> dirs.site_data_dir
+ '/Library/Application Support/SuperApp'
+ >>> dirs.user_cache_dir
+ '/Users/trentm/Library/Caches/SuperApp'
+ >>> dirs.user_log_dir
+ '/Users/trentm/Library/Logs/SuperApp'
+
+
+
+Per-version isolation
+=====================
+
+If you have multiple versions of your app in use that you want to be
+able to run side-by-side, then you may want version-isolation for these
+dirs::
+
+ >>> from appdirs import AppDirs
+ >>> dirs = AppDirs("SuperApp", "Acme", version="1.0")
+ >>> dirs.user_data_dir
+ '/Users/trentm/Library/Application Support/SuperApp/1.0'
+ >>> dirs.site_data_dir
+ '/Library/Application Support/SuperApp/1.0'
+ >>> dirs.user_cache_dir
+ '/Users/trentm/Library/Caches/SuperApp/1.0'
+ >>> dirs.user_log_dir
+ '/Users/trentm/Library/Logs/SuperApp/1.0'
+
+
+
+appdirs Changelog
+=================
+
+appdirs 1.4.4
+-------------
+- [PR #92] Don't import appdirs from setup.py
+
+Project officially classified as Stable which is important
+for inclusion in other distros such as ActivePython.
+
+First of several incremental releases to catch up on maintenance.
+
+appdirs 1.4.3
+-------------
+- [PR #76] Python 3.6 invalid escape sequence deprecation fixes
+- Fix for Python 3.6 support
+
+appdirs 1.4.2
+-------------
+- [PR #84] Allow installing without setuptools
+- [PR #86] Fix string delimiters in setup.py description
+- Add Python 3.6 support
+
+appdirs 1.4.1
+-------------
+- [issue #38] Fix _winreg import on Windows Py3
+- [issue #55] Make appname optional
+
+appdirs 1.4.0
+-------------
+- [PR #42] AppAuthor is now optional on Windows
+- [issue 41] Support Jython on Windows, Mac, and Unix-like platforms. Windows
+ support requires `JNA <https://github.com/twall/jna>`_.
+- [PR #44] Fix incorrect behaviour of the site_config_dir method
+
+appdirs 1.3.0
+-------------
+- [Unix, issue 16] Conform to XDG standard, instead of breaking it for
+ everybody
+- [Unix] Removes gratuitous case mangling of the case, since \*nix-es are
+ usually case sensitive, so mangling is not wise
+- [Unix] Fixes the utterly wrong behaviour in ``site_data_dir``, return result
+ based on XDG_DATA_DIRS and make room for respecting the standard which
+ specifies XDG_DATA_DIRS is a multiple-value variable
+- [Issue 6] Add ``*_config_dir`` which are distinct on nix-es, according to
+ XDG specs; on Windows and Mac return the corresponding ``*_data_dir``
+
+appdirs 1.2.0
+-------------
+
+- [Unix] Put ``user_log_dir`` under the *cache* dir on Unix. Seems to be more
+ typical.
+- [issue 9] Make ``unicode`` work on py3k.
+
+appdirs 1.1.0
+-------------
+
+- [issue 4] Add ``AppDirs.user_log_dir``.
+- [Unix, issue 2, issue 7] appdirs now conforms to `XDG base directory spec
+ <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_.
+- [Mac, issue 5] Fix ``site_data_dir()`` on Mac.
+- [Mac] Drop use of 'Carbon' module in favour of hardcoded paths; supports
+ Python3 now.
+- [Windows] Append "Cache" to ``user_cache_dir`` on Windows by default. Use
+ ``opinion=False`` option to disable this.
+- Add ``appdirs.AppDirs`` convenience class. Usage:
+
+ >>> dirs = AppDirs("SuperApp", "Acme", version="1.0")
+ >>> dirs.user_data_dir
+ '/Users/trentm/Library/Application Support/SuperApp/1.0'
+
+- [Windows] Cherry-pick Komodo's change to downgrade paths to the Windows short
+ paths if there are high bit chars.
+- [Linux] Change default ``user_cache_dir()`` on Linux to be singular, e.g.
+ "~/.superapp/cache".
+- [Windows] Add ``roaming`` option to ``user_data_dir()`` (for use on Windows only)
+ and change the default ``user_data_dir`` behaviour to use a *non*-roaming
+ profile dir (``CSIDL_LOCAL_APPDATA`` instead of ``CSIDL_APPDATA``). Why? Because
+ a large roaming profile can cause login speed issues. The "only syncs on
+ logout" behaviour can cause surprises in appdata info.
+
+
+appdirs 1.0.1 (never released)
+------------------------------
+
+Started this changelog 27 July 2010. Before that this module originated in the
+`Komodo <http://www.activestate.com/komodo>`_ product as ``applib.py`` and then
+as `applib/location.py
+<http://github.com/ActiveState/applib/blob/master/applib/location.py>`_ (used by
+`PyPM <http://code.activestate.com/pypm/>`_ in `ActivePython
+<http://www.activestate.com/activepython>`_). This is basically a fork of
+applib.py 1.0.1 and applib/location.py 1.0.1.
+
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/RECORD
new file mode 100644
index 0000000000..9cbb30620e
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/RECORD
@@ -0,0 +1,6 @@
+appdirs.py,sha256=g99s2sXhnvTEm79oj4bWI0Toapc-_SmKKNXvOXHkVic,24720
+appdirs-1.4.4.dist-info/LICENSE.txt,sha256=Nt200KdFqTqyAyA9cZCBSxuJcn0lTK_0jHp6-71HAAs,1097
+appdirs-1.4.4.dist-info/METADATA,sha256=k5TVfXMNKGHTfp2wm6EJKTuGwGNuoQR5TqQgH8iwG8M,8981
+appdirs-1.4.4.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
+appdirs-1.4.4.dist-info/top_level.txt,sha256=nKncE8CUqZERJ6VuQWL4_bkunSPDNfn7KZqb4Tr5YEM,8
+appdirs-1.4.4.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/WHEEL
new file mode 100644
index 0000000000..ef99c6cf32
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.34.2)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/top_level.txt
new file mode 100644
index 0000000000..d64bc321a1
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info/top_level.txt
@@ -0,0 +1 @@
+appdirs
diff --git a/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py
new file mode 100644
index 0000000000..2acd1debeb
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py
@@ -0,0 +1,608 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2005-2010 ActiveState Software Inc.
+# Copyright (c) 2013 Eddy Petrișor
+
+"""Utilities for determining application-specific dirs.
+
+See <http://github.com/ActiveState/appdirs> for details and usage.
+"""
+# Dev Notes:
+# - MSDN on where to store app data files:
+# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
+# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
+# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+
+__version__ = "1.4.4"
+__version_info__ = tuple(int(segment) for segment in __version__.split("."))
+
+
+import sys
+import os
+
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ unicode = str
+
+if sys.platform.startswith('java'):
+ import platform
+ os_name = platform.java_ver()[3][0]
+ if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc.
+ system = 'win32'
+ elif os_name.startswith('Mac'): # "Mac OS X", etc.
+ system = 'darwin'
+ else: # "Linux", "SunOS", "FreeBSD", etc.
+ # Setting this to "linux2" is not ideal, but only Windows or Mac
+ # are actually checked for and the rest of the module expects
+ # *sys.platform* style strings.
+ system = 'linux2'
+else:
+ system = sys.platform
+
+
+
+def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
+ r"""Return full path to the user-specific data dir for this application.
+
+ "appname" is the name of application.
+ If None, just the system directory is returned.
+ "appauthor" (only used on Windows) is the name of the
+ appauthor or distributing body for this application. Typically
+ it is the owning company name. This falls back to appname. You may
+ pass False to disable it.
+ "version" is 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 "<major>.<minor>".
+ Only applied when appname is present.
+ "roaming" (boolean, default False) can be set True to use the Windows
+ roaming appdata directory. That means that for users on a Windows
+ network setup for roaming profiles, this user data will be
+ sync'd on login. See
+ <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
+ for a discussion of issues.
+
+ Typical user data directories are:
+ Mac OS X: ~/Library/Application Support/<AppName>
+ Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
+ Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
+ Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
+ Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
+ Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
+
+ For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
+ That means, by default "~/.local/share/<AppName>".
+ """
+ if system == "win32":
+ if appauthor is None:
+ appauthor = appname
+ const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
+ path = os.path.normpath(_get_win_folder(const))
+ if appname:
+ if appauthor is not False:
+ path = os.path.join(path, appauthor, appname)
+ else:
+ path = os.path.join(path, appname)
+ elif system == 'darwin':
+ path = os.path.expanduser('~/Library/Application Support/')
+ if appname:
+ path = os.path.join(path, appname)
+ else:
+ path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
+ if appname:
+ path = os.path.join(path, appname)
+ if appname and version:
+ path = os.path.join(path, version)
+ return path
+
+
+def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
+ r"""Return full path to the user-shared data dir for this application.
+
+ "appname" is the name of application.
+ If None, just the system directory is returned.
+ "appauthor" (only used on Windows) is the name of the
+ appauthor or distributing body for this application. Typically
+ it is the owning company name. This falls back to appname. You may
+ pass False to disable it.
+ "version" is 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 "<major>.<minor>".
+ Only applied when appname is present.
+ "multipath" is an optional parameter only applicable to *nix
+ which indicates that the entire list of data dirs should be
+ returned. By default, the first item from XDG_DATA_DIRS is
+ returned, or '/usr/local/share/<AppName>',
+ if XDG_DATA_DIRS is not set
+
+ Typical site data directories are:
+ Mac OS X: /Library/Application Support/<AppName>
+ Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
+ Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
+ Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
+ Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
+
+ For Unix, this is using the $XDG_DATA_DIRS[0] default.
+
+ WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
+ """
+ if system == "win32":
+ if appauthor is None:
+ appauthor = appname
+ path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
+ if appname:
+ if appauthor is not False:
+ path = os.path.join(path, appauthor, appname)
+ else:
+ path = os.path.join(path, appname)
+ elif system == 'darwin':
+ path = os.path.expanduser('/Library/Application Support')
+ if appname:
+ path = os.path.join(path, appname)
+ else:
+ # XDG default for $XDG_DATA_DIRS
+ # only first, if multipath is False
+ path = os.getenv('XDG_DATA_DIRS',
+ os.pathsep.join(['/usr/local/share', '/usr/share']))
+ pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
+ if appname:
+ if version:
+ appname = os.path.join(appname, version)
+ pathlist = [os.sep.join([x, appname]) for x in pathlist]
+
+ if multipath:
+ path = os.pathsep.join(pathlist)
+ else:
+ path = pathlist[0]
+ return path
+
+ if appname and version:
+ path = os.path.join(path, version)
+ return path
+
+
+def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
+ r"""Return full path to the user-specific config dir for this application.
+
+ "appname" is the name of application.
+ If None, just the system directory is returned.
+ "appauthor" (only used on Windows) is the name of the
+ appauthor or distributing body for this application. Typically
+ it is the owning company name. This falls back to appname. You may
+ pass False to disable it.
+ "version" is 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 "<major>.<minor>".
+ Only applied when appname is present.
+ "roaming" (boolean, default False) can be set True to use the Windows
+ roaming appdata directory. That means that for users on a Windows
+ network setup for roaming profiles, this user data will be
+ sync'd on login. See
+ <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
+ for a discussion of issues.
+
+ Typical user config directories are:
+ Mac OS X: same as user_data_dir
+ Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
+ Win *: same as user_data_dir
+
+ For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
+ That means, by default "~/.config/<AppName>".
+ """
+ if system in ["win32", "darwin"]:
+ path = user_data_dir(appname, appauthor, None, roaming)
+ else:
+ path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
+ if appname:
+ path = os.path.join(path, appname)
+ if appname and version:
+ path = os.path.join(path, version)
+ return path
+
+
+def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
+ r"""Return full path to the user-shared data dir for this application.
+
+ "appname" is the name of application.
+ If None, just the system directory is returned.
+ "appauthor" (only used on Windows) is the name of the
+ appauthor or distributing body for this application. Typically
+ it is the owning company name. This falls back to appname. You may
+ pass False to disable it.
+ "version" is 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 "<major>.<minor>".
+ Only applied when appname is present.
+ "multipath" is an optional parameter only applicable to *nix
+ which indicates that the entire list of config dirs should be
+ returned. By default, the first item from XDG_CONFIG_DIRS is
+ returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
+
+ Typical site config directories are:
+ Mac OS X: same as site_data_dir
+ Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
+ $XDG_CONFIG_DIRS
+ Win *: same as site_data_dir
+ Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
+
+ For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
+
+ WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
+ """
+ if system in ["win32", "darwin"]:
+ path = site_data_dir(appname, appauthor)
+ if appname and version:
+ path = os.path.join(path, version)
+ else:
+ # XDG default for $XDG_CONFIG_DIRS
+ # only first, if multipath is False
+ path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
+ pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
+ if appname:
+ if version:
+ appname = os.path.join(appname, version)
+ pathlist = [os.sep.join([x, appname]) for x in pathlist]
+
+ if multipath:
+ path = os.pathsep.join(pathlist)
+ else:
+ path = pathlist[0]
+ return path
+
+
+def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
+ r"""Return full path to the user-specific cache dir for this application.
+
+ "appname" is the name of application.
+ If None, just the system directory is returned.
+ "appauthor" (only used on Windows) is the name of the
+ appauthor or distributing body for this application. Typically
+ it is the owning company name. This falls back to appname. You may
+ pass False to disable it.
+ "version" is 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 "<major>.<minor>".
+ Only applied when appname is present.
+ "opinion" (boolean) can be False to disable the appending of
+ "Cache" to the base app data dir for Windows. See
+ discussion below.
+
+ Typical user cache directories are:
+ Mac OS X: ~/Library/Caches/<AppName>
+ Unix: ~/.cache/<AppName> (XDG default)
+ Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
+ Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
+
+ On Windows the only suggestion in the MSDN docs is that local settings go in
+ the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
+ app data dir (the default returned by `user_data_dir` above). Apps typically
+ put cache data somewhere *under* the given dir here. Some examples:
+ ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
+ ...\Acme\SuperApp\Cache\1.0
+ OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
+ This can be disabled with the `opinion=False` option.
+ """
+ if system == "win32":
+ if appauthor is None:
+ appauthor = appname
+ path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
+ if appname:
+ if appauthor is not False:
+ path = os.path.join(path, appauthor, appname)
+ else:
+ path = os.path.join(path, appname)
+ if opinion:
+ path = os.path.join(path, "Cache")
+ elif system == 'darwin':
+ path = os.path.expanduser('~/Library/Caches')
+ if appname:
+ path = os.path.join(path, appname)
+ else:
+ path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
+ if appname:
+ path = os.path.join(path, appname)
+ if appname and version:
+ path = os.path.join(path, version)
+ return path
+
+
+def user_state_dir(appname=None, appauthor=None, version=None, roaming=False):
+ r"""Return full path to the user-specific state dir for this application.
+
+ "appname" is the name of application.
+ If None, just the system directory is returned.
+ "appauthor" (only used on Windows) is the name of the
+ appauthor or distributing body for this application. Typically
+ it is the owning company name. This falls back to appname. You may
+ pass False to disable it.
+ "version" is 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 "<major>.<minor>".
+ Only applied when appname is present.
+ "roaming" (boolean, default False) can be set True to use the Windows
+ roaming appdata directory. That means that for users on a Windows
+ network setup for roaming profiles, this user data will be
+ sync'd on login. See
+ <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
+ for a discussion of issues.
+
+ Typical user state directories are:
+ Mac OS X: same as user_data_dir
+ Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined
+ Win *: same as user_data_dir
+
+ For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state>
+ to extend the XDG spec and support $XDG_STATE_HOME.
+
+ That means, by default "~/.local/state/<AppName>".
+ """
+ if system in ["win32", "darwin"]:
+ path = user_data_dir(appname, appauthor, None, roaming)
+ else:
+ path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state"))
+ if appname:
+ path = os.path.join(path, appname)
+ if appname and version:
+ path = os.path.join(path, version)
+ return path
+
+
+def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
+ r"""Return full path to the user-specific log dir for this application.
+
+ "appname" is the name of application.
+ If None, just the system directory is returned.
+ "appauthor" (only used on Windows) is the name of the
+ appauthor or distributing body for this application. Typically
+ it is the owning company name. This falls back to appname. You may
+ pass False to disable it.
+ "version" is 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 "<major>.<minor>".
+ Only applied when appname is present.
+ "opinion" (boolean) can be False to disable the appending of
+ "Logs" to the base app data dir for Windows, and "log" to the
+ base cache dir for Unix. See discussion below.
+
+ Typical user log directories are:
+ Mac OS X: ~/Library/Logs/<AppName>
+ Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
+ Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
+ Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
+
+ On Windows the only suggestion in the MSDN docs is that local settings
+ go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
+ examples of what some windows apps use for a logs dir.)
+
+ OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
+ value for Windows and appends "log" to the user cache dir for Unix.
+ This can be disabled with the `opinion=False` option.
+ """
+ if system == "darwin":
+ path = os.path.join(
+ os.path.expanduser('~/Library/Logs'),
+ appname)
+ elif system == "win32":
+ path = user_data_dir(appname, appauthor, version)
+ version = False
+ if opinion:
+ path = os.path.join(path, "Logs")
+ else:
+ path = user_cache_dir(appname, appauthor, version)
+ version = False
+ if opinion:
+ path = os.path.join(path, "log")
+ if appname and version:
+ path = os.path.join(path, version)
+ return path
+
+
+class AppDirs(object):
+ """Convenience wrapper for getting application dirs."""
+ def __init__(self, appname=None, appauthor=None, version=None,
+ roaming=False, multipath=False):
+ self.appname = appname
+ self.appauthor = appauthor
+ self.version = version
+ self.roaming = roaming
+ self.multipath = multipath
+
+ @property
+ def user_data_dir(self):
+ return user_data_dir(self.appname, self.appauthor,
+ version=self.version, roaming=self.roaming)
+
+ @property
+ def site_data_dir(self):
+ return site_data_dir(self.appname, self.appauthor,
+ version=self.version, multipath=self.multipath)
+
+ @property
+ def user_config_dir(self):
+ return user_config_dir(self.appname, self.appauthor,
+ version=self.version, roaming=self.roaming)
+
+ @property
+ def site_config_dir(self):
+ return site_config_dir(self.appname, self.appauthor,
+ version=self.version, multipath=self.multipath)
+
+ @property
+ def user_cache_dir(self):
+ return user_cache_dir(self.appname, self.appauthor,
+ version=self.version)
+
+ @property
+ def user_state_dir(self):
+ return user_state_dir(self.appname, self.appauthor,
+ version=self.version)
+
+ @property
+ def user_log_dir(self):
+ return user_log_dir(self.appname, self.appauthor,
+ version=self.version)
+
+
+#---- internal support stuff
+
+def _get_win_folder_from_registry(csidl_name):
+ """This is a fallback technique at best. I'm not sure if using the
+ registry for this guarantees us the correct answer for all CSIDL_*
+ names.
+ """
+ if PY3:
+ import winreg as _winreg
+ else:
+ import _winreg
+
+ shell_folder_name = {
+ "CSIDL_APPDATA": "AppData",
+ "CSIDL_COMMON_APPDATA": "Common AppData",
+ "CSIDL_LOCAL_APPDATA": "Local AppData",
+ }[csidl_name]
+
+ key = _winreg.OpenKey(
+ _winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
+ )
+ dir, type = _winreg.QueryValueEx(key, shell_folder_name)
+ return dir
+
+
+def _get_win_folder_with_pywin32(csidl_name):
+ from win32com.shell import shellcon, shell
+ dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
+ # Try to make this a unicode path because SHGetFolderPath does
+ # not return unicode strings when there is unicode data in the
+ # path.
+ try:
+ dir = unicode(dir)
+
+ # Downgrade to short path name if have highbit chars. See
+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
+ has_high_char = False
+ for c in dir:
+ if ord(c) > 255:
+ has_high_char = True
+ break
+ if has_high_char:
+ try:
+ import win32api
+ dir = win32api.GetShortPathName(dir)
+ except ImportError:
+ pass
+ except UnicodeError:
+ pass
+ return dir
+
+
+def _get_win_folder_with_ctypes(csidl_name):
+ import ctypes
+
+ csidl_const = {
+ "CSIDL_APPDATA": 26,
+ "CSIDL_COMMON_APPDATA": 35,
+ "CSIDL_LOCAL_APPDATA": 28,
+ }[csidl_name]
+
+ buf = ctypes.create_unicode_buffer(1024)
+ ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
+
+ # Downgrade to short path name if have highbit chars. See
+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
+ has_high_char = False
+ for c in buf:
+ if ord(c) > 255:
+ has_high_char = True
+ break
+ if has_high_char:
+ buf2 = ctypes.create_unicode_buffer(1024)
+ if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
+ buf = buf2
+
+ return buf.value
+
+def _get_win_folder_with_jna(csidl_name):
+ import array
+ from com.sun import jna
+ from com.sun.jna.platform import win32
+
+ buf_size = win32.WinDef.MAX_PATH * 2
+ buf = array.zeros('c', buf_size)
+ shell = win32.Shell32.INSTANCE
+ shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
+ dir = jna.Native.toString(buf.tostring()).rstrip("\0")
+
+ # Downgrade to short path name if have highbit chars. See
+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
+ has_high_char = False
+ for c in dir:
+ if ord(c) > 255:
+ has_high_char = True
+ break
+ if has_high_char:
+ buf = array.zeros('c', buf_size)
+ kernel = win32.Kernel32.INSTANCE
+ if kernel.GetShortPathName(dir, buf, buf_size):
+ dir = jna.Native.toString(buf.tostring()).rstrip("\0")
+
+ return dir
+
+if system == "win32":
+ try:
+ import win32com.shell
+ _get_win_folder = _get_win_folder_with_pywin32
+ except ImportError:
+ try:
+ from ctypes import windll
+ _get_win_folder = _get_win_folder_with_ctypes
+ except ImportError:
+ try:
+ import com.sun.jna
+ _get_win_folder = _get_win_folder_with_jna
+ except ImportError:
+ _get_win_folder = _get_win_folder_from_registry
+
+
+#---- self test code
+
+if __name__ == "__main__":
+ appname = "MyApp"
+ appauthor = "MyCompany"
+
+ props = ("user_data_dir",
+ "user_config_dir",
+ "user_cache_dir",
+ "user_state_dir",
+ "user_log_dir",
+ "site_data_dir",
+ "site_config_dir")
+
+ print("-- app dirs %s --" % __version__)
+
+ print("-- app dirs (with optional 'version')")
+ dirs = AppDirs(appname, appauthor, version="1.0")
+ for prop in props:
+ print("%s: %s" % (prop, getattr(dirs, prop)))
+
+ print("\n-- app dirs (without optional 'version')")
+ dirs = AppDirs(appname, appauthor)
+ for prop in props:
+ print("%s: %s" % (prop, getattr(dirs, prop)))
+
+ print("\n-- app dirs (without optional 'appauthor')")
+ dirs = AppDirs(appname)
+ for prop in props:
+ print("%s: %s" % (prop, getattr(dirs, prop)))
+
+ print("\n-- app dirs (with disabled 'appauthor')")
+ dirs = AppDirs(appname, appauthor=False)
+ for prop in props:
+ print("%s: %s" % (prop, getattr(dirs, prop)))
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/__init__.py b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/__init__.py
new file mode 100644
index 0000000000..1fc3c62e81
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/__init__.py
@@ -0,0 +1,6 @@
+# A Python "namespace package" http://www.python.org/dev/peps/pep-0382/
+# This always goes inside of a namespace package's __init__.py
+
+from pkgutil import extend_path
+
+__path__ = extend_path(__path__, __name__)
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/__init__.py b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/__init__.py
new file mode 100644
index 0000000000..603d604764
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/__init__.py
@@ -0,0 +1,1473 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# flake8: noqa
+
+"""Configuration file parser.
+
+A configuration file consists of sections, lead by a "[section]" header,
+and followed by "name: value" entries, with continuations and such in
+the style of RFC 822.
+
+Intrinsic defaults can be specified by passing them into the
+ConfigParser constructor as a dictionary.
+
+class:
+
+ConfigParser -- responsible for parsing a list of
+ configuration files, and managing the parsed database.
+
+ methods:
+
+ __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
+ delimiters=('=', ':'), comment_prefixes=('#', ';'),
+ inline_comment_prefixes=None, strict=True,
+ empty_lines_in_values=True, default_section='DEFAULT',
+ interpolation=<unset>, converters=<unset>):
+ Create the parser. When `defaults' is given, it is initialized into the
+ dictionary or intrinsic defaults. The keys must be strings, the values
+ must be appropriate for %()s string interpolation.
+
+ When `dict_type' is given, it will be used to create the dictionary
+ objects for the list of sections, for the options within a section, and
+ for the default values.
+
+ When `delimiters' is given, it will be used as the set of substrings
+ that divide keys from values.
+
+ When `comment_prefixes' is given, it will be used as the set of
+ substrings that prefix comments in empty lines. Comments can be
+ indented.
+
+ When `inline_comment_prefixes' is given, it will be used as the set of
+ substrings that prefix comments in non-empty lines.
+
+ When `strict` is True, the parser won't allow for any section or option
+ duplicates while reading from a single source (file, string or
+ dictionary). Default is True.
+
+ When `empty_lines_in_values' is False (default: True), each empty line
+ marks the end of an option. Otherwise, internal empty lines of
+ a multiline option are kept as part of the value.
+
+ When `allow_no_value' is True (default: False), options without
+ values are accepted; the value presented for these is None.
+
+ When `default_section' is given, the name of the special section is
+ named accordingly. By default it is called ``"DEFAULT"`` but this can
+ be customized to point to any other valid section name. Its current
+ value can be retrieved using the ``parser_instance.default_section``
+ attribute and may be modified at runtime.
+
+ When `interpolation` is given, it should be an Interpolation subclass
+ instance. It will be used as the handler for option value
+ pre-processing when using getters. RawConfigParser objects don't do
+ any sort of interpolation, whereas ConfigParser uses an instance of
+ BasicInterpolation. The library also provides a ``zc.buildbot``
+ inspired ExtendedInterpolation implementation.
+
+ When `converters` is given, it should be a dictionary where each key
+ represents the name of a type converter and each value is a callable
+ implementing the conversion from string to the desired datatype. Every
+ converter gets its corresponding get*() method on the parser object and
+ section proxies.
+
+ sections()
+ Return all the configuration section names, sans DEFAULT.
+
+ has_section(section)
+ Return whether the given section exists.
+
+ has_option(section, option)
+ Return whether the given option exists in the given section.
+
+ options(section)
+ Return list of configuration options for the named section.
+
+ read(filenames, encoding=None)
+ Read and parse the iterable of named configuration files, given by
+ name. A single filename is also allowed. Non-existing files
+ are ignored. Return list of successfully read files.
+
+ read_file(f, filename=None)
+ Read and parse one configuration file, given as a file object.
+ The filename defaults to f.name; it is only used in error
+ messages (if f has no `name' attribute, the string `<???>' is used).
+
+ read_string(string)
+ Read configuration from a given string.
+
+ read_dict(dictionary)
+ Read configuration from a dictionary. Keys are section names,
+ values are dictionaries with keys and values that should be present
+ in the section. If the used dictionary type preserves order, sections
+ and their keys will be added in order. Values are automatically
+ converted to strings.
+
+ get(section, option, raw=False, vars=None, fallback=_UNSET)
+ Return a string value for the named option. All % interpolations are
+ expanded in the return values, based on the defaults passed into the
+ constructor and the DEFAULT section. Additional substitutions may be
+ provided using the `vars' argument, which must be a dictionary whose
+ contents override any pre-existing defaults. If `option' is a key in
+ `vars', the value from `vars' is used.
+
+ getint(section, options, raw=False, vars=None, fallback=_UNSET)
+ Like get(), but convert value to an integer.
+
+ getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
+ Like get(), but convert value to a float.
+
+ getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
+ Like get(), but convert value to a boolean (currently case
+ insensitively defined as 0, false, no, off for False, and 1, true,
+ yes, on for True). Returns False or True.
+
+ items(section=_UNSET, raw=False, vars=None)
+ If section is given, return a list of tuples with (name, value) for
+ each option in the section. Otherwise, return a list of tuples with
+ (section_name, section_proxy) for each section, including DEFAULTSECT.
+
+ remove_section(section)
+ Remove the given file section and all its options.
+
+ remove_option(section, option)
+ Remove the given option from the given section.
+
+ set(section, option, value)
+ Set the given option.
+
+ write(fp, space_around_delimiters=True)
+ Write the configuration state in .ini format. If
+ `space_around_delimiters' is True (the default), delimiters
+ between keys and values are surrounded by spaces.
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+try:
+ from collections.abc import MutableMapping
+except ImportError:
+ from collections import MutableMapping
+import functools
+import io
+import itertools
+import os
+import re
+import sys
+import warnings
+
+from backports.configparser.helpers import OrderedDict as _default_dict
+from backports.configparser.helpers import ChainMap as _ChainMap
+from backports.configparser.helpers import from_none, open, str, PY2
+from backports.configparser.helpers import PathLike, fspath
+from backports.configparser.helpers import MutableMapping
+
+__all__ = [
+ "NoSectionError",
+ "DuplicateOptionError",
+ "DuplicateSectionError",
+ "NoOptionError",
+ "InterpolationError",
+ "InterpolationDepthError",
+ "InterpolationMissingOptionError",
+ "InterpolationSyntaxError",
+ "ParsingError",
+ "MissingSectionHeaderError",
+ "ConfigParser",
+ "SafeConfigParser",
+ "RawConfigParser",
+ "Interpolation",
+ "BasicInterpolation",
+ "ExtendedInterpolation",
+ "LegacyInterpolation",
+ "SectionProxy",
+ "ConverterMapping",
+ "DEFAULTSECT",
+ "MAX_INTERPOLATION_DEPTH",
+]
+
+DEFAULTSECT = "DEFAULT"
+
+MAX_INTERPOLATION_DEPTH = 10
+
+
+# exception classes
+class Error(Exception):
+ """Base class for ConfigParser exceptions."""
+
+ def __init__(self, msg=''):
+ self.message = msg
+ Exception.__init__(self, msg)
+
+ def __repr__(self):
+ return self.message
+
+ __str__ = __repr__
+
+
+class NoSectionError(Error):
+ """Raised when no section matches a requested option."""
+
+ def __init__(self, section):
+ Error.__init__(self, 'No section: %r' % (section,))
+ self.section = section
+ self.args = (section,)
+
+
+class DuplicateSectionError(Error):
+ """Raised when a section is repeated in an input source.
+
+ Possible repetitions that raise this exception are: multiple creation
+ using the API or in strict parsers when a section is found more than once
+ in a single input file, string or dictionary.
+ """
+
+ def __init__(self, section, source=None, lineno=None):
+ msg = [repr(section), " already exists"]
+ if source is not None:
+ message = ["While reading from ", repr(source)]
+ if lineno is not None:
+ message.append(" [line {0:2d}]".format(lineno))
+ message.append(": section ")
+ message.extend(msg)
+ msg = message
+ else:
+ msg.insert(0, "Section ")
+ Error.__init__(self, "".join(msg))
+ self.section = section
+ self.source = source
+ self.lineno = lineno
+ self.args = (section, source, lineno)
+
+
+class DuplicateOptionError(Error):
+ """Raised by strict parsers when an option is repeated in an input source.
+
+ Current implementation raises this exception only when an option is found
+ more than once in a single file, string or dictionary.
+ """
+
+ def __init__(self, section, option, source=None, lineno=None):
+ msg = [repr(option), " in section ", repr(section), " already exists"]
+ if source is not None:
+ message = ["While reading from ", repr(source)]
+ if lineno is not None:
+ message.append(" [line {0:2d}]".format(lineno))
+ message.append(": option ")
+ message.extend(msg)
+ msg = message
+ else:
+ msg.insert(0, "Option ")
+ Error.__init__(self, "".join(msg))
+ self.section = section
+ self.option = option
+ self.source = source
+ self.lineno = lineno
+ self.args = (section, option, source, lineno)
+
+
+class NoOptionError(Error):
+ """A requested option was not found."""
+
+ def __init__(self, option, section):
+ Error.__init__(self, "No option %r in section: %r" % (option, section))
+ self.option = option
+ self.section = section
+ self.args = (option, section)
+
+
+class InterpolationError(Error):
+ """Base class for interpolation-related exceptions."""
+
+ def __init__(self, option, section, msg):
+ Error.__init__(self, msg)
+ self.option = option
+ self.section = section
+ self.args = (option, section, msg)
+
+
+class InterpolationMissingOptionError(InterpolationError):
+ """A string substitution required a setting which was not available."""
+
+ def __init__(self, option, section, rawval, reference):
+ msg = (
+ "Bad value substitution: option {0!r} in section {1!r} contains "
+ "an interpolation key {2!r} which is not a valid option name. "
+ "Raw value: {3!r}".format(option, section, reference, rawval)
+ )
+ InterpolationError.__init__(self, option, section, msg)
+ self.reference = reference
+ self.args = (option, section, rawval, reference)
+
+
+class InterpolationSyntaxError(InterpolationError):
+ """Raised when the source text contains invalid syntax.
+
+ Current implementation raises this exception when the source text into
+ which substitutions are made does not conform to the required syntax.
+ """
+
+
+class InterpolationDepthError(InterpolationError):
+ """Raised when substitutions are nested too deeply."""
+
+ def __init__(self, option, section, rawval):
+ msg = (
+ "Recursion limit exceeded in value substitution: option {0!r} "
+ "in section {1!r} contains an interpolation key which "
+ "cannot be substituted in {2} steps. Raw value: {3!r}"
+ "".format(option, section, MAX_INTERPOLATION_DEPTH, rawval)
+ )
+ InterpolationError.__init__(self, option, section, msg)
+ self.args = (option, section, rawval)
+
+
+class ParsingError(Error):
+ """Raised when a configuration file does not follow legal syntax."""
+
+ def __init__(self, source=None, filename=None):
+ # Exactly one of `source'/`filename' arguments has to be given.
+ # `filename' kept for compatibility.
+ if filename and source:
+ raise ValueError(
+ "Cannot specify both `filename' and `source'. " "Use `source'."
+ )
+ elif not filename and not source:
+ raise ValueError("Required argument `source' not given.")
+ elif filename:
+ source = filename
+ Error.__init__(self, 'Source contains parsing errors: %r' % source)
+ self.source = source
+ self.errors = []
+ self.args = (source,)
+
+ @property
+ def filename(self):
+ """Deprecated, use `source'."""
+ warnings.warn(
+ "The 'filename' attribute will be removed in future versions. "
+ "Use 'source' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.source
+
+ @filename.setter
+ def filename(self, value):
+ """Deprecated, user `source'."""
+ warnings.warn(
+ "The 'filename' attribute will be removed in future versions. "
+ "Use 'source' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.source = value
+
+ def append(self, lineno, line):
+ self.errors.append((lineno, line))
+ self.message += '\n\t[line %2d]: %s' % (lineno, line)
+
+
+class MissingSectionHeaderError(ParsingError):
+ """Raised when a key-value pair is found before any section header."""
+
+ def __init__(self, filename, lineno, line):
+ Error.__init__(
+ self,
+ 'File contains no section headers.\nfile: %r, line: %d\n%r'
+ % (filename, lineno, line),
+ )
+ self.source = filename
+ self.lineno = lineno
+ self.line = line
+ self.args = (filename, lineno, line)
+
+
+# Used in parser getters to indicate the default behaviour when a specific
+# option is not found it to raise an exception. Created to enable `None' as
+# a valid fallback value.
+_UNSET = object()
+
+
+class Interpolation(object):
+ """Dummy interpolation that passes the value through with no changes."""
+
+ def before_get(self, parser, section, option, value, defaults):
+ return value
+
+ def before_set(self, parser, section, option, value):
+ return value
+
+ def before_read(self, parser, section, option, value):
+ return value
+
+ def before_write(self, parser, section, option, value):
+ return value
+
+
+class BasicInterpolation(Interpolation):
+ """Interpolation as implemented in the classic ConfigParser.
+
+ The option values can contain format strings which refer to other values in
+ the same section, or values in the special default section.
+
+ For example:
+
+ something: %(dir)s/whatever
+
+ would resolve the "%(dir)s" to the value of dir. All reference
+ expansions are done late, on demand. If a user needs to use a bare % in
+ a configuration file, she can escape it by writing %%. Other % usage
+ is considered a user error and raises `InterpolationSyntaxError'."""
+
+ _KEYCRE = re.compile(r"%\(([^)]+)\)s")
+
+ def before_get(self, parser, section, option, value, defaults):
+ L = []
+ self._interpolate_some(parser, option, L, value, section, defaults, 1)
+ return ''.join(L)
+
+ def before_set(self, parser, section, option, value):
+ tmp_value = value.replace('%%', '') # escaped percent signs
+ tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+ if '%' in tmp_value:
+ raise ValueError(
+ "invalid interpolation syntax in %r at "
+ "position %d" % (value, tmp_value.find('%'))
+ )
+ return value
+
+ def _interpolate_some(self, parser, option, accum, rest, section, map, depth):
+ rawval = parser.get(section, option, raw=True, fallback=rest)
+ if depth > MAX_INTERPOLATION_DEPTH:
+ raise InterpolationDepthError(option, section, rawval)
+ while rest:
+ p = rest.find("%")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+ if c == "%":
+ accum.append("%")
+ rest = rest[2:]
+ elif c == "(":
+ m = self._KEYCRE.match(rest)
+ if m is None:
+ raise InterpolationSyntaxError(
+ option,
+ section,
+ "bad interpolation variable reference %r" % rest,
+ )
+ var = parser.optionxform(m.group(1))
+ rest = rest[m.end() :]
+ try:
+ v = map[var]
+ except KeyError:
+ raise from_none(
+ InterpolationMissingOptionError(option, section, rawval, var)
+ )
+ if "%" in v:
+ self._interpolate_some(
+ parser, option, accum, v, section, map, depth + 1
+ )
+ else:
+ accum.append(v)
+ else:
+ raise InterpolationSyntaxError(
+ option,
+ section,
+ "'%%' must be followed by '%%' or '(', " "found: %r" % (rest,),
+ )
+
+
+class ExtendedInterpolation(Interpolation):
+ """Advanced variant of interpolation, supports the syntax used by
+ `zc.buildout'. Enables interpolation between sections."""
+
+ _KEYCRE = re.compile(r"\$\{([^}]+)\}")
+
+ def before_get(self, parser, section, option, value, defaults):
+ L = []
+ self._interpolate_some(parser, option, L, value, section, defaults, 1)
+ return ''.join(L)
+
+ def before_set(self, parser, section, option, value):
+ tmp_value = value.replace('$$', '') # escaped dollar signs
+ tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+ if '$' in tmp_value:
+ raise ValueError(
+ "invalid interpolation syntax in %r at "
+ "position %d" % (value, tmp_value.find('$'))
+ )
+ return value
+
+ def _interpolate_some(self, parser, option, accum, rest, section, map, depth):
+ rawval = parser.get(section, option, raw=True, fallback=rest)
+ if depth > MAX_INTERPOLATION_DEPTH:
+ raise InterpolationDepthError(option, section, rawval)
+ while rest:
+ p = rest.find("$")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+ if c == "$":
+ accum.append("$")
+ rest = rest[2:]
+ elif c == "{":
+ m = self._KEYCRE.match(rest)
+ if m is None:
+ raise InterpolationSyntaxError(
+ option,
+ section,
+ "bad interpolation variable reference %r" % rest,
+ )
+ path = m.group(1).split(':')
+ rest = rest[m.end() :]
+ sect = section
+ opt = option
+ try:
+ if len(path) == 1:
+ opt = parser.optionxform(path[0])
+ v = map[opt]
+ elif len(path) == 2:
+ sect = path[0]
+ opt = parser.optionxform(path[1])
+ v = parser.get(sect, opt, raw=True)
+ else:
+ raise InterpolationSyntaxError(
+ option, section, "More than one ':' found: %r" % (rest,)
+ )
+ except (KeyError, NoSectionError, NoOptionError):
+ raise from_none(
+ InterpolationMissingOptionError(
+ option, section, rawval, ":".join(path)
+ )
+ )
+ if "$" in v:
+ self._interpolate_some(
+ parser,
+ opt,
+ accum,
+ v,
+ sect,
+ dict(parser.items(sect, raw=True)),
+ depth + 1,
+ )
+ else:
+ accum.append(v)
+ else:
+ raise InterpolationSyntaxError(
+ option,
+ section,
+ "'$' must be followed by '$' or '{', " "found: %r" % (rest,),
+ )
+
+
+class LegacyInterpolation(Interpolation):
+ """Deprecated interpolation used in old versions of ConfigParser.
+ Use BasicInterpolation or ExtendedInterpolation instead."""
+
+ _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
+
+ def before_get(self, parser, section, option, value, vars):
+ rawval = value
+ depth = MAX_INTERPOLATION_DEPTH
+ while depth: # Loop through this until it's done
+ depth -= 1
+ if value and "%(" in value:
+ replace = functools.partial(self._interpolation_replace, parser=parser)
+ value = self._KEYCRE.sub(replace, value)
+ try:
+ value = value % vars
+ except KeyError as e:
+ raise from_none(
+ InterpolationMissingOptionError(
+ option, section, rawval, e.args[0]
+ )
+ )
+ else:
+ break
+ if value and "%(" in value:
+ raise InterpolationDepthError(option, section, rawval)
+ return value
+
+ def before_set(self, parser, section, option, value):
+ return value
+
+ @staticmethod
+ def _interpolation_replace(match, parser):
+ s = match.group(1)
+ if s is None:
+ return match.group()
+ else:
+ return "%%(%s)s" % parser.optionxform(s)
+
+
+class RawConfigParser(MutableMapping):
+ """ConfigParser that does not do interpolation."""
+
+ # Regular expressions for parsing section headers and options
+ _SECT_TMPL = r"""
+ \[ # [
+ (?P<header>[^]]+) # very permissive!
+ \] # ]
+ """
+ _OPT_TMPL = r"""
+ (?P<option>.*?) # very permissive!
+ \s*(?P<vi>{delim})\s* # any number of space/tab,
+ # followed by any of the
+ # allowed delimiters,
+ # followed by any space/tab
+ (?P<value>.*)$ # everything up to eol
+ """
+ _OPT_NV_TMPL = r"""
+ (?P<option>.*?) # very permissive!
+ \s*(?: # any number of space/tab,
+ (?P<vi>{delim})\s* # optionally followed by
+ # any of the allowed
+ # delimiters, followed by any
+ # space/tab
+ (?P<value>.*))?$ # everything up to eol
+ """
+ # Interpolation algorithm to be used if the user does not specify another
+ _DEFAULT_INTERPOLATION = Interpolation()
+ # Compiled regular expression for matching sections
+ SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
+ # Compiled regular expression for matching options with typical separators
+ OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE)
+ # Compiled regular expression for matching options with optional values
+ # delimited using typical separators
+ OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
+ # Compiled regular expression for matching leading whitespace in a line
+ NONSPACECRE = re.compile(r"\S")
+ # Possible boolean values in the configuration.
+ BOOLEAN_STATES = {
+ '1': True,
+ 'yes': True,
+ 'true': True,
+ 'on': True,
+ '0': False,
+ 'no': False,
+ 'false': False,
+ 'off': False,
+ }
+
+ def __init__(
+ self, defaults=None, dict_type=_default_dict, allow_no_value=False, **kwargs
+ ):
+
+ # keyword-only arguments
+ delimiters = kwargs.get('delimiters', ('=', ':'))
+ comment_prefixes = kwargs.get('comment_prefixes', ('#', ';'))
+ inline_comment_prefixes = kwargs.get('inline_comment_prefixes', None)
+ strict = kwargs.get('strict', True)
+ empty_lines_in_values = kwargs.get('empty_lines_in_values', True)
+ default_section = kwargs.get('default_section', DEFAULTSECT)
+ interpolation = kwargs.get('interpolation', _UNSET)
+ converters = kwargs.get('converters', _UNSET)
+
+ self._dict = dict_type
+ self._sections = self._dict()
+ self._defaults = self._dict()
+ self._converters = ConverterMapping(self)
+ self._proxies = self._dict()
+ self._proxies[default_section] = SectionProxy(self, default_section)
+ self._delimiters = tuple(delimiters)
+ if delimiters == ('=', ':'):
+ self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
+ else:
+ d = "|".join(re.escape(d) for d in delimiters)
+ if allow_no_value:
+ self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d), re.VERBOSE)
+ else:
+ self._optcre = re.compile(self._OPT_TMPL.format(delim=d), re.VERBOSE)
+ self._comment_prefixes = tuple(comment_prefixes or ())
+ self._inline_comment_prefixes = tuple(inline_comment_prefixes or ())
+ self._strict = strict
+ self._allow_no_value = allow_no_value
+ self._empty_lines_in_values = empty_lines_in_values
+ self.default_section = default_section
+ self._interpolation = interpolation
+ if self._interpolation is _UNSET:
+ self._interpolation = self._DEFAULT_INTERPOLATION
+ if self._interpolation is None:
+ self._interpolation = Interpolation()
+ if converters is not _UNSET:
+ self._converters.update(converters)
+ if defaults:
+ self._read_defaults(defaults)
+
+ def defaults(self):
+ return self._defaults
+
+ def sections(self):
+ """Return a list of section names, excluding [DEFAULT]"""
+ # self._sections will never have [DEFAULT] in it
+ return list(self._sections.keys())
+
+ def add_section(self, section):
+ """Create a new section in the configuration.
+
+ Raise DuplicateSectionError if a section by the specified name
+ already exists. Raise ValueError if name is DEFAULT.
+ """
+ if section == self.default_section:
+ raise ValueError('Invalid section name: %r' % section)
+
+ if section in self._sections:
+ raise DuplicateSectionError(section)
+ self._sections[section] = self._dict()
+ self._proxies[section] = SectionProxy(self, section)
+
+ def has_section(self, section):
+ """Indicate whether the named section is present in the configuration.
+
+ The DEFAULT section is not acknowledged.
+ """
+ return section in self._sections
+
+ def options(self, section):
+ """Return a list of option names for the given section name."""
+ try:
+ opts = self._sections[section].copy()
+ except KeyError:
+ raise from_none(NoSectionError(section))
+ opts.update(self._defaults)
+ return list(opts.keys())
+
+ def read(self, filenames, encoding=None):
+ """Read and parse a filename or an iterable of filenames.
+
+ Files that cannot be opened are silently ignored; this is
+ designed so that you can specify an iterable of potential
+ configuration file locations (e.g. current directory, user's
+ home directory, systemwide directory), and all existing
+ configuration files in the iterable will be read. A single
+ filename may also be given.
+
+ Return list of successfully read files.
+ """
+ if isinstance(filenames, (str, bytes, PathLike)):
+ filenames = [filenames]
+ read_ok = []
+ for filename in filenames:
+ if isinstance(filename, PathLike):
+ filename = fspath(filename)
+ try:
+ with open(filename, encoding=encoding) as fp:
+ self._read(fp, filename)
+ except IOError:
+ continue
+ read_ok.append(filename)
+ return read_ok
+
+ def read_file(self, f, source=None):
+ """Like read() but the argument must be a file-like object.
+
+ The `f' argument must be iterable, returning one line at a time.
+ Optional second argument is the `source' specifying the name of the
+ file being read. If not given, it is taken from f.name. If `f' has no
+ `name' attribute, `<???>' is used.
+ """
+ if source is None:
+ try:
+ source = f.name
+ except AttributeError:
+ source = '<???>'
+ self._read(f, source)
+
+ def read_string(self, string, source='<string>'):
+ """Read configuration from a given string."""
+ sfile = io.StringIO(string)
+ self.read_file(sfile, source)
+
+ def read_dict(self, dictionary, source='<dict>'):
+ """Read configuration from a dictionary.
+
+ Keys are section names, values are dictionaries with keys and values
+ that should be present in the section. If the used dictionary type
+ preserves order, sections and their keys will be added in order.
+
+ All types held in the dictionary are converted to strings during
+ reading, including section names, option names and keys.
+
+ Optional second argument is the `source' specifying the name of the
+ dictionary being read.
+ """
+ elements_added = set()
+ for section, keys in dictionary.items():
+ section = str(section)
+ try:
+ self.add_section(section)
+ except (DuplicateSectionError, ValueError):
+ if self._strict and section in elements_added:
+ raise
+ elements_added.add(section)
+ for key, value in keys.items():
+ key = self.optionxform(str(key))
+ if value is not None:
+ value = str(value)
+ if self._strict and (section, key) in elements_added:
+ raise DuplicateOptionError(section, key, source)
+ elements_added.add((section, key))
+ self.set(section, key, value)
+
+ def readfp(self, fp, filename=None):
+ """Deprecated, use read_file instead."""
+ warnings.warn(
+ "This method will be removed in future versions. "
+ "Use 'parser.read_file()' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.read_file(fp, source=filename)
+
+ def get(self, section, option, **kwargs):
+ """Get an option value for a given section.
+
+ If `vars' is provided, it must be a dictionary. The option is looked up
+ in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
+ If the key is not found and `fallback' is provided, it is used as
+ a fallback value. `None' can be provided as a `fallback' value.
+
+ If interpolation is enabled and the optional argument `raw' is False,
+ all interpolations are expanded in the return values.
+
+ Arguments `raw', `vars', and `fallback' are keyword only.
+
+ The section DEFAULT is special.
+ """
+ # keyword-only arguments
+ raw = kwargs.get('raw', False)
+ vars = kwargs.get('vars', None)
+ fallback = kwargs.get('fallback', _UNSET)
+
+ try:
+ d = self._unify_values(section, vars)
+ except NoSectionError:
+ if fallback is _UNSET:
+ raise
+ else:
+ return fallback
+ option = self.optionxform(option)
+ try:
+ value = d[option]
+ except KeyError:
+ if fallback is _UNSET:
+ raise NoOptionError(option, section)
+ else:
+ return fallback
+
+ if raw or value is None:
+ return value
+ else:
+ return self._interpolation.before_get(self, section, option, value, d)
+
+ def _get(self, section, conv, option, **kwargs):
+ return conv(self.get(section, option, **kwargs))
+
+ def _get_conv(self, section, option, conv, **kwargs):
+ # keyword-only arguments
+ kwargs.setdefault('raw', False)
+ kwargs.setdefault('vars', None)
+ fallback = kwargs.pop('fallback', _UNSET)
+ try:
+ return self._get(section, conv, option, **kwargs)
+ except (NoSectionError, NoOptionError):
+ if fallback is _UNSET:
+ raise
+ return fallback
+
+ # getint, getfloat and getboolean provided directly for backwards compat
+ def getint(self, section, option, **kwargs):
+ # keyword-only arguments
+ kwargs.setdefault('raw', False)
+ kwargs.setdefault('vars', None)
+ kwargs.setdefault('fallback', _UNSET)
+ return self._get_conv(section, option, int, **kwargs)
+
+ def getfloat(self, section, option, **kwargs):
+ # keyword-only arguments
+ kwargs.setdefault('raw', False)
+ kwargs.setdefault('vars', None)
+ kwargs.setdefault('fallback', _UNSET)
+ return self._get_conv(section, option, float, **kwargs)
+
+ def getboolean(self, section, option, **kwargs):
+ # keyword-only arguments
+ kwargs.setdefault('raw', False)
+ kwargs.setdefault('vars', None)
+ kwargs.setdefault('fallback', _UNSET)
+ return self._get_conv(section, option, self._convert_to_boolean, **kwargs)
+
+ def items(self, section=_UNSET, raw=False, vars=None):
+ """Return a list of (name, value) tuples for each option in a section.
+
+ All % interpolations are expanded in the return values, based on the
+ defaults passed into the constructor, unless the optional argument
+ `raw' is true. Additional substitutions may be provided using the
+ `vars' argument, which must be a dictionary whose contents overrides
+ any pre-existing defaults.
+
+ The section DEFAULT is special.
+ """
+ if section is _UNSET:
+ return super(RawConfigParser, self).items()
+ d = self._defaults.copy()
+ try:
+ d.update(self._sections[section])
+ except KeyError:
+ if section != self.default_section:
+ raise NoSectionError(section)
+ orig_keys = list(d.keys())
+ # Update with the entry specific variables
+ if vars:
+ for key, value in vars.items():
+ d[self.optionxform(key)] = value
+ value_getter = lambda option: self._interpolation.before_get(
+ self, section, option, d[option], d
+ )
+ if raw:
+ value_getter = lambda option: d[option]
+ return [(option, value_getter(option)) for option in orig_keys]
+
+ def popitem(self):
+ """Remove a section from the parser and return it as
+ a (section_name, section_proxy) tuple. If no section is present, raise
+ KeyError.
+
+ The section DEFAULT is never returned because it cannot be removed.
+ """
+ for key in self.sections():
+ value = self[key]
+ del self[key]
+ return key, value
+ raise KeyError
+
+ def optionxform(self, optionstr):
+ return optionstr.lower()
+
+ def has_option(self, section, option):
+ """Check for the existence of a given option in a given section.
+ If the specified `section' is None or an empty string, DEFAULT is
+ assumed. If the specified `section' does not exist, returns False."""
+ if not section or section == self.default_section:
+ option = self.optionxform(option)
+ return option in self._defaults
+ elif section not in self._sections:
+ return False
+ else:
+ option = self.optionxform(option)
+ return option in self._sections[section] or option in self._defaults
+
+ def set(self, section, option, value=None):
+ """Set an option."""
+ if value:
+ value = self._interpolation.before_set(self, section, option, value)
+ if not section or section == self.default_section:
+ sectdict = self._defaults
+ else:
+ try:
+ sectdict = self._sections[section]
+ except KeyError:
+ raise from_none(NoSectionError(section))
+ sectdict[self.optionxform(option)] = value
+
+ def write(self, fp, space_around_delimiters=True):
+ """Write an .ini-format representation of the configuration state.
+
+ If `space_around_delimiters' is True (the default), delimiters
+ between keys and values are surrounded by spaces.
+ """
+ if space_around_delimiters:
+ d = " {0} ".format(self._delimiters[0])
+ else:
+ d = self._delimiters[0]
+ if self._defaults:
+ self._write_section(fp, self.default_section, self._defaults.items(), d)
+ for section in self._sections:
+ self._write_section(fp, section, self._sections[section].items(), d)
+
+ def _write_section(self, fp, section_name, section_items, delimiter):
+ """Write a single section to the specified `fp'."""
+ fp.write("[{0}]\n".format(section_name))
+ for key, value in section_items:
+ value = self._interpolation.before_write(self, section_name, key, value)
+ if value is not None or not self._allow_no_value:
+ value = delimiter + str(value).replace('\n', '\n\t')
+ else:
+ value = ""
+ fp.write("{0}{1}\n".format(key, value))
+ fp.write("\n")
+
+ def remove_option(self, section, option):
+ """Remove an option."""
+ if not section or section == self.default_section:
+ sectdict = self._defaults
+ else:
+ try:
+ sectdict = self._sections[section]
+ except KeyError:
+ raise from_none(NoSectionError(section))
+ option = self.optionxform(option)
+ existed = option in sectdict
+ if existed:
+ del sectdict[option]
+ return existed
+
+ def remove_section(self, section):
+ """Remove a file section."""
+ existed = section in self._sections
+ if existed:
+ del self._sections[section]
+ del self._proxies[section]
+ return existed
+
+ def __getitem__(self, key):
+ if key != self.default_section and not self.has_section(key):
+ raise KeyError(key)
+ return self._proxies[key]
+
+ def __setitem__(self, key, value):
+ # To conform with the mapping protocol, overwrites existing values in
+ # the section.
+ if key in self and self[key] is value:
+ return
+ # XXX this is not atomic if read_dict fails at any point. Then again,
+ # no update method in configparser is atomic in this implementation.
+ if key == self.default_section:
+ self._defaults.clear()
+ elif key in self._sections:
+ self._sections[key].clear()
+ self.read_dict({key: value})
+
+ def __delitem__(self, key):
+ if key == self.default_section:
+ raise ValueError("Cannot remove the default section.")
+ if not self.has_section(key):
+ raise KeyError(key)
+ self.remove_section(key)
+
+ def __contains__(self, key):
+ return key == self.default_section or self.has_section(key)
+
+ def __len__(self):
+ return len(self._sections) + 1 # the default section
+
+ def __iter__(self):
+ # XXX does it break when underlying container state changed?
+ return itertools.chain((self.default_section,), self._sections.keys())
+
+ def _read(self, fp, fpname):
+ """Parse a sectioned configuration file.
+
+ Each section in a configuration file contains a header, indicated by
+ a name in square brackets (`[]'), plus key/value options, indicated by
+ `name' and `value' delimited with a specific substring (`=' or `:' by
+ default).
+
+ Values can span multiple lines, as long as they are indented deeper
+ than the first line of the value. Depending on the parser's mode, blank
+ lines may be treated as parts of multiline values or ignored.
+
+ Configuration files may include comments, prefixed by specific
+ characters (`#' and `;' by default). Comments may appear on their own
+ in an otherwise empty line or may be entered in lines holding values or
+ section names.
+ """
+ elements_added = set()
+ cursect = None # None, or a dictionary
+ sectname = None
+ optname = None
+ lineno = 0
+ indent_level = 0
+ e = None # None, or an exception
+ for lineno, line in enumerate(fp, start=1):
+ comment_start = sys.maxsize
+ # strip inline comments
+ inline_prefixes = dict((p, -1) for p in self._inline_comment_prefixes)
+ while comment_start == sys.maxsize and inline_prefixes:
+ next_prefixes = {}
+ for prefix, index in inline_prefixes.items():
+ index = line.find(prefix, index + 1)
+ if index == -1:
+ continue
+ next_prefixes[prefix] = index
+ if index == 0 or (index > 0 and line[index - 1].isspace()):
+ comment_start = min(comment_start, index)
+ inline_prefixes = next_prefixes
+ # strip full line comments
+ for prefix in self._comment_prefixes:
+ if line.strip().startswith(prefix):
+ comment_start = 0
+ break
+ if comment_start == sys.maxsize:
+ comment_start = None
+ value = line[:comment_start].strip()
+ if not value:
+ if self._empty_lines_in_values:
+ # add empty line to the value, but only if there was no
+ # comment on the line
+ if (
+ comment_start is None
+ and cursect is not None
+ and optname
+ and cursect[optname] is not None
+ ):
+ cursect[optname].append('') # newlines added at join
+ else:
+ # empty line marks end of value
+ indent_level = sys.maxsize
+ continue
+ # continuation line?
+ first_nonspace = self.NONSPACECRE.search(line)
+ cur_indent_level = first_nonspace.start() if first_nonspace else 0
+ if cursect is not None and optname and cur_indent_level > indent_level:
+ cursect[optname].append(value)
+ # a section header or option header?
+ else:
+ indent_level = cur_indent_level
+ # is it a section header?
+ mo = self.SECTCRE.match(value)
+ if mo:
+ sectname = mo.group('header')
+ if sectname in self._sections:
+ if self._strict and sectname in elements_added:
+ raise DuplicateSectionError(sectname, fpname, lineno)
+ cursect = self._sections[sectname]
+ elements_added.add(sectname)
+ elif sectname == self.default_section:
+ cursect = self._defaults
+ else:
+ cursect = self._dict()
+ self._sections[sectname] = cursect
+ self._proxies[sectname] = SectionProxy(self, sectname)
+ elements_added.add(sectname)
+ # So sections can't start with a continuation line
+ optname = None
+ # no section header in the file?
+ elif cursect is None:
+ raise MissingSectionHeaderError(fpname, lineno, line)
+ # an option line?
+ else:
+ mo = self._optcre.match(value)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ if not optname:
+ e = self._handle_error(e, fpname, lineno, line)
+ optname = self.optionxform(optname.rstrip())
+ if self._strict and (sectname, optname) in elements_added:
+ raise DuplicateOptionError(
+ sectname, optname, fpname, lineno
+ )
+ elements_added.add((sectname, optname))
+ # This check is fine because the OPTCRE cannot
+ # match if it would set optval to None
+ if optval is not None:
+ optval = optval.strip()
+ cursect[optname] = [optval]
+ else:
+ # valueless option handling
+ cursect[optname] = None
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ e = self._handle_error(e, fpname, lineno, line)
+ self._join_multiline_values()
+ # if any parsing errors occurred, raise an exception
+ if e:
+ raise e
+
+ def _join_multiline_values(self):
+ defaults = self.default_section, self._defaults
+ all_sections = itertools.chain((defaults,), self._sections.items())
+ for section, options in all_sections:
+ for name, val in options.items():
+ if isinstance(val, list):
+ val = '\n'.join(val).rstrip()
+ options[name] = self._interpolation.before_read(
+ self, section, name, val
+ )
+
+ def _read_defaults(self, defaults):
+ """Read the defaults passed in the initializer.
+ Note: values can be non-string."""
+ for key, value in defaults.items():
+ self._defaults[self.optionxform(key)] = value
+
+ def _handle_error(self, exc, fpname, lineno, line):
+ if not exc:
+ exc = ParsingError(fpname)
+ exc.append(lineno, repr(line))
+ return exc
+
+ def _unify_values(self, section, vars):
+ """Create a sequence of lookups with 'vars' taking priority over
+ the 'section' which takes priority over the DEFAULTSECT.
+
+ """
+ sectiondict = {}
+ try:
+ sectiondict = self._sections[section]
+ except KeyError:
+ if section != self.default_section:
+ raise NoSectionError(section)
+ # Update with the entry specific variables
+ vardict = {}
+ if vars:
+ for key, value in vars.items():
+ if value is not None:
+ value = str(value)
+ vardict[self.optionxform(key)] = value
+ return _ChainMap(vardict, sectiondict, self._defaults)
+
+ def _convert_to_boolean(self, value):
+ """Return a boolean value translating from other types if necessary.
+ """
+ if value.lower() not in self.BOOLEAN_STATES:
+ raise ValueError('Not a boolean: %s' % value)
+ return self.BOOLEAN_STATES[value.lower()]
+
+ def _validate_value_types(self, **kwargs):
+ """Raises a TypeError for non-string values.
+
+ The only legal non-string value if we allow valueless
+ options is None, so we need to check if the value is a
+ string if:
+ - we do not allow valueless options, or
+ - we allow valueless options but the value is not None
+
+ For compatibility reasons this method is not used in classic set()
+ for RawConfigParsers. It is invoked in every case for mapping protocol
+ access and in ConfigParser.set().
+ """
+ # keyword-only arguments
+ section = kwargs.get('section', "")
+ option = kwargs.get('option', "")
+ value = kwargs.get('value', "")
+
+ if PY2 and bytes in (type(section), type(option), type(value)):
+ # we allow for a little unholy magic for Python 2 so that
+ # people not using unicode_literals can still use the library
+ # conveniently
+ warnings.warn(
+ "You passed a bytestring. Implicitly decoding as UTF-8 string."
+ " This will not work on Python 3. Please switch to using"
+ " Unicode strings across the board.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if isinstance(section, bytes):
+ section = section.decode('utf8')
+ if isinstance(option, bytes):
+ option = option.decode('utf8')
+ if isinstance(value, bytes):
+ value = value.decode('utf8')
+
+ if not isinstance(section, str):
+ raise TypeError("section names must be strings")
+ if not isinstance(option, str):
+ raise TypeError("option keys must be strings")
+ if not self._allow_no_value or value:
+ if not isinstance(value, str):
+ raise TypeError("option values must be strings")
+
+ return section, option, value
+
+ @property
+ def converters(self):
+ return self._converters
+
+
+class ConfigParser(RawConfigParser):
+ """ConfigParser implementing interpolation."""
+
+ _DEFAULT_INTERPOLATION = BasicInterpolation()
+
+ def set(self, section, option, value=None):
+ """Set an option. Extends RawConfigParser.set by validating type and
+ interpolation syntax on the value."""
+ _, option, value = self._validate_value_types(option=option, value=value)
+ super(ConfigParser, self).set(section, option, value)
+
+ def add_section(self, section):
+ """Create a new section in the configuration. Extends
+ RawConfigParser.add_section by validating if the section name is
+ a string."""
+ section, _, _ = self._validate_value_types(section=section)
+ super(ConfigParser, self).add_section(section)
+
+ def _read_defaults(self, defaults):
+ """Reads the defaults passed in the initializer, implicitly converting
+ values to strings like the rest of the API.
+
+ Does not perform interpolation for backwards compatibility.
+ """
+ try:
+ hold_interpolation = self._interpolation
+ self._interpolation = Interpolation()
+ self.read_dict({self.default_section: defaults})
+ finally:
+ self._interpolation = hold_interpolation
+
+
+class SafeConfigParser(ConfigParser):
+ """ConfigParser alias for backwards compatibility purposes."""
+
+ def __init__(self, *args, **kwargs):
+ super(SafeConfigParser, self).__init__(*args, **kwargs)
+ warnings.warn(
+ "The SafeConfigParser class has been renamed to ConfigParser "
+ "in Python 3.2. This alias will be removed in future versions."
+ " Use ConfigParser directly instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+
+class SectionProxy(MutableMapping):
+ """A proxy for a single section from a parser."""
+
+ def __init__(self, parser, name):
+ """Creates a view on a section of the specified `name` in `parser`."""
+ self._parser = parser
+ self._name = name
+ for conv in parser.converters:
+ key = 'get' + conv
+ getter = functools.partial(self.get, _impl=getattr(parser, key))
+ setattr(self, key, getter)
+
+ def __repr__(self):
+ return '<Section: {0}>'.format(self._name)
+
+ def __getitem__(self, key):
+ if not self._parser.has_option(self._name, key):
+ raise KeyError(key)
+ return self._parser.get(self._name, key)
+
+ def __setitem__(self, key, value):
+ _, key, value = self._parser._validate_value_types(option=key, value=value)
+ return self._parser.set(self._name, key, value)
+
+ def __delitem__(self, key):
+ if not (
+ self._parser.has_option(self._name, key)
+ and self._parser.remove_option(self._name, key)
+ ):
+ raise KeyError(key)
+
+ def __contains__(self, key):
+ return self._parser.has_option(self._name, key)
+
+ def __len__(self):
+ return len(self._options())
+
+ def __iter__(self):
+ return self._options().__iter__()
+
+ def _options(self):
+ if self._name != self._parser.default_section:
+ return self._parser.options(self._name)
+ else:
+ return self._parser.defaults()
+
+ @property
+ def parser(self):
+ # The parser object of the proxy is read-only.
+ return self._parser
+
+ @property
+ def name(self):
+ # The name of the section on a proxy is read-only.
+ return self._name
+
+ def get(self, option, fallback=None, **kwargs):
+ """Get an option value.
+
+ Unless `fallback` is provided, `None` will be returned if the option
+ is not found.
+
+ """
+ # keyword-only arguments
+ kwargs.setdefault('raw', False)
+ kwargs.setdefault('vars', None)
+ _impl = kwargs.pop('_impl', None)
+ # If `_impl` is provided, it should be a getter method on the parser
+ # object that provides the desired type conversion.
+ if not _impl:
+ _impl = self._parser.get
+ return _impl(self._name, option, fallback=fallback, **kwargs)
+
+
+class ConverterMapping(MutableMapping):
+ """Enables reuse of get*() methods between the parser and section proxies.
+
+ If a parser class implements a getter directly, the value for the given
+ key will be ``None``. The presence of the converter name here enables
+ section proxies to find and use the implementation on the parser class.
+ """
+
+ GETTERCRE = re.compile(r"^get(?P<name>.+)$")
+
+ def __init__(self, parser):
+ self._parser = parser
+ self._data = {}
+ for getter in dir(self._parser):
+ m = self.GETTERCRE.match(getter)
+ if not m or not callable(getattr(self._parser, getter)):
+ continue
+ self._data[m.group('name')] = None # See class docstring.
+
+ def __getitem__(self, key):
+ return self._data[key]
+
+ def __setitem__(self, key, value):
+ try:
+ k = 'get' + key
+ except TypeError:
+ raise ValueError(
+ 'Incompatible key: {} (type: {})' ''.format(key, type(key))
+ )
+ if k == 'get':
+ raise ValueError('Incompatible key: cannot use "" as a name')
+ self._data[key] = value
+ func = functools.partial(self._parser._get_conv, conv=value)
+ func.converter = value
+ setattr(self._parser, k, func)
+ for proxy in self._parser.values():
+ getter = functools.partial(proxy.get, _impl=func)
+ setattr(proxy, k, getter)
+
+ def __delitem__(self, key):
+ try:
+ k = 'get' + (key or None)
+ except TypeError:
+ raise KeyError(key)
+ del self._data[key]
+ for inst in itertools.chain((self._parser,), self._parser.values()):
+ try:
+ delattr(inst, k)
+ except AttributeError:
+ # don't raise since the entry was present in _data, silently
+ # clean up
+ continue
+
+ def __iter__(self):
+ return iter(self._data)
+
+ def __len__(self):
+ return len(self._data)
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/helpers.py b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/helpers.py
new file mode 100644
index 0000000000..e7eb72243f
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/helpers.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import abc
+import os
+
+try:
+ from collections.abc import MutableMapping
+except ImportError:
+ from collections import MutableMapping
+
+try:
+ from collections import UserDict
+except ImportError:
+ from UserDict import UserDict
+
+try:
+ from collections import OrderedDict
+except ImportError:
+ from ordereddict import OrderedDict
+
+try:
+ import pathlib
+except ImportError:
+ pathlib = None
+
+from io import open
+import sys
+
+try:
+ from thread import get_ident
+except ImportError:
+ try:
+ from _thread import get_ident
+ except ImportError:
+ from _dummy_thread import get_ident
+
+
+__all__ = ['UserDict', 'OrderedDict', 'open']
+
+
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+
+native_str = str
+str = type('str')
+
+
+def from_none(exc):
+ """raise from_none(ValueError('a')) == raise ValueError('a') from None"""
+ exc.__cause__ = None
+ exc.__suppress_context__ = True
+ return exc
+
+
+# from reprlib 3.2.1
+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
+
+
+# from collections 3.2.1
+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:
+ # can't use 'key in mapping' with defaultdict
+ return mapping[key]
+ except KeyError:
+ pass
+ # support subclasses that define __missing__
+ return self.__missing__(key)
+
+ def get(self, key, default=None):
+ return self[key] if key in self else default
+
+ def __len__(self):
+ # reuses stored hash values if possible
+ return len(set().union(*self.maps))
+
+ def __iter__(self):
+ return iter(set().union(*self.maps))
+
+ def __contains__(self, key):
+ return any(key in m for m in 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 collections import ChainMap
+except ImportError:
+ ChainMap = _ChainMap
+
+
+_ABC = getattr(
+ abc,
+ 'ABC',
+ # Python 3.3 compatibility
+ abc.ABCMeta(native_str('__ABC'), (object,), dict(__metaclass__=abc.ABCMeta)),
+)
+
+
+class _PathLike(_ABC):
+
+ """Abstract base class for implementing the file system path protocol."""
+
+ @abc.abstractmethod
+ def __fspath__(self):
+ """Return the file system path representation of the object."""
+ raise NotImplementedError
+
+ @classmethod
+ def __subclasshook__(cls, subclass):
+ return bool(
+ hasattr(subclass, '__fspath__')
+ # workaround for Python 3.5
+ or pathlib
+ and issubclass(subclass, pathlib.Path)
+ )
+
+
+PathLike = getattr(os, 'PathLike', _PathLike)
+
+
+def _fspath(path):
+ """Return the path representation of a path-like object.
+
+ If str or bytes is passed in, it is returned unchanged. Otherwise the
+ os.PathLike interface is used to get the path representation. If the
+ path representation is not str or bytes, TypeError is raised. If the
+ provided path is not str, bytes, or os.PathLike, TypeError is raised.
+ """
+ if isinstance(path, (str, bytes)):
+ return path
+
+ if not hasattr(path, '__fspath__') and isinstance(path, pathlib.Path):
+ # workaround for Python 3.5
+ return str(path)
+
+ # Work from the object's type to match method resolution of other magic
+ # methods.
+ path_type = type(path)
+ try:
+ path_repr = path_type.__fspath__(path)
+ except AttributeError:
+
+ if hasattr(path_type, '__fspath__'):
+ raise
+ else:
+ raise TypeError(
+ "expected str, bytes or os.PathLike object, "
+ "not " + path_type.__name__
+ )
+ if isinstance(path_repr, (str, bytes)):
+ return path_repr
+ else:
+ raise TypeError(
+ "expected {}.__fspath__() to return str or bytes, "
+ "not {}".format(path_type.__name__, type(path_repr).__name__)
+ )
+
+
+fspath = getattr(os, 'fspath', _fspath)
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/LICENSE
new file mode 100644
index 0000000000..5e795a61f3
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/LICENSE
@@ -0,0 +1,7 @@
+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.
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/METADATA
new file mode 100644
index 0000000000..e805cc962c
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/METADATA
@@ -0,0 +1,259 @@
+Metadata-Version: 2.1
+Name: configparser
+Version: 4.0.2
+Summary: Updated configparser from Python 3.7 for Python 2.6+.
+Home-page: https://github.com/jaraco/configparser/
+Author: Łukasz Langa
+Author-email: lukasz@langa.pl
+Maintainer: Jason R. Coombs
+Maintainer-email: jaraco@jaraco.com
+License: UNKNOWN
+Keywords: configparser ini parsing conf cfg configuration file
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Requires-Python: >=2.6
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs'
+Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
+Provides-Extra: testing
+Requires-Dist: pytest (!=3.7.3,>=3.5) ; extra == 'testing'
+Requires-Dist: pytest-checkdocs (>=1.2) ; extra == 'testing'
+Requires-Dist: pytest-flake8 ; extra == 'testing'
+Requires-Dist: pytest-black-multipy ; extra == 'testing'
+
+.. image:: https://img.shields.io/pypi/v/configparser.svg
+ :target: https://pypi.org/project/configparser
+
+.. image:: https://img.shields.io/pypi/pyversions/configparser.svg
+
+.. image:: https://img.shields.io/travis/jaraco/configparser/master.svg
+ :target: https://travis-ci.org/jaraco/configparser
+
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+ :target: https://github.com/ambv/black
+ :alt: Code style: Black
+
+.. .. image:: https://img.shields.io/appveyor/ci/jaraco/configparser/master.svg
+.. :target: https://ci.appveyor.com/project/jaraco/configparser/branch/master
+
+.. image:: https://readthedocs.org/projects/configparser/badge/?version=latest
+ :target: https://configparser.readthedocs.io/en/latest/?badge=latest
+
+.. image:: https://tidelift.com/badges/package/pypi/configparser
+ :target: https://tidelift.com/subscription/pkg/pypi-configparser?utm_source=pypi-configparser&utm_medium=readme
+
+
+The ancient ``ConfigParser`` module available in the standard library 2.x has
+seen a major update in Python 3.2. This is a backport of those changes so that
+they can be used directly in Python 2.6 - 3.5.
+
+To use the ``configparser`` backport instead of the built-in version on both
+Python 2 and Python 3, simply import it explicitly as a backport::
+
+ from backports import configparser
+
+If you'd like to use the backport on Python 2 and the built-in version on
+Python 3, use that invocation instead::
+
+ import configparser
+
+For detailed documentation consult the vanilla version at
+http://docs.python.org/3/library/configparser.html.
+
+Why you'll love ``configparser``
+--------------------------------
+
+Whereas almost completely compatible with its older brother, ``configparser``
+sports a bunch of interesting new features:
+
+* full mapping protocol access (`more info
+ <http://docs.python.org/3/library/configparser.html#mapping-protocol-access>`_)::
+
+ >>> parser = ConfigParser()
+ >>> parser.read_string("""
+ [DEFAULT]
+ location = upper left
+ visible = yes
+ editable = no
+ color = blue
+
+ [main]
+ title = Main Menu
+ color = green
+
+ [options]
+ title = Options
+ """)
+ >>> parser['main']['color']
+ 'green'
+ >>> parser['main']['editable']
+ 'no'
+ >>> section = parser['options']
+ >>> section['title']
+ 'Options'
+ >>> section['title'] = 'Options (editable: %(editable)s)'
+ >>> section['title']
+ 'Options (editable: no)'
+
+* there's now one default ``ConfigParser`` class, which basically is the old
+ ``SafeConfigParser`` with a bunch of tweaks which make it more predictable for
+ users. Don't need interpolation? Simply use
+ ``ConfigParser(interpolation=None)``, no need to use a distinct
+ ``RawConfigParser`` anymore.
+
+* the parser is highly `customizable upon instantiation
+ <http://docs.python.org/3/library/configparser.html#customizing-parser-behaviour>`__
+ supporting things like changing option delimiters, comment characters, the
+ name of the DEFAULT section, the interpolation syntax, etc.
+
+* you can easily create your own interpolation syntax but there are two powerful
+ implementations built-in (`more info
+ <http://docs.python.org/3/library/configparser.html#interpolation-of-values>`__):
+
+ * the classic ``%(string-like)s`` syntax (called ``BasicInterpolation``)
+
+ * a new ``${buildout:like}`` syntax (called ``ExtendedInterpolation``)
+
+* fallback values may be specified in getters (`more info
+ <http://docs.python.org/3/library/configparser.html#fallback-values>`__)::
+
+ >>> config.get('closet', 'monster',
+ ... fallback='No such things as monsters')
+ 'No such things as monsters'
+
+* ``ConfigParser`` objects can now read data directly `from strings
+ <http://docs.python.org/3/library/configparser.html#configparser.ConfigParser.read_string>`__
+ and `from dictionaries
+ <http://docs.python.org/3/library/configparser.html#configparser.ConfigParser.read_dict>`__.
+ That means importing configuration from JSON or specifying default values for
+ the whole configuration (multiple sections) is now a single line of code. Same
+ goes for copying data from another ``ConfigParser`` instance, thanks to its
+ mapping protocol support.
+
+* many smaller tweaks, updates and fixes
+
+A few words about Unicode
+-------------------------
+
+``configparser`` comes from Python 3 and as such it works well with Unicode.
+The library is generally cleaned up in terms of internal data storage and
+reading/writing files. There are a couple of incompatibilities with the old
+``ConfigParser`` due to that. However, the work required to migrate is well
+worth it as it shows the issues that would likely come up during migration of
+your project to Python 3.
+
+The design assumes that Unicode strings are used whenever possible [1]_. That
+gives you the certainty that what's stored in a configuration object is text.
+Once your configuration is read, the rest of your application doesn't have to
+deal with encoding issues. All you have is text [2]_. The only two phases when
+you should explicitly state encoding is when you either read from an external
+source (e.g. a file) or write back.
+
+Versioning
+----------
+
+This project uses `semver <https://semver.org/spec/v2.0.0.html>`_ to
+communicate the impact of various releases while periodically syncing
+with the upstream implementation in CPython.
+`The changelog <https://github.com/jaraco/configparser/blob/master/CHANGES.rst>`_
+serves as a reference indicating which versions incorporate
+which upstream functionality.
+
+Prior to the ``4.0.0`` release, `another scheme
+<https://github.com/jaraco/configparser/blob/3.8.1/README.rst#versioning>`_
+was used to associate the CPython and backports releases.
+
+Maintenance
+-----------
+
+This backport was originally authored by Łukasz Langa, the current vanilla
+``configparser`` maintainer for CPython and is currently maintained by
+Jason R. Coombs:
+
+* `configparser repository <https://github.com/jaraco/configparser>`_
+
+* `configparser issue tracker <https://github.com/jaraco/configparser/issues>`_
+
+Security Contact
+----------------
+
+To report a security vulnerability, please use the
+`Tidelift security contact <https://tidelift.com/security>`_.
+Tidelift will coordinate the fix and disclosure.
+
+Conversion Process
+------------------
+
+This section is technical and should bother you only if you are wondering how
+this backport is produced. If the implementation details of this backport are
+not important for you, feel free to ignore the following content.
+
+``configparser`` is converted using `python-future
+<http://python-future.org>`_. The project takes the following
+branching approach:
+
+* the ``3.x`` branch holds unchanged files synchronized from the upstream
+ CPython repository. The synchronization is currently done by manually copying
+ the required files and stating from which CPython changeset they come from.
+
+* the ``master`` branch holds a version of the ``3.x`` code with some tweaks
+ that make it independent from libraries and constructions unavailable on 2.x.
+ Code on this branch still *must* work on the corresponding Python 3.x but
+ will also work on Python 2.6 and 2.7 (including PyPy). You can check this
+ running the supplied unit tests with ``tox``.
+
+The process works like this:
+
+1. In the ``3.x`` branch, run ``pip-run -- sync-upstream.py``, which
+ downloads the latest stable release of Python and copies the relevant
+ files from there into their new locations here and then commits those
+ changes with a nice reference to the relevant upstream commit hash.
+
+2. I check for new names in ``__all__`` and update imports in
+ ``configparser.py`` accordingly. I run the tests on Python 3. Commit.
+
+3. I merge the new commit to ``master``. I run ``tox``. Commit.
+
+4. If there are necessary changes, I do them now (on ``master``). Note that
+ the changes should be written in the syntax subset supported by Python
+ 2.6.
+
+5. I run ``tox``. If it works, I update the docs and release the new version.
+ Otherwise, I go back to point 3. I might use ``pasteurize`` to suggest me
+ required changes but usually I do them manually to keep resulting code in
+ a nicer form.
+
+
+Footnotes
+---------
+
+.. [1] To somewhat ease migration, passing bytestrings is still supported but
+ they are converted to Unicode for internal storage anyway. This means
+ that for the vast majority of strings used in configuration files, it
+ won't matter if you pass them as bytestrings or Unicode. However, if you
+ pass a bytestring that cannot be converted to Unicode using the naive
+ ASCII codec, a ``UnicodeDecodeError`` will be raised. This is purposeful
+ and helps you manage proper encoding for all content you store in
+ memory, read from various sources and write back.
+
+.. [2] Life gets much easier when you understand that you basically manage
+ **text** in your application. You don't care about bytes but about
+ letters. In that regard the concept of content encoding is meaningless.
+ The only time when you deal with raw bytes is when you write the data to
+ a file. Then you have to specify how your text should be encoded. On
+ the other end, to get meaningful text from a file, the application
+ reading it has to know which encoding was used during its creation. But
+ once the bytes are read and properly decoded, all you have is text. This
+ is especially powerful when you start interacting with multiple data
+ sources. Even if each of them uses a different encoding, inside your
+ application data is held in abstract text form. You can program your
+ business logic without worrying about which data came from which source.
+ You can freely exchange the data you store between sources. Only
+ reading/writing files requires encoding your text to bytes.
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/RECORD
new file mode 100644
index 0000000000..a4f777392e
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/RECORD
@@ -0,0 +1,9 @@
+configparser.py,sha256=4VADEswCwzy_RDVgvje3BmZhD6iwo3k4EkUZcgzLD4M,1546
+backports/__init__.py,sha256=elt6uFwbaEv80X8iGWsCJ_w_n_h1X8repgOoNrN0Syg,212
+backports/configparser/__init__.py,sha256=thhQqB1qWNKf-F3CpZFYsjC8YT-_I_vF0w4JiuQfiWI,56628
+backports/configparser/helpers.py,sha256=TxT00ldsHvIciQpml1YaoHfdtTl033Km6ywwT-U2nRc,7543
+configparser-4.0.2.dist-info/LICENSE,sha256=pV4v_ptEmY5iHVHYwJS-0JrMS1I27nPX3zlaM7o8GP0,1050
+configparser-4.0.2.dist-info/METADATA,sha256=oDqeXQXq8JhFEDHKDOBmbUtXmQ3CiiXB1Mr4UheTZ8Y,10910
+configparser-4.0.2.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
+configparser-4.0.2.dist-info/top_level.txt,sha256=mIs8gajd7cvEWhVluv4u6ocaHw_TJ9rOrpkZEFv-7Hc,23
+configparser-4.0.2.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/WHEEL
new file mode 100644
index 0000000000..8b701e93c2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.33.6)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/top_level.txt
new file mode 100644
index 0000000000..a6cb03ad92
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info/top_level.txt
@@ -0,0 +1,2 @@
+backports
+configparser
diff --git a/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser.py b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser.py
new file mode 100644
index 0000000000..0a18360239
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""Convenience module importing everything from backports.configparser."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+from backports.configparser import (
+ RawConfigParser,
+ ConfigParser,
+ SafeConfigParser,
+ SectionProxy,
+ Interpolation,
+ BasicInterpolation,
+ ExtendedInterpolation,
+ LegacyInterpolation,
+ NoSectionError,
+ DuplicateSectionError,
+ DuplicateOptionError,
+ NoOptionError,
+ InterpolationError,
+ InterpolationMissingOptionError,
+ InterpolationSyntaxError,
+ InterpolationDepthError,
+ ParsingError,
+ MissingSectionHeaderError,
+ ConverterMapping,
+ DEFAULTSECT,
+ MAX_INTERPOLATION_DEPTH,
+)
+
+from backports.configparser import Error, _UNSET, _default_dict, _ChainMap # noqa: F401
+
+__all__ = [
+ "NoSectionError",
+ "DuplicateOptionError",
+ "DuplicateSectionError",
+ "NoOptionError",
+ "InterpolationError",
+ "InterpolationDepthError",
+ "InterpolationMissingOptionError",
+ "InterpolationSyntaxError",
+ "ParsingError",
+ "MissingSectionHeaderError",
+ "ConfigParser",
+ "SafeConfigParser",
+ "RawConfigParser",
+ "Interpolation",
+ "BasicInterpolation",
+ "ExtendedInterpolation",
+ "LegacyInterpolation",
+ "SectionProxy",
+ "ConverterMapping",
+ "DEFAULTSECT",
+ "MAX_INTERPOLATION_DEPTH",
+]
+
+# NOTE: names missing from __all__ imported anyway for backwards compatibility.
diff --git a/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/LICENSE.txt b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/LICENSE.txt
new file mode 100644
index 0000000000..5de20277df
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/LICENSE.txt
@@ -0,0 +1,122 @@
+
+
+A. HISTORY OF THE SOFTWARE
+==========================
+
+contextlib2 is a derivative of the contextlib module distributed by the PSF
+as part of the Python standard library. According, it is itself redistributed
+under the PSF license (reproduced in full below). As the contextlib module
+was added only in Python 2.5, the licenses for earlier Python versions are
+not applicable and have not been included.
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC. Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team. In October of the same
+year, the PythonLabs team moved to Digital Creations (now Zope
+Corporation, see http://www.zope.com). In 2001, the Python Software
+Foundation (PSF, see http://www.python.org/psf/) was formed, a
+non-profit organization created specifically to own Python-related
+Intellectual Property. Zope Corporation is a sponsoring member of
+the PSF.
+
+All Python releases are Open Source (see http://www.opensource.org for
+the Open Source Definition). Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases that included the contextlib module.
+
+ Release Derived Year Owner GPL-
+ from compatible? (1)
+
+ 2.5 2.4 2006 PSF yes
+ 2.5.1 2.5 2007 PSF yes
+ 2.5.2 2.5.1 2008 PSF yes
+ 2.5.3 2.5.2 2008 PSF yes
+ 2.6 2.5 2008 PSF yes
+ 2.6.1 2.6 2008 PSF yes
+ 2.6.2 2.6.1 2009 PSF yes
+ 2.6.3 2.6.2 2009 PSF yes
+ 2.6.4 2.6.3 2009 PSF yes
+ 2.6.5 2.6.4 2010 PSF yes
+ 3.0 2.6 2008 PSF yes
+ 3.0.1 3.0 2009 PSF yes
+ 3.1 3.0.1 2009 PSF yes
+ 3.1.1 3.1 2009 PSF yes
+ 3.1.2 3.1.1 2010 PSF yes
+ 3.1.3 3.1.2 2010 PSF yes
+ 3.1.4 3.1.3 2011 PSF yes
+ 3.2 3.1 2011 PSF yes
+ 3.2.1 3.2 2011 PSF yes
+ 3.2.2 3.2.1 2011 PSF yes
+ 3.3 3.2 2012 PSF yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+ the GPL. All Python licenses, unlike the GPL, let you distribute
+ a modified version without making your changes open source. The
+ GPL-compatible licenses make it possible to combine Python with
+ other software that is released under the GPL; the others don't.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+PYTHON SOFTWARE 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/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/METADATA
new file mode 100644
index 0000000000..c44f02deb5
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/METADATA
@@ -0,0 +1,70 @@
+Metadata-Version: 2.1
+Name: contextlib2
+Version: 0.6.0.post1
+Summary: Backports and enhancements for the contextlib module
+Home-page: http://contextlib2.readthedocs.org
+Author: Nick Coghlan
+Author-email: ncoghlan@gmail.com
+License: PSF License
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+
+.. image:: https://jazzband.co/static/img/badge.svg
+ :target: https://jazzband.co/
+ :alt: Jazzband
+
+.. image:: https://readthedocs.org/projects/contextlib2/badge/?version=latest
+ :target: https://contextlib2.readthedocs.org/
+ :alt: Latest Docs
+
+.. image:: https://img.shields.io/travis/jazzband/contextlib2/master.svg
+ :target: http://travis-ci.org/jazzband/contextlib2
+
+.. image:: https://coveralls.io/repos/github/jazzband/contextlib2/badge.svg?branch=master
+ :target: https://coveralls.io/github/jazzband/contextlib2?branch=master
+
+.. image:: https://landscape.io/github/jazzband/contextlib2/master/landscape.svg
+ :target: https://landscape.io/github/jazzband/contextlib2/
+
+contextlib2 is a backport of the `standard library's contextlib
+module <https://docs.python.org/3.5/library/contextlib.html>`_ to
+earlier Python versions.
+
+It also serves as a real world proving ground for possible future
+enhancements to the standard library version.
+
+Development
+-----------
+
+contextlib2 has no runtime dependencies, but requires ``unittest2`` for testing
+on Python 2.x, as well as ``setuptools`` and ``wheel`` to generate universal
+wheel archives.
+
+Local testing is just a matter of running ``python test_contextlib2.py``.
+
+You can test against multiple versions of Python with
+`tox <https://tox.testrun.org/>`_::
+
+ pip install tox
+ tox
+
+Versions currently tested in both tox and Travis CI are:
+
+* CPython 2.7
+* CPython 3.4
+* CPython 3.5
+* CPython 3.6
+* CPython 3.7
+* PyPy
+* PyPy3
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/RECORD
new file mode 100644
index 0000000000..f16410863e
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/RECORD
@@ -0,0 +1,6 @@
+contextlib2.py,sha256=5HjGflUzwWAUfcILhSmC2GqvoYdZZzFzVfIDztHigUs,16915
+contextlib2-0.6.0.post1.dist-info/LICENSE.txt,sha256=xqev-sas2tLS3YfS12hDhiSraSYY2x8CvqOxHT85ePA,6054
+contextlib2-0.6.0.post1.dist-info/METADATA,sha256=_kBcf3VJkbe-EMyAM1c5t5sRwBFfFu5YcfWCJMgVO1Q,2297
+contextlib2-0.6.0.post1.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
+contextlib2-0.6.0.post1.dist-info/top_level.txt,sha256=RxWWBMkHA_rsw1laXJ8L3yE_fyYaBmvt2bVUvj3WbMg,12
+contextlib2-0.6.0.post1.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/WHEEL
new file mode 100644
index 0000000000..8b701e93c2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.33.6)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/top_level.txt
new file mode 100644
index 0000000000..03fdf8ed24
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info/top_level.txt
@@ -0,0 +1 @@
+contextlib2
diff --git a/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2.py b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2.py
new file mode 100644
index 0000000000..3aae8f4117
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2.py
@@ -0,0 +1,518 @@
+"""contextlib2 - backports and enhancements to the contextlib module"""
+
+import abc
+import sys
+import warnings
+from collections import deque
+from functools import wraps
+
+__all__ = ["contextmanager", "closing", "nullcontext",
+ "AbstractContextManager",
+ "ContextDecorator", "ExitStack",
+ "redirect_stdout", "redirect_stderr", "suppress"]
+
+# Backwards compatibility
+__all__ += ["ContextStack"]
+
+
+# Backport abc.ABC
+if sys.version_info[:2] >= (3, 4):
+ _abc_ABC = abc.ABC
+else:
+ _abc_ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()})
+
+
+# Backport classic class MRO
+def _classic_mro(C, result):
+ if C in result:
+ return
+ result.append(C)
+ for B in C.__bases__:
+ _classic_mro(B, result)
+ return result
+
+
+# Backport _collections_abc._check_methods
+def _check_methods(C, *methods):
+ try:
+ mro = C.__mro__
+ except AttributeError:
+ mro = tuple(_classic_mro(C, []))
+
+ for method in methods:
+ for B in mro:
+ if method in B.__dict__:
+ if B.__dict__[method] is None:
+ return NotImplemented
+ break
+ else:
+ return NotImplemented
+ return True
+
+
+class AbstractContextManager(_abc_ABC):
+ """An abstract base class for context managers."""
+
+ def __enter__(self):
+ """Return `self` upon entering the runtime context."""
+ return self
+
+ @abc.abstractmethod
+ def __exit__(self, exc_type, exc_value, traceback):
+ """Raise any exception triggered within the runtime context."""
+ return None
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ """Check whether subclass is considered a subclass of this ABC."""
+ if cls is AbstractContextManager:
+ return _check_methods(C, "__enter__", "__exit__")
+ return NotImplemented
+
+
+class ContextDecorator(object):
+ """A base class or mixin that enables context managers to work as decorators."""
+
+ def refresh_cm(self):
+ """Returns the context manager used to actually wrap the call to the
+ decorated function.
+
+ The default implementation just returns *self*.
+
+ Overriding this method allows otherwise one-shot context managers
+ like _GeneratorContextManager to support use as decorators via
+ implicit recreation.
+
+ DEPRECATED: refresh_cm was never added to the standard library's
+ ContextDecorator API
+ """
+ warnings.warn("refresh_cm was never added to the standard library",
+ DeprecationWarning)
+ return self._recreate_cm()
+
+ def _recreate_cm(self):
+ """Return a recreated instance of self.
+
+ Allows an otherwise one-shot context manager like
+ _GeneratorContextManager to support use as
+ a decorator via implicit recreation.
+
+ This is a private interface just for _GeneratorContextManager.
+ See issue #11647 for details.
+ """
+ return self
+
+ def __call__(self, func):
+ @wraps(func)
+ def inner(*args, **kwds):
+ with self._recreate_cm():
+ return func(*args, **kwds)
+ return inner
+
+
+class _GeneratorContextManager(ContextDecorator):
+ """Helper for @contextmanager decorator."""
+
+ def __init__(self, func, args, kwds):
+ self.gen = func(*args, **kwds)
+ self.func, self.args, self.kwds = func, args, kwds
+ # Issue 19330: ensure context manager instances have good docstrings
+ doc = getattr(func, "__doc__", None)
+ if doc is None:
+ doc = type(self).__doc__
+ self.__doc__ = doc
+ # Unfortunately, this still doesn't provide good help output when
+ # inspecting the created context manager instances, since pydoc
+ # currently bypasses the instance docstring and shows the docstring
+ # for the class instead.
+ # See http://bugs.python.org/issue19404 for more details.
+
+ def _recreate_cm(self):
+ # _GCM instances are one-shot context managers, so the
+ # CM must be recreated each time a decorated function is
+ # called
+ return self.__class__(self.func, self.args, self.kwds)
+
+ def __enter__(self):
+ try:
+ return next(self.gen)
+ except StopIteration:
+ raise RuntimeError("generator didn't yield")
+
+ def __exit__(self, type, value, traceback):
+ if type is None:
+ try:
+ next(self.gen)
+ except StopIteration:
+ return
+ else:
+ raise RuntimeError("generator didn't stop")
+ else:
+ if value is None:
+ # Need to force instantiation so we can reliably
+ # tell if we get the same exception back
+ value = type()
+ try:
+ self.gen.throw(type, value, traceback)
+ raise RuntimeError("generator didn't stop after throw()")
+ except StopIteration as exc:
+ # Suppress StopIteration *unless* it's the same exception that
+ # was passed to throw(). This prevents a StopIteration
+ # raised inside the "with" statement from being suppressed.
+ return exc is not value
+ except RuntimeError as exc:
+ # Don't re-raise the passed in exception
+ if exc is value:
+ return False
+ # Likewise, avoid suppressing if a StopIteration exception
+ # was passed to throw() and later wrapped into a RuntimeError
+ # (see PEP 479).
+ if _HAVE_EXCEPTION_CHAINING and exc.__cause__ is value:
+ return False
+ raise
+ except:
+ # only re-raise if it's *not* the exception that was
+ # passed to throw(), because __exit__() must not raise
+ # an exception unless __exit__() itself failed. But throw()
+ # has to raise the exception to signal propagation, so this
+ # fixes the impedance mismatch between the throw() protocol
+ # and the __exit__() protocol.
+ #
+ if sys.exc_info()[1] is not value:
+ raise
+
+
+def contextmanager(func):
+ """@contextmanager decorator.
+
+ Typical usage:
+
+ @contextmanager
+ def some_generator(<arguments>):
+ <setup>
+ try:
+ yield <value>
+ finally:
+ <cleanup>
+
+ This makes this:
+
+ with some_generator(<arguments>) as <variable>:
+ <body>
+
+ equivalent to this:
+
+ <setup>
+ try:
+ <variable> = <value>
+ <body>
+ finally:
+ <cleanup>
+
+ """
+ @wraps(func)
+ def helper(*args, **kwds):
+ return _GeneratorContextManager(func, args, kwds)
+ return helper
+
+
+class closing(object):
+ """Context to automatically close something at the end of a block.
+
+ Code like this:
+
+ with closing(<module>.open(<arguments>)) as f:
+ <block>
+
+ is equivalent to this:
+
+ f = <module>.open(<arguments>)
+ try:
+ <block>
+ finally:
+ f.close()
+
+ """
+ def __init__(self, thing):
+ self.thing = thing
+
+ def __enter__(self):
+ return self.thing
+
+ def __exit__(self, *exc_info):
+ self.thing.close()
+
+
+class _RedirectStream(object):
+
+ _stream = None
+
+ def __init__(self, new_target):
+ self._new_target = new_target
+ # We use a list of old targets to make this CM re-entrant
+ self._old_targets = []
+
+ def __enter__(self):
+ self._old_targets.append(getattr(sys, self._stream))
+ setattr(sys, self._stream, self._new_target)
+ return self._new_target
+
+ def __exit__(self, exctype, excinst, exctb):
+ setattr(sys, self._stream, self._old_targets.pop())
+
+
+class redirect_stdout(_RedirectStream):
+ """Context manager for temporarily redirecting stdout to another file.
+
+ # How to send help() to stderr
+ with redirect_stdout(sys.stderr):
+ help(dir)
+
+ # How to write help() to a file
+ with open('help.txt', 'w') as f:
+ with redirect_stdout(f):
+ help(pow)
+ """
+
+ _stream = "stdout"
+
+
+class redirect_stderr(_RedirectStream):
+ """Context manager for temporarily redirecting stderr to another file."""
+
+ _stream = "stderr"
+
+
+class suppress(object):
+ """Context manager to suppress specified exceptions
+
+ After the exception is suppressed, execution proceeds with the next
+ statement following the with statement.
+
+ with suppress(FileNotFoundError):
+ os.remove(somefile)
+ # Execution still resumes here if the file was already removed
+ """
+
+ def __init__(self, *exceptions):
+ self._exceptions = exceptions
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, exctype, excinst, exctb):
+ # Unlike isinstance and issubclass, CPython exception handling
+ # currently only looks at the concrete type hierarchy (ignoring
+ # the instance and subclass checking hooks). While Guido considers
+ # that a bug rather than a feature, it's a fairly hard one to fix
+ # due to various internal implementation details. suppress provides
+ # the simpler issubclass based semantics, rather than trying to
+ # exactly reproduce the limitations of the CPython interpreter.
+ #
+ # See http://bugs.python.org/issue12029 for more details
+ return exctype is not None and issubclass(exctype, self._exceptions)
+
+
+# Context manipulation is Python 3 only
+_HAVE_EXCEPTION_CHAINING = sys.version_info[0] >= 3
+if _HAVE_EXCEPTION_CHAINING:
+ def _make_context_fixer(frame_exc):
+ def _fix_exception_context(new_exc, old_exc):
+ # Context may not be correct, so find the end of the chain
+ while 1:
+ exc_context = new_exc.__context__
+ if exc_context is old_exc:
+ # Context is already set correctly (see issue 20317)
+ return
+ if exc_context is None or exc_context is frame_exc:
+ break
+ new_exc = exc_context
+ # Change the end of the chain to point to the exception
+ # we expect it to reference
+ new_exc.__context__ = old_exc
+ return _fix_exception_context
+
+ def _reraise_with_existing_context(exc_details):
+ try:
+ # bare "raise exc_details[1]" replaces our carefully
+ # set-up context
+ fixed_ctx = exc_details[1].__context__
+ raise exc_details[1]
+ except BaseException:
+ exc_details[1].__context__ = fixed_ctx
+ raise
+else:
+ # No exception context in Python 2
+ def _make_context_fixer(frame_exc):
+ return lambda new_exc, old_exc: None
+
+ # Use 3 argument raise in Python 2,
+ # but use exec to avoid SyntaxError in Python 3
+ def _reraise_with_existing_context(exc_details):
+ exc_type, exc_value, exc_tb = exc_details
+ exec("raise exc_type, exc_value, exc_tb")
+
+# Handle old-style classes if they exist
+try:
+ from types import InstanceType
+except ImportError:
+ # Python 3 doesn't have old-style classes
+ _get_type = type
+else:
+ # Need to handle old-style context managers on Python 2
+ def _get_type(obj):
+ obj_type = type(obj)
+ if obj_type is InstanceType:
+ return obj.__class__ # Old-style class
+ return obj_type # New-style class
+
+
+# Inspired by discussions on http://bugs.python.org/issue13585
+class ExitStack(object):
+ """Context manager for dynamic management of a stack of exit callbacks
+
+ For example:
+
+ with ExitStack() as stack:
+ files = [stack.enter_context(open(fname)) for fname in filenames]
+ # All opened files will automatically be closed at the end of
+ # the with statement, even if attempts to open files later
+ # in the list raise an exception
+
+ """
+ def __init__(self):
+ self._exit_callbacks = deque()
+
+ def pop_all(self):
+ """Preserve the context stack by transferring it to a new instance"""
+ new_stack = type(self)()
+ new_stack._exit_callbacks = self._exit_callbacks
+ self._exit_callbacks = deque()
+ return new_stack
+
+ def _push_cm_exit(self, cm, cm_exit):
+ """Helper to correctly register callbacks to __exit__ methods"""
+ def _exit_wrapper(*exc_details):
+ return cm_exit(cm, *exc_details)
+ _exit_wrapper.__self__ = cm
+ self.push(_exit_wrapper)
+
+ def push(self, exit):
+ """Registers a callback with the standard __exit__ method signature
+
+ Can suppress exceptions the same way __exit__ methods can.
+
+ Also accepts any object with an __exit__ method (registering a call
+ to the method instead of the object itself)
+ """
+ # We use an unbound method rather than a bound method to follow
+ # the standard lookup behaviour for special methods
+ _cb_type = _get_type(exit)
+ try:
+ exit_method = _cb_type.__exit__
+ except AttributeError:
+ # Not a context manager, so assume its a callable
+ self._exit_callbacks.append(exit)
+ else:
+ self._push_cm_exit(exit, exit_method)
+ return exit # Allow use as a decorator
+
+ def callback(self, callback, *args, **kwds):
+ """Registers an arbitrary callback and arguments.
+
+ Cannot suppress exceptions.
+ """
+ def _exit_wrapper(exc_type, exc, tb):
+ callback(*args, **kwds)
+ # We changed the signature, so using @wraps is not appropriate, but
+ # setting __wrapped__ may still help with introspection
+ _exit_wrapper.__wrapped__ = callback
+ self.push(_exit_wrapper)
+ return callback # Allow use as a decorator
+
+ def enter_context(self, cm):
+ """Enters the supplied context manager
+
+ If successful, also pushes its __exit__ method as a callback and
+ returns the result of the __enter__ method.
+ """
+ # We look up the special methods on the type to match the with statement
+ _cm_type = _get_type(cm)
+ _exit = _cm_type.__exit__
+ result = _cm_type.__enter__(cm)
+ self._push_cm_exit(cm, _exit)
+ return result
+
+ def close(self):
+ """Immediately unwind the context stack"""
+ self.__exit__(None, None, None)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc_details):
+ received_exc = exc_details[0] is not None
+
+ # We manipulate the exception state so it behaves as though
+ # we were actually nesting multiple with statements
+ frame_exc = sys.exc_info()[1]
+ _fix_exception_context = _make_context_fixer(frame_exc)
+
+ # Callbacks are invoked in LIFO order to match the behaviour of
+ # nested context managers
+ suppressed_exc = False
+ pending_raise = False
+ while self._exit_callbacks:
+ cb = self._exit_callbacks.pop()
+ try:
+ if cb(*exc_details):
+ suppressed_exc = True
+ pending_raise = False
+ exc_details = (None, None, None)
+ except:
+ new_exc_details = sys.exc_info()
+ # simulate the stack of exceptions by setting the context
+ _fix_exception_context(new_exc_details[1], exc_details[1])
+ pending_raise = True
+ exc_details = new_exc_details
+ if pending_raise:
+ _reraise_with_existing_context(exc_details)
+ return received_exc and suppressed_exc
+
+
+# Preserve backwards compatibility
+class ContextStack(ExitStack):
+ """Backwards compatibility alias for ExitStack"""
+
+ def __init__(self):
+ warnings.warn("ContextStack has been renamed to ExitStack",
+ DeprecationWarning)
+ super(ContextStack, self).__init__()
+
+ def register_exit(self, callback):
+ return self.push(callback)
+
+ def register(self, callback, *args, **kwds):
+ return self.callback(callback, *args, **kwds)
+
+ def preserve(self):
+ return self.pop_all()
+
+
+class nullcontext(AbstractContextManager):
+ """Context manager that does no additional processing.
+ Used as a stand-in for a normal context manager, when a particular
+ block of code is only sometimes used with a normal context manager:
+ cm = optional_cm if condition else nullcontext()
+ with cm:
+ # Perform operation, using optional_cm if condition is True
+ """
+
+ def __init__(self, enter_result=None):
+ self.enter_result = enter_result
+
+ def __enter__(self):
+ return self.enter_result
+
+ def __exit__(self, *excinfo):
+ pass
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/METADATA
new file mode 100644
index 0000000000..54f5f6497f
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/METADATA
@@ -0,0 +1,24 @@
+Metadata-Version: 1.1
+Name: distlib
+Version: 0.3.1
+Summary: Distribution utilities
+Description: Low-level components of distutils2/packaging, augmented with higher-level APIs for making packaging easier.
+Home-page: https://bitbucket.org/pypa/distlib
+Author: Vinay Sajip
+Author-email: vinay_sajip@red-dove.com
+License: Python license
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Download-URL: https://bitbucket.org/pypa/distlib/downloads/distlib-0.3.1.zip
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/RECORD
new file mode 100644
index 0000000000..93b724c474
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/RECORD
@@ -0,0 +1,26 @@
+distlib/__init__.py,sha256=3veAk2rPznOB2gsK6tjbbh0TQMmGE5P82eE9wXq6NIk,581
+distlib/compat.py,sha256=ADA56xiAxar3mU6qemlBhNbsrFPosXRhO44RzsbJPqk,41408
+distlib/database.py,sha256=Kl0YvPQKc4OcpVi7k5cFziydM1xOK8iqdxLGXgbZHV4,51059
+distlib/index.py,sha256=SXKzpQCERctxYDMp_OLee2f0J0e19ZhGdCIoMlUfUQM,21066
+distlib/locators.py,sha256=c9E4cDEacJ_uKbuE5BqAVocoWp6rsuBGTkiNDQq3zV4,52100
+distlib/manifest.py,sha256=nQEhYmgoreaBZzyFzwYsXxJARu3fo4EkunU163U16iE,14811
+distlib/markers.py,sha256=6Ac3cCfFBERexiESWIOXmg-apIP8l2esafNSX3KMy-8,4387
+distlib/metadata.py,sha256=z2KPy3h3tcDnb9Xs7nAqQ5Oz0bqjWAUFmKWcFKRoodg,38962
+distlib/resources.py,sha256=2FGv0ZHF14KXjLIlL0R991lyQQGcewOS4mJ-5n-JVnc,10766
+distlib/scripts.py,sha256=_MAj3sMuv56kuM8FsiIWXqbT0gmumPGaOR_atOzn4a4,17180
+distlib/t32.exe,sha256=NS3xBCVAld35JVFNmb-1QRyVtThukMrwZVeXn4LhaEQ,96768
+distlib/t64.exe,sha256=oAqHes78rUWVM0OtVqIhUvequl_PKhAhXYQWnUf7zR0,105984
+distlib/util.py,sha256=f2jZCPrcLCt6LcnC0gUy-Fur60tXD8reA7k4rDpHMDw,59845
+distlib/version.py,sha256=_n7F6juvQGAcn769E_SHa7fOcf5ERlEVymJ_EjPRwGw,23391
+distlib/w32.exe,sha256=lJtnZdeUxTZWya_EW5DZos_K5rswRECGspIl8ZJCIXs,90112
+distlib/w64.exe,sha256=0aRzoN2BO9NWW4ENy4_4vHkHR4qZTFZNVSAJJYlODTI,99840
+distlib/wheel.py,sha256=v6DnwTqhNHwrEVFr8_YeiTW6G4ftP_evsywNgrmdb2o,41144
+distlib/_backport/__init__.py,sha256=bqS_dTOH6uW9iGgd0uzfpPjo6vZ4xpPZ7kyfZJ2vNaw,274
+distlib/_backport/misc.py,sha256=KWecINdbFNOxSOP1fGF680CJnaC6S4fBRgEtaYTw0ig,971
+distlib/_backport/shutil.py,sha256=IX_G2NPqwecJibkIDje04bqu0xpHkfSQ2GaGdEVqM5Y,25707
+distlib/_backport/sysconfig.cfg,sha256=swZKxq9RY5e9r3PXCrlvQPMsvOdiWZBTHLEbqS8LJLU,2617
+distlib/_backport/sysconfig.py,sha256=BQHFlb6pubCl_dvT1NjtzIthylofjKisox239stDg0U,26854
+distlib/_backport/tarfile.py,sha256=Ihp7rXRcjbIKw8COm9wSePV9ARGXbSF9gGXAMn2Q-KU,92628
+distlib-0.3.1.dist-info/METADATA,sha256=i6wrPilVkro9BXvaHkwVsaemMZCx5xbWc8jS9oR_ZJw,1128
+distlib-0.3.1.dist-info/WHEEL,sha256=R4LNelR33E9ZPEGiPwrdPrrHnwkFEjiMPbVCAWVjsxI,106
+distlib-0.3.1.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/WHEEL
new file mode 100644
index 0000000000..78f54a1910
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: distlib 0.3.1.dev0
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any \ No newline at end of file
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py
new file mode 100644
index 0000000000..63d916e345
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012-2019 Vinay Sajip.
+# Licensed to the Python Software Foundation under a contributor agreement.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+import logging
+
+__version__ = '0.3.1'
+
+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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py
new file mode 100644
index 0000000000..f7dbf4c9aa
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py
@@ -0,0 +1,6 @@
+"""Modules copied from Python 3 standard libraries, for internal use only.
+
+Individual classes and functions are found in d2._backport.misc. Intended
+usage is to always import things missing from 3.1 from that module: the
+built-in/stdlib objects will be used if found.
+"""
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py
new file mode 100644
index 0000000000..cfb318d34f
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 The Python Software Foundation.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+"""Backports for individual classes and functions."""
+
+import os
+import sys
+
+__all__ = ['cache_from_source', 'callable', 'fsencode']
+
+
+try:
+ from imp import cache_from_source
+except ImportError:
+ def cache_from_source(py_file, debug=__debug__):
+ ext = debug and 'c' or 'o'
+ return py_file + ext
+
+
+try:
+ callable = callable
+except NameError:
+ from collections import Callable
+
+ def callable(obj):
+ return isinstance(obj, Callable)
+
+
+try:
+ fsencode = os.fsencode
+except AttributeError:
+ def fsencode(filename):
+ if isinstance(filename, bytes):
+ return filename
+ elif isinstance(filename, str):
+ return filename.encode(sys.getfilesystemencoding())
+ else:
+ raise TypeError("expect bytes or str, not %s" %
+ type(filename).__name__)
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py
new file mode 100644
index 0000000000..10ed362539
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py
@@ -0,0 +1,764 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 The Python Software Foundation.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+"""Utility functions for copying and archiving files and directory trees.
+
+XXX The functions here don't copy the resource fork or other metadata on Mac.
+
+"""
+
+import os
+import sys
+import stat
+from os.path import abspath
+import fnmatch
+try:
+ from collections.abc import Callable
+except ImportError:
+ from collections import Callable
+import errno
+from . import tarfile
+
+try:
+ import bz2
+ _BZ2_SUPPORTED = True
+except ImportError:
+ _BZ2_SUPPORTED = False
+
+try:
+ from pwd import getpwnam
+except ImportError:
+ getpwnam = None
+
+try:
+ from grp import getgrnam
+except ImportError:
+ getgrnam = None
+
+__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
+ "copytree", "move", "rmtree", "Error", "SpecialFileError",
+ "ExecError", "make_archive", "get_archive_formats",
+ "register_archive_format", "unregister_archive_format",
+ "get_unpack_formats", "register_unpack_format",
+ "unregister_unpack_format", "unpack_archive", "ignore_patterns"]
+
+class Error(EnvironmentError):
+ pass
+
+class SpecialFileError(EnvironmentError):
+ """Raised when trying to do a kind of operation (e.g. copying) which is
+ not supported on a special file (e.g. a named pipe)"""
+
+class ExecError(EnvironmentError):
+ """Raised when a command could not be executed"""
+
+class ReadError(EnvironmentError):
+ """Raised when an archive cannot be read"""
+
+class RegistryError(Exception):
+ """Raised when a registry operation with the archiving
+ and unpacking registries fails"""
+
+
+try:
+ WindowsError
+except NameError:
+ WindowsError = None
+
+def copyfileobj(fsrc, fdst, length=16*1024):
+ """copy data from file-like object fsrc to file-like object fdst"""
+ while 1:
+ buf = fsrc.read(length)
+ if not buf:
+ break
+ fdst.write(buf)
+
+def _samefile(src, dst):
+ # Macintosh, Unix.
+ if hasattr(os.path, 'samefile'):
+ try:
+ return os.path.samefile(src, dst)
+ except OSError:
+ return False
+
+ # All other platforms: check for same pathname.
+ return (os.path.normcase(os.path.abspath(src)) ==
+ os.path.normcase(os.path.abspath(dst)))
+
+def copyfile(src, dst):
+ """Copy data from src to dst"""
+ if _samefile(src, dst):
+ raise Error("`%s` and `%s` are the same file" % (src, dst))
+
+ for fn in [src, dst]:
+ try:
+ st = os.stat(fn)
+ except OSError:
+ # File most likely does not exist
+ pass
+ else:
+ # XXX What about other special files? (sockets, devices...)
+ if stat.S_ISFIFO(st.st_mode):
+ raise SpecialFileError("`%s` is a named pipe" % fn)
+
+ with open(src, 'rb') as fsrc:
+ with open(dst, 'wb') as fdst:
+ copyfileobj(fsrc, fdst)
+
+def copymode(src, dst):
+ """Copy mode bits from src to dst"""
+ if hasattr(os, 'chmod'):
+ st = os.stat(src)
+ mode = stat.S_IMODE(st.st_mode)
+ os.chmod(dst, mode)
+
+def copystat(src, dst):
+ """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
+ st = os.stat(src)
+ mode = stat.S_IMODE(st.st_mode)
+ if hasattr(os, 'utime'):
+ os.utime(dst, (st.st_atime, st.st_mtime))
+ if hasattr(os, 'chmod'):
+ os.chmod(dst, mode)
+ if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
+ try:
+ os.chflags(dst, st.st_flags)
+ except OSError as why:
+ if (not hasattr(errno, 'EOPNOTSUPP') or
+ why.errno != errno.EOPNOTSUPP):
+ raise
+
+def copy(src, dst):
+ """Copy data and mode bits ("cp src dst").
+
+ The destination may be a directory.
+
+ """
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ copyfile(src, dst)
+ copymode(src, dst)
+
+def copy2(src, dst):
+ """Copy data and all stat info ("cp -p src dst").
+
+ The destination may be a directory.
+
+ """
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ copyfile(src, dst)
+ copystat(src, dst)
+
+def ignore_patterns(*patterns):
+ """Function that can be used as copytree() ignore parameter.
+
+ Patterns is a sequence of glob-style patterns
+ that are used to exclude files"""
+ def _ignore_patterns(path, names):
+ ignored_names = []
+ for pattern in patterns:
+ ignored_names.extend(fnmatch.filter(names, pattern))
+ return set(ignored_names)
+ return _ignore_patterns
+
+def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
+ ignore_dangling_symlinks=False):
+ """Recursively copy a directory tree.
+
+ The destination directory must not already exist.
+ If exception(s) occur, an Error is raised with a list of reasons.
+
+ If the optional symlinks flag is true, symbolic links in the
+ source tree result in symbolic links in the destination tree; if
+ it is false, the contents of the files pointed to by symbolic
+ links are copied. If the file pointed by the symlink doesn't
+ exist, an exception will be added in the list of errors raised in
+ an Error exception at the end of the copy process.
+
+ You can set the optional ignore_dangling_symlinks flag to true if you
+ want to silence this exception. Notice that this has no effect on
+ platforms that don't support os.symlink.
+
+ The optional ignore argument is a callable. If given, it
+ is called with the `src` parameter, which is the directory
+ being visited by copytree(), and `names` which is the list of
+ `src` contents, as returned by os.listdir():
+
+ callable(src, names) -> ignored_names
+
+ Since copytree() is called recursively, the callable will be
+ called once for each directory that is copied. It returns a
+ list of names relative to the `src` directory that should
+ not be copied.
+
+ The optional copy_function argument is a callable that will be used
+ to copy each file. It will be called with the source path and the
+ destination path as arguments. By default, copy2() is used, but any
+ function that supports the same signature (like copy()) can be used.
+
+ """
+ names = os.listdir(src)
+ if ignore is not None:
+ ignored_names = ignore(src, names)
+ else:
+ ignored_names = set()
+
+ os.makedirs(dst)
+ errors = []
+ for name in names:
+ if name in ignored_names:
+ continue
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ try:
+ if os.path.islink(srcname):
+ linkto = os.readlink(srcname)
+ if symlinks:
+ os.symlink(linkto, dstname)
+ else:
+ # ignore dangling symlink if the flag is on
+ if not os.path.exists(linkto) and ignore_dangling_symlinks:
+ continue
+ # otherwise let the copy occurs. copy2 will raise an error
+ copy_function(srcname, dstname)
+ elif os.path.isdir(srcname):
+ copytree(srcname, dstname, symlinks, ignore, copy_function)
+ else:
+ # Will raise a SpecialFileError for unsupported file types
+ copy_function(srcname, dstname)
+ # catch the Error from the recursive copytree so that we can
+ # continue with other files
+ except Error as err:
+ errors.extend(err.args[0])
+ except EnvironmentError as why:
+ errors.append((srcname, dstname, str(why)))
+ try:
+ copystat(src, dst)
+ except OSError as why:
+ if WindowsError is not None and isinstance(why, WindowsError):
+ # Copying file access times may fail on Windows
+ pass
+ else:
+ errors.extend((src, dst, str(why)))
+ if errors:
+ raise Error(errors)
+
+def rmtree(path, ignore_errors=False, onerror=None):
+ """Recursively delete a directory tree.
+
+ If ignore_errors is set, errors are ignored; otherwise, if onerror
+ is set, it is called to handle the error with arguments (func,
+ path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
+ path is the argument to that function that caused it to fail; and
+ exc_info is a tuple returned by sys.exc_info(). If ignore_errors
+ is false and onerror is None, an exception is raised.
+
+ """
+ if ignore_errors:
+ def onerror(*args):
+ pass
+ elif onerror is None:
+ def onerror(*args):
+ raise
+ try:
+ if os.path.islink(path):
+ # symlinks to directories are forbidden, see bug #1669
+ raise OSError("Cannot call rmtree on a symbolic link")
+ except OSError:
+ onerror(os.path.islink, path, sys.exc_info())
+ # can't continue even if onerror hook returns
+ return
+ names = []
+ try:
+ names = os.listdir(path)
+ except os.error:
+ onerror(os.listdir, path, sys.exc_info())
+ for name in names:
+ fullname = os.path.join(path, name)
+ try:
+ mode = os.lstat(fullname).st_mode
+ except os.error:
+ mode = 0
+ if stat.S_ISDIR(mode):
+ rmtree(fullname, ignore_errors, onerror)
+ else:
+ try:
+ os.remove(fullname)
+ except os.error:
+ onerror(os.remove, fullname, sys.exc_info())
+ try:
+ os.rmdir(path)
+ except os.error:
+ onerror(os.rmdir, path, sys.exc_info())
+
+
+def _basename(path):
+ # A basename() variant which first strips the trailing slash, if present.
+ # Thus we always get the last component of the path, even for directories.
+ return os.path.basename(path.rstrip(os.path.sep))
+
+def move(src, dst):
+ """Recursively move a file or directory to another location. This is
+ similar to the Unix "mv" command.
+
+ If the destination is a directory or a symlink to a directory, the source
+ is moved inside the directory. The destination path must not already
+ exist.
+
+ If the destination already exists but is not a directory, it may be
+ overwritten depending on os.rename() semantics.
+
+ If the destination is on our current filesystem, then rename() is used.
+ Otherwise, src is copied to the destination and then removed.
+ A lot more could be done here... A look at a mv.c shows a lot of
+ the issues this implementation glosses over.
+
+ """
+ real_dst = dst
+ if os.path.isdir(dst):
+ if _samefile(src, dst):
+ # We might be on a case insensitive filesystem,
+ # perform the rename anyway.
+ os.rename(src, dst)
+ return
+
+ real_dst = os.path.join(dst, _basename(src))
+ if os.path.exists(real_dst):
+ raise Error("Destination path '%s' already exists" % real_dst)
+ try:
+ os.rename(src, real_dst)
+ except OSError:
+ if os.path.isdir(src):
+ if _destinsrc(src, dst):
+ raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
+ copytree(src, real_dst, symlinks=True)
+ rmtree(src)
+ else:
+ copy2(src, real_dst)
+ os.unlink(src)
+
+def _destinsrc(src, dst):
+ src = abspath(src)
+ dst = abspath(dst)
+ if not src.endswith(os.path.sep):
+ src += os.path.sep
+ if not dst.endswith(os.path.sep):
+ dst += os.path.sep
+ return dst.startswith(src)
+
+def _get_gid(name):
+ """Returns a gid, given a group name."""
+ if getgrnam is None or name is None:
+ return None
+ try:
+ result = getgrnam(name)
+ except KeyError:
+ result = None
+ if result is not None:
+ return result[2]
+ return None
+
+def _get_uid(name):
+ """Returns an uid, given a user name."""
+ if getpwnam is None or name is None:
+ return None
+ try:
+ result = getpwnam(name)
+ except KeyError:
+ result = None
+ if result is not None:
+ return result[2]
+ return None
+
+def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
+ owner=None, group=None, logger=None):
+ """Create a (possibly compressed) tar file from all the files under
+ 'base_dir'.
+
+ 'compress' must be "gzip" (the default), "bzip2", or None.
+
+ 'owner' and 'group' can be used to define an owner and a group for the
+ archive that is being built. If not provided, the current owner and group
+ will be used.
+
+ The output tar file will be named 'base_name' + ".tar", possibly plus
+ the appropriate compression extension (".gz", or ".bz2").
+
+ Returns the output filename.
+ """
+ tar_compression = {'gzip': 'gz', None: ''}
+ compress_ext = {'gzip': '.gz'}
+
+ if _BZ2_SUPPORTED:
+ tar_compression['bzip2'] = 'bz2'
+ compress_ext['bzip2'] = '.bz2'
+
+ # flags for compression program, each element of list will be an argument
+ if compress is not None and compress not in compress_ext:
+ raise ValueError("bad value for 'compress', or compression format not "
+ "supported : {0}".format(compress))
+
+ archive_name = base_name + '.tar' + compress_ext.get(compress, '')
+ archive_dir = os.path.dirname(archive_name)
+
+ if not os.path.exists(archive_dir):
+ if logger is not None:
+ logger.info("creating %s", archive_dir)
+ if not dry_run:
+ os.makedirs(archive_dir)
+
+ # creating the tarball
+ if logger is not None:
+ logger.info('Creating tar archive')
+
+ uid = _get_uid(owner)
+ gid = _get_gid(group)
+
+ def _set_uid_gid(tarinfo):
+ if gid is not None:
+ tarinfo.gid = gid
+ tarinfo.gname = group
+ if uid is not None:
+ tarinfo.uid = uid
+ tarinfo.uname = owner
+ return tarinfo
+
+ if not dry_run:
+ tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
+ try:
+ tar.add(base_dir, filter=_set_uid_gid)
+ finally:
+ tar.close()
+
+ return archive_name
+
+def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
+ # XXX see if we want to keep an external call here
+ if verbose:
+ zipoptions = "-r"
+ else:
+ zipoptions = "-rq"
+ from distutils.errors import DistutilsExecError
+ from distutils.spawn import spawn
+ try:
+ spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
+ except DistutilsExecError:
+ # XXX really should distinguish between "couldn't find
+ # external 'zip' command" and "zip failed".
+ raise ExecError("unable to create zip file '%s': "
+ "could neither import the 'zipfile' module nor "
+ "find a standalone zip utility") % zip_filename
+
+def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
+ """Create a zip file from all the files under 'base_dir'.
+
+ The output zip file will be named 'base_name' + ".zip". Uses either the
+ "zipfile" Python module (if available) or the InfoZIP "zip" utility
+ (if installed and found on the default search path). If neither tool is
+ available, raises ExecError. Returns the name of the output zip
+ file.
+ """
+ zip_filename = base_name + ".zip"
+ archive_dir = os.path.dirname(base_name)
+
+ if not os.path.exists(archive_dir):
+ if logger is not None:
+ logger.info("creating %s", archive_dir)
+ if not dry_run:
+ os.makedirs(archive_dir)
+
+ # If zipfile module is not available, try spawning an external 'zip'
+ # command.
+ try:
+ import zipfile
+ except ImportError:
+ zipfile = None
+
+ if zipfile is None:
+ _call_external_zip(base_dir, zip_filename, verbose, dry_run)
+ else:
+ if logger is not None:
+ logger.info("creating '%s' and adding '%s' to it",
+ zip_filename, base_dir)
+
+ if not dry_run:
+ zip = zipfile.ZipFile(zip_filename, "w",
+ compression=zipfile.ZIP_DEFLATED)
+
+ for dirpath, dirnames, filenames in os.walk(base_dir):
+ for name in filenames:
+ path = os.path.normpath(os.path.join(dirpath, name))
+ if os.path.isfile(path):
+ zip.write(path, path)
+ if logger is not None:
+ logger.info("adding '%s'", path)
+ zip.close()
+
+ return zip_filename
+
+_ARCHIVE_FORMATS = {
+ 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
+ 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
+ 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
+ 'zip': (_make_zipfile, [], "ZIP file"),
+ }
+
+if _BZ2_SUPPORTED:
+ _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
+ "bzip2'ed tar-file")
+
+def get_archive_formats():
+ """Returns a list of supported formats for archiving and unarchiving.
+
+ Each element of the returned sequence is a tuple (name, description)
+ """
+ formats = [(name, registry[2]) for name, registry in
+ _ARCHIVE_FORMATS.items()]
+ formats.sort()
+ return formats
+
+def register_archive_format(name, function, extra_args=None, description=''):
+ """Registers an archive format.
+
+ name is the name of the format. function is the callable that will be
+ used to create archives. If provided, extra_args is a sequence of
+ (name, value) tuples that will be passed as arguments to the callable.
+ description can be provided to describe the format, and will be returned
+ by the get_archive_formats() function.
+ """
+ if extra_args is None:
+ extra_args = []
+ if not isinstance(function, Callable):
+ raise TypeError('The %s object is not callable' % function)
+ if not isinstance(extra_args, (tuple, list)):
+ raise TypeError('extra_args needs to be a sequence')
+ for element in extra_args:
+ if not isinstance(element, (tuple, list)) or len(element) !=2:
+ raise TypeError('extra_args elements are : (arg_name, value)')
+
+ _ARCHIVE_FORMATS[name] = (function, extra_args, description)
+
+def unregister_archive_format(name):
+ del _ARCHIVE_FORMATS[name]
+
+def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
+ dry_run=0, owner=None, group=None, logger=None):
+ """Create an archive file (eg. zip or tar).
+
+ 'base_name' is the name of the file to create, minus any format-specific
+ extension; 'format' is the archive format: one of "zip", "tar", "bztar"
+ or "gztar".
+
+ 'root_dir' is a directory that will be the root directory of the
+ archive; ie. we typically chdir into 'root_dir' before creating the
+ archive. 'base_dir' is the directory where we start archiving from;
+ ie. 'base_dir' will be the common prefix of all files and
+ directories in the archive. 'root_dir' and 'base_dir' both default
+ to the current directory. Returns the name of the archive file.
+
+ 'owner' and 'group' are used when creating a tar archive. By default,
+ uses the current owner and group.
+ """
+ save_cwd = os.getcwd()
+ if root_dir is not None:
+ if logger is not None:
+ logger.debug("changing into '%s'", root_dir)
+ base_name = os.path.abspath(base_name)
+ if not dry_run:
+ os.chdir(root_dir)
+
+ if base_dir is None:
+ base_dir = os.curdir
+
+ kwargs = {'dry_run': dry_run, 'logger': logger}
+
+ try:
+ format_info = _ARCHIVE_FORMATS[format]
+ except KeyError:
+ raise ValueError("unknown archive format '%s'" % format)
+
+ func = format_info[0]
+ for arg, val in format_info[1]:
+ kwargs[arg] = val
+
+ if format != 'zip':
+ kwargs['owner'] = owner
+ kwargs['group'] = group
+
+ try:
+ filename = func(base_name, base_dir, **kwargs)
+ finally:
+ if root_dir is not None:
+ if logger is not None:
+ logger.debug("changing back to '%s'", save_cwd)
+ os.chdir(save_cwd)
+
+ return filename
+
+
+def get_unpack_formats():
+ """Returns a list of supported formats for unpacking.
+
+ Each element of the returned sequence is a tuple
+ (name, extensions, description)
+ """
+ formats = [(name, info[0], info[3]) for name, info in
+ _UNPACK_FORMATS.items()]
+ formats.sort()
+ return formats
+
+def _check_unpack_options(extensions, function, extra_args):
+ """Checks what gets registered as an unpacker."""
+ # first make sure no other unpacker is registered for this extension
+ existing_extensions = {}
+ for name, info in _UNPACK_FORMATS.items():
+ for ext in info[0]:
+ existing_extensions[ext] = name
+
+ for extension in extensions:
+ if extension in existing_extensions:
+ msg = '%s is already registered for "%s"'
+ raise RegistryError(msg % (extension,
+ existing_extensions[extension]))
+
+ if not isinstance(function, Callable):
+ raise TypeError('The registered function must be a callable')
+
+
+def register_unpack_format(name, extensions, function, extra_args=None,
+ description=''):
+ """Registers an unpack format.
+
+ `name` is the name of the format. `extensions` is a list of extensions
+ corresponding to the format.
+
+ `function` is the callable that will be
+ used to unpack archives. The callable will receive archives to unpack.
+ If it's unable to handle an archive, it needs to raise a ReadError
+ exception.
+
+ If provided, `extra_args` is a sequence of
+ (name, value) tuples that will be passed as arguments to the callable.
+ description can be provided to describe the format, and will be returned
+ by the get_unpack_formats() function.
+ """
+ if extra_args is None:
+ extra_args = []
+ _check_unpack_options(extensions, function, extra_args)
+ _UNPACK_FORMATS[name] = extensions, function, extra_args, description
+
+def unregister_unpack_format(name):
+ """Removes the pack format from the registry."""
+ del _UNPACK_FORMATS[name]
+
+def _ensure_directory(path):
+ """Ensure that the parent directory of `path` exists"""
+ dirname = os.path.dirname(path)
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
+
+def _unpack_zipfile(filename, extract_dir):
+ """Unpack zip `filename` to `extract_dir`
+ """
+ try:
+ import zipfile
+ except ImportError:
+ raise ReadError('zlib not supported, cannot unpack this archive.')
+
+ if not zipfile.is_zipfile(filename):
+ raise ReadError("%s is not a zip file" % filename)
+
+ zip = zipfile.ZipFile(filename)
+ try:
+ for info in zip.infolist():
+ name = info.filename
+
+ # don't extract absolute paths or ones with .. in them
+ if name.startswith('/') or '..' in name:
+ continue
+
+ target = os.path.join(extract_dir, *name.split('/'))
+ if not target:
+ continue
+
+ _ensure_directory(target)
+ if not name.endswith('/'):
+ # file
+ data = zip.read(info.filename)
+ f = open(target, 'wb')
+ try:
+ f.write(data)
+ finally:
+ f.close()
+ del data
+ finally:
+ zip.close()
+
+def _unpack_tarfile(filename, extract_dir):
+ """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
+ """
+ try:
+ tarobj = tarfile.open(filename)
+ except tarfile.TarError:
+ raise ReadError(
+ "%s is not a compressed or uncompressed tar file" % filename)
+ try:
+ tarobj.extractall(extract_dir)
+ finally:
+ tarobj.close()
+
+_UNPACK_FORMATS = {
+ 'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
+ 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
+ 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file")
+ }
+
+if _BZ2_SUPPORTED:
+ _UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [],
+ "bzip2'ed tar-file")
+
+def _find_unpack_format(filename):
+ for name, info in _UNPACK_FORMATS.items():
+ for extension in info[0]:
+ if filename.endswith(extension):
+ return name
+ return None
+
+def unpack_archive(filename, extract_dir=None, format=None):
+ """Unpack an archive.
+
+ `filename` is the name of the archive.
+
+ `extract_dir` is the name of the target directory, where the archive
+ is unpacked. If not provided, the current working directory is used.
+
+ `format` is the archive format: one of "zip", "tar", or "gztar". Or any
+ other registered format. If not provided, unpack_archive will use the
+ filename extension and see if an unpacker was registered for that
+ extension.
+
+ In case none is found, a ValueError is raised.
+ """
+ if extract_dir is None:
+ extract_dir = os.getcwd()
+
+ if format is not None:
+ try:
+ format_info = _UNPACK_FORMATS[format]
+ except KeyError:
+ raise ValueError("Unknown unpack format '{0}'".format(format))
+
+ func = format_info[1]
+ func(filename, extract_dir, **dict(format_info[2]))
+ else:
+ # we need to look at the registered unpackers supported extensions
+ format = _find_unpack_format(filename)
+ if format is None:
+ raise ReadError("Unknown archive format '{0}'".format(filename))
+
+ func = _UNPACK_FORMATS[format][1]
+ kwargs = dict(_UNPACK_FORMATS[format][2])
+ func(filename, extract_dir, **kwargs)
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.cfg b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.cfg
new file mode 100644
index 0000000000..1746bd01c1
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.cfg
@@ -0,0 +1,84 @@
+[posix_prefix]
+# Configuration directories. Some of these come straight out of the
+# configure script. They are for implementing the other variables, not to
+# be used directly in [resource_locations].
+confdir = /etc
+datadir = /usr/share
+libdir = /usr/lib
+statedir = /var
+# User resource directory
+local = ~/.local/{distribution.name}
+
+stdlib = {base}/lib/python{py_version_short}
+platstdlib = {platbase}/lib/python{py_version_short}
+purelib = {base}/lib/python{py_version_short}/site-packages
+platlib = {platbase}/lib/python{py_version_short}/site-packages
+include = {base}/include/python{py_version_short}{abiflags}
+platinclude = {platbase}/include/python{py_version_short}{abiflags}
+data = {base}
+
+[posix_home]
+stdlib = {base}/lib/python
+platstdlib = {base}/lib/python
+purelib = {base}/lib/python
+platlib = {base}/lib/python
+include = {base}/include/python
+platinclude = {base}/include/python
+scripts = {base}/bin
+data = {base}
+
+[nt]
+stdlib = {base}/Lib
+platstdlib = {base}/Lib
+purelib = {base}/Lib/site-packages
+platlib = {base}/Lib/site-packages
+include = {base}/Include
+platinclude = {base}/Include
+scripts = {base}/Scripts
+data = {base}
+
+[os2]
+stdlib = {base}/Lib
+platstdlib = {base}/Lib
+purelib = {base}/Lib/site-packages
+platlib = {base}/Lib/site-packages
+include = {base}/Include
+platinclude = {base}/Include
+scripts = {base}/Scripts
+data = {base}
+
+[os2_home]
+stdlib = {userbase}/lib/python{py_version_short}
+platstdlib = {userbase}/lib/python{py_version_short}
+purelib = {userbase}/lib/python{py_version_short}/site-packages
+platlib = {userbase}/lib/python{py_version_short}/site-packages
+include = {userbase}/include/python{py_version_short}
+scripts = {userbase}/bin
+data = {userbase}
+
+[nt_user]
+stdlib = {userbase}/Python{py_version_nodot}
+platstdlib = {userbase}/Python{py_version_nodot}
+purelib = {userbase}/Python{py_version_nodot}/site-packages
+platlib = {userbase}/Python{py_version_nodot}/site-packages
+include = {userbase}/Python{py_version_nodot}/Include
+scripts = {userbase}/Scripts
+data = {userbase}
+
+[posix_user]
+stdlib = {userbase}/lib/python{py_version_short}
+platstdlib = {userbase}/lib/python{py_version_short}
+purelib = {userbase}/lib/python{py_version_short}/site-packages
+platlib = {userbase}/lib/python{py_version_short}/site-packages
+include = {userbase}/include/python{py_version_short}
+scripts = {userbase}/bin
+data = {userbase}
+
+[osx_framework_user]
+stdlib = {userbase}/lib/python
+platstdlib = {userbase}/lib/python
+purelib = {userbase}/lib/python/site-packages
+platlib = {userbase}/lib/python/site-packages
+include = {userbase}/include
+scripts = {userbase}/bin
+data = {userbase}
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py
new file mode 100644
index 0000000000..b470a373c8
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py
@@ -0,0 +1,786 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 The Python Software Foundation.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+"""Access to Python's configuration information."""
+
+import codecs
+import os
+import re
+import sys
+from os.path import pardir, realpath
+try:
+ import configparser
+except ImportError:
+ import ConfigParser as configparser
+
+
+__all__ = [
+ 'get_config_h_filename',
+ 'get_config_var',
+ 'get_config_vars',
+ 'get_makefile_filename',
+ 'get_path',
+ 'get_path_names',
+ 'get_paths',
+ 'get_platform',
+ 'get_python_version',
+ 'get_scheme_names',
+ 'parse_config_h',
+]
+
+
+def _safe_realpath(path):
+ try:
+ return realpath(path)
+ except OSError:
+ return path
+
+
+if sys.executable:
+ _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
+else:
+ # sys.executable can be empty if argv[0] has been changed and Python is
+ # unable to retrieve the real program name
+ _PROJECT_BASE = _safe_realpath(os.getcwd())
+
+if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
+ _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
+# PC/VS7.1
+if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
+ _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
+# PC/AMD64
+if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
+ _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
+
+
+def is_python_build():
+ for fn in ("Setup.dist", "Setup.local"):
+ if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
+ return True
+ return False
+
+_PYTHON_BUILD = is_python_build()
+
+_cfg_read = False
+
+def _ensure_cfg_read():
+ global _cfg_read
+ if not _cfg_read:
+ from ..resources import finder
+ backport_package = __name__.rsplit('.', 1)[0]
+ _finder = finder(backport_package)
+ _cfgfile = _finder.find('sysconfig.cfg')
+ assert _cfgfile, 'sysconfig.cfg exists'
+ with _cfgfile.as_stream() as s:
+ _SCHEMES.readfp(s)
+ if _PYTHON_BUILD:
+ for scheme in ('posix_prefix', 'posix_home'):
+ _SCHEMES.set(scheme, 'include', '{srcdir}/Include')
+ _SCHEMES.set(scheme, 'platinclude', '{projectbase}/.')
+
+ _cfg_read = True
+
+
+_SCHEMES = configparser.RawConfigParser()
+_VAR_REPL = re.compile(r'\{([^{]*?)\}')
+
+def _expand_globals(config):
+ _ensure_cfg_read()
+ if config.has_section('globals'):
+ globals = config.items('globals')
+ else:
+ globals = tuple()
+
+ sections = config.sections()
+ for section in sections:
+ if section == 'globals':
+ continue
+ for option, value in globals:
+ if config.has_option(section, option):
+ continue
+ config.set(section, option, value)
+ config.remove_section('globals')
+
+ # now expanding local variables defined in the cfg file
+ #
+ for section in config.sections():
+ variables = dict(config.items(section))
+
+ def _replacer(matchobj):
+ name = matchobj.group(1)
+ if name in variables:
+ return variables[name]
+ return matchobj.group(0)
+
+ for option, value in config.items(section):
+ config.set(section, option, _VAR_REPL.sub(_replacer, value))
+
+#_expand_globals(_SCHEMES)
+
+_PY_VERSION = '%s.%s.%s' % sys.version_info[:3]
+_PY_VERSION_SHORT = '%s.%s' % sys.version_info[:2]
+_PY_VERSION_SHORT_NO_DOT = '%s%s' % sys.version_info[:2]
+_PREFIX = os.path.normpath(sys.prefix)
+_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
+_CONFIG_VARS = None
+_USER_BASE = None
+
+
+def _subst_vars(path, local_vars):
+ """In the string `path`, replace tokens like {some.thing} with the
+ corresponding value from the map `local_vars`.
+
+ If there is no corresponding value, leave the token unchanged.
+ """
+ def _replacer(matchobj):
+ name = matchobj.group(1)
+ if name in local_vars:
+ return local_vars[name]
+ elif name in os.environ:
+ return os.environ[name]
+ return matchobj.group(0)
+ return _VAR_REPL.sub(_replacer, path)
+
+
+def _extend_dict(target_dict, other_dict):
+ target_keys = target_dict.keys()
+ for key, value in other_dict.items():
+ if key in target_keys:
+ continue
+ target_dict[key] = value
+
+
+def _expand_vars(scheme, vars):
+ res = {}
+ if vars is None:
+ vars = {}
+ _extend_dict(vars, get_config_vars())
+
+ for key, value in _SCHEMES.items(scheme):
+ if os.name in ('posix', 'nt'):
+ value = os.path.expanduser(value)
+ res[key] = os.path.normpath(_subst_vars(value, vars))
+ return res
+
+
+def format_value(value, vars):
+ def _replacer(matchobj):
+ name = matchobj.group(1)
+ if name in vars:
+ return vars[name]
+ return matchobj.group(0)
+ return _VAR_REPL.sub(_replacer, value)
+
+
+def _get_default_scheme():
+ if os.name == 'posix':
+ # the default scheme for posix is posix_prefix
+ return 'posix_prefix'
+ return os.name
+
+
+def _getuserbase():
+ env_base = os.environ.get("PYTHONUSERBASE", None)
+
+ def joinuser(*args):
+ return os.path.expanduser(os.path.join(*args))
+
+ # what about 'os2emx', 'riscos' ?
+ if os.name == "nt":
+ base = os.environ.get("APPDATA") or "~"
+ if env_base:
+ return env_base
+ else:
+ return joinuser(base, "Python")
+
+ if sys.platform == "darwin":
+ framework = get_config_var("PYTHONFRAMEWORK")
+ if framework:
+ if env_base:
+ return env_base
+ else:
+ return joinuser("~", "Library", framework, "%d.%d" %
+ sys.version_info[:2])
+
+ if env_base:
+ return env_base
+ else:
+ return joinuser("~", ".local")
+
+
+def _parse_makefile(filename, vars=None):
+ """Parse a Makefile-style file.
+
+ A dictionary containing name/value pairs is returned. If an
+ optional dictionary is passed in as the second argument, it is
+ used instead of a new dictionary.
+ """
+ # Regexes needed for parsing Makefile (and similar syntaxes,
+ # like old-style Setup files).
+ _variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
+ _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
+ _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
+
+ if vars is None:
+ vars = {}
+ done = {}
+ notdone = {}
+
+ with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f:
+ lines = f.readlines()
+
+ for line in lines:
+ if line.startswith('#') or line.strip() == '':
+ continue
+ m = _variable_rx.match(line)
+ if m:
+ n, v = m.group(1, 2)
+ v = v.strip()
+ # `$$' is a literal `$' in make
+ tmpv = v.replace('$$', '')
+
+ if "$" in tmpv:
+ notdone[n] = v
+ else:
+ try:
+ v = int(v)
+ except ValueError:
+ # insert literal `$'
+ done[n] = v.replace('$$', '$')
+ else:
+ done[n] = v
+
+ # do variable interpolation here
+ variables = list(notdone.keys())
+
+ # Variables with a 'PY_' prefix in the makefile. These need to
+ # be made available without that prefix through sysconfig.
+ # Special care is needed to ensure that variable expansion works, even
+ # if the expansion uses the name without a prefix.
+ renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
+
+ while len(variables) > 0:
+ for name in tuple(variables):
+ value = notdone[name]
+ m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
+ if m is not None:
+ n = m.group(1)
+ found = True
+ if n in done:
+ item = str(done[n])
+ elif n in notdone:
+ # get it on a subsequent round
+ found = False
+ elif n in os.environ:
+ # do it like make: fall back to environment
+ item = os.environ[n]
+
+ elif n in renamed_variables:
+ if (name.startswith('PY_') and
+ name[3:] in renamed_variables):
+ item = ""
+
+ elif 'PY_' + n in notdone:
+ found = False
+
+ else:
+ item = str(done['PY_' + n])
+
+ else:
+ done[n] = item = ""
+
+ if found:
+ after = value[m.end():]
+ value = value[:m.start()] + item + after
+ if "$" in after:
+ notdone[name] = value
+ else:
+ try:
+ value = int(value)
+ except ValueError:
+ done[name] = value.strip()
+ else:
+ done[name] = value
+ variables.remove(name)
+
+ if (name.startswith('PY_') and
+ name[3:] in renamed_variables):
+
+ name = name[3:]
+ if name not in done:
+ done[name] = value
+
+ else:
+ # bogus variable reference (e.g. "prefix=$/opt/python");
+ # just drop it since we can't deal
+ done[name] = value
+ variables.remove(name)
+
+ # strip spurious spaces
+ for k, v in done.items():
+ if isinstance(v, str):
+ done[k] = v.strip()
+
+ # save the results in the global dictionary
+ vars.update(done)
+ return vars
+
+
+def get_makefile_filename():
+ """Return the path of the Makefile."""
+ if _PYTHON_BUILD:
+ return os.path.join(_PROJECT_BASE, "Makefile")
+ if hasattr(sys, 'abiflags'):
+ config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
+ else:
+ config_dir_name = 'config'
+ return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
+
+
+def _init_posix(vars):
+ """Initialize the module as appropriate for POSIX systems."""
+ # load the installed Makefile:
+ makefile = get_makefile_filename()
+ try:
+ _parse_makefile(makefile, vars)
+ except IOError as e:
+ msg = "invalid Python installation: unable to open %s" % makefile
+ if hasattr(e, "strerror"):
+ msg = msg + " (%s)" % e.strerror
+ raise IOError(msg)
+ # load the installed pyconfig.h:
+ config_h = get_config_h_filename()
+ try:
+ with open(config_h) as f:
+ parse_config_h(f, vars)
+ except IOError as e:
+ msg = "invalid Python installation: unable to open %s" % config_h
+ if hasattr(e, "strerror"):
+ msg = msg + " (%s)" % e.strerror
+ raise IOError(msg)
+ # On AIX, there are wrong paths to the linker scripts in the Makefile
+ # -- these paths are relative to the Python source, but when installed
+ # the scripts are in another directory.
+ if _PYTHON_BUILD:
+ vars['LDSHARED'] = vars['BLDSHARED']
+
+
+def _init_non_posix(vars):
+ """Initialize the module as appropriate for NT"""
+ # set basic install directories
+ vars['LIBDEST'] = get_path('stdlib')
+ vars['BINLIBDEST'] = get_path('platstdlib')
+ vars['INCLUDEPY'] = get_path('include')
+ vars['SO'] = '.pyd'
+ vars['EXE'] = '.exe'
+ vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
+ vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
+
+#
+# public APIs
+#
+
+
+def parse_config_h(fp, vars=None):
+ """Parse a config.h-style file.
+
+ A dictionary containing name/value pairs is returned. If an
+ optional dictionary is passed in as the second argument, it is
+ used instead of a new dictionary.
+ """
+ if vars is None:
+ vars = {}
+ define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
+ undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
+
+ while True:
+ line = fp.readline()
+ if not line:
+ break
+ m = define_rx.match(line)
+ if m:
+ n, v = m.group(1, 2)
+ try:
+ v = int(v)
+ except ValueError:
+ pass
+ vars[n] = v
+ else:
+ m = undef_rx.match(line)
+ if m:
+ vars[m.group(1)] = 0
+ return vars
+
+
+def get_config_h_filename():
+ """Return the path of pyconfig.h."""
+ if _PYTHON_BUILD:
+ if os.name == "nt":
+ inc_dir = os.path.join(_PROJECT_BASE, "PC")
+ else:
+ inc_dir = _PROJECT_BASE
+ else:
+ inc_dir = get_path('platinclude')
+ return os.path.join(inc_dir, 'pyconfig.h')
+
+
+def get_scheme_names():
+ """Return a tuple containing the schemes names."""
+ return tuple(sorted(_SCHEMES.sections()))
+
+
+def get_path_names():
+ """Return a tuple containing the paths names."""
+ # xxx see if we want a static list
+ return _SCHEMES.options('posix_prefix')
+
+
+def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
+ """Return a mapping containing an install scheme.
+
+ ``scheme`` is the install scheme name. If not provided, it will
+ return the default scheme for the current platform.
+ """
+ _ensure_cfg_read()
+ if expand:
+ return _expand_vars(scheme, vars)
+ else:
+ return dict(_SCHEMES.items(scheme))
+
+
+def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
+ """Return a path corresponding to the scheme.
+
+ ``scheme`` is the install scheme name.
+ """
+ return get_paths(scheme, vars, expand)[name]
+
+
+def get_config_vars(*args):
+ """With no arguments, return a dictionary of all configuration
+ variables relevant for the current platform.
+
+ On Unix, this means every variable defined in Python's installed Makefile;
+ On Windows and Mac OS it's a much smaller set.
+
+ With arguments, return a list of values that result from looking up
+ each argument in the configuration variable dictionary.
+ """
+ global _CONFIG_VARS
+ if _CONFIG_VARS is None:
+ _CONFIG_VARS = {}
+ # Normalized versions of prefix and exec_prefix are handy to have;
+ # in fact, these are the standard versions used most places in the
+ # distutils2 module.
+ _CONFIG_VARS['prefix'] = _PREFIX
+ _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
+ _CONFIG_VARS['py_version'] = _PY_VERSION
+ _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
+ _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
+ _CONFIG_VARS['base'] = _PREFIX
+ _CONFIG_VARS['platbase'] = _EXEC_PREFIX
+ _CONFIG_VARS['projectbase'] = _PROJECT_BASE
+ try:
+ _CONFIG_VARS['abiflags'] = sys.abiflags
+ except AttributeError:
+ # sys.abiflags may not be defined on all platforms.
+ _CONFIG_VARS['abiflags'] = ''
+
+ if os.name in ('nt', 'os2'):
+ _init_non_posix(_CONFIG_VARS)
+ if os.name == 'posix':
+ _init_posix(_CONFIG_VARS)
+ # Setting 'userbase' is done below the call to the
+ # init function to enable using 'get_config_var' in
+ # the init-function.
+ if sys.version >= '2.6':
+ _CONFIG_VARS['userbase'] = _getuserbase()
+
+ if 'srcdir' not in _CONFIG_VARS:
+ _CONFIG_VARS['srcdir'] = _PROJECT_BASE
+ else:
+ _CONFIG_VARS['srcdir'] = _safe_realpath(_CONFIG_VARS['srcdir'])
+
+ # Convert srcdir into an absolute path if it appears necessary.
+ # Normally it is relative to the build directory. However, during
+ # testing, for example, we might be running a non-installed python
+ # from a different directory.
+ if _PYTHON_BUILD and os.name == "posix":
+ base = _PROJECT_BASE
+ try:
+ cwd = os.getcwd()
+ except OSError:
+ cwd = None
+ if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
+ base != cwd):
+ # srcdir is relative and we are not in the same directory
+ # as the executable. Assume executable is in the build
+ # directory and make srcdir absolute.
+ srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
+ _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
+
+ if sys.platform == 'darwin':
+ kernel_version = os.uname()[2] # Kernel version (8.4.3)
+ major_version = int(kernel_version.split('.')[0])
+
+ if major_version < 8:
+ # On Mac OS X before 10.4, check if -arch and -isysroot
+ # are in CFLAGS or LDFLAGS and remove them if they are.
+ # This is needed when building extensions on a 10.3 system
+ # using a universal build of python.
+ for key in ('LDFLAGS', 'BASECFLAGS',
+ # a number of derived variables. These need to be
+ # patched up as well.
+ 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
+ flags = _CONFIG_VARS[key]
+ flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
+ flags = re.sub('-isysroot [^ \t]*', ' ', flags)
+ _CONFIG_VARS[key] = flags
+ else:
+ # Allow the user to override the architecture flags using
+ # an environment variable.
+ # NOTE: This name was introduced by Apple in OSX 10.5 and
+ # is used by several scripting languages distributed with
+ # that OS release.
+ if 'ARCHFLAGS' in os.environ:
+ arch = os.environ['ARCHFLAGS']
+ for key in ('LDFLAGS', 'BASECFLAGS',
+ # a number of derived variables. These need to be
+ # patched up as well.
+ 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
+
+ flags = _CONFIG_VARS[key]
+ flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
+ flags = flags + ' ' + arch
+ _CONFIG_VARS[key] = flags
+
+ # If we're on OSX 10.5 or later and the user tries to
+ # compiles an extension using an SDK that is not present
+ # on the current machine it is better to not use an SDK
+ # than to fail.
+ #
+ # The major usecase for this is users using a Python.org
+ # binary installer on OSX 10.6: that installer uses
+ # the 10.4u SDK, but that SDK is not installed by default
+ # when you install Xcode.
+ #
+ CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
+ m = re.search(r'-isysroot\s+(\S+)', CFLAGS)
+ if m is not None:
+ sdk = m.group(1)
+ if not os.path.exists(sdk):
+ for key in ('LDFLAGS', 'BASECFLAGS',
+ # a number of derived variables. These need to be
+ # patched up as well.
+ 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
+
+ flags = _CONFIG_VARS[key]
+ flags = re.sub(r'-isysroot\s+\S+(\s|$)', ' ', flags)
+ _CONFIG_VARS[key] = flags
+
+ if args:
+ vals = []
+ for name in args:
+ vals.append(_CONFIG_VARS.get(name))
+ return vals
+ else:
+ return _CONFIG_VARS
+
+
+def get_config_var(name):
+ """Return the value of a single variable using the dictionary returned by
+ 'get_config_vars()'.
+
+ Equivalent to get_config_vars().get(name)
+ """
+ return get_config_vars().get(name)
+
+
+def get_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. for IRIX
+ the architecture isn't particularly important (IRIX only runs on SGI
+ hardware), but for Linux the kernel version isn't particularly
+ important.
+
+ Examples of returned values:
+ linux-i586
+ linux-alpha (?)
+ solaris-2.6-sun4u
+ irix-5.3
+ irix64-6.2
+
+ Windows will return one of:
+ win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
+ win-ia64 (64bit Windows on Itanium)
+ win32 (all others - specifically, sys.platform is returned)
+
+ For other non-POSIX platforms, currently just returns 'sys.platform'.
+ """
+ if os.name == 'nt':
+ # sniff sys.version for architecture.
+ prefix = " bit ("
+ i = sys.version.find(prefix)
+ if i == -1:
+ return sys.platform
+ j = sys.version.find(")", i)
+ look = sys.version[i+len(prefix):j].lower()
+ if look == 'amd64':
+ return 'win-amd64'
+ if look == 'itanium':
+ return 'win-ia64'
+ return sys.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
+ # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
+ osname = osname.lower().replace('/', '')
+ machine = machine.replace(' ', '_')
+ machine = machine.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:])
+ # fall through to standard osname-release-machine representation
+ elif osname[:4] == "irix": # could be "irix64"!
+ return "%s-%s" % (osname, release)
+ elif osname[:3] == "aix":
+ return "%s-%s.%s" % (osname, version, release)
+ elif osname[:6] == "cygwin":
+ osname = "cygwin"
+ rel_re = re.compile(r'[\d.]+')
+ m = rel_re.match(release)
+ if m:
+ release = m.group()
+ elif osname[:6] == "darwin":
+ #
+ # For our purposes, we'll assume that the system version from
+ # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
+ # to. This makes the compatibility story a bit more sane because the
+ # machine is going to compile and link as if it were
+ # MACOSX_DEPLOYMENT_TARGET.
+ cfgvars = get_config_vars()
+ macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
+
+ if True:
+ # Always calculate the release of the running machine,
+ # needed to determine if we can build fat binaries or not.
+
+ macrelease = macver
+ # Get the system version. Reading this plist is a documented
+ # way to get the system version (see the documentation for
+ # the Gestalt Manager)
+ try:
+ f = open('/System/Library/CoreServices/SystemVersion.plist')
+ except IOError:
+ # We're on a plain darwin box, fall back to the default
+ # behaviour.
+ pass
+ else:
+ try:
+ m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
+ r'<string>(.*?)</string>', f.read())
+ finally:
+ f.close()
+ if m is not None:
+ macrelease = '.'.join(m.group(1).split('.')[:2])
+ # else: fall back to the default behaviour
+
+ if not macver:
+ macver = macrelease
+
+ if macver:
+ release = macver
+ osname = "macosx"
+
+ if ((macrelease + '.') >= '10.4.' and
+ '-arch' in get_config_vars().get('CFLAGS', '').strip()):
+ # The universal build will build fat binaries, but not on
+ # systems before 10.4
+ #
+ # Try to detect 4-way universal builds, those have machine-type
+ # 'universal' instead of 'fat'.
+
+ machine = 'fat'
+ cflags = get_config_vars().get('CFLAGS')
+
+ archs = re.findall(r'-arch\s+(\S+)', cflags)
+ archs = tuple(sorted(set(archs)))
+
+ if len(archs) == 1:
+ machine = archs[0]
+ elif archs == ('i386', 'ppc'):
+ machine = 'fat'
+ elif archs == ('i386', 'x86_64'):
+ machine = 'intel'
+ elif archs == ('i386', 'ppc', 'x86_64'):
+ machine = 'fat3'
+ elif archs == ('ppc64', 'x86_64'):
+ machine = 'fat64'
+ elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
+ machine = 'universal'
+ else:
+ raise ValueError(
+ "Don't know machine value for archs=%r" % (archs,))
+
+ elif machine == 'i386':
+ # On OSX the machine type returned by uname is always the
+ # 32-bit variant, even if the executable architecture is
+ # the 64-bit variant
+ if sys.maxsize >= 2**32:
+ machine = 'x86_64'
+
+ elif machine in ('PowerPC', 'Power_Macintosh'):
+ # Pick a sane name for the PPC architecture.
+ # See 'i386' case
+ if sys.maxsize >= 2**32:
+ machine = 'ppc64'
+ else:
+ machine = 'ppc'
+
+ return "%s-%s-%s" % (osname, release, machine)
+
+
+def get_python_version():
+ return _PY_VERSION_SHORT
+
+
+def _print_dict(title, data):
+ for index, (key, value) in enumerate(sorted(data.items())):
+ if index == 0:
+ print('%s: ' % (title))
+ print('\t%s = "%s"' % (key, value))
+
+
+def _main():
+ """Display all information sysconfig detains."""
+ print('Platform: "%s"' % get_platform())
+ print('Python version: "%s"' % get_python_version())
+ print('Current installation scheme: "%s"' % _get_default_scheme())
+ print()
+ _print_dict('Paths', get_paths())
+ print()
+ _print_dict('Variables', get_config_vars())
+
+
+if __name__ == '__main__':
+ _main()
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py
new file mode 100644
index 0000000000..d66d856637
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py
@@ -0,0 +1,2607 @@
+#-------------------------------------------------------------------
+# tarfile.py
+#-------------------------------------------------------------------
+# Copyright (C) 2002 Lars Gustaebel <lars@gustaebel.de>
+# All rights reserved.
+#
+# 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
+
+"""Read from and write to tar format archives.
+"""
+
+__version__ = "$Revision$"
+
+version = "0.9.0"
+__author__ = "Lars Gust\u00e4bel (lars@gustaebel.de)"
+__date__ = "$Date: 2011-02-25 17:42:01 +0200 (Fri, 25 Feb 2011) $"
+__cvsid__ = "$Id: tarfile.py 88586 2011-02-25 15:42:01Z marc-andre.lemburg $"
+__credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend."
+
+#---------
+# Imports
+#---------
+import sys
+import os
+import stat
+import errno
+import time
+import struct
+import copy
+import re
+
+try:
+ import grp, pwd
+except ImportError:
+ grp = pwd = None
+
+# os.symlink on Windows prior to 6.0 raises NotImplementedError
+symlink_exception = (AttributeError, NotImplementedError)
+try:
+ # WindowsError (1314) will be raised if the caller does not hold the
+ # SeCreateSymbolicLinkPrivilege privilege
+ symlink_exception += (WindowsError,)
+except NameError:
+ pass
+
+# from tarfile import *
+__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError"]
+
+if sys.version_info[0] < 3:
+ import __builtin__ as builtins
+else:
+ import builtins
+
+_open = builtins.open # Since 'open' is TarFile.open
+
+#---------------------------------------------------------
+# tar constants
+#---------------------------------------------------------
+NUL = b"\0" # the null character
+BLOCKSIZE = 512 # length of processing blocks
+RECORDSIZE = BLOCKSIZE * 20 # length of records
+GNU_MAGIC = b"ustar \0" # magic gnu tar string
+POSIX_MAGIC = b"ustar\x0000" # magic posix tar string
+
+LENGTH_NAME = 100 # maximum length of a filename
+LENGTH_LINK = 100 # maximum length of a linkname
+LENGTH_PREFIX = 155 # maximum length of the prefix field
+
+REGTYPE = b"0" # regular file
+AREGTYPE = b"\0" # regular file
+LNKTYPE = b"1" # link (inside tarfile)
+SYMTYPE = b"2" # symbolic link
+CHRTYPE = b"3" # character special device
+BLKTYPE = b"4" # block special device
+DIRTYPE = b"5" # directory
+FIFOTYPE = b"6" # fifo special device
+CONTTYPE = b"7" # contiguous file
+
+GNUTYPE_LONGNAME = b"L" # GNU tar longname
+GNUTYPE_LONGLINK = b"K" # GNU tar longlink
+GNUTYPE_SPARSE = b"S" # GNU tar sparse file
+
+XHDTYPE = b"x" # POSIX.1-2001 extended header
+XGLTYPE = b"g" # POSIX.1-2001 global header
+SOLARIS_XHDTYPE = b"X" # Solaris extended header
+
+USTAR_FORMAT = 0 # POSIX.1-1988 (ustar) format
+GNU_FORMAT = 1 # GNU tar format
+PAX_FORMAT = 2 # POSIX.1-2001 (pax) format
+DEFAULT_FORMAT = GNU_FORMAT
+
+#---------------------------------------------------------
+# tarfile constants
+#---------------------------------------------------------
+# File types that tarfile supports:
+SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE,
+ SYMTYPE, DIRTYPE, FIFOTYPE,
+ CONTTYPE, CHRTYPE, BLKTYPE,
+ GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
+ GNUTYPE_SPARSE)
+
+# File types that will be treated as a regular file.
+REGULAR_TYPES = (REGTYPE, AREGTYPE,
+ CONTTYPE, GNUTYPE_SPARSE)
+
+# File types that are part of the GNU tar format.
+GNU_TYPES = (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
+ GNUTYPE_SPARSE)
+
+# Fields from a pax header that override a TarInfo attribute.
+PAX_FIELDS = ("path", "linkpath", "size", "mtime",
+ "uid", "gid", "uname", "gname")
+
+# Fields from a pax header that are affected by hdrcharset.
+PAX_NAME_FIELDS = set(("path", "linkpath", "uname", "gname"))
+
+# Fields in a pax header that are numbers, all other fields
+# are treated as strings.
+PAX_NUMBER_FIELDS = {
+ "atime": float,
+ "ctime": float,
+ "mtime": float,
+ "uid": int,
+ "gid": int,
+ "size": int
+}
+
+#---------------------------------------------------------
+# Bits used in the mode field, values in octal.
+#---------------------------------------------------------
+S_IFLNK = 0o120000 # symbolic link
+S_IFREG = 0o100000 # regular file
+S_IFBLK = 0o060000 # block device
+S_IFDIR = 0o040000 # directory
+S_IFCHR = 0o020000 # character device
+S_IFIFO = 0o010000 # fifo
+
+TSUID = 0o4000 # set UID on execution
+TSGID = 0o2000 # set GID on execution
+TSVTX = 0o1000 # reserved
+
+TUREAD = 0o400 # read by owner
+TUWRITE = 0o200 # write by owner
+TUEXEC = 0o100 # execute/search by owner
+TGREAD = 0o040 # read by group
+TGWRITE = 0o020 # write by group
+TGEXEC = 0o010 # execute/search by group
+TOREAD = 0o004 # read by other
+TOWRITE = 0o002 # write by other
+TOEXEC = 0o001 # execute/search by other
+
+#---------------------------------------------------------
+# initialization
+#---------------------------------------------------------
+if os.name in ("nt", "ce"):
+ ENCODING = "utf-8"
+else:
+ ENCODING = sys.getfilesystemencoding()
+
+#---------------------------------------------------------
+# Some useful functions
+#---------------------------------------------------------
+
+def stn(s, length, encoding, errors):
+ """Convert a string to a null-terminated bytes object.
+ """
+ s = s.encode(encoding, errors)
+ return s[:length] + (length - len(s)) * NUL
+
+def nts(s, encoding, errors):
+ """Convert a null-terminated bytes object to a string.
+ """
+ p = s.find(b"\0")
+ if p != -1:
+ s = s[:p]
+ return s.decode(encoding, errors)
+
+def nti(s):
+ """Convert a number field to a python number.
+ """
+ # There are two possible encodings for a number field, see
+ # itn() below.
+ if s[0] != chr(0o200):
+ try:
+ n = int(nts(s, "ascii", "strict") or "0", 8)
+ except ValueError:
+ raise InvalidHeaderError("invalid header")
+ else:
+ n = 0
+ for i in range(len(s) - 1):
+ n <<= 8
+ n += ord(s[i + 1])
+ return n
+
+def itn(n, digits=8, format=DEFAULT_FORMAT):
+ """Convert a python number to a number field.
+ """
+ # POSIX 1003.1-1988 requires numbers to be encoded as a string of
+ # octal digits followed by a null-byte, this allows values up to
+ # (8**(digits-1))-1. GNU tar allows storing numbers greater than
+ # that if necessary. A leading 0o200 byte indicates this particular
+ # encoding, the following digits-1 bytes are a big-endian
+ # representation. This allows values up to (256**(digits-1))-1.
+ if 0 <= n < 8 ** (digits - 1):
+ s = ("%0*o" % (digits - 1, n)).encode("ascii") + NUL
+ else:
+ if format != GNU_FORMAT or n >= 256 ** (digits - 1):
+ raise ValueError("overflow in number field")
+
+ if n < 0:
+ # XXX We mimic GNU tar's behaviour with negative numbers,
+ # this could raise OverflowError.
+ n = struct.unpack("L", struct.pack("l", n))[0]
+
+ s = bytearray()
+ for i in range(digits - 1):
+ s.insert(0, n & 0o377)
+ n >>= 8
+ s.insert(0, 0o200)
+ return s
+
+def calc_chksums(buf):
+ """Calculate the checksum for a member's header by summing up all
+ characters except for the chksum field which is treated as if
+ it was filled with spaces. According to the GNU tar sources,
+ some tars (Sun and NeXT) calculate chksum with signed char,
+ which will be different if there are chars in the buffer with
+ the high bit set. So we calculate two checksums, unsigned and
+ signed.
+ """
+ unsigned_chksum = 256 + sum(struct.unpack("148B", buf[:148]) + struct.unpack("356B", buf[156:512]))
+ signed_chksum = 256 + sum(struct.unpack("148b", buf[:148]) + struct.unpack("356b", buf[156:512]))
+ return unsigned_chksum, signed_chksum
+
+def copyfileobj(src, dst, length=None):
+ """Copy length bytes from fileobj src to fileobj dst.
+ If length is None, copy the entire content.
+ """
+ if length == 0:
+ return
+ if length is None:
+ while True:
+ buf = src.read(16*1024)
+ if not buf:
+ break
+ dst.write(buf)
+ return
+
+ BUFSIZE = 16 * 1024
+ blocks, remainder = divmod(length, BUFSIZE)
+ for b in range(blocks):
+ buf = src.read(BUFSIZE)
+ if len(buf) < BUFSIZE:
+ raise IOError("end of file reached")
+ dst.write(buf)
+
+ if remainder != 0:
+ buf = src.read(remainder)
+ if len(buf) < remainder:
+ raise IOError("end of file reached")
+ dst.write(buf)
+ return
+
+filemode_table = (
+ ((S_IFLNK, "l"),
+ (S_IFREG, "-"),
+ (S_IFBLK, "b"),
+ (S_IFDIR, "d"),
+ (S_IFCHR, "c"),
+ (S_IFIFO, "p")),
+
+ ((TUREAD, "r"),),
+ ((TUWRITE, "w"),),
+ ((TUEXEC|TSUID, "s"),
+ (TSUID, "S"),
+ (TUEXEC, "x")),
+
+ ((TGREAD, "r"),),
+ ((TGWRITE, "w"),),
+ ((TGEXEC|TSGID, "s"),
+ (TSGID, "S"),
+ (TGEXEC, "x")),
+
+ ((TOREAD, "r"),),
+ ((TOWRITE, "w"),),
+ ((TOEXEC|TSVTX, "t"),
+ (TSVTX, "T"),
+ (TOEXEC, "x"))
+)
+
+def filemode(mode):
+ """Convert a file's mode to a string of the form
+ -rwxrwxrwx.
+ Used by TarFile.list()
+ """
+ perm = []
+ for table in filemode_table:
+ for bit, char in table:
+ if mode & bit == bit:
+ perm.append(char)
+ break
+ else:
+ perm.append("-")
+ return "".join(perm)
+
+class TarError(Exception):
+ """Base exception."""
+ pass
+class ExtractError(TarError):
+ """General exception for extract errors."""
+ pass
+class ReadError(TarError):
+ """Exception for unreadable tar archives."""
+ pass
+class CompressionError(TarError):
+ """Exception for unavailable compression methods."""
+ pass
+class StreamError(TarError):
+ """Exception for unsupported operations on stream-like TarFiles."""
+ pass
+class HeaderError(TarError):
+ """Base exception for header errors."""
+ pass
+class EmptyHeaderError(HeaderError):
+ """Exception for empty headers."""
+ pass
+class TruncatedHeaderError(HeaderError):
+ """Exception for truncated headers."""
+ pass
+class EOFHeaderError(HeaderError):
+ """Exception for end of file headers."""
+ pass
+class InvalidHeaderError(HeaderError):
+ """Exception for invalid headers."""
+ pass
+class SubsequentHeaderError(HeaderError):
+ """Exception for missing and invalid extended headers."""
+ pass
+
+#---------------------------
+# internal stream interface
+#---------------------------
+class _LowLevelFile(object):
+ """Low-level file object. Supports reading and writing.
+ It is used instead of a regular file object for streaming
+ access.
+ """
+
+ def __init__(self, name, mode):
+ mode = {
+ "r": os.O_RDONLY,
+ "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
+ }[mode]
+ if hasattr(os, "O_BINARY"):
+ mode |= os.O_BINARY
+ self.fd = os.open(name, mode, 0o666)
+
+ def close(self):
+ os.close(self.fd)
+
+ def read(self, size):
+ return os.read(self.fd, size)
+
+ def write(self, s):
+ os.write(self.fd, s)
+
+class _Stream(object):
+ """Class that serves as an adapter between TarFile and
+ a stream-like object. The stream-like object only
+ needs to have a read() or write() method and is accessed
+ blockwise. Use of gzip or bzip2 compression is possible.
+ A stream-like object could be for example: sys.stdin,
+ sys.stdout, a socket, a tape device etc.
+
+ _Stream is intended to be used only internally.
+ """
+
+ def __init__(self, name, mode, comptype, fileobj, bufsize):
+ """Construct a _Stream object.
+ """
+ self._extfileobj = True
+ if fileobj is None:
+ fileobj = _LowLevelFile(name, mode)
+ self._extfileobj = False
+
+ if comptype == '*':
+ # Enable transparent compression detection for the
+ # stream interface
+ fileobj = _StreamProxy(fileobj)
+ comptype = fileobj.getcomptype()
+
+ self.name = name or ""
+ self.mode = mode
+ self.comptype = comptype
+ self.fileobj = fileobj
+ self.bufsize = bufsize
+ self.buf = b""
+ self.pos = 0
+ self.closed = False
+
+ try:
+ if comptype == "gz":
+ try:
+ import zlib
+ except ImportError:
+ raise CompressionError("zlib module is not available")
+ self.zlib = zlib
+ self.crc = zlib.crc32(b"")
+ if mode == "r":
+ self._init_read_gz()
+ else:
+ self._init_write_gz()
+
+ if comptype == "bz2":
+ try:
+ import bz2
+ except ImportError:
+ raise CompressionError("bz2 module is not available")
+ if mode == "r":
+ self.dbuf = b""
+ self.cmp = bz2.BZ2Decompressor()
+ else:
+ self.cmp = bz2.BZ2Compressor()
+ except:
+ if not self._extfileobj:
+ self.fileobj.close()
+ self.closed = True
+ raise
+
+ def __del__(self):
+ if hasattr(self, "closed") and not self.closed:
+ self.close()
+
+ def _init_write_gz(self):
+ """Initialize for writing with gzip compression.
+ """
+ self.cmp = self.zlib.compressobj(9, self.zlib.DEFLATED,
+ -self.zlib.MAX_WBITS,
+ self.zlib.DEF_MEM_LEVEL,
+ 0)
+ timestamp = struct.pack("<L", int(time.time()))
+ self.__write(b"\037\213\010\010" + timestamp + b"\002\377")
+ if self.name.endswith(".gz"):
+ self.name = self.name[:-3]
+ # RFC1952 says we must use ISO-8859-1 for the FNAME field.
+ self.__write(self.name.encode("iso-8859-1", "replace") + NUL)
+
+ def write(self, s):
+ """Write string s to the stream.
+ """
+ if self.comptype == "gz":
+ self.crc = self.zlib.crc32(s, self.crc)
+ self.pos += len(s)
+ if self.comptype != "tar":
+ s = self.cmp.compress(s)
+ self.__write(s)
+
+ def __write(self, s):
+ """Write string s to the stream if a whole new block
+ is ready to be written.
+ """
+ self.buf += s
+ while len(self.buf) > self.bufsize:
+ self.fileobj.write(self.buf[:self.bufsize])
+ self.buf = self.buf[self.bufsize:]
+
+ def close(self):
+ """Close the _Stream object. No operation should be
+ done on it afterwards.
+ """
+ if self.closed:
+ return
+
+ if self.mode == "w" and self.comptype != "tar":
+ self.buf += self.cmp.flush()
+
+ if self.mode == "w" and self.buf:
+ self.fileobj.write(self.buf)
+ self.buf = b""
+ if self.comptype == "gz":
+ # The native zlib crc is an unsigned 32-bit integer, but
+ # the Python wrapper implicitly casts that to a signed C
+ # long. So, on a 32-bit box self.crc may "look negative",
+ # while the same crc on a 64-bit box may "look positive".
+ # To avoid irksome warnings from the `struct` module, force
+ # it to look positive on all boxes.
+ self.fileobj.write(struct.pack("<L", self.crc & 0xffffffff))
+ self.fileobj.write(struct.pack("<L", self.pos & 0xffffFFFF))
+
+ if not self._extfileobj:
+ self.fileobj.close()
+
+ self.closed = True
+
+ def _init_read_gz(self):
+ """Initialize for reading a gzip compressed fileobj.
+ """
+ self.cmp = self.zlib.decompressobj(-self.zlib.MAX_WBITS)
+ self.dbuf = b""
+
+ # taken from gzip.GzipFile with some alterations
+ if self.__read(2) != b"\037\213":
+ raise ReadError("not a gzip file")
+ if self.__read(1) != b"\010":
+ raise CompressionError("unsupported compression method")
+
+ flag = ord(self.__read(1))
+ self.__read(6)
+
+ if flag & 4:
+ xlen = ord(self.__read(1)) + 256 * ord(self.__read(1))
+ self.read(xlen)
+ if flag & 8:
+ while True:
+ s = self.__read(1)
+ if not s or s == NUL:
+ break
+ if flag & 16:
+ while True:
+ s = self.__read(1)
+ if not s or s == NUL:
+ break
+ if flag & 2:
+ self.__read(2)
+
+ def tell(self):
+ """Return the stream's file pointer position.
+ """
+ return self.pos
+
+ def seek(self, pos=0):
+ """Set the stream's file pointer to pos. Negative seeking
+ is forbidden.
+ """
+ if pos - self.pos >= 0:
+ blocks, remainder = divmod(pos - self.pos, self.bufsize)
+ for i in range(blocks):
+ self.read(self.bufsize)
+ self.read(remainder)
+ else:
+ raise StreamError("seeking backwards is not allowed")
+ return self.pos
+
+ def read(self, size=None):
+ """Return the next size number of bytes from the stream.
+ If size is not defined, return all bytes of the stream
+ up to EOF.
+ """
+ if size is None:
+ t = []
+ while True:
+ buf = self._read(self.bufsize)
+ if not buf:
+ break
+ t.append(buf)
+ buf = "".join(t)
+ else:
+ buf = self._read(size)
+ self.pos += len(buf)
+ return buf
+
+ def _read(self, size):
+ """Return size bytes from the stream.
+ """
+ if self.comptype == "tar":
+ return self.__read(size)
+
+ c = len(self.dbuf)
+ while c < size:
+ buf = self.__read(self.bufsize)
+ if not buf:
+ break
+ try:
+ buf = self.cmp.decompress(buf)
+ except IOError:
+ raise ReadError("invalid compressed data")
+ self.dbuf += buf
+ c += len(buf)
+ buf = self.dbuf[:size]
+ self.dbuf = self.dbuf[size:]
+ return buf
+
+ def __read(self, size):
+ """Return size bytes from stream. If internal buffer is empty,
+ read another block from the stream.
+ """
+ c = len(self.buf)
+ while c < size:
+ buf = self.fileobj.read(self.bufsize)
+ if not buf:
+ break
+ self.buf += buf
+ c += len(buf)
+ buf = self.buf[:size]
+ self.buf = self.buf[size:]
+ return buf
+# class _Stream
+
+class _StreamProxy(object):
+ """Small proxy class that enables transparent compression
+ detection for the Stream interface (mode 'r|*').
+ """
+
+ def __init__(self, fileobj):
+ self.fileobj = fileobj
+ self.buf = self.fileobj.read(BLOCKSIZE)
+
+ def read(self, size):
+ self.read = self.fileobj.read
+ return self.buf
+
+ def getcomptype(self):
+ if self.buf.startswith(b"\037\213\010"):
+ return "gz"
+ if self.buf.startswith(b"BZh91"):
+ return "bz2"
+ return "tar"
+
+ def close(self):
+ self.fileobj.close()
+# class StreamProxy
+
+class _BZ2Proxy(object):
+ """Small proxy class that enables external file object
+ support for "r:bz2" and "w:bz2" modes. This is actually
+ a workaround for a limitation in bz2 module's BZ2File
+ class which (unlike gzip.GzipFile) has no support for
+ a file object argument.
+ """
+
+ blocksize = 16 * 1024
+
+ def __init__(self, fileobj, mode):
+ self.fileobj = fileobj
+ self.mode = mode
+ self.name = getattr(self.fileobj, "name", None)
+ self.init()
+
+ def init(self):
+ import bz2
+ self.pos = 0
+ if self.mode == "r":
+ self.bz2obj = bz2.BZ2Decompressor()
+ self.fileobj.seek(0)
+ self.buf = b""
+ else:
+ self.bz2obj = bz2.BZ2Compressor()
+
+ def read(self, size):
+ x = len(self.buf)
+ while x < size:
+ raw = self.fileobj.read(self.blocksize)
+ if not raw:
+ break
+ data = self.bz2obj.decompress(raw)
+ self.buf += data
+ x += len(data)
+
+ buf = self.buf[:size]
+ self.buf = self.buf[size:]
+ self.pos += len(buf)
+ return buf
+
+ def seek(self, pos):
+ if pos < self.pos:
+ self.init()
+ self.read(pos - self.pos)
+
+ def tell(self):
+ return self.pos
+
+ def write(self, data):
+ self.pos += len(data)
+ raw = self.bz2obj.compress(data)
+ self.fileobj.write(raw)
+
+ def close(self):
+ if self.mode == "w":
+ raw = self.bz2obj.flush()
+ self.fileobj.write(raw)
+# class _BZ2Proxy
+
+#------------------------
+# Extraction file object
+#------------------------
+class _FileInFile(object):
+ """A thin wrapper around an existing file object that
+ provides a part of its data as an individual file
+ object.
+ """
+
+ def __init__(self, fileobj, offset, size, blockinfo=None):
+ self.fileobj = fileobj
+ self.offset = offset
+ self.size = size
+ self.position = 0
+
+ if blockinfo is None:
+ blockinfo = [(0, size)]
+
+ # Construct a map with data and zero blocks.
+ self.map_index = 0
+ self.map = []
+ lastpos = 0
+ realpos = self.offset
+ for offset, size in blockinfo:
+ if offset > lastpos:
+ self.map.append((False, lastpos, offset, None))
+ self.map.append((True, offset, offset + size, realpos))
+ realpos += size
+ lastpos = offset + size
+ if lastpos < self.size:
+ self.map.append((False, lastpos, self.size, None))
+
+ def seekable(self):
+ if not hasattr(self.fileobj, "seekable"):
+ # XXX gzip.GzipFile and bz2.BZ2File
+ return True
+ return self.fileobj.seekable()
+
+ def tell(self):
+ """Return the current file position.
+ """
+ return self.position
+
+ def seek(self, position):
+ """Seek to a position in the file.
+ """
+ self.position = position
+
+ def read(self, size=None):
+ """Read data from the file.
+ """
+ if size is None:
+ size = self.size - self.position
+ else:
+ size = min(size, self.size - self.position)
+
+ buf = b""
+ while size > 0:
+ while True:
+ data, start, stop, offset = self.map[self.map_index]
+ if start <= self.position < stop:
+ break
+ else:
+ self.map_index += 1
+ if self.map_index == len(self.map):
+ self.map_index = 0
+ length = min(size, stop - self.position)
+ if data:
+ self.fileobj.seek(offset + (self.position - start))
+ buf += self.fileobj.read(length)
+ else:
+ buf += NUL * length
+ size -= length
+ self.position += length
+ return buf
+#class _FileInFile
+
+
+class ExFileObject(object):
+ """File-like object for reading an archive member.
+ Is returned by TarFile.extractfile().
+ """
+ blocksize = 1024
+
+ def __init__(self, tarfile, tarinfo):
+ self.fileobj = _FileInFile(tarfile.fileobj,
+ tarinfo.offset_data,
+ tarinfo.size,
+ tarinfo.sparse)
+ self.name = tarinfo.name
+ self.mode = "r"
+ self.closed = False
+ self.size = tarinfo.size
+
+ self.position = 0
+ self.buffer = b""
+
+ def readable(self):
+ return True
+
+ def writable(self):
+ return False
+
+ def seekable(self):
+ return self.fileobj.seekable()
+
+ def read(self, size=None):
+ """Read at most size bytes from the file. If size is not
+ present or None, read all data until EOF is reached.
+ """
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+
+ buf = b""
+ if self.buffer:
+ if size is None:
+ buf = self.buffer
+ self.buffer = b""
+ else:
+ buf = self.buffer[:size]
+ self.buffer = self.buffer[size:]
+
+ if size is None:
+ buf += self.fileobj.read()
+ else:
+ buf += self.fileobj.read(size - len(buf))
+
+ self.position += len(buf)
+ return buf
+
+ # XXX TextIOWrapper uses the read1() method.
+ read1 = read
+
+ def readline(self, size=-1):
+ """Read one entire line from the file. If size is present
+ and non-negative, return a string with at most that
+ size, which may be an incomplete line.
+ """
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+
+ pos = self.buffer.find(b"\n") + 1
+ if pos == 0:
+ # no newline found.
+ while True:
+ buf = self.fileobj.read(self.blocksize)
+ self.buffer += buf
+ if not buf or b"\n" in buf:
+ pos = self.buffer.find(b"\n") + 1
+ if pos == 0:
+ # no newline found.
+ pos = len(self.buffer)
+ break
+
+ if size != -1:
+ pos = min(size, pos)
+
+ buf = self.buffer[:pos]
+ self.buffer = self.buffer[pos:]
+ self.position += len(buf)
+ return buf
+
+ def readlines(self):
+ """Return a list with all remaining lines.
+ """
+ result = []
+ while True:
+ line = self.readline()
+ if not line: break
+ result.append(line)
+ return result
+
+ def tell(self):
+ """Return the current file position.
+ """
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+
+ return self.position
+
+ def seek(self, pos, whence=os.SEEK_SET):
+ """Seek to a position in the file.
+ """
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+
+ if whence == os.SEEK_SET:
+ self.position = min(max(pos, 0), self.size)
+ elif whence == os.SEEK_CUR:
+ if pos < 0:
+ self.position = max(self.position + pos, 0)
+ else:
+ self.position = min(self.position + pos, self.size)
+ elif whence == os.SEEK_END:
+ self.position = max(min(self.size + pos, self.size), 0)
+ else:
+ raise ValueError("Invalid argument")
+
+ self.buffer = b""
+ self.fileobj.seek(self.position)
+
+ def close(self):
+ """Close the file object.
+ """
+ self.closed = True
+
+ def __iter__(self):
+ """Get an iterator over the file's lines.
+ """
+ while True:
+ line = self.readline()
+ if not line:
+ break
+ yield line
+#class ExFileObject
+
+#------------------
+# Exported Classes
+#------------------
+class TarInfo(object):
+ """Informational class which holds the details about an
+ archive member given by a tar header block.
+ TarInfo objects are returned by TarFile.getmember(),
+ TarFile.getmembers() and TarFile.gettarinfo() and are
+ usually created internally.
+ """
+
+ __slots__ = ("name", "mode", "uid", "gid", "size", "mtime",
+ "chksum", "type", "linkname", "uname", "gname",
+ "devmajor", "devminor",
+ "offset", "offset_data", "pax_headers", "sparse",
+ "tarfile", "_sparse_structs", "_link_target")
+
+ def __init__(self, name=""):
+ """Construct a TarInfo object. name is the optional name
+ of the member.
+ """
+ self.name = name # member name
+ self.mode = 0o644 # file permissions
+ self.uid = 0 # user id
+ self.gid = 0 # group id
+ self.size = 0 # file size
+ self.mtime = 0 # modification time
+ self.chksum = 0 # header checksum
+ self.type = REGTYPE # member type
+ self.linkname = "" # link name
+ self.uname = "" # user name
+ self.gname = "" # group name
+ self.devmajor = 0 # device major number
+ self.devminor = 0 # device minor number
+
+ self.offset = 0 # the tar header starts here
+ self.offset_data = 0 # the file's data starts here
+
+ self.sparse = None # sparse member information
+ self.pax_headers = {} # pax header information
+
+ # In pax headers the "name" and "linkname" field are called
+ # "path" and "linkpath".
+ def _getpath(self):
+ return self.name
+ def _setpath(self, name):
+ self.name = name
+ path = property(_getpath, _setpath)
+
+ def _getlinkpath(self):
+ return self.linkname
+ def _setlinkpath(self, linkname):
+ self.linkname = linkname
+ linkpath = property(_getlinkpath, _setlinkpath)
+
+ def __repr__(self):
+ return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self))
+
+ def get_info(self):
+ """Return the TarInfo's attributes as a dictionary.
+ """
+ info = {
+ "name": self.name,
+ "mode": self.mode & 0o7777,
+ "uid": self.uid,
+ "gid": self.gid,
+ "size": self.size,
+ "mtime": self.mtime,
+ "chksum": self.chksum,
+ "type": self.type,
+ "linkname": self.linkname,
+ "uname": self.uname,
+ "gname": self.gname,
+ "devmajor": self.devmajor,
+ "devminor": self.devminor
+ }
+
+ if info["type"] == DIRTYPE and not info["name"].endswith("/"):
+ info["name"] += "/"
+
+ return info
+
+ def tobuf(self, format=DEFAULT_FORMAT, encoding=ENCODING, errors="surrogateescape"):
+ """Return a tar header as a string of 512 byte blocks.
+ """
+ info = self.get_info()
+
+ if format == USTAR_FORMAT:
+ return self.create_ustar_header(info, encoding, errors)
+ elif format == GNU_FORMAT:
+ return self.create_gnu_header(info, encoding, errors)
+ elif format == PAX_FORMAT:
+ return self.create_pax_header(info, encoding)
+ else:
+ raise ValueError("invalid format")
+
+ def create_ustar_header(self, info, encoding, errors):
+ """Return the object as a ustar header block.
+ """
+ info["magic"] = POSIX_MAGIC
+
+ if len(info["linkname"]) > LENGTH_LINK:
+ raise ValueError("linkname is too long")
+
+ if len(info["name"]) > LENGTH_NAME:
+ info["prefix"], info["name"] = self._posix_split_name(info["name"])
+
+ return self._create_header(info, USTAR_FORMAT, encoding, errors)
+
+ def create_gnu_header(self, info, encoding, errors):
+ """Return the object as a GNU header block sequence.
+ """
+ info["magic"] = GNU_MAGIC
+
+ buf = b""
+ if len(info["linkname"]) > LENGTH_LINK:
+ buf += self._create_gnu_long_header(info["linkname"], GNUTYPE_LONGLINK, encoding, errors)
+
+ if len(info["name"]) > LENGTH_NAME:
+ buf += self._create_gnu_long_header(info["name"], GNUTYPE_LONGNAME, encoding, errors)
+
+ return buf + self._create_header(info, GNU_FORMAT, encoding, errors)
+
+ def create_pax_header(self, info, encoding):
+ """Return the object as a ustar header block. If it cannot be
+ represented this way, prepend a pax extended header sequence
+ with supplement information.
+ """
+ info["magic"] = POSIX_MAGIC
+ pax_headers = self.pax_headers.copy()
+
+ # Test string fields for values that exceed the field length or cannot
+ # be represented in ASCII encoding.
+ for name, hname, length in (
+ ("name", "path", LENGTH_NAME), ("linkname", "linkpath", LENGTH_LINK),
+ ("uname", "uname", 32), ("gname", "gname", 32)):
+
+ if hname in pax_headers:
+ # The pax header has priority.
+ continue
+
+ # Try to encode the string as ASCII.
+ try:
+ info[name].encode("ascii", "strict")
+ except UnicodeEncodeError:
+ pax_headers[hname] = info[name]
+ continue
+
+ if len(info[name]) > length:
+ pax_headers[hname] = info[name]
+
+ # Test number fields for values that exceed the field limit or values
+ # that like to be stored as float.
+ for name, digits in (("uid", 8), ("gid", 8), ("size", 12), ("mtime", 12)):
+ if name in pax_headers:
+ # The pax header has priority. Avoid overflow.
+ info[name] = 0
+ continue
+
+ val = info[name]
+ if not 0 <= val < 8 ** (digits - 1) or isinstance(val, float):
+ pax_headers[name] = str(val)
+ info[name] = 0
+
+ # Create a pax extended header if necessary.
+ if pax_headers:
+ buf = self._create_pax_generic_header(pax_headers, XHDTYPE, encoding)
+ else:
+ buf = b""
+
+ return buf + self._create_header(info, USTAR_FORMAT, "ascii", "replace")
+
+ @classmethod
+ def create_pax_global_header(cls, pax_headers):
+ """Return the object as a pax global header block sequence.
+ """
+ return cls._create_pax_generic_header(pax_headers, XGLTYPE, "utf8")
+
+ def _posix_split_name(self, name):
+ """Split a name longer than 100 chars into a prefix
+ and a name part.
+ """
+ prefix = name[:LENGTH_PREFIX + 1]
+ while prefix and prefix[-1] != "/":
+ prefix = prefix[:-1]
+
+ name = name[len(prefix):]
+ prefix = prefix[:-1]
+
+ if not prefix or len(name) > LENGTH_NAME:
+ raise ValueError("name is too long")
+ return prefix, name
+
+ @staticmethod
+ def _create_header(info, format, encoding, errors):
+ """Return a header block. info is a dictionary with file
+ information, format must be one of the *_FORMAT constants.
+ """
+ parts = [
+ stn(info.get("name", ""), 100, encoding, errors),
+ itn(info.get("mode", 0) & 0o7777, 8, format),
+ itn(info.get("uid", 0), 8, format),
+ itn(info.get("gid", 0), 8, format),
+ itn(info.get("size", 0), 12, format),
+ itn(info.get("mtime", 0), 12, format),
+ b" ", # checksum field
+ info.get("type", REGTYPE),
+ stn(info.get("linkname", ""), 100, encoding, errors),
+ info.get("magic", POSIX_MAGIC),
+ stn(info.get("uname", ""), 32, encoding, errors),
+ stn(info.get("gname", ""), 32, encoding, errors),
+ itn(info.get("devmajor", 0), 8, format),
+ itn(info.get("devminor", 0), 8, format),
+ stn(info.get("prefix", ""), 155, encoding, errors)
+ ]
+
+ buf = struct.pack("%ds" % BLOCKSIZE, b"".join(parts))
+ chksum = calc_chksums(buf[-BLOCKSIZE:])[0]
+ buf = buf[:-364] + ("%06o\0" % chksum).encode("ascii") + buf[-357:]
+ return buf
+
+ @staticmethod
+ def _create_payload(payload):
+ """Return the string payload filled with zero bytes
+ up to the next 512 byte border.
+ """
+ blocks, remainder = divmod(len(payload), BLOCKSIZE)
+ if remainder > 0:
+ payload += (BLOCKSIZE - remainder) * NUL
+ return payload
+
+ @classmethod
+ def _create_gnu_long_header(cls, name, type, encoding, errors):
+ """Return a GNUTYPE_LONGNAME or GNUTYPE_LONGLINK sequence
+ for name.
+ """
+ name = name.encode(encoding, errors) + NUL
+
+ info = {}
+ info["name"] = "././@LongLink"
+ info["type"] = type
+ info["size"] = len(name)
+ info["magic"] = GNU_MAGIC
+
+ # create extended header + name blocks.
+ return cls._create_header(info, USTAR_FORMAT, encoding, errors) + \
+ cls._create_payload(name)
+
+ @classmethod
+ def _create_pax_generic_header(cls, pax_headers, type, encoding):
+ """Return a POSIX.1-2008 extended or global header sequence
+ that contains a list of keyword, value pairs. The values
+ must be strings.
+ """
+ # Check if one of the fields contains surrogate characters and thereby
+ # forces hdrcharset=BINARY, see _proc_pax() for more information.
+ binary = False
+ for keyword, value in pax_headers.items():
+ try:
+ value.encode("utf8", "strict")
+ except UnicodeEncodeError:
+ binary = True
+ break
+
+ records = b""
+ if binary:
+ # Put the hdrcharset field at the beginning of the header.
+ records += b"21 hdrcharset=BINARY\n"
+
+ for keyword, value in pax_headers.items():
+ keyword = keyword.encode("utf8")
+ if binary:
+ # Try to restore the original byte representation of `value'.
+ # Needless to say, that the encoding must match the string.
+ value = value.encode(encoding, "surrogateescape")
+ else:
+ value = value.encode("utf8")
+
+ l = len(keyword) + len(value) + 3 # ' ' + '=' + '\n'
+ n = p = 0
+ while True:
+ n = l + len(str(p))
+ if n == p:
+ break
+ p = n
+ records += bytes(str(p), "ascii") + b" " + keyword + b"=" + value + b"\n"
+
+ # We use a hardcoded "././@PaxHeader" name like star does
+ # instead of the one that POSIX recommends.
+ info = {}
+ info["name"] = "././@PaxHeader"
+ info["type"] = type
+ info["size"] = len(records)
+ info["magic"] = POSIX_MAGIC
+
+ # Create pax header + record blocks.
+ return cls._create_header(info, USTAR_FORMAT, "ascii", "replace") + \
+ cls._create_payload(records)
+
+ @classmethod
+ def frombuf(cls, buf, encoding, errors):
+ """Construct a TarInfo object from a 512 byte bytes object.
+ """
+ if len(buf) == 0:
+ raise EmptyHeaderError("empty header")
+ if len(buf) != BLOCKSIZE:
+ raise TruncatedHeaderError("truncated header")
+ if buf.count(NUL) == BLOCKSIZE:
+ raise EOFHeaderError("end of file header")
+
+ chksum = nti(buf[148:156])
+ if chksum not in calc_chksums(buf):
+ raise InvalidHeaderError("bad checksum")
+
+ obj = cls()
+ obj.name = nts(buf[0:100], encoding, errors)
+ obj.mode = nti(buf[100:108])
+ obj.uid = nti(buf[108:116])
+ obj.gid = nti(buf[116:124])
+ obj.size = nti(buf[124:136])
+ obj.mtime = nti(buf[136:148])
+ obj.chksum = chksum
+ obj.type = buf[156:157]
+ obj.linkname = nts(buf[157:257], encoding, errors)
+ obj.uname = nts(buf[265:297], encoding, errors)
+ obj.gname = nts(buf[297:329], encoding, errors)
+ obj.devmajor = nti(buf[329:337])
+ obj.devminor = nti(buf[337:345])
+ prefix = nts(buf[345:500], encoding, errors)
+
+ # Old V7 tar format represents a directory as a regular
+ # file with a trailing slash.
+ if obj.type == AREGTYPE and obj.name.endswith("/"):
+ obj.type = DIRTYPE
+
+ # The old GNU sparse format occupies some of the unused
+ # space in the buffer for up to 4 sparse structures.
+ # Save the them for later processing in _proc_sparse().
+ if obj.type == GNUTYPE_SPARSE:
+ pos = 386
+ structs = []
+ for i in range(4):
+ try:
+ offset = nti(buf[pos:pos + 12])
+ numbytes = nti(buf[pos + 12:pos + 24])
+ except ValueError:
+ break
+ structs.append((offset, numbytes))
+ pos += 24
+ isextended = bool(buf[482])
+ origsize = nti(buf[483:495])
+ obj._sparse_structs = (structs, isextended, origsize)
+
+ # Remove redundant slashes from directories.
+ if obj.isdir():
+ obj.name = obj.name.rstrip("/")
+
+ # Reconstruct a ustar longname.
+ if prefix and obj.type not in GNU_TYPES:
+ obj.name = prefix + "/" + obj.name
+ return obj
+
+ @classmethod
+ def fromtarfile(cls, tarfile):
+ """Return the next TarInfo object from TarFile object
+ tarfile.
+ """
+ buf = tarfile.fileobj.read(BLOCKSIZE)
+ obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
+ obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
+ return obj._proc_member(tarfile)
+
+ #--------------------------------------------------------------------------
+ # The following are methods that are called depending on the type of a
+ # member. The entry point is _proc_member() which can be overridden in a
+ # subclass to add custom _proc_*() methods. A _proc_*() method MUST
+ # implement the following
+ # operations:
+ # 1. Set self.offset_data to the position where the data blocks begin,
+ # if there is data that follows.
+ # 2. Set tarfile.offset to the position where the next member's header will
+ # begin.
+ # 3. Return self or another valid TarInfo object.
+ def _proc_member(self, tarfile):
+ """Choose the right processing method depending on
+ the type and call it.
+ """
+ if self.type in (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK):
+ return self._proc_gnulong(tarfile)
+ elif self.type == GNUTYPE_SPARSE:
+ return self._proc_sparse(tarfile)
+ elif self.type in (XHDTYPE, XGLTYPE, SOLARIS_XHDTYPE):
+ return self._proc_pax(tarfile)
+ else:
+ return self._proc_builtin(tarfile)
+
+ def _proc_builtin(self, tarfile):
+ """Process a builtin type or an unknown type which
+ will be treated as a regular file.
+ """
+ self.offset_data = tarfile.fileobj.tell()
+ offset = self.offset_data
+ if self.isreg() or self.type not in SUPPORTED_TYPES:
+ # Skip the following data blocks.
+ offset += self._block(self.size)
+ tarfile.offset = offset
+
+ # Patch the TarInfo object with saved global
+ # header information.
+ self._apply_pax_info(tarfile.pax_headers, tarfile.encoding, tarfile.errors)
+
+ return self
+
+ def _proc_gnulong(self, tarfile):
+ """Process the blocks that hold a GNU longname
+ or longlink member.
+ """
+ buf = tarfile.fileobj.read(self._block(self.size))
+
+ # Fetch the next header and process it.
+ try:
+ next = self.fromtarfile(tarfile)
+ except HeaderError:
+ raise SubsequentHeaderError("missing or bad subsequent header")
+
+ # Patch the TarInfo object from the next header with
+ # the longname information.
+ next.offset = self.offset
+ if self.type == GNUTYPE_LONGNAME:
+ next.name = nts(buf, tarfile.encoding, tarfile.errors)
+ elif self.type == GNUTYPE_LONGLINK:
+ next.linkname = nts(buf, tarfile.encoding, tarfile.errors)
+
+ return next
+
+ def _proc_sparse(self, tarfile):
+ """Process a GNU sparse header plus extra headers.
+ """
+ # We already collected some sparse structures in frombuf().
+ structs, isextended, origsize = self._sparse_structs
+ del self._sparse_structs
+
+ # Collect sparse structures from extended header blocks.
+ while isextended:
+ buf = tarfile.fileobj.read(BLOCKSIZE)
+ pos = 0
+ for i in range(21):
+ try:
+ offset = nti(buf[pos:pos + 12])
+ numbytes = nti(buf[pos + 12:pos + 24])
+ except ValueError:
+ break
+ if offset and numbytes:
+ structs.append((offset, numbytes))
+ pos += 24
+ isextended = bool(buf[504])
+ self.sparse = structs
+
+ self.offset_data = tarfile.fileobj.tell()
+ tarfile.offset = self.offset_data + self._block(self.size)
+ self.size = origsize
+ return self
+
+ def _proc_pax(self, tarfile):
+ """Process an extended or global header as described in
+ POSIX.1-2008.
+ """
+ # Read the header information.
+ buf = tarfile.fileobj.read(self._block(self.size))
+
+ # A pax header stores supplemental information for either
+ # the following file (extended) or all following files
+ # (global).
+ if self.type == XGLTYPE:
+ pax_headers = tarfile.pax_headers
+ else:
+ pax_headers = tarfile.pax_headers.copy()
+
+ # Check if the pax header contains a hdrcharset field. This tells us
+ # the encoding of the path, linkpath, uname and gname fields. Normally,
+ # these fields are UTF-8 encoded but since POSIX.1-2008 tar
+ # implementations are allowed to store them as raw binary strings if
+ # the translation to UTF-8 fails.
+ match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf)
+ if match is not None:
+ pax_headers["hdrcharset"] = match.group(1).decode("utf8")
+
+ # For the time being, we don't care about anything other than "BINARY".
+ # The only other value that is currently allowed by the standard is
+ # "ISO-IR 10646 2000 UTF-8" in other words UTF-8.
+ hdrcharset = pax_headers.get("hdrcharset")
+ if hdrcharset == "BINARY":
+ encoding = tarfile.encoding
+ else:
+ encoding = "utf8"
+
+ # Parse pax header information. A record looks like that:
+ # "%d %s=%s\n" % (length, keyword, value). length is the size
+ # of the complete record including the length field itself and
+ # the newline. keyword and value are both UTF-8 encoded strings.
+ regex = re.compile(br"(\d+) ([^=]+)=")
+ pos = 0
+ while True:
+ match = regex.match(buf, pos)
+ if not match:
+ break
+
+ length, keyword = match.groups()
+ length = int(length)
+ value = buf[match.end(2) + 1:match.start(1) + length - 1]
+
+ # Normally, we could just use "utf8" as the encoding and "strict"
+ # as the error handler, but we better not take the risk. For
+ # example, GNU tar <= 1.23 is known to store filenames it cannot
+ # translate to UTF-8 as raw strings (unfortunately without a
+ # hdrcharset=BINARY header).
+ # We first try the strict standard encoding, and if that fails we
+ # fall back on the user's encoding and error handler.
+ keyword = self._decode_pax_field(keyword, "utf8", "utf8",
+ tarfile.errors)
+ if keyword in PAX_NAME_FIELDS:
+ value = self._decode_pax_field(value, encoding, tarfile.encoding,
+ tarfile.errors)
+ else:
+ value = self._decode_pax_field(value, "utf8", "utf8",
+ tarfile.errors)
+
+ pax_headers[keyword] = value
+ pos += length
+
+ # Fetch the next header.
+ try:
+ next = self.fromtarfile(tarfile)
+ except HeaderError:
+ raise SubsequentHeaderError("missing or bad subsequent header")
+
+ # Process GNU sparse information.
+ if "GNU.sparse.map" in pax_headers:
+ # GNU extended sparse format version 0.1.
+ self._proc_gnusparse_01(next, pax_headers)
+
+ elif "GNU.sparse.size" in pax_headers:
+ # GNU extended sparse format version 0.0.
+ self._proc_gnusparse_00(next, pax_headers, buf)
+
+ elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0":
+ # GNU extended sparse format version 1.0.
+ self._proc_gnusparse_10(next, pax_headers, tarfile)
+
+ if self.type in (XHDTYPE, SOLARIS_XHDTYPE):
+ # Patch the TarInfo object with the extended header info.
+ next._apply_pax_info(pax_headers, tarfile.encoding, tarfile.errors)
+ next.offset = self.offset
+
+ if "size" in pax_headers:
+ # If the extended header replaces the size field,
+ # we need to recalculate the offset where the next
+ # header starts.
+ offset = next.offset_data
+ if next.isreg() or next.type not in SUPPORTED_TYPES:
+ offset += next._block(next.size)
+ tarfile.offset = offset
+
+ return next
+
+ def _proc_gnusparse_00(self, next, pax_headers, buf):
+ """Process a GNU tar extended sparse header, version 0.0.
+ """
+ offsets = []
+ for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf):
+ offsets.append(int(match.group(1)))
+ numbytes = []
+ for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf):
+ numbytes.append(int(match.group(1)))
+ next.sparse = list(zip(offsets, numbytes))
+
+ def _proc_gnusparse_01(self, next, pax_headers):
+ """Process a GNU tar extended sparse header, version 0.1.
+ """
+ sparse = [int(x) for x in pax_headers["GNU.sparse.map"].split(",")]
+ next.sparse = list(zip(sparse[::2], sparse[1::2]))
+
+ def _proc_gnusparse_10(self, next, pax_headers, tarfile):
+ """Process a GNU tar extended sparse header, version 1.0.
+ """
+ fields = None
+ sparse = []
+ buf = tarfile.fileobj.read(BLOCKSIZE)
+ fields, buf = buf.split(b"\n", 1)
+ fields = int(fields)
+ while len(sparse) < fields * 2:
+ if b"\n" not in buf:
+ buf += tarfile.fileobj.read(BLOCKSIZE)
+ number, buf = buf.split(b"\n", 1)
+ sparse.append(int(number))
+ next.offset_data = tarfile.fileobj.tell()
+ next.sparse = list(zip(sparse[::2], sparse[1::2]))
+
+ def _apply_pax_info(self, pax_headers, encoding, errors):
+ """Replace fields with supplemental information from a previous
+ pax extended or global header.
+ """
+ for keyword, value in pax_headers.items():
+ if keyword == "GNU.sparse.name":
+ setattr(self, "path", value)
+ elif keyword == "GNU.sparse.size":
+ setattr(self, "size", int(value))
+ elif keyword == "GNU.sparse.realsize":
+ setattr(self, "size", int(value))
+ elif keyword in PAX_FIELDS:
+ if keyword in PAX_NUMBER_FIELDS:
+ try:
+ value = PAX_NUMBER_FIELDS[keyword](value)
+ except ValueError:
+ value = 0
+ if keyword == "path":
+ value = value.rstrip("/")
+ setattr(self, keyword, value)
+
+ self.pax_headers = pax_headers.copy()
+
+ def _decode_pax_field(self, value, encoding, fallback_encoding, fallback_errors):
+ """Decode a single field from a pax record.
+ """
+ try:
+ return value.decode(encoding, "strict")
+ except UnicodeDecodeError:
+ return value.decode(fallback_encoding, fallback_errors)
+
+ def _block(self, count):
+ """Round up a byte count by BLOCKSIZE and return it,
+ e.g. _block(834) => 1024.
+ """
+ blocks, remainder = divmod(count, BLOCKSIZE)
+ if remainder:
+ blocks += 1
+ return blocks * BLOCKSIZE
+
+ def isreg(self):
+ return self.type in REGULAR_TYPES
+ def isfile(self):
+ return self.isreg()
+ def isdir(self):
+ return self.type == DIRTYPE
+ def issym(self):
+ return self.type == SYMTYPE
+ def islnk(self):
+ return self.type == LNKTYPE
+ def ischr(self):
+ return self.type == CHRTYPE
+ def isblk(self):
+ return self.type == BLKTYPE
+ def isfifo(self):
+ return self.type == FIFOTYPE
+ def issparse(self):
+ return self.sparse is not None
+ def isdev(self):
+ return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE)
+# class TarInfo
+
+class TarFile(object):
+ """The TarFile Class provides an interface to tar archives.
+ """
+
+ debug = 0 # May be set from 0 (no msgs) to 3 (all msgs)
+
+ dereference = False # If true, add content of linked file to the
+ # tar file, else the link.
+
+ ignore_zeros = False # If true, skips empty or invalid blocks and
+ # continues processing.
+
+ errorlevel = 1 # If 0, fatal errors only appear in debug
+ # messages (if debug >= 0). If > 0, errors
+ # are passed to the caller as exceptions.
+
+ format = DEFAULT_FORMAT # The format to use when creating an archive.
+
+ encoding = ENCODING # Encoding for 8-bit character strings.
+
+ errors = None # Error handler for unicode conversion.
+
+ tarinfo = TarInfo # The default TarInfo class to use.
+
+ fileobject = ExFileObject # The default ExFileObject class to use.
+
+ def __init__(self, name=None, mode="r", fileobj=None, format=None,
+ tarinfo=None, dereference=None, ignore_zeros=None, encoding=None,
+ errors="surrogateescape", pax_headers=None, debug=None, errorlevel=None):
+ """Open an (uncompressed) tar archive `name'. `mode' is either 'r' to
+ read from an existing archive, 'a' to append data to an existing
+ file or 'w' to create a new file overwriting an existing one. `mode'
+ defaults to 'r'.
+ If `fileobj' is given, it is used for reading or writing data. If it
+ can be determined, `mode' is overridden by `fileobj's mode.
+ `fileobj' is not closed, when TarFile is closed.
+ """
+ if len(mode) > 1 or mode not in "raw":
+ raise ValueError("mode must be 'r', 'a' or 'w'")
+ self.mode = mode
+ self._mode = {"r": "rb", "a": "r+b", "w": "wb"}[mode]
+
+ if not fileobj:
+ if self.mode == "a" and not os.path.exists(name):
+ # Create nonexistent files in append mode.
+ self.mode = "w"
+ self._mode = "wb"
+ fileobj = bltn_open(name, self._mode)
+ self._extfileobj = False
+ else:
+ if name is None and hasattr(fileobj, "name"):
+ name = fileobj.name
+ if hasattr(fileobj, "mode"):
+ self._mode = fileobj.mode
+ self._extfileobj = True
+ self.name = os.path.abspath(name) if name else None
+ self.fileobj = fileobj
+
+ # Init attributes.
+ if format is not None:
+ self.format = format
+ if tarinfo is not None:
+ self.tarinfo = tarinfo
+ if dereference is not None:
+ self.dereference = dereference
+ if ignore_zeros is not None:
+ self.ignore_zeros = ignore_zeros
+ if encoding is not None:
+ self.encoding = encoding
+ self.errors = errors
+
+ if pax_headers is not None and self.format == PAX_FORMAT:
+ self.pax_headers = pax_headers
+ else:
+ self.pax_headers = {}
+
+ if debug is not None:
+ self.debug = debug
+ if errorlevel is not None:
+ self.errorlevel = errorlevel
+
+ # Init datastructures.
+ self.closed = False
+ self.members = [] # list of members as TarInfo objects
+ self._loaded = False # flag if all members have been read
+ self.offset = self.fileobj.tell()
+ # current position in the archive file
+ self.inodes = {} # dictionary caching the inodes of
+ # archive members already added
+
+ try:
+ if self.mode == "r":
+ self.firstmember = None
+ self.firstmember = self.next()
+
+ if self.mode == "a":
+ # Move to the end of the archive,
+ # before the first empty block.
+ while True:
+ self.fileobj.seek(self.offset)
+ try:
+ tarinfo = self.tarinfo.fromtarfile(self)
+ self.members.append(tarinfo)
+ except EOFHeaderError:
+ self.fileobj.seek(self.offset)
+ break
+ except HeaderError as e:
+ raise ReadError(str(e))
+
+ if self.mode in "aw":
+ self._loaded = True
+
+ if self.pax_headers:
+ buf = self.tarinfo.create_pax_global_header(self.pax_headers.copy())
+ self.fileobj.write(buf)
+ self.offset += len(buf)
+ except:
+ if not self._extfileobj:
+ self.fileobj.close()
+ self.closed = True
+ raise
+
+ #--------------------------------------------------------------------------
+ # Below are the classmethods which act as alternate constructors to the
+ # TarFile class. The open() method is the only one that is needed for
+ # public use; it is the "super"-constructor and is able to select an
+ # adequate "sub"-constructor for a particular compression using the mapping
+ # from OPEN_METH.
+ #
+ # This concept allows one to subclass TarFile without losing the comfort of
+ # the super-constructor. A sub-constructor is registered and made available
+ # by adding it to the mapping in OPEN_METH.
+
+ @classmethod
+ def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE, **kwargs):
+ """Open a tar archive for reading, writing or appending. Return
+ an appropriate TarFile class.
+
+ mode:
+ 'r' or 'r:*' open for reading with transparent compression
+ 'r:' open for reading exclusively uncompressed
+ 'r:gz' open for reading with gzip compression
+ 'r:bz2' open for reading with bzip2 compression
+ 'a' or 'a:' open for appending, creating the file if necessary
+ 'w' or 'w:' open for writing without compression
+ 'w:gz' open for writing with gzip compression
+ 'w:bz2' open for writing with bzip2 compression
+
+ 'r|*' open a stream of tar blocks with transparent compression
+ 'r|' open an uncompressed stream of tar blocks for reading
+ 'r|gz' open a gzip compressed stream of tar blocks
+ 'r|bz2' open a bzip2 compressed stream of tar blocks
+ 'w|' open an uncompressed stream for writing
+ 'w|gz' open a gzip compressed stream for writing
+ 'w|bz2' open a bzip2 compressed stream for writing
+ """
+
+ if not name and not fileobj:
+ raise ValueError("nothing to open")
+
+ if mode in ("r", "r:*"):
+ # Find out which *open() is appropriate for opening the file.
+ for comptype in cls.OPEN_METH:
+ func = getattr(cls, cls.OPEN_METH[comptype])
+ if fileobj is not None:
+ saved_pos = fileobj.tell()
+ try:
+ return func(name, "r", fileobj, **kwargs)
+ except (ReadError, CompressionError) as e:
+ if fileobj is not None:
+ fileobj.seek(saved_pos)
+ continue
+ raise ReadError("file could not be opened successfully")
+
+ elif ":" in mode:
+ filemode, comptype = mode.split(":", 1)
+ filemode = filemode or "r"
+ comptype = comptype or "tar"
+
+ # Select the *open() function according to
+ # given compression.
+ if comptype in cls.OPEN_METH:
+ func = getattr(cls, cls.OPEN_METH[comptype])
+ else:
+ raise CompressionError("unknown compression type %r" % comptype)
+ return func(name, filemode, fileobj, **kwargs)
+
+ elif "|" in mode:
+ filemode, comptype = mode.split("|", 1)
+ filemode = filemode or "r"
+ comptype = comptype or "tar"
+
+ if filemode not in "rw":
+ raise ValueError("mode must be 'r' or 'w'")
+
+ stream = _Stream(name, filemode, comptype, fileobj, bufsize)
+ try:
+ t = cls(name, filemode, stream, **kwargs)
+ except:
+ stream.close()
+ raise
+ t._extfileobj = False
+ return t
+
+ elif mode in "aw":
+ return cls.taropen(name, mode, fileobj, **kwargs)
+
+ raise ValueError("undiscernible mode")
+
+ @classmethod
+ def taropen(cls, name, mode="r", fileobj=None, **kwargs):
+ """Open uncompressed tar archive name for reading or writing.
+ """
+ if len(mode) > 1 or mode not in "raw":
+ raise ValueError("mode must be 'r', 'a' or 'w'")
+ return cls(name, mode, fileobj, **kwargs)
+
+ @classmethod
+ def gzopen(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
+ """Open gzip compressed tar archive name for reading or writing.
+ Appending is not allowed.
+ """
+ if len(mode) > 1 or mode not in "rw":
+ raise ValueError("mode must be 'r' or 'w'")
+
+ try:
+ import gzip
+ gzip.GzipFile
+ except (ImportError, AttributeError):
+ raise CompressionError("gzip module is not available")
+
+ extfileobj = fileobj is not None
+ try:
+ fileobj = gzip.GzipFile(name, mode + "b", compresslevel, fileobj)
+ t = cls.taropen(name, mode, fileobj, **kwargs)
+ except IOError:
+ if not extfileobj and fileobj is not None:
+ fileobj.close()
+ if fileobj is None:
+ raise
+ raise ReadError("not a gzip file")
+ except:
+ if not extfileobj and fileobj is not None:
+ fileobj.close()
+ raise
+ t._extfileobj = extfileobj
+ return t
+
+ @classmethod
+ def bz2open(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
+ """Open bzip2 compressed tar archive name for reading or writing.
+ Appending is not allowed.
+ """
+ if len(mode) > 1 or mode not in "rw":
+ raise ValueError("mode must be 'r' or 'w'.")
+
+ try:
+ import bz2
+ except ImportError:
+ raise CompressionError("bz2 module is not available")
+
+ if fileobj is not None:
+ fileobj = _BZ2Proxy(fileobj, mode)
+ else:
+ fileobj = bz2.BZ2File(name, mode, compresslevel=compresslevel)
+
+ try:
+ t = cls.taropen(name, mode, fileobj, **kwargs)
+ except (IOError, EOFError):
+ fileobj.close()
+ raise ReadError("not a bzip2 file")
+ t._extfileobj = False
+ return t
+
+ # All *open() methods are registered here.
+ OPEN_METH = {
+ "tar": "taropen", # uncompressed tar
+ "gz": "gzopen", # gzip compressed tar
+ "bz2": "bz2open" # bzip2 compressed tar
+ }
+
+ #--------------------------------------------------------------------------
+ # The public methods which TarFile provides:
+
+ def close(self):
+ """Close the TarFile. In write-mode, two finishing zero blocks are
+ appended to the archive.
+ """
+ if self.closed:
+ return
+
+ if self.mode in "aw":
+ self.fileobj.write(NUL * (BLOCKSIZE * 2))
+ self.offset += (BLOCKSIZE * 2)
+ # fill up the end with zero-blocks
+ # (like option -b20 for tar does)
+ blocks, remainder = divmod(self.offset, RECORDSIZE)
+ if remainder > 0:
+ self.fileobj.write(NUL * (RECORDSIZE - remainder))
+
+ if not self._extfileobj:
+ self.fileobj.close()
+ self.closed = True
+
+ def getmember(self, name):
+ """Return a TarInfo object for member `name'. If `name' can not be
+ found in the archive, KeyError is raised. If a member occurs more
+ than once in the archive, its last occurrence is assumed to be the
+ most up-to-date version.
+ """
+ tarinfo = self._getmember(name)
+ if tarinfo is None:
+ raise KeyError("filename %r not found" % name)
+ return tarinfo
+
+ def getmembers(self):
+ """Return the members of the archive as a list of TarInfo objects. The
+ list has the same order as the members in the archive.
+ """
+ self._check()
+ if not self._loaded: # if we want to obtain a list of
+ self._load() # all members, we first have to
+ # scan the whole archive.
+ return self.members
+
+ def getnames(self):
+ """Return the members of the archive as a list of their names. It has
+ the same order as the list returned by getmembers().
+ """
+ return [tarinfo.name for tarinfo in self.getmembers()]
+
+ def gettarinfo(self, name=None, arcname=None, fileobj=None):
+ """Create a TarInfo object for either the file `name' or the file
+ object `fileobj' (using os.fstat on its file descriptor). You can
+ modify some of the TarInfo's attributes before you add it using
+ addfile(). If given, `arcname' specifies an alternative name for the
+ file in the archive.
+ """
+ self._check("aw")
+
+ # When fileobj is given, replace name by
+ # fileobj's real name.
+ if fileobj is not None:
+ name = fileobj.name
+
+ # Building the name of the member in the archive.
+ # Backward slashes are converted to forward slashes,
+ # Absolute paths are turned to relative paths.
+ if arcname is None:
+ arcname = name
+ drv, arcname = os.path.splitdrive(arcname)
+ arcname = arcname.replace(os.sep, "/")
+ arcname = arcname.lstrip("/")
+
+ # Now, fill the TarInfo object with
+ # information specific for the file.
+ tarinfo = self.tarinfo()
+ tarinfo.tarfile = self
+
+ # Use os.stat or os.lstat, depending on platform
+ # and if symlinks shall be resolved.
+ if fileobj is None:
+ if hasattr(os, "lstat") and not self.dereference:
+ statres = os.lstat(name)
+ else:
+ statres = os.stat(name)
+ else:
+ statres = os.fstat(fileobj.fileno())
+ linkname = ""
+
+ stmd = statres.st_mode
+ if stat.S_ISREG(stmd):
+ inode = (statres.st_ino, statres.st_dev)
+ if not self.dereference and statres.st_nlink > 1 and \
+ inode in self.inodes and arcname != self.inodes[inode]:
+ # Is it a hardlink to an already
+ # archived file?
+ type = LNKTYPE
+ linkname = self.inodes[inode]
+ else:
+ # The inode is added only if its valid.
+ # For win32 it is always 0.
+ type = REGTYPE
+ if inode[0]:
+ self.inodes[inode] = arcname
+ elif stat.S_ISDIR(stmd):
+ type = DIRTYPE
+ elif stat.S_ISFIFO(stmd):
+ type = FIFOTYPE
+ elif stat.S_ISLNK(stmd):
+ type = SYMTYPE
+ linkname = os.readlink(name)
+ elif stat.S_ISCHR(stmd):
+ type = CHRTYPE
+ elif stat.S_ISBLK(stmd):
+ type = BLKTYPE
+ else:
+ return None
+
+ # Fill the TarInfo object with all
+ # information we can get.
+ tarinfo.name = arcname
+ tarinfo.mode = stmd
+ tarinfo.uid = statres.st_uid
+ tarinfo.gid = statres.st_gid
+ if type == REGTYPE:
+ tarinfo.size = statres.st_size
+ else:
+ tarinfo.size = 0
+ tarinfo.mtime = statres.st_mtime
+ tarinfo.type = type
+ tarinfo.linkname = linkname
+ if pwd:
+ try:
+ tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0]
+ except KeyError:
+ pass
+ if grp:
+ try:
+ tarinfo.gname = grp.getgrgid(tarinfo.gid)[0]
+ except KeyError:
+ pass
+
+ if type in (CHRTYPE, BLKTYPE):
+ if hasattr(os, "major") and hasattr(os, "minor"):
+ tarinfo.devmajor = os.major(statres.st_rdev)
+ tarinfo.devminor = os.minor(statres.st_rdev)
+ return tarinfo
+
+ def list(self, verbose=True):
+ """Print a table of contents to sys.stdout. If `verbose' is False, only
+ the names of the members are printed. If it is True, an `ls -l'-like
+ output is produced.
+ """
+ self._check()
+
+ for tarinfo in self:
+ if verbose:
+ print(filemode(tarinfo.mode), end=' ')
+ print("%s/%s" % (tarinfo.uname or tarinfo.uid,
+ tarinfo.gname or tarinfo.gid), end=' ')
+ if tarinfo.ischr() or tarinfo.isblk():
+ print("%10s" % ("%d,%d" \
+ % (tarinfo.devmajor, tarinfo.devminor)), end=' ')
+ else:
+ print("%10d" % tarinfo.size, end=' ')
+ print("%d-%02d-%02d %02d:%02d:%02d" \
+ % time.localtime(tarinfo.mtime)[:6], end=' ')
+
+ print(tarinfo.name + ("/" if tarinfo.isdir() else ""), end=' ')
+
+ if verbose:
+ if tarinfo.issym():
+ print("->", tarinfo.linkname, end=' ')
+ if tarinfo.islnk():
+ print("link to", tarinfo.linkname, end=' ')
+ print()
+
+ def add(self, name, arcname=None, recursive=True, exclude=None, filter=None):
+ """Add the file `name' to the archive. `name' may be any type of file
+ (directory, fifo, symbolic link, etc.). If given, `arcname'
+ specifies an alternative name for the file in the archive.
+ Directories are added recursively by default. This can be avoided by
+ setting `recursive' to False. `exclude' is a function that should
+ return True for each filename to be excluded. `filter' is a function
+ that expects a TarInfo object argument and returns the changed
+ TarInfo object, if it returns None the TarInfo object will be
+ excluded from the archive.
+ """
+ self._check("aw")
+
+ if arcname is None:
+ arcname = name
+
+ # Exclude pathnames.
+ if exclude is not None:
+ import warnings
+ warnings.warn("use the filter argument instead",
+ DeprecationWarning, 2)
+ if exclude(name):
+ self._dbg(2, "tarfile: Excluded %r" % name)
+ return
+
+ # Skip if somebody tries to archive the archive...
+ if self.name is not None and os.path.abspath(name) == self.name:
+ self._dbg(2, "tarfile: Skipped %r" % name)
+ return
+
+ self._dbg(1, name)
+
+ # Create a TarInfo object from the file.
+ tarinfo = self.gettarinfo(name, arcname)
+
+ if tarinfo is None:
+ self._dbg(1, "tarfile: Unsupported type %r" % name)
+ return
+
+ # Change or exclude the TarInfo object.
+ if filter is not None:
+ tarinfo = filter(tarinfo)
+ if tarinfo is None:
+ self._dbg(2, "tarfile: Excluded %r" % name)
+ return
+
+ # Append the tar header and data to the archive.
+ if tarinfo.isreg():
+ f = bltn_open(name, "rb")
+ self.addfile(tarinfo, f)
+ f.close()
+
+ elif tarinfo.isdir():
+ self.addfile(tarinfo)
+ if recursive:
+ for f in os.listdir(name):
+ self.add(os.path.join(name, f), os.path.join(arcname, f),
+ recursive, exclude, filter=filter)
+
+ else:
+ self.addfile(tarinfo)
+
+ def addfile(self, tarinfo, fileobj=None):
+ """Add the TarInfo object `tarinfo' to the archive. If `fileobj' is
+ given, tarinfo.size bytes are read from it and added to the archive.
+ You can create TarInfo objects using gettarinfo().
+ On Windows platforms, `fileobj' should always be opened with mode
+ 'rb' to avoid irritation about the file size.
+ """
+ self._check("aw")
+
+ tarinfo = copy.copy(tarinfo)
+
+ buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
+ self.fileobj.write(buf)
+ self.offset += len(buf)
+
+ # If there's data to follow, append it.
+ if fileobj is not None:
+ copyfileobj(fileobj, self.fileobj, tarinfo.size)
+ blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
+ if remainder > 0:
+ self.fileobj.write(NUL * (BLOCKSIZE - remainder))
+ blocks += 1
+ self.offset += blocks * BLOCKSIZE
+
+ self.members.append(tarinfo)
+
+ def extractall(self, path=".", members=None):
+ """Extract all members from the archive to the current working
+ directory and set owner, modification time and permissions on
+ directories afterwards. `path' specifies a different directory
+ to extract to. `members' is optional and must be a subset of the
+ list returned by getmembers().
+ """
+ directories = []
+
+ if members is None:
+ members = self
+
+ for tarinfo in members:
+ if tarinfo.isdir():
+ # Extract directories with a safe mode.
+ directories.append(tarinfo)
+ tarinfo = copy.copy(tarinfo)
+ tarinfo.mode = 0o700
+ # Do not set_attrs directories, as we will do that further down
+ self.extract(tarinfo, path, set_attrs=not tarinfo.isdir())
+
+ # Reverse sort directories.
+ directories.sort(key=lambda a: a.name)
+ directories.reverse()
+
+ # Set correct owner, mtime and filemode on directories.
+ for tarinfo in directories:
+ dirpath = os.path.join(path, tarinfo.name)
+ try:
+ self.chown(tarinfo, dirpath)
+ self.utime(tarinfo, dirpath)
+ self.chmod(tarinfo, dirpath)
+ except ExtractError as e:
+ if self.errorlevel > 1:
+ raise
+ else:
+ self._dbg(1, "tarfile: %s" % e)
+
+ def extract(self, member, path="", set_attrs=True):
+ """Extract a member from the archive to the current working directory,
+ using its full name. Its file information is extracted as accurately
+ as possible. `member' may be a filename or a TarInfo object. You can
+ specify a different directory using `path'. File attributes (owner,
+ mtime, mode) are set unless `set_attrs' is False.
+ """
+ self._check("r")
+
+ if isinstance(member, str):
+ tarinfo = self.getmember(member)
+ else:
+ tarinfo = member
+
+ # Prepare the link target for makelink().
+ if tarinfo.islnk():
+ tarinfo._link_target = os.path.join(path, tarinfo.linkname)
+
+ try:
+ self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
+ set_attrs=set_attrs)
+ except EnvironmentError as e:
+ if self.errorlevel > 0:
+ raise
+ else:
+ if e.filename is None:
+ self._dbg(1, "tarfile: %s" % e.strerror)
+ else:
+ self._dbg(1, "tarfile: %s %r" % (e.strerror, e.filename))
+ except ExtractError as e:
+ if self.errorlevel > 1:
+ raise
+ else:
+ self._dbg(1, "tarfile: %s" % e)
+
+ def extractfile(self, member):
+ """Extract a member from the archive as a file object. `member' may be
+ a filename or a TarInfo object. If `member' is a regular file, a
+ file-like object is returned. If `member' is a link, a file-like
+ object is constructed from the link's target. If `member' is none of
+ the above, None is returned.
+ The file-like object is read-only and provides the following
+ methods: read(), readline(), readlines(), seek() and tell()
+ """
+ self._check("r")
+
+ if isinstance(member, str):
+ tarinfo = self.getmember(member)
+ else:
+ tarinfo = member
+
+ if tarinfo.isreg():
+ return self.fileobject(self, tarinfo)
+
+ elif tarinfo.type not in SUPPORTED_TYPES:
+ # If a member's type is unknown, it is treated as a
+ # regular file.
+ return self.fileobject(self, tarinfo)
+
+ elif tarinfo.islnk() or tarinfo.issym():
+ if isinstance(self.fileobj, _Stream):
+ # A small but ugly workaround for the case that someone tries
+ # to extract a (sym)link as a file-object from a non-seekable
+ # stream of tar blocks.
+ raise StreamError("cannot extract (sym)link as file object")
+ else:
+ # A (sym)link's file object is its target's file object.
+ return self.extractfile(self._find_link_target(tarinfo))
+ else:
+ # If there's no data associated with the member (directory, chrdev,
+ # blkdev, etc.), return None instead of a file object.
+ return None
+
+ def _extract_member(self, tarinfo, targetpath, set_attrs=True):
+ """Extract the TarInfo object tarinfo to a physical
+ file called targetpath.
+ """
+ # Fetch the TarInfo object for the given name
+ # and build the destination pathname, replacing
+ # forward slashes to platform specific separators.
+ targetpath = targetpath.rstrip("/")
+ targetpath = targetpath.replace("/", os.sep)
+
+ # Create all upper directories.
+ upperdirs = os.path.dirname(targetpath)
+ if upperdirs and not os.path.exists(upperdirs):
+ # Create directories that are not part of the archive with
+ # default permissions.
+ os.makedirs(upperdirs)
+
+ if tarinfo.islnk() or tarinfo.issym():
+ self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname))
+ else:
+ self._dbg(1, tarinfo.name)
+
+ if tarinfo.isreg():
+ self.makefile(tarinfo, targetpath)
+ elif tarinfo.isdir():
+ self.makedir(tarinfo, targetpath)
+ elif tarinfo.isfifo():
+ self.makefifo(tarinfo, targetpath)
+ elif tarinfo.ischr() or tarinfo.isblk():
+ self.makedev(tarinfo, targetpath)
+ elif tarinfo.islnk() or tarinfo.issym():
+ self.makelink(tarinfo, targetpath)
+ elif tarinfo.type not in SUPPORTED_TYPES:
+ self.makeunknown(tarinfo, targetpath)
+ else:
+ self.makefile(tarinfo, targetpath)
+
+ if set_attrs:
+ self.chown(tarinfo, targetpath)
+ if not tarinfo.issym():
+ self.chmod(tarinfo, targetpath)
+ self.utime(tarinfo, targetpath)
+
+ #--------------------------------------------------------------------------
+ # Below are the different file methods. They are called via
+ # _extract_member() when extract() is called. They can be replaced in a
+ # subclass to implement other functionality.
+
+ def makedir(self, tarinfo, targetpath):
+ """Make a directory called targetpath.
+ """
+ try:
+ # Use a safe mode for the directory, the real mode is set
+ # later in _extract_member().
+ os.mkdir(targetpath, 0o700)
+ except EnvironmentError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ def makefile(self, tarinfo, targetpath):
+ """Make a file called targetpath.
+ """
+ source = self.fileobj
+ source.seek(tarinfo.offset_data)
+ target = bltn_open(targetpath, "wb")
+ if tarinfo.sparse is not None:
+ for offset, size in tarinfo.sparse:
+ target.seek(offset)
+ copyfileobj(source, target, size)
+ else:
+ copyfileobj(source, target, tarinfo.size)
+ target.seek(tarinfo.size)
+ target.truncate()
+ target.close()
+
+ def makeunknown(self, tarinfo, targetpath):
+ """Make a file from a TarInfo object with an unknown type
+ at targetpath.
+ """
+ self.makefile(tarinfo, targetpath)
+ self._dbg(1, "tarfile: Unknown file type %r, " \
+ "extracted as regular file." % tarinfo.type)
+
+ def makefifo(self, tarinfo, targetpath):
+ """Make a fifo called targetpath.
+ """
+ if hasattr(os, "mkfifo"):
+ os.mkfifo(targetpath)
+ else:
+ raise ExtractError("fifo not supported by system")
+
+ def makedev(self, tarinfo, targetpath):
+ """Make a character or block device called targetpath.
+ """
+ if not hasattr(os, "mknod") or not hasattr(os, "makedev"):
+ raise ExtractError("special devices not supported by system")
+
+ mode = tarinfo.mode
+ if tarinfo.isblk():
+ mode |= stat.S_IFBLK
+ else:
+ mode |= stat.S_IFCHR
+
+ os.mknod(targetpath, mode,
+ os.makedev(tarinfo.devmajor, tarinfo.devminor))
+
+ def makelink(self, tarinfo, targetpath):
+ """Make a (symbolic) link called targetpath. If it cannot be created
+ (platform limitation), we try to make a copy of the referenced file
+ instead of a link.
+ """
+ try:
+ # For systems that support symbolic and hard links.
+ if tarinfo.issym():
+ os.symlink(tarinfo.linkname, targetpath)
+ else:
+ # See extract().
+ if os.path.exists(tarinfo._link_target):
+ os.link(tarinfo._link_target, targetpath)
+ else:
+ self._extract_member(self._find_link_target(tarinfo),
+ targetpath)
+ except symlink_exception:
+ if tarinfo.issym():
+ linkpath = os.path.join(os.path.dirname(tarinfo.name),
+ tarinfo.linkname)
+ else:
+ linkpath = tarinfo.linkname
+ else:
+ try:
+ self._extract_member(self._find_link_target(tarinfo),
+ targetpath)
+ except KeyError:
+ raise ExtractError("unable to resolve link inside archive")
+
+ def chown(self, tarinfo, targetpath):
+ """Set owner of targetpath according to tarinfo.
+ """
+ if pwd and hasattr(os, "geteuid") and os.geteuid() == 0:
+ # We have to be root to do so.
+ try:
+ g = grp.getgrnam(tarinfo.gname)[2]
+ except KeyError:
+ g = tarinfo.gid
+ try:
+ u = pwd.getpwnam(tarinfo.uname)[2]
+ except KeyError:
+ u = tarinfo.uid
+ try:
+ if tarinfo.issym() and hasattr(os, "lchown"):
+ os.lchown(targetpath, u, g)
+ else:
+ if sys.platform != "os2emx":
+ os.chown(targetpath, u, g)
+ except EnvironmentError as e:
+ raise ExtractError("could not change owner")
+
+ def chmod(self, tarinfo, targetpath):
+ """Set file permissions of targetpath according to tarinfo.
+ """
+ if hasattr(os, 'chmod'):
+ try:
+ os.chmod(targetpath, tarinfo.mode)
+ except EnvironmentError as e:
+ raise ExtractError("could not change mode")
+
+ def utime(self, tarinfo, targetpath):
+ """Set modification time of targetpath according to tarinfo.
+ """
+ if not hasattr(os, 'utime'):
+ return
+ try:
+ os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime))
+ except EnvironmentError as e:
+ raise ExtractError("could not change modification time")
+
+ #--------------------------------------------------------------------------
+ def next(self):
+ """Return the next member of the archive as a TarInfo object, when
+ TarFile is opened for reading. Return None if there is no more
+ available.
+ """
+ self._check("ra")
+ if self.firstmember is not None:
+ m = self.firstmember
+ self.firstmember = None
+ return m
+
+ # Read the next block.
+ self.fileobj.seek(self.offset)
+ tarinfo = None
+ while True:
+ try:
+ tarinfo = self.tarinfo.fromtarfile(self)
+ except EOFHeaderError as e:
+ if self.ignore_zeros:
+ self._dbg(2, "0x%X: %s" % (self.offset, e))
+ self.offset += BLOCKSIZE
+ continue
+ except InvalidHeaderError as e:
+ if self.ignore_zeros:
+ self._dbg(2, "0x%X: %s" % (self.offset, e))
+ self.offset += BLOCKSIZE
+ continue
+ elif self.offset == 0:
+ raise ReadError(str(e))
+ except EmptyHeaderError:
+ if self.offset == 0:
+ raise ReadError("empty file")
+ except TruncatedHeaderError as e:
+ if self.offset == 0:
+ raise ReadError(str(e))
+ except SubsequentHeaderError as e:
+ raise ReadError(str(e))
+ break
+
+ if tarinfo is not None:
+ self.members.append(tarinfo)
+ else:
+ self._loaded = True
+
+ return tarinfo
+
+ #--------------------------------------------------------------------------
+ # Little helper methods:
+
+ def _getmember(self, name, tarinfo=None, normalize=False):
+ """Find an archive member by name from bottom to top.
+ If tarinfo is given, it is used as the starting point.
+ """
+ # Ensure that all members have been loaded.
+ members = self.getmembers()
+
+ # Limit the member search list up to tarinfo.
+ if tarinfo is not None:
+ members = members[:members.index(tarinfo)]
+
+ if normalize:
+ name = os.path.normpath(name)
+
+ for member in reversed(members):
+ if normalize:
+ member_name = os.path.normpath(member.name)
+ else:
+ member_name = member.name
+
+ if name == member_name:
+ return member
+
+ def _load(self):
+ """Read through the entire archive file and look for readable
+ members.
+ """
+ while True:
+ tarinfo = self.next()
+ if tarinfo is None:
+ break
+ self._loaded = True
+
+ def _check(self, mode=None):
+ """Check if TarFile is still open, and if the operation's mode
+ corresponds to TarFile's mode.
+ """
+ if self.closed:
+ raise IOError("%s is closed" % self.__class__.__name__)
+ if mode is not None and self.mode not in mode:
+ raise IOError("bad operation for mode %r" % self.mode)
+
+ def _find_link_target(self, tarinfo):
+ """Find the target member of a symlink or hardlink member in the
+ archive.
+ """
+ if tarinfo.issym():
+ # Always search the entire archive.
+ linkname = os.path.dirname(tarinfo.name) + "/" + tarinfo.linkname
+ limit = None
+ else:
+ # Search the archive before the link, because a hard link is
+ # just a reference to an already archived file.
+ linkname = tarinfo.linkname
+ limit = tarinfo
+
+ member = self._getmember(linkname, tarinfo=limit, normalize=True)
+ if member is None:
+ raise KeyError("linkname %r not found" % linkname)
+ return member
+
+ def __iter__(self):
+ """Provide an iterator object.
+ """
+ if self._loaded:
+ return iter(self.members)
+ else:
+ return TarIter(self)
+
+ def _dbg(self, level, msg):
+ """Write debugging output to sys.stderr.
+ """
+ if level <= self.debug:
+ print(msg, file=sys.stderr)
+
+ def __enter__(self):
+ self._check()
+ return self
+
+ def __exit__(self, type, value, traceback):
+ if type is None:
+ self.close()
+ else:
+ # An exception occurred. We must not call close() because
+ # it would try to write end-of-archive blocks and padding.
+ if not self._extfileobj:
+ self.fileobj.close()
+ self.closed = True
+# class TarFile
+
+class TarIter(object):
+ """Iterator Class.
+
+ for tarinfo in TarFile(...):
+ suite...
+ """
+
+ def __init__(self, tarfile):
+ """Construct a TarIter object.
+ """
+ self.tarfile = tarfile
+ self.index = 0
+ def __iter__(self):
+ """Return iterator object.
+ """
+ return self
+
+ def __next__(self):
+ """Return the next item using TarFile's next() method.
+ When all members have been read, set TarFile as _loaded.
+ """
+ # Fix for SF #1100429: Under rare circumstances it can
+ # happen that getmembers() is called during iteration,
+ # which will cause TarIter to stop prematurely.
+ if not self.tarfile._loaded:
+ tarinfo = self.tarfile.next()
+ if not tarinfo:
+ self.tarfile._loaded = True
+ raise StopIteration
+ else:
+ try:
+ tarinfo = self.tarfile.members[self.index]
+ except IndexError:
+ raise StopIteration
+ self.index += 1
+ return tarinfo
+
+ next = __next__ # for Python 2.x
+
+#--------------------
+# exported functions
+#--------------------
+def is_tarfile(name):
+ """Return True if name points to a tar archive that we
+ are able to handle, else return False.
+ """
+ try:
+ t = open(name)
+ t.close()
+ return True
+ except TarError:
+ return False
+
+bltn_open = open
+open = TarFile.open
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py
new file mode 100644
index 0000000000..c316fd973a
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py
@@ -0,0 +1,1120 @@
+# -*- 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 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 ._backport import shutil
+ 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
+
+ _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
+ import shutil
+ from urllib.parse import (urlparse, urlunparse, urljoin, splituser, 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 not os.curdir 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 not normdir 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'
+
+try:
+ import sysconfig
+except ImportError: # pragma: no cover
+ from ._backport 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
+ import re
+
+ 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 & <-> &amp; 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
+ try:
+ from imp import cache_from_source
+ 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<prefix>[a-z]+)://(?P<suffix>.*)$')
+
+ 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]]
+ #print d, rest
+ 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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py
new file mode 100644
index 0000000000..0a90c300ba
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py
@@ -0,0 +1,1339 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012-2017 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
+ 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)
+
+ 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
+ logger.debug('Getting requirements from metadata %r', md.todict())
+ reqts = getattr(md, req_attr)
+ 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 '<Distribution %s (%s)%s>' % (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 '<InstalledDistribution %r %s at %r>' % (
+ 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()
+ if line.startswith('['):
+ logger.warning('Unexpected line: quitting requirement scan: %r',
+ line)
+ break
+ r = parse_requirement(line)
+ if not r:
+ logger.warning('Not recognised as a requirement: %r', line)
+ continue
+ if r.extras:
+ 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 '<EggInfoDistribution %r %s at %r>' % (
+ 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 not label is 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
+ """
+ if dist not in dists:
+ raise DistlibException('given distribution %r is not a member '
+ 'of the list' % dist.name)
+ graph = make_graph(dists)
+
+ req = [] # required distributions
+ todo = graph.adjacency_list[dist] # list of nodes we should inspect
+
+ while todo:
+ d = todo.pop()[0]
+ req.append(d)
+ for pred in graph.adjacency_list[d]:
+ if pred not in req:
+ 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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py
new file mode 100644
index 0000000000..7a87cdcf7a
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py
@@ -0,0 +1,516 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 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:
+ from dummy_threading import Thread
+
+from . import DistlibException
+from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
+ urlparse, build_opener, string_types)
+from .util import cached_property, 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 distutils.core import Distribution
+ from distutils.config import PyPIRCCommand
+ d = Distribution()
+ return PyPIRCCommand(d)
+
+ def read_configuration(self):
+ """
+ Read the PyPI access configuration as supported by distutils, getting
+ PyPI to do the actual work. This populates ``username``, ``password``,
+ ``realm`` and ``url`` attributes from the configuration.
+ """
+ # get distutils to do the work
+ c = self._get_pypirc_command()
+ c.repository = self.url
+ cfg = c._read_pypirc()
+ 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.
+
+ Again, distutils is used to do the actual work.
+ """
+ self.check_credentials()
+ # get distutils to do the work
+ c = self._get_pypirc_command()
+ c._store_pypirc(self.username, self.password)
+
+ 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):
+ """
+ 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(), [])
+ response = 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):
+ """
+ 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):
+ """
+ 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):
+ """
+ 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):
+ 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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py
new file mode 100644
index 0000000000..12a1d06351
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py
@@ -0,0 +1,1302 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012-2015 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, string_types, 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, parse_credentials, 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 as e: # 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, '')),
+ #'packagetype': 'sdist',
+ }
+ 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):
+ logger.debug('%s did not match %r', matcher, k)
+ else:
+ if prereleases or not vcls(k).is_prerelease:
+ slist.append(k)
+ else:
+ logger.debug('skipping pre-release '
+ 'version %s of %s', k, matcher.name)
+ 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<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)?
+href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
+(\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))?
+""", re.I | re.S | re.X)
+ _base = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", 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(d)).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.setDaemon(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('<a href=[^>]*>([^<]+)<')
+
+ 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 426 / PEP 440.
+default_locator = AggregatingLocator(
+ JSONLocator(),
+ SimpleScrapingLocator('https://pypi.org/simple/',
+ timeout=3.0),
+ scheme='legacy')
+
+locate = default_locator.locate
+
+NAME_VERSION_RE = re.compile(r'(?P<name>[\w-]+)\s*'
+ r'\(\s*(==\s*)?(?P<ver>[^)]+)\)$')
+
+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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py
new file mode 100644
index 0000000000..ca0fe442d9
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py
@@ -0,0 +1,393 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012-2013 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 on 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:
+ found = self._exclude_pattern(pattern, anchor=True)
+ #if not found:
+ # logger.warning('no previously-included files '
+ # 'found matching %r', pattern)
+
+ 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:
+ found = self._exclude_pattern(pattern, anchor=False)
+ #if not found:
+ # logger.warning('no previously-included files '
+ # 'matching %r found anywhere in '
+ # 'distribution', pattern)
+
+ 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:
+ found = self._exclude_pattern(pattern, prefix=thedir)
+ #if not found:
+ # logger.warning('no previously-included files '
+ # 'matching %r found under directory %r',
+ # pattern, 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 <pattern1> <pattern2> ...' % 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 <dir> <pattern1> <pattern2> ...' % 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 <dir_pattern>' % 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'((?<!\\)(\\\\)*)\.', escaped, pattern_re)
+ return pattern_re
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py
new file mode 100644
index 0000000000..ee1f3e2365
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012-2017 Vinay Sajip.
+# Licensed to the Python Software Foundation under a contributor agreement.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+"""
+Parser for the environment markers micro-language defined in PEP 508.
+"""
+
+# Note: In PEP 345, the micro-language was Python compatible, so the ast
+# module could be used to parse it. However, PEP 508 introduced operators such
+# as ~= and === which aren't in Python, necessitating a different approach.
+
+import os
+import sys
+import platform
+import re
+
+from .compat import python_implementation, urlparse, string_types
+from .util import in_venv, parse_marker
+
+__all__ = ['interpret']
+
+def _is_literal(o):
+ if not isinstance(o, string_types) or not o:
+ return False
+ return o[0] in '\'"'
+
+class Evaluator(object):
+ """
+ This class is used to evaluate marker expessions.
+ """
+
+ operations = {
+ '==': 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,
+ '<=': 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)
+ result = self.operations[op](lhs, rhs)
+ return result
+
+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 = ''
+
+ 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': platform.python_version(),
+ 'python_version': platform.python_version()[:3],
+ '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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py
new file mode 100644
index 0000000000..6d5e236090
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py
@@ -0,0 +1,1056 @@
+# -*- 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 withdrawn 2.0).
+"""
+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).
+_566_FIELDS = _426_FIELDS + ('Description-Content-Type',
+ 'Requires', 'Provides')
+
+_566_MARKERS = ('Description-Content-Type',)
+
+_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)
+
+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'):
+ return _345_FIELDS + _566_FIELDS
+ elif version == '2.0':
+ return _426_FIELDS
+ raise MetadataUnrecognizedVersionError(version)
+
+
+def _best_version(fields):
+ """Detect the best version depending on the fields used."""
+ def _has_marker(keys, markers):
+ for marker in markers:
+ if marker in keys:
+ return True
+ return False
+
+ keys = []
+ for key, value in fields.items():
+ if value in ([], 'UNKNOWN', None):
+ continue
+ keys.append(key)
+
+ possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.0', '2.1']
+
+ # 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 _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)
+ if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_0) > 1:
+ raise MetadataConflictError('You used incompatible 1.1/1.2/2.0/2.1 fields')
+
+ # we have the choice, 1.0, or 1.2, or 2.0
+ # - 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.0 adds more features and is very new
+ if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_0:
+ # 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'
+
+ return '2.0'
+
+# 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')
+_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
+ #
+
+# dependencies = property(_get_dependencies, _set_dependencies)
+
+ 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.0 (JSON)
+ 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)
+
+ 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',)),
+ }
+
+ __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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py
new file mode 100644
index 0000000000..18840167a9
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py
@@ -0,0 +1,355 @@
+# -*- 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 shutil
+import sys
+import types
+import zipimport
+
+from . import DistlibException
+from .util import cached_property, get_cache_base, path_to_cache_dir, 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
+ 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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py
new file mode 100644
index 0000000000..03f8f21e0f
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py
@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013-2015 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
+
+from .compat import sysconfig, detect_encoding, ZipFile
+from .resources import finder
+from .util import (FileOperator, get_export_entry, convert_path,
+ get_executable, in_venv)
+
+logger = logging.getLogger(__name__)
+
+_DEFAULT_MANIFEST = '''
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0"
+ processorArchitecture="X86"
+ name="%s"
+ type="win32"/>
+
+ <!-- Identify the application security requirements. -->
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+</assembly>'''.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())
+'''
+
+
+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
+ 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"' '''"
+ 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
+ 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
+ linesep = os.linesep.encode('utf-8')
+ if not shebang.endswith(linesep):
+ shebang += linesep
+ 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:
+ 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)
+
+ 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')
+ name = entry.name
+ scriptnames = set()
+ if '' in self.variants:
+ scriptnames.add(name)
+ if 'X' in self.variants:
+ scriptnames.add('%s%s' % (name, self.version_info[0]))
+ if 'X.Y' in self.variants:
+ scriptnames.add('%s-%s.%s' % (name, self.version_info[0],
+ self.version_info[1]))
+ 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: %s is an empty file (skipping)',
+ self.get_command_name(), 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'
+ name = '%s%s.exe' % (kind, bits)
+ # Issue 31: don't hardcode an absolute package name, but
+ # determine it relative to the current package
+ distlib_package = __name__.rsplit('.', 1)[0]
+ resource = finder(distlib_package).find(name)
+ if not resource:
+ msg = ('Unable to find resource %s in package %s' % (name,
+ distlib_package))
+ raise ValueError(msg)
+ return resource.bytes
+
+ # 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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t32.exe b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t32.exe
new file mode 100644
index 0000000000..8932a18e45
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t32.exe
Binary files differ
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t64.exe b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t64.exe
new file mode 100644
index 0000000000..325b8057c0
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/t64.exe
Binary files differ
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py
new file mode 100644
index 0000000000..01324eae46
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py
@@ -0,0 +1,1761 @@
+#
+# Copyright (C) 2012-2017 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,
+ splittype, 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()
+ 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
+ result = os.path.normcase(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'):
+ compile_kwargs['invalidation_mode'] = py_compile.PycInvalidationMode.CHECKED_HASH
+ 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 '<ExportEntry %s = %s:%s %s>' % (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<name>(\w|[-.+])+)
+ \s*=\s*(?P<callable>(\w+)([:\.]\w+)*)
+ \s*(\[\s*(?P<flags>[\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):
+ """
+ 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 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<name>[\w .-]+)\s*'
+ r'\(\s*(?P<ver>[^\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):
+ """
+ Converts a resource prefix to a directory name in the cache.
+ """
+ return path_to_cache_dir(prefix)
+
+ 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')
+ 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()
+
+ if not hasattr(ssl, 'SSLContext'):
+ # For 2.x
+ if self.ca_certs:
+ cert_reqs = ssl.CERT_REQUIRED
+ else:
+ cert_reqs = ssl.CERT_NONE
+ self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
+ cert_reqs=cert_reqs,
+ ssl_version=ssl.PROTOCOL_SSLv23,
+ ca_certs=self.ca_certs)
+ else: # pragma: no cover
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ if hasattr(ssl, 'OP_NO_SSLv2'):
+ context.options |= ssl.OP_NO_SSLv2
+ if self.cert_file:
+ 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
+#
+
+_ver_info = sys.version_info[:2]
+
+if _ver_info == (2, 6):
+ class HTTP(httplib.HTTP):
+ def __init__(self, host='', port=None, **kwargs):
+ if port == 0: # 0 means use port 0, not the default port
+ port = None
+ self._setup(self._connection_class(host, port, **kwargs))
+
+
+ if ssl:
+ class HTTPS(httplib.HTTPS):
+ def __init__(self, host='', port=None, **kwargs):
+ if port == 0: # 0 means use port 0, not the default port
+ port = None
+ self._setup(self._connection_class(host, port, **kwargs))
+
+
+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 _ver_info == (2, 6):
+ result = HTTP(h, timeout=self.timeout)
+ else:
+ if not self._connection or host != self._connection[0]:
+ self._extra_headers = eh
+ self._connection = host, httplib.HTTPConnection(h)
+ result = self._connection[1]
+ return result
+
+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 _ver_info == (2, 6):
+ result = HTTPS(host, None, **kwargs)
+ else:
+ if not self._connection or host != self._connection[0]:
+ self._extra_headers = eh
+ self._connection = host, httplib.HTTPSConnection(h, None,
+ **kwargs)
+ result = self._connection[1]
+ return result
+
+
+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)
+ 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()
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py
new file mode 100644
index 0000000000..3eebe18ee8
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py
@@ -0,0 +1,736 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012-2017 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|b|c|rc)(\d+))?'
+ r'(\.(post)(\d+))?(\.(dev)(\d+))?'
+ r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$')
+
+
+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])
+ pre = groups[4:6]
+ post = groups[7:9]
+ dev = groups[10:12]
+ local = groups[13]
+ if pre == (None, None):
+ pre = ()
+ else:
+ pre = pre[0], int(pre[1])
+ if post == (None, None):
+ post = ()
+ else:
+ post = post[0], int(post[1])
+ if dev == (None, None):
+ dev = ()
+ 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',)
+
+ #print('%s -> %s' % (s, m.groups()))
+ 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
+ """
+ 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/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w32.exe b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w32.exe
new file mode 100644
index 0000000000..e6439e9e45
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w32.exe
Binary files differ
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w64.exe b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w64.exe
new file mode 100644
index 0000000000..46139dbf94
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/w64.exe
Binary files differ
diff --git a/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py
new file mode 100644
index 0000000000..1e2c7a020c
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py
@@ -0,0 +1,1018 @@
+# -*- 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 base64
+import codecs
+import datetime
+import distutils.util
+from email import message_from_file
+import hashlib
+import imp
+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, METADATA_FILENAME, WHEEL_METADATA_FILENAME,
+ LEGACY_METADATA_FILENAME)
+from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache,
+ cached_property, get_cache_base, read_exports, tempdir)
+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 = distutils.util.get_platform().replace('-', '_').replace('.', '_')
+
+ABI = sysconfig.get_config_var('SOABI')
+if ABI and ABI.startswith('cpython-'):
+ ABI = ABI.replace('cpython-', 'cp')
+else:
+ def _derive_abi():
+ parts = ['cp', VER_SUFFIX]
+ if sysconfig.get_config_var('Py_DEBUG'):
+ parts.append('d')
+ if sysconfig.get_config_var('WITH_PYMALLOC'):
+ parts.append('m')
+ if sysconfig.get_config_var('Py_UNICODE_SIZE') == 4:
+ parts.append('u')
+ return ''.join(parts)
+ ABI = _derive_abi()
+ del _derive_abi
+
+FILENAME_RE = re.compile(r'''
+(?P<nm>[^-]+)
+-(?P<vn>\d+[^-]*)
+(-(?P<bn>\d+[^-]*))?
+-(?P<py>\w+\d+(\.\w+\d+)*)
+-(?P<bi>\w+)
+-(?P<ar>\w+(\.\w+)*)
+\.whl$
+''', re.IGNORECASE | re.VERBOSE)
+
+NAME_VERSION_RE = re.compile(r'''
+(?P<nm>[^-]+)
+-(?P<vn>\d+[^-]*)
+(-(?P<bn>\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, '/')
+
+
+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 = imp.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:
+ wheel_metadata = 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, base):
+ records = list(records) # make a copy, as mutated
+ p = to_posix(os.path.relpath(record_path, base))
+ records.append((p, '', ''))
+ 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')
+ self.write_record(records, p, libdir)
+ ap = to_posix(os.path.join(info_dir, 'RECORD'))
+ 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 2.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)
+ 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(pathname)
+ 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 = 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:
+ v = 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 compatible_tags():
+ """
+ Return (pyver, abi, arch) tuples compatible with this Python.
+ """
+ versions = [VER_SUFFIX]
+ major = VER_SUFFIX[0]
+ for minor in range(sys.version_info[1] - 1, - 1, -1):
+ versions.append(''.join([major, str(minor)]))
+
+ abis = []
+ for suffix, _, _ in imp.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 abi in abis:
+ for arch in arches:
+ result.append((''.join((IMP_PREFIX, versions[0])), abi, arch))
+
+ # where no ABI / arch dependency, but IMP_PREFIX dependency
+ for i, version in enumerate(versions):
+ 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 in enumerate(versions):
+ 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/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/LICENSE
new file mode 100644
index 0000000000..cf1ab25da0
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+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 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.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/METADATA
new file mode 100644
index 0000000000..79d8d47990
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/METADATA
@@ -0,0 +1,156 @@
+Metadata-Version: 2.1
+Name: filelock
+Version: 3.0.12
+Summary: A platform independent file lock.
+Home-page: https://github.com/benediktschmitt/py-filelock
+Author: Benedikt Schmitt
+Author-email: benedikt@benediktschmitt.de
+License: Public Domain <http://unlicense.org>
+Download-URL: https://github.com/benediktschmitt/py-filelock/archive/master.zip
+Platform: UNKNOWN
+Classifier: License :: Public Domain
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: System
+Classifier: Topic :: Internet
+Classifier: Topic :: Software Development :: Libraries
+Description-Content-Type: text/markdown
+
+# py-filelock
+
+![travis-ci](https://travis-ci.org/benediktschmitt/py-filelock.svg?branch=master)
+
+This package contains a single module, which implements a platform independent
+file lock in Python, which provides a simple way of inter-process communication:
+
+```Python
+from filelock import Timeout, FileLock
+
+lock = FileLock("high_ground.txt.lock")
+with lock:
+ open("high_ground.txt", "a").write("You were the chosen one.")
+```
+
+**Don't use** a *FileLock* to lock the file you want to write to, instead create
+a separate *.lock* file as shown above.
+
+![animated example](https://raw.githubusercontent.com/benediktschmitt/py-filelock/master/example/example.gif)
+
+
+## Similar libraries
+
+Perhaps you are looking for something like
+
+* https://pypi.python.org/pypi/pid/2.1.1
+* https://docs.python.org/3.6/library/msvcrt.html#msvcrt.locking
+* or https://docs.python.org/3/library/fcntl.html#fcntl.flock
+
+
+## Installation
+
+*py-filelock* is available via PyPi:
+
+```
+$ pip3 install filelock
+```
+
+
+## Documentation
+
+The documentation for the API is available on
+[readthedocs.org](https://filelock.readthedocs.io/).
+
+
+### Examples
+
+A *FileLock* is used to indicate another process of your application that a
+resource or working
+directory is currently used. To do so, create a *FileLock* first:
+
+```Python
+from filelock import Timeout, FileLock
+
+file_path = "high_ground.txt"
+lock_path = "high_ground.txt.lock"
+
+lock = FileLock(lock_path, timeout=1)
+```
+
+The lock object supports multiple ways for acquiring the lock, including the
+ones used to acquire standard Python thread locks:
+
+```Python
+with lock:
+ open(file_path, "a").write("Hello there!")
+
+lock.acquire()
+try:
+ open(file_path, "a").write("General Kenobi!")
+finally:
+ lock.release()
+```
+
+The *acquire()* method accepts also a *timeout* parameter. If the lock cannot be
+acquired within *timeout* seconds, a *Timeout* exception is raised:
+
+```Python
+try:
+ with lock.acquire(timeout=10):
+ open(file_path, "a").write("I have a bad feeling about this.")
+except Timeout:
+ print("Another instance of this application currently holds the lock.")
+```
+
+The lock objects are recursive locks, which means that once acquired, they will
+not block on successive lock requests:
+
+```Python
+def cite1():
+ with lock:
+ open(file_path, "a").write("I hate it when he does that.")
+
+def cite2():
+ with lock:
+ open(file_path, "a").write("You don't want to sell me death sticks.")
+
+# The lock is acquired here.
+with lock:
+ cite1()
+ cite2()
+
+# And released here.
+```
+
+
+## FileLock vs SoftFileLock
+
+The *FileLock* is platform dependent while the *SoftFileLock* is not. Use the
+*FileLock* if all instances of your application are running on the same host and
+a *SoftFileLock* otherwise.
+
+The *SoftFileLock* only watches the existence of the lock file. This makes it
+ultra portable, but also more prone to dead locks if the application crashes.
+You can simply delete the lock file in such cases.
+
+
+## Contributions
+
+Contributions are always welcome, please make sure they pass all tests before
+creating a pull request. Never hesitate to open a new issue, although it may
+take some time for me to respond.
+
+
+## License
+
+This package is [public domain](./LICENSE.rst).
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/RECORD
new file mode 100644
index 0000000000..c5f2e1f5cf
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/RECORD
@@ -0,0 +1,6 @@
+filelock.py,sha256=5DQTtOaQq7-vgLkZzvOhqhVMh_umfydWgSA8Vuzmf8M,13229
+filelock-3.0.12.dist-info/LICENSE,sha256=iNm062BXnBkew5HKBMFhMFctfu3EqG2qWL8oxuFMm80,1210
+filelock-3.0.12.dist-info/METADATA,sha256=gjzbv9nxtD-Rj2ysjUuG7SLZCHUQl5hMy68Jij8soPw,4343
+filelock-3.0.12.dist-info/WHEEL,sha256=EVRjI69F5qVjm_YgqcTXPnTAv3BfSUr0WVAHuSP3Xoo,92
+filelock-3.0.12.dist-info/top_level.txt,sha256=NDrf9i5BNogz4hEdsr6Hi7Ws3TlSSKY4Q2Y9_-i2GwU,9
+filelock-3.0.12.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/WHEEL
new file mode 100644
index 0000000000..83ff02e961
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.35.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/top_level.txt
new file mode 100644
index 0000000000..83c2e35706
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info/top_level.txt
@@ -0,0 +1 @@
+filelock
diff --git a/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py
new file mode 100644
index 0000000000..978ff5e865
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py
@@ -0,0 +1,451 @@
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# 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 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.
+#
+# For more information, please refer to <http://unlicense.org>
+
+"""
+A platform independent file lock that supports the with-statement.
+"""
+
+
+# Modules
+# ------------------------------------------------
+import logging
+import os
+import threading
+import time
+try:
+ import warnings
+except ImportError:
+ warnings = None
+
+try:
+ import msvcrt
+except ImportError:
+ msvcrt = None
+
+try:
+ import fcntl
+except ImportError:
+ fcntl = None
+
+
+# Backward compatibility
+# ------------------------------------------------
+try:
+ TimeoutError
+except NameError:
+ TimeoutError = OSError
+
+
+# Data
+# ------------------------------------------------
+__all__ = [
+ "Timeout",
+ "BaseFileLock",
+ "WindowsFileLock",
+ "UnixFileLock",
+ "SoftFileLock",
+ "FileLock"
+]
+
+__version__ = "3.0.12"
+
+
+_logger = None
+def logger():
+ """Returns the logger instance used in this module."""
+ global _logger
+ _logger = _logger or logging.getLogger(__name__)
+ return _logger
+
+
+# Exceptions
+# ------------------------------------------------
+class Timeout(TimeoutError):
+ """
+ Raised when the lock could not be acquired in *timeout*
+ seconds.
+ """
+
+ def __init__(self, lock_file):
+ """
+ """
+ #: The path of the file lock.
+ self.lock_file = lock_file
+ return None
+
+ def __str__(self):
+ temp = "The file lock '{}' could not be acquired."\
+ .format(self.lock_file)
+ return temp
+
+
+# Classes
+# ------------------------------------------------
+
+# This is a helper class which is returned by :meth:`BaseFileLock.acquire`
+# and wraps the lock to make sure __enter__ is not called twice when entering
+# the with statement.
+# If we would simply return *self*, the lock would be acquired again
+# in the *__enter__* method of the BaseFileLock, but not released again
+# automatically.
+#
+# :seealso: issue #37 (memory leak)
+class _Acquire_ReturnProxy(object):
+
+ def __init__(self, lock):
+ self.lock = lock
+ return None
+
+ def __enter__(self):
+ return self.lock
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.lock.release()
+ return None
+
+
+class BaseFileLock(object):
+ """
+ Implements the base class of a file lock.
+ """
+
+ def __init__(self, lock_file, timeout = -1):
+ """
+ """
+ # The path to the lock file.
+ self._lock_file = lock_file
+
+ # The file descriptor for the *_lock_file* as it is returned by the
+ # os.open() function.
+ # This file lock is only NOT None, if the object currently holds the
+ # lock.
+ self._lock_file_fd = None
+
+ # The default timeout value.
+ self.timeout = timeout
+
+ # We use this lock primarily for the lock counter.
+ self._thread_lock = threading.Lock()
+
+ # The lock counter is used for implementing the nested locking
+ # mechanism. Whenever the lock is acquired, the counter is increased and
+ # the lock is only released, when this value is 0 again.
+ self._lock_counter = 0
+ return None
+
+ @property
+ def lock_file(self):
+ """
+ The path to the lock file.
+ """
+ return self._lock_file
+
+ @property
+ def timeout(self):
+ """
+ You can set a default timeout for the filelock. It will be used as
+ fallback value in the acquire method, if no timeout value (*None*) is
+ given.
+
+ If you want to disable the timeout, set it to a negative value.
+
+ A timeout of 0 means, that there is exactly one attempt to acquire the
+ file lock.
+
+ .. versionadded:: 2.0.0
+ """
+ return self._timeout
+
+ @timeout.setter
+ def timeout(self, value):
+ """
+ """
+ self._timeout = float(value)
+ return None
+
+ # Platform dependent locking
+ # --------------------------------------------
+
+ def _acquire(self):
+ """
+ Platform dependent. If the file lock could be
+ acquired, self._lock_file_fd holds the file descriptor
+ of the lock file.
+ """
+ raise NotImplementedError()
+
+ def _release(self):
+ """
+ Releases the lock and sets self._lock_file_fd to None.
+ """
+ raise NotImplementedError()
+
+ # Platform independent methods
+ # --------------------------------------------
+
+ @property
+ def is_locked(self):
+ """
+ True, if the object holds the file lock.
+
+ .. versionchanged:: 2.0.0
+
+ This was previously a method and is now a property.
+ """
+ return self._lock_file_fd is not None
+
+ def acquire(self, timeout=None, poll_intervall=0.05):
+ """
+ Acquires the file lock or fails with a :exc:`Timeout` error.
+
+ .. code-block:: python
+
+ # You can use this method in the context manager (recommended)
+ with lock.acquire():
+ pass
+
+ # Or use an equivalent try-finally construct:
+ lock.acquire()
+ try:
+ pass
+ finally:
+ lock.release()
+
+ :arg float timeout:
+ The maximum time waited for the file lock.
+ If ``timeout < 0``, there is no timeout and this method will
+ block until the lock could be acquired.
+ If ``timeout`` is None, the default :attr:`~timeout` is used.
+
+ :arg float poll_intervall:
+ We check once in *poll_intervall* seconds if we can acquire the
+ file lock.
+
+ :raises Timeout:
+ if the lock could not be acquired in *timeout* seconds.
+
+ .. versionchanged:: 2.0.0
+
+ This method returns now a *proxy* object instead of *self*,
+ so that it can be used in a with statement without side effects.
+ """
+ # Use the default timeout, if no timeout is provided.
+ if timeout is None:
+ timeout = self.timeout
+
+ # Increment the number right at the beginning.
+ # We can still undo it, if something fails.
+ with self._thread_lock:
+ self._lock_counter += 1
+
+ lock_id = id(self)
+ lock_filename = self._lock_file
+ start_time = time.time()
+ try:
+ while True:
+ with self._thread_lock:
+ if not self.is_locked:
+ logger().debug('Attempting to acquire lock %s on %s', lock_id, lock_filename)
+ self._acquire()
+
+ if self.is_locked:
+ logger().info('Lock %s acquired on %s', lock_id, lock_filename)
+ break
+ elif timeout >= 0 and time.time() - start_time > timeout:
+ logger().debug('Timeout on acquiring lock %s on %s', lock_id, lock_filename)
+ raise Timeout(self._lock_file)
+ else:
+ logger().debug(
+ 'Lock %s not acquired on %s, waiting %s seconds ...',
+ lock_id, lock_filename, poll_intervall
+ )
+ time.sleep(poll_intervall)
+ except:
+ # Something did go wrong, so decrement the counter.
+ with self._thread_lock:
+ self._lock_counter = max(0, self._lock_counter - 1)
+
+ raise
+ return _Acquire_ReturnProxy(lock = self)
+
+ def release(self, force = False):
+ """
+ Releases the file lock.
+
+ Please note, that the lock is only completly released, if the lock
+ counter is 0.
+
+ Also note, that the lock file itself is not automatically deleted.
+
+ :arg bool force:
+ If true, the lock counter is ignored and the lock is released in
+ every case.
+ """
+ with self._thread_lock:
+
+ if self.is_locked:
+ self._lock_counter -= 1
+
+ if self._lock_counter == 0 or force:
+ lock_id = id(self)
+ lock_filename = self._lock_file
+
+ logger().debug('Attempting to release lock %s on %s', lock_id, lock_filename)
+ self._release()
+ self._lock_counter = 0
+ logger().info('Lock %s released on %s', lock_id, lock_filename)
+
+ return None
+
+ def __enter__(self):
+ self.acquire()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.release()
+ return None
+
+ def __del__(self):
+ self.release(force = True)
+ return None
+
+
+# Windows locking mechanism
+# ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class WindowsFileLock(BaseFileLock):
+ """
+ Uses the :func:`msvcrt.locking` function to hard lock the lock file on
+ windows systems.
+ """
+
+ def _acquire(self):
+ open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC
+
+ try:
+ fd = os.open(self._lock_file, open_mode)
+ except OSError:
+ pass
+ else:
+ try:
+ msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
+ except (IOError, OSError):
+ os.close(fd)
+ else:
+ self._lock_file_fd = fd
+ return None
+
+ def _release(self):
+ fd = self._lock_file_fd
+ self._lock_file_fd = None
+ msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
+ os.close(fd)
+
+ try:
+ os.remove(self._lock_file)
+ # Probably another instance of the application
+ # that acquired the file lock.
+ except OSError:
+ pass
+ return None
+
+# Unix locking mechanism
+# ~~~~~~~~~~~~~~~~~~~~~~
+
+class UnixFileLock(BaseFileLock):
+ """
+ Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems.
+ """
+
+ def _acquire(self):
+ open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC
+ fd = os.open(self._lock_file, open_mode)
+
+ try:
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except (IOError, OSError):
+ os.close(fd)
+ else:
+ self._lock_file_fd = fd
+ return None
+
+ def _release(self):
+ # Do not remove the lockfile:
+ #
+ # https://github.com/benediktschmitt/py-filelock/issues/31
+ # https://stackoverflow.com/questions/17708885/flock-removing-locked-file-without-race-condition
+ fd = self._lock_file_fd
+ self._lock_file_fd = None
+ fcntl.flock(fd, fcntl.LOCK_UN)
+ os.close(fd)
+ return None
+
+# Soft lock
+# ~~~~~~~~~
+
+class SoftFileLock(BaseFileLock):
+ """
+ Simply watches the existence of the lock file.
+ """
+
+ def _acquire(self):
+ open_mode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC
+ try:
+ fd = os.open(self._lock_file, open_mode)
+ except (IOError, OSError):
+ pass
+ else:
+ self._lock_file_fd = fd
+ return None
+
+ def _release(self):
+ os.close(self._lock_file_fd)
+ self._lock_file_fd = None
+
+ try:
+ os.remove(self._lock_file)
+ # The file is already deleted and that's what we want.
+ except OSError:
+ pass
+ return None
+
+
+# Platform filelock
+# ~~~~~~~~~~~~~~~~~
+
+#: Alias for the lock, which should be used for the current platform. On
+#: Windows, this is an alias for :class:`WindowsFileLock`, on Unix for
+#: :class:`UnixFileLock` and otherwise for :class:`SoftFileLock`.
+FileLock = None
+
+if msvcrt:
+ FileLock = WindowsFileLock
+elif fcntl:
+ FileLock = UnixFileLock
+else:
+ FileLock = SoftFileLock
+
+ if warnings is not None:
+ warnings.warn("only soft file lock is available")
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/LICENSE
new file mode 100644
index 0000000000..be7e092b0b
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2017-2019 Jason R. Coombs, Barry Warsaw
+
+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/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/METADATA
new file mode 100644
index 0000000000..165a67ded5
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/METADATA
@@ -0,0 +1,65 @@
+Metadata-Version: 2.1
+Name: importlib-metadata
+Version: 1.1.3
+Summary: Read metadata from Python packages
+Home-page: http://importlib-metadata.readthedocs.io/
+Author: Barry Warsaw
+Author-email: barry@python.org
+License: Apache Software License
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 2
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7
+Requires-Dist: zipp (>=0.5)
+Requires-Dist: contextlib2 ; python_version < "3"
+Requires-Dist: configparser (>=3.5) ; python_version < "3"
+Requires-Dist: pathlib2 ; python_version == "3.4.*" or python_version < "3"
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: rst.linker ; extra == 'docs'
+Provides-Extra: testing
+Requires-Dist: packaging ; extra == 'testing'
+Requires-Dist: importlib-resources ; (python_version < "3.7") and extra == 'testing'
+
+=========================
+ ``importlib_metadata``
+=========================
+
+``importlib_metadata`` is a library to access the metadata for a Python
+package. It is intended to be ported to Python 3.8.
+
+
+Usage
+=====
+
+See the `online documentation <https://importlib_metadata.readthedocs.io/>`_
+for usage details.
+
+`Finder authors
+<https://docs.python.org/3/reference/import.html#finders-and-loaders>`_ 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://gitlab.com/python-devs/importlib_metadata
+ * Report bugs at: https://gitlab.com/python-devs/importlib_metadata/issues
+ * Code hosting: https://gitlab.com/python-devs/importlib_metadata.git
+ * Documentation: http://importlib_metadata.readthedocs.io/
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/RECORD
new file mode 100644
index 0000000000..d0eac8875c
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/RECORD
@@ -0,0 +1,21 @@
+importlib_metadata/__init__.py,sha256=wjIJ8vwgfW6r1J8Yckbk2mqOk_ZDPe7fQvsDj1oG-aQ,16840
+importlib_metadata/_compat.py,sha256=EwnYmvejrDFHENaQEutLz7L1rvyK6jJv9-xwk_bWVTI,4265
+importlib_metadata/docs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_metadata/docs/changelog.rst,sha256=QZ-WVYSgPlbeva4C8z5o58Ufpku4_JGfEOvEoPK-qt4,7086
+importlib_metadata/docs/conf.py,sha256=DM_-W8bvIar_YqWeRQUcgWT1_phXe-H2IcYgM8JIkiY,5468
+importlib_metadata/docs/index.rst,sha256=bHIGj1koPACV8OV02uHTGRMax46lGj00KLOji1aPl_c,2165
+importlib_metadata/docs/using.rst,sha256=2S6KGhJ66t8kM3cik7K03X1AJUGX0TWr6byaHEsJjnc,9826
+importlib_metadata/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_metadata/tests/fixtures.py,sha256=sshuoJ4ezljeouUddVg-76K1UOStKWBecovZOKOBguk,5004
+importlib_metadata/tests/test_api.py,sha256=YMAGTsRENrtvpw2CSLmRndJMBeT4q_M0GSe-QsnnMZ4,5544
+importlib_metadata/tests/test_integration.py,sha256=kzqav9qAePjz7UR-GNna65xLwXlRcxEDYDwmuOFwpKE,686
+importlib_metadata/tests/test_main.py,sha256=nnKTmcIA14lhynepCfXtiTYWH35hNFuFfIcKBkzShuY,7179
+importlib_metadata/tests/test_zip.py,sha256=qG3IquiTFLSrUtpxEJblqiUtgEcOTfjU2yM35REk0fo,2372
+importlib_metadata/tests/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_metadata/tests/data/example-21.12-py3-none-any.whl,sha256=I-kYufETid-tDYyR8f1OFJ3t5u_Io23k0cbQxJTUN4I,1455
+importlib_metadata/tests/data/example-21.12-py3.6.egg,sha256=-EeugFAijkdUO9xyQHTZkQwZoFXK0_QxICBj6R5AAJo,1497
+importlib_metadata-1.1.3.dist-info/LICENSE,sha256=wNe6dAchmJ1VvVB8D9oTc-gHHadCuaSBAev36sYEM6U,571
+importlib_metadata-1.1.3.dist-info/METADATA,sha256=zI5ihvOML51dmmsBF9_GrpnlUCgU8PTWXYa0Eb47nZU,2114
+importlib_metadata-1.1.3.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
+importlib_metadata-1.1.3.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
+importlib_metadata-1.1.3.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/WHEEL
new file mode 100644
index 0000000000..8b701e93c2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.33.6)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/top_level.txt
new file mode 100644
index 0000000000..bbb07547a1
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info/top_level.txt
@@ -0,0 +1 @@
+importlib_metadata
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/__init__.py b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/__init__.py
new file mode 100644
index 0000000000..31ff8462f3
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/__init__.py
@@ -0,0 +1,541 @@
+from __future__ import unicode_literals, absolute_import
+
+import io
+import os
+import re
+import abc
+import csv
+import sys
+import zipp
+import operator
+import functools
+import itertools
+import collections
+
+from ._compat import (
+ install,
+ NullFinder,
+ ConfigParser,
+ suppress,
+ map,
+ FileNotFoundError,
+ IsADirectoryError,
+ NotADirectoryError,
+ PermissionError,
+ pathlib,
+ PYPY_OPEN_BUG,
+ ModuleNotFoundError,
+ MetaPathFinder,
+ email_message_from_string,
+ ensure_is_path,
+ PyPy_repr,
+ )
+from importlib import import_module
+from itertools import starmap
+
+
+__metaclass__ = type
+
+
+__all__ = [
+ 'Distribution',
+ 'DistributionFinder',
+ 'PackageNotFoundError',
+ 'distribution',
+ 'distributions',
+ 'entry_points',
+ 'files',
+ 'metadata',
+ 'requires',
+ 'version',
+ ]
+
+
+class PackageNotFoundError(ModuleNotFoundError):
+ """The package was not found."""
+
+
+class EntryPoint(
+ PyPy_repr,
+ collections.namedtuple('EntryPointBase', 'name value group')):
+ """An entry point as defined by Python packaging conventions.
+
+ See `the packaging docs on entry points
+ <https://packaging.python.org/specifications/entry-points/>`_
+ for more information.
+ """
+
+ pattern = re.compile(
+ r'(?P<module>[\w.]+)\s*'
+ r'(:\s*(?P<attr>[\w.]+))?\s*'
+ r'(?P<extras>\[.*\])?\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.
+ """
+
+ def load(self):
+ """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 = 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 extras(self):
+ match = self.pattern.match(self.value)
+ return list(re.finditer(r'\w+', match.group('extras') or ''))
+
+ @classmethod
+ def _from_config(cls, config):
+ return [
+ cls(name, value, group)
+ for group in config.sections()
+ for name, value in config.items(group)
+ ]
+
+ @classmethod
+ def _from_text(cls, text):
+ config = ConfigParser(delimiters='=')
+ # case sensitive: https://stackoverflow.com/q/1611799/812183
+ config.optionxform = str
+ try:
+ config.read_string(text)
+ except AttributeError: # pragma: nocover
+ # Python 2 has no read_string
+ config.readfp(io.StringIO(text))
+ return EntryPoint._from_config(config)
+
+ def __iter__(self):
+ """
+ Supply iter so one may construct dicts of EntryPoints easily.
+ """
+ return iter((self.name, self))
+
+ def __reduce__(self):
+ return (
+ self.__class__,
+ (self.name, self.value, self.group),
+ )
+
+
+class PackagePath(pathlib.PurePosixPath):
+ """A reference to a path in a package"""
+
+ def read_text(self, encoding='utf-8'):
+ with self.locate().open(encoding=encoding) as stream:
+ return stream.read()
+
+ def read_binary(self):
+ with self.locate().open('rb') as stream:
+ return stream.read()
+
+ def locate(self):
+ """Return a path-like object for this path"""
+ return self.dist.locate_file(self)
+
+
+class FileHash:
+ def __init__(self, spec):
+ self.mode, _, self.value = spec.partition('=')
+
+ def __repr__(self):
+ return '<FileHash mode: {} value: {}>'.format(self.mode, self.value)
+
+
+class Distribution:
+ """A Python distribution package."""
+
+ @abc.abstractmethod
+ def read_text(self, filename):
+ """Attempt to load metadata file given by the name.
+
+ :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):
+ """
+ Given a path to a file in this distribution, return a path
+ to it.
+ """
+
+ @classmethod
+ def from_name(cls, name):
+ """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.
+ """
+ for resolver in cls._discover_resolvers():
+ dists = resolver(DistributionFinder.Context(name=name))
+ dist = next(dists, None)
+ if dist is not None:
+ return dist
+ else:
+ raise PackageNotFoundError(name)
+
+ @classmethod
+ def discover(cls, **kwargs):
+ """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 all packages.
+ """
+ context = kwargs.pop('context', None)
+ 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 at(path):
+ """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(ensure_is_path(path))
+
+ @staticmethod
+ def _discover_resolvers():
+ """Search the meta_path for resolvers."""
+ declared = (
+ getattr(finder, 'find_distributions', None)
+ for finder in sys.meta_path
+ )
+ return filter(None, declared)
+
+ @property
+ def metadata(self):
+ """Return the parsed metadata for this Distribution.
+
+ The returned object will have keys that name the various bits of
+ metadata. See PEP 566 for details.
+ """
+ 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('')
+ )
+ return email_message_from_string(text)
+
+ @property
+ def version(self):
+ """Return the 'Version' metadata for the distribution package."""
+ return self.metadata['Version']
+
+ @property
+ def entry_points(self):
+ return EntryPoint._from_text(self.read_text('entry_points.txt'))
+
+ @property
+ def files(self):
+ """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 SOURCES.txt for egg-info) is
+ missing.
+ Result may be empty if the metadata exists but is empty.
+ """
+ file_lines = self._read_files_distinfo() or self._read_files_egginfo()
+
+ 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
+
+ return file_lines and list(starmap(make_file, csv.reader(file_lines)))
+
+ def _read_files_distinfo(self):
+ """
+ Read the lines of RECORD
+ """
+ text = self.read_text('RECORD')
+ return text and text.splitlines()
+
+ def _read_files_egginfo(self):
+ """
+ SOURCES.txt might contain literal commas, so wrap each line
+ in quotes.
+ """
+ text = self.read_text('SOURCES.txt')
+ return text and map('"{}"'.format, text.splitlines())
+
+ @property
+ def requires(self):
+ """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 source and self._deps_from_requires_text(source)
+
+ @classmethod
+ def _deps_from_requires_text(cls, source):
+ section_pairs = cls._read_sections(source.splitlines())
+ sections = {
+ section: list(map(operator.itemgetter('line'), results))
+ for section, results in
+ itertools.groupby(section_pairs, operator.itemgetter('section'))
+ }
+ return cls._convert_egg_info_reqs_to_simple_reqs(sections)
+
+ @staticmethod
+ def _read_sections(lines):
+ section = None
+ for line in filter(None, lines):
+ section_match = re.match(r'\[(.*)\]$', line)
+ if section_match:
+ section = section_match.group(1)
+ continue
+ yield locals()
+
+ @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 'extra == "{name}"'.format(name=name)
+
+ def parse_condition(section):
+ section = section or ''
+ extra, sep, markers = section.partition(':')
+ if extra and markers:
+ markers = '({markers})'.format(markers=markers)
+ conditions = list(filter(None, [markers, make_condition(extra)]))
+ return '; ' + ' and '.join(conditions) if conditions else ''
+
+ for section, deps in sections.items():
+ for dep in deps:
+ yield dep + parse_condition(section)
+
+
+class DistributionFinder(MetaPathFinder):
+ """
+ A MetaPathFinder capable of discovering installed distributions.
+ """
+
+ class Context:
+
+ name = None
+ """
+ Specific name for which a distribution finder should match.
+ """
+
+ def __init__(self, **kwargs):
+ vars(self).update(kwargs)
+
+ @property
+ def path(self):
+ """
+ The path that a distribution finder should search.
+ """
+ return vars(self).get('path', sys.path)
+
+ @property
+ def pattern(self):
+ return '.*' if self.name is None else re.escape(self.name)
+
+ @abc.abstractmethod
+ def find_distributions(self, context=Context()):
+ """
+ Find distributions.
+
+ Return an iterable of all Distribution instances capable of
+ loading the metadata for packages matching the ``context``,
+ a DistributionFinder.Context instance.
+ """
+
+
+@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().
+ """
+
+ def find_distributions(self, context=DistributionFinder.Context()):
+ """
+ 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 = self._search_paths(context.pattern, context.path)
+ return map(PathDistribution, found)
+
+ @classmethod
+ def _search_paths(cls, pattern, paths):
+ """Find metadata directories in paths heuristically."""
+ return itertools.chain.from_iterable(
+ cls._search_path(path, pattern)
+ for path in map(cls._switch_path, paths)
+ )
+
+ @staticmethod
+ def _switch_path(path):
+ if not PYPY_OPEN_BUG or os.path.isfile(path): # pragma: no branch
+ with suppress(Exception):
+ return zipp.Path(path)
+ return pathlib.Path(path)
+
+ @classmethod
+ def _matches_info(cls, normalized, item):
+ template = r'{pattern}(-.*)?\.(dist|egg)-info'
+ manifest = template.format(pattern=normalized)
+ return re.match(manifest, item.name, flags=re.IGNORECASE)
+
+ @classmethod
+ def _matches_legacy(cls, normalized, item):
+ template = r'{pattern}-.*\.egg[\\/]EGG-INFO'
+ manifest = template.format(pattern=normalized)
+ return re.search(manifest, str(item), flags=re.IGNORECASE)
+
+ @classmethod
+ def _search_path(cls, root, pattern):
+ if not root.is_dir():
+ return ()
+ normalized = pattern.replace('-', '_')
+ return (item for item in root.iterdir()
+ if cls._matches_info(normalized, item)
+ or cls._matches_legacy(normalized, item))
+
+
+class PathDistribution(Distribution):
+ def __init__(self, path):
+ """Construct a distribution from a path to the metadata directory.
+
+ :param path: A pathlib.Path or similar object supporting
+ .joinpath(), __div__, .parent, and .read_text().
+ """
+ self._path = path
+
+ def read_text(self, filename):
+ with suppress(FileNotFoundError, IsADirectoryError, KeyError,
+ NotADirectoryError, PermissionError):
+ return self._path.joinpath(filename).read_text(encoding='utf-8')
+ read_text.__doc__ = Distribution.read_text.__doc__
+
+ def locate_file(self, path):
+ return self._path.parent / path
+
+
+def distribution(distribution_name):
+ """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):
+ """Get all ``Distribution`` instances in the current environment.
+
+ :return: An iterable of ``Distribution`` instances.
+ """
+ return Distribution.discover(**kwargs)
+
+
+def metadata(distribution_name):
+ """Get the metadata for the named package.
+
+ :param distribution_name: The name of the distribution package to query.
+ :return: An email.Message containing the parsed metadata.
+ """
+ return Distribution.from_name(distribution_name).metadata
+
+
+def version(distribution_name):
+ """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
+
+
+def entry_points():
+ """Return EntryPoint objects for all installed packages.
+
+ :return: EntryPoint objects for all installed packages.
+ """
+ eps = itertools.chain.from_iterable(
+ dist.entry_points for dist in distributions())
+ by_group = operator.attrgetter('group')
+ ordered = sorted(eps, key=by_group)
+ grouped = itertools.groupby(ordered, by_group)
+ return {
+ group: tuple(eps)
+ for group, eps in grouped
+ }
+
+
+def files(distribution_name):
+ """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):
+ """
+ Return a list of requirements for the named package.
+
+ :return: An iterator of requirements, suitable for
+ packaging.requirement.Requirement.
+ """
+ return distribution(distribution_name).requires
+
+
+__version__ = version(__name__)
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/_compat.py b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/_compat.py
new file mode 100644
index 0000000000..6e663662d2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/_compat.py
@@ -0,0 +1,143 @@
+from __future__ import absolute_import
+
+import io
+import abc
+import sys
+import email
+
+
+if sys.version_info > (3,): # pragma: nocover
+ import builtins
+ from configparser import ConfigParser
+ from contextlib import suppress
+ FileNotFoundError = builtins.FileNotFoundError
+ IsADirectoryError = builtins.IsADirectoryError
+ NotADirectoryError = builtins.NotADirectoryError
+ PermissionError = builtins.PermissionError
+ map = builtins.map
+else: # pragma: nocover
+ from backports.configparser import ConfigParser
+ from itertools import imap as map # type: ignore
+ from contextlib2 import suppress # noqa
+ FileNotFoundError = IOError, OSError
+ IsADirectoryError = IOError, OSError
+ NotADirectoryError = IOError, OSError
+ PermissionError = IOError, OSError
+
+if sys.version_info > (3, 5): # pragma: nocover
+ import pathlib
+else: # pragma: nocover
+ import pathlib2 as pathlib
+
+try:
+ ModuleNotFoundError = builtins.FileNotFoundError
+except (NameError, AttributeError): # pragma: nocover
+ ModuleNotFoundError = ImportError # type: ignore
+
+
+if sys.version_info >= (3,): # pragma: nocover
+ from importlib.abc import MetaPathFinder
+else: # pragma: nocover
+ class MetaPathFinder(object):
+ __metaclass__ = abc.ABCMeta
+
+
+__metaclass__ = type
+__all__ = [
+ 'install', 'NullFinder', 'MetaPathFinder', 'ModuleNotFoundError',
+ 'pathlib', 'ConfigParser', 'map', 'suppress', 'FileNotFoundError',
+ 'NotADirectoryError', 'email_message_from_string',
+ ]
+
+
+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 (
+ finder.__module__ == '_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 "MetaClassFinder") that never finds any modules,
+ but may find distributions.
+ """
+ @staticmethod
+ def find_spec(*args, **kwargs):
+ return None
+
+ # In Python 2, the import system requires finders
+ # to have a find_module() method, but this usage
+ # is deprecated in Python 3 in favor of find_spec().
+ # For the purposes of this finder (i.e. being present
+ # on sys.meta_path but having no other import
+ # system functionality), the two methods are identical.
+ find_module = find_spec
+
+
+def py2_message_from_string(text): # nocoverpy3
+ # Work around https://bugs.python.org/issue25545 where
+ # email.message_from_string cannot handle Unicode on Python 2.
+ io_buffer = io.StringIO(text)
+ return email.message_from_file(io_buffer)
+
+
+email_message_from_string = (
+ py2_message_from_string
+ if sys.version_info < (3,) else
+ email.message_from_string
+ )
+
+# https://bitbucket.org/pypy/pypy/issues/3021/ioopen-directory-leaks-a-file-descriptor
+PYPY_OPEN_BUG = getattr(sys, 'pypy_version_info', (9, 9, 9))[:3] <= (7, 1, 1)
+
+
+def ensure_is_path(ob):
+ """Construct a Path from ob even if it's already one.
+ Specialized for Python 3.4.
+ """
+ if (3,) < sys.version_info < (3, 5):
+ ob = str(ob) # pragma: nocover
+ return pathlib.Path(ob)
+
+
+class PyPy_repr:
+ """
+ Override repr for EntryPoint objects on PyPy to avoid __iter__ access.
+ Ref #97, #102.
+ """
+ affected = hasattr(sys, 'pypy_version_info')
+
+ def __compat_repr__(self): # pragma: nocover
+ def make_param(name):
+ value = getattr(self, name)
+ return '{name}={value!r}'.format(**locals())
+ params = ', '.join(map(make_param, self._fields))
+ return 'EntryPoint({params})'.format(**locals())
+
+ if affected: # pragma: nocover
+ __repr__ = __compat_repr__
+ del affected
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/LICENSE
new file mode 100644
index 0000000000..be7e092b0b
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2017-2019 Jason R. Coombs, Barry Warsaw
+
+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/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/METADATA
new file mode 100644
index 0000000000..cda63e8502
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/METADATA
@@ -0,0 +1,73 @@
+Metadata-Version: 2.1
+Name: importlib-metadata
+Version: 2.1.1
+Summary: Read metadata from Python packages
+Home-page: http://importlib-metadata.readthedocs.io/
+Author: Jason R. Coombs
+Author-email: jaraco@jaraco.com
+License: Apache Software License
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 2
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
+Requires-Dist: zipp (>=0.5)
+Requires-Dist: pathlib2 ; python_version < "3"
+Requires-Dist: contextlib2 ; python_version < "3"
+Requires-Dist: configparser (>=3.5) ; python_version < "3"
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: rst.linker ; extra == 'docs'
+Provides-Extra: testing
+Requires-Dist: packaging ; extra == 'testing'
+Requires-Dist: pep517 ; extra == 'testing'
+Requires-Dist: unittest2 ; (python_version < "3") and extra == 'testing'
+Requires-Dist: importlib-resources (>=1.3) ; (python_version < "3.9") and extra == 'testing'
+
+=========================
+ ``importlib_metadata``
+=========================
+
+``importlib_metadata`` is a library to access the metadata for a
+Python package.
+
+As of Python 3.8, this functionality has been added to the
+`Python standard library
+<https://docs.python.org/3/library/importlib.metadata.html>`_.
+This package supplies backports of that functionality including
+improvements added to subsequent Python versions.
+
+
+Usage
+=====
+
+See the `online documentation <https://importlib_metadata.readthedocs.io/>`_
+for usage details.
+
+`Finder authors
+<https://docs.python.org/3/reference/import.html#finders-and-loaders>`_ 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/
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/RECORD
new file mode 100644
index 0000000000..301525c826
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/RECORD
@@ -0,0 +1,7 @@
+importlib_metadata/__init__.py,sha256=uaN7KDLs3-irvgwsxg4VZIuY3ZEo3jhu1dShjE7fR88,19587
+importlib_metadata/_compat.py,sha256=DnM55BbJKFCcZmJOkArmyO76-0g7pA6HEfzSYWXN88k,4417
+importlib_metadata-2.1.1.dist-info/LICENSE,sha256=wNe6dAchmJ1VvVB8D9oTc-gHHadCuaSBAev36sYEM6U,571
+importlib_metadata-2.1.1.dist-info/METADATA,sha256=gBf5nX-Ff6_Ue9dSH4dkWg2FCNHHtQrs6mhunTAac8k,2421
+importlib_metadata-2.1.1.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110
+importlib_metadata-2.1.1.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
+importlib_metadata-2.1.1.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/WHEEL
new file mode 100644
index 0000000000..6d38aa0601
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.35.1)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/top_level.txt
new file mode 100644
index 0000000000..bbb07547a1
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+importlib_metadata
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/__init__.py b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/__init__.py
new file mode 100644
index 0000000000..e296a2c7bd
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/__init__.py
@@ -0,0 +1,644 @@
+from __future__ import unicode_literals, absolute_import
+
+import io
+import os
+import re
+import abc
+import csv
+import sys
+import zipp
+import operator
+import functools
+import itertools
+import posixpath
+import collections
+
+from ._compat import (
+ install,
+ NullFinder,
+ ConfigParser,
+ suppress,
+ map,
+ FileNotFoundError,
+ IsADirectoryError,
+ NotADirectoryError,
+ PermissionError,
+ pathlib,
+ ModuleNotFoundError,
+ MetaPathFinder,
+ email_message_from_string,
+ PyPy_repr,
+ unique_ordered,
+ str,
+ )
+from importlib import import_module
+from itertools import starmap
+
+
+__metaclass__ = type
+
+
+__all__ = [
+ 'Distribution',
+ 'DistributionFinder',
+ 'PackageNotFoundError',
+ 'distribution',
+ 'distributions',
+ 'entry_points',
+ 'files',
+ 'metadata',
+ 'requires',
+ 'version',
+ ]
+
+
+class PackageNotFoundError(ModuleNotFoundError):
+ """The package was not found."""
+
+ def __str__(self):
+ tmpl = "No package metadata was found for {self.name}"
+ return tmpl.format(**locals())
+
+ @property
+ def name(self):
+ name, = self.args
+ return name
+
+
+class EntryPoint(
+ PyPy_repr,
+ collections.namedtuple('EntryPointBase', 'name value group')):
+ """An entry point as defined by Python packaging conventions.
+
+ See `the packaging docs on entry points
+ <https://packaging.python.org/specifications/entry-points/>`_
+ for more information.
+ """
+
+ pattern = re.compile(
+ r'(?P<module>[\w.]+)\s*'
+ r'(:\s*(?P<attr>[\w.]+))?\s*'
+ r'(?P<extras>\[.*\])?\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.
+ """
+
+ def load(self):
+ """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 = 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):
+ match = self.pattern.match(self.value)
+ return match.group('module')
+
+ @property
+ def attr(self):
+ match = self.pattern.match(self.value)
+ return match.group('attr')
+
+ @property
+ def extras(self):
+ match = self.pattern.match(self.value)
+ return list(re.finditer(r'\w+', match.group('extras') or ''))
+
+ @classmethod
+ def _from_config(cls, config):
+ return [
+ cls(name, value, group)
+ for group in config.sections()
+ for name, value in config.items(group)
+ ]
+
+ @classmethod
+ def _from_text(cls, text):
+ config = ConfigParser(delimiters='=')
+ # case sensitive: https://stackoverflow.com/q/1611799/812183
+ config.optionxform = str
+ try:
+ config.read_string(text)
+ except AttributeError: # pragma: nocover
+ # Python 2 has no read_string
+ config.readfp(io.StringIO(text))
+ return EntryPoint._from_config(config)
+
+ def __iter__(self):
+ """
+ Supply iter so one may construct dicts of EntryPoints easily.
+ """
+ return iter((self.name, self))
+
+ def __reduce__(self):
+ return (
+ self.__class__,
+ (self.name, self.value, self.group),
+ )
+
+
+class PackagePath(pathlib.PurePosixPath):
+ """A reference to a path in a package"""
+
+ def read_text(self, encoding='utf-8'):
+ with self.locate().open(encoding=encoding) as stream:
+ return stream.read()
+
+ def read_binary(self):
+ with self.locate().open('rb') as stream:
+ return stream.read()
+
+ def locate(self):
+ """Return a path-like object for this path"""
+ return self.dist.locate_file(self)
+
+
+class FileHash:
+ def __init__(self, spec):
+ self.mode, _, self.value = spec.partition('=')
+
+ def __repr__(self):
+ return '<FileHash mode: {} value: {}>'.format(self.mode, self.value)
+
+
+class Distribution:
+ """A Python distribution package."""
+
+ @abc.abstractmethod
+ def read_text(self, filename):
+ """Attempt to load metadata file given by the name.
+
+ :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):
+ """
+ Given a path to a file in this distribution, return a path
+ to it.
+ """
+
+ @classmethod
+ def from_name(cls, name):
+ """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.
+ """
+ for resolver in cls._discover_resolvers():
+ dists = resolver(DistributionFinder.Context(name=name))
+ dist = next(iter(dists), None)
+ if dist is not None:
+ return dist
+ else:
+ raise PackageNotFoundError(name)
+
+ @classmethod
+ def discover(cls, **kwargs):
+ """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 all packages.
+ """
+ context = kwargs.pop('context', None)
+ 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 at(path):
+ """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."""
+ declared = (
+ getattr(finder, 'find_distributions', None)
+ for finder in sys.meta_path
+ )
+ return filter(None, declared)
+
+ @classmethod
+ def _local(cls, root='.'):
+ from pep517 import build, meta
+ system = build.compat_system(root)
+ builder = functools.partial(
+ meta.build,
+ source_dir=root,
+ system=system,
+ )
+ return PathDistribution(zipp.Path(meta.build_as_zip(builder)))
+
+ @property
+ def metadata(self):
+ """Return the parsed metadata for this Distribution.
+
+ The returned object will have keys that name the various bits of
+ metadata. See PEP 566 for details.
+ """
+ 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('')
+ )
+ return email_message_from_string(text)
+
+ @property
+ def version(self):
+ """Return the 'Version' metadata for the distribution package."""
+ return self.metadata['Version']
+
+ @property
+ def entry_points(self):
+ return EntryPoint._from_text(self.read_text('entry_points.txt'))
+
+ @property
+ def files(self):
+ """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 SOURCES.txt for egg-info) is
+ missing.
+ Result may be empty if the metadata exists but is empty.
+ """
+ file_lines = self._read_files_distinfo() or self._read_files_egginfo()
+
+ 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
+
+ return file_lines and list(starmap(make_file, csv.reader(file_lines)))
+
+ def _read_files_distinfo(self):
+ """
+ Read the lines of RECORD
+ """
+ text = self.read_text('RECORD')
+ return text and text.splitlines()
+
+ def _read_files_egginfo(self):
+ """
+ SOURCES.txt might contain literal commas, so wrap each line
+ in quotes.
+ """
+ text = self.read_text('SOURCES.txt')
+ return text and map('"{}"'.format, text.splitlines())
+
+ @property
+ def requires(self):
+ """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 source and self._deps_from_requires_text(source)
+
+ @classmethod
+ def _deps_from_requires_text(cls, source):
+ section_pairs = cls._read_sections(source.splitlines())
+ sections = {
+ section: list(map(operator.itemgetter('line'), results))
+ for section, results in
+ itertools.groupby(section_pairs, operator.itemgetter('section'))
+ }
+ return cls._convert_egg_info_reqs_to_simple_reqs(sections)
+
+ @staticmethod
+ def _read_sections(lines):
+ section = None
+ for line in filter(None, lines):
+ section_match = re.match(r'\[(.*)\]$', line)
+ if section_match:
+ section = section_match.group(1)
+ continue
+ yield locals()
+
+ @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 'extra == "{name}"'.format(name=name)
+
+ def parse_condition(section):
+ section = section or ''
+ extra, sep, markers = section.partition(':')
+ if extra and markers:
+ markers = '({markers})'.format(markers=markers)
+ conditions = list(filter(None, [markers, make_condition(extra)]))
+ return '; ' + ' and '.join(conditions) if conditions else ''
+
+ for section, deps in sections.items():
+ for dep in deps:
+ yield dep + parse_condition(section)
+
+
+class DistributionFinder(MetaPathFinder):
+ """
+ A MetaPathFinder capable of discovering installed distributions.
+ """
+
+ 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.
+ """
+
+ 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):
+ """
+ The path that a distribution finder should search.
+
+ Typically refers to Python package paths and defaults
+ to ``sys.path``.
+ """
+ return vars(self).get('path', sys.path)
+
+ @abc.abstractmethod
+ def find_distributions(self, context=Context()):
+ """
+ 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 path for
+ children.
+ """
+
+ def __init__(self, root):
+ self.root = str(root)
+ self.base = os.path.basename(self.root).lower()
+
+ 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):
+ zip_path = zipp.Path(self.root)
+ names = zip_path.root.namelist()
+ self.joinpath = zip_path.joinpath
+
+ return unique_ordered(
+ child.split(posixpath.sep, 1)[0]
+ for child in names
+ )
+
+ def search(self, name):
+ return (
+ self.joinpath(child)
+ for child in self.children()
+ if name.matches(child, self.base)
+ )
+
+
+class Prepared:
+ """
+ A prepared search for metadata on a possibly-named package.
+ """
+ normalized = None
+ suffixes = '.dist-info', '.egg-info'
+ exact_matches = [''][:0]
+
+ def __init__(self, name):
+ self.name = name
+ if name is None:
+ return
+ self.normalized = self.normalize(name)
+ self.exact_matches = [
+ self.normalized + suffix for suffix in self.suffixes]
+
+ @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 matches(self, cand, base):
+ low = cand.lower()
+ pre, ext = os.path.splitext(low)
+ name, sep, rest = pre.partition('-')
+ return (
+ low in self.exact_matches
+ or ext in self.suffixes and (
+ not self.normalized or
+ name.replace('.', '_') == self.normalized
+ )
+ # legacy case:
+ or self.is_egg(base) and low == 'egg-info'
+ )
+
+ def is_egg(self, base):
+ normalized = self.legacy_normalize(self.name or '')
+ prefix = normalized + '-' if normalized else ''
+ versionless_egg_name = normalized + '.egg' if self.name else ''
+ return (
+ base == versionless_egg_name
+ or base.startswith(prefix)
+ and base.endswith('.egg'))
+
+
+@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().
+ """
+
+ def find_distributions(self, context=DistributionFinder.Context()):
+ """
+ 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 = self._search_paths(context.name, context.path)
+ return map(PathDistribution, found)
+
+ @classmethod
+ def _search_paths(cls, name, paths):
+ """Find metadata directories in paths heuristically."""
+ return itertools.chain.from_iterable(
+ path.search(Prepared(name))
+ for path in map(FastPath, paths)
+ )
+
+
+class PathDistribution(Distribution):
+ def __init__(self, path):
+ """Construct a distribution from a path to the metadata directory.
+
+ :param path: A pathlib.Path or similar object supporting
+ .joinpath(), __div__, .parent, and .read_text().
+ """
+ self._path = path
+
+ def read_text(self, filename):
+ with suppress(FileNotFoundError, IsADirectoryError, KeyError,
+ NotADirectoryError, PermissionError):
+ return self._path.joinpath(filename).read_text(encoding='utf-8')
+ read_text.__doc__ = Distribution.read_text.__doc__
+
+ def locate_file(self, path):
+ return self._path.parent / path
+
+
+def distribution(distribution_name):
+ """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):
+ """Get all ``Distribution`` instances in the current environment.
+
+ :return: An iterable of ``Distribution`` instances.
+ """
+ return Distribution.discover(**kwargs)
+
+
+def metadata(distribution_name):
+ """Get the metadata for the named package.
+
+ :param distribution_name: The name of the distribution package to query.
+ :return: An email.Message containing the parsed metadata.
+ """
+ return Distribution.from_name(distribution_name).metadata
+
+
+def version(distribution_name):
+ """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
+
+
+def entry_points():
+ """Return EntryPoint objects for all installed packages.
+
+ :return: EntryPoint objects for all installed packages.
+ """
+ eps = itertools.chain.from_iterable(
+ dist.entry_points for dist in distributions())
+ by_group = operator.attrgetter('group')
+ ordered = sorted(eps, key=by_group)
+ grouped = itertools.groupby(ordered, by_group)
+ return {
+ group: tuple(eps)
+ for group, eps in grouped
+ }
+
+
+def files(distribution_name):
+ """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):
+ """
+ Return a list of requirements for the named package.
+
+ :return: An iterator of requirements, suitable for
+ packaging.requirement.Requirement.
+ """
+ return distribution(distribution_name).requires
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/_compat.py b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/_compat.py
new file mode 100644
index 0000000000..303d4a22e8
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/_compat.py
@@ -0,0 +1,152 @@
+from __future__ import absolute_import, unicode_literals
+
+import io
+import abc
+import sys
+import email
+
+
+if sys.version_info > (3,): # pragma: nocover
+ import builtins
+ from configparser import ConfigParser
+ import contextlib
+ FileNotFoundError = builtins.FileNotFoundError
+ IsADirectoryError = builtins.IsADirectoryError
+ NotADirectoryError = builtins.NotADirectoryError
+ PermissionError = builtins.PermissionError
+ map = builtins.map
+ from itertools import filterfalse
+else: # pragma: nocover
+ from backports.configparser import ConfigParser
+ from itertools import imap as map # type: ignore
+ from itertools import ifilterfalse as filterfalse
+ import contextlib2 as contextlib
+ FileNotFoundError = IOError, OSError
+ IsADirectoryError = IOError, OSError
+ NotADirectoryError = IOError, OSError
+ PermissionError = IOError, OSError
+
+str = type('')
+
+suppress = contextlib.suppress
+
+if sys.version_info > (3, 5): # pragma: nocover
+ import pathlib
+else: # pragma: nocover
+ import pathlib2 as pathlib
+
+try:
+ ModuleNotFoundError = builtins.FileNotFoundError
+except (NameError, AttributeError): # pragma: nocover
+ ModuleNotFoundError = ImportError # type: ignore
+
+
+if sys.version_info >= (3,): # pragma: nocover
+ from importlib.abc import MetaPathFinder
+else: # pragma: nocover
+ class MetaPathFinder(object):
+ __metaclass__ = abc.ABCMeta
+
+
+__metaclass__ = type
+__all__ = [
+ 'install', 'NullFinder', 'MetaPathFinder', 'ModuleNotFoundError',
+ 'pathlib', 'ConfigParser', 'map', 'suppress', 'FileNotFoundError',
+ 'NotADirectoryError', 'email_message_from_string',
+ ]
+
+
+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 "MetaClassFinder") that never finds any modules,
+ but may find distributions.
+ """
+ @staticmethod
+ def find_spec(*args, **kwargs):
+ return None
+
+ # In Python 2, the import system requires finders
+ # to have a find_module() method, but this usage
+ # is deprecated in Python 3 in favor of find_spec().
+ # For the purposes of this finder (i.e. being present
+ # on sys.meta_path but having no other import
+ # system functionality), the two methods are identical.
+ find_module = find_spec
+
+
+def py2_message_from_string(text): # nocoverpy3
+ # Work around https://bugs.python.org/issue25545 where
+ # email.message_from_string cannot handle Unicode on Python 2.
+ io_buffer = io.StringIO(text)
+ return email.message_from_file(io_buffer)
+
+
+email_message_from_string = (
+ py2_message_from_string
+ if sys.version_info < (3,) else
+ email.message_from_string
+ )
+
+
+class PyPy_repr:
+ """
+ Override repr for EntryPoint objects on PyPy to avoid __iter__ access.
+ Ref #97, #102.
+ """
+ affected = hasattr(sys, 'pypy_version_info')
+
+ def __compat_repr__(self): # pragma: nocover
+ def make_param(name):
+ value = getattr(self, name)
+ return '{name}={value!r}'.format(**locals())
+ params = ', '.join(map(make_param, self._fields))
+ return 'EntryPoint({params})'.format(**locals())
+
+ if affected: # pragma: nocover
+ __repr__ = __compat_repr__
+ del affected
+
+
+# from itertools recipes
+def unique_everseen(iterable): # pragma: nocover
+ "List unique elements, preserving order. Remember all elements ever seen."
+ seen = set()
+ seen_add = seen.add
+
+ for element in filterfalse(seen.__contains__, iterable):
+ seen_add(element)
+ yield element
+
+
+unique_ordered = (
+ unique_everseen if sys.version_info < (3, 7) else dict.fromkeys)
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/LICENSE
new file mode 100644
index 0000000000..be7e092b0b
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2017-2019 Jason R. Coombs, Barry Warsaw
+
+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/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/METADATA
new file mode 100644
index 0000000000..ce9f563a78
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/METADATA
@@ -0,0 +1,94 @@
+Metadata-Version: 2.1
+Name: importlib-metadata
+Version: 3.1.1
+Summary: Read metadata from Python packages
+Home-page: https://github.com/python/importlib_metadata
+Author: Jason R. Coombs
+Author-email: jaraco@jaraco.com
+License: UNKNOWN
+Platform: UNKNOWN
+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.6
+Requires-Dist: zipp (>=0.5)
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs'
+Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
+Provides-Extra: testing
+Requires-Dist: pytest (!=3.7.3,>=3.5) ; extra == 'testing'
+Requires-Dist: pytest-checkdocs (>=1.2.3) ; extra == 'testing'
+Requires-Dist: pytest-flake8 ; extra == 'testing'
+Requires-Dist: pytest-cov ; extra == 'testing'
+Requires-Dist: jaraco.test (>=3.2.0) ; extra == 'testing'
+Requires-Dist: packaging ; extra == 'testing'
+Requires-Dist: pep517 ; extra == 'testing'
+Requires-Dist: pyfakefs ; extra == 'testing'
+Requires-Dist: flufl.flake8 ; extra == 'testing'
+Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing'
+Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing'
+Requires-Dist: importlib-resources (>=1.3) ; (python_version < "3.9") and extra == 'testing'
+
+.. image:: https://img.shields.io/pypi/v/importlib_metadata.svg
+ :target: `PyPI link`_
+
+.. image:: https://img.shields.io/pypi/pyversions/importlib_metadata.svg
+ :target: `PyPI link`_
+
+.. _PyPI link: https://pypi.org/project/importlib_metadata
+
+.. image:: https://github.com/python/importlib_metadata/workflows/Automated%20Tests/badge.svg
+ :target: https://github.com/python/importlib_metadata/actions?query=workflow%3A%22Automated+Tests%22
+ :alt: Automated Tests
+
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+ :target: https://github.com/psf/black
+ :alt: Code style: Black
+
+.. image:: https://readthedocs.org/projects/importlib-metadata/badge/?version=latest
+ :target: https://importlib-metadata.readthedocs.io/en/latest/?badge=latest
+
+
+``importlib_metadata`` is a library to access the metadata for a
+Python package.
+
+As of Python 3.8, this functionality has been added to the
+`Python standard library
+<https://docs.python.org/3/library/importlib.metadata.html>`_.
+This package supplies backports of that functionality including
+improvements added to subsequent Python versions.
+
+
+Usage
+=====
+
+See the `online documentation <https://importlib_metadata.readthedocs.io/>`_
+for usage details.
+
+`Finder authors
+<https://docs.python.org/3/reference/import.html#finders-and-loaders>`_ 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/
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/RECORD
new file mode 100644
index 0000000000..89bbf7e386
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/RECORD
@@ -0,0 +1,7 @@
+importlib_metadata/__init__.py,sha256=QM4Oo096u6JYeokkDUwHgazI_h3o0w9tISPjHtVko_U,19266
+importlib_metadata/_compat.py,sha256=OS4joET_vaQClxhumw0NWYdS5N3FX1Ii895aZXLpQaA,2028
+importlib_metadata-3.1.1.dist-info/LICENSE,sha256=wNe6dAchmJ1VvVB8D9oTc-gHHadCuaSBAev36sYEM6U,571
+importlib_metadata-3.1.1.dist-info/METADATA,sha256=rdblRVlpAdjDcYkqWhn2yVNwrpBqpamdKvxrgA6EWE0,3442
+importlib_metadata-3.1.1.dist-info/WHEEL,sha256=gm79cMopkncyn0iSnI0vQNiDJ8t9on0H4_iz-CrpXMk,92
+importlib_metadata-3.1.1.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
+importlib_metadata-3.1.1.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/WHEEL
new file mode 100644
index 0000000000..0863016bc2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.36.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/top_level.txt
new file mode 100644
index 0000000000..bbb07547a1
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+importlib_metadata
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/__init__.py b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/__init__.py
new file mode 100644
index 0000000000..eec9195367
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/__init__.py
@@ -0,0 +1,631 @@
+import io
+import os
+import re
+import abc
+import csv
+import sys
+import zipp
+import email
+import pathlib
+import operator
+import functools
+import itertools
+import posixpath
+import collections
+
+from ._compat import (
+ NullFinder,
+ PyPy_repr,
+ install,
+)
+
+from configparser import ConfigParser
+from contextlib import suppress
+from importlib import import_module
+from importlib.abc import MetaPathFinder
+from itertools import starmap
+
+
+__all__ = [
+ 'Distribution',
+ 'DistributionFinder',
+ 'PackageNotFoundError',
+ 'distribution',
+ 'distributions',
+ 'entry_points',
+ 'files',
+ 'metadata',
+ 'requires',
+ 'version',
+]
+
+
+class PackageNotFoundError(ModuleNotFoundError):
+ """The package was not found."""
+
+ def __str__(self):
+ tmpl = "No package metadata was found for {self.name}"
+ return tmpl.format(**locals())
+
+ @property
+ def name(self):
+ (name,) = self.args
+ return name
+
+
+class EntryPoint(
+ PyPy_repr, collections.namedtuple('EntryPointBase', 'name value group')
+):
+ """An entry point as defined by Python packaging conventions.
+
+ See `the packaging docs on entry points
+ <https://packaging.python.org/specifications/entry-points/>`_
+ for more information.
+ """
+
+ pattern = re.compile(
+ r'(?P<module>[\w.]+)\s*'
+ r'(:\s*(?P<attr>[\w.]+))?\s*'
+ r'(?P<extras>\[.*\])?\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.
+ """
+
+ def load(self):
+ """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 = 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):
+ match = self.pattern.match(self.value)
+ return match.group('module')
+
+ @property
+ def attr(self):
+ match = self.pattern.match(self.value)
+ return match.group('attr')
+
+ @property
+ def extras(self):
+ match = self.pattern.match(self.value)
+ return list(re.finditer(r'\w+', match.group('extras') or ''))
+
+ @classmethod
+ def _from_config(cls, config):
+ return [
+ cls(name, value, group)
+ for group in config.sections()
+ for name, value in config.items(group)
+ ]
+
+ @classmethod
+ def _from_text(cls, text):
+ config = ConfigParser(delimiters='=')
+ # case sensitive: https://stackoverflow.com/q/1611799/812183
+ config.optionxform = str
+ try:
+ config.read_string(text)
+ except AttributeError: # pragma: nocover
+ # Python 2 has no read_string
+ config.readfp(io.StringIO(text))
+ return EntryPoint._from_config(config)
+
+ def __iter__(self):
+ """
+ Supply iter so one may construct dicts of EntryPoints easily.
+ """
+ return iter((self.name, self))
+
+ def __reduce__(self):
+ return (
+ self.__class__,
+ (self.name, self.value, self.group),
+ )
+
+
+class PackagePath(pathlib.PurePosixPath):
+ """A reference to a path in a package"""
+
+ def read_text(self, encoding='utf-8'):
+ with self.locate().open(encoding=encoding) as stream:
+ return stream.read()
+
+ def read_binary(self):
+ with self.locate().open('rb') as stream:
+ return stream.read()
+
+ def locate(self):
+ """Return a path-like object for this path"""
+ return self.dist.locate_file(self)
+
+
+class FileHash:
+ def __init__(self, spec):
+ self.mode, _, self.value = spec.partition('=')
+
+ def __repr__(self):
+ return '<FileHash mode: {} value: {}>'.format(self.mode, self.value)
+
+
+class Distribution:
+ """A Python distribution package."""
+
+ @abc.abstractmethod
+ def read_text(self, filename):
+ """Attempt to load metadata file given by the name.
+
+ :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):
+ """
+ Given a path to a file in this distribution, return a path
+ to it.
+ """
+
+ @classmethod
+ def from_name(cls, name):
+ """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.
+ """
+ for resolver in cls._discover_resolvers():
+ dists = resolver(DistributionFinder.Context(name=name))
+ dist = next(iter(dists), None)
+ if dist is not None:
+ return dist
+ else:
+ raise PackageNotFoundError(name)
+
+ @classmethod
+ def discover(cls, **kwargs):
+ """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 all packages.
+ """
+ context = kwargs.pop('context', None)
+ 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 at(path):
+ """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."""
+ declared = (
+ getattr(finder, 'find_distributions', None) for finder in sys.meta_path
+ )
+ return filter(None, declared)
+
+ @classmethod
+ def _local(cls, root='.'):
+ from pep517 import build, meta
+
+ system = build.compat_system(root)
+ builder = functools.partial(
+ meta.build,
+ source_dir=root,
+ system=system,
+ )
+ return PathDistribution(zipp.Path(meta.build_as_zip(builder)))
+
+ @property
+ def metadata(self):
+ """Return the parsed metadata for this Distribution.
+
+ The returned object will have keys that name the various bits of
+ metadata. See PEP 566 for details.
+ """
+ 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('')
+ )
+ return email.message_from_string(text)
+
+ @property
+ def version(self):
+ """Return the 'Version' metadata for the distribution package."""
+ return self.metadata['Version']
+
+ @property
+ def entry_points(self):
+ return EntryPoint._from_text(self.read_text('entry_points.txt'))
+
+ @property
+ def files(self):
+ """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 SOURCES.txt for egg-info) is
+ missing.
+ Result may be empty if the metadata exists but is empty.
+ """
+ file_lines = self._read_files_distinfo() or self._read_files_egginfo()
+
+ 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
+
+ return file_lines and list(starmap(make_file, csv.reader(file_lines)))
+
+ def _read_files_distinfo(self):
+ """
+ Read the lines of RECORD
+ """
+ text = self.read_text('RECORD')
+ return text and text.splitlines()
+
+ def _read_files_egginfo(self):
+ """
+ SOURCES.txt might contain literal commas, so wrap each line
+ in quotes.
+ """
+ text = self.read_text('SOURCES.txt')
+ return text and map('"{}"'.format, text.splitlines())
+
+ @property
+ def requires(self):
+ """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 source and self._deps_from_requires_text(source)
+
+ @classmethod
+ def _deps_from_requires_text(cls, source):
+ section_pairs = cls._read_sections(source.splitlines())
+ sections = {
+ section: list(map(operator.itemgetter('line'), results))
+ for section, results in itertools.groupby(
+ section_pairs, operator.itemgetter('section')
+ )
+ }
+ return cls._convert_egg_info_reqs_to_simple_reqs(sections)
+
+ @staticmethod
+ def _read_sections(lines):
+ section = None
+ for line in filter(None, lines):
+ section_match = re.match(r'\[(.*)\]$', line)
+ if section_match:
+ section = section_match.group(1)
+ continue
+ yield locals()
+
+ @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 'extra == "{name}"'.format(name=name)
+
+ def parse_condition(section):
+ section = section or ''
+ extra, sep, markers = section.partition(':')
+ if extra and markers:
+ markers = '({markers})'.format(markers=markers)
+ conditions = list(filter(None, [markers, make_condition(extra)]))
+ return '; ' + ' and '.join(conditions) if conditions else ''
+
+ for section, deps in sections.items():
+ for dep in deps:
+ yield dep + parse_condition(section)
+
+
+class DistributionFinder(MetaPathFinder):
+ """
+ A MetaPathFinder capable of discovering installed distributions.
+ """
+
+ 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.
+ """
+
+ 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):
+ """
+ The path that a distribution finder should search.
+
+ Typically refers to Python package paths and defaults
+ to ``sys.path``.
+ """
+ return vars(self).get('path', sys.path)
+
+ @abc.abstractmethod
+ def find_distributions(self, context=Context()):
+ """
+ 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 path for
+ children.
+ """
+
+ def __init__(self, root):
+ self.root = str(root)
+ self.base = os.path.basename(self.root).lower()
+
+ 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):
+ zip_path = zipp.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.joinpath(child)
+ for child in self.children()
+ if name.matches(child, self.base)
+ )
+
+
+class Prepared:
+ """
+ A prepared search for metadata on a possibly-named package.
+ """
+
+ normalized = None
+ suffixes = '.dist-info', '.egg-info'
+ exact_matches = [''][:0]
+
+ def __init__(self, name):
+ self.name = name
+ if name is None:
+ return
+ self.normalized = self.normalize(name)
+ self.exact_matches = [self.normalized + suffix for suffix in self.suffixes]
+
+ @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 matches(self, cand, base):
+ low = cand.lower()
+ pre, ext = os.path.splitext(low)
+ name, sep, rest = pre.partition('-')
+ return (
+ low in self.exact_matches
+ or ext in self.suffixes
+ and (not self.normalized or name.replace('.', '_') == self.normalized)
+ # legacy case:
+ or self.is_egg(base)
+ and low == 'egg-info'
+ )
+
+ def is_egg(self, base):
+ normalized = self.legacy_normalize(self.name or '')
+ prefix = normalized + '-' if normalized else ''
+ versionless_egg_name = normalized + '.egg' if self.name else ''
+ return (
+ base == versionless_egg_name
+ or base.startswith(prefix)
+ and base.endswith('.egg')
+ )
+
+
+@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().
+ """
+
+ def find_distributions(self, context=DistributionFinder.Context()):
+ """
+ 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 = self._search_paths(context.name, context.path)
+ return map(PathDistribution, found)
+
+ @classmethod
+ def _search_paths(cls, name, paths):
+ """Find metadata directories in paths heuristically."""
+ return itertools.chain.from_iterable(
+ path.search(Prepared(name)) for path in map(FastPath, paths)
+ )
+
+
+class PathDistribution(Distribution):
+ def __init__(self, path):
+ """Construct a distribution from a path to the metadata directory.
+
+ :param path: A pathlib.Path or similar object supporting
+ .joinpath(), __div__, .parent, and .read_text().
+ """
+ self._path = path
+
+ def read_text(self, filename):
+ with suppress(
+ FileNotFoundError,
+ IsADirectoryError,
+ KeyError,
+ NotADirectoryError,
+ PermissionError,
+ ):
+ return self._path.joinpath(filename).read_text(encoding='utf-8')
+
+ read_text.__doc__ = Distribution.read_text.__doc__
+
+ def locate_file(self, path):
+ return self._path.parent / path
+
+
+def distribution(distribution_name):
+ """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):
+ """Get all ``Distribution`` instances in the current environment.
+
+ :return: An iterable of ``Distribution`` instances.
+ """
+ return Distribution.discover(**kwargs)
+
+
+def metadata(distribution_name):
+ """Get the metadata for the named package.
+
+ :param distribution_name: The name of the distribution package to query.
+ :return: An email.Message containing the parsed metadata.
+ """
+ return Distribution.from_name(distribution_name).metadata
+
+
+def version(distribution_name):
+ """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
+
+
+def entry_points():
+ """Return EntryPoint objects for all installed packages.
+
+ :return: EntryPoint objects for all installed packages.
+ """
+ eps = itertools.chain.from_iterable(dist.entry_points for dist in distributions())
+ by_group = operator.attrgetter('group')
+ ordered = sorted(eps, key=by_group)
+ grouped = itertools.groupby(ordered, by_group)
+ return {group: tuple(eps) for group, eps in grouped}
+
+
+def files(distribution_name):
+ """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):
+ """
+ Return a list of requirements for the named package.
+
+ :return: An iterator of requirements, suitable for
+ packaging.requirement.Requirement.
+ """
+ return distribution(distribution_name).requires
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/_compat.py b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/_compat.py
new file mode 100644
index 0000000000..c1362d5360
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/_compat.py
@@ -0,0 +1,75 @@
+import sys
+
+
+__all__ = ['install', 'NullFinder', 'PyPy_repr']
+
+
+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 "MetaClassFinder") that never finds any modules,
+ but may find distributions.
+ """
+
+ @staticmethod
+ def find_spec(*args, **kwargs):
+ return None
+
+ # In Python 2, the import system requires finders
+ # to have a find_module() method, but this usage
+ # is deprecated in Python 3 in favor of find_spec().
+ # For the purposes of this finder (i.e. being present
+ # on sys.meta_path but having no other import
+ # system functionality), the two methods are identical.
+ find_module = find_spec
+
+
+class PyPy_repr:
+ """
+ Override repr for EntryPoint objects on PyPy to avoid __iter__ access.
+ Ref #97, #102.
+ """
+
+ affected = hasattr(sys, 'pypy_version_info')
+
+ def __compat_repr__(self): # pragma: nocover
+ def make_param(name):
+ value = getattr(self, name)
+ return '{name}={value!r}'.format(**locals())
+
+ params = ', '.join(map(make_param, self._fields))
+ return 'EntryPoint({params})'.format(**locals())
+
+ if affected: # pragma: nocover
+ __repr__ = __compat_repr__
+ del affected
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/LICENSE
new file mode 100644
index 0000000000..7e4791068d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2017-2018 Brett Cannon, Barry Warsaw
+
+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/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/METADATA
new file mode 100644
index 0000000000..8eb23366fa
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/METADATA
@@ -0,0 +1,49 @@
+Metadata-Version: 2.1
+Name: importlib-resources
+Version: 1.0.2
+Summary: Read resources from Python packages
+Home-page: http://importlib-resources.readthedocs.io/
+Author: Barry Warsaw
+Author-email: barry@python.org
+License: Apache Software License
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Requires-Python: >=2.7,!=3.0,!=3.1,!=3.2,!=3.3
+Requires-Dist: pathlib2; python_version < "3"
+Requires-Dist: typing; python_version < "3.5"
+
+=========================
+ ``importlib_resources``
+=========================
+
+``importlib_resources`` is a backport of Python 3.7's standard library
+`importlib.resources
+<https://docs.python.org/3.7/library/importlib.html#module-importlib.resources>`_
+module for Python 2.7, and 3.4 through 3.6. Users of Python 3.7 and beyond
+should use the standard library module, since for these versions,
+``importlib_resources`` just delegates to that module.
+
+The key goal of this module is to replace parts of `pkg_resources
+<https://setuptools.readthedocs.io/en/latest/pkg_resources.html>`_ 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.
+
+Note that ``pip 10`` is required if you are going to ``pip install
+importlib_resources``.
+
+
+Project details
+===============
+
+ * Project home: https://gitlab.com/python-devs/importlib_resources
+ * Report bugs at: https://gitlab.com/python-devs/importlib_resources/issues
+ * Code hosting: https://gitlab.com/python-devs/importlib_resources.git
+ * Documentation: http://importlib_resources.readthedocs.io/
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/RECORD
new file mode 100644
index 0000000000..b728f0e05a
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/RECORD
@@ -0,0 +1,39 @@
+importlib_resources/__init__.py,sha256=rzQAetwEwMAwz3RonnegirJDoyexftQNVgVAtPqs91k,1064
+importlib_resources/_compat.py,sha256=ldJ5ebXghEZdDKuFIbigsNuSp4VrBi8a6XAG2tXtebc,581
+importlib_resources/_py2.py,sha256=EypZLeKb03aScgvtpzJxcr-E6CjL8DJLWTls8ql3QVY,11601
+importlib_resources/_py3.py,sha256=hUfpyjcsu13D57VyJKSRzYnA4RTp7FaQYwPous33yEk,12882
+importlib_resources/abc.py,sha256=U9Q4qZImO0rpCF9aoV1a5tS1IrXDhrAoT5PUFReSZs0,1946
+importlib_resources/version.txt,sha256=n9KGQtOsoZHlx_wjg8_W-rsqrIdD8Cnau4mJrFhOMbw,6
+importlib_resources/docs/changelog.rst,sha256=uWSJrcIlTNTj2tGRpGLzeaz9eLcM3pu_6yVqnQH_F94,2020
+importlib_resources/docs/conf.py,sha256=x7IPypqIitt3ztWBP4KKAxDHMfDI6eEVSD1K-fs000w,5557
+importlib_resources/docs/index.rst,sha256=ZgWQVxUPNyYZYUS5pRZXboxfc1-S0z8NBhcCQz0_YTQ,2138
+importlib_resources/docs/migration.rst,sha256=RdJE8S_bh50d6-63UrjrKuojcfYxv2gx3qcyHSy42DA,6329
+importlib_resources/docs/using.rst,sha256=epgk0GWhEwKGWCzL3DvU3GnGalp1jwxiU-XZL5eaC5w,8586
+importlib_resources/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/test_open.py,sha256=yDXmTGXQspByj6WU0prnoVwab1yWWEA3fwz_XIx7TQU,2288
+importlib_resources/tests/test_path.py,sha256=yVYMwuECJiivtubCGnYA0-6e-LSpbnTKjcBHuKk-oMc,1178
+importlib_resources/tests/test_read.py,sha256=DpA7tzxSQlU0_YQuWibB3E5PDL9fQUdzeKoEUGnAx78,2046
+importlib_resources/tests/test_resource.py,sha256=X77DzU2BRoM6d59iEh74zDHHw3pKOBGLCg3lP3dH4BI,6467
+importlib_resources/tests/util.py,sha256=f0RZU-RkEkybJjXRd7C5HcWMsoLFRWJL4FIUF1CJ2wo,6980
+importlib_resources/tests/data01/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data01/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/data01/utf-16.file,sha256=t5q9qhxX0rYqItBOM8D3ylwG-RHrnOYteTLtQr6sF7g,44
+importlib_resources/tests/data01/utf-8.file,sha256=kwWgYG4yQ-ZF2X_WA66EjYPmxJRn-w8aSOiS9e8tKYY,20
+importlib_resources/tests/data01/subdirectory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data01/subdirectory/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/data02/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/one/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/one/resource1.txt,sha256=10flKac7c-XXFzJ3t-AB5MJjlBy__dSZvPE_dOm2q6U,13
+importlib_resources/tests/data02/two/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/two/resource2.txt,sha256=lt2jbN3TMn9QiFKM832X39bU_62UptDdUkoYzkvEbl0,13
+importlib_resources/tests/data03/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data03/namespace/resource1.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/zipdata01/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/zipdata01/ziptestdata.zip,sha256=gAC1vleFnNtdAHuNyYQ30gvIZ5itNRfZtaF0hxGHAi4,876
+importlib_resources/tests/zipdata02/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/zipdata02/ziptestdata.zip,sha256=kL-RiB7ndv8FHBCJde6oj34_C90gtrSCYgYk98osm6M,698
+importlib_resources-1.0.2.dist-info/LICENSE,sha256=xS4YxCplVSZiTNBwkotq9YkkHJ8nlkctJpFZvlLA9NM,568
+importlib_resources-1.0.2.dist-info/METADATA,sha256=WiWlAvBr3XA3pXUg2NJ08qHO-NM93m6v1aXlega5BMk,1881
+importlib_resources-1.0.2.dist-info/WHEEL,sha256=CihQvCnsGZQBGAHLEUMf0IdA4fRduS_NBUTMgCTtvPM,110
+importlib_resources-1.0.2.dist-info/top_level.txt,sha256=fHIjHU1GZwAjvcydpmUnUrTnbvdiWjG4OEVZK8by0TQ,20
+importlib_resources-1.0.2.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/WHEEL
new file mode 100644
index 0000000000..dea0e20ccd
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.32.2)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/top_level.txt
new file mode 100644
index 0000000000..58ad1bd333
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info/top_level.txt
@@ -0,0 +1 @@
+importlib_resources
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/__init__.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/__init__.py
new file mode 100644
index 0000000000..fab437a4ad
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/__init__.py
@@ -0,0 +1,36 @@
+"""Read resources contained within a package."""
+
+import sys
+
+
+__all__ = [
+ 'contents',
+ 'is_resource',
+ 'open_binary',
+ 'open_text',
+ 'path',
+ 'read_binary',
+ 'read_text',
+ ]
+
+
+# Use the Python 3.7 stdlib implementation if available.
+if sys.version_info >= (3, 7):
+ from importlib.resources import (
+ Package, Resource, contents, is_resource, open_binary, open_text, path,
+ read_binary, read_text)
+ from importlib.abc import ResourceReader
+ __all__.extend(['Package', 'Resource', 'ResourceReader'])
+elif sys.version_info >= (3,):
+ from importlib_resources._py3 import (
+ Package, Resource, contents, is_resource, open_binary, open_text, path,
+ read_binary, read_text)
+ from importlib_resources.abc import ResourceReader
+ __all__.extend(['Package', 'Resource', 'ResourceReader'])
+else:
+ from importlib_resources._py2 import (
+ contents, is_resource, open_binary, open_text, path, read_binary,
+ read_text)
+
+
+__version__ = read_text('importlib_resources', 'version.txt').strip()
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_compat.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_compat.py
new file mode 100644
index 0000000000..28d61276e0
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_compat.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import
+
+# flake8: noqa
+
+try:
+ from pathlib import Path, PurePath
+except ImportError:
+ from pathlib2 import Path, PurePath # type: ignore
+
+
+try:
+ from abc import ABC # type: ignore
+except ImportError:
+ from abc import ABCMeta
+
+ class ABC(object): # type: ignore
+ __metaclass__ = ABCMeta
+
+
+try:
+ FileNotFoundError = FileNotFoundError # type: ignore
+except NameError:
+ FileNotFoundError = OSError
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py2.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py2.py
new file mode 100644
index 0000000000..376f0e3813
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py2.py
@@ -0,0 +1,270 @@
+import os
+import errno
+import tempfile
+
+from ._compat import FileNotFoundError
+from contextlib import contextmanager
+from importlib import import_module
+from io import BytesIO, TextIOWrapper, open as io_open
+from pathlib2 import Path
+from zipfile import ZipFile
+
+
+def _get_package(package):
+ """Normalize a path by ensuring it is a string.
+
+ If the resulting string contains path separators, an exception is raised.
+ """
+ if isinstance(package, basestring): # noqa: F821
+ module = import_module(package)
+ else:
+ module = package
+ if not hasattr(module, '__path__'):
+ raise TypeError("{!r} is not a package".format(package))
+ return module
+
+
+def _normalize_path(path):
+ """Normalize a path by ensuring it is a string.
+
+ If the resulting string contains path separators, an exception is raised.
+ """
+ str_path = str(path)
+ parent, file_name = os.path.split(str_path)
+ if parent:
+ raise ValueError("{!r} must be only a file name".format(path))
+ else:
+ return file_name
+
+
+def open_binary(package, resource):
+ """Return a file-like object opened for binary reading of the resource."""
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ # Using pathlib doesn't work well here due to the lack of 'strict' argument
+ # for pathlib.Path.resolve() prior to Python 3.6.
+ package_path = os.path.dirname(package.__file__)
+ relative_path = os.path.join(package_path, resource)
+ full_path = os.path.abspath(relative_path)
+ try:
+ return io_open(full_path, 'rb')
+ except IOError:
+ # This might be a package in a zip file. zipimport provides a loader
+ # with a functioning get_data() method, however we have to strip the
+ # archive (i.e. the .zip file's name) off the front of the path. This
+ # is because the zipimport loader in Python 2 doesn't actually follow
+ # PEP 302. It should allow the full path, but actually requires that
+ # the path be relative to the zip file.
+ try:
+ loader = package.__loader__
+ full_path = relative_path[len(loader.archive)+1:]
+ data = loader.get_data(full_path)
+ except (IOError, AttributeError):
+ package_name = package.__name__
+ message = '{!r} resource not found in {!r}'.format(
+ resource, package_name)
+ raise FileNotFoundError(message)
+ else:
+ return BytesIO(data)
+
+
+def open_text(package, resource, encoding='utf-8', errors='strict'):
+ """Return a file-like object opened for text reading of the resource."""
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ # Using pathlib doesn't work well here due to the lack of 'strict' argument
+ # for pathlib.Path.resolve() prior to Python 3.6.
+ package_path = os.path.dirname(package.__file__)
+ relative_path = os.path.join(package_path, resource)
+ full_path = os.path.abspath(relative_path)
+ try:
+ return io_open(full_path, mode='r', encoding=encoding, errors=errors)
+ except IOError:
+ # This might be a package in a zip file. zipimport provides a loader
+ # with a functioning get_data() method, however we have to strip the
+ # archive (i.e. the .zip file's name) off the front of the path. This
+ # is because the zipimport loader in Python 2 doesn't actually follow
+ # PEP 302. It should allow the full path, but actually requires that
+ # the path be relative to the zip file.
+ try:
+ loader = package.__loader__
+ full_path = relative_path[len(loader.archive)+1:]
+ data = loader.get_data(full_path)
+ except (IOError, AttributeError):
+ package_name = package.__name__
+ message = '{!r} resource not found in {!r}'.format(
+ resource, package_name)
+ raise FileNotFoundError(message)
+ else:
+ return TextIOWrapper(BytesIO(data), encoding, errors)
+
+
+def read_binary(package, resource):
+ """Return the binary contents of the resource."""
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ with open_binary(package, resource) as fp:
+ return fp.read()
+
+
+def read_text(package, resource, encoding='utf-8', errors='strict'):
+ """Return the decoded string of the resource.
+
+ The decoding-related arguments have the same semantics as those of
+ bytes.decode().
+ """
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ with open_text(package, resource, encoding, errors) as fp:
+ return fp.read()
+
+
+@contextmanager
+def path(package, resource):
+ """A context manager providing a file path object to the resource.
+
+ If the resource does not already exist on its own on the file system,
+ a temporary file will be created. If the file was created, the file
+ will be deleted upon exiting the context manager (no exception is
+ raised if the file was deleted prior to the context manager
+ exiting).
+ """
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ package_directory = Path(package.__file__).parent
+ file_path = package_directory / resource
+ # If the file actually exists on the file system, just return it.
+ # Otherwise, it's probably in a zip file, so we need to create a temporary
+ # file and copy the contents into that file, hence the contextmanager to
+ # clean up the temp file resource.
+ if file_path.exists():
+ yield file_path
+ else:
+ with open_binary(package, resource) as fp:
+ data = fp.read()
+ # 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()
+ try:
+ os.write(fd, data)
+ os.close(fd)
+ yield Path(raw_path)
+ finally:
+ try:
+ os.remove(raw_path)
+ except FileNotFoundError:
+ pass
+
+
+def is_resource(package, name):
+ """True if name is a resource inside package.
+
+ Directories are *not* resources.
+ """
+ package = _get_package(package)
+ _normalize_path(name)
+ try:
+ package_contents = set(contents(package))
+ except OSError as error:
+ if error.errno not in (errno.ENOENT, errno.ENOTDIR):
+ # We won't hit this in the Python 2 tests, so it'll appear
+ # uncovered. We could mock os.listdir() to return a non-ENOENT or
+ # ENOTDIR, but then we'd have to depend on another external
+ # library since Python 2 doesn't have unittest.mock. It's not
+ # worth it.
+ raise # pragma: nocover
+ return False
+ if name not in package_contents:
+ return False
+ # Just because the given file_name lives as an entry in the package's
+ # contents doesn't necessarily mean it's a resource. Directories are not
+ # resources, so let's try to find out if it's a directory or not.
+ path = Path(package.__file__).parent / name
+ if path.is_file():
+ return True
+ if path.is_dir():
+ return False
+ # If it's not a file and it's not a directory, what is it? Well, this
+ # means the file doesn't exist on the file system, so it probably lives
+ # inside a zip file. We have to crack open the zip, look at its table of
+ # contents, and make sure that this entry doesn't have sub-entries.
+ archive_path = package.__loader__.archive # type: ignore
+ package_directory = Path(package.__file__).parent
+ with ZipFile(archive_path) as zf:
+ toc = zf.namelist()
+ relpath = package_directory.relative_to(archive_path)
+ candidate_path = relpath / name
+ for entry in toc: # pragma: nobranch
+ try:
+ relative_to_candidate = Path(entry).relative_to(candidate_path)
+ except ValueError:
+ # The two paths aren't relative to each other so we can ignore it.
+ continue
+ # Since directories aren't explicitly listed in the zip file, we must
+ # infer their 'directory-ness' by looking at the number of path
+ # components in the path relative to the package resource we're
+ # looking up. If there are zero additional parts, it's a file, i.e. a
+ # resource. If there are more than zero it's a directory, i.e. not a
+ # resource. It has to be one of these two cases.
+ return len(relative_to_candidate.parts) == 0
+ # I think it's impossible to get here. It would mean that we are looking
+ # for a resource in a zip file, there's an entry matching it in the return
+ # value of contents(), but we never actually found it in the zip's table of
+ # contents.
+ raise AssertionError('Impossible situation')
+
+
+def contents(package):
+ """Return an iterable of entries in `package`.
+
+ Note that not all entries are resources. Specifically, directories are
+ not considered resources. Use `is_resource()` on each entry returned here
+ to check if it is a resource or not.
+ """
+ package = _get_package(package)
+ package_directory = Path(package.__file__).parent
+ try:
+ return os.listdir(str(package_directory))
+ except OSError as error:
+ if error.errno not in (errno.ENOENT, errno.ENOTDIR):
+ # We won't hit this in the Python 2 tests, so it'll appear
+ # uncovered. We could mock os.listdir() to return a non-ENOENT or
+ # ENOTDIR, but then we'd have to depend on another external
+ # library since Python 2 doesn't have unittest.mock. It's not
+ # worth it.
+ raise # pragma: nocover
+ # The package is probably in a zip file.
+ archive_path = getattr(package.__loader__, 'archive', None)
+ if archive_path is None:
+ raise
+ relpath = package_directory.relative_to(archive_path)
+ with ZipFile(archive_path) as zf:
+ toc = zf.namelist()
+ subdirs_seen = set() # type: Set
+ subdirs_returned = []
+ for filename in toc:
+ path = Path(filename)
+ # Strip off any path component parts that are in common with the
+ # package directory, relative to the zip archive's file system
+ # path. This gives us all the parts that live under the named
+ # package inside the zip file. If the length of these subparts is
+ # exactly 1, then it is situated inside the package. The resulting
+ # length will be 0 if it's above the package, and it will be
+ # greater than 1 if it lives in a subdirectory of the package
+ # directory.
+ #
+ # However, since directories themselves don't appear in the zip
+ # archive as a separate entry, we need to return the first path
+ # component for any case that has > 1 subparts -- but only once!
+ if path.parts[:len(relpath.parts)] != relpath.parts:
+ continue
+ subparts = path.parts[len(relpath.parts):]
+ if len(subparts) == 1:
+ subdirs_returned.append(subparts[0])
+ elif len(subparts) > 1: # pragma: nobranch
+ subdir = subparts[0]
+ if subdir not in subdirs_seen:
+ subdirs_seen.add(subdir)
+ subdirs_returned.append(subdir)
+ return subdirs_returned
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py3.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py3.py
new file mode 100644
index 0000000000..00781bd918
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py3.py
@@ -0,0 +1,312 @@
+import os
+import sys
+import tempfile
+
+from . import abc as resources_abc
+from contextlib import contextmanager, suppress
+from importlib import import_module
+from importlib.abc import ResourceLoader
+from io import BytesIO, TextIOWrapper
+from pathlib import Path
+from types import ModuleType
+from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
+from typing import cast
+from typing.io import BinaryIO, TextIO
+from zipfile import ZipFile
+
+
+Package = Union[ModuleType, str]
+if sys.version_info >= (3, 6):
+ Resource = Union[str, os.PathLike] # pragma: <=35
+else:
+ Resource = str # pragma: >=36
+
+
+def _get_package(package) -> ModuleType:
+ """Take a package name or module object and return the module.
+
+ If a name, the module is imported. If the passed or imported module
+ object is not a package, raise an exception.
+ """
+ if hasattr(package, '__spec__'):
+ if package.__spec__.submodule_search_locations is None:
+ raise TypeError('{!r} is not a package'.format(
+ package.__spec__.name))
+ else:
+ return package
+ else:
+ module = import_module(package)
+ if module.__spec__.submodule_search_locations is None:
+ raise TypeError('{!r} is not a package'.format(package))
+ else:
+ return module
+
+
+def _normalize_path(path) -> str:
+ """Normalize a path by ensuring it is a string.
+
+ If the resulting string contains path separators, an exception is raised.
+ """
+ str_path = str(path)
+ parent, file_name = os.path.split(str_path)
+ if parent:
+ raise ValueError('{!r} must be only a file name'.format(path))
+ else:
+ return file_name
+
+
+def _get_resource_reader(
+ package: ModuleType) -> Optional[resources_abc.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)
+ if reader is None:
+ return None
+ return cast(resources_abc.ResourceReader, reader(spec.name))
+
+
+def open_binary(package: Package, resource: Resource) -> BinaryIO:
+ """Return a file-like object opened for binary reading of the resource."""
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ reader = _get_resource_reader(package)
+ if reader is not None:
+ return reader.open_resource(resource)
+ # Using pathlib doesn't work well here due to the lack of 'strict'
+ # argument for pathlib.Path.resolve() prior to Python 3.6.
+ absolute_package_path = os.path.abspath(package.__spec__.origin)
+ package_path = os.path.dirname(absolute_package_path)
+ full_path = os.path.join(package_path, resource)
+ try:
+ return open(full_path, mode='rb')
+ except OSError:
+ # Just assume the loader is a resource loader; all the relevant
+ # importlib.machinery loaders are and an AttributeError for
+ # get_data() will make it clear what is needed from the loader.
+ loader = cast(ResourceLoader, package.__spec__.loader)
+ data = None
+ if hasattr(package.__spec__.loader, 'get_data'):
+ with suppress(OSError):
+ data = loader.get_data(full_path)
+ if data is None:
+ package_name = package.__spec__.name
+ message = '{!r} resource not found in {!r}'.format(
+ resource, package_name)
+ raise FileNotFoundError(message)
+ else:
+ return BytesIO(data)
+
+
+def open_text(package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict') -> TextIO:
+ """Return a file-like object opened for text reading of the resource."""
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ reader = _get_resource_reader(package)
+ if reader is not None:
+ return TextIOWrapper(reader.open_resource(resource), encoding, errors)
+ # Using pathlib doesn't work well here due to the lack of 'strict'
+ # argument for pathlib.Path.resolve() prior to Python 3.6.
+ absolute_package_path = os.path.abspath(package.__spec__.origin)
+ package_path = os.path.dirname(absolute_package_path)
+ full_path = os.path.join(package_path, resource)
+ try:
+ return open(full_path, mode='r', encoding=encoding, errors=errors)
+ except OSError:
+ # Just assume the loader is a resource loader; all the relevant
+ # importlib.machinery loaders are and an AttributeError for
+ # get_data() will make it clear what is needed from the loader.
+ loader = cast(ResourceLoader, package.__spec__.loader)
+ data = None
+ if hasattr(package.__spec__.loader, 'get_data'):
+ with suppress(OSError):
+ data = loader.get_data(full_path)
+ if data is None:
+ package_name = package.__spec__.name
+ message = '{!r} resource not found in {!r}'.format(
+ resource, package_name)
+ raise FileNotFoundError(message)
+ else:
+ return TextIOWrapper(BytesIO(data), encoding, errors)
+
+
+def read_binary(package: Package, resource: Resource) -> bytes:
+ """Return the binary contents of the resource."""
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ with open_binary(package, resource) as fp:
+ return fp.read()
+
+
+def read_text(package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict') -> str:
+ """Return the decoded string of the resource.
+
+ The decoding-related arguments have the same semantics as those of
+ bytes.decode().
+ """
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ with open_text(package, resource, encoding, errors) as fp:
+ return fp.read()
+
+
+@contextmanager
+def path(package: Package, resource: Resource) -> Iterator[Path]:
+ """A context manager providing a file path object to the resource.
+
+ If the resource does not already exist on its own on the file system,
+ a temporary file will be created. If the file was created, the file
+ will be deleted upon exiting the context manager (no exception is
+ raised if the file was deleted prior to the context manager
+ exiting).
+ """
+ resource = _normalize_path(resource)
+ package = _get_package(package)
+ reader = _get_resource_reader(package)
+ if reader is not None:
+ try:
+ yield Path(reader.resource_path(resource))
+ return
+ except FileNotFoundError:
+ pass
+ # Fall-through for both the lack of resource_path() *and* if
+ # resource_path() raises FileNotFoundError.
+ package_directory = Path(package.__spec__.origin).parent
+ file_path = package_directory / resource
+ if file_path.exists():
+ yield file_path
+ else:
+ with open_binary(package, resource) as fp:
+ data = fp.read()
+ # 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()
+ try:
+ os.write(fd, data)
+ os.close(fd)
+ yield Path(raw_path)
+ finally:
+ try:
+ os.remove(raw_path)
+ except FileNotFoundError:
+ pass
+
+
+def is_resource(package: Package, name: str) -> bool:
+ """True if `name` is a resource inside `package`.
+
+ Directories are *not* resources.
+ """
+ package = _get_package(package)
+ _normalize_path(name)
+ reader = _get_resource_reader(package)
+ if reader is not None:
+ return reader.is_resource(name)
+ try:
+ package_contents = set(contents(package))
+ except (NotADirectoryError, FileNotFoundError):
+ return False
+ if name not in package_contents:
+ return False
+ # Just because the given file_name lives as an entry in the package's
+ # contents doesn't necessarily mean it's a resource. Directories are not
+ # resources, so let's try to find out if it's a directory or not.
+ path = Path(package.__spec__.origin).parent / name
+ if path.is_file():
+ return True
+ if path.is_dir():
+ return False
+ # If it's not a file and it's not a directory, what is it? Well, this
+ # means the file doesn't exist on the file system, so it probably lives
+ # inside a zip file. We have to crack open the zip, look at its table of
+ # contents, and make sure that this entry doesn't have sub-entries.
+ archive_path = package.__spec__.loader.archive # type: ignore
+ package_directory = Path(package.__spec__.origin).parent
+ with ZipFile(archive_path) as zf:
+ toc = zf.namelist()
+ relpath = package_directory.relative_to(archive_path)
+ candidate_path = relpath / name
+ for entry in toc: # pragma: nobranch
+ try:
+ relative_to_candidate = Path(entry).relative_to(candidate_path)
+ except ValueError:
+ # The two paths aren't relative to each other so we can ignore it.
+ continue
+ # Since directories aren't explicitly listed in the zip file, we must
+ # infer their 'directory-ness' by looking at the number of path
+ # components in the path relative to the package resource we're
+ # looking up. If there are zero additional parts, it's a file, i.e. a
+ # resource. If there are more than zero it's a directory, i.e. not a
+ # resource. It has to be one of these two cases.
+ return len(relative_to_candidate.parts) == 0
+ # I think it's impossible to get here. It would mean that we are looking
+ # for a resource in a zip file, there's an entry matching it in the return
+ # value of contents(), but we never actually found it in the zip's table of
+ # contents.
+ raise AssertionError('Impossible situation')
+
+
+def contents(package: Package) -> Iterable[str]:
+ """Return an iterable of entries in `package`.
+
+ Note that not all entries are resources. Specifically, directories are
+ not considered resources. Use `is_resource()` on each entry returned here
+ to check if it is a resource or not.
+ """
+ package = _get_package(package)
+ reader = _get_resource_reader(package)
+ if reader is not None:
+ return reader.contents()
+ # Is the package a namespace package? By definition, namespace packages
+ # cannot have resources.
+ if (package.__spec__.origin == 'namespace' and
+ not package.__spec__.has_location):
+ return ()
+ package_directory = Path(package.__spec__.origin).parent
+ try:
+ return os.listdir(str(package_directory))
+ except (NotADirectoryError, FileNotFoundError):
+ # The package is probably in a zip file.
+ archive_path = getattr(package.__spec__.loader, 'archive', None)
+ if archive_path is None:
+ raise
+ relpath = package_directory.relative_to(archive_path)
+ with ZipFile(archive_path) as zf:
+ toc = zf.namelist()
+ subdirs_seen = set() # type: Set
+ subdirs_returned = []
+ for filename in toc:
+ path = Path(filename)
+ # Strip off any path component parts that are in common with the
+ # package directory, relative to the zip archive's file system
+ # path. This gives us all the parts that live under the named
+ # package inside the zip file. If the length of these subparts is
+ # exactly 1, then it is situated inside the package. The resulting
+ # length will be 0 if it's above the package, and it will be
+ # greater than 1 if it lives in a subdirectory of the package
+ # directory.
+ #
+ # However, since directories themselves don't appear in the zip
+ # archive as a separate entry, we need to return the first path
+ # component for any case that has > 1 subparts -- but only once!
+ if path.parts[:len(relpath.parts)] != relpath.parts:
+ continue
+ subparts = path.parts[len(relpath.parts):]
+ if len(subparts) == 1:
+ subdirs_returned.append(subparts[0])
+ elif len(subparts) > 1: # pragma: nobranch
+ subdir = subparts[0]
+ if subdir not in subdirs_seen:
+ subdirs_seen.add(subdir)
+ subdirs_returned.append(subdir)
+ return subdirs_returned
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/abc.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/abc.py
new file mode 100644
index 0000000000..f49e8c7008
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/abc.py
@@ -0,0 +1,58 @@
+from __future__ import absolute_import
+
+from ._compat import ABC, FileNotFoundError
+from abc import abstractmethod
+
+# We use mypy's comment syntax here since this file must be compatible with
+# both Python 2 and 3.
+try:
+ from typing import BinaryIO, Iterable, Text # noqa: F401
+except ImportError:
+ # Python 2
+ pass
+
+
+class ResourceReader(ABC):
+ """Abstract base class for loaders to provide resource reading support."""
+
+ @abstractmethod
+ def open_resource(self, resource):
+ # type: (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
+
+ @abstractmethod
+ def resource_path(self, resource):
+ # type: (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
+
+ @abstractmethod
+ def is_resource(self, path):
+ # type: (Text) -> bool
+ """Return True if the named 'path' is a resource.
+
+ Files are resources, directories are not.
+ """
+ raise FileNotFoundError
+
+ @abstractmethod
+ def contents(self):
+ # type: () -> Iterable[str]
+ """Return an iterable of entries in `package`."""
+ raise FileNotFoundError
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/version.txt b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/version.txt
new file mode 100644
index 0000000000..6d7de6e6ab
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/version.txt
@@ -0,0 +1 @@
+1.0.2
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/LICENSE
new file mode 100644
index 0000000000..378b991a4d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2017-2019 Brett Cannon, Barry Warsaw
+
+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/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/METADATA
new file mode 100644
index 0000000000..78b550540d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/METADATA
@@ -0,0 +1,41 @@
+Metadata-Version: 2.1
+Name: importlib-resources
+Version: 3.2.1
+Summary: Read resources from Python packages
+Home-page: https://github.com/python/importlib_resources
+Author: Barry Warsaw
+Author-email: barry@python.org
+License: UNKNOWN
+Project-URL: Documentation, https://importlib-resources.readthedocs.io/
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
+Requires-Dist: pathlib2 ; python_version < "3"
+Requires-Dist: contextlib2 ; python_version < "3"
+Requires-Dist: singledispatch ; python_version < "3.4"
+Requires-Dist: typing ; python_version < "3.5"
+Requires-Dist: zipp (>=0.4) ; python_version < "3.8"
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: rst.linker ; extra == 'docs'
+Requires-Dist: jaraco.packaging ; extra == 'docs'
+
+``importlib_resources`` is a backport of Python standard library
+`importlib.resources
+<https://docs.python.org/3.9/library/importlib.html#module-importlib.resources>`_
+module for Python 2.7, and 3.4 through 3.8. Users of Python 3.9 and beyond
+should use the standard library module, since for these versions,
+``importlib_resources`` just delegates to that module.
+
+The key goal of this module is to replace parts of `pkg_resources
+<https://setuptools.readthedocs.io/en/latest/pkg_resources.html>`_ 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.
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/RECORD
new file mode 100644
index 0000000000..dc92a03ce4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/RECORD
@@ -0,0 +1,42 @@
+importlib_resources/__init__.py,sha256=hswDmLAH0IUlLWwmdHXPN2mgus2bk5IwDP-BFzg7VKo,977
+importlib_resources/_common.py,sha256=RN8cXOZtlygvlbyTewd-ni9wC1hwXpfbZnrl7kbx0nI,3121
+importlib_resources/_compat.py,sha256=NDCXOf1097aDJJx-_pQ0UIktzVx2G1aPIQTRFGx0FHI,3694
+importlib_resources/_py2.py,sha256=G9M5mv1ILl8NARGdNX0v9_F_Hb4HUKCS-FCNK63Ajvw,4146
+importlib_resources/_py3.py,sha256=5_FhUUHWFG1c3HcLrmDy65ZFB7EYxmHfOV3ybv4uTHM,5710
+importlib_resources/abc.py,sha256=6PX4Nprv39YnAht3NymhHIuSso0ocAKqDJZf-A6BgIw,3894
+importlib_resources/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/readers.py,sha256=fGuSBoMeeERUVrscN9Grhp0s-wKMy7nMVbCx92vIlGs,3674
+importlib_resources/trees.py,sha256=U3FlQSI5--eF4AdzOjBvW4xnjL21OFX8ivk82Quwv_M,117
+importlib_resources/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/_compat.py,sha256=geKWJe8UGXjC181JxmtxR3A_o5VrR4yxolS0xbnxMlw,801
+importlib_resources/tests/py27compat.py,sha256=9lDJkGV2swPVQJg6isOorRNFWuP6KeoWd4D2bFNmzLI,965
+importlib_resources/tests/test_files.py,sha256=91rf4C74_aJsKNSt-a-03slVpY9QSAuCbogFWnsaPjE,1017
+importlib_resources/tests/test_open.py,sha256=pIYWvuTDpQOJKX0SEuOKGotssZcEeY_xNPDqLGCvP_U,2565
+importlib_resources/tests/test_path.py,sha256=GnUOu-338o9offnC8xwbXjH9JIQJpD7JujgQkGB106Q,1548
+importlib_resources/tests/test_read.py,sha256=DpA7tzxSQlU0_YQuWibB3E5PDL9fQUdzeKoEUGnAx78,2046
+importlib_resources/tests/test_reader.py,sha256=yEO0xyrYDcGRmsBC6A1n99GXiTZpVvp-uGA313s6aao,4638
+importlib_resources/tests/test_resource.py,sha256=GbrMeHJ74N6KJG38TDodCp--nsRnFHXJc7NrAEqUPaU,8766
+importlib_resources/tests/util.py,sha256=8hBFwqIZRJFNvkboEB7aWsCqTtgUjlWI_iQ0KV158Yk,5914
+importlib_resources/tests/data01/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data01/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/data01/utf-16.file,sha256=t5q9qhxX0rYqItBOM8D3ylwG-RHrnOYteTLtQr6sF7g,44
+importlib_resources/tests/data01/utf-8.file,sha256=kwWgYG4yQ-ZF2X_WA66EjYPmxJRn-w8aSOiS9e8tKYY,20
+importlib_resources/tests/data01/subdirectory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data01/subdirectory/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/data02/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/one/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/one/resource1.txt,sha256=10flKac7c-XXFzJ3t-AB5MJjlBy__dSZvPE_dOm2q6U,13
+importlib_resources/tests/data02/two/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/two/resource2.txt,sha256=lt2jbN3TMn9QiFKM832X39bU_62UptDdUkoYzkvEbl0,13
+importlib_resources/tests/namespacedata01/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/namespacedata01/utf-16.file,sha256=t5q9qhxX0rYqItBOM8D3ylwG-RHrnOYteTLtQr6sF7g,44
+importlib_resources/tests/namespacedata01/utf-8.file,sha256=kwWgYG4yQ-ZF2X_WA66EjYPmxJRn-w8aSOiS9e8tKYY,20
+importlib_resources/tests/zipdata01/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/zipdata01/ziptestdata.zip,sha256=AYf51fj80OKCRis93v2DlZjt5rM-VQOPptSHJbFtkXw,1131
+importlib_resources/tests/zipdata02/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/zipdata02/ziptestdata.zip,sha256=e6HXvTEObXvJcNxyX5I8tu5M8_6mSN8ALahHfqE7ADA,698
+importlib_resources-3.2.1.dist-info/LICENSE,sha256=uWRjFdYGataJX2ziXk048ItUglQmjng3GWBALaWA36U,568
+importlib_resources-3.2.1.dist-info/METADATA,sha256=d_tMNLHsZ_lPU-wq04MWr0yEfpwbNFKgfO_CU5GCC9g,1783
+importlib_resources-3.2.1.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110
+importlib_resources-3.2.1.dist-info/top_level.txt,sha256=fHIjHU1GZwAjvcydpmUnUrTnbvdiWjG4OEVZK8by0TQ,20
+importlib_resources-3.2.1.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/WHEEL
new file mode 100644
index 0000000000..6d38aa0601
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.35.1)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/top_level.txt
new file mode 100644
index 0000000000..58ad1bd333
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+importlib_resources
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/__init__.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/__init__.py
new file mode 100644
index 0000000000..f122f95e87
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/__init__.py
@@ -0,0 +1,53 @@
+"""Read resources contained within a package."""
+
+import sys
+
+from ._common import (
+ as_file, files,
+ )
+
+# For compatibility. Ref #88.
+# Also requires hook-importlib_resources.py (Ref #101).
+__import__('importlib_resources.trees')
+
+
+__all__ = [
+ 'Package',
+ 'Resource',
+ 'ResourceReader',
+ 'as_file',
+ 'contents',
+ 'files',
+ 'is_resource',
+ 'open_binary',
+ 'open_text',
+ 'path',
+ 'read_binary',
+ 'read_text',
+ ]
+
+
+if sys.version_info >= (3,):
+ from importlib_resources._py3 import (
+ Package,
+ Resource,
+ contents,
+ is_resource,
+ open_binary,
+ open_text,
+ path,
+ read_binary,
+ read_text,
+ )
+ from importlib_resources.abc import ResourceReader
+else:
+ from importlib_resources._py2 import (
+ contents,
+ is_resource,
+ open_binary,
+ open_text,
+ path,
+ read_binary,
+ read_text,
+ )
+ del __all__[:3]
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_common.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_common.py
new file mode 100644
index 0000000000..a7c2bf815d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_common.py
@@ -0,0 +1,120 @@
+from __future__ import absolute_import
+
+import os
+import tempfile
+import contextlib
+import types
+import importlib
+
+from ._compat import (
+ Path, FileNotFoundError,
+ singledispatch, package_spec,
+ )
+
+if False: # TYPE_CHECKING
+ from typing import Union, Any, Optional
+ from .abc import ResourceReader
+ Package = Union[types.ModuleType, str]
+
+
+def files(package):
+ """
+ Get a Traversable resource from a package
+ """
+ return from_package(get_package(package))
+
+
+def normalize_path(path):
+ # type: (Any) -> str
+ """Normalize a path by ensuring it is a string.
+
+ If the resulting string contains path separators, an exception is raised.
+ """
+ str_path = str(path)
+ parent, file_name = os.path.split(str_path)
+ if parent:
+ raise ValueError('{!r} must be only a file name'.format(path))
+ return file_name
+
+
+def get_resource_reader(package):
+ # type: (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)
+ if reader is None:
+ return None
+ return reader(spec.name)
+
+
+def resolve(cand):
+ # type: (Package) -> types.ModuleType
+ return (
+ cand if isinstance(cand, types.ModuleType)
+ else importlib.import_module(cand)
+ )
+
+
+def get_package(package):
+ # type: (Package) -> types.ModuleType
+ """Take a package name or module object and return the module.
+
+ Raise an exception if the resolved module is not a package.
+ """
+ resolved = resolve(package)
+ if package_spec(resolved).submodule_search_locations is None:
+ raise TypeError('{!r} is not a package'.format(package))
+ return resolved
+
+
+def from_package(package):
+ """
+ Return a Traversable object for the given package.
+
+ """
+ spec = package_spec(package)
+ reader = spec.loader.get_resource_reader(spec.name)
+ return reader.files()
+
+
+@contextlib.contextmanager
+def _tempfile(reader, suffix=''):
+ # 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:
+ os.write(fd, reader())
+ os.close(fd)
+ del reader
+ yield Path(raw_path)
+ finally:
+ try:
+ os.remove(raw_path)
+ except FileNotFoundError:
+ pass
+
+
+@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 _tempfile(path.read_bytes, suffix=path.name)
+
+
+@as_file.register(Path)
+@contextlib.contextmanager
+def _(path):
+ """
+ Degenerate behavior for pathlib.Path objects.
+ """
+ yield path
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_compat.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_compat.py
new file mode 100644
index 0000000000..70b0f6b4a4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_compat.py
@@ -0,0 +1,139 @@
+from __future__ import absolute_import
+import sys
+
+# flake8: noqa
+
+if sys.version_info > (3,5):
+ from pathlib import Path, PurePath
+else:
+ from pathlib2 import Path, PurePath # type: ignore
+
+
+if sys.version_info > (3,):
+ from contextlib import suppress
+else:
+ from contextlib2 import suppress # type: ignore
+
+
+try:
+ from functools import singledispatch
+except ImportError:
+ from singledispatch import singledispatch # type: ignore
+
+
+try:
+ from abc import ABC # type: ignore
+except ImportError:
+ from abc import ABCMeta
+
+ class ABC(object): # type: ignore
+ __metaclass__ = ABCMeta
+
+
+try:
+ FileNotFoundError = FileNotFoundError # type: ignore
+except NameError:
+ FileNotFoundError = OSError # type: ignore
+
+
+try:
+ NotADirectoryError = NotADirectoryError # type: ignore
+except NameError:
+ NotADirectoryError = OSError # type: ignore
+
+
+try:
+ from zipfile import Path as ZipPath # type: ignore
+except ImportError:
+ from zipp import Path as ZipPath # type: ignore
+
+
+try:
+ from typing import runtime_checkable # type: ignore
+except ImportError:
+ def runtime_checkable(cls): # type: ignore
+ return cls
+
+
+try:
+ from typing import Protocol # type: ignore
+except ImportError:
+ Protocol = ABC # type: ignore
+
+
+__metaclass__ = type
+
+
+class PackageSpec:
+ def __init__(self, **kwargs):
+ vars(self).update(kwargs)
+
+
+class TraversableResourcesAdapter:
+ def __init__(self, spec):
+ self.spec = spec
+ self.loader = LoaderAdapter(spec)
+
+ def __getattr__(self, name):
+ return getattr(self.spec, name)
+
+
+class LoaderAdapter:
+ """
+ Adapt loaders to provide TraversableResources and other
+ compatibility.
+ """
+ def __init__(self, spec):
+ self.spec = spec
+
+ @property
+ def path(self):
+ # Python < 3
+ return self.spec.origin
+
+ def get_resource_reader(self, name):
+ # Python < 3.9
+ from . import readers
+
+ def _zip_reader(spec):
+ with suppress(AttributeError):
+ return readers.ZipReader(spec.loader, spec.name)
+
+ def _namespace_reader(spec):
+ with suppress(AttributeError, ValueError):
+ return readers.NamespaceReader(spec.submodule_search_locations)
+
+ def _available_reader(spec):
+ with suppress(AttributeError):
+ return spec.loader.get_resource_reader(spec.name)
+
+ def _native_reader(spec):
+ reader = _available_reader(spec)
+ return reader if hasattr(reader, 'files') else None
+
+ return (
+ # native reader if it supplies 'files'
+ _native_reader(self.spec) or
+ # local ZipReader if a zip module
+ _zip_reader(self.spec) or
+ # local NamespaceReader if a namespace module
+ _namespace_reader(self.spec) or
+ # local FileReader
+ readers.FileReader(self)
+ )
+
+
+def package_spec(package):
+ """
+ Construct a minimal package spec suitable for
+ matching the interfaces this library relies upon
+ in later Python versions.
+ """
+ spec = getattr(package, '__spec__', None) or \
+ PackageSpec(
+ origin=package.__file__,
+ loader=getattr(package, '__loader__', None),
+ name=package.__name__,
+ submodule_search_locations=getattr(package, '__path__', None),
+ )
+ return TraversableResourcesAdapter(spec)
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py2.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py2.py
new file mode 100644
index 0000000000..dd8c7d627d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py2.py
@@ -0,0 +1,107 @@
+import os
+import errno
+
+from . import _common
+from ._compat import FileNotFoundError
+from io import BytesIO, TextIOWrapper, open as io_open
+
+
+def open_binary(package, resource):
+ """Return a file-like object opened for binary reading of the resource."""
+ resource = _common.normalize_path(resource)
+ package = _common.get_package(package)
+ # Using pathlib doesn't work well here due to the lack of 'strict' argument
+ # for pathlib.Path.resolve() prior to Python 3.6.
+ package_path = os.path.dirname(package.__file__)
+ relative_path = os.path.join(package_path, resource)
+ full_path = os.path.abspath(relative_path)
+ try:
+ return io_open(full_path, 'rb')
+ except IOError:
+ # This might be a package in a zip file. zipimport provides a loader
+ # with a functioning get_data() method, however we have to strip the
+ # archive (i.e. the .zip file's name) off the front of the path. This
+ # is because the zipimport loader in Python 2 doesn't actually follow
+ # PEP 302. It should allow the full path, but actually requires that
+ # the path be relative to the zip file.
+ try:
+ loader = package.__loader__
+ full_path = relative_path[len(loader.archive)+1:]
+ data = loader.get_data(full_path)
+ except (IOError, AttributeError):
+ package_name = package.__name__
+ message = '{!r} resource not found in {!r}'.format(
+ resource, package_name)
+ raise FileNotFoundError(message)
+ return BytesIO(data)
+
+
+def open_text(package, resource, encoding='utf-8', errors='strict'):
+ """Return a file-like object opened for text reading of the resource."""
+ return TextIOWrapper(
+ open_binary(package, resource), encoding=encoding, errors=errors)
+
+
+def read_binary(package, resource):
+ """Return the binary contents of the resource."""
+ with open_binary(package, resource) as fp:
+ return fp.read()
+
+
+def read_text(package, resource, encoding='utf-8', errors='strict'):
+ """Return the decoded string of the resource.
+
+ The decoding-related arguments have the same semantics as those of
+ bytes.decode().
+ """
+ with open_text(package, resource, encoding, errors) as fp:
+ return fp.read()
+
+
+def path(package, resource):
+ """A context manager providing a file path object to the resource.
+
+ If the resource does not already exist on its own on the file system,
+ a temporary file will be created. If the file was created, the file
+ will be deleted upon exiting the context manager (no exception is
+ raised if the file was deleted prior to the context manager
+ exiting).
+ """
+ path = _common.files(package).joinpath(_common.normalize_path(resource))
+ if not path.is_file():
+ raise FileNotFoundError(path)
+ return _common.as_file(path)
+
+
+def is_resource(package, name):
+ """True if name is a resource inside package.
+
+ Directories are *not* resources.
+ """
+ package = _common.get_package(package)
+ _common.normalize_path(name)
+ try:
+ package_contents = set(contents(package))
+ except OSError as error:
+ if error.errno not in (errno.ENOENT, errno.ENOTDIR):
+ # We won't hit this in the Python 2 tests, so it'll appear
+ # uncovered. We could mock os.listdir() to return a non-ENOENT or
+ # ENOTDIR, but then we'd have to depend on another external
+ # library since Python 2 doesn't have unittest.mock. It's not
+ # worth it.
+ raise # pragma: nocover
+ return False
+ if name not in package_contents:
+ return False
+ return (_common.from_package(package) / name).is_file()
+
+
+def contents(package):
+ """Return an iterable of entries in `package`.
+
+ Note that not all entries are resources. Specifically, directories are
+ not considered resources. Use `is_resource()` on each entry returned here
+ to check if it is a resource or not.
+ """
+ package = _common.get_package(package)
+ return list(item.name for item in _common.from_package(package).iterdir())
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py3.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py3.py
new file mode 100644
index 0000000000..7aa2773f81
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py3.py
@@ -0,0 +1,164 @@
+import os
+import sys
+import io
+
+from . import _common
+from contextlib import suppress
+from importlib.abc import ResourceLoader
+from io import BytesIO, TextIOWrapper
+from pathlib import Path
+from types import ModuleType
+from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
+from typing import cast
+from typing.io import BinaryIO, TextIO
+from collections.abc import Sequence
+from ._compat import singledispatch
+
+if False: # TYPE_CHECKING
+ from typing import ContextManager
+
+Package = Union[ModuleType, str]
+if sys.version_info >= (3, 6):
+ Resource = Union[str, os.PathLike] # pragma: <=35
+else:
+ Resource = str # pragma: >=36
+
+
+def open_binary(package: Package, resource: Resource) -> BinaryIO:
+ """Return a file-like object opened for binary reading of the resource."""
+ resource = _common.normalize_path(resource)
+ package = _common.get_package(package)
+ reader = _common.get_resource_reader(package)
+ if reader is not None:
+ return reader.open_resource(resource)
+ # Using pathlib doesn't work well here due to the lack of 'strict'
+ # argument for pathlib.Path.resolve() prior to Python 3.6.
+ if package.__spec__.submodule_search_locations is not None:
+ paths = package.__spec__.submodule_search_locations
+ elif package.__spec__.origin is not None:
+ paths = [os.path.dirname(os.path.abspath(package.__spec__.origin))]
+
+ for package_path in paths:
+ full_path = os.path.join(package_path, resource)
+ try:
+ return open(full_path, mode='rb')
+ except OSError:
+ # Just assume the loader is a resource loader; all the relevant
+ # importlib.machinery loaders are and an AttributeError for
+ # get_data() will make it clear what is needed from the loader.
+ loader = cast(ResourceLoader, package.__spec__.loader)
+ data = None
+ if hasattr(package.__spec__.loader, 'get_data'):
+ with suppress(OSError):
+ data = loader.get_data(full_path)
+ if data is not None:
+ return BytesIO(data)
+
+ raise FileNotFoundError('{!r} resource not found in {!r}'.format(
+ resource, package.__spec__.name))
+
+
+def open_text(package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict') -> TextIO:
+ """Return a file-like object opened for text reading of the resource."""
+ return TextIOWrapper(
+ open_binary(package, resource), encoding=encoding, errors=errors)
+
+
+def read_binary(package: Package, resource: Resource) -> bytes:
+ """Return the binary contents of the resource."""
+ with open_binary(package, resource) as fp:
+ return fp.read()
+
+
+def read_text(package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict') -> str:
+ """Return the decoded string of the resource.
+
+ The decoding-related arguments have the same semantics as those of
+ bytes.decode().
+ """
+ with open_text(package, resource, encoding, errors) as fp:
+ return fp.read()
+
+
+def path(
+ package: Package, resource: Resource,
+ ) -> 'ContextManager[Path]':
+ """A context manager providing a file path object to the resource.
+
+ If the resource does not already exist on its own on the file system,
+ a temporary file will be created. If the file was created, the file
+ will be deleted upon exiting the context manager (no exception is
+ raised if the file was deleted prior to the context manager
+ exiting).
+ """
+ reader = _common.get_resource_reader(_common.get_package(package))
+ return (
+ _path_from_reader(reader, _common.normalize_path(resource))
+ if reader else
+ _common.as_file(
+ _common.files(package).joinpath(_common.normalize_path(resource)))
+ )
+
+
+def _path_from_reader(reader, resource):
+ return _path_from_resource_path(reader, resource) or \
+ _path_from_open_resource(reader, resource)
+
+
+def _path_from_resource_path(reader, resource):
+ with suppress(FileNotFoundError):
+ return Path(reader.resource_path(resource))
+
+
+def _path_from_open_resource(reader, resource):
+ saved = io.BytesIO(reader.open_resource(resource).read())
+ return _common._tempfile(saved.read, suffix=resource)
+
+
+def is_resource(package: Package, name: str) -> bool:
+ """True if `name` is a resource inside `package`.
+
+ Directories are *not* resources.
+ """
+ package = _common.get_package(package)
+ _common.normalize_path(name)
+ reader = _common.get_resource_reader(package)
+ if reader is not None:
+ return reader.is_resource(name)
+ package_contents = set(contents(package))
+ if name not in package_contents:
+ return False
+ return (_common.from_package(package) / name).is_file()
+
+
+def contents(package: Package) -> Iterable[str]:
+ """Return an iterable of entries in `package`.
+
+ Note that not all entries are resources. Specifically, directories are
+ not considered resources. Use `is_resource()` on each entry returned here
+ to check if it is a resource or not.
+ """
+ package = _common.get_package(package)
+ reader = _common.get_resource_reader(package)
+ if reader is not None:
+ return _ensure_sequence(reader.contents())
+ transversable = _common.from_package(package)
+ if transversable.is_dir():
+ return list(item.name for item in transversable.iterdir())
+ return []
+
+
+@singledispatch
+def _ensure_sequence(iterable):
+ return list(iterable)
+
+
+@_ensure_sequence.register(Sequence)
+def _(iterable):
+ return iterable
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/abc.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/abc.py
new file mode 100644
index 0000000000..18bc4ef876
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/abc.py
@@ -0,0 +1,142 @@
+from __future__ import absolute_import
+
+import abc
+
+from ._compat import ABC, FileNotFoundError, runtime_checkable, Protocol
+
+# Use mypy's comment syntax for Python 2 compatibility
+try:
+ from typing import BinaryIO, Iterable, Text
+except ImportError:
+ pass
+
+
+class ResourceReader(ABC):
+ """Abstract base class for loaders to provide resource reading support."""
+
+ @abc.abstractmethod
+ def open_resource(self, resource):
+ # type: (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):
+ # type: (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):
+ # type: (Text) -> bool
+ """Return True if the named 'path' is a resource.
+
+ Files are resources, directories are not.
+ """
+ raise FileNotFoundError
+
+ @abc.abstractmethod
+ def contents(self):
+ # type: () -> Iterable[str]
+ """Return an iterable of entries in `package`."""
+ raise FileNotFoundError
+
+
+@runtime_checkable
+class Traversable(Protocol):
+ """
+ An object with a subset of pathlib.Path methods suitable for
+ traversing directories and opening files.
+ """
+
+ @abc.abstractmethod
+ def iterdir(self):
+ """
+ Yield Traversable objects in self
+ """
+
+ @abc.abstractmethod
+ def read_bytes(self):
+ """
+ Read contents of self as bytes
+ """
+
+ @abc.abstractmethod
+ def read_text(self, encoding=None):
+ """
+ Read contents of self as bytes
+ """
+
+ @abc.abstractmethod
+ def is_dir(self):
+ """
+ Return True if self is a dir
+ """
+
+ @abc.abstractmethod
+ def is_file(self):
+ """
+ Return True if self is a file
+ """
+
+ @abc.abstractmethod
+ def joinpath(self, child):
+ """
+ Return Traversable child in self
+ """
+
+ @abc.abstractmethod
+ def __truediv__(self, child):
+ """
+ Return Traversable child in self
+ """
+
+ @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.
+ """
+
+ @abc.abstractproperty
+ def name(self):
+ # type: () -> str
+ """
+ The base name of this object without any parent references.
+ """
+
+
+class TraversableResources(ResourceReader):
+ @abc.abstractmethod
+ def files(self):
+ """Return a Traversable object for the loaded package."""
+
+ def open_resource(self, resource):
+ return self.files().joinpath(resource).open('rb')
+
+ def resource_path(self, resource):
+ raise FileNotFoundError(resource)
+
+ def is_resource(self, path):
+ return self.files().joinpath(path).is_file()
+
+ def contents(self):
+ return (item.name for item in self.files().iterdir())
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/py.typed b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/py.typed
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/py.typed
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/readers.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/readers.py
new file mode 100644
index 0000000000..ce9c0caec4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/readers.py
@@ -0,0 +1,123 @@
+import os.path
+
+from collections import OrderedDict
+
+from . import abc
+
+from ._compat import Path, ZipPath
+from ._compat import FileNotFoundError, NotADirectoryError
+
+
+class FileReader(abc.TraversableResources):
+ def __init__(self, loader):
+ self.path = Path(loader.path).parent
+
+ 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
+
+
+class ZipReader(abc.TraversableResources):
+ def __init__(self, loader, module):
+ _, _, name = module.rpartition('.')
+ self.prefix = loader.prefix.replace('\\', '/') + name + '/'
+ self.archive = loader.archive
+
+ def open_resource(self, resource):
+ try:
+ return super().open_resource(resource)
+ except KeyError as exc:
+ raise FileNotFoundError(exc.args[0])
+
+ def is_resource(self, path):
+ # workaround for `zipfile.Path.is_file` returning true
+ # for non-existent paths.
+ target = self.files().joinpath(path)
+ return target.is_file() and target.exists()
+
+ def files(self):
+ return ZipPath(self.archive, self.prefix)
+
+
+class MultiplexedPath(abc.Traversable):
+ """
+ Given a series of Traversable objects, implement a merged
+ version of the interface across all objects. Useful for
+ namespace packages which may be multihomed at a single
+ name.
+ """
+ def __init__(self, *paths):
+ paths = list(OrderedDict.fromkeys(paths)) # remove duplicates
+ self._paths = list(map(Path, paths))
+ if not self._paths:
+ message = 'MultiplexedPath must contain at least one path'
+ raise FileNotFoundError(message)
+ if any(not path.is_dir() for path in self._paths):
+ raise NotADirectoryError(
+ 'MultiplexedPath only supports directories')
+
+ def iterdir(self):
+ visited = []
+ for path in self._paths:
+ for file in path.iterdir():
+ if file.name in visited:
+ continue
+ visited.append(file.name)
+ yield file
+
+ def read_bytes(self):
+ raise FileNotFoundError('{} is not a file'.format(self))
+
+ def read_text(self, *args, **kwargs):
+ raise FileNotFoundError('{} is not a file'.format(self))
+
+ def is_dir(self):
+ return True
+
+ def is_file(self):
+ return False
+
+ def joinpath(self, child):
+ # first try to find child in current paths
+ for file in self.iterdir():
+ if file.name == child:
+ return file
+ # if it does not exist, construct it with the first path
+ return self._paths[0] / child
+
+ __truediv__ = joinpath
+
+ def open(self, *args, **kwargs):
+ raise FileNotFoundError('{} is not a file'.format(self))
+
+ def name(self):
+ return os.path.basename(self._paths[0])
+
+ def __repr__(self):
+ return 'MultiplexedPath({})'.format(
+ ', '.join("'{}'".format(path) for path in self._paths))
+
+
+class NamespaceReader(abc.TraversableResources):
+ def __init__(self, namespace_path):
+ if 'NamespacePath' not in str(namespace_path):
+ raise ValueError('Invalid path')
+ self.path = MultiplexedPath(*list(namespace_path))
+
+ 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
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/trees.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/trees.py
new file mode 100644
index 0000000000..ba42bb55b7
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/trees.py
@@ -0,0 +1,6 @@
+# for compatibility with 1.1, continue to expose as_file here.
+
+from ._common import as_file
+
+
+__all__ = ['as_file']
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/LICENSE
new file mode 100644
index 0000000000..378b991a4d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2017-2019 Brett Cannon, Barry Warsaw
+
+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/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/METADATA
new file mode 100644
index 0000000000..66db8b78aa
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/METADATA
@@ -0,0 +1,41 @@
+Metadata-Version: 2.1
+Name: importlib-resources
+Version: 3.3.0
+Summary: Read resources from Python packages
+Home-page: https://github.com/python/importlib_resources
+Author: Barry Warsaw
+Author-email: barry@python.org
+License: UNKNOWN
+Project-URL: Documentation, https://importlib-resources.readthedocs.io/
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7
+Requires-Dist: pathlib2 ; python_version < "3"
+Requires-Dist: contextlib2 ; python_version < "3"
+Requires-Dist: singledispatch ; python_version < "3.4"
+Requires-Dist: typing ; python_version < "3.5"
+Requires-Dist: zipp (>=0.4) ; python_version < "3.8"
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: rst.linker ; extra == 'docs'
+Requires-Dist: jaraco.packaging ; extra == 'docs'
+
+``importlib_resources`` is a backport of Python standard library
+`importlib.resources
+<https://docs.python.org/3.9/library/importlib.html#module-importlib.resources>`_
+module for Python 2.7, and 3.4 through 3.8. Users of Python 3.9 and beyond
+should use the standard library module, since for these versions,
+``importlib_resources`` just delegates to that module.
+
+The key goal of this module is to replace parts of `pkg_resources
+<https://setuptools.readthedocs.io/en/latest/pkg_resources.html>`_ 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.
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/RECORD
new file mode 100644
index 0000000000..20e1b9b44a
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/RECORD
@@ -0,0 +1,42 @@
+importlib_resources/__init__.py,sha256=hswDmLAH0IUlLWwmdHXPN2mgus2bk5IwDP-BFzg7VKo,977
+importlib_resources/_common.py,sha256=RN8cXOZtlygvlbyTewd-ni9wC1hwXpfbZnrl7kbx0nI,3121
+importlib_resources/_compat.py,sha256=NDCXOf1097aDJJx-_pQ0UIktzVx2G1aPIQTRFGx0FHI,3694
+importlib_resources/_py2.py,sha256=G9M5mv1ILl8NARGdNX0v9_F_Hb4HUKCS-FCNK63Ajvw,4146
+importlib_resources/_py3.py,sha256=Bc-p0UYfPVWXFJ21HLNfVvbVrPJFXBA0g80rqPInkq8,5564
+importlib_resources/abc.py,sha256=6PX4Nprv39YnAht3NymhHIuSso0ocAKqDJZf-A6BgIw,3894
+importlib_resources/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/readers.py,sha256=fGuSBoMeeERUVrscN9Grhp0s-wKMy7nMVbCx92vIlGs,3674
+importlib_resources/trees.py,sha256=U3FlQSI5--eF4AdzOjBvW4xnjL21OFX8ivk82Quwv_M,117
+importlib_resources/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/_compat.py,sha256=geKWJe8UGXjC181JxmtxR3A_o5VrR4yxolS0xbnxMlw,801
+importlib_resources/tests/py27compat.py,sha256=9lDJkGV2swPVQJg6isOorRNFWuP6KeoWd4D2bFNmzLI,965
+importlib_resources/tests/test_files.py,sha256=91rf4C74_aJsKNSt-a-03slVpY9QSAuCbogFWnsaPjE,1017
+importlib_resources/tests/test_open.py,sha256=pIYWvuTDpQOJKX0SEuOKGotssZcEeY_xNPDqLGCvP_U,2565
+importlib_resources/tests/test_path.py,sha256=GnUOu-338o9offnC8xwbXjH9JIQJpD7JujgQkGB106Q,1548
+importlib_resources/tests/test_read.py,sha256=DpA7tzxSQlU0_YQuWibB3E5PDL9fQUdzeKoEUGnAx78,2046
+importlib_resources/tests/test_reader.py,sha256=yEO0xyrYDcGRmsBC6A1n99GXiTZpVvp-uGA313s6aao,4638
+importlib_resources/tests/test_resource.py,sha256=GbrMeHJ74N6KJG38TDodCp--nsRnFHXJc7NrAEqUPaU,8766
+importlib_resources/tests/util.py,sha256=8hBFwqIZRJFNvkboEB7aWsCqTtgUjlWI_iQ0KV158Yk,5914
+importlib_resources/tests/data01/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data01/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/data01/utf-16.file,sha256=t5q9qhxX0rYqItBOM8D3ylwG-RHrnOYteTLtQr6sF7g,44
+importlib_resources/tests/data01/utf-8.file,sha256=kwWgYG4yQ-ZF2X_WA66EjYPmxJRn-w8aSOiS9e8tKYY,20
+importlib_resources/tests/data01/subdirectory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data01/subdirectory/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/data02/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/one/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/one/resource1.txt,sha256=10flKac7c-XXFzJ3t-AB5MJjlBy__dSZvPE_dOm2q6U,13
+importlib_resources/tests/data02/two/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/data02/two/resource2.txt,sha256=lt2jbN3TMn9QiFKM832X39bU_62UptDdUkoYzkvEbl0,13
+importlib_resources/tests/namespacedata01/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
+importlib_resources/tests/namespacedata01/utf-16.file,sha256=t5q9qhxX0rYqItBOM8D3ylwG-RHrnOYteTLtQr6sF7g,44
+importlib_resources/tests/namespacedata01/utf-8.file,sha256=kwWgYG4yQ-ZF2X_WA66EjYPmxJRn-w8aSOiS9e8tKYY,20
+importlib_resources/tests/zipdata01/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/zipdata01/ziptestdata.zip,sha256=AYf51fj80OKCRis93v2DlZjt5rM-VQOPptSHJbFtkXw,1131
+importlib_resources/tests/zipdata02/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+importlib_resources/tests/zipdata02/ziptestdata.zip,sha256=e6HXvTEObXvJcNxyX5I8tu5M8_6mSN8ALahHfqE7ADA,698
+importlib_resources-3.3.0.dist-info/LICENSE,sha256=uWRjFdYGataJX2ziXk048ItUglQmjng3GWBALaWA36U,568
+importlib_resources-3.3.0.dist-info/METADATA,sha256=GxPMbCwUwlCuHNCiPJvP4IC_mTKqP4b_W7UqqNidcF4,1791
+importlib_resources-3.3.0.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110
+importlib_resources-3.3.0.dist-info/top_level.txt,sha256=fHIjHU1GZwAjvcydpmUnUrTnbvdiWjG4OEVZK8by0TQ,20
+importlib_resources-3.3.0.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/WHEEL
new file mode 100644
index 0000000000..6d38aa0601
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.35.1)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/top_level.txt
new file mode 100644
index 0000000000..58ad1bd333
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+importlib_resources
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/__init__.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/__init__.py
new file mode 100644
index 0000000000..f122f95e87
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/__init__.py
@@ -0,0 +1,53 @@
+"""Read resources contained within a package."""
+
+import sys
+
+from ._common import (
+ as_file, files,
+ )
+
+# For compatibility. Ref #88.
+# Also requires hook-importlib_resources.py (Ref #101).
+__import__('importlib_resources.trees')
+
+
+__all__ = [
+ 'Package',
+ 'Resource',
+ 'ResourceReader',
+ 'as_file',
+ 'contents',
+ 'files',
+ 'is_resource',
+ 'open_binary',
+ 'open_text',
+ 'path',
+ 'read_binary',
+ 'read_text',
+ ]
+
+
+if sys.version_info >= (3,):
+ from importlib_resources._py3 import (
+ Package,
+ Resource,
+ contents,
+ is_resource,
+ open_binary,
+ open_text,
+ path,
+ read_binary,
+ read_text,
+ )
+ from importlib_resources.abc import ResourceReader
+else:
+ from importlib_resources._py2 import (
+ contents,
+ is_resource,
+ open_binary,
+ open_text,
+ path,
+ read_binary,
+ read_text,
+ )
+ del __all__[:3]
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_common.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_common.py
new file mode 100644
index 0000000000..a7c2bf815d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_common.py
@@ -0,0 +1,120 @@
+from __future__ import absolute_import
+
+import os
+import tempfile
+import contextlib
+import types
+import importlib
+
+from ._compat import (
+ Path, FileNotFoundError,
+ singledispatch, package_spec,
+ )
+
+if False: # TYPE_CHECKING
+ from typing import Union, Any, Optional
+ from .abc import ResourceReader
+ Package = Union[types.ModuleType, str]
+
+
+def files(package):
+ """
+ Get a Traversable resource from a package
+ """
+ return from_package(get_package(package))
+
+
+def normalize_path(path):
+ # type: (Any) -> str
+ """Normalize a path by ensuring it is a string.
+
+ If the resulting string contains path separators, an exception is raised.
+ """
+ str_path = str(path)
+ parent, file_name = os.path.split(str_path)
+ if parent:
+ raise ValueError('{!r} must be only a file name'.format(path))
+ return file_name
+
+
+def get_resource_reader(package):
+ # type: (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)
+ if reader is None:
+ return None
+ return reader(spec.name)
+
+
+def resolve(cand):
+ # type: (Package) -> types.ModuleType
+ return (
+ cand if isinstance(cand, types.ModuleType)
+ else importlib.import_module(cand)
+ )
+
+
+def get_package(package):
+ # type: (Package) -> types.ModuleType
+ """Take a package name or module object and return the module.
+
+ Raise an exception if the resolved module is not a package.
+ """
+ resolved = resolve(package)
+ if package_spec(resolved).submodule_search_locations is None:
+ raise TypeError('{!r} is not a package'.format(package))
+ return resolved
+
+
+def from_package(package):
+ """
+ Return a Traversable object for the given package.
+
+ """
+ spec = package_spec(package)
+ reader = spec.loader.get_resource_reader(spec.name)
+ return reader.files()
+
+
+@contextlib.contextmanager
+def _tempfile(reader, suffix=''):
+ # 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:
+ os.write(fd, reader())
+ os.close(fd)
+ del reader
+ yield Path(raw_path)
+ finally:
+ try:
+ os.remove(raw_path)
+ except FileNotFoundError:
+ pass
+
+
+@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 _tempfile(path.read_bytes, suffix=path.name)
+
+
+@as_file.register(Path)
+@contextlib.contextmanager
+def _(path):
+ """
+ Degenerate behavior for pathlib.Path objects.
+ """
+ yield path
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_compat.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_compat.py
new file mode 100644
index 0000000000..70b0f6b4a4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_compat.py
@@ -0,0 +1,139 @@
+from __future__ import absolute_import
+import sys
+
+# flake8: noqa
+
+if sys.version_info > (3,5):
+ from pathlib import Path, PurePath
+else:
+ from pathlib2 import Path, PurePath # type: ignore
+
+
+if sys.version_info > (3,):
+ from contextlib import suppress
+else:
+ from contextlib2 import suppress # type: ignore
+
+
+try:
+ from functools import singledispatch
+except ImportError:
+ from singledispatch import singledispatch # type: ignore
+
+
+try:
+ from abc import ABC # type: ignore
+except ImportError:
+ from abc import ABCMeta
+
+ class ABC(object): # type: ignore
+ __metaclass__ = ABCMeta
+
+
+try:
+ FileNotFoundError = FileNotFoundError # type: ignore
+except NameError:
+ FileNotFoundError = OSError # type: ignore
+
+
+try:
+ NotADirectoryError = NotADirectoryError # type: ignore
+except NameError:
+ NotADirectoryError = OSError # type: ignore
+
+
+try:
+ from zipfile import Path as ZipPath # type: ignore
+except ImportError:
+ from zipp import Path as ZipPath # type: ignore
+
+
+try:
+ from typing import runtime_checkable # type: ignore
+except ImportError:
+ def runtime_checkable(cls): # type: ignore
+ return cls
+
+
+try:
+ from typing import Protocol # type: ignore
+except ImportError:
+ Protocol = ABC # type: ignore
+
+
+__metaclass__ = type
+
+
+class PackageSpec:
+ def __init__(self, **kwargs):
+ vars(self).update(kwargs)
+
+
+class TraversableResourcesAdapter:
+ def __init__(self, spec):
+ self.spec = spec
+ self.loader = LoaderAdapter(spec)
+
+ def __getattr__(self, name):
+ return getattr(self.spec, name)
+
+
+class LoaderAdapter:
+ """
+ Adapt loaders to provide TraversableResources and other
+ compatibility.
+ """
+ def __init__(self, spec):
+ self.spec = spec
+
+ @property
+ def path(self):
+ # Python < 3
+ return self.spec.origin
+
+ def get_resource_reader(self, name):
+ # Python < 3.9
+ from . import readers
+
+ def _zip_reader(spec):
+ with suppress(AttributeError):
+ return readers.ZipReader(spec.loader, spec.name)
+
+ def _namespace_reader(spec):
+ with suppress(AttributeError, ValueError):
+ return readers.NamespaceReader(spec.submodule_search_locations)
+
+ def _available_reader(spec):
+ with suppress(AttributeError):
+ return spec.loader.get_resource_reader(spec.name)
+
+ def _native_reader(spec):
+ reader = _available_reader(spec)
+ return reader if hasattr(reader, 'files') else None
+
+ return (
+ # native reader if it supplies 'files'
+ _native_reader(self.spec) or
+ # local ZipReader if a zip module
+ _zip_reader(self.spec) or
+ # local NamespaceReader if a namespace module
+ _namespace_reader(self.spec) or
+ # local FileReader
+ readers.FileReader(self)
+ )
+
+
+def package_spec(package):
+ """
+ Construct a minimal package spec suitable for
+ matching the interfaces this library relies upon
+ in later Python versions.
+ """
+ spec = getattr(package, '__spec__', None) or \
+ PackageSpec(
+ origin=package.__file__,
+ loader=getattr(package, '__loader__', None),
+ name=package.__name__,
+ submodule_search_locations=getattr(package, '__path__', None),
+ )
+ return TraversableResourcesAdapter(spec)
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py2.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py2.py
new file mode 100644
index 0000000000..dd8c7d627d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py2.py
@@ -0,0 +1,107 @@
+import os
+import errno
+
+from . import _common
+from ._compat import FileNotFoundError
+from io import BytesIO, TextIOWrapper, open as io_open
+
+
+def open_binary(package, resource):
+ """Return a file-like object opened for binary reading of the resource."""
+ resource = _common.normalize_path(resource)
+ package = _common.get_package(package)
+ # Using pathlib doesn't work well here due to the lack of 'strict' argument
+ # for pathlib.Path.resolve() prior to Python 3.6.
+ package_path = os.path.dirname(package.__file__)
+ relative_path = os.path.join(package_path, resource)
+ full_path = os.path.abspath(relative_path)
+ try:
+ return io_open(full_path, 'rb')
+ except IOError:
+ # This might be a package in a zip file. zipimport provides a loader
+ # with a functioning get_data() method, however we have to strip the
+ # archive (i.e. the .zip file's name) off the front of the path. This
+ # is because the zipimport loader in Python 2 doesn't actually follow
+ # PEP 302. It should allow the full path, but actually requires that
+ # the path be relative to the zip file.
+ try:
+ loader = package.__loader__
+ full_path = relative_path[len(loader.archive)+1:]
+ data = loader.get_data(full_path)
+ except (IOError, AttributeError):
+ package_name = package.__name__
+ message = '{!r} resource not found in {!r}'.format(
+ resource, package_name)
+ raise FileNotFoundError(message)
+ return BytesIO(data)
+
+
+def open_text(package, resource, encoding='utf-8', errors='strict'):
+ """Return a file-like object opened for text reading of the resource."""
+ return TextIOWrapper(
+ open_binary(package, resource), encoding=encoding, errors=errors)
+
+
+def read_binary(package, resource):
+ """Return the binary contents of the resource."""
+ with open_binary(package, resource) as fp:
+ return fp.read()
+
+
+def read_text(package, resource, encoding='utf-8', errors='strict'):
+ """Return the decoded string of the resource.
+
+ The decoding-related arguments have the same semantics as those of
+ bytes.decode().
+ """
+ with open_text(package, resource, encoding, errors) as fp:
+ return fp.read()
+
+
+def path(package, resource):
+ """A context manager providing a file path object to the resource.
+
+ If the resource does not already exist on its own on the file system,
+ a temporary file will be created. If the file was created, the file
+ will be deleted upon exiting the context manager (no exception is
+ raised if the file was deleted prior to the context manager
+ exiting).
+ """
+ path = _common.files(package).joinpath(_common.normalize_path(resource))
+ if not path.is_file():
+ raise FileNotFoundError(path)
+ return _common.as_file(path)
+
+
+def is_resource(package, name):
+ """True if name is a resource inside package.
+
+ Directories are *not* resources.
+ """
+ package = _common.get_package(package)
+ _common.normalize_path(name)
+ try:
+ package_contents = set(contents(package))
+ except OSError as error:
+ if error.errno not in (errno.ENOENT, errno.ENOTDIR):
+ # We won't hit this in the Python 2 tests, so it'll appear
+ # uncovered. We could mock os.listdir() to return a non-ENOENT or
+ # ENOTDIR, but then we'd have to depend on another external
+ # library since Python 2 doesn't have unittest.mock. It's not
+ # worth it.
+ raise # pragma: nocover
+ return False
+ if name not in package_contents:
+ return False
+ return (_common.from_package(package) / name).is_file()
+
+
+def contents(package):
+ """Return an iterable of entries in `package`.
+
+ Note that not all entries are resources. Specifically, directories are
+ not considered resources. Use `is_resource()` on each entry returned here
+ to check if it is a resource or not.
+ """
+ package = _common.get_package(package)
+ return list(item.name for item in _common.from_package(package).iterdir())
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py3.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py3.py
new file mode 100644
index 0000000000..ffeb616d6e
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py3.py
@@ -0,0 +1,160 @@
+import os
+import io
+
+from . import _common
+from contextlib import suppress
+from importlib.abc import ResourceLoader
+from io import BytesIO, TextIOWrapper
+from pathlib import Path
+from types import ModuleType
+from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
+from typing import cast
+from typing.io import BinaryIO, TextIO
+from collections.abc import Sequence
+from functools import singledispatch
+
+if False: # TYPE_CHECKING
+ from typing import ContextManager
+
+Package = Union[ModuleType, str]
+Resource = Union[str, os.PathLike]
+
+
+def open_binary(package: Package, resource: Resource) -> BinaryIO:
+ """Return a file-like object opened for binary reading of the resource."""
+ resource = _common.normalize_path(resource)
+ package = _common.get_package(package)
+ reader = _common.get_resource_reader(package)
+ if reader is not None:
+ return reader.open_resource(resource)
+ # Using pathlib doesn't work well here due to the lack of 'strict'
+ # argument for pathlib.Path.resolve() prior to Python 3.6.
+ if package.__spec__.submodule_search_locations is not None:
+ paths = package.__spec__.submodule_search_locations
+ elif package.__spec__.origin is not None:
+ paths = [os.path.dirname(os.path.abspath(package.__spec__.origin))]
+
+ for package_path in paths:
+ full_path = os.path.join(package_path, resource)
+ try:
+ return open(full_path, mode='rb')
+ except OSError:
+ # Just assume the loader is a resource loader; all the relevant
+ # importlib.machinery loaders are and an AttributeError for
+ # get_data() will make it clear what is needed from the loader.
+ loader = cast(ResourceLoader, package.__spec__.loader)
+ data = None
+ if hasattr(package.__spec__.loader, 'get_data'):
+ with suppress(OSError):
+ data = loader.get_data(full_path)
+ if data is not None:
+ return BytesIO(data)
+
+ raise FileNotFoundError('{!r} resource not found in {!r}'.format(
+ resource, package.__spec__.name))
+
+
+def open_text(package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict') -> TextIO:
+ """Return a file-like object opened for text reading of the resource."""
+ return TextIOWrapper(
+ open_binary(package, resource), encoding=encoding, errors=errors)
+
+
+def read_binary(package: Package, resource: Resource) -> bytes:
+ """Return the binary contents of the resource."""
+ with open_binary(package, resource) as fp:
+ return fp.read()
+
+
+def read_text(package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict') -> str:
+ """Return the decoded string of the resource.
+
+ The decoding-related arguments have the same semantics as those of
+ bytes.decode().
+ """
+ with open_text(package, resource, encoding, errors) as fp:
+ return fp.read()
+
+
+def path(
+ package: Package, resource: Resource,
+ ) -> 'ContextManager[Path]':
+ """A context manager providing a file path object to the resource.
+
+ If the resource does not already exist on its own on the file system,
+ a temporary file will be created. If the file was created, the file
+ will be deleted upon exiting the context manager (no exception is
+ raised if the file was deleted prior to the context manager
+ exiting).
+ """
+ reader = _common.get_resource_reader(_common.get_package(package))
+ return (
+ _path_from_reader(reader, _common.normalize_path(resource))
+ if reader else
+ _common.as_file(
+ _common.files(package).joinpath(_common.normalize_path(resource)))
+ )
+
+
+def _path_from_reader(reader, resource):
+ return _path_from_resource_path(reader, resource) or \
+ _path_from_open_resource(reader, resource)
+
+
+def _path_from_resource_path(reader, resource):
+ with suppress(FileNotFoundError):
+ return Path(reader.resource_path(resource))
+
+
+def _path_from_open_resource(reader, resource):
+ saved = io.BytesIO(reader.open_resource(resource).read())
+ return _common._tempfile(saved.read, suffix=resource)
+
+
+def is_resource(package: Package, name: str) -> bool:
+ """True if `name` is a resource inside `package`.
+
+ Directories are *not* resources.
+ """
+ package = _common.get_package(package)
+ _common.normalize_path(name)
+ reader = _common.get_resource_reader(package)
+ if reader is not None:
+ return reader.is_resource(name)
+ package_contents = set(contents(package))
+ if name not in package_contents:
+ return False
+ return (_common.from_package(package) / name).is_file()
+
+
+def contents(package: Package) -> Iterable[str]:
+ """Return an iterable of entries in `package`.
+
+ Note that not all entries are resources. Specifically, directories are
+ not considered resources. Use `is_resource()` on each entry returned here
+ to check if it is a resource or not.
+ """
+ package = _common.get_package(package)
+ reader = _common.get_resource_reader(package)
+ if reader is not None:
+ return _ensure_sequence(reader.contents())
+ transversable = _common.from_package(package)
+ if transversable.is_dir():
+ return list(item.name for item in transversable.iterdir())
+ return []
+
+
+@singledispatch
+def _ensure_sequence(iterable):
+ return list(iterable)
+
+
+@_ensure_sequence.register(Sequence)
+def _(iterable):
+ return iterable
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/abc.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/abc.py
new file mode 100644
index 0000000000..18bc4ef876
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/abc.py
@@ -0,0 +1,142 @@
+from __future__ import absolute_import
+
+import abc
+
+from ._compat import ABC, FileNotFoundError, runtime_checkable, Protocol
+
+# Use mypy's comment syntax for Python 2 compatibility
+try:
+ from typing import BinaryIO, Iterable, Text
+except ImportError:
+ pass
+
+
+class ResourceReader(ABC):
+ """Abstract base class for loaders to provide resource reading support."""
+
+ @abc.abstractmethod
+ def open_resource(self, resource):
+ # type: (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):
+ # type: (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):
+ # type: (Text) -> bool
+ """Return True if the named 'path' is a resource.
+
+ Files are resources, directories are not.
+ """
+ raise FileNotFoundError
+
+ @abc.abstractmethod
+ def contents(self):
+ # type: () -> Iterable[str]
+ """Return an iterable of entries in `package`."""
+ raise FileNotFoundError
+
+
+@runtime_checkable
+class Traversable(Protocol):
+ """
+ An object with a subset of pathlib.Path methods suitable for
+ traversing directories and opening files.
+ """
+
+ @abc.abstractmethod
+ def iterdir(self):
+ """
+ Yield Traversable objects in self
+ """
+
+ @abc.abstractmethod
+ def read_bytes(self):
+ """
+ Read contents of self as bytes
+ """
+
+ @abc.abstractmethod
+ def read_text(self, encoding=None):
+ """
+ Read contents of self as bytes
+ """
+
+ @abc.abstractmethod
+ def is_dir(self):
+ """
+ Return True if self is a dir
+ """
+
+ @abc.abstractmethod
+ def is_file(self):
+ """
+ Return True if self is a file
+ """
+
+ @abc.abstractmethod
+ def joinpath(self, child):
+ """
+ Return Traversable child in self
+ """
+
+ @abc.abstractmethod
+ def __truediv__(self, child):
+ """
+ Return Traversable child in self
+ """
+
+ @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.
+ """
+
+ @abc.abstractproperty
+ def name(self):
+ # type: () -> str
+ """
+ The base name of this object without any parent references.
+ """
+
+
+class TraversableResources(ResourceReader):
+ @abc.abstractmethod
+ def files(self):
+ """Return a Traversable object for the loaded package."""
+
+ def open_resource(self, resource):
+ return self.files().joinpath(resource).open('rb')
+
+ def resource_path(self, resource):
+ raise FileNotFoundError(resource)
+
+ def is_resource(self, path):
+ return self.files().joinpath(path).is_file()
+
+ def contents(self):
+ return (item.name for item in self.files().iterdir())
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/py.typed b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/py.typed
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/py.typed
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/readers.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/readers.py
new file mode 100644
index 0000000000..ce9c0caec4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/readers.py
@@ -0,0 +1,123 @@
+import os.path
+
+from collections import OrderedDict
+
+from . import abc
+
+from ._compat import Path, ZipPath
+from ._compat import FileNotFoundError, NotADirectoryError
+
+
+class FileReader(abc.TraversableResources):
+ def __init__(self, loader):
+ self.path = Path(loader.path).parent
+
+ 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
+
+
+class ZipReader(abc.TraversableResources):
+ def __init__(self, loader, module):
+ _, _, name = module.rpartition('.')
+ self.prefix = loader.prefix.replace('\\', '/') + name + '/'
+ self.archive = loader.archive
+
+ def open_resource(self, resource):
+ try:
+ return super().open_resource(resource)
+ except KeyError as exc:
+ raise FileNotFoundError(exc.args[0])
+
+ def is_resource(self, path):
+ # workaround for `zipfile.Path.is_file` returning true
+ # for non-existent paths.
+ target = self.files().joinpath(path)
+ return target.is_file() and target.exists()
+
+ def files(self):
+ return ZipPath(self.archive, self.prefix)
+
+
+class MultiplexedPath(abc.Traversable):
+ """
+ Given a series of Traversable objects, implement a merged
+ version of the interface across all objects. Useful for
+ namespace packages which may be multihomed at a single
+ name.
+ """
+ def __init__(self, *paths):
+ paths = list(OrderedDict.fromkeys(paths)) # remove duplicates
+ self._paths = list(map(Path, paths))
+ if not self._paths:
+ message = 'MultiplexedPath must contain at least one path'
+ raise FileNotFoundError(message)
+ if any(not path.is_dir() for path in self._paths):
+ raise NotADirectoryError(
+ 'MultiplexedPath only supports directories')
+
+ def iterdir(self):
+ visited = []
+ for path in self._paths:
+ for file in path.iterdir():
+ if file.name in visited:
+ continue
+ visited.append(file.name)
+ yield file
+
+ def read_bytes(self):
+ raise FileNotFoundError('{} is not a file'.format(self))
+
+ def read_text(self, *args, **kwargs):
+ raise FileNotFoundError('{} is not a file'.format(self))
+
+ def is_dir(self):
+ return True
+
+ def is_file(self):
+ return False
+
+ def joinpath(self, child):
+ # first try to find child in current paths
+ for file in self.iterdir():
+ if file.name == child:
+ return file
+ # if it does not exist, construct it with the first path
+ return self._paths[0] / child
+
+ __truediv__ = joinpath
+
+ def open(self, *args, **kwargs):
+ raise FileNotFoundError('{} is not a file'.format(self))
+
+ def name(self):
+ return os.path.basename(self._paths[0])
+
+ def __repr__(self):
+ return 'MultiplexedPath({})'.format(
+ ', '.join("'{}'".format(path) for path in self._paths))
+
+
+class NamespaceReader(abc.TraversableResources):
+ def __init__(self, namespace_path):
+ if 'NamespacePath' not in str(namespace_path):
+ raise ValueError('Invalid path')
+ self.path = MultiplexedPath(*list(namespace_path))
+
+ 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
diff --git a/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/trees.py b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/trees.py
new file mode 100644
index 0000000000..ba42bb55b7
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/trees.py
@@ -0,0 +1,6 @@
+# for compatibility with 1.1, continue to expose as_file here.
+
+from ._common import as_file
+
+
+__all__ = ['as_file']
diff --git a/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/DESCRIPTION.rst b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/DESCRIPTION.rst
new file mode 100644
index 0000000000..74e3bab198
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/DESCRIPTION.rst
@@ -0,0 +1,61 @@
+The `old pathlib <https://bitbucket.org/pitrou/pathlib>`_
+module on bitbucket is in bugfix-only mode.
+The goal of pathlib2 is to provide a backport of
+`standard pathlib <http://docs.python.org/dev/library/pathlib.html>`_
+module which tracks the standard library module,
+so all the newest features of the standard pathlib can be
+used also on older Python versions.
+
+Download
+--------
+
+Standalone releases are available on PyPI:
+http://pypi.python.org/pypi/pathlib2/
+
+Development
+-----------
+
+The main development takes place in the Python standard library: see
+the `Python developer's guide <http://docs.python.org/devguide/>`_.
+In particular, new features should be submitted to the
+`Python bug tracker <http://bugs.python.org/>`_.
+
+Issues that occur in this backport, but that do not occur not in the
+standard Python pathlib module can be submitted on
+the `pathlib2 bug tracker <https://github.com/mcmtroffaes/pathlib2/issues>`_.
+
+Documentation
+-------------
+
+Refer to the
+`standard pathlib <http://docs.python.org/dev/library/pathlib.html>`_
+documentation.
+
+Known Issues
+------------
+
+For historic reasons, pathlib2 still uses bytes to represent file paths internally.
+Unfortunately, on Windows with Python 2.7, the file system encoder (``mcbs``)
+has only poor support for non-ascii characters,
+and can silently replace non-ascii characters without warning.
+For example, ``u'тест'.encode(sys.getfilesystemencoding())`` results in ``????``
+which is obviously completely useless.
+
+Therefore, on Windows with Python 2.7, until this problem is fixed upstream,
+unfortunately you cannot rely on pathlib2 to support the full unicode range for filenames.
+See `issue #56 <https://github.com/mcmtroffaes/pathlib2/issues/56>`_ for more details.
+
+.. |travis| image:: https://travis-ci.org/mcmtroffaes/pathlib2.png?branch=develop
+ :target: https://travis-ci.org/mcmtroffaes/pathlib2
+ :alt: travis-ci
+
+.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/baddx3rpet2wyi2c?svg=true
+ :target: https://ci.appveyor.com/project/mcmtroffaes/pathlib2
+ :alt: appveyor
+
+.. |codecov| image:: https://codecov.io/gh/mcmtroffaes/pathlib2/branch/develop/graph/badge.svg
+ :target: https://codecov.io/gh/mcmtroffaes/pathlib2
+ :alt: codecov
+
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/METADATA
new file mode 100644
index 0000000000..df7284e078
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/METADATA
@@ -0,0 +1,88 @@
+Metadata-Version: 2.0
+Name: pathlib2
+Version: 2.3.5
+Summary: Object-oriented filesystem paths
+Home-page: https://github.com/mcmtroffaes/pathlib2
+Author: Matthias C. M. Troffaes
+Author-email: matthias.troffaes@gmail.com
+License: MIT
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: System :: Filesystems
+Requires-Dist: six
+Requires-Dist: scandir; python_version<"3.5"
+
+The `old pathlib <https://bitbucket.org/pitrou/pathlib>`_
+module on bitbucket is in bugfix-only mode.
+The goal of pathlib2 is to provide a backport of
+`standard pathlib <http://docs.python.org/dev/library/pathlib.html>`_
+module which tracks the standard library module,
+so all the newest features of the standard pathlib can be
+used also on older Python versions.
+
+Download
+--------
+
+Standalone releases are available on PyPI:
+http://pypi.python.org/pypi/pathlib2/
+
+Development
+-----------
+
+The main development takes place in the Python standard library: see
+the `Python developer's guide <http://docs.python.org/devguide/>`_.
+In particular, new features should be submitted to the
+`Python bug tracker <http://bugs.python.org/>`_.
+
+Issues that occur in this backport, but that do not occur not in the
+standard Python pathlib module can be submitted on
+the `pathlib2 bug tracker <https://github.com/mcmtroffaes/pathlib2/issues>`_.
+
+Documentation
+-------------
+
+Refer to the
+`standard pathlib <http://docs.python.org/dev/library/pathlib.html>`_
+documentation.
+
+Known Issues
+------------
+
+For historic reasons, pathlib2 still uses bytes to represent file paths internally.
+Unfortunately, on Windows with Python 2.7, the file system encoder (``mcbs``)
+has only poor support for non-ascii characters,
+and can silently replace non-ascii characters without warning.
+For example, ``u'тест'.encode(sys.getfilesystemencoding())`` results in ``????``
+which is obviously completely useless.
+
+Therefore, on Windows with Python 2.7, until this problem is fixed upstream,
+unfortunately you cannot rely on pathlib2 to support the full unicode range for filenames.
+See `issue #56 <https://github.com/mcmtroffaes/pathlib2/issues/56>`_ for more details.
+
+.. |travis| image:: https://travis-ci.org/mcmtroffaes/pathlib2.png?branch=develop
+ :target: https://travis-ci.org/mcmtroffaes/pathlib2
+ :alt: travis-ci
+
+.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/baddx3rpet2wyi2c?svg=true
+ :target: https://ci.appveyor.com/project/mcmtroffaes/pathlib2
+ :alt: appveyor
+
+.. |codecov| image:: https://codecov.io/gh/mcmtroffaes/pathlib2/branch/develop/graph/badge.svg
+ :target: https://codecov.io/gh/mcmtroffaes/pathlib2
+ :alt: codecov
+
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/RECORD
new file mode 100644
index 0000000000..6f922ae1f7
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/RECORD
@@ -0,0 +1,7 @@
+pathlib2-2.3.5.dist-info/metadata.json,sha256=yGoZ-uMKSmkZuZplsz2mNc8SWNIVEbnBaSyya01u5PI,1177
+pathlib2-2.3.5.dist-info/top_level.txt,sha256=tNPkisFiGBFsPUnCIHg62vSFlkx_1NO86Id8lbJmfFQ,9
+pathlib2-2.3.5.dist-info/METADATA,sha256=PEsNR-yYpbPUheyBje2_-GdAJfwXPDtWMSeSsR9VMY0,3300
+pathlib2-2.3.5.dist-info/RECORD,,
+pathlib2-2.3.5.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
+pathlib2-2.3.5.dist-info/DESCRIPTION.rst,sha256=E6WnieIR9MTnqUQ1746RCpdq3fqlkvqX0Z51-Wpxga8,2250
+pathlib2/__init__.py,sha256=NBfu5wacps1y1YtlXVSPJ8FbE4WtIXucrp5uOYNOO-U,59133
diff --git a/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/WHEEL
new file mode 100644
index 0000000000..9dff69d861
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.24.0)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/metadata.json b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/metadata.json
new file mode 100644
index 0000000000..575c5271d2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/metadata.json
@@ -0,0 +1 @@
+{"license": "MIT", "name": "pathlib2", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "Object-oriented filesystem paths", "run_requires": [{"environment": "python_version<\"3.5\"", "requires": ["scandir"]}, {"requires": ["six"]}], "version": "2.3.5", "extensions": {"python.details": {"project_urls": {"Home": "https://github.com/mcmtroffaes/pathlib2"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "matthias.troffaes@gmail.com", "name": "Matthias C. M. Troffaes"}]}}, "classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries", "Topic :: System :: Filesystems"], "extras": []} \ No newline at end of file
diff --git a/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/top_level.txt
new file mode 100644
index 0000000000..83f3ebe0dd
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info/top_level.txt
@@ -0,0 +1 @@
+pathlib2
diff --git a/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2/__init__.py b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2/__init__.py
new file mode 100644
index 0000000000..d5a47a66c6
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2/__init__.py
@@ -0,0 +1,1809 @@
+# Copyright (c) 2014-2017 Matthias C. M. Troffaes
+# Copyright (c) 2012-2014 Antoine Pitrou and contributors
+# Distributed under the terms of the MIT License.
+
+import ctypes
+import fnmatch
+import functools
+import io
+import ntpath
+import os
+import posixpath
+import re
+import six
+import sys
+
+from errno import EINVAL, ENOENT, ENOTDIR, EBADF
+from errno import EEXIST, EPERM, EACCES
+from operator import attrgetter
+from stat import (
+ S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO)
+
+try:
+ from collections.abc import Sequence
+except ImportError:
+ from collections import Sequence
+
+try:
+ from urllib import quote as urlquote_from_bytes
+except ImportError:
+ from urllib.parse import quote_from_bytes as urlquote_from_bytes
+
+
+try:
+ intern = intern
+except NameError:
+ intern = sys.intern
+
+supports_symlinks = True
+if os.name == 'nt':
+ import nt
+ if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
+ from nt import _getfinalpathname
+ else:
+ supports_symlinks = False
+ _getfinalpathname = None
+else:
+ nt = None
+
+try:
+ from os import scandir as os_scandir
+except ImportError:
+ from scandir import scandir as os_scandir
+
+__all__ = [
+ "PurePath", "PurePosixPath", "PureWindowsPath",
+ "Path", "PosixPath", "WindowsPath",
+ ]
+
+#
+# Internals
+#
+
+# EBADF - guard agains macOS `stat` throwing EBADF
+_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF)
+
+_IGNORED_WINERRORS = (
+ 21, # ERROR_NOT_READY - drive exists but is not accessible
+)
+
+
+def _ignore_error(exception):
+ return (getattr(exception, 'errno', None) in _IGNORED_ERROS or
+ getattr(exception, 'winerror', None) in _IGNORED_WINERRORS)
+
+
+def _py2_fsencode(parts):
+ # py2 => minimal unicode support
+ assert six.PY2
+ return [part.encode('ascii') if isinstance(part, six.text_type)
+ else part for part in parts]
+
+
+def _try_except_fileexistserror(try_func, except_func, else_func=None):
+ if sys.version_info >= (3, 3):
+ try:
+ try_func()
+ except FileExistsError as exc:
+ except_func(exc)
+ else:
+ if else_func is not None:
+ else_func()
+ else:
+ try:
+ try_func()
+ except EnvironmentError as exc:
+ if exc.errno != EEXIST:
+ raise
+ else:
+ except_func(exc)
+ else:
+ if else_func is not None:
+ else_func()
+
+
+def _try_except_filenotfounderror(try_func, except_func):
+ if sys.version_info >= (3, 3):
+ try:
+ try_func()
+ except FileNotFoundError as exc:
+ except_func(exc)
+ elif os.name != 'nt':
+ try:
+ try_func()
+ except EnvironmentError as exc:
+ if exc.errno != ENOENT:
+ raise
+ else:
+ except_func(exc)
+ else:
+ try:
+ try_func()
+ except WindowsError as exc:
+ # errno contains winerror
+ # 2 = file not found
+ # 3 = path not found
+ if exc.errno not in (2, 3):
+ raise
+ else:
+ except_func(exc)
+ except EnvironmentError as exc:
+ if exc.errno != ENOENT:
+ raise
+ else:
+ except_func(exc)
+
+
+def _try_except_permissionerror_iter(try_iter, except_iter):
+ if sys.version_info >= (3, 3):
+ try:
+ for x in try_iter():
+ yield x
+ except PermissionError as exc:
+ for x in except_iter(exc):
+ yield x
+ else:
+ try:
+ for x in try_iter():
+ yield x
+ except EnvironmentError as exc:
+ if exc.errno not in (EPERM, EACCES):
+ raise
+ else:
+ for x in except_iter(exc):
+ yield x
+
+
+def _win32_get_unique_path_id(path):
+ # get file information, needed for samefile on older Python versions
+ # see http://timgolden.me.uk/python/win32_how_do_i/
+ # see_if_two_files_are_the_same_file.html
+ from ctypes import POINTER, Structure, WinError
+ from ctypes.wintypes import DWORD, HANDLE, BOOL
+
+ class FILETIME(Structure):
+ _fields_ = [("datetime_lo", DWORD),
+ ("datetime_hi", DWORD),
+ ]
+
+ class BY_HANDLE_FILE_INFORMATION(Structure):
+ _fields_ = [("attributes", DWORD),
+ ("created_at", FILETIME),
+ ("accessed_at", FILETIME),
+ ("written_at", FILETIME),
+ ("volume", DWORD),
+ ("file_hi", DWORD),
+ ("file_lo", DWORD),
+ ("n_links", DWORD),
+ ("index_hi", DWORD),
+ ("index_lo", DWORD),
+ ]
+
+ CreateFile = ctypes.windll.kernel32.CreateFileW
+ CreateFile.argtypes = [ctypes.c_wchar_p, DWORD, DWORD, ctypes.c_void_p,
+ DWORD, DWORD, HANDLE]
+ CreateFile.restype = HANDLE
+ GetFileInformationByHandle = (
+ ctypes.windll.kernel32.GetFileInformationByHandle)
+ GetFileInformationByHandle.argtypes = [
+ HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)]
+ GetFileInformationByHandle.restype = BOOL
+ CloseHandle = ctypes.windll.kernel32.CloseHandle
+ CloseHandle.argtypes = [HANDLE]
+ CloseHandle.restype = BOOL
+ GENERIC_READ = 0x80000000
+ FILE_SHARE_READ = 0x00000001
+ FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
+ OPEN_EXISTING = 3
+ if os.path.isdir(path):
+ flags = FILE_FLAG_BACKUP_SEMANTICS
+ else:
+ flags = 0
+ hfile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ,
+ None, OPEN_EXISTING, flags, None)
+ if hfile == 0xffffffff:
+ if sys.version_info >= (3, 3):
+ raise FileNotFoundError(path)
+ else:
+ exc = OSError("file not found: path")
+ exc.errno = ENOENT
+ raise exc
+ info = BY_HANDLE_FILE_INFORMATION()
+ success = GetFileInformationByHandle(hfile, info)
+ CloseHandle(hfile)
+ if success == 0:
+ raise WinError()
+ return info.volume, info.index_hi, info.index_lo
+
+
+def _is_wildcard_pattern(pat):
+ # Whether this pattern needs actual matching using fnmatch, or can
+ # be looked up directly as a file.
+ return "*" in pat or "?" in pat or "[" in pat
+
+
+class _Flavour(object):
+
+ """A flavour implements a particular (platform-specific) set of path
+ semantics."""
+
+ def __init__(self):
+ self.join = self.sep.join
+
+ def parse_parts(self, parts):
+ if six.PY2:
+ parts = _py2_fsencode(parts)
+ parsed = []
+ sep = self.sep
+ altsep = self.altsep
+ drv = root = ''
+ it = reversed(parts)
+ for part in it:
+ if not part:
+ continue
+ if altsep:
+ part = part.replace(altsep, sep)
+ drv, root, rel = self.splitroot(part)
+ if sep in rel:
+ for x in reversed(rel.split(sep)):
+ if x and x != '.':
+ parsed.append(intern(x))
+ else:
+ if rel and rel != '.':
+ parsed.append(intern(rel))
+ if drv or root:
+ if not drv:
+ # If no drive is present, try to find one in the previous
+ # parts. This makes the result of parsing e.g.
+ # ("C:", "/", "a") reasonably intuitive.
+ for part in it:
+ if not part:
+ continue
+ if altsep:
+ part = part.replace(altsep, sep)
+ drv = self.splitroot(part)[0]
+ if drv:
+ break
+ break
+ if drv or root:
+ parsed.append(drv + root)
+ parsed.reverse()
+ return drv, root, parsed
+
+ def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+ """
+ Join the two paths represented by the respective
+ (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+ """
+ if root2:
+ if not drv2 and drv:
+ return drv, root2, [drv + root2] + parts2[1:]
+ elif drv2:
+ if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
+ # Same drive => second path is relative to the first
+ return drv, root, parts + parts2[1:]
+ else:
+ # Second path is non-anchored (common case)
+ return drv, root, parts + parts2
+ return drv2, root2, parts2
+
+
+class _WindowsFlavour(_Flavour):
+ # Reference for Windows paths can be found at
+ # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+
+ sep = '\\'
+ altsep = '/'
+ has_drv = True
+ pathmod = ntpath
+
+ is_supported = (os.name == 'nt')
+
+ drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
+ ext_namespace_prefix = '\\\\?\\'
+
+ reserved_names = (
+ set(['CON', 'PRN', 'AUX', 'NUL']) |
+ set(['COM%d' % i for i in range(1, 10)]) |
+ set(['LPT%d' % i for i in range(1, 10)])
+ )
+
+ # Interesting findings about extended paths:
+ # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+ # but '\\?\c:/a' is not
+ # - extended paths are always absolute; "relative" extended paths will
+ # fail.
+
+ def splitroot(self, part, sep=sep):
+ first = part[0:1]
+ second = part[1:2]
+ if (second == sep and first == sep):
+ # XXX extended paths should also disable the collapsing of "."
+ # components (according to MSDN docs).
+ prefix, part = self._split_extended_path(part)
+ first = part[0:1]
+ second = part[1:2]
+ else:
+ prefix = ''
+ third = part[2:3]
+ if (second == sep and first == sep and third != sep):
+ # is a UNC path:
+ # vvvvvvvvvvvvvvvvvvvvv root
+ # \\machine\mountpoint\directory\etc\...
+ # directory ^^^^^^^^^^^^^^
+ index = part.find(sep, 2)
+ if index != -1:
+ index2 = part.find(sep, index + 1)
+ # a UNC path can't have two slashes in a row
+ # (after the initial two)
+ if index2 != index + 1:
+ if index2 == -1:
+ index2 = len(part)
+ if prefix:
+ return prefix + part[1:index2], sep, part[index2 + 1:]
+ else:
+ return part[:index2], sep, part[index2 + 1:]
+ drv = root = ''
+ if second == ':' and first in self.drive_letters:
+ drv = part[:2]
+ part = part[2:]
+ first = third
+ if first == sep:
+ root = first
+ part = part.lstrip(sep)
+ return prefix + drv, root, part
+
+ def casefold(self, s):
+ return s.lower()
+
+ def casefold_parts(self, parts):
+ return [p.lower() for p in parts]
+
+ def resolve(self, path, strict=False):
+ s = str(path)
+ if not s:
+ return os.getcwd()
+ previous_s = None
+ if _getfinalpathname is not None:
+ if strict:
+ return self._ext_to_normal(_getfinalpathname(s))
+ else:
+ # End of the path after the first one not found
+ tail_parts = []
+
+ def _try_func():
+ result[0] = self._ext_to_normal(_getfinalpathname(s))
+ # if there was no exception, set flag to 0
+ result[1] = 0
+
+ def _exc_func(exc):
+ pass
+
+ while True:
+ result = [None, 1]
+ _try_except_filenotfounderror(_try_func, _exc_func)
+ if result[1] == 1: # file not found exception raised
+ previous_s = s
+ s, tail = os.path.split(s)
+ tail_parts.append(tail)
+ if previous_s == s:
+ return path
+ else:
+ s = result[0]
+ return os.path.join(s, *reversed(tail_parts))
+ # Means fallback on absolute
+ return None
+
+ def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+ prefix = ''
+ if s.startswith(ext_prefix):
+ prefix = s[:4]
+ s = s[4:]
+ if s.startswith('UNC\\'):
+ prefix += s[:3]
+ s = '\\' + s[3:]
+ return prefix, s
+
+ def _ext_to_normal(self, s):
+ # Turn back an extended path into a normal DOS-like path
+ return self._split_extended_path(s)[1]
+
+ def is_reserved(self, parts):
+ # NOTE: the rules for reserved names seem somewhat complicated
+ # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+ # We err on the side of caution and return True for paths which are
+ # not considered reserved by Windows.
+ if not parts:
+ return False
+ if parts[0].startswith('\\\\'):
+ # UNC paths are never reserved
+ return False
+ return parts[-1].partition('.')[0].upper() in self.reserved_names
+
+ def make_uri(self, path):
+ # Under Windows, file URIs use the UTF-8 encoding.
+ drive = path.drive
+ if len(drive) == 2 and drive[1] == ':':
+ # It's a path on a local drive => 'file:///c:/a/b'
+ rest = path.as_posix()[2:].lstrip('/')
+ return 'file:///%s/%s' % (
+ drive, urlquote_from_bytes(rest.encode('utf-8')))
+ else:
+ # It's a path on a network drive => 'file://host/share/a/b'
+ return 'file:' + urlquote_from_bytes(
+ path.as_posix().encode('utf-8'))
+
+ def gethomedir(self, username):
+ if 'HOME' in os.environ:
+ userhome = os.environ['HOME']
+ elif 'USERPROFILE' in os.environ:
+ userhome = os.environ['USERPROFILE']
+ elif 'HOMEPATH' in os.environ:
+ try:
+ drv = os.environ['HOMEDRIVE']
+ except KeyError:
+ drv = ''
+ userhome = drv + os.environ['HOMEPATH']
+ else:
+ raise RuntimeError("Can't determine home directory")
+
+ if username:
+ # Try to guess user home directory. By default all users
+ # directories are located in the same place and are named by
+ # corresponding usernames. If current user home directory points
+ # to nonstandard place, this guess is likely wrong.
+ if os.environ['USERNAME'] != username:
+ drv, root, parts = self.parse_parts((userhome,))
+ if parts[-1] != os.environ['USERNAME']:
+ raise RuntimeError("Can't determine home directory "
+ "for %r" % username)
+ parts[-1] = username
+ if drv or root:
+ userhome = drv + root + self.join(parts[1:])
+ else:
+ userhome = self.join(parts)
+ return userhome
+
+
+class _PosixFlavour(_Flavour):
+ sep = '/'
+ altsep = ''
+ has_drv = False
+ pathmod = posixpath
+
+ is_supported = (os.name != 'nt')
+
+ def splitroot(self, part, sep=sep):
+ if part and part[0] == sep:
+ stripped_part = part.lstrip(sep)
+ # According to POSIX path resolution:
+ # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/
+ # xbd_chap04.html#tag_04_11
+ # "A pathname that begins with two successive slashes may be
+ # interpreted in an implementation-defined manner, although more
+ # than two leading slashes shall be treated as a single slash".
+ if len(part) - len(stripped_part) == 2:
+ return '', sep * 2, stripped_part
+ else:
+ return '', sep, stripped_part
+ else:
+ return '', '', part
+
+ def casefold(self, s):
+ return s
+
+ def casefold_parts(self, parts):
+ return parts
+
+ def resolve(self, path, strict=False):
+ sep = self.sep
+ accessor = path._accessor
+ seen = {}
+
+ def _resolve(path, rest):
+ if rest.startswith(sep):
+ path = ''
+
+ for name in rest.split(sep):
+ if not name or name == '.':
+ # current dir
+ continue
+ if name == '..':
+ # parent dir
+ path, _, _ = path.rpartition(sep)
+ continue
+ newpath = path + sep + name
+ if newpath in seen:
+ # Already seen this path
+ path = seen[newpath]
+ if path is not None:
+ # use cached value
+ continue
+ # The symlink is not resolved, so we must have a symlink
+ # loop.
+ raise RuntimeError("Symlink loop from %r" % newpath)
+ # Resolve the symbolic link
+ try:
+ target = accessor.readlink(newpath)
+ except OSError as e:
+ if e.errno != EINVAL and strict:
+ raise
+ # Not a symlink, or non-strict mode. We just leave the path
+ # untouched.
+ path = newpath
+ else:
+ seen[newpath] = None # not resolved symlink
+ path = _resolve(path, target)
+ seen[newpath] = path # resolved symlink
+
+ return path
+ # NOTE: according to POSIX, getcwd() cannot contain path components
+ # which are symlinks.
+ base = '' if path.is_absolute() else os.getcwd()
+ return _resolve(base, str(path)) or sep
+
+ def is_reserved(self, parts):
+ return False
+
+ def make_uri(self, path):
+ # We represent the path using the local filesystem encoding,
+ # for portability to other applications.
+ bpath = bytes(path)
+ return 'file://' + urlquote_from_bytes(bpath)
+
+ def gethomedir(self, username):
+ if not username:
+ try:
+ return os.environ['HOME']
+ except KeyError:
+ import pwd
+ return pwd.getpwuid(os.getuid()).pw_dir
+ else:
+ import pwd
+ try:
+ return pwd.getpwnam(username).pw_dir
+ except KeyError:
+ raise RuntimeError("Can't determine home directory "
+ "for %r" % username)
+
+
+_windows_flavour = _WindowsFlavour()
+_posix_flavour = _PosixFlavour()
+
+
+class _Accessor:
+
+ """An accessor implements a particular (system-specific or not) way of
+ accessing paths on the filesystem."""
+
+
+class _NormalAccessor(_Accessor):
+
+ def _wrap_strfunc(strfunc):
+ @functools.wraps(strfunc)
+ def wrapped(pathobj, *args):
+ return strfunc(str(pathobj), *args)
+ return staticmethod(wrapped)
+
+ def _wrap_binary_strfunc(strfunc):
+ @functools.wraps(strfunc)
+ def wrapped(pathobjA, pathobjB, *args):
+ return strfunc(str(pathobjA), str(pathobjB), *args)
+ return staticmethod(wrapped)
+
+ stat = _wrap_strfunc(os.stat)
+
+ lstat = _wrap_strfunc(os.lstat)
+
+ open = _wrap_strfunc(os.open)
+
+ listdir = _wrap_strfunc(os.listdir)
+
+ scandir = _wrap_strfunc(os_scandir)
+
+ chmod = _wrap_strfunc(os.chmod)
+
+ if hasattr(os, "lchmod"):
+ lchmod = _wrap_strfunc(os.lchmod)
+ else:
+ def lchmod(self, pathobj, mode):
+ raise NotImplementedError("lchmod() not available on this system")
+
+ mkdir = _wrap_strfunc(os.mkdir)
+
+ unlink = _wrap_strfunc(os.unlink)
+
+ rmdir = _wrap_strfunc(os.rmdir)
+
+ rename = _wrap_binary_strfunc(os.rename)
+
+ if sys.version_info >= (3, 3):
+ replace = _wrap_binary_strfunc(os.replace)
+
+ if nt:
+ if supports_symlinks:
+ symlink = _wrap_binary_strfunc(os.symlink)
+ else:
+ def symlink(a, b, target_is_directory):
+ raise NotImplementedError(
+ "symlink() not available on this system")
+ else:
+ # Under POSIX, os.symlink() takes two args
+ @staticmethod
+ def symlink(a, b, target_is_directory):
+ return os.symlink(str(a), str(b))
+
+ utime = _wrap_strfunc(os.utime)
+
+ # Helper for resolve()
+ def readlink(self, path):
+ return os.readlink(path)
+
+
+_normal_accessor = _NormalAccessor()
+
+
+#
+# Globbing helpers
+#
+
+def _make_selector(pattern_parts):
+ pat = pattern_parts[0]
+ child_parts = pattern_parts[1:]
+ if pat == '**':
+ cls = _RecursiveWildcardSelector
+ elif '**' in pat:
+ raise ValueError(
+ "Invalid pattern: '**' can only be an entire path component")
+ elif _is_wildcard_pattern(pat):
+ cls = _WildcardSelector
+ else:
+ cls = _PreciseSelector
+ return cls(pat, child_parts)
+
+
+if hasattr(functools, "lru_cache"):
+ _make_selector = functools.lru_cache()(_make_selector)
+
+
+class _Selector:
+
+ """A selector matches a specific glob pattern part against the children
+ of a given path."""
+
+ def __init__(self, child_parts):
+ self.child_parts = child_parts
+ if child_parts:
+ self.successor = _make_selector(child_parts)
+ self.dironly = True
+ else:
+ self.successor = _TerminatingSelector()
+ self.dironly = False
+
+ def select_from(self, parent_path):
+ """Iterate over all child paths of `parent_path` matched by this
+ selector. This can contain parent_path itself."""
+ path_cls = type(parent_path)
+ is_dir = path_cls.is_dir
+ exists = path_cls.exists
+ scandir = parent_path._accessor.scandir
+ if not is_dir(parent_path):
+ return iter([])
+ return self._select_from(parent_path, is_dir, exists, scandir)
+
+
+class _TerminatingSelector:
+
+ def _select_from(self, parent_path, is_dir, exists, scandir):
+ yield parent_path
+
+
+class _PreciseSelector(_Selector):
+
+ def __init__(self, name, child_parts):
+ self.name = name
+ _Selector.__init__(self, child_parts)
+
+ def _select_from(self, parent_path, is_dir, exists, scandir):
+ def try_iter():
+ path = parent_path._make_child_relpath(self.name)
+ if (is_dir if self.dironly else exists)(path):
+ for p in self.successor._select_from(
+ path, is_dir, exists, scandir):
+ yield p
+
+ def except_iter(exc):
+ return
+ yield
+
+ for x in _try_except_permissionerror_iter(try_iter, except_iter):
+ yield x
+
+
+class _WildcardSelector(_Selector):
+
+ def __init__(self, pat, child_parts):
+ self.pat = re.compile(fnmatch.translate(pat))
+ _Selector.__init__(self, child_parts)
+
+ def _select_from(self, parent_path, is_dir, exists, scandir):
+ def try_iter():
+ cf = parent_path._flavour.casefold
+ entries = list(scandir(parent_path))
+ for entry in entries:
+ if not self.dironly or entry.is_dir():
+ name = entry.name
+ casefolded = cf(name)
+ if self.pat.match(casefolded):
+ path = parent_path._make_child_relpath(name)
+ for p in self.successor._select_from(
+ path, is_dir, exists, scandir):
+ yield p
+
+ def except_iter(exc):
+ return
+ yield
+
+ for x in _try_except_permissionerror_iter(try_iter, except_iter):
+ yield x
+
+
+class _RecursiveWildcardSelector(_Selector):
+
+ def __init__(self, pat, child_parts):
+ _Selector.__init__(self, child_parts)
+
+ def _iterate_directories(self, parent_path, is_dir, scandir):
+ yield parent_path
+
+ def try_iter():
+ entries = list(scandir(parent_path))
+ for entry in entries:
+ entry_is_dir = False
+ try:
+ entry_is_dir = entry.is_dir()
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ if entry_is_dir and not entry.is_symlink():
+ path = parent_path._make_child_relpath(entry.name)
+ for p in self._iterate_directories(path, is_dir, scandir):
+ yield p
+
+ def except_iter(exc):
+ return
+ yield
+
+ for x in _try_except_permissionerror_iter(try_iter, except_iter):
+ yield x
+
+ def _select_from(self, parent_path, is_dir, exists, scandir):
+ def try_iter():
+ yielded = set()
+ try:
+ successor_select = self.successor._select_from
+ for starting_point in self._iterate_directories(
+ parent_path, is_dir, scandir):
+ for p in successor_select(
+ starting_point, is_dir, exists, scandir):
+ if p not in yielded:
+ yield p
+ yielded.add(p)
+ finally:
+ yielded.clear()
+
+ def except_iter(exc):
+ return
+ yield
+
+ for x in _try_except_permissionerror_iter(try_iter, except_iter):
+ yield x
+
+
+#
+# Public API
+#
+
+class _PathParents(Sequence):
+
+ """This object provides sequence-like access to the logical ancestors
+ of a path. Don't try to construct it yourself."""
+ __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+
+ def __init__(self, path):
+ # We don't store the instance to avoid reference cycles
+ self._pathcls = type(path)
+ self._drv = path._drv
+ self._root = path._root
+ self._parts = path._parts
+
+ def __len__(self):
+ if self._drv or self._root:
+ return len(self._parts) - 1
+ else:
+ return len(self._parts)
+
+ def __getitem__(self, idx):
+ if idx < 0 or idx >= len(self):
+ raise IndexError(idx)
+ return self._pathcls._from_parsed_parts(self._drv, self._root,
+ self._parts[:-idx - 1])
+
+ def __repr__(self):
+ return "<{0}.parents>".format(self._pathcls.__name__)
+
+
+class PurePath(object):
+
+ """PurePath represents a filesystem path and offers operations which
+ don't imply any actual filesystem I/O. Depending on your system,
+ instantiating a PurePath will return either a PurePosixPath or a
+ PureWindowsPath object. You can also instantiate either of these classes
+ directly, regardless of your system.
+ """
+ __slots__ = (
+ '_drv', '_root', '_parts',
+ '_str', '_hash', '_pparts', '_cached_cparts',
+ )
+
+ def __new__(cls, *args):
+ """Construct a PurePath from one or several strings and or existing
+ PurePath objects. The strings and path objects are combined so as
+ to yield a canonicalized path, which is incorporated into the
+ new PurePath object.
+ """
+ if cls is PurePath:
+ cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+ return cls._from_parts(args)
+
+ def __reduce__(self):
+ # Using the parts tuple helps share interned path parts
+ # when pickling related paths.
+ return (self.__class__, tuple(self._parts))
+
+ @classmethod
+ def _parse_args(cls, args):
+ # This is useful when you don't want to create an instance, just
+ # canonicalize some constructor arguments.
+ parts = []
+ for a in args:
+ if isinstance(a, PurePath):
+ parts += a._parts
+ else:
+ if sys.version_info >= (3, 6):
+ a = os.fspath(a)
+ else:
+ # duck typing for older Python versions
+ if hasattr(a, "__fspath__"):
+ a = a.__fspath__()
+ if isinstance(a, str):
+ # Force-cast str subclasses to str (issue #21127)
+ parts.append(str(a))
+ # also handle unicode for PY2 (six.text_type = unicode)
+ elif six.PY2 and isinstance(a, six.text_type):
+ # cast to str using filesystem encoding
+ # note: in rare circumstances, on Python < 3.2,
+ # getfilesystemencoding can return None, in that
+ # case fall back to ascii
+ parts.append(a.encode(
+ sys.getfilesystemencoding() or "ascii"))
+ else:
+ raise TypeError(
+ "argument should be a str object or an os.PathLike "
+ "object returning str, not %r"
+ % type(a))
+ return cls._flavour.parse_parts(parts)
+
+ @classmethod
+ def _from_parts(cls, args, init=True):
+ # We need to call _parse_args on the instance, so as to get the
+ # right flavour.
+ self = object.__new__(cls)
+ drv, root, parts = self._parse_args(args)
+ self._drv = drv
+ self._root = root
+ self._parts = parts
+ if init:
+ self._init()
+ return self
+
+ @classmethod
+ def _from_parsed_parts(cls, drv, root, parts, init=True):
+ self = object.__new__(cls)
+ self._drv = drv
+ self._root = root
+ self._parts = parts
+ if init:
+ self._init()
+ return self
+
+ @classmethod
+ def _format_parsed_parts(cls, drv, root, parts):
+ if drv or root:
+ return drv + root + cls._flavour.join(parts[1:])
+ else:
+ return cls._flavour.join(parts)
+
+ def _init(self):
+ # Overridden in concrete Path
+ pass
+
+ def _make_child(self, args):
+ drv, root, parts = self._parse_args(args)
+ drv, root, parts = self._flavour.join_parsed_parts(
+ self._drv, self._root, self._parts, drv, root, parts)
+ return self._from_parsed_parts(drv, root, parts)
+
+ def __str__(self):
+ """Return the string representation of the path, suitable for
+ passing to system calls."""
+ try:
+ return self._str
+ except AttributeError:
+ self._str = self._format_parsed_parts(self._drv, self._root,
+ self._parts) or '.'
+ return self._str
+
+ def __fspath__(self):
+ return str(self)
+
+ def as_posix(self):
+ """Return the string representation of the path with forward (/)
+ slashes."""
+ f = self._flavour
+ return str(self).replace(f.sep, '/')
+
+ def __bytes__(self):
+ """Return the bytes representation of the path. This is only
+ recommended to use under Unix."""
+ if sys.version_info < (3, 2):
+ raise NotImplementedError("needs Python 3.2 or later")
+ return os.fsencode(str(self))
+
+ def __repr__(self):
+ return "{0}({1!r})".format(self.__class__.__name__, self.as_posix())
+
+ def as_uri(self):
+ """Return the path as a 'file' URI."""
+ if not self.is_absolute():
+ raise ValueError("relative path can't be expressed as a file URI")
+ return self._flavour.make_uri(self)
+
+ @property
+ def _cparts(self):
+ # Cached casefolded parts, for hashing and comparison
+ try:
+ return self._cached_cparts
+ except AttributeError:
+ self._cached_cparts = self._flavour.casefold_parts(self._parts)
+ return self._cached_cparts
+
+ def __eq__(self, other):
+ if not isinstance(other, PurePath):
+ return NotImplemented
+ return (
+ self._cparts == other._cparts
+ and self._flavour is other._flavour)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ try:
+ return self._hash
+ except AttributeError:
+ self._hash = hash(tuple(self._cparts))
+ return self._hash
+
+ def __lt__(self, other):
+ if (not isinstance(other, PurePath)
+ or self._flavour is not other._flavour):
+ return NotImplemented
+ return self._cparts < other._cparts
+
+ def __le__(self, other):
+ if (not isinstance(other, PurePath)
+ or self._flavour is not other._flavour):
+ return NotImplemented
+ return self._cparts <= other._cparts
+
+ def __gt__(self, other):
+ if (not isinstance(other, PurePath)
+ or self._flavour is not other._flavour):
+ return NotImplemented
+ return self._cparts > other._cparts
+
+ def __ge__(self, other):
+ if (not isinstance(other, PurePath)
+ or self._flavour is not other._flavour):
+ return NotImplemented
+ return self._cparts >= other._cparts
+
+ drive = property(attrgetter('_drv'),
+ doc="""The drive prefix (letter or UNC path), if any.""")
+
+ root = property(attrgetter('_root'),
+ doc="""The root of the path, if any.""")
+
+ @property
+ def anchor(self):
+ """The concatenation of the drive and root, or ''."""
+ anchor = self._drv + self._root
+ return anchor
+
+ @property
+ def name(self):
+ """The final path component, if any."""
+ parts = self._parts
+ if len(parts) == (1 if (self._drv or self._root) else 0):
+ return ''
+ return parts[-1]
+
+ @property
+ def suffix(self):
+ """The final component's last suffix, if any."""
+ name = self.name
+ i = name.rfind('.')
+ if 0 < i < len(name) - 1:
+ return name[i:]
+ else:
+ return ''
+
+ @property
+ def suffixes(self):
+ """A list of the final component's suffixes, if any."""
+ name = self.name
+ if name.endswith('.'):
+ return []
+ name = name.lstrip('.')
+ return ['.' + suffix for suffix in name.split('.')[1:]]
+
+ @property
+ def stem(self):
+ """The final path component, minus its last suffix."""
+ name = self.name
+ i = name.rfind('.')
+ if 0 < i < len(name) - 1:
+ return name[:i]
+ else:
+ return name
+
+ def with_name(self, name):
+ """Return a new path with the file name changed."""
+ if not self.name:
+ raise ValueError("%r has an empty name" % (self,))
+ drv, root, parts = self._flavour.parse_parts((name,))
+ if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep]
+ or drv or root or len(parts) != 1):
+ raise ValueError("Invalid name %r" % (name))
+ return self._from_parsed_parts(self._drv, self._root,
+ self._parts[:-1] + [name])
+
+ def with_suffix(self, suffix):
+ """Return a new path with the file suffix changed. If the path
+ has no suffix, add given suffix. If the given suffix is an empty
+ string, remove the suffix from the path.
+ """
+ # XXX if suffix is None, should the current suffix be removed?
+ f = self._flavour
+ if f.sep in suffix or f.altsep and f.altsep in suffix:
+ raise ValueError("Invalid suffix %r" % (suffix))
+ if suffix and not suffix.startswith('.') or suffix == '.':
+ raise ValueError("Invalid suffix %r" % (suffix))
+ name = self.name
+ if not name:
+ raise ValueError("%r has an empty name" % (self,))
+ old_suffix = self.suffix
+ if not old_suffix:
+ name = name + suffix
+ else:
+ name = name[:-len(old_suffix)] + suffix
+ return self._from_parsed_parts(self._drv, self._root,
+ self._parts[:-1] + [name])
+
+ def relative_to(self, *other):
+ """Return the relative path to another path identified by the passed
+ arguments. If the operation is not possible (because this is not
+ a subpath of the other path), raise ValueError.
+ """
+ # For the purpose of this method, drive and root are considered
+ # separate parts, i.e.:
+ # Path('c:/').relative_to('c:') gives Path('/')
+ # Path('c:/').relative_to('/') raise ValueError
+ if not other:
+ raise TypeError("need at least one argument")
+ parts = self._parts
+ drv = self._drv
+ root = self._root
+ if root:
+ abs_parts = [drv, root] + parts[1:]
+ else:
+ abs_parts = parts
+ to_drv, to_root, to_parts = self._parse_args(other)
+ if to_root:
+ to_abs_parts = [to_drv, to_root] + to_parts[1:]
+ else:
+ to_abs_parts = to_parts
+ n = len(to_abs_parts)
+ cf = self._flavour.casefold_parts
+ if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
+ formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+ raise ValueError("{0!r} does not start with {1!r}"
+ .format(str(self), str(formatted)))
+ return self._from_parsed_parts('', root if n == 1 else '',
+ abs_parts[n:])
+
+ @property
+ def parts(self):
+ """An object providing sequence-like access to the
+ components in the filesystem path."""
+ # We cache the tuple to avoid building a new one each time .parts
+ # is accessed. XXX is this necessary?
+ try:
+ return self._pparts
+ except AttributeError:
+ self._pparts = tuple(self._parts)
+ return self._pparts
+
+ def joinpath(self, *args):
+ """Combine this path with one or several arguments, and return a
+ new path representing either a subpath (if all arguments are relative
+ paths) or a totally different path (if one of the arguments is
+ anchored).
+ """
+ return self._make_child(args)
+
+ def __truediv__(self, key):
+ return self._make_child((key,))
+
+ def __rtruediv__(self, key):
+ return self._from_parts([key] + self._parts)
+
+ if six.PY2:
+ __div__ = __truediv__
+ __rdiv__ = __rtruediv__
+
+ @property
+ def parent(self):
+ """The logical parent of the path."""
+ drv = self._drv
+ root = self._root
+ parts = self._parts
+ if len(parts) == 1 and (drv or root):
+ return self
+ return self._from_parsed_parts(drv, root, parts[:-1])
+
+ @property
+ def parents(self):
+ """A sequence of this path's logical parents."""
+ return _PathParents(self)
+
+ def is_absolute(self):
+ """True if the path is absolute (has both a root and, if applicable,
+ a drive)."""
+ if not self._root:
+ return False
+ return not self._flavour.has_drv or bool(self._drv)
+
+ def is_reserved(self):
+ """Return True if the path contains one of the special names reserved
+ by the system, if any."""
+ return self._flavour.is_reserved(self._parts)
+
+ def match(self, path_pattern):
+ """
+ Return True if this path matches the given pattern.
+ """
+ cf = self._flavour.casefold
+ path_pattern = cf(path_pattern)
+ drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+ if not pat_parts:
+ raise ValueError("empty pattern")
+ if drv and drv != cf(self._drv):
+ return False
+ if root and root != cf(self._root):
+ return False
+ parts = self._cparts
+ if drv or root:
+ if len(pat_parts) != len(parts):
+ return False
+ pat_parts = pat_parts[1:]
+ elif len(pat_parts) > len(parts):
+ return False
+ for part, pat in zip(reversed(parts), reversed(pat_parts)):
+ if not fnmatch.fnmatchcase(part, pat):
+ return False
+ return True
+
+
+# Can't subclass os.PathLike from PurePath and keep the constructor
+# optimizations in PurePath._parse_args().
+if sys.version_info >= (3, 6):
+ os.PathLike.register(PurePath)
+
+
+class PurePosixPath(PurePath):
+ _flavour = _posix_flavour
+ __slots__ = ()
+
+
+class PureWindowsPath(PurePath):
+ """PurePath subclass for Windows systems.
+
+ On a Windows system, instantiating a PurePath should return this object.
+ However, you can also instantiate it directly on any system.
+ """
+ _flavour = _windows_flavour
+ __slots__ = ()
+
+
+# Filesystem-accessing classes
+
+
+class Path(PurePath):
+ """PurePath subclass that can make system calls.
+
+ Path represents a filesystem path but unlike PurePath, also offers
+ methods to do system calls on path objects. Depending on your system,
+ instantiating a Path will return either a PosixPath or a WindowsPath
+ object. You can also instantiate a PosixPath or WindowsPath directly,
+ but cannot instantiate a WindowsPath on a POSIX system or vice versa.
+ """
+ __slots__ = (
+ '_accessor',
+ '_closed',
+ )
+
+ def __new__(cls, *args, **kwargs):
+ if cls is Path:
+ cls = WindowsPath if os.name == 'nt' else PosixPath
+ self = cls._from_parts(args, init=False)
+ if not self._flavour.is_supported:
+ raise NotImplementedError("cannot instantiate %r on your system"
+ % (cls.__name__,))
+ self._init()
+ return self
+
+ def _init(self,
+ # Private non-constructor arguments
+ template=None,
+ ):
+ self._closed = False
+ if template is not None:
+ self._accessor = template._accessor
+ else:
+ self._accessor = _normal_accessor
+
+ def _make_child_relpath(self, part):
+ # This is an optimization used for dir walking. `part` must be
+ # a single part relative to this path.
+ parts = self._parts + [part]
+ return self._from_parsed_parts(self._drv, self._root, parts)
+
+ def __enter__(self):
+ if self._closed:
+ self._raise_closed()
+ return self
+
+ def __exit__(self, t, v, tb):
+ self._closed = True
+
+ def _raise_closed(self):
+ raise ValueError("I/O operation on closed path")
+
+ def _opener(self, name, flags, mode=0o666):
+ # A stub for the opener argument to built-in open()
+ return self._accessor.open(self, flags, mode)
+
+ def _raw_open(self, flags, mode=0o777):
+ """
+ Open the file pointed by this path and return a file descriptor,
+ as os.open() does.
+ """
+ if self._closed:
+ self._raise_closed()
+ return self._accessor.open(self, flags, mode)
+
+ # Public API
+
+ @classmethod
+ def cwd(cls):
+ """Return a new path pointing to the current working directory
+ (as returned by os.getcwd()).
+ """
+ return cls(os.getcwd())
+
+ @classmethod
+ def home(cls):
+ """Return a new path pointing to the user's home directory (as
+ returned by os.path.expanduser('~')).
+ """
+ return cls(cls()._flavour.gethomedir(None))
+
+ def samefile(self, other_path):
+ """Return whether other_path is the same or not as this file
+ (as returned by os.path.samefile()).
+ """
+ if hasattr(os.path, "samestat"):
+ st = self.stat()
+ try:
+ other_st = other_path.stat()
+ except AttributeError:
+ other_st = os.stat(other_path)
+ return os.path.samestat(st, other_st)
+ else:
+ filename1 = six.text_type(self)
+ filename2 = six.text_type(other_path)
+ st1 = _win32_get_unique_path_id(filename1)
+ st2 = _win32_get_unique_path_id(filename2)
+ return st1 == st2
+
+ def iterdir(self):
+ """Iterate over the files in this directory. Does not yield any
+ result for the special paths '.' and '..'.
+ """
+ if self._closed:
+ self._raise_closed()
+ for name in self._accessor.listdir(self):
+ if name in ('.', '..'):
+ # Yielding a path object for these makes little sense
+ continue
+ yield self._make_child_relpath(name)
+ if self._closed:
+ self._raise_closed()
+
+ def glob(self, pattern):
+ """Iterate over this subtree and yield all existing files (of any
+ kind, including directories) matching the given relative pattern.
+ """
+ if not pattern:
+ raise ValueError("Unacceptable pattern: {0!r}".format(pattern))
+ pattern = self._flavour.casefold(pattern)
+ drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+ if drv or root:
+ raise NotImplementedError("Non-relative patterns are unsupported")
+ selector = _make_selector(tuple(pattern_parts))
+ for p in selector.select_from(self):
+ yield p
+
+ def rglob(self, pattern):
+ """Recursively yield all existing files (of any kind, including
+ directories) matching the given relative pattern, anywhere in
+ this subtree.
+ """
+ pattern = self._flavour.casefold(pattern)
+ drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+ if drv or root:
+ raise NotImplementedError("Non-relative patterns are unsupported")
+ selector = _make_selector(("**",) + tuple(pattern_parts))
+ for p in selector.select_from(self):
+ yield p
+
+ def absolute(self):
+ """Return an absolute version of this path. This function works
+ even if the path doesn't point to anything.
+
+ No normalization is done, i.e. all '.' and '..' will be kept along.
+ Use resolve() to get the canonical path to a file.
+ """
+ # XXX untested yet!
+ if self._closed:
+ self._raise_closed()
+ if self.is_absolute():
+ return self
+ # FIXME this must defer to the specific flavour (and, under Windows,
+ # use nt._getfullpathname())
+ obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+ obj._init(template=self)
+ return obj
+
+ def resolve(self, strict=False):
+ """
+ Make the path absolute, resolving all symlinks on the way and also
+ normalizing it (for example turning slashes into backslashes under
+ Windows).
+ """
+ if self._closed:
+ self._raise_closed()
+ s = self._flavour.resolve(self, strict=strict)
+ if s is None:
+ # No symlink resolution => for consistency, raise an error if
+ # the path is forbidden
+ # but not raise error if file does not exist (see issue #54).
+
+ def _try_func():
+ self.stat()
+
+ def _exc_func(exc):
+ pass
+
+ _try_except_filenotfounderror(_try_func, _exc_func)
+ s = str(self.absolute())
+ else:
+ # ensure s is a string (normpath requires this on older python)
+ s = str(s)
+ # Now we have no symlinks in the path, it's safe to normalize it.
+ normed = self._flavour.pathmod.normpath(s)
+ obj = self._from_parts((normed,), init=False)
+ obj._init(template=self)
+ return obj
+
+ def stat(self):
+ """
+ Return the result of the stat() system call on this path, like
+ os.stat() does.
+ """
+ return self._accessor.stat(self)
+
+ def owner(self):
+ """
+ Return the login name of the file owner.
+ """
+ import pwd
+ return pwd.getpwuid(self.stat().st_uid).pw_name
+
+ def group(self):
+ """
+ Return the group name of the file gid.
+ """
+ import grp
+ return grp.getgrgid(self.stat().st_gid).gr_name
+
+ def open(self, mode='r', buffering=-1, encoding=None,
+ errors=None, newline=None):
+ """
+ Open the file pointed by this path and return a file object, as
+ the built-in open() function does.
+ """
+ if self._closed:
+ self._raise_closed()
+ if sys.version_info >= (3, 3):
+ return io.open(
+ str(self), mode, buffering, encoding, errors, newline,
+ opener=self._opener)
+ else:
+ return io.open(str(self), mode, buffering,
+ encoding, errors, newline)
+
+ def read_bytes(self):
+ """
+ Open the file in bytes mode, read it, and close the file.
+ """
+ with self.open(mode='rb') as f:
+ return f.read()
+
+ def read_text(self, encoding=None, errors=None):
+ """
+ Open the file in text mode, read it, and close the file.
+ """
+ with self.open(mode='r', encoding=encoding, errors=errors) as f:
+ return f.read()
+
+ def write_bytes(self, data):
+ """
+ Open the file in bytes mode, write to it, and close the file.
+ """
+ if not isinstance(data, six.binary_type):
+ raise TypeError(
+ 'data must be %s, not %s' %
+ (six.binary_type.__name__, data.__class__.__name__))
+ with self.open(mode='wb') as f:
+ return f.write(data)
+
+ def write_text(self, data, encoding=None, errors=None):
+ """
+ Open the file in text mode, write to it, and close the file.
+ """
+ if not isinstance(data, six.text_type):
+ raise TypeError(
+ 'data must be %s, not %s' %
+ (six.text_type.__name__, data.__class__.__name__))
+ with self.open(mode='w', encoding=encoding, errors=errors) as f:
+ return f.write(data)
+
+ def touch(self, mode=0o666, exist_ok=True):
+ """
+ Create this file with the given access mode, if it doesn't exist.
+ """
+ if self._closed:
+ self._raise_closed()
+ if exist_ok:
+ # First try to bump modification time
+ # Implementation note: GNU touch uses the UTIME_NOW option of
+ # the utimensat() / futimens() functions.
+ try:
+ self._accessor.utime(self, None)
+ except OSError:
+ # Avoid exception chaining
+ pass
+ else:
+ return
+ flags = os.O_CREAT | os.O_WRONLY
+ if not exist_ok:
+ flags |= os.O_EXCL
+ fd = self._raw_open(flags, mode)
+ os.close(fd)
+
+ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
+ """
+ Create a new directory at this given path.
+ """
+ if self._closed:
+ self._raise_closed()
+
+ def _try_func():
+ self._accessor.mkdir(self, mode)
+
+ def _exc_func(exc):
+ if not parents or self.parent == self:
+ raise exc
+ self.parent.mkdir(parents=True, exist_ok=True)
+ self.mkdir(mode, parents=False, exist_ok=exist_ok)
+
+ try:
+ _try_except_filenotfounderror(_try_func, _exc_func)
+ except OSError:
+ # Cannot rely on checking for EEXIST, since the operating system
+ # could give priority to other errors like EACCES or EROFS
+ if not exist_ok or not self.is_dir():
+ raise
+
+ def chmod(self, mode):
+ """
+ Change the permissions of the path, like os.chmod().
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.chmod(self, mode)
+
+ def lchmod(self, mode):
+ """
+ Like chmod(), except if the path points to a symlink, the symlink's
+ permissions are changed, rather than its target's.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.lchmod(self, mode)
+
+ def unlink(self):
+ """
+ Remove this file or link.
+ If the path is a directory, use rmdir() instead.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.unlink(self)
+
+ def rmdir(self):
+ """
+ Remove this directory. The directory must be empty.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.rmdir(self)
+
+ def lstat(self):
+ """
+ Like stat(), except if the path points to a symlink, the symlink's
+ status information is returned, rather than its target's.
+ """
+ if self._closed:
+ self._raise_closed()
+ return self._accessor.lstat(self)
+
+ def rename(self, target):
+ """
+ Rename this path to the given path.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.rename(self, target)
+
+ def replace(self, target):
+ """
+ Rename this path to the given path, clobbering the existing
+ destination if it exists.
+ """
+ if sys.version_info < (3, 3):
+ raise NotImplementedError("replace() is only available "
+ "with Python 3.3 and later")
+ if self._closed:
+ self._raise_closed()
+ self._accessor.replace(self, target)
+
+ def symlink_to(self, target, target_is_directory=False):
+ """
+ Make this path a symlink pointing to the given path.
+ Note the order of arguments (self, target) is the reverse of
+ os.symlink's.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.symlink(target, self, target_is_directory)
+
+ # Convenience functions for querying the stat results
+
+ def exists(self):
+ """
+ Whether this path exists.
+ """
+ try:
+ self.stat()
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+ return True
+
+ def is_dir(self):
+ """
+ Whether this path is a directory.
+ """
+ try:
+ return S_ISDIR(self.stat().st_mode)
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+
+ def is_file(self):
+ """
+ Whether this path is a regular file (also True for symlinks pointing
+ to regular files).
+ """
+ try:
+ return S_ISREG(self.stat().st_mode)
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+
+ def is_mount(self):
+ """
+ Check if this path is a POSIX mount point
+ """
+ # Need to exist and be a dir
+ if not self.exists() or not self.is_dir():
+ return False
+
+ parent = Path(self.parent)
+ try:
+ parent_dev = parent.stat().st_dev
+ except OSError:
+ return False
+
+ dev = self.stat().st_dev
+ if dev != parent_dev:
+ return True
+ ino = self.stat().st_ino
+ parent_ino = parent.stat().st_ino
+ return ino == parent_ino
+
+ def is_symlink(self):
+ """
+ Whether this path is a symbolic link.
+ """
+ try:
+ return S_ISLNK(self.lstat().st_mode)
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ # Path doesn't exist
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+
+ def is_block_device(self):
+ """
+ Whether this path is a block device.
+ """
+ try:
+ return S_ISBLK(self.stat().st_mode)
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+
+ def is_char_device(self):
+ """
+ Whether this path is a character device.
+ """
+ try:
+ return S_ISCHR(self.stat().st_mode)
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+
+ def is_fifo(self):
+ """
+ Whether this path is a FIFO.
+ """
+ try:
+ return S_ISFIFO(self.stat().st_mode)
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+
+ def is_socket(self):
+ """
+ Whether this path is a socket.
+ """
+ try:
+ return S_ISSOCK(self.stat().st_mode)
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+ except ValueError:
+ # Non-encodable path
+ return False
+
+ def expanduser(self):
+ """ Return a new path with expanded ~ and ~user constructs
+ (as returned by os.path.expanduser)
+ """
+ if (not (self._drv or self._root)
+ and self._parts and self._parts[0][:1] == '~'):
+ homedir = self._flavour.gethomedir(self._parts[0][1:])
+ return self._from_parts([homedir] + self._parts[1:])
+
+ return self
+
+
+class PosixPath(Path, PurePosixPath):
+ """Path subclass for non-Windows systems.
+
+ On a POSIX system, instantiating a Path should return this object.
+ """
+ __slots__ = ()
+
+
+class WindowsPath(Path, PureWindowsPath):
+ """Path subclass for Windows systems.
+
+ On a Windows system, instantiating a Path should return this object.
+ """
+ __slots__ = ()
+
+ def owner(self):
+ raise NotImplementedError("Path.owner() is unsupported on this system")
+
+ def group(self):
+ raise NotImplementedError("Path.group() is unsupported on this system")
+
+ def is_mount(self):
+ raise NotImplementedError(
+ "Path.is_mount() is unsupported on this system")
diff --git a/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/LICENSE.txt b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/LICENSE.txt
new file mode 100644
index 0000000000..0759f503f2
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/LICENSE.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2012, Ben Hoyt
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+* Neither the name of Ben Hoyt 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/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/METADATA
new file mode 100644
index 0000000000..ee4b11a523
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/METADATA
@@ -0,0 +1,238 @@
+Metadata-Version: 2.1
+Name: scandir
+Version: 1.10.0
+Summary: scandir, a better directory iterator and faster os.walk()
+Home-page: https://github.com/benhoyt/scandir
+Author: Ben Hoyt
+Author-email: benhoyt@gmail.com
+License: New BSD License
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: OS Independent
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Topic :: System :: Filesystems
+Classifier: Topic :: System :: Operating System
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: Implementation :: CPython
+
+
+scandir, a better directory iterator and faster os.walk()
+=========================================================
+
+.. image:: https://img.shields.io/pypi/v/scandir.svg
+ :target: https://pypi.python.org/pypi/scandir
+ :alt: scandir on PyPI (Python Package Index)
+
+.. image:: https://travis-ci.org/benhoyt/scandir.svg?branch=master
+ :target: https://travis-ci.org/benhoyt/scandir
+ :alt: Travis CI tests (Linux)
+
+.. image:: https://ci.appveyor.com/api/projects/status/github/benhoyt/scandir?branch=master&svg=true
+ :target: https://ci.appveyor.com/project/benhoyt/scandir
+ :alt: Appveyor tests (Windows)
+
+
+``scandir()`` is a directory iteration function like ``os.listdir()``,
+except that instead of returning a list of bare filenames, it yields
+``DirEntry`` objects that include file type and stat information along
+with the name. Using ``scandir()`` increases the speed of ``os.walk()``
+by 2-20 times (depending on the platform and file system) by avoiding
+unnecessary calls to ``os.stat()`` in most cases.
+
+
+Now included in a Python near you!
+----------------------------------
+
+``scandir`` has been included in the Python 3.5 standard library as
+``os.scandir()``, and the related performance improvements to
+``os.walk()`` have also been included. So if you're lucky enough to be
+using Python 3.5 (release date September 13, 2015) you get the benefit
+immediately, otherwise just
+`download this module from PyPI <https://pypi.python.org/pypi/scandir>`_,
+install it with ``pip install scandir``, and then do something like
+this in your code:
+
+.. code-block:: python
+
+ # Use the built-in version of scandir/walk if possible, otherwise
+ # use the scandir module version
+ try:
+ from os import scandir, walk
+ except ImportError:
+ from scandir import scandir, walk
+
+`PEP 471 <https://www.python.org/dev/peps/pep-0471/>`_, which is the
+PEP that proposes including ``scandir`` in the Python standard library,
+was `accepted <https://mail.python.org/pipermail/python-dev/2014-July/135561.html>`_
+in July 2014 by Victor Stinner, the BDFL-delegate for the PEP.
+
+This ``scandir`` module is intended to work on Python 2.7+ and Python
+3.4+ (and it has been tested on those versions).
+
+
+Background
+----------
+
+Python's built-in ``os.walk()`` is significantly slower than it needs to be,
+because -- in addition to calling ``listdir()`` on each directory -- it calls
+``stat()`` on each file to determine whether the filename is a directory or not.
+But both ``FindFirstFile`` / ``FindNextFile`` on Windows and ``readdir`` on Linux/OS
+X already tell you whether the files returned are directories or not, so
+no further ``stat`` system calls are needed. In short, you can reduce the number
+of system calls from about 2N to N, where N is the total number of files and
+directories in the tree.
+
+In practice, removing all those extra system calls makes ``os.walk()`` about
+**7-50 times as fast on Windows, and about 3-10 times as fast on Linux and Mac OS
+X.** So we're not talking about micro-optimizations. See more benchmarks
+in the "Benchmarks" section below.
+
+Somewhat relatedly, many people have also asked for a version of
+``os.listdir()`` that yields filenames as it iterates instead of returning them
+as one big list. This improves memory efficiency for iterating very large
+directories.
+
+So as well as a faster ``walk()``, scandir adds a new ``scandir()`` function.
+They're pretty easy to use, but see "The API" below for the full docs.
+
+
+Benchmarks
+----------
+
+Below are results showing how many times as fast ``scandir.walk()`` is than
+``os.walk()`` on various systems, found by running ``benchmark.py`` with no
+arguments:
+
+==================== ============== =============
+System version Python version Times as fast
+==================== ============== =============
+Windows 7 64-bit 2.7.7 64-bit 10.4
+Windows 7 64-bit SSD 2.7.7 64-bit 10.3
+Windows 7 64-bit NFS 2.7.6 64-bit 36.8
+Windows 7 64-bit SSD 3.4.1 64-bit 9.9
+Windows 7 64-bit SSD 3.5.0 64-bit 9.5
+Ubuntu 14.04 64-bit 2.7.6 64-bit 5.8
+Mac OS X 10.9.3 2.7.5 64-bit 3.8
+==================== ============== =============
+
+All of the above tests were done using the fast C version of scandir
+(source code in ``_scandir.c``).
+
+Note that the gains are less than the above on smaller directories and greater
+on larger directories. This is why ``benchmark.py`` creates a test directory
+tree with a standardized size.
+
+
+The API
+-------
+
+walk()
+~~~~~~
+
+The API for ``scandir.walk()`` is exactly the same as ``os.walk()``, so just
+`read the Python docs <https://docs.python.org/3.5/library/os.html#os.walk>`_.
+
+scandir()
+~~~~~~~~~
+
+The full docs for ``scandir()`` and the ``DirEntry`` objects it yields are
+available in the `Python documentation here <https://docs.python.org/3.5/library/os.html#os.scandir>`_.
+But below is a brief summary as well.
+
+ scandir(path='.') -> iterator of DirEntry objects for given path
+
+Like ``listdir``, ``scandir`` calls the operating system's directory
+iteration system calls to get the names of the files in the given
+``path``, but it's different from ``listdir`` in two ways:
+
+* Instead of returning bare filename strings, it returns lightweight
+ ``DirEntry`` objects that hold the filename string and provide
+ simple methods that allow access to the additional data the
+ operating system may have returned.
+
+* It returns a generator instead of a list, so that ``scandir`` acts
+ as a true iterator instead of returning the full list immediately.
+
+``scandir()`` yields a ``DirEntry`` object for each file and
+sub-directory in ``path``. Just like ``listdir``, the ``'.'``
+and ``'..'`` pseudo-directories are skipped, and the entries are
+yielded in system-dependent order. Each ``DirEntry`` object has the
+following attributes and methods:
+
+* ``name``: the entry's filename, relative to the scandir ``path``
+ argument (corresponds to the return values of ``os.listdir``)
+
+* ``path``: the entry's full path name (not necessarily an absolute
+ path) -- the equivalent of ``os.path.join(scandir_path, entry.name)``
+
+* ``is_dir(*, follow_symlinks=True)``: similar to
+ ``pathlib.Path.is_dir()``, but the return value is cached on the
+ ``DirEntry`` object; doesn't require a system call in most cases;
+ don't follow symbolic links if ``follow_symlinks`` is False
+
+* ``is_file(*, follow_symlinks=True)``: similar to
+ ``pathlib.Path.is_file()``, but the return value is cached on the
+ ``DirEntry`` object; doesn't require a system call in most cases;
+ don't follow symbolic links if ``follow_symlinks`` is False
+
+* ``is_symlink()``: similar to ``pathlib.Path.is_symlink()``, but the
+ return value is cached on the ``DirEntry`` object; doesn't require a
+ system call in most cases
+
+* ``stat(*, follow_symlinks=True)``: like ``os.stat()``, but the
+ return value is cached on the ``DirEntry`` object; does not require a
+ system call on Windows (except for symlinks); don't follow symbolic links
+ (like ``os.lstat()``) if ``follow_symlinks`` is False
+
+* ``inode()``: return the inode number of the entry; the return value
+ is cached on the ``DirEntry`` object
+
+Here's a very simple example of ``scandir()`` showing use of the
+``DirEntry.name`` attribute and the ``DirEntry.is_dir()`` method:
+
+.. code-block:: python
+
+ def subdirs(path):
+ """Yield directory names not starting with '.' under given path."""
+ for entry in os.scandir(path):
+ if not entry.name.startswith('.') and entry.is_dir():
+ yield entry.name
+
+This ``subdirs()`` function will be significantly faster with scandir
+than ``os.listdir()`` and ``os.path.isdir()`` on both Windows and POSIX
+systems, especially on medium-sized or large directories.
+
+
+Further reading
+---------------
+
+* `The Python docs for scandir <https://docs.python.org/3.5/library/os.html#os.scandir>`_
+* `PEP 471 <https://www.python.org/dev/peps/pep-0471/>`_, the
+ (now-accepted) Python Enhancement Proposal that proposed adding
+ ``scandir`` to the standard library -- a lot of details here,
+ including rejected ideas and previous discussion
+
+
+Flames, comments, bug reports
+-----------------------------
+
+Please send flames, comments, and questions about scandir to Ben Hoyt:
+
+http://benhoyt.com/
+
+File bug reports for the version in the Python 3.5 standard library
+`here <https://docs.python.org/3.5/bugs.html>`_, or file bug reports
+or feature requests for this module at the GitHub project page:
+
+https://github.com/benhoyt/scandir
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/RECORD
new file mode 100644
index 0000000000..2140d975b4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/RECORD
@@ -0,0 +1,6 @@
+scandir.py,sha256=97C2AQInuKk-Phb3aXM7fJomhc-00pZMcBur23NUmrE,24827
+scandir-1.10.0.dist-info/LICENSE.txt,sha256=peL73COXREGdKUB828knk8TZwdlWwXT3y3-W-m0FjIY,1464
+scandir-1.10.0.dist-info/METADATA,sha256=cv1fZ5DeC3DJqnMByWGiprvGhLpQCkWOZiJduweakGk,9559
+scandir-1.10.0.dist-info/WHEEL,sha256=WO4o60shExe_A5pkiO6Yb-8OHLGhlAGcs2oJ7aUuE5Q,110
+scandir-1.10.0.dist-info/top_level.txt,sha256=Ixze5mNjmis99ql7JEtAYc9-djJMbfRx-FFw3R_zZf8,17
+scandir-1.10.0.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/WHEEL
new file mode 100644
index 0000000000..310051fe9e
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.35.1)
+Root-Is-Purelib: false
+Tag: cp39-cp39-macosx_10_15_x86_64
+
diff --git a/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/top_level.txt
new file mode 100644
index 0000000000..b13832ba1d
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info/top_level.txt
@@ -0,0 +1,2 @@
+_scandir
+scandir
diff --git a/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir.py b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir.py
new file mode 100644
index 0000000000..c565b23f89
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir.py
@@ -0,0 +1,693 @@
+"""scandir, a better directory iterator and faster os.walk(), now in the Python 3.5 stdlib
+
+scandir() is a generator version of os.listdir() that returns an
+iterator over files in a directory, and also exposes the extra
+information most OSes provide while iterating files in a directory
+(such as type and stat information).
+
+This module also includes a version of os.walk() that uses scandir()
+to speed it up significantly.
+
+See README.md or https://github.com/benhoyt/scandir for rationale and
+docs, or read PEP 471 (https://www.python.org/dev/peps/pep-0471/) for
+more details on its inclusion into Python 3.5
+
+scandir is released under the new BSD 3-clause license. See
+LICENSE.txt for the full license text.
+"""
+
+from __future__ import division
+
+from errno import ENOENT
+from os import listdir, lstat, stat, strerror
+from os.path import join, islink
+from stat import S_IFDIR, S_IFLNK, S_IFREG
+import collections
+import sys
+
+try:
+ import _scandir
+except ImportError:
+ _scandir = None
+
+try:
+ import ctypes
+except ImportError:
+ ctypes = None
+
+if _scandir is None and ctypes is None:
+ import warnings
+ warnings.warn("scandir can't find the compiled _scandir C module "
+ "or ctypes, using slow generic fallback")
+
+__version__ = '1.10.0'
+__all__ = ['scandir', 'walk']
+
+# Windows FILE_ATTRIBUTE constants for interpreting the
+# FIND_DATA.dwFileAttributes member
+FILE_ATTRIBUTE_ARCHIVE = 32
+FILE_ATTRIBUTE_COMPRESSED = 2048
+FILE_ATTRIBUTE_DEVICE = 64
+FILE_ATTRIBUTE_DIRECTORY = 16
+FILE_ATTRIBUTE_ENCRYPTED = 16384
+FILE_ATTRIBUTE_HIDDEN = 2
+FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768
+FILE_ATTRIBUTE_NORMAL = 128
+FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192
+FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072
+FILE_ATTRIBUTE_OFFLINE = 4096
+FILE_ATTRIBUTE_READONLY = 1
+FILE_ATTRIBUTE_REPARSE_POINT = 1024
+FILE_ATTRIBUTE_SPARSE_FILE = 512
+FILE_ATTRIBUTE_SYSTEM = 4
+FILE_ATTRIBUTE_TEMPORARY = 256
+FILE_ATTRIBUTE_VIRTUAL = 65536
+
+IS_PY3 = sys.version_info >= (3, 0)
+
+if IS_PY3:
+ unicode = str # Because Python <= 3.2 doesn't have u'unicode' syntax
+
+
+class GenericDirEntry(object):
+ __slots__ = ('name', '_stat', '_lstat', '_scandir_path', '_path')
+
+ def __init__(self, scandir_path, name):
+ self._scandir_path = scandir_path
+ self.name = name
+ self._stat = None
+ self._lstat = None
+ self._path = None
+
+ @property
+ def path(self):
+ if self._path is None:
+ self._path = join(self._scandir_path, self.name)
+ return self._path
+
+ def stat(self, follow_symlinks=True):
+ if follow_symlinks:
+ if self._stat is None:
+ self._stat = stat(self.path)
+ return self._stat
+ else:
+ if self._lstat is None:
+ self._lstat = lstat(self.path)
+ return self._lstat
+
+ # The code duplication below is intentional: this is for slightly
+ # better performance on systems that fall back to GenericDirEntry.
+ # It avoids an additional attribute lookup and method call, which
+ # are relatively slow on CPython.
+ def is_dir(self, follow_symlinks=True):
+ try:
+ st = self.stat(follow_symlinks=follow_symlinks)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False # Path doesn't exist or is a broken symlink
+ return st.st_mode & 0o170000 == S_IFDIR
+
+ def is_file(self, follow_symlinks=True):
+ try:
+ st = self.stat(follow_symlinks=follow_symlinks)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False # Path doesn't exist or is a broken symlink
+ return st.st_mode & 0o170000 == S_IFREG
+
+ def is_symlink(self):
+ try:
+ st = self.stat(follow_symlinks=False)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False # Path doesn't exist or is a broken symlink
+ return st.st_mode & 0o170000 == S_IFLNK
+
+ def inode(self):
+ st = self.stat(follow_symlinks=False)
+ return st.st_ino
+
+ def __str__(self):
+ return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
+
+ __repr__ = __str__
+
+
+def _scandir_generic(path=unicode('.')):
+ """Like os.listdir(), but yield DirEntry objects instead of returning
+ a list of names.
+ """
+ for name in listdir(path):
+ yield GenericDirEntry(path, name)
+
+
+if IS_PY3 and sys.platform == 'win32':
+ def scandir_generic(path=unicode('.')):
+ if isinstance(path, bytes):
+ raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
+ return _scandir_generic(path)
+ scandir_generic.__doc__ = _scandir_generic.__doc__
+else:
+ scandir_generic = _scandir_generic
+
+
+scandir_c = None
+scandir_python = None
+
+
+if sys.platform == 'win32':
+ if ctypes is not None:
+ from ctypes import wintypes
+
+ # Various constants from windows.h
+ INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
+ ERROR_FILE_NOT_FOUND = 2
+ ERROR_NO_MORE_FILES = 18
+ IO_REPARSE_TAG_SYMLINK = 0xA000000C
+
+ # Numer of seconds between 1601-01-01 and 1970-01-01
+ SECONDS_BETWEEN_EPOCHS = 11644473600
+
+ kernel32 = ctypes.windll.kernel32
+
+ # ctypes wrappers for (wide string versions of) FindFirstFile,
+ # FindNextFile, and FindClose
+ FindFirstFile = kernel32.FindFirstFileW
+ FindFirstFile.argtypes = [
+ wintypes.LPCWSTR,
+ ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
+ ]
+ FindFirstFile.restype = wintypes.HANDLE
+
+ FindNextFile = kernel32.FindNextFileW
+ FindNextFile.argtypes = [
+ wintypes.HANDLE,
+ ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
+ ]
+ FindNextFile.restype = wintypes.BOOL
+
+ FindClose = kernel32.FindClose
+ FindClose.argtypes = [wintypes.HANDLE]
+ FindClose.restype = wintypes.BOOL
+
+ Win32StatResult = collections.namedtuple('Win32StatResult', [
+ 'st_mode',
+ 'st_ino',
+ 'st_dev',
+ 'st_nlink',
+ 'st_uid',
+ 'st_gid',
+ 'st_size',
+ 'st_atime',
+ 'st_mtime',
+ 'st_ctime',
+ 'st_atime_ns',
+ 'st_mtime_ns',
+ 'st_ctime_ns',
+ 'st_file_attributes',
+ ])
+
+ def filetime_to_time(filetime):
+ """Convert Win32 FILETIME to time since Unix epoch in seconds."""
+ total = filetime.dwHighDateTime << 32 | filetime.dwLowDateTime
+ return total / 10000000 - SECONDS_BETWEEN_EPOCHS
+
+ def find_data_to_stat(data):
+ """Convert Win32 FIND_DATA struct to stat_result."""
+ # First convert Win32 dwFileAttributes to st_mode
+ attributes = data.dwFileAttributes
+ st_mode = 0
+ if attributes & FILE_ATTRIBUTE_DIRECTORY:
+ st_mode |= S_IFDIR | 0o111
+ else:
+ st_mode |= S_IFREG
+ if attributes & FILE_ATTRIBUTE_READONLY:
+ st_mode |= 0o444
+ else:
+ st_mode |= 0o666
+ if (attributes & FILE_ATTRIBUTE_REPARSE_POINT and
+ data.dwReserved0 == IO_REPARSE_TAG_SYMLINK):
+ st_mode ^= st_mode & 0o170000
+ st_mode |= S_IFLNK
+
+ st_size = data.nFileSizeHigh << 32 | data.nFileSizeLow
+ st_atime = filetime_to_time(data.ftLastAccessTime)
+ st_mtime = filetime_to_time(data.ftLastWriteTime)
+ st_ctime = filetime_to_time(data.ftCreationTime)
+
+ # Some fields set to zero per CPython's posixmodule.c: st_ino, st_dev,
+ # st_nlink, st_uid, st_gid
+ return Win32StatResult(st_mode, 0, 0, 0, 0, 0, st_size,
+ st_atime, st_mtime, st_ctime,
+ int(st_atime * 1000000000),
+ int(st_mtime * 1000000000),
+ int(st_ctime * 1000000000),
+ attributes)
+
+ class Win32DirEntryPython(object):
+ __slots__ = ('name', '_stat', '_lstat', '_find_data', '_scandir_path', '_path', '_inode')
+
+ def __init__(self, scandir_path, name, find_data):
+ self._scandir_path = scandir_path
+ self.name = name
+ self._stat = None
+ self._lstat = None
+ self._find_data = find_data
+ self._path = None
+ self._inode = None
+
+ @property
+ def path(self):
+ if self._path is None:
+ self._path = join(self._scandir_path, self.name)
+ return self._path
+
+ def stat(self, follow_symlinks=True):
+ if follow_symlinks:
+ if self._stat is None:
+ if self.is_symlink():
+ # It's a symlink, call link-following stat()
+ self._stat = stat(self.path)
+ else:
+ # Not a symlink, stat is same as lstat value
+ if self._lstat is None:
+ self._lstat = find_data_to_stat(self._find_data)
+ self._stat = self._lstat
+ return self._stat
+ else:
+ if self._lstat is None:
+ # Lazily convert to stat object, because it's slow
+ # in Python, and often we only need is_dir() etc
+ self._lstat = find_data_to_stat(self._find_data)
+ return self._lstat
+
+ def is_dir(self, follow_symlinks=True):
+ is_symlink = self.is_symlink()
+ if follow_symlinks and is_symlink:
+ try:
+ return self.stat().st_mode & 0o170000 == S_IFDIR
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False
+ elif is_symlink:
+ return False
+ else:
+ return (self._find_data.dwFileAttributes &
+ FILE_ATTRIBUTE_DIRECTORY != 0)
+
+ def is_file(self, follow_symlinks=True):
+ is_symlink = self.is_symlink()
+ if follow_symlinks and is_symlink:
+ try:
+ return self.stat().st_mode & 0o170000 == S_IFREG
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False
+ elif is_symlink:
+ return False
+ else:
+ return (self._find_data.dwFileAttributes &
+ FILE_ATTRIBUTE_DIRECTORY == 0)
+
+ def is_symlink(self):
+ return (self._find_data.dwFileAttributes &
+ FILE_ATTRIBUTE_REPARSE_POINT != 0 and
+ self._find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK)
+
+ def inode(self):
+ if self._inode is None:
+ self._inode = lstat(self.path).st_ino
+ return self._inode
+
+ def __str__(self):
+ return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
+
+ __repr__ = __str__
+
+ def win_error(error, filename):
+ exc = WindowsError(error, ctypes.FormatError(error))
+ exc.filename = filename
+ return exc
+
+ def _scandir_python(path=unicode('.')):
+ """Like os.listdir(), but yield DirEntry objects instead of returning
+ a list of names.
+ """
+ # Call FindFirstFile and handle errors
+ if isinstance(path, bytes):
+ is_bytes = True
+ filename = join(path.decode('mbcs', 'strict'), '*.*')
+ else:
+ is_bytes = False
+ filename = join(path, '*.*')
+ data = wintypes.WIN32_FIND_DATAW()
+ data_p = ctypes.byref(data)
+ handle = FindFirstFile(filename, data_p)
+ if handle == INVALID_HANDLE_VALUE:
+ error = ctypes.GetLastError()
+ if error == ERROR_FILE_NOT_FOUND:
+ # No files, don't yield anything
+ return
+ raise win_error(error, path)
+
+ # Call FindNextFile in a loop, stopping when no more files
+ try:
+ while True:
+ # Skip '.' and '..' (current and parent directory), but
+ # otherwise yield (filename, stat_result) tuple
+ name = data.cFileName
+ if name not in ('.', '..'):
+ if is_bytes:
+ name = name.encode('mbcs', 'replace')
+ yield Win32DirEntryPython(path, name, data)
+
+ data = wintypes.WIN32_FIND_DATAW()
+ data_p = ctypes.byref(data)
+ success = FindNextFile(handle, data_p)
+ if not success:
+ error = ctypes.GetLastError()
+ if error == ERROR_NO_MORE_FILES:
+ break
+ raise win_error(error, path)
+ finally:
+ if not FindClose(handle):
+ raise win_error(ctypes.GetLastError(), path)
+
+ if IS_PY3:
+ def scandir_python(path=unicode('.')):
+ if isinstance(path, bytes):
+ raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
+ return _scandir_python(path)
+ scandir_python.__doc__ = _scandir_python.__doc__
+ else:
+ scandir_python = _scandir_python
+
+ if _scandir is not None:
+ scandir_c = _scandir.scandir
+ DirEntry_c = _scandir.DirEntry
+
+ if _scandir is not None:
+ scandir = scandir_c
+ DirEntry = DirEntry_c
+ elif ctypes is not None:
+ scandir = scandir_python
+ DirEntry = Win32DirEntryPython
+ else:
+ scandir = scandir_generic
+ DirEntry = GenericDirEntry
+
+
+# Linux, OS X, and BSD implementation
+elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.platform:
+ have_dirent_d_type = (sys.platform != 'sunos5')
+
+ if ctypes is not None and have_dirent_d_type:
+ import ctypes.util
+
+ DIR_p = ctypes.c_void_p
+
+ # Rather annoying how the dirent struct is slightly different on each
+ # platform. The only fields we care about are d_name and d_type.
+ class Dirent(ctypes.Structure):
+ if sys.platform.startswith('linux'):
+ _fields_ = (
+ ('d_ino', ctypes.c_ulong),
+ ('d_off', ctypes.c_long),
+ ('d_reclen', ctypes.c_ushort),
+ ('d_type', ctypes.c_byte),
+ ('d_name', ctypes.c_char * 256),
+ )
+ elif 'openbsd' in sys.platform:
+ _fields_ = (
+ ('d_ino', ctypes.c_uint64),
+ ('d_off', ctypes.c_uint64),
+ ('d_reclen', ctypes.c_uint16),
+ ('d_type', ctypes.c_uint8),
+ ('d_namlen', ctypes.c_uint8),
+ ('__d_padding', ctypes.c_uint8 * 4),
+ ('d_name', ctypes.c_char * 256),
+ )
+ else:
+ _fields_ = (
+ ('d_ino', ctypes.c_uint32), # must be uint32, not ulong
+ ('d_reclen', ctypes.c_ushort),
+ ('d_type', ctypes.c_byte),
+ ('d_namlen', ctypes.c_byte),
+ ('d_name', ctypes.c_char * 256),
+ )
+
+ DT_UNKNOWN = 0
+ DT_DIR = 4
+ DT_REG = 8
+ DT_LNK = 10
+
+ Dirent_p = ctypes.POINTER(Dirent)
+ Dirent_pp = ctypes.POINTER(Dirent_p)
+
+ libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
+ opendir = libc.opendir
+ opendir.argtypes = [ctypes.c_char_p]
+ opendir.restype = DIR_p
+
+ readdir_r = libc.readdir_r
+ readdir_r.argtypes = [DIR_p, Dirent_p, Dirent_pp]
+ readdir_r.restype = ctypes.c_int
+
+ closedir = libc.closedir
+ closedir.argtypes = [DIR_p]
+ closedir.restype = ctypes.c_int
+
+ file_system_encoding = sys.getfilesystemencoding()
+
+ class PosixDirEntry(object):
+ __slots__ = ('name', '_d_type', '_stat', '_lstat', '_scandir_path', '_path', '_inode')
+
+ def __init__(self, scandir_path, name, d_type, inode):
+ self._scandir_path = scandir_path
+ self.name = name
+ self._d_type = d_type
+ self._inode = inode
+ self._stat = None
+ self._lstat = None
+ self._path = None
+
+ @property
+ def path(self):
+ if self._path is None:
+ self._path = join(self._scandir_path, self.name)
+ return self._path
+
+ def stat(self, follow_symlinks=True):
+ if follow_symlinks:
+ if self._stat is None:
+ if self.is_symlink():
+ self._stat = stat(self.path)
+ else:
+ if self._lstat is None:
+ self._lstat = lstat(self.path)
+ self._stat = self._lstat
+ return self._stat
+ else:
+ if self._lstat is None:
+ self._lstat = lstat(self.path)
+ return self._lstat
+
+ def is_dir(self, follow_symlinks=True):
+ if (self._d_type == DT_UNKNOWN or
+ (follow_symlinks and self.is_symlink())):
+ try:
+ st = self.stat(follow_symlinks=follow_symlinks)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False
+ return st.st_mode & 0o170000 == S_IFDIR
+ else:
+ return self._d_type == DT_DIR
+
+ def is_file(self, follow_symlinks=True):
+ if (self._d_type == DT_UNKNOWN or
+ (follow_symlinks and self.is_symlink())):
+ try:
+ st = self.stat(follow_symlinks=follow_symlinks)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False
+ return st.st_mode & 0o170000 == S_IFREG
+ else:
+ return self._d_type == DT_REG
+
+ def is_symlink(self):
+ if self._d_type == DT_UNKNOWN:
+ try:
+ st = self.stat(follow_symlinks=False)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False
+ return st.st_mode & 0o170000 == S_IFLNK
+ else:
+ return self._d_type == DT_LNK
+
+ def inode(self):
+ return self._inode
+
+ def __str__(self):
+ return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
+
+ __repr__ = __str__
+
+ def posix_error(filename):
+ errno = ctypes.get_errno()
+ exc = OSError(errno, strerror(errno))
+ exc.filename = filename
+ return exc
+
+ def scandir_python(path=unicode('.')):
+ """Like os.listdir(), but yield DirEntry objects instead of returning
+ a list of names.
+ """
+ if isinstance(path, bytes):
+ opendir_path = path
+ is_bytes = True
+ else:
+ opendir_path = path.encode(file_system_encoding)
+ is_bytes = False
+ dir_p = opendir(opendir_path)
+ if not dir_p:
+ raise posix_error(path)
+ try:
+ result = Dirent_p()
+ while True:
+ entry = Dirent()
+ if readdir_r(dir_p, entry, result):
+ raise posix_error(path)
+ if not result:
+ break
+ name = entry.d_name
+ if name not in (b'.', b'..'):
+ if not is_bytes:
+ name = name.decode(file_system_encoding)
+ yield PosixDirEntry(path, name, entry.d_type, entry.d_ino)
+ finally:
+ if closedir(dir_p):
+ raise posix_error(path)
+
+ if _scandir is not None:
+ scandir_c = _scandir.scandir
+ DirEntry_c = _scandir.DirEntry
+
+ if _scandir is not None:
+ scandir = scandir_c
+ DirEntry = DirEntry_c
+ elif ctypes is not None and have_dirent_d_type:
+ scandir = scandir_python
+ DirEntry = PosixDirEntry
+ else:
+ scandir = scandir_generic
+ DirEntry = GenericDirEntry
+
+
+# Some other system -- no d_type or stat information
+else:
+ scandir = scandir_generic
+ DirEntry = GenericDirEntry
+
+
+def _walk(top, topdown=True, onerror=None, followlinks=False):
+ """Like Python 3.5's implementation of os.walk() -- faster than
+ the pre-Python 3.5 version as it uses scandir() internally.
+ """
+ dirs = []
+ nondirs = []
+
+ # We may not have read permission for top, in which case we can't
+ # get a list of the files the directory contains. os.walk
+ # always suppressed the exception then, rather than blow up for a
+ # minor reason when (say) a thousand readable directories are still
+ # left to visit. That logic is copied here.
+ try:
+ scandir_it = scandir(top)
+ except OSError as error:
+ if onerror is not None:
+ onerror(error)
+ return
+
+ while True:
+ try:
+ try:
+ entry = next(scandir_it)
+ except StopIteration:
+ break
+ except OSError as error:
+ if onerror is not None:
+ onerror(error)
+ return
+
+ try:
+ is_dir = entry.is_dir()
+ except OSError:
+ # If is_dir() raises an OSError, consider that the entry is not
+ # a directory, same behaviour than os.path.isdir().
+ is_dir = False
+
+ if is_dir:
+ dirs.append(entry.name)
+ else:
+ nondirs.append(entry.name)
+
+ if not topdown and is_dir:
+ # Bottom-up: recurse into sub-directory, but exclude symlinks to
+ # directories if followlinks is False
+ if followlinks:
+ walk_into = True
+ else:
+ try:
+ is_symlink = entry.is_symlink()
+ except OSError:
+ # If is_symlink() raises an OSError, consider that the
+ # entry is not a symbolic link, same behaviour than
+ # os.path.islink().
+ is_symlink = False
+ walk_into = not is_symlink
+
+ if walk_into:
+ for entry in walk(entry.path, topdown, onerror, followlinks):
+ yield entry
+
+ # Yield before recursion if going top down
+ if topdown:
+ yield top, dirs, nondirs
+
+ # Recurse into sub-directories
+ for name in dirs:
+ new_path = join(top, name)
+ # Issue #23605: os.path.islink() is used instead of caching
+ # entry.is_symlink() result during the loop on os.scandir() because
+ # the caller can replace the directory entry during the "yield"
+ # above.
+ if followlinks or not islink(new_path):
+ for entry in walk(new_path, topdown, onerror, followlinks):
+ yield entry
+ else:
+ # Yield after recursion if going bottom up
+ yield top, dirs, nondirs
+
+
+if IS_PY3 or sys.platform != 'win32':
+ walk = _walk
+else:
+ # Fix for broken unicode handling on Windows on Python 2.x, see:
+ # https://github.com/benhoyt/scandir/issues/54
+ file_system_encoding = sys.getfilesystemencoding()
+
+ def walk(top, topdown=True, onerror=None, followlinks=False):
+ if isinstance(top, bytes):
+ top = top.decode(file_system_encoding)
+ return _walk(top, topdown, onerror, followlinks)
diff --git a/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/LICENSE
new file mode 100644
index 0000000000..de6633112c
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2010-2020 Benjamin Peterson
+
+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/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/METADATA
new file mode 100644
index 0000000000..869bf25a88
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/METADATA
@@ -0,0 +1,49 @@
+Metadata-Version: 2.1
+Name: six
+Version: 1.15.0
+Summary: Python 2 and 3 compatibility utilities
+Home-page: https://github.com/benjaminp/six
+Author: Benjamin Peterson
+Author-email: benjamin@python.org
+License: MIT
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Utilities
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*
+
+.. image:: https://img.shields.io/pypi/v/six.svg
+ :target: https://pypi.org/project/six/
+ :alt: six on PyPI
+
+.. image:: https://travis-ci.org/benjaminp/six.svg?branch=master
+ :target: https://travis-ci.org/benjaminp/six
+ :alt: six on TravisCI
+
+.. image:: https://readthedocs.org/projects/six/badge/?version=latest
+ :target: https://six.readthedocs.io/
+ :alt: six's documentation on Read the Docs
+
+.. image:: https://img.shields.io/badge/license-MIT-green.svg
+ :target: https://github.com/benjaminp/six/blob/master/LICENSE
+ :alt: MIT License badge
+
+Six is a Python 2 and 3 compatibility library. It provides utility functions
+for smoothing over the differences between the Python versions with the goal of
+writing Python code that is compatible on both Python versions. See the
+documentation for more information on what is provided.
+
+Six supports Python 2.7 and 3.3+. It is contained in only one Python
+file, so it can be easily copied into your project. (The copyright and license
+notice must be retained.)
+
+Online documentation is at https://six.readthedocs.io/.
+
+Bugs can be reported to https://github.com/benjaminp/six. The code can also
+be found there.
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/RECORD
new file mode 100644
index 0000000000..4cccdb4af6
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/RECORD
@@ -0,0 +1,6 @@
+six.py,sha256=U4Z_yv534W5CNyjY9i8V1OXY2SjAny8y2L5vDLhhThM,34159
+six-1.15.0.dist-info/LICENSE,sha256=i7hQxWWqOJ_cFvOkaWWtI9gq3_YPI5P8J2K2MYXo5sk,1066
+six-1.15.0.dist-info/METADATA,sha256=W6rlyoeMZHXh6srP9NXNsm0rjAf_660re8WdH5TBT8E,1795
+six-1.15.0.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
+six-1.15.0.dist-info/top_level.txt,sha256=_iVH_iYEtEXnD8nYGQYpYFUvkUW9sEO1GYbkeKSAais,4
+six-1.15.0.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/WHEEL
new file mode 100644
index 0000000000..ef99c6cf32
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.34.2)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/top_level.txt
new file mode 100644
index 0000000000..ffe2fce498
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+six
diff --git a/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six.py b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six.py
new file mode 100644
index 0000000000..83f69783d1
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/six-1.15.0-py2.py3-none-any/six.py
@@ -0,0 +1,982 @@
+# Copyright (c) 2010-2020 Benjamin Peterson
+#
+# 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.
+
+"""Utilities for writing code that runs on Python 2 and 3"""
+
+from __future__ import absolute_import
+
+import functools
+import itertools
+import operator
+import sys
+import types
+
+__author__ = "Benjamin Peterson <benjamin@python.org>"
+__version__ = "1.15.0"
+
+
+# Useful for very coarse version differentiation.
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+PY34 = sys.version_info[0:2] >= (3, 4)
+
+if PY3:
+ string_types = str,
+ integer_types = int,
+ class_types = type,
+ text_type = str
+ binary_type = bytes
+
+ MAXSIZE = sys.maxsize
+else:
+ string_types = basestring,
+ integer_types = (int, long)
+ class_types = (type, types.ClassType)
+ text_type = unicode
+ binary_type = str
+
+ if sys.platform.startswith("java"):
+ # Jython always uses 32 bits.
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+ class X(object):
+
+ def __len__(self):
+ return 1 << 31
+ try:
+ len(X())
+ except OverflowError:
+ # 32-bit
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # 64-bit
+ MAXSIZE = int((1 << 63) - 1)
+ del X
+
+
+def _add_doc(func, doc):
+ """Add documentation to a function."""
+ func.__doc__ = doc
+
+
+def _import_module(name):
+ """Import module, returning the module after the last dot."""
+ __import__(name)
+ return sys.modules[name]
+
+
+class _LazyDescr(object):
+
+ def __init__(self, name):
+ self.name = name
+
+ def __get__(self, obj, tp):
+ result = self._resolve()
+ setattr(obj, self.name, result) # Invokes __set__.
+ try:
+ # This is a bit ugly, but it avoids running this again by
+ # removing this descriptor.
+ delattr(obj.__class__, self.name)
+ except AttributeError:
+ pass
+ return result
+
+
+class MovedModule(_LazyDescr):
+
+ def __init__(self, name, old, new=None):
+ super(MovedModule, self).__init__(name)
+ if PY3:
+ if new is None:
+ new = name
+ self.mod = new
+ else:
+ self.mod = old
+
+ def _resolve(self):
+ return _import_module(self.mod)
+
+ def __getattr__(self, attr):
+ _module = self._resolve()
+ value = getattr(_module, attr)
+ setattr(self, attr, value)
+ return value
+
+
+class _LazyModule(types.ModuleType):
+
+ def __init__(self, name):
+ super(_LazyModule, self).__init__(name)
+ self.__doc__ = self.__class__.__doc__
+
+ def __dir__(self):
+ attrs = ["__doc__", "__name__"]
+ attrs += [attr.name for attr in self._moved_attributes]
+ return attrs
+
+ # Subclasses should override this
+ _moved_attributes = []
+
+
+class MovedAttribute(_LazyDescr):
+
+ def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+ super(MovedAttribute, self).__init__(name)
+ if PY3:
+ if new_mod is None:
+ new_mod = name
+ self.mod = new_mod
+ if new_attr is None:
+ if old_attr is None:
+ new_attr = name
+ else:
+ new_attr = old_attr
+ self.attr = new_attr
+ else:
+ self.mod = old_mod
+ if old_attr is None:
+ old_attr = name
+ self.attr = old_attr
+
+ def _resolve(self):
+ module = _import_module(self.mod)
+ return getattr(module, self.attr)
+
+
+class _SixMetaPathImporter(object):
+
+ """
+ A meta path importer to import six.moves and its submodules.
+
+ This class implements a PEP302 finder and loader. It should be compatible
+ with Python 2.5 and all existing versions of Python3
+ """
+
+ def __init__(self, six_module_name):
+ self.name = six_module_name
+ self.known_modules = {}
+
+ def _add_module(self, mod, *fullnames):
+ for fullname in fullnames:
+ self.known_modules[self.name + "." + fullname] = mod
+
+ def _get_module(self, fullname):
+ return self.known_modules[self.name + "." + fullname]
+
+ def find_module(self, fullname, path=None):
+ if fullname in self.known_modules:
+ return self
+ return None
+
+ def __get_module(self, fullname):
+ try:
+ return self.known_modules[fullname]
+ except KeyError:
+ raise ImportError("This loader does not know module " + fullname)
+
+ def load_module(self, fullname):
+ try:
+ # in case of a reload
+ return sys.modules[fullname]
+ except KeyError:
+ pass
+ mod = self.__get_module(fullname)
+ if isinstance(mod, MovedModule):
+ mod = mod._resolve()
+ else:
+ mod.__loader__ = self
+ sys.modules[fullname] = mod
+ return mod
+
+ def is_package(self, fullname):
+ """
+ Return true, if the named module is a package.
+
+ We need this method to get correct spec objects with
+ Python 3.4 (see PEP451)
+ """
+ return hasattr(self.__get_module(fullname), "__path__")
+
+ def get_code(self, fullname):
+ """Return None
+
+ Required, if is_package is implemented"""
+ self.__get_module(fullname) # eventually raises ImportError
+ return None
+ get_source = get_code # same as get_code
+
+_importer = _SixMetaPathImporter(__name__)
+
+
+class _MovedItems(_LazyModule):
+
+ """Lazy loading of moved objects"""
+ __path__ = [] # mark as package
+
+
+_moved_attributes = [
+ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+ MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+ MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+ MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+ MovedAttribute("intern", "__builtin__", "sys"),
+ MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+ MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+ MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
+ MovedAttribute("getoutput", "commands", "subprocess"),
+ MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+ MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
+ MovedAttribute("reduce", "__builtin__", "functools"),
+ MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
+ MovedAttribute("StringIO", "StringIO", "io"),
+ MovedAttribute("UserDict", "UserDict", "collections"),
+ MovedAttribute("UserList", "UserList", "collections"),
+ MovedAttribute("UserString", "UserString", "collections"),
+ MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+ MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+ MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+ MovedModule("builtins", "__builtin__"),
+ MovedModule("configparser", "ConfigParser"),
+ MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
+ MovedModule("copyreg", "copy_reg"),
+ MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+ MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
+ MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
+ MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+ MovedModule("http_cookies", "Cookie", "http.cookies"),
+ MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+ MovedModule("html_parser", "HTMLParser", "html.parser"),
+ MovedModule("http_client", "httplib", "http.client"),
+ MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+ MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
+ MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+ MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
+ MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+ MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+ MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+ MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+ MovedModule("cPickle", "cPickle", "pickle"),
+ MovedModule("queue", "Queue"),
+ MovedModule("reprlib", "repr"),
+ MovedModule("socketserver", "SocketServer"),
+ MovedModule("_thread", "thread", "_thread"),
+ MovedModule("tkinter", "Tkinter"),
+ MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+ MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+ MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+ MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+ MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+ MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+ MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+ MovedModule("tkinter_colorchooser", "tkColorChooser",
+ "tkinter.colorchooser"),
+ MovedModule("tkinter_commondialog", "tkCommonDialog",
+ "tkinter.commondialog"),
+ MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+ MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+ MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+ "tkinter.simpledialog"),
+ MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+ MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+ MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+ MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+ MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+ MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
+]
+# Add windows specific modules.
+if sys.platform == "win32":
+ _moved_attributes += [
+ MovedModule("winreg", "_winreg"),
+ ]
+
+for attr in _moved_attributes:
+ setattr(_MovedItems, attr.name, attr)
+ if isinstance(attr, MovedModule):
+ _importer._add_module(attr, "moves." + attr.name)
+del attr
+
+_MovedItems._moved_attributes = _moved_attributes
+
+moves = _MovedItems(__name__ + ".moves")
+_importer._add_module(moves, "moves")
+
+
+class Module_six_moves_urllib_parse(_LazyModule):
+
+ """Lazy loading of moved objects in six.moves.urllib_parse"""
+
+
+_urllib_parse_moved_attributes = [
+ MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+ MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
+ MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+ MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+ MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+ MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+ MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+ MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+ MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+ MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+ MovedAttribute("quote", "urllib", "urllib.parse"),
+ MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+ MovedAttribute("unquote", "urllib", "urllib.parse"),
+ MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+ MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
+ MovedAttribute("urlencode", "urllib", "urllib.parse"),
+ MovedAttribute("splitquery", "urllib", "urllib.parse"),
+ MovedAttribute("splittag", "urllib", "urllib.parse"),
+ MovedAttribute("splituser", "urllib", "urllib.parse"),
+ MovedAttribute("splitvalue", "urllib", "urllib.parse"),
+ MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
+ MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
+ MovedAttribute("uses_params", "urlparse", "urllib.parse"),
+ MovedAttribute("uses_query", "urlparse", "urllib.parse"),
+ MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
+]
+for attr in _urllib_parse_moved_attributes:
+ setattr(Module_six_moves_urllib_parse, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
+ "moves.urllib_parse", "moves.urllib.parse")
+
+
+class Module_six_moves_urllib_error(_LazyModule):
+
+ """Lazy loading of moved objects in six.moves.urllib_error"""
+
+
+_urllib_error_moved_attributes = [
+ MovedAttribute("URLError", "urllib2", "urllib.error"),
+ MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+ MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+]
+for attr in _urllib_error_moved_attributes:
+ setattr(Module_six_moves_urllib_error, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
+ "moves.urllib_error", "moves.urllib.error")
+
+
+class Module_six_moves_urllib_request(_LazyModule):
+
+ """Lazy loading of moved objects in six.moves.urllib_request"""
+
+
+_urllib_request_moved_attributes = [
+ MovedAttribute("urlopen", "urllib2", "urllib.request"),
+ MovedAttribute("install_opener", "urllib2", "urllib.request"),
+ MovedAttribute("build_opener", "urllib2", "urllib.request"),
+ MovedAttribute("pathname2url", "urllib", "urllib.request"),
+ MovedAttribute("url2pathname", "urllib", "urllib.request"),
+ MovedAttribute("getproxies", "urllib", "urllib.request"),
+ MovedAttribute("Request", "urllib2", "urllib.request"),
+ MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+ MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+ MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+ MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+ MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+ MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+ MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+ MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+ MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+ MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+ MovedAttribute("URLopener", "urllib", "urllib.request"),
+ MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+ MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+ MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
+ MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
+]
+for attr in _urllib_request_moved_attributes:
+ setattr(Module_six_moves_urllib_request, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
+ "moves.urllib_request", "moves.urllib.request")
+
+
+class Module_six_moves_urllib_response(_LazyModule):
+
+ """Lazy loading of moved objects in six.moves.urllib_response"""
+
+
+_urllib_response_moved_attributes = [
+ MovedAttribute("addbase", "urllib", "urllib.response"),
+ MovedAttribute("addclosehook", "urllib", "urllib.response"),
+ MovedAttribute("addinfo", "urllib", "urllib.response"),
+ MovedAttribute("addinfourl", "urllib", "urllib.response"),
+]
+for attr in _urllib_response_moved_attributes:
+ setattr(Module_six_moves_urllib_response, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
+ "moves.urllib_response", "moves.urllib.response")
+
+
+class Module_six_moves_urllib_robotparser(_LazyModule):
+
+ """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+
+
+_urllib_robotparser_moved_attributes = [
+ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+]
+for attr in _urllib_robotparser_moved_attributes:
+ setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+
+_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
+ "moves.urllib_robotparser", "moves.urllib.robotparser")
+
+
+class Module_six_moves_urllib(types.ModuleType):
+
+ """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+ __path__ = [] # mark as package
+ parse = _importer._get_module("moves.urllib_parse")
+ error = _importer._get_module("moves.urllib_error")
+ request = _importer._get_module("moves.urllib_request")
+ response = _importer._get_module("moves.urllib_response")
+ robotparser = _importer._get_module("moves.urllib_robotparser")
+
+ def __dir__(self):
+ return ['parse', 'error', 'request', 'response', 'robotparser']
+
+_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
+ "moves.urllib")
+
+
+def add_move(move):
+ """Add an item to six.moves."""
+ setattr(_MovedItems, move.name, move)
+
+
+def remove_move(name):
+ """Remove item from six.moves."""
+ try:
+ delattr(_MovedItems, name)
+ except AttributeError:
+ try:
+ del moves.__dict__[name]
+ except KeyError:
+ raise AttributeError("no such move, %r" % (name,))
+
+
+if PY3:
+ _meth_func = "__func__"
+ _meth_self = "__self__"
+
+ _func_closure = "__closure__"
+ _func_code = "__code__"
+ _func_defaults = "__defaults__"
+ _func_globals = "__globals__"
+else:
+ _meth_func = "im_func"
+ _meth_self = "im_self"
+
+ _func_closure = "func_closure"
+ _func_code = "func_code"
+ _func_defaults = "func_defaults"
+ _func_globals = "func_globals"
+
+
+try:
+ advance_iterator = next
+except NameError:
+ def advance_iterator(it):
+ return it.next()
+next = advance_iterator
+
+
+try:
+ callable = callable
+except NameError:
+ def callable(obj):
+ return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+
+
+if PY3:
+ def get_unbound_function(unbound):
+ return unbound
+
+ create_bound_method = types.MethodType
+
+ def create_unbound_method(func, cls):
+ return func
+
+ Iterator = object
+else:
+ def get_unbound_function(unbound):
+ return unbound.im_func
+
+ def create_bound_method(func, obj):
+ return types.MethodType(func, obj, obj.__class__)
+
+ def create_unbound_method(func, cls):
+ return types.MethodType(func, None, cls)
+
+ class Iterator(object):
+
+ def next(self):
+ return type(self).__next__(self)
+
+ callable = callable
+_add_doc(get_unbound_function,
+ """Get the function out of a possibly unbound function""")
+
+
+get_method_function = operator.attrgetter(_meth_func)
+get_method_self = operator.attrgetter(_meth_self)
+get_function_closure = operator.attrgetter(_func_closure)
+get_function_code = operator.attrgetter(_func_code)
+get_function_defaults = operator.attrgetter(_func_defaults)
+get_function_globals = operator.attrgetter(_func_globals)
+
+
+if PY3:
+ def iterkeys(d, **kw):
+ return iter(d.keys(**kw))
+
+ def itervalues(d, **kw):
+ return iter(d.values(**kw))
+
+ def iteritems(d, **kw):
+ return iter(d.items(**kw))
+
+ def iterlists(d, **kw):
+ return iter(d.lists(**kw))
+
+ viewkeys = operator.methodcaller("keys")
+
+ viewvalues = operator.methodcaller("values")
+
+ viewitems = operator.methodcaller("items")
+else:
+ def iterkeys(d, **kw):
+ return d.iterkeys(**kw)
+
+ def itervalues(d, **kw):
+ return d.itervalues(**kw)
+
+ def iteritems(d, **kw):
+ return d.iteritems(**kw)
+
+ def iterlists(d, **kw):
+ return d.iterlists(**kw)
+
+ viewkeys = operator.methodcaller("viewkeys")
+
+ viewvalues = operator.methodcaller("viewvalues")
+
+ viewitems = operator.methodcaller("viewitems")
+
+_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
+_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
+_add_doc(iteritems,
+ "Return an iterator over the (key, value) pairs of a dictionary.")
+_add_doc(iterlists,
+ "Return an iterator over the (key, [values]) pairs of a dictionary.")
+
+
+if PY3:
+ def b(s):
+ return s.encode("latin-1")
+
+ def u(s):
+ return s
+ unichr = chr
+ import struct
+ int2byte = struct.Struct(">B").pack
+ del struct
+ byte2int = operator.itemgetter(0)
+ indexbytes = operator.getitem
+ iterbytes = iter
+ import io
+ StringIO = io.StringIO
+ BytesIO = io.BytesIO
+ del io
+ _assertCountEqual = "assertCountEqual"
+ if sys.version_info[1] <= 1:
+ _assertRaisesRegex = "assertRaisesRegexp"
+ _assertRegex = "assertRegexpMatches"
+ _assertNotRegex = "assertNotRegexpMatches"
+ else:
+ _assertRaisesRegex = "assertRaisesRegex"
+ _assertRegex = "assertRegex"
+ _assertNotRegex = "assertNotRegex"
+else:
+ def b(s):
+ return s
+ # Workaround for standalone backslash
+
+ def u(s):
+ return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+ unichr = unichr
+ int2byte = chr
+
+ def byte2int(bs):
+ return ord(bs[0])
+
+ def indexbytes(buf, i):
+ return ord(buf[i])
+ iterbytes = functools.partial(itertools.imap, ord)
+ import StringIO
+ StringIO = BytesIO = StringIO.StringIO
+ _assertCountEqual = "assertItemsEqual"
+ _assertRaisesRegex = "assertRaisesRegexp"
+ _assertRegex = "assertRegexpMatches"
+ _assertNotRegex = "assertNotRegexpMatches"
+_add_doc(b, """Byte literal""")
+_add_doc(u, """Text literal""")
+
+
+def assertCountEqual(self, *args, **kwargs):
+ return getattr(self, _assertCountEqual)(*args, **kwargs)
+
+
+def assertRaisesRegex(self, *args, **kwargs):
+ return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+
+
+def assertRegex(self, *args, **kwargs):
+ return getattr(self, _assertRegex)(*args, **kwargs)
+
+
+def assertNotRegex(self, *args, **kwargs):
+ return getattr(self, _assertNotRegex)(*args, **kwargs)
+
+
+if PY3:
+ exec_ = getattr(moves.builtins, "exec")
+
+ def reraise(tp, value, tb=None):
+ try:
+ if value is None:
+ value = tp()
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+ finally:
+ value = None
+ tb = None
+
+else:
+ def exec_(_code_, _globs_=None, _locs_=None):
+ """Execute code in a namespace."""
+ if _globs_ is None:
+ frame = sys._getframe(1)
+ _globs_ = frame.f_globals
+ if _locs_ is None:
+ _locs_ = frame.f_locals
+ del frame
+ elif _locs_ is None:
+ _locs_ = _globs_
+ exec("""exec _code_ in _globs_, _locs_""")
+
+ exec_("""def reraise(tp, value, tb=None):
+ try:
+ raise tp, value, tb
+ finally:
+ tb = None
+""")
+
+
+if sys.version_info[:2] > (3,):
+ exec_("""def raise_from(value, from_value):
+ try:
+ raise value from from_value
+ finally:
+ value = None
+""")
+else:
+ def raise_from(value, from_value):
+ raise value
+
+
+print_ = getattr(moves.builtins, "print", None)
+if print_ is None:
+ def print_(*args, **kwargs):
+ """The new-style print function for Python 2.4 and 2.5."""
+ fp = kwargs.pop("file", sys.stdout)
+ if fp is None:
+ return
+
+ def write(data):
+ if not isinstance(data, basestring):
+ data = str(data)
+ # If the file has an encoding, encode unicode with it.
+ if (isinstance(fp, file) and
+ isinstance(data, unicode) and
+ fp.encoding is not None):
+ errors = getattr(fp, "errors", None)
+ if errors is None:
+ errors = "strict"
+ data = data.encode(fp.encoding, errors)
+ fp.write(data)
+ want_unicode = False
+ sep = kwargs.pop("sep", None)
+ if sep is not None:
+ if isinstance(sep, unicode):
+ want_unicode = True
+ elif not isinstance(sep, str):
+ raise TypeError("sep must be None or a string")
+ end = kwargs.pop("end", None)
+ if end is not None:
+ if isinstance(end, unicode):
+ want_unicode = True
+ elif not isinstance(end, str):
+ raise TypeError("end must be None or a string")
+ if kwargs:
+ raise TypeError("invalid keyword arguments to print()")
+ if not want_unicode:
+ for arg in args:
+ if isinstance(arg, unicode):
+ want_unicode = True
+ break
+ if want_unicode:
+ newline = unicode("\n")
+ space = unicode(" ")
+ else:
+ newline = "\n"
+ space = " "
+ if sep is None:
+ sep = space
+ if end is None:
+ end = newline
+ for i, arg in enumerate(args):
+ if i:
+ write(sep)
+ write(arg)
+ write(end)
+if sys.version_info[:2] < (3, 3):
+ _print = print_
+
+ def print_(*args, **kwargs):
+ fp = kwargs.get("file", sys.stdout)
+ flush = kwargs.pop("flush", False)
+ _print(*args, **kwargs)
+ if flush and fp is not None:
+ fp.flush()
+
+_add_doc(reraise, """Reraise an exception.""")
+
+if sys.version_info[0:2] < (3, 4):
+ # This does exactly the same what the :func:`py3:functools.update_wrapper`
+ # function does on Python versions after 3.2. It sets the ``__wrapped__``
+ # attribute on ``wrapper`` object and it doesn't raise an error if any of
+ # the attributes mentioned in ``assigned`` and ``updated`` are missing on
+ # ``wrapped`` object.
+ def _update_wrapper(wrapper, wrapped,
+ assigned=functools.WRAPPER_ASSIGNMENTS,
+ updated=functools.WRAPPER_UPDATES):
+ for attr in assigned:
+ try:
+ value = getattr(wrapped, attr)
+ except AttributeError:
+ continue
+ else:
+ setattr(wrapper, attr, value)
+ for attr in updated:
+ getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+ wrapper.__wrapped__ = wrapped
+ return wrapper
+ _update_wrapper.__doc__ = functools.update_wrapper.__doc__
+
+ def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
+ updated=functools.WRAPPER_UPDATES):
+ return functools.partial(_update_wrapper, wrapped=wrapped,
+ assigned=assigned, updated=updated)
+ wraps.__doc__ = functools.wraps.__doc__
+
+else:
+ wraps = functools.wraps
+
+
+def with_metaclass(meta, *bases):
+ """Create a base class with a metaclass."""
+ # This requires a bit of explanation: the basic idea is to make a dummy
+ # metaclass for one level of class instantiation that replaces itself with
+ # the actual metaclass.
+ class metaclass(type):
+
+ def __new__(cls, name, this_bases, d):
+ if sys.version_info[:2] >= (3, 7):
+ # This version introduced PEP 560 that requires a bit
+ # of extra care (we mimic what is done by __build_class__).
+ resolved_bases = types.resolve_bases(bases)
+ if resolved_bases is not bases:
+ d['__orig_bases__'] = bases
+ else:
+ resolved_bases = bases
+ return meta(name, resolved_bases, d)
+
+ @classmethod
+ def __prepare__(cls, name, this_bases):
+ return meta.__prepare__(name, bases)
+ return type.__new__(metaclass, 'temporary_class', (), {})
+
+
+def add_metaclass(metaclass):
+ """Class decorator for creating a class with a metaclass."""
+ def wrapper(cls):
+ orig_vars = cls.__dict__.copy()
+ slots = orig_vars.get('__slots__')
+ if slots is not None:
+ if isinstance(slots, str):
+ slots = [slots]
+ for slots_var in slots:
+ orig_vars.pop(slots_var)
+ orig_vars.pop('__dict__', None)
+ orig_vars.pop('__weakref__', None)
+ if hasattr(cls, '__qualname__'):
+ orig_vars['__qualname__'] = cls.__qualname__
+ return metaclass(cls.__name__, cls.__bases__, orig_vars)
+ return wrapper
+
+
+def ensure_binary(s, encoding='utf-8', errors='strict'):
+ """Coerce **s** to six.binary_type.
+
+ For Python 2:
+ - `unicode` -> encoded to `str`
+ - `str` -> `str`
+
+ For Python 3:
+ - `str` -> encoded to `bytes`
+ - `bytes` -> `bytes`
+ """
+ if isinstance(s, binary_type):
+ return s
+ if isinstance(s, text_type):
+ return s.encode(encoding, errors)
+ raise TypeError("not expecting type '%s'" % type(s))
+
+
+def ensure_str(s, encoding='utf-8', errors='strict'):
+ """Coerce *s* to `str`.
+
+ For Python 2:
+ - `unicode` -> encoded to `str`
+ - `str` -> `str`
+
+ For Python 3:
+ - `str` -> `str`
+ - `bytes` -> decoded to `str`
+ """
+ # Optimization: Fast return for the common case.
+ if type(s) is str:
+ return s
+ if PY2 and isinstance(s, text_type):
+ return s.encode(encoding, errors)
+ elif PY3 and isinstance(s, binary_type):
+ return s.decode(encoding, errors)
+ elif not isinstance(s, (text_type, binary_type)):
+ raise TypeError("not expecting type '%s'" % type(s))
+ return s
+
+
+def ensure_text(s, encoding='utf-8', errors='strict'):
+ """Coerce *s* to six.text_type.
+
+ For Python 2:
+ - `unicode` -> `unicode`
+ - `str` -> `unicode`
+
+ For Python 3:
+ - `str` -> `str`
+ - `bytes` -> decoded to `str`
+ """
+ if isinstance(s, binary_type):
+ return s.decode(encoding, errors)
+ elif isinstance(s, text_type):
+ return s
+ else:
+ raise TypeError("not expecting type '%s'" % type(s))
+
+
+def python_2_unicode_compatible(klass):
+ """
+ A class decorator that defines __unicode__ and __str__ methods under Python 2.
+ Under Python 3 it does nothing.
+
+ To support Python 2 and 3 with a single code base, define a __str__ method
+ returning text and apply this decorator to the class.
+ """
+ if PY2:
+ if '__str__' not in klass.__dict__:
+ raise ValueError("@python_2_unicode_compatible cannot be applied "
+ "to %s because it doesn't define __str__()." %
+ klass.__name__)
+ klass.__unicode__ = klass.__str__
+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+ return klass
+
+
+# Complete the moves implementation.
+# This code is at the end of this module to speed up module loading.
+# Turn this module into a package.
+__path__ = [] # required for PEP 302 and PEP 451
+__package__ = __name__ # see PEP 366 @ReservedAssignment
+if globals().get("__spec__") is not None:
+ __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
+# Remove other six meta path importers, since they cause problems. This can
+# happen if six is removed from sys.modules and then reloaded. (Setuptools does
+# this for some reason.)
+if sys.meta_path:
+ for i, importer in enumerate(sys.meta_path):
+ # Here's some real nastiness: Another "instance" of the six module might
+ # be floating around. Therefore, we can't use isinstance() to check for
+ # the six meta path importer, since the other six instance will have
+ # inserted an importer with different class.
+ if (type(importer).__name__ == "_SixMetaPathImporter" and
+ importer.name == __name__):
+ del sys.meta_path[i]
+ break
+ del i, importer
+# Finally, add the importer to the meta path import hook.
+sys.meta_path.append(_importer)
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/LICENSE
new file mode 100644
index 0000000000..583f9f6e61
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/LICENSE
@@ -0,0 +1,254 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC. Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team. In October of the same
+year, the PythonLabs team moved to Digital Creations (now Zope
+Corporation, see http://www.zope.com). In 2001, the Python Software
+Foundation (PSF, see http://www.python.org/psf/) was formed, a
+non-profit organization created specifically to own Python-related
+Intellectual Property. Zope Corporation is a sponsoring member of
+the PSF.
+
+All Python releases are Open Source (see http://www.opensource.org for
+the Open Source Definition). Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+ Release Derived Year Owner GPL-
+ from compatible? (1)
+
+ 0.9.0 thru 1.2 1991-1995 CWI yes
+ 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
+ 1.6 1.5.2 2000 CNRI no
+ 2.0 1.6 2000 BeOpen.com no
+ 1.6.1 1.6 2001 CNRI yes (2)
+ 2.1 2.0+1.6.1 2001 PSF no
+ 2.0.1 2.0+1.6.1 2001 PSF yes
+ 2.1.1 2.1+2.0.1 2001 PSF yes
+ 2.1.2 2.1.1 2002 PSF yes
+ 2.1.3 2.1.2 2002 PSF yes
+ 2.2 and above 2.1.1 2001-now PSF yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+ the GPL. All Python licenses, unlike the GPL, let you distribute
+ a modified version without making your changes open source. The
+ GPL-compatible licenses make it possible to combine Python with
+ other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+ because its license has a choice of law clause. According to
+ CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+ is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are
+retained in Python alone or in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions. Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee. This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party. As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee. Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement. This Agreement together with
+Python 1.6.1 may be located on the Internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013. This
+Agreement may also be obtained from a proxy server on the Internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee. This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+ ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands. All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/METADATA
new file mode 100644
index 0000000000..d98aa65996
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/METADATA
@@ -0,0 +1,41 @@
+Metadata-Version: 2.1
+Name: typing
+Version: 3.7.4.1
+Summary: Type Hints for Python
+Home-page: https://docs.python.org/3/library/typing.html
+Author: Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Ivan Levkivskyi
+Author-email: jukka.lehtosalo@iki.fi
+License: PSF
+Keywords: typing function annotations type hints hinting checking checker typehints typehinting typechecking backport
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Software Development
+
+Typing -- Type Hints for Python
+
+This is a backport of the standard library typing module to Python
+versions older than 3.5. (See note below for newer versions.)
+
+Typing defines a standard notation for Python function and variable
+type annotations. The notation can be used for documenting code in a
+concise, standard format, and it has been designed to also be used by
+static and runtime type checkers, static analyzers, IDEs and other
+tools.
+
+NOTE: in Python 3.5 and later, the typing module lives in the stdlib,
+and installing this package has NO EFFECT. To get a newer version of
+the typing module in Python 3.5 or later, you have to upgrade to a
+newer Python (bugfix) version. For example, typing in Python 3.6.0 is
+missing the definition of 'Type' -- upgrading to 3.6.2 will fix this.
+
+Also note that most improvements to the typing module in Python 3.7
+will not be included in this package, since Python 3.7 has some
+built-in support that is not present in older versions (See PEP 560.)
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/RECORD
new file mode 100644
index 0000000000..921edbf40f
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/RECORD
@@ -0,0 +1,6 @@
+typing.py,sha256=JfGga08eJ_AJ-n_EX5EHtDjNNI5h79rYSXucibO6yNg,80432
+typing-3.7.4.1.dist-info/LICENSE,sha256=_xfOlOECAk3raHc-scx0ynbaTmWPNzUx8Kwi1oprsa0,12755
+typing-3.7.4.1.dist-info/METADATA,sha256=bDK323dZ06sy5ADWZkwBpgq6jS9nwECYjA2oysfGjeg,1798
+typing-3.7.4.1.dist-info/WHEEL,sha256=p46_5Uhzqz6AzeSosiOnxK-zmFja1i22CrQCjmYe8ec,92
+typing-3.7.4.1.dist-info/top_level.txt,sha256=oG8QCMTRcfcgGpEVbdwBU2DM8MthjmZSDaaQ6WWHx4o,7
+typing-3.7.4.1.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/WHEEL
new file mode 100644
index 0000000000..3b5c4038dd
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.33.6)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/top_level.txt
new file mode 100644
index 0000000000..c997f364b4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+typing
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing.py b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing.py
new file mode 100644
index 0000000000..62a677eee3
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.1-py3-none-any/typing.py
@@ -0,0 +1,2422 @@
+import abc
+from abc import abstractmethod, abstractproperty
+import collections
+import contextlib
+import functools
+import re as stdlib_re # Avoid confusion with the re we export.
+import sys
+import types
+try:
+ import collections.abc as collections_abc
+except ImportError:
+ import collections as collections_abc # Fallback for PY3.2.
+if sys.version_info[:2] >= (3, 6):
+ import _collections_abc # Needed for private function _check_methods # noqa
+try:
+ from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType
+except ImportError:
+ WrapperDescriptorType = type(object.__init__)
+ MethodWrapperType = type(object().__str__)
+ MethodDescriptorType = type(str.join)
+
+
+# Please keep __all__ alphabetized within each category.
+__all__ = [
+ # Super-special typing primitives.
+ 'Any',
+ 'Callable',
+ 'ClassVar',
+ 'Generic',
+ 'Optional',
+ 'Tuple',
+ 'Type',
+ 'TypeVar',
+ 'Union',
+
+ # ABCs (from collections.abc).
+ 'AbstractSet', # collections.abc.Set.
+ 'GenericMeta', # subclass of abc.ABCMeta and a metaclass
+ # for 'Generic' and ABCs below.
+ 'ByteString',
+ 'Container',
+ 'ContextManager',
+ 'Hashable',
+ 'ItemsView',
+ 'Iterable',
+ 'Iterator',
+ 'KeysView',
+ 'Mapping',
+ 'MappingView',
+ 'MutableMapping',
+ 'MutableSequence',
+ 'MutableSet',
+ 'Sequence',
+ 'Sized',
+ 'ValuesView',
+ # The following are added depending on presence
+ # of their non-generic counterparts in stdlib:
+ # Awaitable,
+ # AsyncIterator,
+ # AsyncIterable,
+ # Coroutine,
+ # Collection,
+ # AsyncGenerator,
+ # AsyncContextManager
+
+ # Structural checks, a.k.a. protocols.
+ 'Reversible',
+ 'SupportsAbs',
+ 'SupportsBytes',
+ 'SupportsComplex',
+ 'SupportsFloat',
+ 'SupportsIndex',
+ 'SupportsInt',
+ 'SupportsRound',
+
+ # Concrete collection types.
+ 'Counter',
+ 'Deque',
+ 'Dict',
+ 'DefaultDict',
+ 'List',
+ 'Set',
+ 'FrozenSet',
+ 'NamedTuple', # Not really a type.
+ 'Generator',
+
+ # One-off things.
+ 'AnyStr',
+ 'cast',
+ 'get_type_hints',
+ 'NewType',
+ 'no_type_check',
+ 'no_type_check_decorator',
+ 'NoReturn',
+ 'overload',
+ 'Text',
+ 'TYPE_CHECKING',
+]
+
+# The pseudo-submodules 're' and 'io' are part of the public
+# namespace, but excluded from __all__ because they might stomp on
+# legitimate imports of those modules.
+
+
+def _qualname(x):
+ if sys.version_info[:2] >= (3, 3):
+ return x.__qualname__
+ else:
+ # Fall back to just name.
+ return x.__name__
+
+
+def _trim_name(nm):
+ whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase')
+ if nm.startswith('_') and nm not in whitelist:
+ nm = nm[1:]
+ return nm
+
+
+class TypingMeta(type):
+ """Metaclass for most types defined in typing module
+ (not a part of public API).
+
+ This overrides __new__() to require an extra keyword parameter
+ '_root', which serves as a guard against naive subclassing of the
+ typing classes. Any legitimate class defined using a metaclass
+ derived from TypingMeta must pass _root=True.
+
+ This also defines a dummy constructor (all the work for most typing
+ constructs is done in __new__) and a nicer repr().
+ """
+
+ _is_protocol = False
+
+ def __new__(cls, name, bases, namespace, *, _root=False):
+ if not _root:
+ raise TypeError("Cannot subclass %s" %
+ (', '.join(map(_type_repr, bases)) or '()'))
+ return super().__new__(cls, name, bases, namespace)
+
+ def __init__(self, *args, **kwds):
+ pass
+
+ def _eval_type(self, globalns, localns):
+ """Override this in subclasses to interpret forward references.
+
+ For example, List['C'] is internally stored as
+ List[_ForwardRef('C')], which should evaluate to List[C],
+ where C is an object found in globalns or localns (searching
+ localns first, of course).
+ """
+ return self
+
+ def _get_type_vars(self, tvars):
+ pass
+
+ def __repr__(self):
+ qname = _trim_name(_qualname(self))
+ return '%s.%s' % (self.__module__, qname)
+
+
+class _TypingBase(metaclass=TypingMeta, _root=True):
+ """Internal indicator of special typing constructs."""
+
+ __slots__ = ('__weakref__',)
+
+ def __init__(self, *args, **kwds):
+ pass
+
+ def __new__(cls, *args, **kwds):
+ """Constructor.
+
+ This only exists to give a better error message in case
+ someone tries to subclass a special typing object (not a good idea).
+ """
+ if (len(args) == 3 and
+ isinstance(args[0], str) and
+ isinstance(args[1], tuple)):
+ # Close enough.
+ raise TypeError("Cannot subclass %r" % cls)
+ return super().__new__(cls)
+
+ # Things that are not classes also need these.
+ def _eval_type(self, globalns, localns):
+ return self
+
+ def _get_type_vars(self, tvars):
+ pass
+
+ def __repr__(self):
+ cls = type(self)
+ qname = _trim_name(_qualname(cls))
+ return '%s.%s' % (cls.__module__, qname)
+
+ def __call__(self, *args, **kwds):
+ raise TypeError("Cannot instantiate %r" % type(self))
+
+
+class _FinalTypingBase(_TypingBase, _root=True):
+ """Internal mix-in class to prevent instantiation.
+
+ Prevents instantiation unless _root=True is given in class call.
+ It is used to create pseudo-singleton instances Any, Union, Optional, etc.
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, *args, _root=False, **kwds):
+ self = super().__new__(cls, *args, **kwds)
+ if _root is True:
+ return self
+ raise TypeError("Cannot instantiate %r" % cls)
+
+ def __reduce__(self):
+ return _trim_name(type(self).__name__)
+
+
+class _ForwardRef(_TypingBase, _root=True):
+ """Internal wrapper to hold a forward reference."""
+
+ __slots__ = ('__forward_arg__', '__forward_code__',
+ '__forward_evaluated__', '__forward_value__')
+
+ def __init__(self, arg):
+ super().__init__(arg)
+ if not isinstance(arg, str):
+ raise TypeError('Forward reference must be a string -- got %r' % (arg,))
+ try:
+ code = compile(arg, '<string>', 'eval')
+ except SyntaxError:
+ raise SyntaxError('Forward reference must be an expression -- got %r' %
+ (arg,))
+ self.__forward_arg__ = arg
+ self.__forward_code__ = code
+ self.__forward_evaluated__ = False
+ self.__forward_value__ = None
+
+ def _eval_type(self, globalns, localns):
+ if not self.__forward_evaluated__ or localns is not globalns:
+ if globalns is None and localns is None:
+ globalns = localns = {}
+ elif globalns is None:
+ globalns = localns
+ elif localns is None:
+ localns = globalns
+ self.__forward_value__ = _type_check(
+ eval(self.__forward_code__, globalns, localns),
+ "Forward references must evaluate to types.")
+ self.__forward_evaluated__ = True
+ return self.__forward_value__
+
+ def __eq__(self, other):
+ if not isinstance(other, _ForwardRef):
+ return NotImplemented
+ return (self.__forward_arg__ == other.__forward_arg__ and
+ self.__forward_value__ == other.__forward_value__)
+
+ def __hash__(self):
+ return hash((self.__forward_arg__, self.__forward_value__))
+
+ def __instancecheck__(self, obj):
+ raise TypeError("Forward references cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Forward references cannot be used with issubclass().")
+
+ def __repr__(self):
+ return '_ForwardRef(%r)' % (self.__forward_arg__,)
+
+
+class _TypeAlias(_TypingBase, _root=True):
+ """Internal helper class for defining generic variants of concrete types.
+
+ Note that this is not a type; let's call it a pseudo-type. It cannot
+ be used in instance and subclass checks in parameterized form, i.e.
+ ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning
+ ``False``.
+ """
+
+ __slots__ = ('name', 'type_var', 'impl_type', 'type_checker')
+
+ def __init__(self, name, type_var, impl_type, type_checker):
+ """Initializer.
+
+ Args:
+ name: The name, e.g. 'Pattern'.
+ type_var: The type parameter, e.g. AnyStr, or the
+ specific type, e.g. str.
+ impl_type: The implementation type.
+ type_checker: Function that takes an impl_type instance.
+ and returns a value that should be a type_var instance.
+ """
+ assert isinstance(name, str), repr(name)
+ assert isinstance(impl_type, type), repr(impl_type)
+ assert not isinstance(impl_type, TypingMeta), repr(impl_type)
+ assert isinstance(type_var, (type, _TypingBase)), repr(type_var)
+ self.name = name
+ self.type_var = type_var
+ self.impl_type = impl_type
+ self.type_checker = type_checker
+
+ def __repr__(self):
+ return "%s[%s]" % (self.name, _type_repr(self.type_var))
+
+ def __getitem__(self, parameter):
+ if not isinstance(self.type_var, TypeVar):
+ raise TypeError("%s cannot be further parameterized." % self)
+ if self.type_var.__constraints__ and isinstance(parameter, type):
+ if not issubclass(parameter, self.type_var.__constraints__):
+ raise TypeError("%s is not a valid substitution for %s." %
+ (parameter, self.type_var))
+ if isinstance(parameter, TypeVar) and parameter is not self.type_var:
+ raise TypeError("%s cannot be re-parameterized." % self)
+ return self.__class__(self.name, parameter,
+ self.impl_type, self.type_checker)
+
+ def __eq__(self, other):
+ if not isinstance(other, _TypeAlias):
+ return NotImplemented
+ return self.name == other.name and self.type_var == other.type_var
+
+ def __hash__(self):
+ return hash((self.name, self.type_var))
+
+ def __instancecheck__(self, obj):
+ if not isinstance(self.type_var, TypeVar):
+ raise TypeError("Parameterized type aliases cannot be used "
+ "with isinstance().")
+ return isinstance(obj, self.impl_type)
+
+ def __subclasscheck__(self, cls):
+ if not isinstance(self.type_var, TypeVar):
+ raise TypeError("Parameterized type aliases cannot be used "
+ "with issubclass().")
+ return issubclass(cls, self.impl_type)
+
+
+def _get_type_vars(types, tvars):
+ for t in types:
+ if isinstance(t, TypingMeta) or isinstance(t, _TypingBase):
+ t._get_type_vars(tvars)
+
+
+def _type_vars(types):
+ tvars = []
+ _get_type_vars(types, tvars)
+ return tuple(tvars)
+
+
+def _eval_type(t, globalns, localns):
+ if isinstance(t, TypingMeta) or isinstance(t, _TypingBase):
+ return t._eval_type(globalns, localns)
+ return t
+
+
+def _type_check(arg, msg):
+ """Check that the argument is a type, and return it (internal helper).
+
+ As a special case, accept None and return type(None) instead.
+ Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable.
+
+ The msg argument is a human-readable error message, e.g.
+
+ "Union[arg, ...]: arg should be a type."
+
+ We append the repr() of the actual value (truncated to 100 chars).
+ """
+ if arg is None:
+ return type(None)
+ if isinstance(arg, str):
+ arg = _ForwardRef(arg)
+ if (
+ isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or
+ not isinstance(arg, (type, _TypingBase)) and not callable(arg)
+ ):
+ raise TypeError(msg + " Got %.100r." % (arg,))
+ # Bare Union etc. are not valid as type arguments
+ if (
+ type(arg).__name__ in ('_Union', '_Optional') and
+ not getattr(arg, '__origin__', None) or
+ isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol)
+ ):
+ raise TypeError("Plain %s is not valid as type argument" % arg)
+ return arg
+
+
+def _type_repr(obj):
+ """Return the repr() of an object, special-casing types (internal helper).
+
+ If obj is a type, we return a shorter version than the default
+ type.__repr__, based on the module and qualified name, which is
+ typically enough to uniquely identify a type. For everything
+ else, we fall back on repr(obj).
+ """
+ if isinstance(obj, type) and not isinstance(obj, TypingMeta):
+ if obj.__module__ == 'builtins':
+ return _qualname(obj)
+ return '%s.%s' % (obj.__module__, _qualname(obj))
+ if obj is ...:
+ return('...')
+ if isinstance(obj, types.FunctionType):
+ return obj.__name__
+ return repr(obj)
+
+
+class _Any(_FinalTypingBase, _root=True):
+ """Special type indicating an unconstrained type.
+
+ - Any is compatible with every type.
+ - Any assumed to have all methods.
+ - All values assumed to be instances of Any.
+
+ Note that all the above statements are true from the point of view of
+ static type checkers. At runtime, Any should not be used with instance
+ or class checks.
+ """
+
+ __slots__ = ()
+
+ def __instancecheck__(self, obj):
+ raise TypeError("Any cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Any cannot be used with issubclass().")
+
+
+Any = _Any(_root=True)
+
+
+class _NoReturn(_FinalTypingBase, _root=True):
+ """Special type indicating functions that never return.
+ Example::
+
+ from typing import NoReturn
+
+ def stop() -> NoReturn:
+ raise Exception('no way')
+
+ This type is invalid in other positions, e.g., ``List[NoReturn]``
+ will fail in static type checkers.
+ """
+
+ __slots__ = ()
+
+ def __instancecheck__(self, obj):
+ raise TypeError("NoReturn cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("NoReturn cannot be used with issubclass().")
+
+
+NoReturn = _NoReturn(_root=True)
+
+
+class TypeVar(_TypingBase, _root=True):
+ """Type variable.
+
+ Usage::
+
+ T = TypeVar('T') # Can be anything
+ A = TypeVar('A', str, bytes) # Must be str or bytes
+
+ Type variables exist primarily for the benefit of static type
+ checkers. They serve as the parameters for generic types as well
+ as for generic function definitions. See class Generic for more
+ information on generic types. Generic functions work as follows:
+
+ def repeat(x: T, n: int) -> List[T]:
+ '''Return a list containing n references to x.'''
+ return [x]*n
+
+ def longest(x: A, y: A) -> A:
+ '''Return the longest of two strings.'''
+ return x if len(x) >= len(y) else y
+
+ The latter example's signature is essentially the overloading
+ of (str, str) -> str and (bytes, bytes) -> bytes. Also note
+ that if the arguments are instances of some subclass of str,
+ the return type is still plain str.
+
+ At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError.
+
+ Type variables defined with covariant=True or contravariant=True
+ can be used do declare covariant or contravariant generic types.
+ See PEP 484 for more details. By default generic types are invariant
+ in all type variables.
+
+ Type variables can be introspected. e.g.:
+
+ T.__name__ == 'T'
+ T.__constraints__ == ()
+ T.__covariant__ == False
+ T.__contravariant__ = False
+ A.__constraints__ == (str, bytes)
+ """
+
+ __slots__ = ('__name__', '__bound__', '__constraints__',
+ '__covariant__', '__contravariant__')
+
+ def __init__(self, name, *constraints, bound=None,
+ covariant=False, contravariant=False):
+ super().__init__(name, *constraints, bound=bound,
+ covariant=covariant, contravariant=contravariant)
+ self.__name__ = name
+ if covariant and contravariant:
+ raise ValueError("Bivariant types are not supported.")
+ self.__covariant__ = bool(covariant)
+ self.__contravariant__ = bool(contravariant)
+ if constraints and bound is not None:
+ raise TypeError("Constraints cannot be combined with bound=...")
+ if constraints and len(constraints) == 1:
+ raise TypeError("A single constraint is not allowed")
+ msg = "TypeVar(name, constraint, ...): constraints must be types."
+ self.__constraints__ = tuple(_type_check(t, msg) for t in constraints)
+ if bound:
+ self.__bound__ = _type_check(bound, "Bound must be a type.")
+ else:
+ self.__bound__ = None
+
+ def _get_type_vars(self, tvars):
+ if self not in tvars:
+ tvars.append(self)
+
+ def __repr__(self):
+ if self.__covariant__:
+ prefix = '+'
+ elif self.__contravariant__:
+ prefix = '-'
+ else:
+ prefix = '~'
+ return prefix + self.__name__
+
+ def __instancecheck__(self, instance):
+ raise TypeError("Type variables cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Type variables cannot be used with issubclass().")
+
+
+# Some unconstrained type variables. These are used by the container types.
+# (These are not for export.)
+T = TypeVar('T') # Any type.
+KT = TypeVar('KT') # Key type.
+VT = TypeVar('VT') # Value type.
+T_co = TypeVar('T_co', covariant=True) # Any type covariant containers.
+V_co = TypeVar('V_co', covariant=True) # Any type covariant containers.
+VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers.
+T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.
+
+# A useful type variable with constraints. This represents string types.
+# (This one *is* for export!)
+AnyStr = TypeVar('AnyStr', bytes, str)
+
+
+def _replace_arg(arg, tvars, args):
+ """An internal helper function: replace arg if it is a type variable
+ found in tvars with corresponding substitution from args or
+ with corresponding substitution sub-tree if arg is a generic type.
+ """
+
+ if tvars is None:
+ tvars = []
+ if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)):
+ return arg._subs_tree(tvars, args)
+ if isinstance(arg, TypeVar):
+ for i, tvar in enumerate(tvars):
+ if arg == tvar:
+ return args[i]
+ return arg
+
+
+# Special typing constructs Union, Optional, Generic, Callable and Tuple
+# use three special attributes for internal bookkeeping of generic types:
+# * __parameters__ is a tuple of unique free type parameters of a generic
+# type, for example, Dict[T, T].__parameters__ == (T,);
+# * __origin__ keeps a reference to a type that was subscripted,
+# e.g., Union[T, int].__origin__ == Union;
+# * __args__ is a tuple of all arguments used in subscripting,
+# e.g., Dict[T, int].__args__ == (T, int).
+
+
+def _subs_tree(cls, tvars=None, args=None):
+ """An internal helper function: calculate substitution tree
+ for generic cls after replacing its type parameters with
+ substitutions in tvars -> args (if any).
+ Repeat the same following __origin__'s.
+
+ Return a list of arguments with all possible substitutions
+ performed. Arguments that are generic classes themselves are represented
+ as tuples (so that no new classes are created by this function).
+ For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)]
+ """
+
+ if cls.__origin__ is None:
+ return cls
+ # Make of chain of origins (i.e. cls -> cls.__origin__)
+ current = cls.__origin__
+ orig_chain = []
+ while current.__origin__ is not None:
+ orig_chain.append(current)
+ current = current.__origin__
+ # Replace type variables in __args__ if asked ...
+ tree_args = []
+ for arg in cls.__args__:
+ tree_args.append(_replace_arg(arg, tvars, args))
+ # ... then continue replacing down the origin chain.
+ for ocls in orig_chain:
+ new_tree_args = []
+ for arg in ocls.__args__:
+ new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args))
+ tree_args = new_tree_args
+ return tree_args
+
+
+def _remove_dups_flatten(parameters):
+ """An internal helper for Union creation and substitution: flatten Union's
+ among parameters, then remove duplicates and strict subclasses.
+ """
+
+ # Flatten out Union[Union[...], ...].
+ params = []
+ for p in parameters:
+ if isinstance(p, _Union) and p.__origin__ is Union:
+ params.extend(p.__args__)
+ elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union:
+ params.extend(p[1:])
+ else:
+ params.append(p)
+ # Weed out strict duplicates, preserving the first of each occurrence.
+ all_params = set(params)
+ if len(all_params) < len(params):
+ new_params = []
+ for t in params:
+ if t in all_params:
+ new_params.append(t)
+ all_params.remove(t)
+ params = new_params
+ assert not all_params, all_params
+ # Weed out subclasses.
+ # E.g. Union[int, Employee, Manager] == Union[int, Employee].
+ # If object is present it will be sole survivor among proper classes.
+ # Never discard type variables.
+ # (In particular, Union[str, AnyStr] != AnyStr.)
+ all_params = set(params)
+ for t1 in params:
+ if not isinstance(t1, type):
+ continue
+ if any(isinstance(t2, type) and issubclass(t1, t2)
+ for t2 in all_params - {t1}
+ if not (isinstance(t2, GenericMeta) and
+ t2.__origin__ is not None)):
+ all_params.remove(t1)
+ return tuple(t for t in params if t in all_params)
+
+
+def _check_generic(cls, parameters):
+ # Check correct count for parameters of a generic cls (internal helper).
+ if not cls.__parameters__:
+ raise TypeError("%s is not a generic class" % repr(cls))
+ alen = len(parameters)
+ elen = len(cls.__parameters__)
+ if alen != elen:
+ raise TypeError("Too %s parameters for %s; actual %s, expected %s" %
+ ("many" if alen > elen else "few", repr(cls), alen, elen))
+
+
+_cleanups = []
+
+
+def _tp_cache(func):
+ """Internal wrapper caching __getitem__ of generic types with a fallback to
+ original function for non-hashable arguments.
+ """
+
+ cached = functools.lru_cache()(func)
+ _cleanups.append(cached.cache_clear)
+
+ @functools.wraps(func)
+ def inner(*args, **kwds):
+ try:
+ return cached(*args, **kwds)
+ except TypeError:
+ pass # All real errors (not unhashable args) are raised below.
+ return func(*args, **kwds)
+ return inner
+
+
+class _Union(_FinalTypingBase, _root=True):
+ """Union type; Union[X, Y] means either X or Y.
+
+ To define a union, use e.g. Union[int, str]. Details:
+
+ - The arguments must be types and there must be at least one.
+
+ - None as an argument is a special case and is replaced by
+ type(None).
+
+ - Unions of unions are flattened, e.g.::
+
+ Union[Union[int, str], float] == Union[int, str, float]
+
+ - Unions of a single argument vanish, e.g.::
+
+ Union[int] == int # The constructor actually returns int
+
+ - Redundant arguments are skipped, e.g.::
+
+ Union[int, str, int] == Union[int, str]
+
+ - When comparing unions, the argument order is ignored, e.g.::
+
+ Union[int, str] == Union[str, int]
+
+ - When two arguments have a subclass relationship, the least
+ derived argument is kept, e.g.::
+
+ class Employee: pass
+ class Manager(Employee): pass
+ Union[int, Employee, Manager] == Union[int, Employee]
+ Union[Manager, int, Employee] == Union[int, Employee]
+ Union[Employee, Manager] == Employee
+
+ - Similar for object::
+
+ Union[int, object] == object
+
+ - You cannot subclass or instantiate a union.
+
+ - You can use Optional[X] as a shorthand for Union[X, None].
+ """
+
+ __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__')
+
+ def __new__(cls, parameters=None, origin=None, *args, _root=False):
+ self = super().__new__(cls, parameters, origin, *args, _root=_root)
+ if origin is None:
+ self.__parameters__ = None
+ self.__args__ = None
+ self.__origin__ = None
+ self.__tree_hash__ = hash(frozenset(('Union',)))
+ return self
+ if not isinstance(parameters, tuple):
+ raise TypeError("Expected parameters=<tuple>")
+ if origin is Union:
+ parameters = _remove_dups_flatten(parameters)
+ # It's not a union if there's only one type left.
+ if len(parameters) == 1:
+ return parameters[0]
+ self.__parameters__ = _type_vars(parameters)
+ self.__args__ = parameters
+ self.__origin__ = origin
+ # Pre-calculate the __hash__ on instantiation.
+ # This improves speed for complex substitutions.
+ subs_tree = self._subs_tree()
+ if isinstance(subs_tree, tuple):
+ self.__tree_hash__ = hash(frozenset(subs_tree))
+ else:
+ self.__tree_hash__ = hash(subs_tree)
+ return self
+
+ def _eval_type(self, globalns, localns):
+ if self.__args__ is None:
+ return self
+ ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__)
+ ev_origin = _eval_type(self.__origin__, globalns, localns)
+ if ev_args == self.__args__ and ev_origin == self.__origin__:
+ # Everything is already evaluated.
+ return self
+ return self.__class__(ev_args, ev_origin, _root=True)
+
+ def _get_type_vars(self, tvars):
+ if self.__origin__ and self.__parameters__:
+ _get_type_vars(self.__parameters__, tvars)
+
+ def __repr__(self):
+ if self.__origin__ is None:
+ return super().__repr__()
+ tree = self._subs_tree()
+ if not isinstance(tree, tuple):
+ return repr(tree)
+ return tree[0]._tree_repr(tree)
+
+ def _tree_repr(self, tree):
+ arg_list = []
+ for arg in tree[1:]:
+ if not isinstance(arg, tuple):
+ arg_list.append(_type_repr(arg))
+ else:
+ arg_list.append(arg[0]._tree_repr(arg))
+ return super().__repr__() + '[%s]' % ', '.join(arg_list)
+
+ @_tp_cache
+ def __getitem__(self, parameters):
+ if parameters == ():
+ raise TypeError("Cannot take a Union of no types.")
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+ if self.__origin__ is None:
+ msg = "Union[arg, ...]: each arg must be a type."
+ else:
+ msg = "Parameters to generic types must be types."
+ parameters = tuple(_type_check(p, msg) for p in parameters)
+ if self is not Union:
+ _check_generic(self, parameters)
+ return self.__class__(parameters, origin=self, _root=True)
+
+ def _subs_tree(self, tvars=None, args=None):
+ if self is Union:
+ return Union # Nothing to substitute
+ tree_args = _subs_tree(self, tvars, args)
+ tree_args = _remove_dups_flatten(tree_args)
+ if len(tree_args) == 1:
+ return tree_args[0] # Union of a single type is that type
+ return (Union,) + tree_args
+
+ def __eq__(self, other):
+ if isinstance(other, _Union):
+ return self.__tree_hash__ == other.__tree_hash__
+ elif self is not Union:
+ return self._subs_tree() == other
+ else:
+ return self is other
+
+ def __hash__(self):
+ return self.__tree_hash__
+
+ def __instancecheck__(self, obj):
+ raise TypeError("Unions cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Unions cannot be used with issubclass().")
+
+
+Union = _Union(_root=True)
+
+
+class _Optional(_FinalTypingBase, _root=True):
+ """Optional type.
+
+ Optional[X] is equivalent to Union[X, None].
+ """
+
+ __slots__ = ()
+
+ @_tp_cache
+ def __getitem__(self, arg):
+ arg = _type_check(arg, "Optional[t] requires a single type.")
+ return Union[arg, type(None)]
+
+
+Optional = _Optional(_root=True)
+
+
+def _next_in_mro(cls):
+ """Helper for Generic.__new__.
+
+ Returns the class after the last occurrence of Generic or
+ Generic[...] in cls.__mro__.
+ """
+ next_in_mro = object
+ # Look for the last occurrence of Generic or Generic[...].
+ for i, c in enumerate(cls.__mro__[:-1]):
+ if isinstance(c, GenericMeta) and c._gorg is Generic:
+ next_in_mro = cls.__mro__[i + 1]
+ return next_in_mro
+
+
+def _make_subclasshook(cls):
+ """Construct a __subclasshook__ callable that incorporates
+ the associated __extra__ class in subclass checks performed
+ against cls.
+ """
+ if isinstance(cls.__extra__, abc.ABCMeta):
+ # The logic mirrors that of ABCMeta.__subclasscheck__.
+ # Registered classes need not be checked here because
+ # cls and its extra share the same _abc_registry.
+ def __extrahook__(subclass):
+ res = cls.__extra__.__subclasshook__(subclass)
+ if res is not NotImplemented:
+ return res
+ if cls.__extra__ in subclass.__mro__:
+ return True
+ for scls in cls.__extra__.__subclasses__():
+ if isinstance(scls, GenericMeta):
+ continue
+ if issubclass(subclass, scls):
+ return True
+ return NotImplemented
+ else:
+ # For non-ABC extras we'll just call issubclass().
+ def __extrahook__(subclass):
+ if cls.__extra__ and issubclass(subclass, cls.__extra__):
+ return True
+ return NotImplemented
+ return __extrahook__
+
+
+def _no_slots_copy(dct):
+ """Internal helper: copy class __dict__ and clean slots class variables.
+ (They will be re-created if necessary by normal class machinery.)
+ """
+ dict_copy = dict(dct)
+ if '__slots__' in dict_copy:
+ for slot in dict_copy['__slots__']:
+ dict_copy.pop(slot, None)
+ return dict_copy
+
+
+class GenericMeta(TypingMeta, abc.ABCMeta):
+ """Metaclass for generic types.
+
+ This is a metaclass for typing.Generic and generic ABCs defined in
+ typing module. User defined subclasses of GenericMeta can override
+ __new__ and invoke super().__new__. Note that GenericMeta.__new__
+ has strict rules on what is allowed in its bases argument:
+ * plain Generic is disallowed in bases;
+ * Generic[...] should appear in bases at most once;
+ * if Generic[...] is present, then it should list all type variables
+ that appear in other bases.
+ In addition, type of all generic bases is erased, e.g., C[int] is
+ stripped to plain C.
+ """
+
+ def __new__(cls, name, bases, namespace,
+ tvars=None, args=None, origin=None, extra=None, orig_bases=None):
+ """Create a new generic class. GenericMeta.__new__ accepts
+ keyword arguments that are used for internal bookkeeping, therefore
+ an override should pass unused keyword arguments to super().
+ """
+ if tvars is not None:
+ # Called from __getitem__() below.
+ assert origin is not None
+ assert all(isinstance(t, TypeVar) for t in tvars), tvars
+ else:
+ # Called from class statement.
+ assert tvars is None, tvars
+ assert args is None, args
+ assert origin is None, origin
+
+ # Get the full set of tvars from the bases.
+ tvars = _type_vars(bases)
+ # Look for Generic[T1, ..., Tn].
+ # If found, tvars must be a subset of it.
+ # If not found, tvars is it.
+ # Also check for and reject plain Generic,
+ # and reject multiple Generic[...].
+ gvars = None
+ for base in bases:
+ if base is Generic:
+ raise TypeError("Cannot inherit from plain Generic")
+ if (isinstance(base, GenericMeta) and
+ base.__origin__ is Generic):
+ if gvars is not None:
+ raise TypeError(
+ "Cannot inherit from Generic[...] multiple types.")
+ gvars = base.__parameters__
+ if gvars is None:
+ gvars = tvars
+ else:
+ tvarset = set(tvars)
+ gvarset = set(gvars)
+ if not tvarset <= gvarset:
+ raise TypeError(
+ "Some type variables (%s) "
+ "are not listed in Generic[%s]" %
+ (", ".join(str(t) for t in tvars if t not in gvarset),
+ ", ".join(str(g) for g in gvars)))
+ tvars = gvars
+
+ initial_bases = bases
+ if extra is not None and type(extra) is abc.ABCMeta and extra not in bases:
+ bases = (extra,) + bases
+ bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases)
+
+ # remove bare Generic from bases if there are other generic bases
+ if any(isinstance(b, GenericMeta) and b is not Generic for b in bases):
+ bases = tuple(b for b in bases if b is not Generic)
+ namespace.update({'__origin__': origin, '__extra__': extra,
+ '_gorg': None if not origin else origin._gorg})
+ self = super().__new__(cls, name, bases, namespace, _root=True)
+ super(GenericMeta, self).__setattr__('_gorg',
+ self if not origin else origin._gorg)
+ self.__parameters__ = tvars
+ # Be prepared that GenericMeta will be subclassed by TupleMeta
+ # and CallableMeta, those two allow ..., (), or [] in __args___.
+ self.__args__ = tuple(... if a is _TypingEllipsis else
+ () if a is _TypingEmpty else
+ a for a in args) if args else None
+ # Speed hack (https://github.com/python/typing/issues/196).
+ self.__next_in_mro__ = _next_in_mro(self)
+ # Preserve base classes on subclassing (__bases__ are type erased now).
+ if orig_bases is None:
+ self.__orig_bases__ = initial_bases
+
+ # This allows unparameterized generic collections to be used
+ # with issubclass() and isinstance() in the same way as their
+ # collections.abc counterparts (e.g., isinstance([], Iterable)).
+ if (
+ '__subclasshook__' not in namespace and extra or
+ # allow overriding
+ getattr(self.__subclasshook__, '__name__', '') == '__extrahook__'
+ ):
+ self.__subclasshook__ = _make_subclasshook(self)
+ if isinstance(extra, abc.ABCMeta):
+ self._abc_registry = extra._abc_registry
+ self._abc_cache = extra._abc_cache
+ elif origin is not None:
+ self._abc_registry = origin._abc_registry
+ self._abc_cache = origin._abc_cache
+
+ if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2.
+ self.__qualname__ = origin.__qualname__
+ self.__tree_hash__ = (hash(self._subs_tree()) if origin else
+ super(GenericMeta, self).__hash__())
+ return self
+
+ # _abc_negative_cache and _abc_negative_cache_version
+ # realised as descriptors, since GenClass[t1, t2, ...] always
+ # share subclass info with GenClass.
+ # This is an important memory optimization.
+ @property
+ def _abc_negative_cache(self):
+ if isinstance(self.__extra__, abc.ABCMeta):
+ return self.__extra__._abc_negative_cache
+ return self._gorg._abc_generic_negative_cache
+
+ @_abc_negative_cache.setter
+ def _abc_negative_cache(self, value):
+ if self.__origin__ is None:
+ if isinstance(self.__extra__, abc.ABCMeta):
+ self.__extra__._abc_negative_cache = value
+ else:
+ self._abc_generic_negative_cache = value
+
+ @property
+ def _abc_negative_cache_version(self):
+ if isinstance(self.__extra__, abc.ABCMeta):
+ return self.__extra__._abc_negative_cache_version
+ return self._gorg._abc_generic_negative_cache_version
+
+ @_abc_negative_cache_version.setter
+ def _abc_negative_cache_version(self, value):
+ if self.__origin__ is None:
+ if isinstance(self.__extra__, abc.ABCMeta):
+ self.__extra__._abc_negative_cache_version = value
+ else:
+ self._abc_generic_negative_cache_version = value
+
+ def _get_type_vars(self, tvars):
+ if self.__origin__ and self.__parameters__:
+ _get_type_vars(self.__parameters__, tvars)
+
+ def _eval_type(self, globalns, localns):
+ ev_origin = (self.__origin__._eval_type(globalns, localns)
+ if self.__origin__ else None)
+ ev_args = tuple(_eval_type(a, globalns, localns) for a
+ in self.__args__) if self.__args__ else None
+ if ev_origin == self.__origin__ and ev_args == self.__args__:
+ return self
+ return self.__class__(self.__name__,
+ self.__bases__,
+ _no_slots_copy(self.__dict__),
+ tvars=_type_vars(ev_args) if ev_args else None,
+ args=ev_args,
+ origin=ev_origin,
+ extra=self.__extra__,
+ orig_bases=self.__orig_bases__)
+
+ def __repr__(self):
+ if self.__origin__ is None:
+ return super().__repr__()
+ return self._tree_repr(self._subs_tree())
+
+ def _tree_repr(self, tree):
+ arg_list = []
+ for arg in tree[1:]:
+ if arg == ():
+ arg_list.append('()')
+ elif not isinstance(arg, tuple):
+ arg_list.append(_type_repr(arg))
+ else:
+ arg_list.append(arg[0]._tree_repr(arg))
+ return super().__repr__() + '[%s]' % ', '.join(arg_list)
+
+ def _subs_tree(self, tvars=None, args=None):
+ if self.__origin__ is None:
+ return self
+ tree_args = _subs_tree(self, tvars, args)
+ return (self._gorg,) + tuple(tree_args)
+
+ def __eq__(self, other):
+ if not isinstance(other, GenericMeta):
+ return NotImplemented
+ if self.__origin__ is None or other.__origin__ is None:
+ return self is other
+ return self.__tree_hash__ == other.__tree_hash__
+
+ def __hash__(self):
+ return self.__tree_hash__
+
+ @_tp_cache
+ def __getitem__(self, params):
+ if not isinstance(params, tuple):
+ params = (params,)
+ if not params and self._gorg is not Tuple:
+ raise TypeError(
+ "Parameter list to %s[...] cannot be empty" % _qualname(self))
+ msg = "Parameters to generic types must be types."
+ params = tuple(_type_check(p, msg) for p in params)
+ if self is Generic:
+ # Generic can only be subscripted with unique type variables.
+ if not all(isinstance(p, TypeVar) for p in params):
+ raise TypeError(
+ "Parameters to Generic[...] must all be type variables")
+ if len(set(params)) != len(params):
+ raise TypeError(
+ "Parameters to Generic[...] must all be unique")
+ tvars = params
+ args = params
+ elif self in (Tuple, Callable):
+ tvars = _type_vars(params)
+ args = params
+ elif self is _Protocol:
+ # _Protocol is internal, don't check anything.
+ tvars = params
+ args = params
+ elif self.__origin__ in (Generic, _Protocol):
+ # Can't subscript Generic[...] or _Protocol[...].
+ raise TypeError("Cannot subscript already-subscripted %s" %
+ repr(self))
+ else:
+ # Subscripting a regular Generic subclass.
+ _check_generic(self, params)
+ tvars = _type_vars(params)
+ args = params
+
+ prepend = (self,) if self.__origin__ is None else ()
+ return self.__class__(self.__name__,
+ prepend + self.__bases__,
+ _no_slots_copy(self.__dict__),
+ tvars=tvars,
+ args=args,
+ origin=self,
+ extra=self.__extra__,
+ orig_bases=self.__orig_bases__)
+
+ def __subclasscheck__(self, cls):
+ if self.__origin__ is not None:
+ if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']:
+ raise TypeError("Parameterized generics cannot be used with class "
+ "or instance checks")
+ return False
+ if self is Generic:
+ raise TypeError("Class %r cannot be used with class "
+ "or instance checks" % self)
+ return super().__subclasscheck__(cls)
+
+ def __instancecheck__(self, instance):
+ # Since we extend ABC.__subclasscheck__ and
+ # ABC.__instancecheck__ inlines the cache checking done by the
+ # latter, we must extend __instancecheck__ too. For simplicity
+ # we just skip the cache check -- instance checks for generic
+ # classes are supposed to be rare anyways.
+ return issubclass(instance.__class__, self)
+
+ def __setattr__(self, attr, value):
+ # We consider all the subscripted generics as proxies for original class
+ if (
+ attr.startswith('__') and attr.endswith('__') or
+ attr.startswith('_abc_') or
+ self._gorg is None # The class is not fully created, see #typing/506
+ ):
+ super(GenericMeta, self).__setattr__(attr, value)
+ else:
+ super(GenericMeta, self._gorg).__setattr__(attr, value)
+
+
+# Prevent checks for Generic to crash when defining Generic.
+Generic = None
+
+
+def _generic_new(base_cls, cls, *args, **kwds):
+ # Assure type is erased on instantiation,
+ # but attempt to store it in __orig_class__
+ if cls.__origin__ is None:
+ if (base_cls.__new__ is object.__new__ and
+ cls.__init__ is not object.__init__):
+ return base_cls.__new__(cls)
+ else:
+ return base_cls.__new__(cls, *args, **kwds)
+ else:
+ origin = cls._gorg
+ if (base_cls.__new__ is object.__new__ and
+ cls.__init__ is not object.__init__):
+ obj = base_cls.__new__(origin)
+ else:
+ obj = base_cls.__new__(origin, *args, **kwds)
+ try:
+ obj.__orig_class__ = cls
+ except AttributeError:
+ pass
+ obj.__init__(*args, **kwds)
+ return obj
+
+
+class Generic(metaclass=GenericMeta):
+ """Abstract base class for generic types.
+
+ A generic type is typically declared by inheriting from
+ this class parameterized with one or more type variables.
+ For example, a generic mapping type might be defined as::
+
+ class Mapping(Generic[KT, VT]):
+ def __getitem__(self, key: KT) -> VT:
+ ...
+ # Etc.
+
+ This class can then be used as follows::
+
+ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
+ try:
+ return mapping[key]
+ except KeyError:
+ return default
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Generic:
+ raise TypeError("Type Generic cannot be instantiated; "
+ "it can be used only as a base class")
+ return _generic_new(cls.__next_in_mro__, cls, *args, **kwds)
+
+
+class _TypingEmpty:
+ """Internal placeholder for () or []. Used by TupleMeta and CallableMeta
+ to allow empty list/tuple in specific places, without allowing them
+ to sneak in where prohibited.
+ """
+
+
+class _TypingEllipsis:
+ """Internal placeholder for ... (ellipsis)."""
+
+
+class TupleMeta(GenericMeta):
+ """Metaclass for Tuple (internal)."""
+
+ @_tp_cache
+ def __getitem__(self, parameters):
+ if self.__origin__ is not None or self._gorg is not Tuple:
+ # Normal generic rules apply if this is not the first subscription
+ # or a subscription of a subclass.
+ return super().__getitem__(parameters)
+ if parameters == ():
+ return super().__getitem__((_TypingEmpty,))
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+ if len(parameters) == 2 and parameters[1] is ...:
+ msg = "Tuple[t, ...]: t must be a type."
+ p = _type_check(parameters[0], msg)
+ return super().__getitem__((p, _TypingEllipsis))
+ msg = "Tuple[t0, t1, ...]: each t must be a type."
+ parameters = tuple(_type_check(p, msg) for p in parameters)
+ return super().__getitem__(parameters)
+
+ def __instancecheck__(self, obj):
+ if self.__args__ is None:
+ return isinstance(obj, tuple)
+ raise TypeError("Parameterized Tuple cannot be used "
+ "with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ if self.__args__ is None:
+ return issubclass(cls, tuple)
+ raise TypeError("Parameterized Tuple cannot be used "
+ "with issubclass().")
+
+
+class Tuple(tuple, extra=tuple, metaclass=TupleMeta):
+ """Tuple type; Tuple[X, Y] is the cross-product type of X and Y.
+
+ Example: Tuple[T1, T2] is a tuple of two elements corresponding
+ to type variables T1 and T2. Tuple[int, float, str] is a tuple
+ of an int, a float and a string.
+
+ To specify a variable-length tuple of homogeneous type, use Tuple[T, ...].
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Tuple:
+ raise TypeError("Type Tuple cannot be instantiated; "
+ "use tuple() instead")
+ return _generic_new(tuple, cls, *args, **kwds)
+
+
+class CallableMeta(GenericMeta):
+ """Metaclass for Callable (internal)."""
+
+ def __repr__(self):
+ if self.__origin__ is None:
+ return super().__repr__()
+ return self._tree_repr(self._subs_tree())
+
+ def _tree_repr(self, tree):
+ if self._gorg is not Callable:
+ return super()._tree_repr(tree)
+ # For actual Callable (not its subclass) we override
+ # super()._tree_repr() for nice formatting.
+ arg_list = []
+ for arg in tree[1:]:
+ if not isinstance(arg, tuple):
+ arg_list.append(_type_repr(arg))
+ else:
+ arg_list.append(arg[0]._tree_repr(arg))
+ if arg_list[0] == '...':
+ return repr(tree[0]) + '[..., %s]' % arg_list[1]
+ return (repr(tree[0]) +
+ '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1]))
+
+ def __getitem__(self, parameters):
+ """A thin wrapper around __getitem_inner__ to provide the latter
+ with hashable arguments to improve speed.
+ """
+
+ if self.__origin__ is not None or self._gorg is not Callable:
+ return super().__getitem__(parameters)
+ if not isinstance(parameters, tuple) or len(parameters) != 2:
+ raise TypeError("Callable must be used as "
+ "Callable[[arg, ...], result].")
+ args, result = parameters
+ if args is Ellipsis:
+ parameters = (Ellipsis, result)
+ else:
+ if not isinstance(args, list):
+ raise TypeError("Callable[args, result]: args must be a list."
+ " Got %.100r." % (args,))
+ parameters = (tuple(args), result)
+ return self.__getitem_inner__(parameters)
+
+ @_tp_cache
+ def __getitem_inner__(self, parameters):
+ args, result = parameters
+ msg = "Callable[args, result]: result must be a type."
+ result = _type_check(result, msg)
+ if args is Ellipsis:
+ return super().__getitem__((_TypingEllipsis, result))
+ msg = "Callable[[arg, ...], result]: each arg must be a type."
+ args = tuple(_type_check(arg, msg) for arg in args)
+ parameters = args + (result,)
+ return super().__getitem__(parameters)
+
+
+class Callable(extra=collections_abc.Callable, metaclass=CallableMeta):
+ """Callable type; Callable[[int], str] is a function of (int) -> str.
+
+ The subscription syntax must always be used with exactly two
+ values: the argument list and the return type. The argument list
+ must be a list of types or ellipsis; the return type must be a single type.
+
+ There is no syntax to indicate optional or keyword arguments,
+ such function types are rarely used as callback types.
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Callable:
+ raise TypeError("Type Callable cannot be instantiated; "
+ "use a non-abstract subclass instead")
+ return _generic_new(cls.__next_in_mro__, cls, *args, **kwds)
+
+
+class _ClassVar(_FinalTypingBase, _root=True):
+ """Special type construct to mark class variables.
+
+ An annotation wrapped in ClassVar indicates that a given
+ attribute is intended to be used as a class variable and
+ should not be set on instances of that class. Usage::
+
+ class Starship:
+ stats: ClassVar[Dict[str, int]] = {} # class variable
+ damage: int = 10 # instance variable
+
+ ClassVar accepts only types and cannot be further subscribed.
+
+ Note that ClassVar is not a class itself, and should not
+ be used with isinstance() or issubclass().
+ """
+
+ __slots__ = ('__type__',)
+
+ def __init__(self, tp=None, **kwds):
+ self.__type__ = tp
+
+ def __getitem__(self, item):
+ cls = type(self)
+ if self.__type__ is None:
+ return cls(_type_check(item,
+ '{} accepts only single type.'.format(cls.__name__[1:])),
+ _root=True)
+ raise TypeError('{} cannot be further subscripted'
+ .format(cls.__name__[1:]))
+
+ def _eval_type(self, globalns, localns):
+ new_tp = _eval_type(self.__type__, globalns, localns)
+ if new_tp == self.__type__:
+ return self
+ return type(self)(new_tp, _root=True)
+
+ def __repr__(self):
+ r = super().__repr__()
+ if self.__type__ is not None:
+ r += '[{}]'.format(_type_repr(self.__type__))
+ return r
+
+ def __hash__(self):
+ return hash((type(self).__name__, self.__type__))
+
+ def __eq__(self, other):
+ if not isinstance(other, _ClassVar):
+ return NotImplemented
+ if self.__type__ is not None:
+ return self.__type__ == other.__type__
+ return self is other
+
+
+ClassVar = _ClassVar(_root=True)
+
+
+def cast(typ, val):
+ """Cast a value to a type.
+
+ This returns the value unchanged. To the type checker this
+ signals that the return value has the designated type, but at
+ runtime we intentionally don't check anything (we want this
+ to be as fast as possible).
+ """
+ return val
+
+
+def _get_defaults(func):
+ """Internal helper to extract the default arguments, by name."""
+ try:
+ code = func.__code__
+ except AttributeError:
+ # Some built-in functions don't have __code__, __defaults__, etc.
+ return {}
+ pos_count = code.co_argcount
+ arg_names = code.co_varnames
+ arg_names = arg_names[:pos_count]
+ defaults = func.__defaults__ or ()
+ kwdefaults = func.__kwdefaults__
+ res = dict(kwdefaults) if kwdefaults else {}
+ pos_offset = pos_count - len(defaults)
+ for name, value in zip(arg_names[pos_offset:], defaults):
+ assert name not in res
+ res[name] = value
+ return res
+
+
+_allowed_types = (types.FunctionType, types.BuiltinFunctionType,
+ types.MethodType, types.ModuleType,
+ WrapperDescriptorType, MethodWrapperType, MethodDescriptorType)
+
+
+def get_type_hints(obj, globalns=None, localns=None):
+ """Return type hints for an object.
+
+ This is often the same as obj.__annotations__, but it handles
+ forward references encoded as string literals, and if necessary
+ adds Optional[t] if a default value equal to None is set.
+
+ The argument may be a module, class, method, or function. The annotations
+ are returned as a dictionary. For classes, annotations include also
+ inherited members.
+
+ TypeError is raised if the argument is not of a type that can contain
+ annotations, and an empty dictionary is returned if no annotations are
+ present.
+
+ BEWARE -- the behavior of globalns and localns is counterintuitive
+ (unless you are familiar with how eval() and exec() work). The
+ search order is locals first, then globals.
+
+ - If no dict arguments are passed, an attempt is made to use the
+ globals from obj (or the respective module's globals for classes),
+ and these are also used as the locals. If the object does not appear
+ to have globals, an empty dictionary is used.
+
+ - If one dict argument is passed, it is used for both globals and
+ locals.
+
+ - If two dict arguments are passed, they specify globals and
+ locals, respectively.
+ """
+
+ if getattr(obj, '__no_type_check__', None):
+ return {}
+ # Classes require a special treatment.
+ if isinstance(obj, type):
+ hints = {}
+ for base in reversed(obj.__mro__):
+ if globalns is None:
+ base_globals = sys.modules[base.__module__].__dict__
+ else:
+ base_globals = globalns
+ ann = base.__dict__.get('__annotations__', {})
+ for name, value in ann.items():
+ if value is None:
+ value = type(None)
+ if isinstance(value, str):
+ value = _ForwardRef(value)
+ value = _eval_type(value, base_globals, localns)
+ hints[name] = value
+ return hints
+
+ if globalns is None:
+ if isinstance(obj, types.ModuleType):
+ globalns = obj.__dict__
+ else:
+ globalns = getattr(obj, '__globals__', {})
+ if localns is None:
+ localns = globalns
+ elif localns is None:
+ localns = globalns
+ hints = getattr(obj, '__annotations__', None)
+ if hints is None:
+ # Return empty annotations for something that _could_ have them.
+ if isinstance(obj, _allowed_types):
+ return {}
+ else:
+ raise TypeError('{!r} is not a module, class, method, '
+ 'or function.'.format(obj))
+ defaults = _get_defaults(obj)
+ hints = dict(hints)
+ for name, value in hints.items():
+ if value is None:
+ value = type(None)
+ if isinstance(value, str):
+ value = _ForwardRef(value)
+ value = _eval_type(value, globalns, localns)
+ if name in defaults and defaults[name] is None:
+ value = Optional[value]
+ hints[name] = value
+ return hints
+
+
+def no_type_check(arg):
+ """Decorator to indicate that annotations are not type hints.
+
+ The argument must be a class or function; if it is a class, it
+ applies recursively to all methods and classes defined in that class
+ (but not to methods defined in its superclasses or subclasses).
+
+ This mutates the function(s) or class(es) in place.
+ """
+ if isinstance(arg, type):
+ arg_attrs = arg.__dict__.copy()
+ for attr, val in arg.__dict__.items():
+ if val in arg.__bases__ + (arg,):
+ arg_attrs.pop(attr)
+ for obj in arg_attrs.values():
+ if isinstance(obj, types.FunctionType):
+ obj.__no_type_check__ = True
+ if isinstance(obj, type):
+ no_type_check(obj)
+ try:
+ arg.__no_type_check__ = True
+ except TypeError: # built-in classes
+ pass
+ return arg
+
+
+def no_type_check_decorator(decorator):
+ """Decorator to give another decorator the @no_type_check effect.
+
+ This wraps the decorator with something that wraps the decorated
+ function in @no_type_check.
+ """
+
+ @functools.wraps(decorator)
+ def wrapped_decorator(*args, **kwds):
+ func = decorator(*args, **kwds)
+ func = no_type_check(func)
+ return func
+
+ return wrapped_decorator
+
+
+def _overload_dummy(*args, **kwds):
+ """Helper for @overload to raise when called."""
+ raise NotImplementedError(
+ "You should not call an overloaded function. "
+ "A series of @overload-decorated functions "
+ "outside a stub module should always be followed "
+ "by an implementation that is not @overload-ed.")
+
+
+def overload(func):
+ """Decorator for overloaded functions/methods.
+
+ In a stub file, place two or more stub definitions for the same
+ function in a row, each decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+
+ In a non-stub file (i.e. a regular .py file), do the same but
+ follow it with an implementation. The implementation should *not*
+ be decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+ def utf8(value):
+ # implementation goes here
+ """
+ return _overload_dummy
+
+
+class _ProtocolMeta(GenericMeta):
+ """Internal metaclass for _Protocol.
+
+ This exists so _Protocol classes can be generic without deriving
+ from Generic.
+ """
+
+ def __instancecheck__(self, obj):
+ if _Protocol not in self.__bases__:
+ return super().__instancecheck__(obj)
+ raise TypeError("Protocols cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ if not self._is_protocol:
+ # No structural checks since this isn't a protocol.
+ return NotImplemented
+
+ if self is _Protocol:
+ # Every class is a subclass of the empty protocol.
+ return True
+
+ # Find all attributes defined in the protocol.
+ attrs = self._get_protocol_attrs()
+
+ for attr in attrs:
+ if not any(attr in d.__dict__ for d in cls.__mro__):
+ return False
+ return True
+
+ def _get_protocol_attrs(self):
+ # Get all Protocol base classes.
+ protocol_bases = []
+ for c in self.__mro__:
+ if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol':
+ protocol_bases.append(c)
+
+ # Get attributes included in protocol.
+ attrs = set()
+ for base in protocol_bases:
+ for attr in base.__dict__.keys():
+ # Include attributes not defined in any non-protocol bases.
+ for c in self.__mro__:
+ if (c is not base and attr in c.__dict__ and
+ not getattr(c, '_is_protocol', False)):
+ break
+ else:
+ if (not attr.startswith('_abc_') and
+ attr != '__abstractmethods__' and
+ attr != '__annotations__' and
+ attr != '__weakref__' and
+ attr != '_is_protocol' and
+ attr != '_gorg' and
+ attr != '__dict__' and
+ attr != '__args__' and
+ attr != '__slots__' and
+ attr != '_get_protocol_attrs' and
+ attr != '__next_in_mro__' and
+ attr != '__parameters__' and
+ attr != '__origin__' and
+ attr != '__orig_bases__' and
+ attr != '__extra__' and
+ attr != '__tree_hash__' and
+ attr != '__module__'):
+ attrs.add(attr)
+
+ return attrs
+
+
+class _Protocol(metaclass=_ProtocolMeta):
+ """Internal base class for protocol classes.
+
+ This implements a simple-minded structural issubclass check
+ (similar but more general than the one-offs in collections.abc
+ such as Hashable).
+ """
+
+ __slots__ = ()
+
+ _is_protocol = True
+
+
+# Various ABCs mimicking those in collections.abc.
+# A few are simply re-exported for completeness.
+
+Hashable = collections_abc.Hashable # Not generic.
+
+
+if hasattr(collections_abc, 'Awaitable'):
+ class Awaitable(Generic[T_co], extra=collections_abc.Awaitable):
+ __slots__ = ()
+
+ __all__.append('Awaitable')
+
+
+if hasattr(collections_abc, 'Coroutine'):
+ class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co],
+ extra=collections_abc.Coroutine):
+ __slots__ = ()
+
+ __all__.append('Coroutine')
+
+
+if hasattr(collections_abc, 'AsyncIterable'):
+
+ class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable):
+ __slots__ = ()
+
+ class AsyncIterator(AsyncIterable[T_co],
+ extra=collections_abc.AsyncIterator):
+ __slots__ = ()
+
+ __all__.append('AsyncIterable')
+ __all__.append('AsyncIterator')
+
+
+class Iterable(Generic[T_co], extra=collections_abc.Iterable):
+ __slots__ = ()
+
+
+class Iterator(Iterable[T_co], extra=collections_abc.Iterator):
+ __slots__ = ()
+
+
+class SupportsInt(_Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __int__(self) -> int:
+ pass
+
+
+class SupportsFloat(_Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __float__(self) -> float:
+ pass
+
+
+class SupportsComplex(_Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __complex__(self) -> complex:
+ pass
+
+
+class SupportsBytes(_Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __bytes__(self) -> bytes:
+ pass
+
+
+class SupportsIndex(_Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __index__(self) -> int:
+ pass
+
+
+class SupportsAbs(_Protocol[T_co]):
+ __slots__ = ()
+
+ @abstractmethod
+ def __abs__(self) -> T_co:
+ pass
+
+
+class SupportsRound(_Protocol[T_co]):
+ __slots__ = ()
+
+ @abstractmethod
+ def __round__(self, ndigits: int = 0) -> T_co:
+ pass
+
+
+if hasattr(collections_abc, 'Reversible'):
+ class Reversible(Iterable[T_co], extra=collections_abc.Reversible):
+ __slots__ = ()
+else:
+ class Reversible(_Protocol[T_co]):
+ __slots__ = ()
+
+ @abstractmethod
+ def __reversed__(self) -> 'Iterator[T_co]':
+ pass
+
+
+Sized = collections_abc.Sized # Not generic.
+
+
+class Container(Generic[T_co], extra=collections_abc.Container):
+ __slots__ = ()
+
+
+if hasattr(collections_abc, 'Collection'):
+ class Collection(Sized, Iterable[T_co], Container[T_co],
+ extra=collections_abc.Collection):
+ __slots__ = ()
+
+ __all__.append('Collection')
+
+
+# Callable was defined earlier.
+
+if hasattr(collections_abc, 'Collection'):
+ class AbstractSet(Collection[T_co],
+ extra=collections_abc.Set):
+ __slots__ = ()
+else:
+ class AbstractSet(Sized, Iterable[T_co], Container[T_co],
+ extra=collections_abc.Set):
+ __slots__ = ()
+
+
+class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet):
+ __slots__ = ()
+
+
+# NOTE: It is only covariant in the value type.
+if hasattr(collections_abc, 'Collection'):
+ class Mapping(Collection[KT], Generic[KT, VT_co],
+ extra=collections_abc.Mapping):
+ __slots__ = ()
+else:
+ class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co],
+ extra=collections_abc.Mapping):
+ __slots__ = ()
+
+
+class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping):
+ __slots__ = ()
+
+
+if hasattr(collections_abc, 'Reversible'):
+ if hasattr(collections_abc, 'Collection'):
+ class Sequence(Reversible[T_co], Collection[T_co],
+ extra=collections_abc.Sequence):
+ __slots__ = ()
+ else:
+ class Sequence(Sized, Reversible[T_co], Container[T_co],
+ extra=collections_abc.Sequence):
+ __slots__ = ()
+else:
+ class Sequence(Sized, Iterable[T_co], Container[T_co],
+ extra=collections_abc.Sequence):
+ __slots__ = ()
+
+
+class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence):
+ __slots__ = ()
+
+
+class ByteString(Sequence[int], extra=collections_abc.ByteString):
+ __slots__ = ()
+
+
+class List(list, MutableSequence[T], extra=list):
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is List:
+ raise TypeError("Type List cannot be instantiated; "
+ "use list() instead")
+ return _generic_new(list, cls, *args, **kwds)
+
+
+class Deque(collections.deque, MutableSequence[T], extra=collections.deque):
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Deque:
+ return collections.deque(*args, **kwds)
+ return _generic_new(collections.deque, cls, *args, **kwds)
+
+
+class Set(set, MutableSet[T], extra=set):
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Set:
+ raise TypeError("Type Set cannot be instantiated; "
+ "use set() instead")
+ return _generic_new(set, cls, *args, **kwds)
+
+
+class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset):
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is FrozenSet:
+ raise TypeError("Type FrozenSet cannot be instantiated; "
+ "use frozenset() instead")
+ return _generic_new(frozenset, cls, *args, **kwds)
+
+
+class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView):
+ __slots__ = ()
+
+
+class KeysView(MappingView[KT], AbstractSet[KT],
+ extra=collections_abc.KeysView):
+ __slots__ = ()
+
+
+class ItemsView(MappingView[Tuple[KT, VT_co]],
+ AbstractSet[Tuple[KT, VT_co]],
+ Generic[KT, VT_co],
+ extra=collections_abc.ItemsView):
+ __slots__ = ()
+
+
+class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView):
+ __slots__ = ()
+
+
+if hasattr(contextlib, 'AbstractContextManager'):
+ class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
+ __slots__ = ()
+else:
+ class ContextManager(Generic[T_co]):
+ __slots__ = ()
+
+ def __enter__(self):
+ return self
+
+ @abc.abstractmethod
+ def __exit__(self, exc_type, exc_value, traceback):
+ return None
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is ContextManager:
+ # In Python 3.6+, it is possible to set a method to None to
+ # explicitly indicate that the class does not implement an ABC
+ # (https://bugs.python.org/issue25958), but we do not support
+ # that pattern here because this fallback class is only used
+ # in Python 3.5 and earlier.
+ if (any("__enter__" in B.__dict__ for B in C.__mro__) and
+ any("__exit__" in B.__dict__ for B in C.__mro__)):
+ return True
+ return NotImplemented
+
+
+if hasattr(contextlib, 'AbstractAsyncContextManager'):
+ class AsyncContextManager(Generic[T_co],
+ extra=contextlib.AbstractAsyncContextManager):
+ __slots__ = ()
+
+ __all__.append('AsyncContextManager')
+elif sys.version_info[:2] >= (3, 5):
+ exec("""
+class AsyncContextManager(Generic[T_co]):
+ __slots__ = ()
+
+ async def __aenter__(self):
+ return self
+
+ @abc.abstractmethod
+ async def __aexit__(self, exc_type, exc_value, traceback):
+ return None
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is AsyncContextManager:
+ if sys.version_info[:2] >= (3, 6):
+ return _collections_abc._check_methods(C, "__aenter__", "__aexit__")
+ if (any("__aenter__" in B.__dict__ for B in C.__mro__) and
+ any("__aexit__" in B.__dict__ for B in C.__mro__)):
+ return True
+ return NotImplemented
+
+__all__.append('AsyncContextManager')
+""")
+
+
+class Dict(dict, MutableMapping[KT, VT], extra=dict):
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Dict:
+ raise TypeError("Type Dict cannot be instantiated; "
+ "use dict() instead")
+ return _generic_new(dict, cls, *args, **kwds)
+
+
+class DefaultDict(collections.defaultdict, MutableMapping[KT, VT],
+ extra=collections.defaultdict):
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is DefaultDict:
+ return collections.defaultdict(*args, **kwds)
+ return _generic_new(collections.defaultdict, cls, *args, **kwds)
+
+
+class Counter(collections.Counter, Dict[T, int], extra=collections.Counter):
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Counter:
+ return collections.Counter(*args, **kwds)
+ return _generic_new(collections.Counter, cls, *args, **kwds)
+
+
+if hasattr(collections, 'ChainMap'):
+ # ChainMap only exists in 3.3+
+ __all__.append('ChainMap')
+
+ class ChainMap(collections.ChainMap, MutableMapping[KT, VT],
+ extra=collections.ChainMap):
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is ChainMap:
+ return collections.ChainMap(*args, **kwds)
+ return _generic_new(collections.ChainMap, cls, *args, **kwds)
+
+
+# Determine what base class to use for Generator.
+if hasattr(collections_abc, 'Generator'):
+ # Sufficiently recent versions of 3.5 have a Generator ABC.
+ _G_base = collections_abc.Generator
+else:
+ # Fall back on the exact type.
+ _G_base = types.GeneratorType
+
+
+class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co],
+ extra=_G_base):
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Generator:
+ raise TypeError("Type Generator cannot be instantiated; "
+ "create a subclass instead")
+ return _generic_new(_G_base, cls, *args, **kwds)
+
+
+if hasattr(collections_abc, 'AsyncGenerator'):
+ class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra],
+ extra=collections_abc.AsyncGenerator):
+ __slots__ = ()
+
+ __all__.append('AsyncGenerator')
+
+
+# Internal type variable used for Type[].
+CT_co = TypeVar('CT_co', covariant=True, bound=type)
+
+
+# This is not a real generic class. Don't use outside annotations.
+class Type(Generic[CT_co], extra=type):
+ """A special construct usable to annotate class objects.
+
+ For example, suppose we have the following classes::
+
+ class User: ... # Abstract base for User classes
+ class BasicUser(User): ...
+ class ProUser(User): ...
+ class TeamUser(User): ...
+
+ And a function that takes a class argument that's a subclass of
+ User and returns an instance of the corresponding class::
+
+ U = TypeVar('U', bound=User)
+ def new_user(user_class: Type[U]) -> U:
+ user = user_class()
+ # (Here we could write the user object to a database)
+ return user
+
+ joe = new_user(BasicUser)
+
+ At this point the type checker knows that joe has type BasicUser.
+ """
+
+ __slots__ = ()
+
+
+def _make_nmtuple(name, types):
+ msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type"
+ types = [(n, _type_check(t, msg)) for n, t in types]
+ nm_tpl = collections.namedtuple(name, [n for n, t in types])
+ # Prior to PEP 526, only _field_types attribute was assigned.
+ # Now, both __annotations__ and _field_types are used to maintain compatibility.
+ nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types)
+ try:
+ nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ pass
+ return nm_tpl
+
+
+_PY36 = sys.version_info[:2] >= (3, 6)
+
+# attributes prohibited to set in NamedTuple class syntax
+_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__',
+ '_fields', '_field_defaults', '_field_types',
+ '_make', '_replace', '_asdict', '_source')
+
+_special = ('__module__', '__name__', '__qualname__', '__annotations__')
+
+
+class NamedTupleMeta(type):
+
+ def __new__(cls, typename, bases, ns):
+ if ns.get('_root', False):
+ return super().__new__(cls, typename, bases, ns)
+ if not _PY36:
+ raise TypeError("Class syntax for NamedTuple is only supported"
+ " in Python 3.6+")
+ types = ns.get('__annotations__', {})
+ nm_tpl = _make_nmtuple(typename, types.items())
+ defaults = []
+ defaults_dict = {}
+ for field_name in types:
+ if field_name in ns:
+ default_value = ns[field_name]
+ defaults.append(default_value)
+ defaults_dict[field_name] = default_value
+ elif defaults:
+ raise TypeError("Non-default namedtuple field {field_name} cannot "
+ "follow default field(s) {default_names}"
+ .format(field_name=field_name,
+ default_names=', '.join(defaults_dict.keys())))
+ nm_tpl.__new__.__annotations__ = collections.OrderedDict(types)
+ nm_tpl.__new__.__defaults__ = tuple(defaults)
+ nm_tpl._field_defaults = defaults_dict
+ # update from user namespace without overriding special namedtuple attributes
+ for key in ns:
+ if key in _prohibited:
+ raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
+ elif key not in _special and key not in nm_tpl._fields:
+ setattr(nm_tpl, key, ns[key])
+ return nm_tpl
+
+
+class NamedTuple(metaclass=NamedTupleMeta):
+ """Typed version of namedtuple.
+
+ Usage in Python versions >= 3.6::
+
+ class Employee(NamedTuple):
+ name: str
+ id: int
+
+ This is equivalent to::
+
+ Employee = collections.namedtuple('Employee', ['name', 'id'])
+
+ The resulting class has extra __annotations__ and _field_types
+ attributes, giving an ordered dict mapping field names to types.
+ __annotations__ should be preferred, while _field_types
+ is kept to maintain pre PEP 526 compatibility. (The field names
+ are in the _fields attribute, which is part of the namedtuple
+ API.) Alternative equivalent keyword syntax is also accepted::
+
+ Employee = NamedTuple('Employee', name=str, id=int)
+
+ In Python versions <= 3.5 use::
+
+ Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+ """
+ _root = True
+
+ def __new__(self, typename, fields=None, **kwargs):
+ if kwargs and not _PY36:
+ raise TypeError("Keyword syntax for NamedTuple is only supported"
+ " in Python 3.6+")
+ if fields is None:
+ fields = kwargs.items()
+ elif kwargs:
+ raise TypeError("Either list of fields or keywords"
+ " can be provided to NamedTuple, not both")
+ return _make_nmtuple(typename, fields)
+
+
+def NewType(name, tp):
+ """NewType creates simple unique types with almost zero
+ runtime overhead. NewType(name, tp) is considered a subtype of tp
+ by static type checkers. At runtime, NewType(name, tp) returns
+ a dummy function that simply returns its argument. Usage::
+
+ UserId = NewType('UserId', int)
+
+ def name_by_id(user_id: UserId) -> str:
+ ...
+
+ UserId('user') # Fails type check
+
+ name_by_id(42) # Fails type check
+ name_by_id(UserId(42)) # OK
+
+ num = UserId(5) + 1 # type: int
+ """
+
+ def new_type(x):
+ return x
+
+ new_type.__name__ = name
+ new_type.__supertype__ = tp
+ return new_type
+
+
+# Python-version-specific alias (Python 2: unicode; Python 3: str)
+Text = str
+
+
+# Constant that's True when type checking, but False here.
+TYPE_CHECKING = False
+
+
+class IO(Generic[AnyStr]):
+ """Generic base class for TextIO and BinaryIO.
+
+ This is an abstract, generic version of the return of open().
+
+ NOTE: This does not distinguish between the different possible
+ classes (text vs. binary, read vs. write vs. read/write,
+ append-only, unbuffered). The TextIO and BinaryIO subclasses
+ below capture the distinctions between text vs. binary, which is
+ pervasive in the interface; however we currently do not offer a
+ way to track the other distinctions in the type system.
+ """
+
+ __slots__ = ()
+
+ @abstractproperty
+ def mode(self) -> str:
+ pass
+
+ @abstractproperty
+ def name(self) -> str:
+ pass
+
+ @abstractmethod
+ def close(self) -> None:
+ pass
+
+ @abstractproperty
+ def closed(self) -> bool:
+ pass
+
+ @abstractmethod
+ def fileno(self) -> int:
+ pass
+
+ @abstractmethod
+ def flush(self) -> None:
+ pass
+
+ @abstractmethod
+ def isatty(self) -> bool:
+ pass
+
+ @abstractmethod
+ def read(self, n: int = -1) -> AnyStr:
+ pass
+
+ @abstractmethod
+ def readable(self) -> bool:
+ pass
+
+ @abstractmethod
+ def readline(self, limit: int = -1) -> AnyStr:
+ pass
+
+ @abstractmethod
+ def readlines(self, hint: int = -1) -> List[AnyStr]:
+ pass
+
+ @abstractmethod
+ def seek(self, offset: int, whence: int = 0) -> int:
+ pass
+
+ @abstractmethod
+ def seekable(self) -> bool:
+ pass
+
+ @abstractmethod
+ def tell(self) -> int:
+ pass
+
+ @abstractmethod
+ def truncate(self, size: int = None) -> int:
+ pass
+
+ @abstractmethod
+ def writable(self) -> bool:
+ pass
+
+ @abstractmethod
+ def write(self, s: AnyStr) -> int:
+ pass
+
+ @abstractmethod
+ def writelines(self, lines: List[AnyStr]) -> None:
+ pass
+
+ @abstractmethod
+ def __enter__(self) -> 'IO[AnyStr]':
+ pass
+
+ @abstractmethod
+ def __exit__(self, type, value, traceback) -> None:
+ pass
+
+
+class BinaryIO(IO[bytes]):
+ """Typed version of the return of open() in binary mode."""
+
+ __slots__ = ()
+
+ @abstractmethod
+ def write(self, s: Union[bytes, bytearray]) -> int:
+ pass
+
+ @abstractmethod
+ def __enter__(self) -> 'BinaryIO':
+ pass
+
+
+class TextIO(IO[str]):
+ """Typed version of the return of open() in text mode."""
+
+ __slots__ = ()
+
+ @abstractproperty
+ def buffer(self) -> BinaryIO:
+ pass
+
+ @abstractproperty
+ def encoding(self) -> str:
+ pass
+
+ @abstractproperty
+ def errors(self) -> Optional[str]:
+ pass
+
+ @abstractproperty
+ def line_buffering(self) -> bool:
+ pass
+
+ @abstractproperty
+ def newlines(self) -> Any:
+ pass
+
+ @abstractmethod
+ def __enter__(self) -> 'TextIO':
+ pass
+
+
+class io:
+ """Wrapper namespace for IO generic classes."""
+
+ __all__ = ['IO', 'TextIO', 'BinaryIO']
+ IO = IO
+ TextIO = TextIO
+ BinaryIO = BinaryIO
+
+
+io.__name__ = __name__ + '.io'
+sys.modules[io.__name__] = io
+
+
+Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')),
+ lambda p: p.pattern)
+Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')),
+ lambda m: m.re.pattern)
+
+
+class re:
+ """Wrapper namespace for re type aliases."""
+
+ __all__ = ['Pattern', 'Match']
+ Pattern = Pattern
+ Match = Match
+
+
+re.__name__ = __name__ + '.re'
+sys.modules[re.__name__] = re
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/LICENSE
new file mode 100644
index 0000000000..583f9f6e61
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/LICENSE
@@ -0,0 +1,254 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC. Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team. In October of the same
+year, the PythonLabs team moved to Digital Creations (now Zope
+Corporation, see http://www.zope.com). In 2001, the Python Software
+Foundation (PSF, see http://www.python.org/psf/) was formed, a
+non-profit organization created specifically to own Python-related
+Intellectual Property. Zope Corporation is a sponsoring member of
+the PSF.
+
+All Python releases are Open Source (see http://www.opensource.org for
+the Open Source Definition). Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+ Release Derived Year Owner GPL-
+ from compatible? (1)
+
+ 0.9.0 thru 1.2 1991-1995 CWI yes
+ 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
+ 1.6 1.5.2 2000 CNRI no
+ 2.0 1.6 2000 BeOpen.com no
+ 1.6.1 1.6 2001 CNRI yes (2)
+ 2.1 2.0+1.6.1 2001 PSF no
+ 2.0.1 2.0+1.6.1 2001 PSF yes
+ 2.1.1 2.1+2.0.1 2001 PSF yes
+ 2.1.2 2.1.1 2002 PSF yes
+ 2.1.3 2.1.2 2002 PSF yes
+ 2.2 and above 2.1.1 2001-now PSF yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+ the GPL. All Python licenses, unlike the GPL, let you distribute
+ a modified version without making your changes open source. The
+ GPL-compatible licenses make it possible to combine Python with
+ other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+ because its license has a choice of law clause. According to
+ CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+ is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are
+retained in Python alone or in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions. Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee. This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party. As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee. Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement. This Agreement together with
+Python 1.6.1 may be located on the Internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013. This
+Agreement may also be obtained from a proxy server on the Internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee. This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+ ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands. All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/METADATA
new file mode 100644
index 0000000000..d685344892
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/METADATA
@@ -0,0 +1,50 @@
+Metadata-Version: 2.1
+Name: typing
+Version: 3.7.4.3
+Summary: Type Hints for Python
+Home-page: https://docs.python.org/3/library/typing.html
+Author: Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Ivan Levkivskyi
+Author-email: jukka.lehtosalo@iki.fi
+License: PSF
+Project-URL: Source, https://github.com/python/typing
+Keywords: typing function annotations type hints hinting checking checker typehints typehinting typechecking backport
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Software Development
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+
+Typing -- Type Hints for Python
+
+This is a backport of the standard library typing module to Python
+versions older than 3.5. (See note below for newer versions.)
+
+Typing defines a standard notation for Python function and variable
+type annotations. The notation can be used for documenting code in a
+concise, standard format, and it has been designed to also be used by
+static and runtime type checkers, static analyzers, IDEs and other
+tools.
+
+NOTE: in Python 3.5 and later, the typing module lives in the stdlib,
+and installing this package has NO EFFECT, because stdlib takes higher
+precedence than the installation directory. To get a newer version of
+the typing module in Python 3.5 or later, you have to upgrade to a
+newer Python (bugfix) version. For example, typing in Python 3.6.0 is
+missing the definition of 'Type' -- upgrading to 3.6.2 will fix this.
+
+Also note that most improvements to the typing module in Python 3.7
+will not be included in this package, since Python 3.7 has some
+built-in support that is not present in older versions (See PEP 560.)
+
+For package maintainers, it is preferred to use
+``typing;python_version<"3.5"`` if your package requires it to support
+earlier Python versions. This will avoid shadowing the stdlib typing
+module when your package is installed via ``pip install -t .`` on
+Python 3.5 or later.
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/RECORD
new file mode 100644
index 0000000000..3d79342232
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/RECORD
@@ -0,0 +1,6 @@
+typing.py,sha256=yP2fxy8eprK-cHMe9bAcvU7QL7n_YGtoTFOG3bsWVJQ,84492
+typing-3.7.4.3.dist-info/LICENSE,sha256=_xfOlOECAk3raHc-scx0ynbaTmWPNzUx8Kwi1oprsa0,12755
+typing-3.7.4.3.dist-info/METADATA,sha256=t3uvms3cJatf6uhsaHM3PP7HWbkjVUh4AE9tb8xCSsQ,2258
+typing-3.7.4.3.dist-info/WHEEL,sha256=CbUdLTqD3-4zWemf83rgR_2_MC4TeXw9qXwrXte5w4w,92
+typing-3.7.4.3.dist-info/top_level.txt,sha256=oG8QCMTRcfcgGpEVbdwBU2DM8MthjmZSDaaQ6WWHx4o,7
+typing-3.7.4.3.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/WHEEL
new file mode 100644
index 0000000000..2b098df983
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.34.2)
+Root-Is-Purelib: true
+Tag: py2-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/top_level.txt
new file mode 100644
index 0000000000..c997f364b4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info/top_level.txt
@@ -0,0 +1 @@
+typing
diff --git a/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing.py b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing.py
new file mode 100644
index 0000000000..dd16d9af96
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/typing-3.7.4.3-py2-none-any/typing.py
@@ -0,0 +1,2550 @@
+from __future__ import absolute_import, unicode_literals
+
+import abc
+from abc import abstractmethod, abstractproperty
+import collections
+import functools
+import re as stdlib_re # Avoid confusion with the re we export.
+import sys
+import types
+import copy
+try:
+ import collections.abc as collections_abc
+except ImportError:
+ import collections as collections_abc # Fallback for PY3.2.
+
+
+# Please keep __all__ alphabetized within each category.
+__all__ = [
+ # Super-special typing primitives.
+ 'Any',
+ 'Callable',
+ 'ClassVar',
+ 'Final',
+ 'Generic',
+ 'Literal',
+ 'Optional',
+ 'Protocol',
+ 'Tuple',
+ 'Type',
+ 'TypeVar',
+ 'Union',
+
+ # ABCs (from collections.abc).
+ 'AbstractSet', # collections.abc.Set.
+ 'GenericMeta', # subclass of abc.ABCMeta and a metaclass
+ # for 'Generic' and ABCs below.
+ 'ByteString',
+ 'Container',
+ 'ContextManager',
+ 'Hashable',
+ 'ItemsView',
+ 'Iterable',
+ 'Iterator',
+ 'KeysView',
+ 'Mapping',
+ 'MappingView',
+ 'MutableMapping',
+ 'MutableSequence',
+ 'MutableSet',
+ 'Sequence',
+ 'Sized',
+ 'ValuesView',
+
+ # Structural checks, a.k.a. protocols.
+ 'Reversible',
+ 'SupportsAbs',
+ 'SupportsComplex',
+ 'SupportsFloat',
+ 'SupportsIndex',
+ 'SupportsInt',
+
+ # Concrete collection types.
+ 'Counter',
+ 'Deque',
+ 'Dict',
+ 'DefaultDict',
+ 'List',
+ 'Set',
+ 'FrozenSet',
+ 'NamedTuple', # Not really a type.
+ 'TypedDict', # Not really a type.
+ 'Generator',
+
+ # One-off things.
+ 'AnyStr',
+ 'cast',
+ 'final',
+ 'get_type_hints',
+ 'NewType',
+ 'no_type_check',
+ 'no_type_check_decorator',
+ 'NoReturn',
+ 'overload',
+ 'runtime_checkable',
+ 'Text',
+ 'TYPE_CHECKING',
+]
+
+# The pseudo-submodules 're' and 'io' are part of the public
+# namespace, but excluded from __all__ because they might stomp on
+# legitimate imports of those modules.
+
+
+def _qualname(x):
+ if sys.version_info[:2] >= (3, 3):
+ return x.__qualname__
+ else:
+ # Fall back to just name.
+ return x.__name__
+
+
+def _trim_name(nm):
+ whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase')
+ if nm.startswith('_') and nm not in whitelist:
+ nm = nm[1:]
+ return nm
+
+
+class TypingMeta(type):
+ """Metaclass for most types defined in typing module
+ (not a part of public API).
+
+ This also defines a dummy constructor (all the work for most typing
+ constructs is done in __new__) and a nicer repr().
+ """
+
+ _is_protocol = False
+
+ def __new__(cls, name, bases, namespace):
+ return super(TypingMeta, cls).__new__(cls, str(name), bases, namespace)
+
+ @classmethod
+ def assert_no_subclassing(cls, bases):
+ for base in bases:
+ if isinstance(base, cls):
+ raise TypeError("Cannot subclass %s" %
+ (', '.join(map(_type_repr, bases)) or '()'))
+
+ def __init__(self, *args, **kwds):
+ pass
+
+ def _eval_type(self, globalns, localns):
+ """Override this in subclasses to interpret forward references.
+
+ For example, List['C'] is internally stored as
+ List[_ForwardRef('C')], which should evaluate to List[C],
+ where C is an object found in globalns or localns (searching
+ localns first, of course).
+ """
+ return self
+
+ def _get_type_vars(self, tvars):
+ pass
+
+ def __repr__(self):
+ qname = _trim_name(_qualname(self))
+ return '%s.%s' % (self.__module__, qname)
+
+
+class _TypingBase(object):
+ """Internal indicator of special typing constructs."""
+ __metaclass__ = TypingMeta
+ __slots__ = ('__weakref__',)
+
+ def __init__(self, *args, **kwds):
+ pass
+
+ def __new__(cls, *args, **kwds):
+ """Constructor.
+
+ This only exists to give a better error message in case
+ someone tries to subclass a special typing object (not a good idea).
+ """
+ if (len(args) == 3 and
+ isinstance(args[0], str) and
+ isinstance(args[1], tuple)):
+ # Close enough.
+ raise TypeError("Cannot subclass %r" % cls)
+ return super(_TypingBase, cls).__new__(cls)
+
+ # Things that are not classes also need these.
+ def _eval_type(self, globalns, localns):
+ return self
+
+ def _get_type_vars(self, tvars):
+ pass
+
+ def __repr__(self):
+ cls = type(self)
+ qname = _trim_name(_qualname(cls))
+ return '%s.%s' % (cls.__module__, qname)
+
+ def __call__(self, *args, **kwds):
+ raise TypeError("Cannot instantiate %r" % type(self))
+
+
+class _FinalTypingBase(_TypingBase):
+ """Internal mix-in class to prevent instantiation.
+
+ Prevents instantiation unless _root=True is given in class call.
+ It is used to create pseudo-singleton instances Any, Union, Optional, etc.
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ self = super(_FinalTypingBase, cls).__new__(cls, *args, **kwds)
+ if '_root' in kwds and kwds['_root'] is True:
+ return self
+ raise TypeError("Cannot instantiate %r" % cls)
+
+ def __reduce__(self):
+ return _trim_name(type(self).__name__)
+
+
+class _ForwardRef(_TypingBase):
+ """Internal wrapper to hold a forward reference."""
+
+ __slots__ = ('__forward_arg__', '__forward_code__',
+ '__forward_evaluated__', '__forward_value__')
+
+ def __init__(self, arg):
+ super(_ForwardRef, self).__init__(arg)
+ if not isinstance(arg, basestring):
+ raise TypeError('Forward reference must be a string -- got %r' % (arg,))
+ try:
+ code = compile(arg, '<string>', 'eval')
+ except SyntaxError:
+ raise SyntaxError('Forward reference must be an expression -- got %r' %
+ (arg,))
+ self.__forward_arg__ = arg
+ self.__forward_code__ = code
+ self.__forward_evaluated__ = False
+ self.__forward_value__ = None
+
+ def _eval_type(self, globalns, localns):
+ if not self.__forward_evaluated__ or localns is not globalns:
+ if globalns is None and localns is None:
+ globalns = localns = {}
+ elif globalns is None:
+ globalns = localns
+ elif localns is None:
+ localns = globalns
+ self.__forward_value__ = _type_check(
+ eval(self.__forward_code__, globalns, localns),
+ "Forward references must evaluate to types.")
+ self.__forward_evaluated__ = True
+ return self.__forward_value__
+
+ def __eq__(self, other):
+ if not isinstance(other, _ForwardRef):
+ return NotImplemented
+ return (self.__forward_arg__ == other.__forward_arg__ and
+ self.__forward_value__ == other.__forward_value__)
+
+ def __hash__(self):
+ return hash((self.__forward_arg__, self.__forward_value__))
+
+ def __instancecheck__(self, obj):
+ raise TypeError("Forward references cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Forward references cannot be used with issubclass().")
+
+ def __repr__(self):
+ return '_ForwardRef(%r)' % (self.__forward_arg__,)
+
+
+class _TypeAlias(_TypingBase):
+ """Internal helper class for defining generic variants of concrete types.
+
+ Note that this is not a type; let's call it a pseudo-type. It cannot
+ be used in instance and subclass checks in parameterized form, i.e.
+ ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning
+ ``False``.
+ """
+
+ __slots__ = ('name', 'type_var', 'impl_type', 'type_checker')
+
+ def __init__(self, name, type_var, impl_type, type_checker):
+ """Initializer.
+
+ Args:
+ name: The name, e.g. 'Pattern'.
+ type_var: The type parameter, e.g. AnyStr, or the
+ specific type, e.g. str.
+ impl_type: The implementation type.
+ type_checker: Function that takes an impl_type instance.
+ and returns a value that should be a type_var instance.
+ """
+ assert isinstance(name, basestring), repr(name)
+ assert isinstance(impl_type, type), repr(impl_type)
+ assert not isinstance(impl_type, TypingMeta), repr(impl_type)
+ assert isinstance(type_var, (type, _TypingBase)), repr(type_var)
+ self.name = name
+ self.type_var = type_var
+ self.impl_type = impl_type
+ self.type_checker = type_checker
+
+ def __repr__(self):
+ return "%s[%s]" % (self.name, _type_repr(self.type_var))
+
+ def __getitem__(self, parameter):
+ if not isinstance(self.type_var, TypeVar):
+ raise TypeError("%s cannot be further parameterized." % self)
+ if self.type_var.__constraints__ and isinstance(parameter, type):
+ if not issubclass(parameter, self.type_var.__constraints__):
+ raise TypeError("%s is not a valid substitution for %s." %
+ (parameter, self.type_var))
+ if isinstance(parameter, TypeVar) and parameter is not self.type_var:
+ raise TypeError("%s cannot be re-parameterized." % self)
+ return self.__class__(self.name, parameter,
+ self.impl_type, self.type_checker)
+
+ def __eq__(self, other):
+ if not isinstance(other, _TypeAlias):
+ return NotImplemented
+ return self.name == other.name and self.type_var == other.type_var
+
+ def __hash__(self):
+ return hash((self.name, self.type_var))
+
+ def __instancecheck__(self, obj):
+ if not isinstance(self.type_var, TypeVar):
+ raise TypeError("Parameterized type aliases cannot be used "
+ "with isinstance().")
+ return isinstance(obj, self.impl_type)
+
+ def __subclasscheck__(self, cls):
+ if not isinstance(self.type_var, TypeVar):
+ raise TypeError("Parameterized type aliases cannot be used "
+ "with issubclass().")
+ return issubclass(cls, self.impl_type)
+
+
+def _get_type_vars(types, tvars):
+ for t in types:
+ if isinstance(t, TypingMeta) or isinstance(t, _TypingBase):
+ t._get_type_vars(tvars)
+
+
+def _type_vars(types):
+ tvars = []
+ _get_type_vars(types, tvars)
+ return tuple(tvars)
+
+
+def _eval_type(t, globalns, localns):
+ if isinstance(t, TypingMeta) or isinstance(t, _TypingBase):
+ return t._eval_type(globalns, localns)
+ return t
+
+
+def _type_check(arg, msg):
+ """Check that the argument is a type, and return it (internal helper).
+
+ As a special case, accept None and return type(None) instead.
+ Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable.
+
+ The msg argument is a human-readable error message, e.g.
+
+ "Union[arg, ...]: arg should be a type."
+
+ We append the repr() of the actual value (truncated to 100 chars).
+ """
+ if arg is None:
+ return type(None)
+ if isinstance(arg, basestring):
+ arg = _ForwardRef(arg)
+ if (
+ isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or
+ not isinstance(arg, (type, _TypingBase)) and not callable(arg)
+ ):
+ raise TypeError(msg + " Got %.100r." % (arg,))
+ # Bare Union etc. are not valid as type arguments
+ if (
+ type(arg).__name__ in ('_Union', '_Optional') and
+ not getattr(arg, '__origin__', None) or
+ isinstance(arg, TypingMeta) and arg._gorg in (Generic, Protocol)
+ ):
+ raise TypeError("Plain %s is not valid as type argument" % arg)
+ return arg
+
+
+def _type_repr(obj):
+ """Return the repr() of an object, special-casing types (internal helper).
+
+ If obj is a type, we return a shorter version than the default
+ type.__repr__, based on the module and qualified name, which is
+ typically enough to uniquely identify a type. For everything
+ else, we fall back on repr(obj).
+ """
+ if isinstance(obj, type) and not isinstance(obj, TypingMeta):
+ if obj.__module__ == '__builtin__':
+ return _qualname(obj)
+ return '%s.%s' % (obj.__module__, _qualname(obj))
+ if obj is Ellipsis:
+ return '...'
+ if isinstance(obj, types.FunctionType):
+ return obj.__name__
+ return repr(obj)
+
+
+class ClassVarMeta(TypingMeta):
+ """Metaclass for _ClassVar"""
+
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ self = super(ClassVarMeta, cls).__new__(cls, name, bases, namespace)
+ return self
+
+
+class _ClassVar(_FinalTypingBase):
+ """Special type construct to mark class variables.
+
+ An annotation wrapped in ClassVar indicates that a given
+ attribute is intended to be used as a class variable and
+ should not be set on instances of that class. Usage::
+
+ class Starship:
+ stats = {} # type: ClassVar[Dict[str, int]] # class variable
+ damage = 10 # type: int # instance variable
+
+ ClassVar accepts only types and cannot be further subscribed.
+
+ Note that ClassVar is not a class itself, and should not
+ be used with isinstance() or issubclass().
+ """
+
+ __metaclass__ = ClassVarMeta
+ __slots__ = ('__type__',)
+
+ def __init__(self, tp=None, _root=False):
+ self.__type__ = tp
+
+ def __getitem__(self, item):
+ cls = type(self)
+ if self.__type__ is None:
+ return cls(_type_check(item,
+ '{} accepts only types.'.format(cls.__name__[1:])),
+ _root=True)
+ raise TypeError('{} cannot be further subscripted'
+ .format(cls.__name__[1:]))
+
+ def _eval_type(self, globalns, localns):
+ return type(self)(_eval_type(self.__type__, globalns, localns),
+ _root=True)
+
+ def __repr__(self):
+ r = super(_ClassVar, self).__repr__()
+ if self.__type__ is not None:
+ r += '[{}]'.format(_type_repr(self.__type__))
+ return r
+
+ def __hash__(self):
+ return hash((type(self).__name__, self.__type__))
+
+ def __eq__(self, other):
+ if not isinstance(other, _ClassVar):
+ return NotImplemented
+ if self.__type__ is not None:
+ return self.__type__ == other.__type__
+ return self is other
+
+
+ClassVar = _ClassVar(_root=True)
+
+
+class _FinalMeta(TypingMeta):
+ """Metaclass for _Final"""
+
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ self = super(_FinalMeta, cls).__new__(cls, name, bases, namespace)
+ return self
+
+
+class _Final(_FinalTypingBase):
+ """A special typing construct to indicate that a name
+ cannot be re-assigned or overridden in a subclass.
+ For example:
+
+ MAX_SIZE: Final = 9000
+ MAX_SIZE += 1 # Error reported by type checker
+
+ class Connection:
+ TIMEOUT: Final[int] = 10
+ class FastConnector(Connection):
+ TIMEOUT = 1 # Error reported by type checker
+
+ There is no runtime checking of these properties.
+ """
+
+ __metaclass__ = _FinalMeta
+ __slots__ = ('__type__',)
+
+ def __init__(self, tp=None, **kwds):
+ self.__type__ = tp
+
+ def __getitem__(self, item):
+ cls = type(self)
+ if self.__type__ is None:
+ return cls(_type_check(item,
+ '{} accepts only single type.'.format(cls.__name__[1:])),
+ _root=True)
+ raise TypeError('{} cannot be further subscripted'
+ .format(cls.__name__[1:]))
+
+ def _eval_type(self, globalns, localns):
+ new_tp = _eval_type(self.__type__, globalns, localns)
+ if new_tp == self.__type__:
+ return self
+ return type(self)(new_tp, _root=True)
+
+ def __repr__(self):
+ r = super(_Final, self).__repr__()
+ if self.__type__ is not None:
+ r += '[{}]'.format(_type_repr(self.__type__))
+ return r
+
+ def __hash__(self):
+ return hash((type(self).__name__, self.__type__))
+
+ def __eq__(self, other):
+ if not isinstance(other, _Final):
+ return NotImplemented
+ if self.__type__ is not None:
+ return self.__type__ == other.__type__
+ return self is other
+
+
+Final = _Final(_root=True)
+
+
+def final(f):
+ """This decorator can be used to indicate to type checkers that
+ the decorated method cannot be overridden, and decorated class
+ cannot be subclassed. For example:
+
+ class Base:
+ @final
+ def done(self) -> None:
+ ...
+ class Sub(Base):
+ def done(self) -> None: # Error reported by type checker
+ ...
+ @final
+ class Leaf:
+ ...
+ class Other(Leaf): # Error reported by type checker
+ ...
+
+ There is no runtime checking of these properties.
+ """
+ return f
+
+
+class _LiteralMeta(TypingMeta):
+ """Metaclass for _Literal"""
+
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ self = super(_LiteralMeta, cls).__new__(cls, name, bases, namespace)
+ return self
+
+
+class _Literal(_FinalTypingBase):
+ """A type that can be used to indicate to type checkers that the
+ corresponding value has a value literally equivalent to the
+ provided parameter. For example:
+
+ var: Literal[4] = 4
+
+ The type checker understands that 'var' is literally equal to the
+ value 4 and no other value.
+
+ Literal[...] cannot be subclassed. There is no runtime checking
+ verifying that the parameter is actually a value instead of a type.
+ """
+
+ __metaclass__ = _LiteralMeta
+ __slots__ = ('__values__',)
+
+ def __init__(self, values=None, **kwds):
+ self.__values__ = values
+
+ def __getitem__(self, item):
+ cls = type(self)
+ if self.__values__ is None:
+ if not isinstance(item, tuple):
+ item = (item,)
+ return cls(values=item,
+ _root=True)
+ raise TypeError('{} cannot be further subscripted'
+ .format(cls.__name__[1:]))
+
+ def _eval_type(self, globalns, localns):
+ return self
+
+ def __repr__(self):
+ r = super(_Literal, self).__repr__()
+ if self.__values__ is not None:
+ r += '[{}]'.format(', '.join(map(_type_repr, self.__values__)))
+ return r
+
+ def __hash__(self):
+ return hash((type(self).__name__, self.__values__))
+
+ def __eq__(self, other):
+ if not isinstance(other, _Literal):
+ return NotImplemented
+ if self.__values__ is not None:
+ return self.__values__ == other.__values__
+ return self is other
+
+
+Literal = _Literal(_root=True)
+
+
+class AnyMeta(TypingMeta):
+ """Metaclass for Any."""
+
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ self = super(AnyMeta, cls).__new__(cls, name, bases, namespace)
+ return self
+
+
+class _Any(_FinalTypingBase):
+ """Special type indicating an unconstrained type.
+
+ - Any is compatible with every type.
+ - Any assumed to have all methods.
+ - All values assumed to be instances of Any.
+
+ Note that all the above statements are true from the point of view of
+ static type checkers. At runtime, Any should not be used with instance
+ or class checks.
+ """
+ __metaclass__ = AnyMeta
+ __slots__ = ()
+
+ def __instancecheck__(self, obj):
+ raise TypeError("Any cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Any cannot be used with issubclass().")
+
+
+Any = _Any(_root=True)
+
+
+class NoReturnMeta(TypingMeta):
+ """Metaclass for NoReturn."""
+
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ self = super(NoReturnMeta, cls).__new__(cls, name, bases, namespace)
+ return self
+
+
+class _NoReturn(_FinalTypingBase):
+ """Special type indicating functions that never return.
+ Example::
+
+ from typing import NoReturn
+
+ def stop() -> NoReturn:
+ raise Exception('no way')
+
+ This type is invalid in other positions, e.g., ``List[NoReturn]``
+ will fail in static type checkers.
+ """
+ __metaclass__ = NoReturnMeta
+ __slots__ = ()
+
+ def __instancecheck__(self, obj):
+ raise TypeError("NoReturn cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("NoReturn cannot be used with issubclass().")
+
+
+NoReturn = _NoReturn(_root=True)
+
+
+class TypeVarMeta(TypingMeta):
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ return super(TypeVarMeta, cls).__new__(cls, name, bases, namespace)
+
+
+class TypeVar(_TypingBase):
+ """Type variable.
+
+ Usage::
+
+ T = TypeVar('T') # Can be anything
+ A = TypeVar('A', str, bytes) # Must be str or bytes
+
+ Type variables exist primarily for the benefit of static type
+ checkers. They serve as the parameters for generic types as well
+ as for generic function definitions. See class Generic for more
+ information on generic types. Generic functions work as follows:
+
+ def repeat(x: T, n: int) -> List[T]:
+ '''Return a list containing n references to x.'''
+ return [x]*n
+
+ def longest(x: A, y: A) -> A:
+ '''Return the longest of two strings.'''
+ return x if len(x) >= len(y) else y
+
+ The latter example's signature is essentially the overloading
+ of (str, str) -> str and (bytes, bytes) -> bytes. Also note
+ that if the arguments are instances of some subclass of str,
+ the return type is still plain str.
+
+ At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError.
+
+ Type variables defined with covariant=True or contravariant=True
+ can be used do declare covariant or contravariant generic types.
+ See PEP 484 for more details. By default generic types are invariant
+ in all type variables.
+
+ Type variables can be introspected. e.g.:
+
+ T.__name__ == 'T'
+ T.__constraints__ == ()
+ T.__covariant__ == False
+ T.__contravariant__ = False
+ A.__constraints__ == (str, bytes)
+ """
+
+ __metaclass__ = TypeVarMeta
+ __slots__ = ('__name__', '__bound__', '__constraints__',
+ '__covariant__', '__contravariant__')
+
+ def __init__(self, name, *constraints, **kwargs):
+ super(TypeVar, self).__init__(name, *constraints, **kwargs)
+ bound = kwargs.get('bound', None)
+ covariant = kwargs.get('covariant', False)
+ contravariant = kwargs.get('contravariant', False)
+ self.__name__ = name
+ if covariant and contravariant:
+ raise ValueError("Bivariant types are not supported.")
+ self.__covariant__ = bool(covariant)
+ self.__contravariant__ = bool(contravariant)
+ if constraints and bound is not None:
+ raise TypeError("Constraints cannot be combined with bound=...")
+ if constraints and len(constraints) == 1:
+ raise TypeError("A single constraint is not allowed")
+ msg = "TypeVar(name, constraint, ...): constraints must be types."
+ self.__constraints__ = tuple(_type_check(t, msg) for t in constraints)
+ if bound:
+ self.__bound__ = _type_check(bound, "Bound must be a type.")
+ else:
+ self.__bound__ = None
+
+ def _get_type_vars(self, tvars):
+ if self not in tvars:
+ tvars.append(self)
+
+ def __repr__(self):
+ if self.__covariant__:
+ prefix = '+'
+ elif self.__contravariant__:
+ prefix = '-'
+ else:
+ prefix = '~'
+ return prefix + self.__name__
+
+ def __instancecheck__(self, instance):
+ raise TypeError("Type variables cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Type variables cannot be used with issubclass().")
+
+
+# Some unconstrained type variables. These are used by the container types.
+# (These are not for export.)
+T = TypeVar('T') # Any type.
+KT = TypeVar('KT') # Key type.
+VT = TypeVar('VT') # Value type.
+T_co = TypeVar('T_co', covariant=True) # Any type covariant containers.
+V_co = TypeVar('V_co', covariant=True) # Any type covariant containers.
+VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers.
+T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.
+
+# A useful type variable with constraints. This represents string types.
+# (This one *is* for export!)
+AnyStr = TypeVar('AnyStr', bytes, unicode)
+
+
+def _replace_arg(arg, tvars, args):
+ """An internal helper function: replace arg if it is a type variable
+ found in tvars with corresponding substitution from args or
+ with corresponding substitution sub-tree if arg is a generic type.
+ """
+
+ if tvars is None:
+ tvars = []
+ if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)):
+ return arg._subs_tree(tvars, args)
+ if isinstance(arg, TypeVar):
+ for i, tvar in enumerate(tvars):
+ if arg == tvar:
+ return args[i]
+ return arg
+
+
+# Special typing constructs Union, Optional, Generic, Callable and Tuple
+# use three special attributes for internal bookkeeping of generic types:
+# * __parameters__ is a tuple of unique free type parameters of a generic
+# type, for example, Dict[T, T].__parameters__ == (T,);
+# * __origin__ keeps a reference to a type that was subscripted,
+# e.g., Union[T, int].__origin__ == Union;
+# * __args__ is a tuple of all arguments used in subscripting,
+# e.g., Dict[T, int].__args__ == (T, int).
+
+
+def _subs_tree(cls, tvars=None, args=None):
+ """An internal helper function: calculate substitution tree
+ for generic cls after replacing its type parameters with
+ substitutions in tvars -> args (if any).
+ Repeat the same following __origin__'s.
+
+ Return a list of arguments with all possible substitutions
+ performed. Arguments that are generic classes themselves are represented
+ as tuples (so that no new classes are created by this function).
+ For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)]
+ """
+
+ if cls.__origin__ is None:
+ return cls
+ # Make of chain of origins (i.e. cls -> cls.__origin__)
+ current = cls.__origin__
+ orig_chain = []
+ while current.__origin__ is not None:
+ orig_chain.append(current)
+ current = current.__origin__
+ # Replace type variables in __args__ if asked ...
+ tree_args = []
+ for arg in cls.__args__:
+ tree_args.append(_replace_arg(arg, tvars, args))
+ # ... then continue replacing down the origin chain.
+ for ocls in orig_chain:
+ new_tree_args = []
+ for arg in ocls.__args__:
+ new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args))
+ tree_args = new_tree_args
+ return tree_args
+
+
+def _remove_dups_flatten(parameters):
+ """An internal helper for Union creation and substitution: flatten Union's
+ among parameters, then remove duplicates and strict subclasses.
+ """
+
+ # Flatten out Union[Union[...], ...].
+ params = []
+ for p in parameters:
+ if isinstance(p, _Union) and p.__origin__ is Union:
+ params.extend(p.__args__)
+ elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union:
+ params.extend(p[1:])
+ else:
+ params.append(p)
+ # Weed out strict duplicates, preserving the first of each occurrence.
+ all_params = set(params)
+ if len(all_params) < len(params):
+ new_params = []
+ for t in params:
+ if t in all_params:
+ new_params.append(t)
+ all_params.remove(t)
+ params = new_params
+ assert not all_params, all_params
+ # Weed out subclasses.
+ # E.g. Union[int, Employee, Manager] == Union[int, Employee].
+ # If object is present it will be sole survivor among proper classes.
+ # Never discard type variables.
+ # (In particular, Union[str, AnyStr] != AnyStr.)
+ all_params = set(params)
+ for t1 in params:
+ if not isinstance(t1, type):
+ continue
+ if any(isinstance(t2, type) and issubclass(t1, t2)
+ for t2 in all_params - {t1}
+ if not (isinstance(t2, GenericMeta) and
+ t2.__origin__ is not None)):
+ all_params.remove(t1)
+ return tuple(t for t in params if t in all_params)
+
+
+def _check_generic(cls, parameters):
+ # Check correct count for parameters of a generic cls (internal helper).
+ if not cls.__parameters__:
+ raise TypeError("%s is not a generic class" % repr(cls))
+ alen = len(parameters)
+ elen = len(cls.__parameters__)
+ if alen != elen:
+ raise TypeError("Too %s parameters for %s; actual %s, expected %s" %
+ ("many" if alen > elen else "few", repr(cls), alen, elen))
+
+
+_cleanups = []
+
+
+def _tp_cache(func):
+ maxsize = 128
+ cache = {}
+ _cleanups.append(cache.clear)
+
+ @functools.wraps(func)
+ def inner(*args):
+ key = args
+ try:
+ return cache[key]
+ except TypeError:
+ # Assume it's an unhashable argument.
+ return func(*args)
+ except KeyError:
+ value = func(*args)
+ if len(cache) >= maxsize:
+ # If the cache grows too much, just start over.
+ cache.clear()
+ cache[key] = value
+ return value
+
+ return inner
+
+
+class UnionMeta(TypingMeta):
+ """Metaclass for Union."""
+
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ return super(UnionMeta, cls).__new__(cls, name, bases, namespace)
+
+
+class _Union(_FinalTypingBase):
+ """Union type; Union[X, Y] means either X or Y.
+
+ To define a union, use e.g. Union[int, str]. Details:
+
+ - The arguments must be types and there must be at least one.
+
+ - None as an argument is a special case and is replaced by
+ type(None).
+
+ - Unions of unions are flattened, e.g.::
+
+ Union[Union[int, str], float] == Union[int, str, float]
+
+ - Unions of a single argument vanish, e.g.::
+
+ Union[int] == int # The constructor actually returns int
+
+ - Redundant arguments are skipped, e.g.::
+
+ Union[int, str, int] == Union[int, str]
+
+ - When comparing unions, the argument order is ignored, e.g.::
+
+ Union[int, str] == Union[str, int]
+
+ - When two arguments have a subclass relationship, the least
+ derived argument is kept, e.g.::
+
+ class Employee: pass
+ class Manager(Employee): pass
+ Union[int, Employee, Manager] == Union[int, Employee]
+ Union[Manager, int, Employee] == Union[int, Employee]
+ Union[Employee, Manager] == Employee
+
+ - Similar for object::
+
+ Union[int, object] == object
+
+ - You cannot subclass or instantiate a union.
+
+ - You can use Optional[X] as a shorthand for Union[X, None].
+ """
+
+ __metaclass__ = UnionMeta
+ __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__')
+
+ def __new__(cls, parameters=None, origin=None, *args, **kwds):
+ self = super(_Union, cls).__new__(cls, parameters, origin, *args, **kwds)
+ if origin is None:
+ self.__parameters__ = None
+ self.__args__ = None
+ self.__origin__ = None
+ self.__tree_hash__ = hash(frozenset(('Union',)))
+ return self
+ if not isinstance(parameters, tuple):
+ raise TypeError("Expected parameters=<tuple>")
+ if origin is Union:
+ parameters = _remove_dups_flatten(parameters)
+ # It's not a union if there's only one type left.
+ if len(parameters) == 1:
+ return parameters[0]
+ self.__parameters__ = _type_vars(parameters)
+ self.__args__ = parameters
+ self.__origin__ = origin
+ # Pre-calculate the __hash__ on instantiation.
+ # This improves speed for complex substitutions.
+ subs_tree = self._subs_tree()
+ if isinstance(subs_tree, tuple):
+ self.__tree_hash__ = hash(frozenset(subs_tree))
+ else:
+ self.__tree_hash__ = hash(subs_tree)
+ return self
+
+ def _eval_type(self, globalns, localns):
+ if self.__args__ is None:
+ return self
+ ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__)
+ ev_origin = _eval_type(self.__origin__, globalns, localns)
+ if ev_args == self.__args__ and ev_origin == self.__origin__:
+ # Everything is already evaluated.
+ return self
+ return self.__class__(ev_args, ev_origin, _root=True)
+
+ def _get_type_vars(self, tvars):
+ if self.__origin__ and self.__parameters__:
+ _get_type_vars(self.__parameters__, tvars)
+
+ def __repr__(self):
+ if self.__origin__ is None:
+ return super(_Union, self).__repr__()
+ tree = self._subs_tree()
+ if not isinstance(tree, tuple):
+ return repr(tree)
+ return tree[0]._tree_repr(tree)
+
+ def _tree_repr(self, tree):
+ arg_list = []
+ for arg in tree[1:]:
+ if not isinstance(arg, tuple):
+ arg_list.append(_type_repr(arg))
+ else:
+ arg_list.append(arg[0]._tree_repr(arg))
+ return super(_Union, self).__repr__() + '[%s]' % ', '.join(arg_list)
+
+ @_tp_cache
+ def __getitem__(self, parameters):
+ if parameters == ():
+ raise TypeError("Cannot take a Union of no types.")
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+ if self.__origin__ is None:
+ msg = "Union[arg, ...]: each arg must be a type."
+ else:
+ msg = "Parameters to generic types must be types."
+ parameters = tuple(_type_check(p, msg) for p in parameters)
+ if self is not Union:
+ _check_generic(self, parameters)
+ return self.__class__(parameters, origin=self, _root=True)
+
+ def _subs_tree(self, tvars=None, args=None):
+ if self is Union:
+ return Union # Nothing to substitute
+ tree_args = _subs_tree(self, tvars, args)
+ tree_args = _remove_dups_flatten(tree_args)
+ if len(tree_args) == 1:
+ return tree_args[0] # Union of a single type is that type
+ return (Union,) + tree_args
+
+ def __eq__(self, other):
+ if isinstance(other, _Union):
+ return self.__tree_hash__ == other.__tree_hash__
+ elif self is not Union:
+ return self._subs_tree() == other
+ else:
+ return self is other
+
+ def __hash__(self):
+ return self.__tree_hash__
+
+ def __instancecheck__(self, obj):
+ raise TypeError("Unions cannot be used with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError("Unions cannot be used with issubclass().")
+
+
+Union = _Union(_root=True)
+
+
+class OptionalMeta(TypingMeta):
+ """Metaclass for Optional."""
+
+ def __new__(cls, name, bases, namespace):
+ cls.assert_no_subclassing(bases)
+ return super(OptionalMeta, cls).__new__(cls, name, bases, namespace)
+
+
+class _Optional(_FinalTypingBase):
+ """Optional type.
+
+ Optional[X] is equivalent to Union[X, None].
+ """
+
+ __metaclass__ = OptionalMeta
+ __slots__ = ()
+
+ @_tp_cache
+ def __getitem__(self, arg):
+ arg = _type_check(arg, "Optional[t] requires a single type.")
+ return Union[arg, type(None)]
+
+
+Optional = _Optional(_root=True)
+
+
+def _next_in_mro(cls):
+ """Helper for Generic.__new__.
+
+ Returns the class after the last occurrence of Generic or
+ Generic[...] in cls.__mro__.
+ """
+ next_in_mro = object
+ # Look for the last occurrence of Generic or Generic[...].
+ for i, c in enumerate(cls.__mro__[:-1]):
+ if isinstance(c, GenericMeta) and c._gorg is Generic:
+ next_in_mro = cls.__mro__[i + 1]
+ return next_in_mro
+
+
+def _make_subclasshook(cls):
+ """Construct a __subclasshook__ callable that incorporates
+ the associated __extra__ class in subclass checks performed
+ against cls.
+ """
+ if isinstance(cls.__extra__, abc.ABCMeta):
+ # The logic mirrors that of ABCMeta.__subclasscheck__.
+ # Registered classes need not be checked here because
+ # cls and its extra share the same _abc_registry.
+ def __extrahook__(cls, subclass):
+ res = cls.__extra__.__subclasshook__(subclass)
+ if res is not NotImplemented:
+ return res
+ if cls.__extra__ in getattr(subclass, '__mro__', ()):
+ return True
+ for scls in cls.__extra__.__subclasses__():
+ if isinstance(scls, GenericMeta):
+ continue
+ if issubclass(subclass, scls):
+ return True
+ return NotImplemented
+ else:
+ # For non-ABC extras we'll just call issubclass().
+ def __extrahook__(cls, subclass):
+ if cls.__extra__ and issubclass(subclass, cls.__extra__):
+ return True
+ return NotImplemented
+ return classmethod(__extrahook__)
+
+
+class GenericMeta(TypingMeta, abc.ABCMeta):
+ """Metaclass for generic types.
+
+ This is a metaclass for typing.Generic and generic ABCs defined in
+ typing module. User defined subclasses of GenericMeta can override
+ __new__ and invoke super().__new__. Note that GenericMeta.__new__
+ has strict rules on what is allowed in its bases argument:
+ * plain Generic is disallowed in bases;
+ * Generic[...] should appear in bases at most once;
+ * if Generic[...] is present, then it should list all type variables
+ that appear in other bases.
+ In addition, type of all generic bases is erased, e.g., C[int] is
+ stripped to plain C.
+ """
+
+ def __new__(cls, name, bases, namespace,
+ tvars=None, args=None, origin=None, extra=None, orig_bases=None):
+ """Create a new generic class. GenericMeta.__new__ accepts
+ keyword arguments that are used for internal bookkeeping, therefore
+ an override should pass unused keyword arguments to super().
+ """
+ if tvars is not None:
+ # Called from __getitem__() below.
+ assert origin is not None
+ assert all(isinstance(t, TypeVar) for t in tvars), tvars
+ else:
+ # Called from class statement.
+ assert tvars is None, tvars
+ assert args is None, args
+ assert origin is None, origin
+
+ # Get the full set of tvars from the bases.
+ tvars = _type_vars(bases)
+ # Look for Generic[T1, ..., Tn].
+ # If found, tvars must be a subset of it.
+ # If not found, tvars is it.
+ # Also check for and reject plain Generic,
+ # and reject multiple Generic[...].
+ gvars = None
+ for base in bases:
+ if base is Generic:
+ raise TypeError("Cannot inherit from plain Generic")
+ if (isinstance(base, GenericMeta) and
+ base.__origin__ in (Generic, Protocol)):
+ if gvars is not None:
+ raise TypeError(
+ "Cannot inherit from Generic[...] or"
+ " Protocol[...] multiple times.")
+ gvars = base.__parameters__
+ if gvars is None:
+ gvars = tvars
+ else:
+ tvarset = set(tvars)
+ gvarset = set(gvars)
+ if not tvarset <= gvarset:
+ raise TypeError(
+ "Some type variables (%s) "
+ "are not listed in %s[%s]" %
+ (", ".join(str(t) for t in tvars if t not in gvarset),
+ "Generic" if any(b.__origin__ is Generic
+ for b in bases) else "Protocol",
+ ", ".join(str(g) for g in gvars)))
+ tvars = gvars
+
+ initial_bases = bases
+ if extra is None:
+ extra = namespace.get('__extra__')
+ if extra is not None and type(extra) is abc.ABCMeta and extra not in bases:
+ bases = (extra,) + bases
+ bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases)
+
+ # remove bare Generic from bases if there are other generic bases
+ if any(isinstance(b, GenericMeta) and b is not Generic for b in bases):
+ bases = tuple(b for b in bases if b is not Generic)
+ namespace.update({'__origin__': origin, '__extra__': extra})
+ self = super(GenericMeta, cls).__new__(cls, name, bases, namespace)
+ super(GenericMeta, self).__setattr__('_gorg',
+ self if not origin else origin._gorg)
+
+ self.__parameters__ = tvars
+ # Be prepared that GenericMeta will be subclassed by TupleMeta
+ # and CallableMeta, those two allow ..., (), or [] in __args___.
+ self.__args__ = tuple(Ellipsis if a is _TypingEllipsis else
+ () if a is _TypingEmpty else
+ a for a in args) if args else None
+ # Speed hack (https://github.com/python/typing/issues/196).
+ self.__next_in_mro__ = _next_in_mro(self)
+ # Preserve base classes on subclassing (__bases__ are type erased now).
+ if orig_bases is None:
+ self.__orig_bases__ = initial_bases
+
+ # This allows unparameterized generic collections to be used
+ # with issubclass() and isinstance() in the same way as their
+ # collections.abc counterparts (e.g., isinstance([], Iterable)).
+ if (
+ '__subclasshook__' not in namespace and extra or
+ # allow overriding
+ getattr(self.__subclasshook__, '__name__', '') == '__extrahook__'
+ ):
+ self.__subclasshook__ = _make_subclasshook(self)
+
+ if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2.
+ self.__qualname__ = origin.__qualname__
+ self.__tree_hash__ = (hash(self._subs_tree()) if origin else
+ super(GenericMeta, self).__hash__())
+ return self
+
+ def __init__(self, *args, **kwargs):
+ super(GenericMeta, self).__init__(*args, **kwargs)
+ if isinstance(self.__extra__, abc.ABCMeta):
+ self._abc_registry = self.__extra__._abc_registry
+ self._abc_cache = self.__extra__._abc_cache
+ elif self.__origin__ is not None:
+ self._abc_registry = self.__origin__._abc_registry
+ self._abc_cache = self.__origin__._abc_cache
+
+ # _abc_negative_cache and _abc_negative_cache_version
+ # realised as descriptors, since GenClass[t1, t2, ...] always
+ # share subclass info with GenClass.
+ # This is an important memory optimization.
+ @property
+ def _abc_negative_cache(self):
+ if isinstance(self.__extra__, abc.ABCMeta):
+ return self.__extra__._abc_negative_cache
+ return self._gorg._abc_generic_negative_cache
+
+ @_abc_negative_cache.setter
+ def _abc_negative_cache(self, value):
+ if self.__origin__ is None:
+ if isinstance(self.__extra__, abc.ABCMeta):
+ self.__extra__._abc_negative_cache = value
+ else:
+ self._abc_generic_negative_cache = value
+
+ @property
+ def _abc_negative_cache_version(self):
+ if isinstance(self.__extra__, abc.ABCMeta):
+ return self.__extra__._abc_negative_cache_version
+ return self._gorg._abc_generic_negative_cache_version
+
+ @_abc_negative_cache_version.setter
+ def _abc_negative_cache_version(self, value):
+ if self.__origin__ is None:
+ if isinstance(self.__extra__, abc.ABCMeta):
+ self.__extra__._abc_negative_cache_version = value
+ else:
+ self._abc_generic_negative_cache_version = value
+
+ def _get_type_vars(self, tvars):
+ if self.__origin__ and self.__parameters__:
+ _get_type_vars(self.__parameters__, tvars)
+
+ def _eval_type(self, globalns, localns):
+ ev_origin = (self.__origin__._eval_type(globalns, localns)
+ if self.__origin__ else None)
+ ev_args = tuple(_eval_type(a, globalns, localns) for a
+ in self.__args__) if self.__args__ else None
+ if ev_origin == self.__origin__ and ev_args == self.__args__:
+ return self
+ return self.__class__(self.__name__,
+ self.__bases__,
+ dict(self.__dict__),
+ tvars=_type_vars(ev_args) if ev_args else None,
+ args=ev_args,
+ origin=ev_origin,
+ extra=self.__extra__,
+ orig_bases=self.__orig_bases__)
+
+ def __repr__(self):
+ if self.__origin__ is None:
+ return super(GenericMeta, self).__repr__()
+ return self._tree_repr(self._subs_tree())
+
+ def _tree_repr(self, tree):
+ arg_list = []
+ for arg in tree[1:]:
+ if arg == ():
+ arg_list.append('()')
+ elif not isinstance(arg, tuple):
+ arg_list.append(_type_repr(arg))
+ else:
+ arg_list.append(arg[0]._tree_repr(arg))
+ return super(GenericMeta, self).__repr__() + '[%s]' % ', '.join(arg_list)
+
+ def _subs_tree(self, tvars=None, args=None):
+ if self.__origin__ is None:
+ return self
+ tree_args = _subs_tree(self, tvars, args)
+ return (self._gorg,) + tuple(tree_args)
+
+ def __eq__(self, other):
+ if not isinstance(other, GenericMeta):
+ return NotImplemented
+ if self.__origin__ is None or other.__origin__ is None:
+ return self is other
+ return self.__tree_hash__ == other.__tree_hash__
+
+ def __hash__(self):
+ return self.__tree_hash__
+
+ @_tp_cache
+ def __getitem__(self, params):
+ if not isinstance(params, tuple):
+ params = (params,)
+ if not params and self._gorg is not Tuple:
+ raise TypeError(
+ "Parameter list to %s[...] cannot be empty" % _qualname(self))
+ msg = "Parameters to generic types must be types."
+ params = tuple(_type_check(p, msg) for p in params)
+ if self in (Generic, Protocol):
+ # Generic can only be subscripted with unique type variables.
+ if not all(isinstance(p, TypeVar) for p in params):
+ raise TypeError(
+ "Parameters to %s[...] must all be type variables" % self.__name__)
+ if len(set(params)) != len(params):
+ raise TypeError(
+ "Parameters to %s[...] must all be unique" % self.__name__)
+ tvars = params
+ args = params
+ elif self in (Tuple, Callable):
+ tvars = _type_vars(params)
+ args = params
+ elif self.__origin__ in (Generic, Protocol):
+ # Can't subscript Generic[...] or Protocol[...].
+ raise TypeError("Cannot subscript already-subscripted %s" %
+ repr(self))
+ else:
+ # Subscripting a regular Generic subclass.
+ _check_generic(self, params)
+ tvars = _type_vars(params)
+ args = params
+
+ prepend = (self,) if self.__origin__ is None else ()
+ return self.__class__(self.__name__,
+ prepend + self.__bases__,
+ dict(self.__dict__),
+ tvars=tvars,
+ args=args,
+ origin=self,
+ extra=self.__extra__,
+ orig_bases=self.__orig_bases__)
+
+ def __subclasscheck__(self, cls):
+ if self.__origin__ is not None:
+ # These should only be modules within the standard library.
+ # singledispatch is an exception, because it's a Python 2 backport
+ # of functools.singledispatch.
+ whitelist = ['abc', 'functools', 'singledispatch']
+ if (sys._getframe(1).f_globals['__name__'] in whitelist or
+ # The second frame is needed for the case where we came
+ # from _ProtocolMeta.__subclasscheck__.
+ sys._getframe(2).f_globals['__name__'] in whitelist):
+ return False
+ raise TypeError("Parameterized generics cannot be used with class "
+ "or instance checks")
+ if self is Generic:
+ raise TypeError("Class %r cannot be used with class "
+ "or instance checks" % self)
+ return super(GenericMeta, self).__subclasscheck__(cls)
+
+ def __instancecheck__(self, instance):
+ # Since we extend ABC.__subclasscheck__ and
+ # ABC.__instancecheck__ inlines the cache checking done by the
+ # latter, we must extend __instancecheck__ too. For simplicity
+ # we just skip the cache check -- instance checks for generic
+ # classes are supposed to be rare anyways.
+ if hasattr(instance, "__class__"):
+ return issubclass(instance.__class__, self)
+ return False
+
+ def __setattr__(self, attr, value):
+ # We consider all the subscripted genrics as proxies for original class
+ if (
+ attr.startswith('__') and attr.endswith('__') or
+ attr.startswith('_abc_')
+ ):
+ super(GenericMeta, self).__setattr__(attr, value)
+ else:
+ super(GenericMeta, self._gorg).__setattr__(attr, value)
+
+
+def _copy_generic(self):
+ """Hack to work around https://bugs.python.org/issue11480 on Python 2"""
+ return self.__class__(self.__name__, self.__bases__, dict(self.__dict__),
+ self.__parameters__, self.__args__, self.__origin__,
+ self.__extra__, self.__orig_bases__)
+
+
+copy._copy_dispatch[GenericMeta] = _copy_generic
+
+
+# Prevent checks for Generic to crash when defining Generic.
+Generic = None
+
+
+def _generic_new(base_cls, cls, *args, **kwds):
+ # Assure type is erased on instantiation,
+ # but attempt to store it in __orig_class__
+ if cls.__origin__ is None:
+ if (base_cls.__new__ is object.__new__ and
+ cls.__init__ is not object.__init__):
+ return base_cls.__new__(cls)
+ else:
+ return base_cls.__new__(cls, *args, **kwds)
+ else:
+ origin = cls._gorg
+ if (base_cls.__new__ is object.__new__ and
+ cls.__init__ is not object.__init__):
+ obj = base_cls.__new__(origin)
+ else:
+ obj = base_cls.__new__(origin, *args, **kwds)
+ try:
+ obj.__orig_class__ = cls
+ except AttributeError:
+ pass
+ obj.__init__(*args, **kwds)
+ return obj
+
+
+class Generic(object):
+ """Abstract base class for generic types.
+
+ A generic type is typically declared by inheriting from
+ this class parameterized with one or more type variables.
+ For example, a generic mapping type might be defined as::
+
+ class Mapping(Generic[KT, VT]):
+ def __getitem__(self, key: KT) -> VT:
+ ...
+ # Etc.
+
+ This class can then be used as follows::
+
+ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
+ try:
+ return mapping[key]
+ except KeyError:
+ return default
+ """
+
+ __metaclass__ = GenericMeta
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Generic:
+ raise TypeError("Type Generic cannot be instantiated; "
+ "it can be used only as a base class")
+ return _generic_new(cls.__next_in_mro__, cls, *args, **kwds)
+
+
+class _TypingEmpty(object):
+ """Internal placeholder for () or []. Used by TupleMeta and CallableMeta
+ to allow empty list/tuple in specific places, without allowing them
+ to sneak in where prohibited.
+ """
+
+
+class _TypingEllipsis(object):
+ """Internal placeholder for ... (ellipsis)."""
+
+
+class TupleMeta(GenericMeta):
+ """Metaclass for Tuple (internal)."""
+
+ @_tp_cache
+ def __getitem__(self, parameters):
+ if self.__origin__ is not None or self._gorg is not Tuple:
+ # Normal generic rules apply if this is not the first subscription
+ # or a subscription of a subclass.
+ return super(TupleMeta, self).__getitem__(parameters)
+ if parameters == ():
+ return super(TupleMeta, self).__getitem__((_TypingEmpty,))
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+ if len(parameters) == 2 and parameters[1] is Ellipsis:
+ msg = "Tuple[t, ...]: t must be a type."
+ p = _type_check(parameters[0], msg)
+ return super(TupleMeta, self).__getitem__((p, _TypingEllipsis))
+ msg = "Tuple[t0, t1, ...]: each t must be a type."
+ parameters = tuple(_type_check(p, msg) for p in parameters)
+ return super(TupleMeta, self).__getitem__(parameters)
+
+ def __instancecheck__(self, obj):
+ if self.__args__ is None:
+ return isinstance(obj, tuple)
+ raise TypeError("Parameterized Tuple cannot be used "
+ "with isinstance().")
+
+ def __subclasscheck__(self, cls):
+ if self.__args__ is None:
+ return issubclass(cls, tuple)
+ raise TypeError("Parameterized Tuple cannot be used "
+ "with issubclass().")
+
+
+copy._copy_dispatch[TupleMeta] = _copy_generic
+
+
+class Tuple(tuple):
+ """Tuple type; Tuple[X, Y] is the cross-product type of X and Y.
+
+ Example: Tuple[T1, T2] is a tuple of two elements corresponding
+ to type variables T1 and T2. Tuple[int, float, str] is a tuple
+ of an int, a float and a string.
+
+ To specify a variable-length tuple of homogeneous type, use Tuple[T, ...].
+ """
+
+ __metaclass__ = TupleMeta
+ __extra__ = tuple
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Tuple:
+ raise TypeError("Type Tuple cannot be instantiated; "
+ "use tuple() instead")
+ return _generic_new(tuple, cls, *args, **kwds)
+
+
+class CallableMeta(GenericMeta):
+ """ Metaclass for Callable."""
+
+ def __repr__(self):
+ if self.__origin__ is None:
+ return super(CallableMeta, self).__repr__()
+ return self._tree_repr(self._subs_tree())
+
+ def _tree_repr(self, tree):
+ if self._gorg is not Callable:
+ return super(CallableMeta, self)._tree_repr(tree)
+ # For actual Callable (not its subclass) we override
+ # super(CallableMeta, self)._tree_repr() for nice formatting.
+ arg_list = []
+ for arg in tree[1:]:
+ if not isinstance(arg, tuple):
+ arg_list.append(_type_repr(arg))
+ else:
+ arg_list.append(arg[0]._tree_repr(arg))
+ if arg_list[0] == '...':
+ return repr(tree[0]) + '[..., %s]' % arg_list[1]
+ return (repr(tree[0]) +
+ '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1]))
+
+ def __getitem__(self, parameters):
+ """A thin wrapper around __getitem_inner__ to provide the latter
+ with hashable arguments to improve speed.
+ """
+
+ if self.__origin__ is not None or self._gorg is not Callable:
+ return super(CallableMeta, self).__getitem__(parameters)
+ if not isinstance(parameters, tuple) or len(parameters) != 2:
+ raise TypeError("Callable must be used as "
+ "Callable[[arg, ...], result].")
+ args, result = parameters
+ if args is Ellipsis:
+ parameters = (Ellipsis, result)
+ else:
+ if not isinstance(args, list):
+ raise TypeError("Callable[args, result]: args must be a list."
+ " Got %.100r." % (args,))
+ parameters = (tuple(args), result)
+ return self.__getitem_inner__(parameters)
+
+ @_tp_cache
+ def __getitem_inner__(self, parameters):
+ args, result = parameters
+ msg = "Callable[args, result]: result must be a type."
+ result = _type_check(result, msg)
+ if args is Ellipsis:
+ return super(CallableMeta, self).__getitem__((_TypingEllipsis, result))
+ msg = "Callable[[arg, ...], result]: each arg must be a type."
+ args = tuple(_type_check(arg, msg) for arg in args)
+ parameters = args + (result,)
+ return super(CallableMeta, self).__getitem__(parameters)
+
+
+copy._copy_dispatch[CallableMeta] = _copy_generic
+
+
+class Callable(object):
+ """Callable type; Callable[[int], str] is a function of (int) -> str.
+
+ The subscription syntax must always be used with exactly two
+ values: the argument list and the return type. The argument list
+ must be a list of types or ellipsis; the return type must be a single type.
+
+ There is no syntax to indicate optional or keyword arguments,
+ such function types are rarely used as callback types.
+ """
+
+ __metaclass__ = CallableMeta
+ __extra__ = collections_abc.Callable
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Callable:
+ raise TypeError("Type Callable cannot be instantiated; "
+ "use a non-abstract subclass instead")
+ return _generic_new(cls.__next_in_mro__, cls, *args, **kwds)
+
+
+def cast(typ, val):
+ """Cast a value to a type.
+
+ This returns the value unchanged. To the type checker this
+ signals that the return value has the designated type, but at
+ runtime we intentionally don't check anything (we want this
+ to be as fast as possible).
+ """
+ return val
+
+
+def _get_defaults(func):
+ """Internal helper to extract the default arguments, by name."""
+ code = func.__code__
+ pos_count = code.co_argcount
+ arg_names = code.co_varnames
+ arg_names = arg_names[:pos_count]
+ defaults = func.__defaults__ or ()
+ kwdefaults = func.__kwdefaults__
+ res = dict(kwdefaults) if kwdefaults else {}
+ pos_offset = pos_count - len(defaults)
+ for name, value in zip(arg_names[pos_offset:], defaults):
+ assert name not in res
+ res[name] = value
+ return res
+
+
+def get_type_hints(obj, globalns=None, localns=None):
+ """In Python 2 this is not supported and always returns None."""
+ return None
+
+
+def no_type_check(arg):
+ """Decorator to indicate that annotations are not type hints.
+
+ The argument must be a class or function; if it is a class, it
+ applies recursively to all methods and classes defined in that class
+ (but not to methods defined in its superclasses or subclasses).
+
+ This mutates the function(s) or class(es) in place.
+ """
+ if isinstance(arg, type):
+ arg_attrs = arg.__dict__.copy()
+ for attr, val in arg.__dict__.items():
+ if val in arg.__bases__ + (arg,):
+ arg_attrs.pop(attr)
+ for obj in arg_attrs.values():
+ if isinstance(obj, types.FunctionType):
+ obj.__no_type_check__ = True
+ if isinstance(obj, type):
+ no_type_check(obj)
+ try:
+ arg.__no_type_check__ = True
+ except TypeError: # built-in classes
+ pass
+ return arg
+
+
+def no_type_check_decorator(decorator):
+ """Decorator to give another decorator the @no_type_check effect.
+
+ This wraps the decorator with something that wraps the decorated
+ function in @no_type_check.
+ """
+
+ @functools.wraps(decorator)
+ def wrapped_decorator(*args, **kwds):
+ func = decorator(*args, **kwds)
+ func = no_type_check(func)
+ return func
+
+ return wrapped_decorator
+
+
+def _overload_dummy(*args, **kwds):
+ """Helper for @overload to raise when called."""
+ raise NotImplementedError(
+ "You should not call an overloaded function. "
+ "A series of @overload-decorated functions "
+ "outside a stub module should always be followed "
+ "by an implementation that is not @overload-ed.")
+
+
+def overload(func):
+ """Decorator for overloaded functions/methods.
+
+ In a stub file, place two or more stub definitions for the same
+ function in a row, each decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+
+ In a non-stub file (i.e. a regular .py file), do the same but
+ follow it with an implementation. The implementation should *not*
+ be decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+ def utf8(value):
+ # implementation goes here
+ """
+ return _overload_dummy
+
+
+_PROTO_WHITELIST = ['Callable', 'Iterable', 'Iterator',
+ 'Hashable', 'Sized', 'Container', 'Collection',
+ 'Reversible', 'ContextManager']
+
+
+class _ProtocolMeta(GenericMeta):
+ """Internal metaclass for Protocol.
+
+ This exists so Protocol classes can be generic without deriving
+ from Generic.
+ """
+ def __init__(cls, *args, **kwargs):
+ super(_ProtocolMeta, cls).__init__(*args, **kwargs)
+ if not cls.__dict__.get('_is_protocol', None):
+ cls._is_protocol = any(b is Protocol or
+ isinstance(b, _ProtocolMeta) and
+ b.__origin__ is Protocol
+ for b in cls.__bases__)
+ if cls._is_protocol:
+ for base in cls.__mro__[1:]:
+ if not (base in (object, Generic) or
+ base.__module__ == '_abcoll' and
+ base.__name__ in _PROTO_WHITELIST or
+ isinstance(base, TypingMeta) and base._is_protocol or
+ isinstance(base, GenericMeta) and base.__origin__ is Generic):
+ raise TypeError('Protocols can only inherit from other protocols,'
+ ' got %r' % base)
+ cls._callable_members_only = all(callable(getattr(cls, attr))
+ for attr in cls._get_protocol_attrs())
+
+ def _no_init(self, *args, **kwargs):
+ if type(self)._is_protocol:
+ raise TypeError('Protocols cannot be instantiated')
+ cls.__init__ = _no_init
+
+ def _proto_hook(cls, other):
+ if not cls.__dict__.get('_is_protocol', None):
+ return NotImplemented
+ if not isinstance(other, type):
+ # Similar error as for issubclass(1, int)
+ # (also not a chance for old-style classes)
+ raise TypeError('issubclass() arg 1 must be a new-style class')
+ for attr in cls._get_protocol_attrs():
+ for base in other.__mro__:
+ if attr in base.__dict__:
+ if base.__dict__[attr] is None:
+ return NotImplemented
+ break
+ else:
+ return NotImplemented
+ return True
+ if '__subclasshook__' not in cls.__dict__:
+ cls.__subclasshook__ = classmethod(_proto_hook)
+
+ def __instancecheck__(self, instance):
+ # We need this method for situations where attributes are assigned in __init__
+ if isinstance(instance, type):
+ # This looks like a fundamental limitation of Python 2.
+ # It cannot support runtime protocol metaclasses, On Python 2 classes
+ # cannot be correctly inspected as instances of protocols.
+ return False
+ if ((not getattr(self, '_is_protocol', False) or
+ self._callable_members_only) and
+ issubclass(instance.__class__, self)):
+ return True
+ if self._is_protocol:
+ if all(hasattr(instance, attr) and
+ (not callable(getattr(self, attr)) or
+ getattr(instance, attr) is not None)
+ for attr in self._get_protocol_attrs()):
+ return True
+ return super(GenericMeta, self).__instancecheck__(instance)
+
+ def __subclasscheck__(self, cls):
+ if (self.__dict__.get('_is_protocol', None) and
+ not self.__dict__.get('_is_runtime_protocol', None)):
+ if (sys._getframe(1).f_globals['__name__'] in ['abc', 'functools'] or
+ # This is needed because we remove subclasses from unions on Python 2.
+ sys._getframe(2).f_globals['__name__'] == 'typing'):
+ return False
+ raise TypeError("Instance and class checks can only be used with"
+ " @runtime_checkable protocols")
+ if (self.__dict__.get('_is_runtime_protocol', None) and
+ not self._callable_members_only):
+ if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools']:
+ return super(GenericMeta, self).__subclasscheck__(cls)
+ raise TypeError("Protocols with non-method members"
+ " don't support issubclass()")
+ return super(_ProtocolMeta, self).__subclasscheck__(cls)
+
+ def _get_protocol_attrs(self):
+ attrs = set()
+ for base in self.__mro__[:-1]: # without object
+ if base.__name__ in ('Protocol', 'Generic'):
+ continue
+ annotations = getattr(base, '__annotations__', {})
+ for attr in list(base.__dict__.keys()) + list(annotations.keys()):
+ if (not attr.startswith('_abc_') and attr not in (
+ '__abstractmethods__', '__annotations__', '__weakref__',
+ '_is_protocol', '_is_runtime_protocol', '__dict__',
+ '__args__', '__slots__', '_get_protocol_attrs',
+ '__next_in_mro__', '__parameters__', '__origin__',
+ '__orig_bases__', '__extra__', '__tree_hash__',
+ '__doc__', '__subclasshook__', '__init__', '__new__',
+ '__module__', '_MutableMapping__marker',
+ '__metaclass__', '_gorg', '_callable_members_only')):
+ attrs.add(attr)
+ return attrs
+
+
+class Protocol(object):
+ """Base class for protocol classes. Protocol classes are defined as::
+
+ class Proto(Protocol):
+ def meth(self):
+ # type: () -> int
+ pass
+
+ Such classes are primarily used with static type checkers that recognize
+ structural subtyping (static duck-typing), for example::
+
+ class C:
+ def meth(self):
+ # type: () -> int
+ return 0
+
+ def func(x):
+ # type: (Proto) -> int
+ return x.meth()
+
+ func(C()) # Passes static type check
+
+ See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable
+ act as simple-minded runtime protocols that checks only the presence of
+ given attributes, ignoring their type signatures.
+
+ Protocol classes can be generic, they are defined as::
+
+ class GenProto(Protocol[T]):
+ def meth(self):
+ # type: () -> T
+ pass
+ """
+
+ __metaclass__ = _ProtocolMeta
+ __slots__ = ()
+ _is_protocol = True
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Protocol:
+ raise TypeError("Type Protocol cannot be instantiated; "
+ "it can be used only as a base class")
+ return _generic_new(cls.__next_in_mro__, cls, *args, **kwds)
+
+
+def runtime_checkable(cls):
+ """Mark a protocol class as a runtime protocol, so that it
+ can be used with isinstance() and issubclass(). Raise TypeError
+ if applied to a non-protocol class.
+
+ This allows a simple-minded structural check very similar to the
+ one-offs in collections.abc such as Hashable.
+ """
+ if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol:
+ raise TypeError('@runtime_checkable can be only applied to protocol classes,'
+ ' got %r' % cls)
+ cls._is_runtime_protocol = True
+ return cls
+
+
+# Various ABCs mimicking those in collections.abc.
+# A few are simply re-exported for completeness.
+
+Hashable = collections_abc.Hashable # Not generic.
+
+
+class Iterable(Generic[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Iterable
+
+
+class Iterator(Iterable[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Iterator
+
+
+@runtime_checkable
+class SupportsInt(Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __int__(self):
+ pass
+
+
+@runtime_checkable
+class SupportsFloat(Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __float__(self):
+ pass
+
+
+@runtime_checkable
+class SupportsComplex(Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __complex__(self):
+ pass
+
+
+@runtime_checkable
+class SupportsIndex(Protocol):
+ __slots__ = ()
+
+ @abstractmethod
+ def __index__(self):
+ pass
+
+
+@runtime_checkable
+class SupportsAbs(Protocol[T_co]):
+ __slots__ = ()
+
+ @abstractmethod
+ def __abs__(self):
+ pass
+
+
+if hasattr(collections_abc, 'Reversible'):
+ class Reversible(Iterable[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Reversible
+else:
+ @runtime_checkable
+ class Reversible(Protocol[T_co]):
+ __slots__ = ()
+
+ @abstractmethod
+ def __reversed__(self):
+ pass
+
+
+Sized = collections_abc.Sized # Not generic.
+
+
+class Container(Generic[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Container
+
+
+# Callable was defined earlier.
+
+
+class AbstractSet(Sized, Iterable[T_co], Container[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Set
+
+
+class MutableSet(AbstractSet[T]):
+ __slots__ = ()
+ __extra__ = collections_abc.MutableSet
+
+
+# NOTE: It is only covariant in the value type.
+class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Mapping
+
+
+class MutableMapping(Mapping[KT, VT]):
+ __slots__ = ()
+ __extra__ = collections_abc.MutableMapping
+
+
+if hasattr(collections_abc, 'Reversible'):
+ class Sequence(Sized, Reversible[T_co], Container[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Sequence
+else:
+ class Sequence(Sized, Iterable[T_co], Container[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.Sequence
+
+
+class MutableSequence(Sequence[T]):
+ __slots__ = ()
+ __extra__ = collections_abc.MutableSequence
+
+
+class ByteString(Sequence[int]):
+ pass
+
+
+ByteString.register(str)
+ByteString.register(bytearray)
+
+
+class List(list, MutableSequence[T]):
+ __slots__ = ()
+ __extra__ = list
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is List:
+ raise TypeError("Type List cannot be instantiated; "
+ "use list() instead")
+ return _generic_new(list, cls, *args, **kwds)
+
+
+class Deque(collections.deque, MutableSequence[T]):
+ __slots__ = ()
+ __extra__ = collections.deque
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Deque:
+ return collections.deque(*args, **kwds)
+ return _generic_new(collections.deque, cls, *args, **kwds)
+
+
+class Set(set, MutableSet[T]):
+ __slots__ = ()
+ __extra__ = set
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Set:
+ raise TypeError("Type Set cannot be instantiated; "
+ "use set() instead")
+ return _generic_new(set, cls, *args, **kwds)
+
+
+class FrozenSet(frozenset, AbstractSet[T_co]):
+ __slots__ = ()
+ __extra__ = frozenset
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is FrozenSet:
+ raise TypeError("Type FrozenSet cannot be instantiated; "
+ "use frozenset() instead")
+ return _generic_new(frozenset, cls, *args, **kwds)
+
+
+class MappingView(Sized, Iterable[T_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.MappingView
+
+
+class KeysView(MappingView[KT], AbstractSet[KT]):
+ __slots__ = ()
+ __extra__ = collections_abc.KeysView
+
+
+class ItemsView(MappingView[Tuple[KT, VT_co]],
+ AbstractSet[Tuple[KT, VT_co]],
+ Generic[KT, VT_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.ItemsView
+
+
+class ValuesView(MappingView[VT_co]):
+ __slots__ = ()
+ __extra__ = collections_abc.ValuesView
+
+
+class ContextManager(Generic[T_co]):
+ __slots__ = ()
+
+ def __enter__(self):
+ return self
+
+ @abc.abstractmethod
+ def __exit__(self, exc_type, exc_value, traceback):
+ return None
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is ContextManager:
+ # In Python 3.6+, it is possible to set a method to None to
+ # explicitly indicate that the class does not implement an ABC
+ # (https://bugs.python.org/issue25958), but we do not support
+ # that pattern here because this fallback class is only used
+ # in Python 3.5 and earlier.
+ if (any("__enter__" in B.__dict__ for B in C.__mro__) and
+ any("__exit__" in B.__dict__ for B in C.__mro__)):
+ return True
+ return NotImplemented
+
+
+class Dict(dict, MutableMapping[KT, VT]):
+ __slots__ = ()
+ __extra__ = dict
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Dict:
+ raise TypeError("Type Dict cannot be instantiated; "
+ "use dict() instead")
+ return _generic_new(dict, cls, *args, **kwds)
+
+
+class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]):
+ __slots__ = ()
+ __extra__ = collections.defaultdict
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is DefaultDict:
+ return collections.defaultdict(*args, **kwds)
+ return _generic_new(collections.defaultdict, cls, *args, **kwds)
+
+
+class Counter(collections.Counter, Dict[T, int]):
+ __slots__ = ()
+ __extra__ = collections.Counter
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Counter:
+ return collections.Counter(*args, **kwds)
+ return _generic_new(collections.Counter, cls, *args, **kwds)
+
+
+# Determine what base class to use for Generator.
+if hasattr(collections_abc, 'Generator'):
+ # Sufficiently recent versions of 3.5 have a Generator ABC.
+ _G_base = collections_abc.Generator
+else:
+ # Fall back on the exact type.
+ _G_base = types.GeneratorType
+
+
+class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co]):
+ __slots__ = ()
+ __extra__ = _G_base
+
+ def __new__(cls, *args, **kwds):
+ if cls._gorg is Generator:
+ raise TypeError("Type Generator cannot be instantiated; "
+ "create a subclass instead")
+ return _generic_new(_G_base, cls, *args, **kwds)
+
+
+# Internal type variable used for Type[].
+CT_co = TypeVar('CT_co', covariant=True, bound=type)
+
+
+# This is not a real generic class. Don't use outside annotations.
+class Type(Generic[CT_co]):
+ """A special construct usable to annotate class objects.
+
+ For example, suppose we have the following classes::
+
+ class User: ... # Abstract base for User classes
+ class BasicUser(User): ...
+ class ProUser(User): ...
+ class TeamUser(User): ...
+
+ And a function that takes a class argument that's a subclass of
+ User and returns an instance of the corresponding class::
+
+ U = TypeVar('U', bound=User)
+ def new_user(user_class: Type[U]) -> U:
+ user = user_class()
+ # (Here we could write the user object to a database)
+ return user
+
+ joe = new_user(BasicUser)
+
+ At this point the type checker knows that joe has type BasicUser.
+ """
+ __slots__ = ()
+ __extra__ = type
+
+
+def NamedTuple(typename, fields):
+ """Typed version of namedtuple.
+
+ Usage::
+
+ Employee = typing.NamedTuple('Employee', [('name', str), ('id', int)])
+
+ This is equivalent to::
+
+ Employee = collections.namedtuple('Employee', ['name', 'id'])
+
+ The resulting class has one extra attribute: _field_types,
+ giving a dict mapping field names to types. (The field names
+ are in the _fields attribute, which is part of the namedtuple
+ API.)
+ """
+ fields = [(n, t) for n, t in fields]
+ cls = collections.namedtuple(typename, [n for n, t in fields])
+ cls._field_types = dict(fields)
+ # Set the module to the caller's module (otherwise it'd be 'typing').
+ try:
+ cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ pass
+ return cls
+
+
+def _check_fails(cls, other):
+ try:
+ if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']:
+ # Typed dicts are only for static structural subtyping.
+ raise TypeError('TypedDict does not support instance and class checks')
+ except (AttributeError, ValueError):
+ pass
+ return False
+
+
+def _dict_new(cls, *args, **kwargs):
+ return dict(*args, **kwargs)
+
+
+def _typeddict_new(cls, _typename, _fields=None, **kwargs):
+ total = kwargs.pop('total', True)
+ if _fields is None:
+ _fields = kwargs
+ elif kwargs:
+ raise TypeError("TypedDict takes either a dict or keyword arguments,"
+ " but not both")
+
+ ns = {'__annotations__': dict(_fields), '__total__': total}
+ try:
+ # Setting correct module is necessary to make typed dict classes pickleable.
+ ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ pass
+
+ return _TypedDictMeta(_typename, (), ns)
+
+
+class _TypedDictMeta(type):
+ def __new__(cls, name, bases, ns, total=True):
+ # Create new typed dict class object.
+ # This method is called directly when TypedDict is subclassed,
+ # or via _typeddict_new when TypedDict is instantiated. This way
+ # TypedDict supports all three syntaxes described in its docstring.
+ # Subclasses and instances of TypedDict return actual dictionaries
+ # via _dict_new.
+ ns['__new__'] = _typeddict_new if name == b'TypedDict' else _dict_new
+ tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns)
+
+ anns = ns.get('__annotations__', {})
+ msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
+ anns = {n: _type_check(tp, msg) for n, tp in anns.items()}
+ for base in bases:
+ anns.update(base.__dict__.get('__annotations__', {}))
+ tp_dict.__annotations__ = anns
+ if not hasattr(tp_dict, '__total__'):
+ tp_dict.__total__ = total
+ return tp_dict
+
+ __instancecheck__ = __subclasscheck__ = _check_fails
+
+
+TypedDict = _TypedDictMeta(b'TypedDict', (dict,), {})
+TypedDict.__module__ = __name__
+TypedDict.__doc__ = \
+ """A simple typed name space. At runtime it is equivalent to a plain dict.
+
+ TypedDict creates a dictionary type that expects all of its
+ instances to have a certain set of keys, with each key
+ associated with a value of a consistent type. This expectation
+ is not checked at runtime but is only enforced by type checkers.
+ Usage::
+
+ Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
+
+ a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
+ b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check
+
+ assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
+
+ The type info could be accessed via Point2D.__annotations__. TypedDict
+ supports an additional equivalent form::
+
+ Point2D = TypedDict('Point2D', x=int, y=int, label=str)
+ """
+
+
+def NewType(name, tp):
+ """NewType creates simple unique types with almost zero
+ runtime overhead. NewType(name, tp) is considered a subtype of tp
+ by static type checkers. At runtime, NewType(name, tp) returns
+ a dummy function that simply returns its argument. Usage::
+
+ UserId = NewType('UserId', int)
+
+ def name_by_id(user_id):
+ # type: (UserId) -> str
+ ...
+
+ UserId('user') # Fails type check
+
+ name_by_id(42) # Fails type check
+ name_by_id(UserId(42)) # OK
+
+ num = UserId(5) + 1 # type: int
+ """
+
+ def new_type(x):
+ return x
+
+ # Some versions of Python 2 complain because of making all strings unicode
+ new_type.__name__ = str(name)
+ new_type.__supertype__ = tp
+ return new_type
+
+
+# Python-version-specific alias (Python 2: unicode; Python 3: str)
+Text = unicode
+
+
+# Constant that's True when type checking, but False here.
+TYPE_CHECKING = False
+
+
+class IO(Generic[AnyStr]):
+ """Generic base class for TextIO and BinaryIO.
+
+ This is an abstract, generic version of the return of open().
+
+ NOTE: This does not distinguish between the different possible
+ classes (text vs. binary, read vs. write vs. read/write,
+ append-only, unbuffered). The TextIO and BinaryIO subclasses
+ below capture the distinctions between text vs. binary, which is
+ pervasive in the interface; however we currently do not offer a
+ way to track the other distinctions in the type system.
+ """
+
+ __slots__ = ()
+
+ @abstractproperty
+ def mode(self):
+ pass
+
+ @abstractproperty
+ def name(self):
+ pass
+
+ @abstractmethod
+ def close(self):
+ pass
+
+ @abstractproperty
+ def closed(self):
+ pass
+
+ @abstractmethod
+ def fileno(self):
+ pass
+
+ @abstractmethod
+ def flush(self):
+ pass
+
+ @abstractmethod
+ def isatty(self):
+ pass
+
+ @abstractmethod
+ def read(self, n=-1):
+ pass
+
+ @abstractmethod
+ def readable(self):
+ pass
+
+ @abstractmethod
+ def readline(self, limit=-1):
+ pass
+
+ @abstractmethod
+ def readlines(self, hint=-1):
+ pass
+
+ @abstractmethod
+ def seek(self, offset, whence=0):
+ pass
+
+ @abstractmethod
+ def seekable(self):
+ pass
+
+ @abstractmethod
+ def tell(self):
+ pass
+
+ @abstractmethod
+ def truncate(self, size=None):
+ pass
+
+ @abstractmethod
+ def writable(self):
+ pass
+
+ @abstractmethod
+ def write(self, s):
+ pass
+
+ @abstractmethod
+ def writelines(self, lines):
+ pass
+
+ @abstractmethod
+ def __enter__(self):
+ pass
+
+ @abstractmethod
+ def __exit__(self, type, value, traceback):
+ pass
+
+
+class BinaryIO(IO[bytes]):
+ """Typed version of the return of open() in binary mode."""
+
+ __slots__ = ()
+
+ @abstractmethod
+ def write(self, s):
+ pass
+
+ @abstractmethod
+ def __enter__(self):
+ pass
+
+
+class TextIO(IO[unicode]):
+ """Typed version of the return of open() in text mode."""
+
+ __slots__ = ()
+
+ @abstractproperty
+ def buffer(self):
+ pass
+
+ @abstractproperty
+ def encoding(self):
+ pass
+
+ @abstractproperty
+ def errors(self):
+ pass
+
+ @abstractproperty
+ def line_buffering(self):
+ pass
+
+ @abstractproperty
+ def newlines(self):
+ pass
+
+ @abstractmethod
+ def __enter__(self):
+ pass
+
+
+class io(object):
+ """Wrapper namespace for IO generic classes."""
+
+ __all__ = ['IO', 'TextIO', 'BinaryIO']
+ IO = IO
+ TextIO = TextIO
+ BinaryIO = BinaryIO
+
+
+io.__name__ = __name__ + b'.io'
+sys.modules[io.__name__] = io
+
+
+Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')),
+ lambda p: p.pattern)
+Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')),
+ lambda m: m.re.pattern)
+
+
+class re(object):
+ """Wrapper namespace for re type aliases."""
+
+ __all__ = ['Pattern', 'Match']
+ Pattern = Pattern
+ Match = Match
+
+
+re.__name__ = __name__ + b'.re'
+sys.modules[re.__name__] = re
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/LICENSE
new file mode 100644
index 0000000000..5e795a61f3
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/LICENSE
@@ -0,0 +1,7 @@
+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.
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/METADATA
new file mode 100644
index 0000000000..723cd1b4d8
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/METADATA
@@ -0,0 +1,49 @@
+Metadata-Version: 2.1
+Name: zipp
+Version: 1.2.0
+Summary: Backport of pathlib-compatible object wrapper for zip files
+Home-page: https://github.com/jaraco/zipp
+Author: Jason R. Coombs
+Author-email: jaraco@jaraco.com
+License: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Requires-Python: >=2.7
+Requires-Dist: contextlib2 ; python_version < "3.4"
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs'
+Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
+Provides-Extra: testing
+Requires-Dist: pathlib2 ; extra == 'testing'
+Requires-Dist: unittest2 ; extra == 'testing'
+Requires-Dist: jaraco.itertools ; extra == 'testing'
+Requires-Dist: func-timeout ; extra == 'testing'
+
+.. image:: https://img.shields.io/pypi/v/zipp.svg
+ :target: https://pypi.org/project/zipp
+
+.. image:: https://img.shields.io/pypi/pyversions/zipp.svg
+
+.. image:: https://img.shields.io/travis/jaraco/zipp/master.svg
+ :target: https://travis-ci.org/jaraco/zipp
+
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+ :target: https://github.com/ambv/black
+ :alt: Code style: Black
+
+.. image:: https://img.shields.io/appveyor/ci/jaraco/zipp/master.svg
+ :target: https://ci.appveyor.com/project/jaraco/zipp/branch/master
+
+.. .. image:: https://readthedocs.org/projects/zipp/badge/?version=latest
+.. :target: https://zipp.readthedocs.io/en/latest/?badge=latest
+
+
+A pathlib-compatible Zipfile object wrapper. A backport of the
+`Path object <https://docs.python.org/3.8/library/zipfile.html#path-objects>`_.
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/RECORD
new file mode 100644
index 0000000000..be9f067d8f
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/RECORD
@@ -0,0 +1,6 @@
+zipp.py,sha256=uw2fKH3c8O07ReW9L27THYalYWMMWv_juvr-5BG-3zA,7039
+zipp-1.2.0.dist-info/LICENSE,sha256=pV4v_ptEmY5iHVHYwJS-0JrMS1I27nPX3zlaM7o8GP0,1050
+zipp-1.2.0.dist-info/METADATA,sha256=5-1p1kkPwGBPcQe6naFJUH024SFYA4rOmDzqvrLFRGM,1795
+zipp-1.2.0.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
+zipp-1.2.0.dist-info/top_level.txt,sha256=iAbdoSHfaGqBfVb2XuR9JqSQHCoOsOtG6y9C_LSpqFw,5
+zipp-1.2.0.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/WHEEL
new file mode 100644
index 0000000000..ef99c6cf32
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.34.2)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/top_level.txt
new file mode 100644
index 0000000000..e82f676f82
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+zipp
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp.py b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp.py
new file mode 100644
index 0000000000..892205834a
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp.py
@@ -0,0 +1,286 @@
+# coding: utf-8
+
+from __future__ import division
+
+import io
+import sys
+import posixpath
+import zipfile
+import functools
+import itertools
+from collections import OrderedDict
+
+try:
+ from contextlib import suppress
+except ImportError:
+ from contextlib2 import suppress
+
+__metaclass__ = type
+
+
+def _parents(path):
+ """
+ Given a path with elements separated by
+ posixpath.sep, generate all parents of that path.
+
+ >>> list(_parents('b/d'))
+ ['b']
+ >>> list(_parents('/b/d/'))
+ ['/b']
+ >>> list(_parents('b/d/f/'))
+ ['b/d', 'b']
+ >>> list(_parents('b'))
+ []
+ >>> list(_parents(''))
+ []
+ """
+ return itertools.islice(_ancestry(path), 1, None)
+
+
+def _ancestry(path):
+ """
+ Given a path with elements separated by
+ posixpath.sep, generate all elements of that path
+
+ >>> list(_ancestry('b/d'))
+ ['b/d', 'b']
+ >>> list(_ancestry('/b/d/'))
+ ['/b/d', '/b']
+ >>> list(_ancestry('b/d/f/'))
+ ['b/d/f', 'b/d', 'b']
+ >>> list(_ancestry('b'))
+ ['b']
+ >>> list(_ancestry(''))
+ []
+ """
+ path = path.rstrip(posixpath.sep)
+ while path and path != posixpath.sep:
+ yield path
+ path, tail = posixpath.split(path)
+
+
+class CompleteDirs(zipfile.ZipFile):
+ """
+ A ZipFile subclass that ensures that implied directories
+ are always included in the namelist.
+ """
+
+ @staticmethod
+ def _implied_dirs(names):
+ parents = itertools.chain.from_iterable(map(_parents, names))
+ # Cast names to a set for O(1) lookups
+ existing = set(names)
+ # Deduplicate entries in original order
+ implied_dirs = OrderedDict.fromkeys(
+ p + posixpath.sep for p in parents
+ if p + posixpath.sep not in existing
+ )
+ return implied_dirs
+
+ def namelist(self):
+ names = super(CompleteDirs, self).namelist()
+ return names + list(self._implied_dirs(names))
+
+ def _name_set(self):
+ return set(self.namelist())
+
+ def resolve_dir(self, name):
+ """
+ If the name represents a directory, return that name
+ as a directory (with the trailing slash).
+ """
+ names = self._name_set()
+ dirname = name + '/'
+ dir_match = name not in names and dirname in names
+ return dirname if dir_match else name
+
+ @classmethod
+ def make(cls, source):
+ """
+ Given a source (filename or zipfile), return an
+ appropriate CompleteDirs subclass.
+ """
+ if isinstance(source, CompleteDirs):
+ return source
+
+ if not isinstance(source, zipfile.ZipFile):
+ return cls(_pathlib_compat(source))
+
+ # Only allow for FastPath when supplied zipfile is read-only
+ if 'r' not in source.mode:
+ cls = CompleteDirs
+
+ res = cls.__new__(cls)
+ vars(res).update(vars(source))
+ return res
+
+
+class FastLookup(CompleteDirs):
+ """
+ ZipFile subclass to ensure implicit
+ dirs exist and are resolved rapidly.
+ """
+ def namelist(self):
+ with suppress(AttributeError):
+ return self.__names
+ self.__names = super(FastLookup, self).namelist()
+ return self.__names
+
+ def _name_set(self):
+ with suppress(AttributeError):
+ return self.__lookup
+ self.__lookup = super(FastLookup, self)._name_set()
+ return self.__lookup
+
+
+def _pathlib_compat(path):
+ """
+ For path-like objects, convert to a filename for compatibility
+ on Python 3.6.1 and earlier.
+ """
+ try:
+ return path.__fspath__()
+ except AttributeError:
+ return str(path)
+
+
+class Path:
+ """
+ A pathlib-compatible interface for zip files.
+
+ Consider a zip file with this structure::
+
+ .
+ ├── a.txt
+ └── b
+ ├── c.txt
+ └── d
+ └── e.txt
+
+ >>> data = io.BytesIO()
+ >>> zf = zipfile.ZipFile(data, 'w')
+ >>> zf.writestr('a.txt', 'content of a')
+ >>> zf.writestr('b/c.txt', 'content of c')
+ >>> zf.writestr('b/d/e.txt', 'content of e')
+ >>> zf.filename = 'abcde.zip'
+
+ Path accepts the zipfile object itself or a filename
+
+ >>> root = Path(zf)
+
+ From there, several path operations are available.
+
+ Directory iteration (including the zip file itself):
+
+ >>> a, b = root.iterdir()
+ >>> a
+ Path('abcde.zip', 'a.txt')
+ >>> b
+ Path('abcde.zip', 'b/')
+
+ name property:
+
+ >>> b.name
+ 'b'
+
+ join with divide operator:
+
+ >>> c = b / 'c.txt'
+ >>> c
+ Path('abcde.zip', 'b/c.txt')
+ >>> c.name
+ 'c.txt'
+
+ Read text:
+
+ >>> c.read_text()
+ 'content of c'
+
+ existence:
+
+ >>> c.exists()
+ True
+ >>> (b / 'missing.txt').exists()
+ False
+
+ Coercion to string:
+
+ >>> str(c)
+ 'abcde.zip/b/c.txt'
+ """
+
+ __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
+
+ def __init__(self, root, at=""):
+ self.root = FastLookup.make(root)
+ self.at = at
+
+ def open(self, mode='r', *args, **kwargs):
+ """
+ Open this entry as text or binary following the semantics
+ of ``pathlib.Path.open()`` by passing arguments through
+ to io.TextIOWrapper().
+ """
+ pwd = kwargs.pop('pwd', None)
+ zip_mode = mode[0]
+ stream = self.root.open(self.at, zip_mode, pwd=pwd)
+ if 'b' in mode:
+ if args or kwargs:
+ raise ValueError("encoding args invalid for binary operation")
+ return stream
+ return io.TextIOWrapper(stream, *args, **kwargs)
+
+ @property
+ def name(self):
+ return posixpath.basename(self.at.rstrip("/"))
+
+ def read_text(self, *args, **kwargs):
+ with self.open('r', *args, **kwargs) as strm:
+ return strm.read()
+
+ def read_bytes(self):
+ with self.open('rb') as strm:
+ return strm.read()
+
+ def _is_child(self, path):
+ return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
+
+ def _next(self, at):
+ return Path(self.root, at)
+
+ def is_dir(self):
+ return not self.at or self.at.endswith("/")
+
+ def is_file(self):
+ return not self.is_dir()
+
+ def exists(self):
+ return self.at in self.root._name_set()
+
+ def iterdir(self):
+ if not self.is_dir():
+ raise ValueError("Can't listdir a file")
+ subs = map(self._next, self.root.namelist())
+ return filter(self._is_child, subs)
+
+ def __str__(self):
+ return posixpath.join(self.root.filename, self.at)
+
+ def __repr__(self):
+ return self.__repr.format(self=self)
+
+ def joinpath(self, add):
+ next = posixpath.join(self.at, _pathlib_compat(add))
+ return self._next(self.root.resolve_dir(next))
+
+ __truediv__ = joinpath
+
+ @property
+ def parent(self):
+ parent_at = posixpath.dirname(self.at.rstrip('/'))
+ if parent_at:
+ parent_at += '/'
+ return self._next(parent_at)
+
+ if sys.version_info < (3,):
+ __div__ = __truediv__
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/LICENSE b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/LICENSE
new file mode 100644
index 0000000000..353924be0e
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/LICENSE
@@ -0,0 +1,19 @@
+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.
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/METADATA b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/METADATA
new file mode 100644
index 0000000000..9e71c5a8c4
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/METADATA
@@ -0,0 +1,54 @@
+Metadata-Version: 2.1
+Name: zipp
+Version: 3.4.0
+Summary: Backport of pathlib-compatible object wrapper for zip files
+Home-page: https://github.com/jaraco/zipp
+Author: Jason R. Coombs
+Author-email: jaraco@jaraco.com
+License: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Requires-Python: >=3.6
+Provides-Extra: docs
+Requires-Dist: sphinx ; extra == 'docs'
+Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs'
+Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
+Provides-Extra: testing
+Requires-Dist: pytest (!=3.7.3,>=3.5) ; extra == 'testing'
+Requires-Dist: pytest-checkdocs (>=1.2.3) ; extra == 'testing'
+Requires-Dist: pytest-flake8 ; extra == 'testing'
+Requires-Dist: pytest-cov ; extra == 'testing'
+Requires-Dist: jaraco.test (>=3.2.0) ; extra == 'testing'
+Requires-Dist: jaraco.itertools ; extra == 'testing'
+Requires-Dist: func-timeout ; extra == 'testing'
+Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing'
+Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing'
+
+.. image:: https://img.shields.io/pypi/v/zipp.svg
+ :target: `PyPI link`_
+
+.. image:: https://img.shields.io/pypi/pyversions/zipp.svg
+ :target: `PyPI link`_
+
+.. _PyPI link: https://pypi.org/project/zipp
+
+.. image:: https://github.com/jaraco/zipp/workflows/Automated%20Tests/badge.svg
+ :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22Automated+Tests%22
+ :alt: Automated Tests
+
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+ :target: https://github.com/psf/black
+ :alt: Code style: Black
+
+.. .. image:: https://readthedocs.org/projects/zipp/badge/?version=latest
+.. :target: https://zipp.readthedocs.io/en/latest/?badge=latest
+
+
+A pathlib-compatible Zipfile object wrapper. A backport of the
+`Path object <https://docs.python.org/3.8/library/zipfile.html#path-objects>`_.
+
+
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/RECORD b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/RECORD
new file mode 100644
index 0000000000..3c441ec9bd
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/RECORD
@@ -0,0 +1,6 @@
+zipp.py,sha256=wMSoYxAIPgYnqJAW0JcAl5sWaIcFc5xk3dNjf6ElGgU,8089
+zipp-3.4.0.dist-info/LICENSE,sha256=2z8CRrH5J48VhFuZ_sR4uLUG63ZIeZNyL4xuJUKF-vg,1050
+zipp-3.4.0.dist-info/METADATA,sha256=noSfks-ReGCmOSTxll7TELBJy0P_yAvVLa0FCFyhMeM,2134
+zipp-3.4.0.dist-info/WHEEL,sha256=EVRjI69F5qVjm_YgqcTXPnTAv3BfSUr0WVAHuSP3Xoo,92
+zipp-3.4.0.dist-info/top_level.txt,sha256=iAbdoSHfaGqBfVb2XuR9JqSQHCoOsOtG6y9C_LSpqFw,5
+zipp-3.4.0.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/WHEEL b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/WHEEL
new file mode 100644
index 0000000000..83ff02e961
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.35.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/top_level.txt b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/top_level.txt
new file mode 100644
index 0000000000..e82f676f82
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+zipp
diff --git a/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp.py b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp.py
new file mode 100644
index 0000000000..25ef06e929
--- /dev/null
+++ b/third_party/python/virtualenv/__virtualenv__/zipp-3.4.0-py3-none-any/zipp.py
@@ -0,0 +1,314 @@
+import io
+import posixpath
+import zipfile
+import itertools
+import contextlib
+import sys
+import pathlib
+
+if sys.version_info < (3, 7):
+ from collections import OrderedDict
+else:
+ OrderedDict = dict
+
+
+def _parents(path):
+ """
+ Given a path with elements separated by
+ posixpath.sep, generate all parents of that path.
+
+ >>> list(_parents('b/d'))
+ ['b']
+ >>> list(_parents('/b/d/'))
+ ['/b']
+ >>> list(_parents('b/d/f/'))
+ ['b/d', 'b']
+ >>> list(_parents('b'))
+ []
+ >>> list(_parents(''))
+ []
+ """
+ return itertools.islice(_ancestry(path), 1, None)
+
+
+def _ancestry(path):
+ """
+ Given a path with elements separated by
+ posixpath.sep, generate all elements of that path
+
+ >>> list(_ancestry('b/d'))
+ ['b/d', 'b']
+ >>> list(_ancestry('/b/d/'))
+ ['/b/d', '/b']
+ >>> list(_ancestry('b/d/f/'))
+ ['b/d/f', 'b/d', 'b']
+ >>> list(_ancestry('b'))
+ ['b']
+ >>> list(_ancestry(''))
+ []
+ """
+ path = path.rstrip(posixpath.sep)
+ while path and path != posixpath.sep:
+ yield path
+ path, tail = posixpath.split(path)
+
+
+_dedupe = OrderedDict.fromkeys
+"""Deduplicate an iterable in original order"""
+
+
+def _difference(minuend, subtrahend):
+ """
+ Return items in minuend not in subtrahend, retaining order
+ with O(1) lookup.
+ """
+ return itertools.filterfalse(set(subtrahend).__contains__, minuend)
+
+
+class CompleteDirs(zipfile.ZipFile):
+ """
+ A ZipFile subclass that ensures that implied directories
+ are always included in the namelist.
+ """
+
+ @staticmethod
+ def _implied_dirs(names):
+ parents = itertools.chain.from_iterable(map(_parents, names))
+ as_dirs = (p + posixpath.sep for p in parents)
+ return _dedupe(_difference(as_dirs, names))
+
+ def namelist(self):
+ names = super(CompleteDirs, self).namelist()
+ return names + list(self._implied_dirs(names))
+
+ def _name_set(self):
+ return set(self.namelist())
+
+ def resolve_dir(self, name):
+ """
+ If the name represents a directory, return that name
+ as a directory (with the trailing slash).
+ """
+ names = self._name_set()
+ dirname = name + '/'
+ dir_match = name not in names and dirname in names
+ return dirname if dir_match else name
+
+ @classmethod
+ def make(cls, source):
+ """
+ Given a source (filename or zipfile), return an
+ appropriate CompleteDirs subclass.
+ """
+ if isinstance(source, CompleteDirs):
+ return source
+
+ if not isinstance(source, zipfile.ZipFile):
+ return cls(_pathlib_compat(source))
+
+ # Only allow for FastLookup when supplied zipfile is read-only
+ if 'r' not in source.mode:
+ cls = CompleteDirs
+
+ source.__class__ = cls
+ return source
+
+
+class FastLookup(CompleteDirs):
+ """
+ ZipFile subclass to ensure implicit
+ dirs exist and are resolved rapidly.
+ """
+
+ def namelist(self):
+ with contextlib.suppress(AttributeError):
+ return self.__names
+ self.__names = super(FastLookup, self).namelist()
+ return self.__names
+
+ def _name_set(self):
+ with contextlib.suppress(AttributeError):
+ return self.__lookup
+ self.__lookup = super(FastLookup, self)._name_set()
+ return self.__lookup
+
+
+def _pathlib_compat(path):
+ """
+ For path-like objects, convert to a filename for compatibility
+ on Python 3.6.1 and earlier.
+ """
+ try:
+ return path.__fspath__()
+ except AttributeError:
+ return str(path)
+
+
+class Path:
+ """
+ A pathlib-compatible interface for zip files.
+
+ Consider a zip file with this structure::
+
+ .
+ ├── a.txt
+ └── b
+ ├── c.txt
+ └── d
+ └── e.txt
+
+ >>> data = io.BytesIO()
+ >>> zf = zipfile.ZipFile(data, 'w')
+ >>> zf.writestr('a.txt', 'content of a')
+ >>> zf.writestr('b/c.txt', 'content of c')
+ >>> zf.writestr('b/d/e.txt', 'content of e')
+ >>> zf.filename = 'mem/abcde.zip'
+
+ Path accepts the zipfile object itself or a filename
+
+ >>> root = Path(zf)
+
+ From there, several path operations are available.
+
+ Directory iteration (including the zip file itself):
+
+ >>> a, b = root.iterdir()
+ >>> a
+ Path('mem/abcde.zip', 'a.txt')
+ >>> b
+ Path('mem/abcde.zip', 'b/')
+
+ name property:
+
+ >>> b.name
+ 'b'
+
+ join with divide operator:
+
+ >>> c = b / 'c.txt'
+ >>> c
+ Path('mem/abcde.zip', 'b/c.txt')
+ >>> c.name
+ 'c.txt'
+
+ Read text:
+
+ >>> c.read_text()
+ 'content of c'
+
+ existence:
+
+ >>> c.exists()
+ True
+ >>> (b / 'missing.txt').exists()
+ False
+
+ Coercion to string:
+
+ >>> import os
+ >>> str(c).replace(os.sep, posixpath.sep)
+ 'mem/abcde.zip/b/c.txt'
+
+ At the root, ``name``, ``filename``, and ``parent``
+ resolve to the zipfile. Note these attributes are not
+ valid and will raise a ``ValueError`` if the zipfile
+ has no filename.
+
+ >>> root.name
+ 'abcde.zip'
+ >>> str(root.filename).replace(os.sep, posixpath.sep)
+ 'mem/abcde.zip'
+ >>> str(root.parent)
+ 'mem'
+ """
+
+ __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
+
+ def __init__(self, root, at=""):
+ """
+ Construct a Path from a ZipFile or filename.
+
+ Note: When the source is an existing ZipFile object,
+ its type (__class__) will be mutated to a
+ specialized type. If the caller wishes to retain the
+ original type, the caller should either create a
+ separate ZipFile object or pass a filename.
+ """
+ self.root = FastLookup.make(root)
+ self.at = at
+
+ def open(self, mode='r', *args, pwd=None, **kwargs):
+ """
+ Open this entry as text or binary following the semantics
+ of ``pathlib.Path.open()`` by passing arguments through
+ to io.TextIOWrapper().
+ """
+ if self.is_dir():
+ raise IsADirectoryError(self)
+ zip_mode = mode[0]
+ if not self.exists() and zip_mode == 'r':
+ raise FileNotFoundError(self)
+ stream = self.root.open(self.at, zip_mode, pwd=pwd)
+ if 'b' in mode:
+ if args or kwargs:
+ raise ValueError("encoding args invalid for binary operation")
+ return stream
+ return io.TextIOWrapper(stream, *args, **kwargs)
+
+ @property
+ def name(self):
+ return pathlib.Path(self.at).name or self.filename.name
+
+ @property
+ def filename(self):
+ return pathlib.Path(self.root.filename).joinpath(self.at)
+
+ def read_text(self, *args, **kwargs):
+ with self.open('r', *args, **kwargs) as strm:
+ return strm.read()
+
+ def read_bytes(self):
+ with self.open('rb') as strm:
+ return strm.read()
+
+ def _is_child(self, path):
+ return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
+
+ def _next(self, at):
+ return self.__class__(self.root, at)
+
+ def is_dir(self):
+ return not self.at or self.at.endswith("/")
+
+ def is_file(self):
+ return self.exists() and not self.is_dir()
+
+ def exists(self):
+ return self.at in self.root._name_set()
+
+ def iterdir(self):
+ if not self.is_dir():
+ raise ValueError("Can't listdir a file")
+ subs = map(self._next, self.root.namelist())
+ return filter(self._is_child, subs)
+
+ def __str__(self):
+ return posixpath.join(self.root.filename, self.at)
+
+ def __repr__(self):
+ return self.__repr.format(self=self)
+
+ def joinpath(self, *other):
+ next = posixpath.join(self.at, *map(_pathlib_compat, other))
+ return self._next(self.root.resolve_dir(next))
+
+ __truediv__ = joinpath
+
+ @property
+ def parent(self):
+ if not self.at:
+ return self.filename.parent
+ parent_at = posixpath.dirname(self.at.rstrip('/'))
+ if parent_at:
+ parent_at += '/'
+ return self._next(parent_at)
diff --git a/third_party/python/virtualenv/distributions.json b/third_party/python/virtualenv/distributions.json
new file mode 100644
index 0000000000..7a56846d1c
--- /dev/null
+++ b/third_party/python/virtualenv/distributions.json
@@ -0,0 +1,83 @@
+{
+ "3.9": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info"
+ }
+ },
+ "3.8": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info"
+ }
+ },
+ "3.7": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info",
+ "zipp": "__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info"
+ }
+ },
+ "3.6": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata-3.1.1.dist-info",
+ "zipp": "__virtualenv__/zipp-3.4.0-py3-none-any/zipp-3.4.0.dist-info",
+ "importlib_resources": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources-3.3.0.dist-info"
+ }
+ },
+ "3.5": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata-2.1.1.dist-info",
+ "zipp": "__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info",
+ "importlib_resources": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources-3.2.1.dist-info"
+ }
+ },
+ "3.4": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info",
+ "zipp": "__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info",
+ "importlib_resources": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info",
+ "typing": "__virtualenv__/typing-3.7.4.1-py3-none-any/typing-3.7.4.1.dist-info",
+ "pathlib2": "__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info",
+ "scandir": "__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info"
+ }
+ },
+ "2.7": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs-1.4.4.dist-info",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib-0.3.1.dist-info",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock-3.0.12.dist-info",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six-1.15.0.dist-info",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata-1.1.3.dist-info",
+ "zipp": "__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp-1.2.0.dist-info",
+ "importlib_resources": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources-1.0.2.dist-info",
+ "typing": "__virtualenv__/typing-3.7.4.3-py2-none-any/typing-3.7.4.3.dist-info",
+ "pathlib2": "__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info",
+ "scandir": "__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir-1.10.0.dist-info",
+ "contextlib2": "__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2-0.6.0.post1.dist-info",
+ "configparser": "__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser-4.0.2.dist-info"
+ },
+ "!=win32": {
+ "pathlib2": "__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2-2.3.5.dist-info"
+ }
+ }
+} \ No newline at end of file
diff --git a/third_party/python/virtualenv/modules.json b/third_party/python/virtualenv/modules.json
new file mode 100644
index 0000000000..de3c039da7
--- /dev/null
+++ b/third_party/python/virtualenv/modules.json
@@ -0,0 +1,314 @@
+{
+ "3.9": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py",
+ "distlib.compat": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py",
+ "distlib.database": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py",
+ "distlib.index": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py",
+ "distlib.locators": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py",
+ "distlib.manifest": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py",
+ "distlib.markers": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py",
+ "distlib.metadata": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py",
+ "distlib.resources": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py",
+ "distlib.scripts": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py",
+ "distlib.util": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py",
+ "distlib.version": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py",
+ "distlib.wheel": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py",
+ "distlib._backport": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py",
+ "distlib._backport.misc": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py",
+ "distlib._backport.shutil": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py",
+ "distlib._backport.sysconfig": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py",
+ "distlib._backport.tarfile": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six.py"
+ }
+ },
+ "3.8": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py",
+ "distlib.compat": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py",
+ "distlib.database": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py",
+ "distlib.index": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py",
+ "distlib.locators": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py",
+ "distlib.manifest": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py",
+ "distlib.markers": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py",
+ "distlib.metadata": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py",
+ "distlib.resources": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py",
+ "distlib.scripts": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py",
+ "distlib.util": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py",
+ "distlib.version": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py",
+ "distlib.wheel": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py",
+ "distlib._backport": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py",
+ "distlib._backport.misc": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py",
+ "distlib._backport.shutil": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py",
+ "distlib._backport.sysconfig": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py",
+ "distlib._backport.tarfile": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six.py"
+ }
+ },
+ "3.7": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py",
+ "distlib.compat": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py",
+ "distlib.database": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py",
+ "distlib.index": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py",
+ "distlib.locators": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py",
+ "distlib.manifest": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py",
+ "distlib.markers": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py",
+ "distlib.metadata": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py",
+ "distlib.resources": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py",
+ "distlib.scripts": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py",
+ "distlib.util": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py",
+ "distlib.version": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py",
+ "distlib.wheel": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py",
+ "distlib._backport": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py",
+ "distlib._backport.misc": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py",
+ "distlib._backport.shutil": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py",
+ "distlib._backport.sysconfig": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py",
+ "distlib._backport.tarfile": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six.py",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/__init__.py",
+ "importlib_metadata._compat": "__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/_compat.py",
+ "zipp": "__virtualenv__/zipp-3.4.0-py3-none-any/zipp.py"
+ }
+ },
+ "3.6": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py",
+ "distlib.compat": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py",
+ "distlib.database": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py",
+ "distlib.index": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py",
+ "distlib.locators": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py",
+ "distlib.manifest": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py",
+ "distlib.markers": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py",
+ "distlib.metadata": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py",
+ "distlib.resources": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py",
+ "distlib.scripts": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py",
+ "distlib.util": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py",
+ "distlib.version": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py",
+ "distlib.wheel": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py",
+ "distlib._backport": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py",
+ "distlib._backport.misc": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py",
+ "distlib._backport.shutil": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py",
+ "distlib._backport.sysconfig": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py",
+ "distlib._backport.tarfile": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six.py",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/__init__.py",
+ "importlib_metadata._compat": "__virtualenv__/importlib_metadata-3.1.1-py3-none-any/importlib_metadata/_compat.py",
+ "zipp": "__virtualenv__/zipp-3.4.0-py3-none-any/zipp.py",
+ "importlib_resources": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/__init__.py",
+ "importlib_resources._common": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_common.py",
+ "importlib_resources._compat": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_compat.py",
+ "importlib_resources._py2": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py2.py",
+ "importlib_resources._py3": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/_py3.py",
+ "importlib_resources.abc": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/abc.py",
+ "importlib_resources.readers": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/readers.py",
+ "importlib_resources.trees": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/trees.py",
+ "importlib_resources.tests": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/__init__.py",
+ "importlib_resources.tests._compat": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/_compat.py",
+ "importlib_resources.tests.py27compat": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/py27compat.py",
+ "importlib_resources.tests.test_files": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/test_files.py",
+ "importlib_resources.tests.test_open": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/test_open.py",
+ "importlib_resources.tests.test_path": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/test_path.py",
+ "importlib_resources.tests.test_read": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/test_read.py",
+ "importlib_resources.tests.test_reader": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/test_reader.py",
+ "importlib_resources.tests.test_resource": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/test_resource.py",
+ "importlib_resources.tests.util": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/util.py",
+ "importlib_resources.tests.data01": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/data01/__init__.py",
+ "importlib_resources.tests.data01.subdirectory": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/data01/subdirectory/__init__.py",
+ "importlib_resources.tests.data02": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/data02/__init__.py",
+ "importlib_resources.tests.data02.one": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/data02/one/__init__.py",
+ "importlib_resources.tests.data02.two": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/data02/two/__init__.py",
+ "importlib_resources.tests.zipdata01": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/zipdata01/__init__.py",
+ "importlib_resources.tests.zipdata02": "__virtualenv__/importlib_resources-3.3.0-py2.py3-none-any/importlib_resources/tests/zipdata02/__init__.py"
+ }
+ },
+ "3.5": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py",
+ "distlib.compat": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py",
+ "distlib.database": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py",
+ "distlib.index": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py",
+ "distlib.locators": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py",
+ "distlib.manifest": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py",
+ "distlib.markers": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py",
+ "distlib.metadata": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py",
+ "distlib.resources": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py",
+ "distlib.scripts": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py",
+ "distlib.util": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py",
+ "distlib.version": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py",
+ "distlib.wheel": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py",
+ "distlib._backport": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py",
+ "distlib._backport.misc": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py",
+ "distlib._backport.shutil": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py",
+ "distlib._backport.sysconfig": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py",
+ "distlib._backport.tarfile": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six.py",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/__init__.py",
+ "importlib_metadata._compat": "__virtualenv__/importlib_metadata-2.1.1-py2.py3-none-any/importlib_metadata/_compat.py",
+ "zipp": "__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp.py",
+ "importlib_resources": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/__init__.py",
+ "importlib_resources._common": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_common.py",
+ "importlib_resources._compat": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_compat.py",
+ "importlib_resources._py2": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py2.py",
+ "importlib_resources._py3": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/_py3.py",
+ "importlib_resources.abc": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/abc.py",
+ "importlib_resources.readers": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/readers.py",
+ "importlib_resources.trees": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/trees.py",
+ "importlib_resources.tests": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/__init__.py",
+ "importlib_resources.tests._compat": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/_compat.py",
+ "importlib_resources.tests.py27compat": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/py27compat.py",
+ "importlib_resources.tests.test_files": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/test_files.py",
+ "importlib_resources.tests.test_open": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/test_open.py",
+ "importlib_resources.tests.test_path": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/test_path.py",
+ "importlib_resources.tests.test_read": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/test_read.py",
+ "importlib_resources.tests.test_reader": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/test_reader.py",
+ "importlib_resources.tests.test_resource": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/test_resource.py",
+ "importlib_resources.tests.util": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/util.py",
+ "importlib_resources.tests.data01": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/data01/__init__.py",
+ "importlib_resources.tests.data01.subdirectory": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/data01/subdirectory/__init__.py",
+ "importlib_resources.tests.data02": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/data02/__init__.py",
+ "importlib_resources.tests.data02.one": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/data02/one/__init__.py",
+ "importlib_resources.tests.data02.two": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/data02/two/__init__.py",
+ "importlib_resources.tests.zipdata01": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/zipdata01/__init__.py",
+ "importlib_resources.tests.zipdata02": "__virtualenv__/importlib_resources-3.2.1-py2.py3-none-any/importlib_resources/tests/zipdata02/__init__.py"
+ }
+ },
+ "3.4": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py",
+ "distlib.compat": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py",
+ "distlib.database": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py",
+ "distlib.index": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py",
+ "distlib.locators": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py",
+ "distlib.manifest": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py",
+ "distlib.markers": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py",
+ "distlib.metadata": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py",
+ "distlib.resources": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py",
+ "distlib.scripts": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py",
+ "distlib.util": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py",
+ "distlib.version": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py",
+ "distlib.wheel": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py",
+ "distlib._backport": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py",
+ "distlib._backport.misc": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py",
+ "distlib._backport.shutil": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py",
+ "distlib._backport.sysconfig": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py",
+ "distlib._backport.tarfile": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six.py",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/__init__.py",
+ "importlib_metadata._compat": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/_compat.py",
+ "importlib_metadata.docs": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/docs/__init__.py",
+ "importlib_metadata.docs.conf": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/docs/conf.py",
+ "importlib_metadata.tests": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/__init__.py",
+ "importlib_metadata.tests.fixtures": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/fixtures.py",
+ "importlib_metadata.tests.test_api": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_api.py",
+ "importlib_metadata.tests.test_integration": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_integration.py",
+ "importlib_metadata.tests.test_main": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_main.py",
+ "importlib_metadata.tests.test_zip": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_zip.py",
+ "importlib_metadata.tests.data": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/data/__init__.py",
+ "zipp": "__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp.py",
+ "importlib_resources": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/__init__.py",
+ "importlib_resources._compat": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_compat.py",
+ "importlib_resources._py2": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py2.py",
+ "importlib_resources._py3": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py3.py",
+ "importlib_resources.abc": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/abc.py",
+ "importlib_resources.docs.conf": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/docs/conf.py",
+ "importlib_resources.tests": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/__init__.py",
+ "importlib_resources.tests.test_open": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_open.py",
+ "importlib_resources.tests.test_path": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_path.py",
+ "importlib_resources.tests.test_read": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_read.py",
+ "importlib_resources.tests.test_resource": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_resource.py",
+ "importlib_resources.tests.util": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/util.py",
+ "importlib_resources.tests.data01": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data01/__init__.py",
+ "importlib_resources.tests.data01.subdirectory": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data01/subdirectory/__init__.py",
+ "importlib_resources.tests.data02": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data02/__init__.py",
+ "importlib_resources.tests.data02.one": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data02/one/__init__.py",
+ "importlib_resources.tests.data02.two": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data02/two/__init__.py",
+ "importlib_resources.tests.data03": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data03/__init__.py",
+ "importlib_resources.tests.zipdata01": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/zipdata01/__init__.py",
+ "importlib_resources.tests.zipdata02": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/zipdata02/__init__.py",
+ "typing": "__virtualenv__/typing-3.7.4.1-py3-none-any/typing.py",
+ "pathlib2": "__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2/__init__.py",
+ "scandir": "__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir.py"
+ }
+ },
+ "2.7": {
+ "==any": {
+ "appdirs": "__virtualenv__/appdirs-1.4.4-py2.py3-none-any/appdirs.py",
+ "distlib": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/__init__.py",
+ "distlib.compat": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/compat.py",
+ "distlib.database": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/database.py",
+ "distlib.index": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/index.py",
+ "distlib.locators": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/locators.py",
+ "distlib.manifest": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/manifest.py",
+ "distlib.markers": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/markers.py",
+ "distlib.metadata": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/metadata.py",
+ "distlib.resources": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/resources.py",
+ "distlib.scripts": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/scripts.py",
+ "distlib.util": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/util.py",
+ "distlib.version": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/version.py",
+ "distlib.wheel": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/wheel.py",
+ "distlib._backport": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/__init__.py",
+ "distlib._backport.misc": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/misc.py",
+ "distlib._backport.shutil": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/shutil.py",
+ "distlib._backport.sysconfig": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/sysconfig.py",
+ "distlib._backport.tarfile": "__virtualenv__/distlib-0.3.1-py2.py3-none-any/distlib/_backport/tarfile.py",
+ "filelock": "__virtualenv__/filelock-3.0.12-py3-none-any/filelock.py",
+ "six": "__virtualenv__/six-1.15.0-py2.py3-none-any/six.py",
+ "importlib_metadata": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/__init__.py",
+ "importlib_metadata._compat": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/_compat.py",
+ "importlib_metadata.docs": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/docs/__init__.py",
+ "importlib_metadata.docs.conf": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/docs/conf.py",
+ "importlib_metadata.tests": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/__init__.py",
+ "importlib_metadata.tests.fixtures": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/fixtures.py",
+ "importlib_metadata.tests.test_api": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_api.py",
+ "importlib_metadata.tests.test_integration": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_integration.py",
+ "importlib_metadata.tests.test_main": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_main.py",
+ "importlib_metadata.tests.test_zip": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/test_zip.py",
+ "importlib_metadata.tests.data": "__virtualenv__/importlib_metadata-1.1.3-py2.py3-none-any/importlib_metadata/tests/data/__init__.py",
+ "zipp": "__virtualenv__/zipp-1.2.0-py2.py3-none-any/zipp.py",
+ "importlib_resources": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/__init__.py",
+ "importlib_resources._compat": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_compat.py",
+ "importlib_resources._py2": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py2.py",
+ "importlib_resources._py3": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/_py3.py",
+ "importlib_resources.abc": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/abc.py",
+ "importlib_resources.docs.conf": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/docs/conf.py",
+ "importlib_resources.tests": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/__init__.py",
+ "importlib_resources.tests.test_open": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_open.py",
+ "importlib_resources.tests.test_path": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_path.py",
+ "importlib_resources.tests.test_read": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_read.py",
+ "importlib_resources.tests.test_resource": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/test_resource.py",
+ "importlib_resources.tests.util": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/util.py",
+ "importlib_resources.tests.data01": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data01/__init__.py",
+ "importlib_resources.tests.data01.subdirectory": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data01/subdirectory/__init__.py",
+ "importlib_resources.tests.data02": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data02/__init__.py",
+ "importlib_resources.tests.data02.one": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data02/one/__init__.py",
+ "importlib_resources.tests.data02.two": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data02/two/__init__.py",
+ "importlib_resources.tests.data03": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/data03/__init__.py",
+ "importlib_resources.tests.zipdata01": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/zipdata01/__init__.py",
+ "importlib_resources.tests.zipdata02": "__virtualenv__/importlib_resources-1.0.2-py2.py3-none-any/importlib_resources/tests/zipdata02/__init__.py",
+ "typing": "__virtualenv__/typing-3.7.4.3-py2-none-any/typing.py",
+ "pathlib2": "__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2/__init__.py",
+ "scandir": "__virtualenv__/scandir-1.10.0-cp39-cp39-macosx_10_15_x86_64/scandir.py",
+ "contextlib2": "__virtualenv__/contextlib2-0.6.0.post1-py2.py3-none-any/contextlib2.py",
+ "configparser": "__virtualenv__/configparser-4.0.2-py2.py3-none-any/configparser.py",
+ "backports": "__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/__init__.py",
+ "backports.configparser": "__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/__init__.py",
+ "backports.configparser.helpers": "__virtualenv__/configparser-4.0.2-py2.py3-none-any/backports/configparser/helpers.py"
+ },
+ "!=win32": {
+ "pathlib2": "__virtualenv__/pathlib2-2.3.5-py2.py3-none-any/pathlib2/__init__.py"
+ }
+ }
+} \ No newline at end of file
diff --git a/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/LICENSE b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/LICENSE
new file mode 100644
index 0000000000..be9700d61a
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2020-202x The virtualenv developers
+
+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/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/METADATA b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/METADATA
new file mode 100644
index 0000000000..9c0e3ad119
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/METADATA
@@ -0,0 +1,92 @@
+Metadata-Version: 2.1
+Name: virtualenv
+Version: 20.2.2
+Summary: Virtual Python Environment builder
+Home-page: https://virtualenv.pypa.io/
+Author: Bernat Gabor
+Author-email: gaborjbernat@gmail.com
+Maintainer: Bernat Gabor
+Maintainer-email: gaborjbernat@gmail.com
+License: MIT
+Project-URL: Source, https://github.com/pypa/virtualenv
+Project-URL: Tracker, https://github.com/pypa/virtualenv/issues
+Keywords: virtual,environments,isolated
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Testing
+Classifier: Topic :: Utilities
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7
+Description-Content-Type: text/markdown
+Requires-Dist: appdirs (<2,>=1.4.3)
+Requires-Dist: distlib (<1,>=0.3.1)
+Requires-Dist: filelock (<4,>=3.0.0)
+Requires-Dist: six (<2,>=1.9.0)
+Requires-Dist: pathlib2 (<3,>=2.3.3) ; python_version < "3.4" and sys_platform != "win32"
+Requires-Dist: importlib-resources (>=1.0) ; python_version < "3.7"
+Requires-Dist: importlib-metadata (>=0.12) ; python_version < "3.8"
+Provides-Extra: docs
+Requires-Dist: proselint (>=0.10.2) ; extra == 'docs'
+Requires-Dist: sphinx (>=3) ; extra == 'docs'
+Requires-Dist: sphinx-argparse (>=0.2.5) ; extra == 'docs'
+Requires-Dist: sphinx-rtd-theme (>=0.4.3) ; extra == 'docs'
+Requires-Dist: towncrier (>=19.9.0rc1) ; extra == 'docs'
+Provides-Extra: testing
+Requires-Dist: coverage (>=4) ; extra == 'testing'
+Requires-Dist: coverage-enable-subprocess (>=1) ; extra == 'testing'
+Requires-Dist: flaky (>=3) ; extra == 'testing'
+Requires-Dist: pytest (>=4) ; extra == 'testing'
+Requires-Dist: pytest-env (>=0.6.2) ; extra == 'testing'
+Requires-Dist: pytest-freezegun (>=0.4.1) ; extra == 'testing'
+Requires-Dist: pytest-mock (>=2) ; extra == 'testing'
+Requires-Dist: pytest-randomly (>=1) ; extra == 'testing'
+Requires-Dist: pytest-timeout (>=1) ; extra == 'testing'
+Requires-Dist: pytest-xdist (>=1.31.0) ; extra == 'testing'
+Requires-Dist: packaging (>=20.0) ; (python_version > "3.4") and extra == 'testing'
+Requires-Dist: xonsh (>=0.9.16) ; (python_version > "3.4" and python_version != "3.9") and extra == 'testing'
+
+# virtualenv
+
+[![PyPI](https://img.shields.io/pypi/v/virtualenv?style=flat-square)](https://pypi.org/project/virtualenv)
+[![PyPI - Implementation](https://img.shields.io/pypi/implementation/virtualenv?style=flat-square)](https://pypi.org/project/virtualenv)
+[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/virtualenv?style=flat-square)](https://pypi.org/project/virtualenv)
+[![Documentation](https://readthedocs.org/projects/virtualenv/badge/?version=latest&style=flat-square)](http://virtualenv.pypa.io)
+[![Gitter Chat](https://img.shields.io/gitter/room/pypa/virtualenv?color=FF004F&style=flat-square)](https://gitter.im/pypa/virtualenv)
+[![PyPI - Downloads](https://img.shields.io/pypi/dm/virtualenv?style=flat-square)](https://pypistats.org/packages/virtualenv)
+[![PyPI - License](https://img.shields.io/pypi/l/virtualenv?style=flat-square)](https://opensource.org/licenses/MIT)
+[![Build Status](https://github.com/pypa/virtualenv/workflows/check/badge.svg?branch=main&event=push)](https://github.com/pypa/virtualenv/actions?query=workflow%3Acheck)
+[![codecov](https://codecov.io/gh/pypa/virtualenv/branch/main/graph/badge.svg)](https://codecov.io/gh/pypa/virtualenv)
+[![Code style:
+black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black)
+
+A tool for creating isolated `virtual` python environments.
+
+- [Installation](https://virtualenv.pypa.io/en/latest/installation.html)
+- [Documentation](https://virtualenv.pypa.io)
+- [Changelog](https://virtualenv.pypa.io/en/latest/changelog.html)
+- [Issues](https://github.com/pypa/virtualenv/issues)
+- [PyPI](https://pypi.org/project/virtualenv)
+- [Github](https://github.com/pypa/virtualenv)
+
+## Code of Conduct
+
+Everyone interacting in the virtualenv project's codebases, issue trackers, chat rooms, and mailing lists is expected to
+follow the [PSF Code of Conduct](https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md).
+
+
diff --git a/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/RECORD b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/RECORD
new file mode 100644
index 0000000000..b8808400e6
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/RECORD
@@ -0,0 +1,122 @@
+virtualenv/__init__.py,sha256=SMvpjz4VJ3vJ_yfDDPzJAdi2GJOYd_UBXXuvImO07gk,205
+virtualenv/__main__.py,sha256=QMwDqrR4QbhEivl8yoRmAr6G1BY92gr4n1ConcDIxc4,2770
+virtualenv/info.py,sha256=-2pI_kyC9fNj5OR8AQWkKjlpOk4_96Lmbco3atYYBdY,1921
+virtualenv/report.py,sha256=M2OHHCWdOHZsn74tj1MYYKmaI3QRJF8VA1FZIdkQTMQ,1594
+virtualenv/version.py,sha256=T9L0FIrWWe1IEvi_PNtZQcEIf_WbHAtFeLA1_hwZ07I,65
+virtualenv/activation/__init__.py,sha256=jLIERxJXMnHq2fH49RdWqBoaiASres4CTKMdUJOeos0,480
+virtualenv/activation/activator.py,sha256=CXomkRvhzcAeygYlDwQdDjfPyZQG85aBab5GIVQPv2M,1341
+virtualenv/activation/via_template.py,sha256=U8LgH-lyTjXIQBUdbd0xOZpXNICpiKhsfpiZwzQg7tU,2372
+virtualenv/activation/bash/__init__.py,sha256=7aC1WfvyzgFrIQs13jOuESuAbuiAnTsKkOe0iReRoaE,312
+virtualenv/activation/bash/activate.sh,sha256=aHia5vyXg2JwymkvRXCp29Aswcg88Mz5UrssXbX9Jjc,2398
+virtualenv/activation/batch/__init__.py,sha256=K0gVfwuXV7uoaMDL7moWGCq7uTDzI64giZzQQ8s2qnU,733
+virtualenv/activation/batch/activate.bat,sha256=PeQnWWsjvHT-jIWhYI7hbdzkDBZx5UOstnsCmq5PYtw,1031
+virtualenv/activation/batch/deactivate.bat,sha256=6OznnO-HC2wnWUN7YAT-bj815zeKMXEPC0keyBYwKUU,510
+virtualenv/activation/batch/pydoc.bat,sha256=pVuxn8mn9P_Rd0349fiBEiwIuMvfJQSfgJ2dljUT2fA,24
+virtualenv/activation/cshell/__init__.py,sha256=pw4s5idqQhaEccPxadETEvilBcoxW-UkVQ-RNqPyVCQ,344
+virtualenv/activation/cshell/activate.csh,sha256=jYwms8OTiVu9MJwXltuEm43HU09BJUqkrVqyj4sjpDA,1468
+virtualenv/activation/fish/__init__.py,sha256=hDkJq1P1wK2qm6BXydXWA9GMkBpj-TaejbKSceFnGZU,251
+virtualenv/activation/fish/activate.fish,sha256=V7nVwSI_nsFEMlJjSQxCayNWkjubXi1KSgSw1bEakh8,3099
+virtualenv/activation/powershell/__init__.py,sha256=EA-73s5TUMkgxAhLwucFg3gsBwW5huNh7qB4I7uEU-U,256
+virtualenv/activation/powershell/activate.ps1,sha256=jVw_FwfVJzcByQ3Sku-wlnOo_a0-OSpAQ8R17kXVgIM,1807
+virtualenv/activation/python/__init__.py,sha256=Uv53LqOrIT_2dO1FXcUYAnwH1eypG8CJ2InhSx1GRI4,1323
+virtualenv/activation/python/activate_this.py,sha256=Xpz7exdGSjmWk0KfwHLofIpDPUtazNSNGrxT0-5ZG_s,1208
+virtualenv/activation/xonsh/__init__.py,sha256=7NUevd5EpHRMZdSyR1KgFTe9QQBO94zZOwFH6MR6zjo,355
+virtualenv/activation/xonsh/activate.xsh,sha256=qkKgWfrUjYKrgrmhf45VuBz99EMadtiNU8GMfHZZ7AU,1172
+virtualenv/app_data/__init__.py,sha256=nwgqY-Our_SYcDisLfRLmWrTSPytDkjck9-lzg-pOI8,1462
+virtualenv/app_data/base.py,sha256=dbS5Maob1-Cqs6EVqTmmbjAGeNYA1iw3pmdgYPWCJak,2129
+virtualenv/app_data/na.py,sha256=iMRVpCe4m5Q5WM5bC3ee1wYyfkfHvkcQ-8tgIw4druc,1306
+virtualenv/app_data/read_only.py,sha256=MD-4Bl2SZZiGw0g8qZy0YLBGZGCuFYXnAEvWboF1PSc,1006
+virtualenv/app_data/via_disk_folder.py,sha256=CdNXQkenyH178MtSs2Ve6uDUs30-oZpkOz_1guTtTz0,5597
+virtualenv/app_data/via_tempdir.py,sha256=Z_-PoU7qeZe-idzi3nqys4FX0rfsRgOQ9_7XwX3hxSA,770
+virtualenv/config/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57
+virtualenv/config/convert.py,sha256=WYGjMRKVriZkfTH3z1fI0sDQRZxCxAedqWbOGsaquyg,2693
+virtualenv/config/env_var.py,sha256=48XpOurSLLjMX-kXjvOpZuAoOUP-LvnbotTlmebhhFk,844
+virtualenv/config/ini.py,sha256=neMqXrA6IOkLF_M_MCQWQSeqNm4CT8tj_h3GdbJv1Cg,2783
+virtualenv/config/cli/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57
+virtualenv/config/cli/parser.py,sha256=y5IqHccLBqFpocpE75X611nVrP8v394VW94a9GAojvE,4524
+virtualenv/create/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57
+virtualenv/create/creator.py,sha256=4jxxEGXCWd6tInT37QNt-13_yDtcIJdPB6EkoYzDkbM,8889
+virtualenv/create/debug.py,sha256=ETOke8w4Ib8fiufAHVeOkH3v0zrztljw3WjGvZyE0Mk,3342
+virtualenv/create/describe.py,sha256=bm0V2wpFOjdN_MkzZuJAEBSttmi5YGPVwxtwGYU5zQU,3561
+virtualenv/create/pyenv_cfg.py,sha256=VsOGfzUpaVCO3J29zrhIeip4jZ4b7llbe45iOQAIRGg,1717
+virtualenv/create/via_global_ref/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/create/via_global_ref/_virtualenv.py,sha256=aEuMB5MrpKhKwuWumv5J7wTpK6w9jUGR1FXPCdCT5fw,5662
+virtualenv/create/via_global_ref/api.py,sha256=5MPq3XJBuUOBj53oIigeWWPm68M-J_E644WWbz37qOU,4357
+virtualenv/create/via_global_ref/store.py,sha256=cqLBEhQ979xHnlidqmxlDjsvj2Wr-mBo7shvGQSEBxU,685
+virtualenv/create/via_global_ref/venv.py,sha256=p5RkDcXhr1pmOwnl1dpS06UYHmfNVy2ld4sTwsYjYWU,2955
+virtualenv/create/via_global_ref/builtin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/create/via_global_ref/builtin/builtin_way.py,sha256=hO22nT-itVoYiy8wXrXXYzHw86toCp_Uq-cURR7w6ck,546
+virtualenv/create/via_global_ref/builtin/ref.py,sha256=xCTICJhE-OiopBxl6ymo1P1NqgK3KEF8ZUOtQDtDTVA,5477
+virtualenv/create/via_global_ref/builtin/via_global_self_do.py,sha256=d569fX7fjq5vHvGGXDjo-1Xi__HhqU2xjDJOuYrmGjw,4552
+virtualenv/create/via_global_ref/builtin/cpython/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57
+virtualenv/create/via_global_ref/builtin/cpython/common.py,sha256=U7EvB9-2DlOQTGrTyPrEcItEbJ1sFBzo1EAOcAIjQ5Q,2392
+virtualenv/create/via_global_ref/builtin/cpython/cpython2.py,sha256=p41H2g6wAqhJzeUU48nH3u05-yWEbwCzhyj4pn8rnm4,3757
+virtualenv/create/via_global_ref/builtin/cpython/cpython3.py,sha256=gguQAhTQb0PH7Xg-G-mgQm5LlhyyW0V0piV3LwI-PeM,3111
+virtualenv/create/via_global_ref/builtin/cpython/mac_os.py,sha256=B0Lqgo8geZBSKSpHWUB46lDYRggW4Kg2AZUp3Z7xn9M,12382
+virtualenv/create/via_global_ref/builtin/pypy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/create/via_global_ref/builtin/pypy/common.py,sha256=-t-TZxCTJwpIh_oRsDyv5IilH19jKqJrZa27zWN_8Ws,1816
+virtualenv/create/via_global_ref/builtin/pypy/pypy2.py,sha256=bmMY_KJZ1iD_ifq-X9ZBOlOpJ1aN7839qigBgnWRIdA,3535
+virtualenv/create/via_global_ref/builtin/pypy/pypy3.py,sha256=ti6hmOIC4HiTBnEYKytO-d9wH-eLeMoQxQ0kZRhnNrw,1751
+virtualenv/create/via_global_ref/builtin/python2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/create/via_global_ref/builtin/python2/python2.py,sha256=jkJwmkeJVTzwzo95eMIptTfdBA-qmyIqZcpt48iOitU,4276
+virtualenv/create/via_global_ref/builtin/python2/site.py,sha256=4uguJDuWPmB25yBmpsMYKLOnIVXkerck0UO8CP8F2c4,6078
+virtualenv/discovery/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57
+virtualenv/discovery/builtin.py,sha256=rB6XaQwuK1HfvJsrla3BoSQUH9QkJnwKHGWBdbK4QGM,5432
+virtualenv/discovery/cached_py_info.py,sha256=l2lELE8YkwKXCNopImY2VjmpHPTawh1d3qmdsXMtkRs,5043
+virtualenv/discovery/discover.py,sha256=evJYn4APkwjNmdolNeIBSHiOudkvN59c5oVYI2Zsjlg,1209
+virtualenv/discovery/py_info.py,sha256=QtZFq0xav1tEpKI5seEJaEOkc_FXer21Gzgl_Ccqy98,21793
+virtualenv/discovery/py_spec.py,sha256=wQhLzCfXoSPsAAO9nm5-I2lNolVDux4W2vPSUfJGjlc,4790
+virtualenv/discovery/windows/__init__.py,sha256=TPbnzCtRyw47pRVHTO8ikwljNcczxmSLDdWtwasxvQU,1036
+virtualenv/discovery/windows/pep514.py,sha256=YYiaJzo-XuMtO78BMFMAudqkeJiLQkFnUTOuQZ5lJz8,5451
+virtualenv/run/__init__.py,sha256=lVIiIq_LoMHUGYkrTSx0tpFG_aYywy_u6GWUReHRcUA,5777
+virtualenv/run/session.py,sha256=S4NZiHzij1vp895mN9s9ZwYobJjjdP37QOHCb1o-Ufo,2563
+virtualenv/run/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/run/plugin/activators.py,sha256=kmHShj36eHfbnsiAJzX0U5LYvGhe0WkRYjbuKDz6gVM,2117
+virtualenv/run/plugin/base.py,sha256=-2185C01PaxOG7gnMbWWyZlo24n_FYo5J5_naeNZw8s,1934
+virtualenv/run/plugin/creators.py,sha256=PIxJ85KmrQU7lUO-r8Znxbb4lTEzwHggc9lcDqmt2tc,3494
+virtualenv/run/plugin/discovery.py,sha256=3ykxRvPA1FJMkqsbr2TV0LBRPT5UCFeJdzEHfuEjxRM,1002
+virtualenv/run/plugin/seeders.py,sha256=c1mhzu0HNzKdif6YUV35fuAOS0XHFJz3TtccLW5fWG0,1074
+virtualenv/seed/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57
+virtualenv/seed/seeder.py,sha256=DSGE_8Ycj01vj8mkppUBA9h7JG76XsVBMt-5MWlMF6k,1178
+virtualenv/seed/embed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/seed/embed/base_embed.py,sha256=46mWtqWj_MjOQEqMJyosL0RWGL6HwrHAL2r1Jxc9DuI,4182
+virtualenv/seed/embed/pip_invoke.py,sha256=EMVwIeoW15SuorJ8z_-vBxPXwQJLS0ILA0Va9zNoOLI,2127
+virtualenv/seed/embed/via_app_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/seed/embed/via_app_data/via_app_data.py,sha256=NkVhEFv4iuKG0qvEg4AAmucMwmQgNaPLB-Syepzgps0,5994
+virtualenv/seed/embed/via_app_data/pip_install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+virtualenv/seed/embed/via_app_data/pip_install/base.py,sha256=rnR60JzM7G04cPDo2eH-aR8-iQuFXBgHJ2lQnSf0Gfs,6355
+virtualenv/seed/embed/via_app_data/pip_install/copy.py,sha256=gG2NePFHOYh-bsCf6TpsaQ_qrYhdBy67k0RWuwHSAwo,1307
+virtualenv/seed/embed/via_app_data/pip_install/symlink.py,sha256=wHCpfKobvjjaZLUSwM3FSCblZfiBFw4IQYsxwlfEEu0,2362
+virtualenv/seed/wheels/__init__.py,sha256=1J7el7lNjAwGxM4dmricrbVhSbYxs5sPzv9PTx2A6qA,226
+virtualenv/seed/wheels/acquire.py,sha256=qchqlIynLi2VP2VtdAfVfZJHbUPcLY2Ui5r7Eh-aZz8,4426
+virtualenv/seed/wheels/bundle.py,sha256=W0uVjClv9IBa50jRvPKm0jMwWnrYTEfDny2Z6bw2W7c,1835
+virtualenv/seed/wheels/periodic_update.py,sha256=HNVEuU2OYdWHW7lVO0h3NkpLkC8bu-5R7igJRXBnGDc,12792
+virtualenv/seed/wheels/util.py,sha256=Zdo76KEDqbNmM5u9JTuyu5uzEN_fQ4oj6qHOt0h0o1M,3960
+virtualenv/seed/wheels/embed/__init__.py,sha256=CLMKoeveDRyiNAdZjEtD38cepgNXkg65xzFu5OSHEus,1995
+virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl,sha256=mTE08EdUcbkUUsoCnUOQ3I8pisY6cSgU8QHNG220ZnY,1360957
+virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl,sha256=Ql55sgk5q7_6djOpEVGogq7cd1ZNkxPjWE6wQWwoxVg,1518513
+virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl,sha256=pn-qUVGe8ozYJhr_DiIbbkw3D4-4utqKo-etiUUZmWM,583228
+virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl,sha256=J6cUwJJTE05gpvpoEw94xwN-VWLE8h-PMY8q6QDRUtU,583493
+virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl,sha256=LCQqCFb7rX775WDfSnrdkyTzQM9I30NlHpYEkkRmeUo,785194
+virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl,sha256=jBd5NiFZRcmjfvgJraD6s2UZGVL3oSNhhDK7-sNTxSk,785164
+virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl,sha256=9NoXY9O-zy4s2SoUp8kg8PAOyjD93p6pksg2aFufryg,21556
+virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl,sha256=kGhk-3IsCrXy-cNbLGXjrzwAlALBCKcJwKyie8LJGHs,34788
+virtualenv/util/__init__.py,sha256=om6Hs2lH5igf5lkcSmQFiU7iMZ0Wx4dmSlMc6XW_Llg,199
+virtualenv/util/error.py,sha256=SRSZlXvMYQuJwxoUfNhlAyo3VwrAnIsZemSwPOxpjns,352
+virtualenv/util/lock.py,sha256=oFa0FcbE_TVDHOol44Mgtfa4D3ZjnVy-HSQx-y7ERKQ,4727
+virtualenv/util/six.py,sha256=_8KWXUWi3-AaFmz4LkdyNra-uNuf70vlxwjN7oeRo8g,1463
+virtualenv/util/zipapp.py,sha256=jtf4Vn7XBnjPs_B_ObIQv_x4pFlIlPKAWHYLFV59h6U,1054
+virtualenv/util/path/__init__.py,sha256=YaBAxtzGBdMu0uUtppe0ZeCHw5HhO-5zjeb3-fzyMoI,336
+virtualenv/util/path/_permission.py,sha256=XpO2vGAk_92_biD4MEQcAQq2Zc8_rpm3M3n_hMUA1rw,745
+virtualenv/util/path/_sync.py,sha256=rheUrGsCqmhMwNs-uc5rDthNSUlsOrBJPoK8KZj3O1o,2393
+virtualenv/util/path/_pathlib/__init__.py,sha256=FjKCi8scB5MnHg2fLX5REoE0bOPkMXqpBEILVTeJZGQ,2130
+virtualenv/util/path/_pathlib/via_os_path.py,sha256=fYDFAX483zVvC9hAOAC9FYtrGdZethS0vtYtKsL5r-s,3772
+virtualenv/util/subprocess/__init__.py,sha256=1UmFrdBv2sVeUfZbDcO2yZpe28AE0ULOu9dRKlpJaa0,801
+virtualenv/util/subprocess/_win_subprocess.py,sha256=SChkXAKVbpehyrHod1ld76RSdTIalrgME1rtz5jUfm0,5655
+virtualenv-20.2.2.dist-info/LICENSE,sha256=XBWRk3jFsqqrexnOpw2M3HX3aHnjJFTkwDmfi3HRcek,1074
+virtualenv-20.2.2.dist-info/METADATA,sha256=OWyC_GXU3AvST-YiGhmI2iE4ntdcBm-6Q1yCaU9Bx_U,4965
+virtualenv-20.2.2.dist-info/WHEEL,sha256=oh0NKYrTcu1i1-wgrI1cnhkjYIi8WJ-8qd9Jrr5_y4E,110
+virtualenv-20.2.2.dist-info/entry_points.txt,sha256=1DALKzYOcffJa7Q15TQlMQu0yeFXEy5W124y0aJEfYU,1615
+virtualenv-20.2.2.dist-info/top_level.txt,sha256=JV-LVlC8YeIw1DgiYI0hEot7tgFy5IWdKVcSG7NyzaI,11
+virtualenv-20.2.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
+virtualenv-20.2.2.dist-info/RECORD,,
diff --git a/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/WHEEL b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/WHEEL
new file mode 100644
index 0000000000..1f227afa9f
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.36.1)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/entry_points.txt b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/entry_points.txt
new file mode 100644
index 0000000000..3effb4ba11
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/entry_points.txt
@@ -0,0 +1,32 @@
+[console_scripts]
+virtualenv = virtualenv.__main__:run_with_catch
+
+[virtualenv.activate]
+bash = virtualenv.activation.bash:BashActivator
+batch = virtualenv.activation.batch:BatchActivator
+cshell = virtualenv.activation.cshell:CShellActivator
+fish = virtualenv.activation.fish:FishActivator
+powershell = virtualenv.activation.powershell:PowerShellActivator
+python = virtualenv.activation.python:PythonActivator
+xonsh = virtualenv.activation.xonsh:XonshActivator
+
+[virtualenv.create]
+cpython2-mac-framework = virtualenv.create.via_global_ref.builtin.cpython.mac_os:CPython2macOsFramework
+cpython2-posix = virtualenv.create.via_global_ref.builtin.cpython.cpython2:CPython2Posix
+cpython2-win = virtualenv.create.via_global_ref.builtin.cpython.cpython2:CPython2Windows
+cpython3-mac-framework = virtualenv.create.via_global_ref.builtin.cpython.mac_os:CPython3macOsFramework
+cpython3-posix = virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Posix
+cpython3-win = virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Windows
+pypy2-posix = virtualenv.create.via_global_ref.builtin.pypy.pypy2:PyPy2Posix
+pypy2-win = virtualenv.create.via_global_ref.builtin.pypy.pypy2:Pypy2Windows
+pypy3-posix = virtualenv.create.via_global_ref.builtin.pypy.pypy3:PyPy3Posix
+pypy3-win = virtualenv.create.via_global_ref.builtin.pypy.pypy3:Pypy3Windows
+venv = virtualenv.create.via_global_ref.venv:Venv
+
+[virtualenv.discovery]
+builtin = virtualenv.discovery.builtin:Builtin
+
+[virtualenv.seed]
+app-data = virtualenv.seed.embed.via_app_data.via_app_data:FromAppData
+pip = virtualenv.seed.embed.pip_invoke:PipInvoke
+
diff --git a/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/top_level.txt b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/top_level.txt
new file mode 100644
index 0000000000..66072c7645
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/top_level.txt
@@ -0,0 +1 @@
+virtualenv
diff --git a/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/zip-safe b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/zip-safe
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv-20.2.2.dist-info/zip-safe
@@ -0,0 +1 @@
+
diff --git a/third_party/python/virtualenv/virtualenv.py b/third_party/python/virtualenv/virtualenv.py
new file mode 100644
index 0000000000..2435b9dfdd
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv.py
@@ -0,0 +1,55 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This script is not to be imported.
+assert __name__ == '__main__'
+import os
+import sys
+# If some older version of virtualenv is installed, it may interfere with this one.
+# So filter-out site-packages and dist-packages directories where it might be installed.
+# This is kinda sorta like invoking python with -S, but invoking a virtualenv python 2.7
+# with -S is broken (sys.path becomes completely wrong, and even `import os` fails).
+# (And while yes, it is kind of silly to use a virtualenv python to run virtualenv, we
+# do)
+sys.path = [p for p in sys.path if os.path.basename(p) not in ('site-packages', 'dist-packages')]
+try:
+ import importlib.util
+ spec = importlib.util.spec_from_file_location('main', os.path.join(os.path.dirname(__file__), '__main__.py'))
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+except ImportError:
+ import imp
+ mod_info = imp.find_module('__main__', [os.path.dirname(__file__)])
+ mod = imp.load_module('main', *mod_info)
+
+
+# Fake zipfile module to make `mod.VersionedFindLoad` able to read the extracted
+# files rather than the zipapp.
+class fake_zipfile(object):
+ class ZipFile(object):
+ def __init__(self, path, mode):
+ self._path = path
+ self._mode = mode
+
+ def open(self, path):
+ # The caller expects a raw file object, with no unicode handling.
+ return open(os.path.join(self._path, path), self._mode + 'b')
+
+ def close(self):
+ pass
+
+mod.zipfile = fake_zipfile
+
+
+def run():
+ with mod.VersionedFindLoad() as finder:
+ sys.meta_path.insert(0, finder)
+ finder._register_distutils_finder()
+ from virtualenv.__main__ import run as run_virtualenv
+
+ run_virtualenv()
+
+
+if __name__ == "__main__":
+ run()
diff --git a/third_party/python/virtualenv/virtualenv/__init__.py b/third_party/python/virtualenv/virtualenv/__init__.py
new file mode 100644
index 0000000000..5f74e3ef21
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/__init__.py
@@ -0,0 +1,10 @@
+from __future__ import absolute_import, unicode_literals
+
+from .run import cli_run, session_via_cli
+from .version import __version__
+
+__all__ = (
+ "__version__",
+ "cli_run",
+ "session_via_cli",
+)
diff --git a/third_party/python/virtualenv/virtualenv/__main__.py b/third_party/python/virtualenv/virtualenv/__main__.py
new file mode 100644
index 0000000000..0995e4c18b
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/__main__.py
@@ -0,0 +1,77 @@
+from __future__ import absolute_import, print_function, unicode_literals
+
+import logging
+import sys
+from datetime import datetime
+
+
+def run(args=None, options=None):
+ start = datetime.now()
+ from virtualenv.run import cli_run
+ from virtualenv.util.error import ProcessCallFailed
+
+ if args is None:
+ args = sys.argv[1:]
+ try:
+ session = cli_run(args, options)
+ logging.warning(LogSession(session, start))
+ except ProcessCallFailed as exception:
+ print("subprocess call failed for {} with code {}".format(exception.cmd, exception.code))
+ print(exception.out, file=sys.stdout, end="")
+ print(exception.err, file=sys.stderr, end="")
+ raise SystemExit(exception.code)
+
+
+class LogSession(object):
+ def __init__(self, session, start):
+ self.session = session
+ self.start = start
+
+ def __str__(self):
+ from virtualenv.util.six import ensure_text
+
+ spec = self.session.creator.interpreter.spec
+ elapsed = (datetime.now() - self.start).total_seconds() * 1000
+ lines = [
+ "created virtual environment {} in {:.0f}ms".format(spec, elapsed),
+ " creator {}".format(ensure_text(str(self.session.creator))),
+ ]
+ if self.session.seeder.enabled:
+ lines += (
+ " seeder {}".format(ensure_text(str(self.session.seeder))),
+ " added seed packages: {}".format(
+ ", ".join(
+ sorted(
+ "==".join(i.stem.split("-"))
+ for i in self.session.creator.purelib.iterdir()
+ if i.suffix == ".dist-info"
+ ),
+ ),
+ ),
+ )
+ if self.session.activators:
+ lines.append(" activators {}".format(",".join(i.__class__.__name__ for i in self.session.activators)))
+ return "\n".join(lines)
+
+
+def run_with_catch(args=None):
+ from virtualenv.config.cli.parser import VirtualEnvOptions
+
+ options = VirtualEnvOptions()
+ try:
+ run(args, options)
+ except (KeyboardInterrupt, SystemExit, Exception) as exception:
+ try:
+ if getattr(options, "with_traceback", False):
+ raise
+ else:
+ if not (isinstance(exception, SystemExit) and exception.code == 0):
+ logging.error("%s: %s", type(exception).__name__, exception)
+ code = exception.code if isinstance(exception, SystemExit) else 1
+ sys.exit(code)
+ finally:
+ logging.shutdown() # force flush of log messages before the trace is printed
+
+
+if __name__ == "__main__": # pragma: no cov
+ run_with_catch() # pragma: no cov
diff --git a/third_party/python/virtualenv/virtualenv/activation/__init__.py b/third_party/python/virtualenv/virtualenv/activation/__init__.py
new file mode 100644
index 0000000000..fa2f0b4af7
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/__init__.py
@@ -0,0 +1,19 @@
+from __future__ import absolute_import, unicode_literals
+
+from .bash import BashActivator
+from .batch import BatchActivator
+from .cshell import CShellActivator
+from .fish import FishActivator
+from .powershell import PowerShellActivator
+from .python import PythonActivator
+from .xonsh import XonshActivator
+
+__all__ = [
+ "BashActivator",
+ "PowerShellActivator",
+ "XonshActivator",
+ "CShellActivator",
+ "PythonActivator",
+ "BatchActivator",
+ "FishActivator",
+]
diff --git a/third_party/python/virtualenv/virtualenv/activation/activator.py b/third_party/python/virtualenv/virtualenv/activation/activator.py
new file mode 100644
index 0000000000..587ac105bc
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/activator.py
@@ -0,0 +1,44 @@
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta, abstractmethod
+
+from six import add_metaclass
+
+
+@add_metaclass(ABCMeta)
+class Activator(object):
+ """Generates an activate script for the virtual environment"""
+
+ def __init__(self, options):
+ """Create a new activator generator.
+
+ :param options: the parsed options as defined within :meth:`add_parser_arguments`
+ """
+ self.flag_prompt = options.prompt
+
+ @classmethod
+ def supports(cls, interpreter):
+ """Check if the activation script is supported in the given interpreter.
+
+ :param interpreter: the interpreter we need to support
+ :return: ``True`` if supported, ``False`` otherwise
+ """
+ return True
+
+ @classmethod
+ def add_parser_arguments(cls, parser, interpreter):
+ """
+ Add CLI arguments for this activation script.
+
+ :param parser: the CLI parser
+ :param interpreter: the interpreter this virtual environment is based of
+ """
+
+ @abstractmethod
+ def generate(self, creator):
+ """Generate the activate script for the given creator.
+
+ :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this \
+ virtual environment
+ """
+ raise NotImplementedError
diff --git a/third_party/python/virtualenv/virtualenv/activation/bash/__init__.py b/third_party/python/virtualenv/virtualenv/activation/bash/__init__.py
new file mode 100644
index 0000000000..22c90c3827
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/bash/__init__.py
@@ -0,0 +1,13 @@
+from __future__ import absolute_import, unicode_literals
+
+from virtualenv.util.path import Path
+
+from ..via_template import ViaTemplateActivator
+
+
+class BashActivator(ViaTemplateActivator):
+ def templates(self):
+ yield Path("activate.sh")
+
+ def as_name(self, template):
+ return template.stem
diff --git a/third_party/python/virtualenv/virtualenv/activation/bash/activate.sh b/third_party/python/virtualenv/virtualenv/activation/bash/activate.sh
new file mode 100644
index 0000000000..222d982043
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/bash/activate.sh
@@ -0,0 +1,87 @@
+# This file must be used with "source bin/activate" *from bash*
+# you cannot run it directly
+
+
+if [ "${BASH_SOURCE-}" = "$0" ]; then
+ echo "You must source this script: \$ source $0" >&2
+ exit 33
+fi
+
+deactivate () {
+ unset -f pydoc >/dev/null 2>&1
+
+ # reset old environment variables
+ # ! [ -z ${VAR+_} ] returns true if VAR is declared at all
+ if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
+ PATH="$_OLD_VIRTUAL_PATH"
+ export PATH
+ unset _OLD_VIRTUAL_PATH
+ fi
+ if ! [ -z "${_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-}" ] || [ -n "${ZSH_VERSION-}" ] ; then
+ hash -r 2>/dev/null
+ fi
+
+ if ! [ -z "${_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='__VIRTUAL_ENV__'
+if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
+ VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
+fi
+export VIRTUAL_ENV
+
+_OLD_VIRTUAL_PATH="$PATH"
+PATH="$VIRTUAL_ENV/__BIN_NAME__:$PATH"
+export PATH
+
+# unset PYTHONHOME if set
+if ! [ -z "${PYTHONHOME+_}" ] ; then
+ _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
+ unset PYTHONHOME
+fi
+
+if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
+ _OLD_VIRTUAL_PS1="${PS1-}"
+ if [ "x__VIRTUAL_PROMPT__" != x ] ; then
+ PS1="__VIRTUAL_PROMPT__${PS1-}"
+ else
+ PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
+ fi
+ export PS1
+fi
+
+# Make sure to unalias pydoc if it's already there
+alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
+
+pydoc () {
+ python -m pydoc "$@"
+}
+
+# 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-}" ] || [ -n "${ZSH_VERSION-}" ] ; then
+ hash -r 2>/dev/null
+fi
diff --git a/third_party/python/virtualenv/virtualenv/activation/batch/__init__.py b/third_party/python/virtualenv/virtualenv/activation/batch/__init__.py
new file mode 100644
index 0000000000..4149712d87
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/batch/__init__.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+
+from virtualenv.util.path import Path
+
+from ..via_template import ViaTemplateActivator
+
+
+class BatchActivator(ViaTemplateActivator):
+ @classmethod
+ def supports(cls, interpreter):
+ return interpreter.os == "nt"
+
+ def templates(self):
+ yield Path("activate.bat")
+ yield Path("deactivate.bat")
+ yield Path("pydoc.bat")
+
+ def instantiate_template(self, replacements, template, creator):
+ # ensure the text has all newlines as \r\n - required by batch
+ base = super(BatchActivator, self).instantiate_template(replacements, template, creator)
+ return base.replace(os.linesep, "\n").replace("\n", os.linesep)
diff --git a/third_party/python/virtualenv/virtualenv/activation/batch/activate.bat b/third_party/python/virtualenv/virtualenv/activation/batch/activate.bat
new file mode 100644
index 0000000000..8dae28d19a
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/batch/activate.bat
@@ -0,0 +1,40 @@
+@echo off
+
+set "VIRTUAL_ENV=__VIRTUAL_ENV__"
+
+if defined _OLD_VIRTUAL_PROMPT (
+ set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
+) else (
+ if not defined PROMPT (
+ set "PROMPT=$P$G"
+ )
+ if not defined VIRTUAL_ENV_DISABLE_PROMPT (
+ set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
+ )
+)
+if not defined VIRTUAL_ENV_DISABLE_PROMPT (
+ set "ENV_PROMPT=__VIRTUAL_PROMPT__"
+ if NOT DEFINED ENV_PROMPT (
+ for %%d in ("%VIRTUAL_ENV%") do set "ENV_PROMPT=(%%~nxd) "
+ )
+ )
+ set "PROMPT=%ENV_PROMPT%%PROMPT%"
+)
+
+REM Don't use () to avoid problems with them in %PATH%
+if defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME
+ set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
+:ENDIFVHOME
+
+set PYTHONHOME=
+
+REM if defined _OLD_VIRTUAL_PATH (
+if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH1
+ set "PATH=%_OLD_VIRTUAL_PATH%"
+:ENDIFVPATH1
+REM ) else (
+if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2
+ set "_OLD_VIRTUAL_PATH=%PATH%"
+:ENDIFVPATH2
+
+set "PATH=%VIRTUAL_ENV%\__BIN_NAME__;%PATH%"
diff --git a/third_party/python/virtualenv/virtualenv/activation/batch/deactivate.bat b/third_party/python/virtualenv/virtualenv/activation/batch/deactivate.bat
new file mode 100644
index 0000000000..c33186657f
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/batch/deactivate.bat
@@ -0,0 +1,19 @@
+@echo off
+
+set VIRTUAL_ENV=
+
+REM Don't use () to avoid problems with them in %PATH%
+if not defined _OLD_VIRTUAL_PROMPT goto ENDIFVPROMPT
+ set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
+ set _OLD_VIRTUAL_PROMPT=
+:ENDIFVPROMPT
+
+if not defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME
+ set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
+ set _OLD_VIRTUAL_PYTHONHOME=
+:ENDIFVHOME
+
+if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH
+ set "PATH=%_OLD_VIRTUAL_PATH%"
+ set _OLD_VIRTUAL_PATH=
+:ENDIFVPATH
diff --git a/third_party/python/virtualenv/virtualenv/activation/batch/pydoc.bat b/third_party/python/virtualenv/virtualenv/activation/batch/pydoc.bat
new file mode 100644
index 0000000000..45ddc13275
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/batch/pydoc.bat
@@ -0,0 +1 @@
+python.exe -m pydoc %*
diff --git a/third_party/python/virtualenv/virtualenv/activation/cshell/__init__.py b/third_party/python/virtualenv/virtualenv/activation/cshell/__init__.py
new file mode 100644
index 0000000000..b25c602a58
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/cshell/__init__.py
@@ -0,0 +1,14 @@
+from __future__ import absolute_import, unicode_literals
+
+from virtualenv.util.path import Path
+
+from ..via_template import ViaTemplateActivator
+
+
+class CShellActivator(ViaTemplateActivator):
+ @classmethod
+ def supports(cls, interpreter):
+ return interpreter.os != "nt"
+
+ def templates(self):
+ yield Path("activate.csh")
diff --git a/third_party/python/virtualenv/virtualenv/activation/cshell/activate.csh b/third_party/python/virtualenv/virtualenv/activation/cshell/activate.csh
new file mode 100644
index 0000000000..72b2cf8eff
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/cshell/activate.csh
@@ -0,0 +1,55 @@
+# This file must be used with "source bin/activate.csh" *from csh*.
+# You cannot run it directly.
+# Created by Davide Di Blasi <davidedb@gmail.com>.
+
+set newline='\
+'
+
+alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+setenv VIRTUAL_ENV '__VIRTUAL_ENV__'
+
+set _OLD_VIRTUAL_PATH="$PATH:q"
+setenv PATH "$VIRTUAL_ENV:q/__BIN_NAME__:$PATH:q"
+
+
+
+if ('__VIRTUAL_PROMPT__' != "") then
+ set env_name = '__VIRTUAL_PROMPT__'
+else
+ set env_name = '('"$VIRTUAL_ENV:t:q"') '
+endif
+
+if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then
+ if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then
+ set do_prompt = "1"
+ else
+ set do_prompt = "0"
+ endif
+else
+ set do_prompt = "1"
+endif
+
+if ( $do_prompt == "1" ) then
+ # Could be in a non-interactive environment,
+ # in which case, $prompt is undefined and we wouldn't
+ # care about the prompt anyway.
+ if ( $?prompt ) then
+ set _OLD_VIRTUAL_PROMPT="$prompt:q"
+ if ( "$prompt:q" =~ *"$newline:q"* ) then
+ :
+ else
+ set prompt = "$env_name:q$prompt:q"
+ endif
+ endif
+endif
+
+unset env_name
+unset do_prompt
+
+alias pydoc python -m pydoc
+
+rehash
diff --git a/third_party/python/virtualenv/virtualenv/activation/fish/__init__.py b/third_party/python/virtualenv/virtualenv/activation/fish/__init__.py
new file mode 100644
index 0000000000..8d0e19c2cd
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/fish/__init__.py
@@ -0,0 +1,10 @@
+from __future__ import absolute_import, unicode_literals
+
+from virtualenv.util.path import Path
+
+from ..via_template import ViaTemplateActivator
+
+
+class FishActivator(ViaTemplateActivator):
+ def templates(self):
+ yield Path("activate.fish")
diff --git a/third_party/python/virtualenv/virtualenv/activation/fish/activate.fish b/third_party/python/virtualenv/virtualenv/activation/fish/activate.fish
new file mode 100644
index 0000000000..faa262270a
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/fish/activate.fish
@@ -0,0 +1,100 @@
+# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.
+# Do not run it directly.
+
+function _bashify_path -d "Converts a fish path to something bash can recognize"
+ set fishy_path $argv
+ set bashy_path $fishy_path[1]
+ for path_part in $fishy_path[2..-1]
+ set bashy_path "$bashy_path:$path_part"
+ end
+ echo $bashy_path
+end
+
+function _fishify_path -d "Converts a bash path to something fish can recognize"
+ echo $argv | tr ':' '\n'
+end
+
+function deactivate -d 'Exit virtualenv mode and return to the normal environment.'
+ # reset old environment variables
+ if test -n "$_OLD_VIRTUAL_PATH"
+ # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
+ if test (echo $FISH_VERSION | head -c 1) -lt 3
+ set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH")
+ else
+ set -gx PATH "$_OLD_VIRTUAL_PATH"
+ end
+ 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"
+ and functions -q _old_fish_prompt
+ # Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`.
+ set -l fish_function_path
+
+ # Erase virtualenv's `fish_prompt` and restore the original.
+ functions -e fish_prompt
+ functions -c _old_fish_prompt fish_prompt
+ functions -e _old_fish_prompt
+ set -e _OLD_FISH_PROMPT_OVERRIDE
+ end
+
+ set -e VIRTUAL_ENV
+
+ if test "$argv[1]" != 'nondestructive'
+ # Self-destruct!
+ functions -e pydoc
+ functions -e deactivate
+ functions -e _bashify_path
+ functions -e _fishify_path
+ end
+end
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+set -gx VIRTUAL_ENV '__VIRTUAL_ENV__'
+
+# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
+if test (echo $FISH_VERSION | head -c 1) -lt 3
+ set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
+else
+ set -gx _OLD_VIRTUAL_PATH "$PATH"
+end
+set -gx PATH "$VIRTUAL_ENV"'/__BIN_NAME__' $PATH
+
+# Unset `$PYTHONHOME` if set.
+if set -q PYTHONHOME
+ set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
+ set -e PYTHONHOME
+end
+
+function pydoc
+ python -m pydoc $argv
+end
+
+if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
+ # Copy the current `fish_prompt` function as `_old_fish_prompt`.
+ functions -c fish_prompt _old_fish_prompt
+
+ function fish_prompt
+ # Run the user's prompt first; it might depend on (pipe)status.
+ set -l prompt (_old_fish_prompt)
+
+ # Prompt override provided?
+ # If not, just prepend the environment name.
+ if test -n '__VIRTUAL_PROMPT__'
+ printf '%s%s' '__VIRTUAL_PROMPT__' (set_color normal)
+ else
+ printf '%s(%s) ' (set_color normal) (basename "$VIRTUAL_ENV")
+ end
+
+ string join -- \n $prompt # handle multi-line prompts
+ end
+
+ set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
+end
diff --git a/third_party/python/virtualenv/virtualenv/activation/powershell/__init__.py b/third_party/python/virtualenv/virtualenv/activation/powershell/__init__.py
new file mode 100644
index 0000000000..4fadc63bc1
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/powershell/__init__.py
@@ -0,0 +1,10 @@
+from __future__ import absolute_import, unicode_literals
+
+from virtualenv.util.path import Path
+
+from ..via_template import ViaTemplateActivator
+
+
+class PowerShellActivator(ViaTemplateActivator):
+ def templates(self):
+ yield Path("activate.ps1")
diff --git a/third_party/python/virtualenv/virtualenv/activation/powershell/activate.ps1 b/third_party/python/virtualenv/virtualenv/activation/powershell/activate.ps1
new file mode 100644
index 0000000000..a370a63f55
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/powershell/activate.ps1
@@ -0,0 +1,60 @@
+$script:THIS_PATH = $myinvocation.mycommand.path
+$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
+
+function global:deactivate([switch] $NonDestructive) {
+ if (Test-Path variable:_OLD_VIRTUAL_PATH) {
+ $env:PATH = $variable:_OLD_VIRTUAL_PATH
+ Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
+ }
+
+ if (Test-Path function:_old_virtual_prompt) {
+ $function:prompt = $function:_old_virtual_prompt
+ Remove-Item function:\_old_virtual_prompt
+ }
+
+ if ($env:VIRTUAL_ENV) {
+ Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
+ }
+
+ if (!$NonDestructive) {
+ # Self destruct!
+ Remove-Item function:deactivate
+ Remove-Item function:pydoc
+ }
+}
+
+function global:pydoc {
+ python -m pydoc $args
+}
+
+# unset irrelevant variables
+deactivate -nondestructive
+
+$VIRTUAL_ENV = $BASE_DIR
+$env:VIRTUAL_ENV = $VIRTUAL_ENV
+
+New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
+
+$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH
+if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
+ function global:_old_virtual_prompt {
+ ""
+ }
+ $function:_old_virtual_prompt = $function:prompt
+
+ if ("__VIRTUAL_PROMPT__" -ne "") {
+ function global:prompt {
+ # Add the custom prefix to the existing prompt
+ $previous_prompt_value = & $function:_old_virtual_prompt
+ ("__VIRTUAL_PROMPT__" + $previous_prompt_value)
+ }
+ }
+ else {
+ function global:prompt {
+ # Add a prefix to the current prompt, but don't discard it.
+ $previous_prompt_value = & $function:_old_virtual_prompt
+ $new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) "
+ ($new_prompt_value + $previous_prompt_value)
+ }
+ }
+}
diff --git a/third_party/python/virtualenv/virtualenv/activation/python/__init__.py b/third_party/python/virtualenv/virtualenv/activation/python/__init__.py
new file mode 100644
index 0000000000..9e579124d7
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/python/__init__.py
@@ -0,0 +1,35 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+import sys
+from collections import OrderedDict
+
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_text
+
+from ..via_template import ViaTemplateActivator
+
+
+class PythonActivator(ViaTemplateActivator):
+ def templates(self):
+ yield Path("activate_this.py")
+
+ def replacements(self, creator, dest_folder):
+ replacements = super(PythonActivator, self).replacements(creator, dest_folder)
+ lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs)
+ win_py2 = creator.interpreter.platform == "win32" and creator.interpreter.version_info.major == 2
+ replacements.update(
+ {
+ "__LIB_FOLDERS__": ensure_text(os.pathsep.join(lib_folders.keys())),
+ "__DECODE_PATH__": ("yes" if win_py2 else ""),
+ },
+ )
+ return replacements
+
+ @staticmethod
+ def _repr_unicode(creator, value):
+ py2 = creator.interpreter.version_info.major == 2
+ if py2: # on Python 2 we need to encode this into explicit utf-8, py3 supports unicode literals
+ start = 2 if sys.version_info[0] == 3 else 1
+ value = ensure_text(repr(value.encode("utf-8"))[start:-1])
+ return value
diff --git a/third_party/python/virtualenv/virtualenv/activation/python/activate_this.py b/third_party/python/virtualenv/virtualenv/activation/python/activate_this.py
new file mode 100644
index 0000000000..29debe3e74
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/python/activate_this.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+"""Activate virtualenv for current interpreter:
+
+Use exec(open(this_file).read(), {'__file__': this_file}).
+
+This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
+"""
+import os
+import site
+import sys
+
+try:
+ abs_file = os.path.abspath(__file__)
+except NameError:
+ raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))")
+
+bin_dir = os.path.dirname(abs_file)
+base = bin_dir[: -len("__BIN_NAME__") - 1] # strip away the bin part from the __file__, plus the path separator
+
+# prepend bin to PATH (this file is inside the bin directory)
+os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep))
+os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
+
+# add the virtual environments libraries to the host python import mechanism
+prev_length = len(sys.path)
+for lib in "__LIB_FOLDERS__".split(os.pathsep):
+ path = os.path.realpath(os.path.join(bin_dir, lib))
+ site.addsitedir(path.decode("utf-8") if "__DECODE_PATH__" else path)
+sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
+
+sys.real_prefix = sys.prefix
+sys.prefix = base
diff --git a/third_party/python/virtualenv/virtualenv/activation/via_template.py b/third_party/python/virtualenv/virtualenv/activation/via_template.py
new file mode 100644
index 0000000000..14f097973f
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/via_template.py
@@ -0,0 +1,67 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+import sys
+from abc import ABCMeta, abstractmethod
+
+from six import add_metaclass
+
+from virtualenv.util.six import ensure_text
+
+from .activator import Activator
+
+if sys.version_info >= (3, 7):
+ from importlib.resources import read_binary
+else:
+ from importlib_resources import read_binary
+
+
+@add_metaclass(ABCMeta)
+class ViaTemplateActivator(Activator):
+ @abstractmethod
+ def templates(self):
+ raise NotImplementedError
+
+ def generate(self, creator):
+ dest_folder = creator.bin_dir
+ replacements = self.replacements(creator, dest_folder)
+ generated = self._generate(replacements, self.templates(), dest_folder, creator)
+ if self.flag_prompt is not None:
+ creator.pyenv_cfg["prompt"] = self.flag_prompt
+ return generated
+
+ def replacements(self, creator, dest_folder):
+ return {
+ "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
+ "__VIRTUAL_ENV__": ensure_text(str(creator.dest)),
+ "__VIRTUAL_NAME__": creator.env_name,
+ "__BIN_NAME__": ensure_text(str(creator.bin_dir.relative_to(creator.dest))),
+ "__PATH_SEP__": ensure_text(os.pathsep),
+ }
+
+ def _generate(self, replacements, templates, to_folder, creator):
+ generated = []
+ for template in templates:
+ text = self.instantiate_template(replacements, template, creator)
+ dest = to_folder / self.as_name(template)
+ # use write_bytes to avoid platform specific line normalization (\n -> \r\n)
+ dest.write_bytes(text.encode("utf-8"))
+ generated.append(dest)
+ return generated
+
+ def as_name(self, template):
+ return template.name
+
+ def instantiate_template(self, replacements, template, creator):
+ # read content as binary to avoid platform specific line normalization (\n -> \r\n)
+ binary = read_binary(self.__module__, str(template))
+ text = binary.decode("utf-8", errors="strict")
+ for key, value in replacements.items():
+ value = self._repr_unicode(creator, value)
+ text = text.replace(key, value)
+ return text
+
+ @staticmethod
+ def _repr_unicode(creator, value):
+ # by default we just let it be unicode
+ return value
diff --git a/third_party/python/virtualenv/virtualenv/activation/xonsh/__init__.py b/third_party/python/virtualenv/virtualenv/activation/xonsh/__init__.py
new file mode 100644
index 0000000000..d92411c20f
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/xonsh/__init__.py
@@ -0,0 +1,14 @@
+from __future__ import absolute_import, unicode_literals
+
+from virtualenv.util.path import Path
+
+from ..via_template import ViaTemplateActivator
+
+
+class XonshActivator(ViaTemplateActivator):
+ def templates(self):
+ yield Path("activate.xsh")
+
+ @classmethod
+ def supports(cls, interpreter):
+ return interpreter.version_info >= (3, 5)
diff --git a/third_party/python/virtualenv/virtualenv/activation/xonsh/activate.xsh b/third_party/python/virtualenv/virtualenv/activation/xonsh/activate.xsh
new file mode 100644
index 0000000000..c77ea62786
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/activation/xonsh/activate.xsh
@@ -0,0 +1,46 @@
+"""Xonsh activate script for virtualenv"""
+from xonsh.tools import get_sep as _get_sep
+
+def _deactivate(args):
+ if "pydoc" in aliases:
+ del aliases["pydoc"]
+
+ if ${...}.get("_OLD_VIRTUAL_PATH", ""):
+ $PATH = $_OLD_VIRTUAL_PATH
+ del $_OLD_VIRTUAL_PATH
+
+ if ${...}.get("_OLD_VIRTUAL_PYTHONHOME", ""):
+ $PYTHONHOME = $_OLD_VIRTUAL_PYTHONHOME
+ del $_OLD_VIRTUAL_PYTHONHOME
+
+ if "VIRTUAL_ENV" in ${...}:
+ del $VIRTUAL_ENV
+
+ if "VIRTUAL_ENV_PROMPT" in ${...}:
+ del $VIRTUAL_ENV_PROMPT
+
+ if "nondestructive" not in args:
+ # Self destruct!
+ del aliases["deactivate"]
+
+
+# unset irrelevant variables
+_deactivate(["nondestructive"])
+aliases["deactivate"] = _deactivate
+
+$VIRTUAL_ENV = r"__VIRTUAL_ENV__"
+
+$_OLD_VIRTUAL_PATH = $PATH
+$PATH = $PATH[:]
+$PATH.add($VIRTUAL_ENV + _get_sep() + "__BIN_NAME__", front=True, replace=True)
+
+if ${...}.get("PYTHONHOME", ""):
+ # unset PYTHONHOME if set
+ $_OLD_VIRTUAL_PYTHONHOME = $PYTHONHOME
+ del $PYTHONHOME
+
+$VIRTUAL_ENV_PROMPT = "__VIRTUAL_PROMPT__"
+if not $VIRTUAL_ENV_PROMPT:
+ del $VIRTUAL_ENV_PROMPT
+
+aliases["pydoc"] = ["python", "-m", "pydoc"]
diff --git a/third_party/python/virtualenv/virtualenv/app_data/__init__.py b/third_party/python/virtualenv/virtualenv/app_data/__init__.py
new file mode 100644
index 0000000000..2df0cae5d3
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/app_data/__init__.py
@@ -0,0 +1,57 @@
+"""
+Application data stored by virtualenv.
+"""
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+
+from appdirs import user_data_dir
+
+from .na import AppDataDisabled
+from .read_only import ReadOnlyAppData
+from .via_disk_folder import AppDataDiskFolder
+from .via_tempdir import TempAppData
+
+
+def _default_app_data_dir(): # type: () -> str
+ key = str("VIRTUALENV_OVERRIDE_APP_DATA")
+ if key in os.environ:
+ return os.environ[key]
+ else:
+ return user_data_dir(appname="virtualenv", appauthor="pypa")
+
+
+def make_app_data(folder, **kwargs):
+ read_only = kwargs.pop("read_only")
+ if kwargs: # py3+ kwonly
+ raise TypeError("unexpected keywords: {}")
+
+ if folder is None:
+ folder = _default_app_data_dir()
+ folder = os.path.abspath(folder)
+
+ if read_only:
+ return ReadOnlyAppData(folder)
+
+ if not os.path.isdir(folder):
+ try:
+ os.makedirs(folder)
+ logging.debug("created app data folder %s", folder)
+ except OSError as exception:
+ logging.info("could not create app data folder %s due to %r", folder, exception)
+
+ if os.access(folder, os.W_OK):
+ return AppDataDiskFolder(folder)
+ else:
+ logging.debug("app data folder %s has no write access", folder)
+ return TempAppData()
+
+
+__all__ = (
+ "AppDataDisabled",
+ "AppDataDiskFolder",
+ "ReadOnlyAppData",
+ "TempAppData",
+ "make_app_data",
+)
diff --git a/third_party/python/virtualenv/virtualenv/app_data/base.py b/third_party/python/virtualenv/virtualenv/app_data/base.py
new file mode 100644
index 0000000000..4ea54d9f64
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/app_data/base.py
@@ -0,0 +1,95 @@
+"""
+Application data stored by virtualenv.
+"""
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta, abstractmethod
+from contextlib import contextmanager
+
+import six
+
+from virtualenv.info import IS_ZIPAPP
+
+
+@six.add_metaclass(ABCMeta)
+class AppData(object):
+ """Abstract storage interface for the virtualenv application"""
+
+ @abstractmethod
+ def close(self):
+ """called before virtualenv exits"""
+
+ @abstractmethod
+ def reset(self):
+ """called when the user passes in the reset app data"""
+
+ @abstractmethod
+ def py_info(self, path):
+ raise NotImplementedError
+
+ @abstractmethod
+ def py_info_clear(self):
+ raise NotImplementedError
+
+ @property
+ def can_update(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def embed_update_log(self, distribution, for_py_version):
+ raise NotImplementedError
+
+ @property
+ def house(self):
+ raise NotImplementedError
+
+ @property
+ def transient(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def wheel_image(self, for_py_version, name):
+ raise NotImplementedError
+
+ @contextmanager
+ def ensure_extracted(self, path, to_folder=None):
+ """Some paths might be within the zipapp, unzip these to a path on the disk"""
+ if IS_ZIPAPP:
+ with self.extract(path, to_folder) as result:
+ yield result
+ else:
+ yield path
+
+ @abstractmethod
+ @contextmanager
+ def extract(self, path, to_folder):
+ raise NotImplementedError
+
+ @abstractmethod
+ @contextmanager
+ def locked(self, path):
+ raise NotImplementedError
+
+
+@six.add_metaclass(ABCMeta)
+class ContentStore(object):
+ @abstractmethod
+ def exists(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def read(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def write(self, content):
+ raise NotImplementedError
+
+ @abstractmethod
+ def remove(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ @contextmanager
+ def locked(self):
+ pass
diff --git a/third_party/python/virtualenv/virtualenv/app_data/na.py b/third_party/python/virtualenv/virtualenv/app_data/na.py
new file mode 100644
index 0000000000..5f7200d3a3
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/app_data/na.py
@@ -0,0 +1,66 @@
+from __future__ import absolute_import, unicode_literals
+
+from contextlib import contextmanager
+
+from .base import AppData, ContentStore
+
+
+class AppDataDisabled(AppData):
+ """No application cache available (most likely as we don't have write permissions)"""
+
+ transient = True
+ can_update = False
+
+ def __init__(self):
+ pass
+
+ error = RuntimeError("no app data folder available, probably no write access to the folder")
+
+ def close(self):
+ """do nothing"""
+
+ def reset(self):
+ """do nothing"""
+
+ def py_info(self, path):
+ return ContentStoreNA()
+
+ def embed_update_log(self, distribution, for_py_version):
+ return ContentStoreNA()
+
+ def extract(self, path, to_folder):
+ raise self.error
+
+ @contextmanager
+ def locked(self, path):
+ """do nothing"""
+ yield
+
+ @property
+ def house(self):
+ raise self.error
+
+ def wheel_image(self, for_py_version, name):
+ raise self.error
+
+ def py_info_clear(self):
+ """"""
+
+
+class ContentStoreNA(ContentStore):
+ def exists(self):
+ return False
+
+ def read(self):
+ """"""
+ return None
+
+ def write(self, content):
+ """"""
+
+ def remove(self):
+ """"""
+
+ @contextmanager
+ def locked(self):
+ yield
diff --git a/third_party/python/virtualenv/virtualenv/app_data/read_only.py b/third_party/python/virtualenv/virtualenv/app_data/read_only.py
new file mode 100644
index 0000000000..858978cd08
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/app_data/read_only.py
@@ -0,0 +1,34 @@
+import os.path
+
+from virtualenv.util.lock import NoOpFileLock
+
+from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk
+
+
+class ReadOnlyAppData(AppDataDiskFolder):
+ can_update = False
+
+ def __init__(self, folder): # type: (str) -> None
+ if not os.path.isdir(folder):
+ raise RuntimeError("read-only app data directory {} does not exist".format(folder))
+ self.lock = NoOpFileLock(folder)
+
+ def reset(self): # type: () -> None
+ raise RuntimeError("read-only app data does not support reset")
+
+ def py_info_clear(self): # type: () -> None
+ raise NotImplementedError
+
+ def py_info(self, path):
+ return _PyInfoStoreDiskReadOnly(self.py_info_at, path)
+
+ def embed_update_log(self, distribution, for_py_version):
+ raise NotImplementedError
+
+
+class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk):
+ def write(self, content):
+ raise RuntimeError("read-only app data python info cannot be updated")
+
+
+__all__ = ("ReadOnlyAppData",)
diff --git a/third_party/python/virtualenv/virtualenv/app_data/via_disk_folder.py b/third_party/python/virtualenv/virtualenv/app_data/via_disk_folder.py
new file mode 100644
index 0000000000..2243f1670e
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/app_data/via_disk_folder.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+"""
+A rough layout of the current storage goes as:
+
+virtualenv-app-data
+├── py - <version> <cache information about python interpreters>
+│   └── *.json/lock
+├── wheel <cache wheels used for seeding>
+│   ├── house
+│ │ └── *.whl <wheels downloaded go here>
+│ └── <python major.minor> -> 3.9
+│ ├── img-<version>
+│ │ └── image
+│ │ └── <install class> -> CopyPipInstall / SymlinkPipInstall
+│ │ └── <wheel name> -> pip-20.1.1-py2.py3-none-any
+│ └── embed
+│ └── 1
+│ └── *.json -> for every distribution contains data about newer embed versions and releases
+└─── unzip <in zip app we cannot refer to some internal files, so first extract them>
+ └── <virtualenv version>
+ ├── py_info.py
+ ├── debug.py
+ └── _virtualenv.py
+"""
+from __future__ import absolute_import, unicode_literals
+
+import json
+import logging
+from abc import ABCMeta
+from contextlib import contextmanager
+from hashlib import sha256
+
+import six
+
+from virtualenv.util.lock import ReentrantFileLock
+from virtualenv.util.path import safe_delete
+from virtualenv.util.six import ensure_text
+from virtualenv.util.zipapp import extract
+from virtualenv.version import __version__
+
+from .base import AppData, ContentStore
+
+
+class AppDataDiskFolder(AppData):
+ """
+ Store the application data on the disk within a folder layout.
+ """
+
+ transient = False
+ can_update = True
+
+ def __init__(self, folder):
+ self.lock = ReentrantFileLock(folder)
+
+ def __repr__(self):
+ return "{}({})".format(type(self).__name__, self.lock.path)
+
+ def __str__(self):
+ return str(self.lock.path)
+
+ def reset(self):
+ logging.debug("reset app data folder %s", self.lock.path)
+ safe_delete(self.lock.path)
+
+ def close(self):
+ """do nothing"""
+
+ @contextmanager
+ def locked(self, path):
+ path_lock = self.lock / path
+ with path_lock:
+ yield path_lock.path
+
+ @contextmanager
+ def extract(self, path, to_folder):
+ if to_folder is not None:
+ root = ReentrantFileLock(to_folder())
+ else:
+ root = self.lock / "unzip" / __version__
+ with root.lock_for_key(path.name):
+ dest = root.path / path.name
+ if not dest.exists():
+ extract(path, dest)
+ yield dest
+
+ @property
+ def py_info_at(self):
+ return self.lock / "py_info" / "1"
+
+ def py_info(self, path):
+ return PyInfoStoreDisk(self.py_info_at, path)
+
+ def py_info_clear(self):
+ """"""
+ py_info_folder = self.py_info_at
+ with py_info_folder:
+ for filename in py_info_folder.path.iterdir():
+ if filename.suffix == ".json":
+ with py_info_folder.lock_for_key(filename.stem):
+ if filename.exists():
+ filename.unlink()
+
+ def embed_update_log(self, distribution, for_py_version):
+ return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "1", distribution)
+
+ @property
+ def house(self):
+ path = self.lock.path / "wheel" / "house"
+ path.mkdir(parents=True, exist_ok=True)
+ return path
+
+ def wheel_image(self, for_py_version, name):
+ return self.lock.path / "wheel" / for_py_version / "image" / "1" / name
+
+
+@six.add_metaclass(ABCMeta)
+class JSONStoreDisk(ContentStore):
+ def __init__(self, in_folder, key, msg, msg_args):
+ self.in_folder = in_folder
+ self.key = key
+ self.msg = msg
+ self.msg_args = msg_args + (self.file,)
+
+ @property
+ def file(self):
+ return self.in_folder.path / "{}.json".format(self.key)
+
+ def exists(self):
+ return self.file.exists()
+
+ def read(self):
+ data, bad_format = None, False
+ try:
+ data = json.loads(self.file.read_text())
+ logging.debug("got {} from %s".format(self.msg), *self.msg_args)
+ return data
+ except ValueError:
+ bad_format = True
+ except Exception: # noqa
+ pass
+ if bad_format:
+ try:
+ self.remove()
+ except OSError: # reading and writing on the same file may cause race on multiple processes
+ pass
+ return None
+
+ def remove(self):
+ self.file.unlink()
+ logging.debug("removed {} at %s".format(self.msg), *self.msg_args)
+
+ @contextmanager
+ def locked(self):
+ with self.in_folder.lock_for_key(self.key):
+ yield
+
+ def write(self, content):
+ folder = self.file.parent
+ folder.mkdir(parents=True, exist_ok=True)
+ self.file.write_text(ensure_text(json.dumps(content, sort_keys=True, indent=2)))
+ logging.debug("wrote {} at %s".format(self.msg), *self.msg_args)
+
+
+class PyInfoStoreDisk(JSONStoreDisk):
+ def __init__(self, in_folder, path):
+ key = sha256(str(path).encode("utf-8") if six.PY3 else str(path)).hexdigest()
+ super(PyInfoStoreDisk, self).__init__(in_folder, key, "python info of %s", (path,))
+
+
+class EmbedDistributionUpdateStoreDisk(JSONStoreDisk):
+ def __init__(self, in_folder, distribution):
+ super(EmbedDistributionUpdateStoreDisk, self).__init__(
+ in_folder,
+ distribution,
+ "embed update of distribution %s",
+ (distribution,),
+ )
diff --git a/third_party/python/virtualenv/virtualenv/app_data/via_tempdir.py b/third_party/python/virtualenv/virtualenv/app_data/via_tempdir.py
new file mode 100644
index 0000000000..112a3fe6bc
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/app_data/via_tempdir.py
@@ -0,0 +1,27 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+from tempfile import mkdtemp
+
+from virtualenv.util.path import safe_delete
+
+from .via_disk_folder import AppDataDiskFolder
+
+
+class TempAppData(AppDataDiskFolder):
+ transient = True
+ can_update = False
+
+ def __init__(self):
+ super(TempAppData, self).__init__(folder=mkdtemp())
+ logging.debug("created temporary app data folder %s", self.lock.path)
+
+ def reset(self):
+ """this is a temporary folder, is already empty to start with"""
+
+ def close(self):
+ logging.debug("remove temporary app data folder %s", self.lock.path)
+ safe_delete(self.lock.path)
+
+ def embed_update_log(self, distribution, for_py_version):
+ raise NotImplementedError
diff --git a/third_party/python/virtualenv/virtualenv/config/__init__.py b/third_party/python/virtualenv/virtualenv/config/__init__.py
new file mode 100644
index 0000000000..01e6d4f49d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/config/__init__.py
@@ -0,0 +1 @@
+from __future__ import absolute_import, unicode_literals
diff --git a/third_party/python/virtualenv/virtualenv/config/cli/__init__.py b/third_party/python/virtualenv/virtualenv/config/cli/__init__.py
new file mode 100644
index 0000000000..01e6d4f49d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/config/cli/__init__.py
@@ -0,0 +1 @@
+from __future__ import absolute_import, unicode_literals
diff --git a/third_party/python/virtualenv/virtualenv/config/cli/parser.py b/third_party/python/virtualenv/virtualenv/config/cli/parser.py
new file mode 100644
index 0000000000..eb4db30a70
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/config/cli/parser.py
@@ -0,0 +1,120 @@
+from __future__ import absolute_import, unicode_literals
+
+from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
+from collections import OrderedDict
+
+from virtualenv.config.convert import get_type
+
+from ..env_var import get_env_var
+from ..ini import IniConfig
+
+
+class VirtualEnvOptions(Namespace):
+ def __init__(self, **kwargs):
+ super(VirtualEnvOptions, self).__init__(**kwargs)
+ self._src = None
+ self._sources = {}
+
+ def set_src(self, key, value, src):
+ setattr(self, key, value)
+ if src.startswith("env var"):
+ src = "env var"
+ self._sources[key] = src
+
+ def __setattr__(self, key, value):
+ if getattr(self, "_src", None) is not None:
+ self._sources[key] = self._src
+ super(VirtualEnvOptions, self).__setattr__(key, value)
+
+ def get_source(self, key):
+ return self._sources.get(key)
+
+ @property
+ def verbosity(self):
+ if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
+ return None
+ return max(self.verbose - self.quiet, 0)
+
+ def __repr__(self):
+ return "{}({})".format(
+ type(self).__name__,
+ ", ".join("{}={}".format(k, v) for k, v in vars(self).items() if not k.startswith("_")),
+ )
+
+
+class VirtualEnvConfigParser(ArgumentParser):
+ """
+ Custom option parser which updates its defaults by checking the configuration files and environmental variables
+ """
+
+ def __init__(self, options=None, *args, **kwargs):
+ self.file_config = IniConfig()
+ self.epilog_list = []
+ kwargs["epilog"] = self.file_config.epilog
+ kwargs["add_help"] = False
+ kwargs["formatter_class"] = HelpFormatter
+ kwargs["prog"] = "virtualenv"
+ super(VirtualEnvConfigParser, self).__init__(*args, **kwargs)
+ self._fixed = set()
+ if options is not None and not isinstance(options, VirtualEnvOptions):
+ raise TypeError("options must be of type VirtualEnvOptions")
+ self.options = VirtualEnvOptions() if options is None else options
+ self._interpreter = None
+ self._app_data = None
+
+ def _fix_defaults(self):
+ for action in self._actions:
+ action_id = id(action)
+ if action_id not in self._fixed:
+ self._fix_default(action)
+ self._fixed.add(action_id)
+
+ def _fix_default(self, action):
+ if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS:
+ as_type = get_type(action)
+ names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings)
+ outcome = None
+ for name in names:
+ outcome = get_env_var(name, as_type)
+ if outcome is not None:
+ break
+ if outcome is None and self.file_config:
+ for name in names:
+ outcome = self.file_config.get(name, as_type)
+ if outcome is not None:
+ break
+ if outcome is not None:
+ action.default, action.default_source = outcome
+ else:
+ outcome = action.default, "default"
+ self.options.set_src(action.dest, *outcome)
+
+ def enable_help(self):
+ self._fix_defaults()
+ self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
+
+ def parse_known_args(self, args=None, namespace=None):
+ if namespace is None:
+ namespace = self.options
+ elif namespace is not self.options:
+ raise ValueError("can only pass in parser.options")
+ self._fix_defaults()
+ self.options._src = "cli"
+ try:
+ return super(VirtualEnvConfigParser, self).parse_known_args(args, namespace=namespace)
+ finally:
+ self.options._src = None
+
+
+class HelpFormatter(ArgumentDefaultsHelpFormatter):
+ def __init__(self, prog):
+ super(HelpFormatter, self).__init__(prog, max_help_position=32, width=240)
+
+ def _get_help_string(self, action):
+ # noinspection PyProtectedMember
+ text = super(HelpFormatter, self)._get_help_string(action)
+ if hasattr(action, "default_source"):
+ default = " (default: %(default)s)"
+ if text.endswith(default):
+ text = "{} (default: %(default)s -> from %(default_source)s)".format(text[: -len(default)])
+ return text
diff --git a/third_party/python/virtualenv/virtualenv/config/convert.py b/third_party/python/virtualenv/virtualenv/config/convert.py
new file mode 100644
index 0000000000..562720a57e
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/config/convert.py
@@ -0,0 +1,98 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+
+
+class TypeData(object):
+ def __init__(self, default_type, as_type):
+ self.default_type = default_type
+ self.as_type = as_type
+
+ def __repr__(self):
+ return "{}(base={}, as={})".format(self.__class__.__name__, self.default_type, self.as_type)
+
+ def convert(self, value):
+ return self.default_type(value)
+
+
+class BoolType(TypeData):
+ BOOLEAN_STATES = {
+ "1": True,
+ "yes": True,
+ "true": True,
+ "on": True,
+ "0": False,
+ "no": False,
+ "false": False,
+ "off": False,
+ }
+
+ def convert(self, value):
+ if value.lower() not in self.BOOLEAN_STATES:
+ raise ValueError("Not a boolean: %s" % value)
+ return self.BOOLEAN_STATES[value.lower()]
+
+
+class NoneType(TypeData):
+ def convert(self, value):
+ if not value:
+ return None
+ return str(value)
+
+
+class ListType(TypeData):
+ def _validate(self):
+ """"""
+
+ def convert(self, value, flatten=True):
+ values = self.split_values(value)
+ result = []
+ for value in values:
+ sub_values = value.split(os.pathsep)
+ result.extend(sub_values)
+ converted = [self.as_type(i) for i in result]
+ return converted
+
+ def split_values(self, value):
+ """Split the provided value into a list.
+
+ First this is done by newlines. If there were no newlines in the text,
+ then we next try to split by comma.
+ """
+ if isinstance(value, (str, bytes)):
+ # Use `splitlines` rather than a custom check for whether there is
+ # more than one line. This ensures that the full `splitlines()`
+ # logic is supported here.
+ values = value.splitlines()
+ if len(values) <= 1:
+ values = value.split(",")
+ values = filter(None, [x.strip() for x in values])
+ else:
+ values = list(value)
+
+ return values
+
+
+def convert(value, as_type, source):
+ """Convert the value as a given type where the value comes from the given source"""
+ try:
+ return as_type.convert(value)
+ except Exception as exception:
+ logging.warning("%s failed to convert %r as %r because %r", source, value, as_type, exception)
+ raise
+
+
+_CONVERT = {bool: BoolType, type(None): NoneType, list: ListType}
+
+
+def get_type(action):
+ default_type = type(action.default)
+ as_type = default_type if action.type is None else action.type
+ return _CONVERT.get(default_type, TypeData)(default_type, as_type)
+
+
+__all__ = (
+ "convert",
+ "get_type",
+)
diff --git a/third_party/python/virtualenv/virtualenv/config/env_var.py b/third_party/python/virtualenv/virtualenv/config/env_var.py
new file mode 100644
index 0000000000..259399a705
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/config/env_var.py
@@ -0,0 +1,29 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+
+from virtualenv.util.six import ensure_str, ensure_text
+
+from .convert import convert
+
+
+def get_env_var(key, as_type):
+ """Get the environment variable option.
+
+ :param key: the config key requested
+ :param as_type: the type we would like to convert it to
+ :return:
+ """
+ environ_key = ensure_str("VIRTUALENV_{}".format(key.upper()))
+ if os.environ.get(environ_key):
+ value = os.environ[environ_key]
+ # noinspection PyBroadException
+ try:
+ source = "env var {}".format(ensure_text(environ_key))
+ as_type = convert(value, as_type, source)
+ return as_type, source
+ except Exception: # note the converter already logs a warning when failures happen
+ pass
+
+
+__all__ = ("get_env_var",)
diff --git a/third_party/python/virtualenv/virtualenv/config/ini.py b/third_party/python/virtualenv/virtualenv/config/ini.py
new file mode 100644
index 0000000000..4dec629a97
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/config/ini.py
@@ -0,0 +1,83 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+
+from appdirs import user_config_dir
+
+from virtualenv.info import PY3
+from virtualenv.util import ConfigParser
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_str
+
+from .convert import convert
+
+
+class IniConfig(object):
+ VIRTUALENV_CONFIG_FILE_ENV_VAR = ensure_str("VIRTUALENV_CONFIG_FILE")
+ STATE = {None: "failed to parse", True: "active", False: "missing"}
+
+ section = "virtualenv"
+
+ def __init__(self):
+ config_file = os.environ.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None)
+ self.is_env_var = config_file is not None
+ config_file = (
+ Path(config_file)
+ if config_file is not None
+ else Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini"
+ )
+ self.config_file = config_file
+ self._cache = {}
+
+ exception = None
+ self.has_config_file = None
+ try:
+ self.has_config_file = self.config_file.exists()
+ except OSError as exc:
+ exception = exc
+ else:
+ if self.has_config_file:
+ self.config_file = self.config_file.resolve()
+ self.config_parser = ConfigParser.ConfigParser()
+ try:
+ self._load()
+ self.has_virtualenv_section = self.config_parser.has_section(self.section)
+ except Exception as exc:
+ exception = exc
+ if exception is not None:
+ logging.error("failed to read config file %s because %r", config_file, exception)
+
+ def _load(self):
+ with self.config_file.open("rt") as file_handler:
+ reader = getattr(self.config_parser, "read_file" if PY3 else "readfp")
+ reader(file_handler)
+
+ def get(self, key, as_type):
+ cache_key = key, as_type
+ if cache_key in self._cache:
+ return self._cache[cache_key]
+ # noinspection PyBroadException
+ try:
+ source = "file"
+ raw_value = self.config_parser.get(self.section, key.lower())
+ value = convert(raw_value, as_type, source)
+ result = value, source
+ except Exception:
+ result = None
+ self._cache[cache_key] = result
+ return result
+
+ def __bool__(self):
+ return bool(self.has_config_file) and bool(self.has_virtualenv_section)
+
+ @property
+ def epilog(self):
+ msg = "{}config file {} {} (change{} via env var {})"
+ return msg.format(
+ "\n",
+ self.config_file,
+ self.STATE[self.has_config_file],
+ "d" if self.is_env_var else "",
+ self.VIRTUALENV_CONFIG_FILE_ENV_VAR,
+ )
diff --git a/third_party/python/virtualenv/virtualenv/create/__init__.py b/third_party/python/virtualenv/virtualenv/create/__init__.py
new file mode 100644
index 0000000000..01e6d4f49d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/__init__.py
@@ -0,0 +1 @@
+from __future__ import absolute_import, unicode_literals
diff --git a/third_party/python/virtualenv/virtualenv/create/creator.py b/third_party/python/virtualenv/virtualenv/create/creator.py
new file mode 100644
index 0000000000..1b4ea69f66
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/creator.py
@@ -0,0 +1,238 @@
+from __future__ import absolute_import, print_function, unicode_literals
+
+import json
+import logging
+import os
+import sys
+from abc import ABCMeta, abstractmethod
+from argparse import ArgumentTypeError
+from ast import literal_eval
+from collections import OrderedDict
+from textwrap import dedent
+
+from six import add_metaclass
+
+from virtualenv.discovery.cached_py_info import LogCmd
+from virtualenv.info import WIN_CPYTHON_2
+from virtualenv.util.path import Path, safe_delete
+from virtualenv.util.six import ensure_str, ensure_text
+from virtualenv.util.subprocess import run_cmd
+from virtualenv.version import __version__
+
+from .pyenv_cfg import PyEnvCfg
+
+HERE = Path(os.path.abspath(__file__)).parent
+DEBUG_SCRIPT = HERE / "debug.py"
+
+
+class CreatorMeta(object):
+ def __init__(self):
+ self.error = None
+
+
+@add_metaclass(ABCMeta)
+class Creator(object):
+ """A class that given a python Interpreter creates a virtual environment"""
+
+ def __init__(self, options, interpreter):
+ """Construct a new virtual environment creator.
+
+ :param options: the CLI option as parsed from :meth:`add_parser_arguments`
+ :param interpreter: the interpreter to create virtual environment from
+ """
+ self.interpreter = interpreter
+ self._debug = None
+ self.dest = Path(options.dest)
+ self.clear = options.clear
+ self.no_vcs_ignore = options.no_vcs_ignore
+ self.pyenv_cfg = PyEnvCfg.from_folder(self.dest)
+ self.app_data = options.app_data
+
+ def __repr__(self):
+ return ensure_str(self.__unicode__())
+
+ def __unicode__(self):
+ return "{}({})".format(self.__class__.__name__, ", ".join("{}={}".format(k, v) for k, v in self._args()))
+
+ def _args(self):
+ return [
+ ("dest", ensure_text(str(self.dest))),
+ ("clear", self.clear),
+ ("no_vcs_ignore", self.no_vcs_ignore),
+ ]
+
+ @classmethod
+ def can_create(cls, interpreter):
+ """Determine if we can create a virtual environment.
+
+ :param interpreter: the interpreter in question
+ :return: ``None`` if we can't create, any other object otherwise that will be forwarded to \
+ :meth:`add_parser_arguments`
+ """
+ return True
+
+ @classmethod
+ def add_parser_arguments(cls, parser, interpreter, meta, app_data):
+ """Add CLI arguments for the creator.
+
+ :param parser: the CLI parser
+ :param app_data: the application data folder
+ :param interpreter: the interpreter we're asked to create virtual environment for
+ :param meta: value as returned by :meth:`can_create`
+ """
+ parser.add_argument(
+ "dest",
+ help="directory to create virtualenv at",
+ type=cls.validate_dest,
+ )
+ parser.add_argument(
+ "--clear",
+ dest="clear",
+ action="store_true",
+ help="remove the destination directory if exist before starting (will overwrite files otherwise)",
+ default=False,
+ )
+ parser.add_argument(
+ "--no-vcs-ignore",
+ dest="no_vcs_ignore",
+ action="store_true",
+ help="don't create VCS ignore directive in the destination directory",
+ default=False,
+ )
+
+ @abstractmethod
+ def create(self):
+ """Perform the virtual environment creation."""
+ raise NotImplementedError
+
+ @classmethod
+ def validate_dest(cls, raw_value):
+ """No path separator in the path, valid chars and must be write-able"""
+
+ def non_write_able(dest, value):
+ common = Path(*os.path.commonprefix([value.parts, dest.parts]))
+ raise ArgumentTypeError(
+ "the destination {} is not write-able at {}".format(dest.relative_to(common), common),
+ )
+
+ # the file system must be able to encode
+ # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/
+ encoding = sys.getfilesystemencoding()
+ refused = OrderedDict()
+ kwargs = {"errors": "ignore"} if encoding != "mbcs" else {}
+ for char in ensure_text(raw_value):
+ try:
+ trip = char.encode(encoding, **kwargs).decode(encoding)
+ if trip == char:
+ continue
+ raise ValueError(trip)
+ except ValueError:
+ refused[char] = None
+ if refused:
+ raise ArgumentTypeError(
+ "the file system codec ({}) cannot handle characters {!r} within {!r}".format(
+ encoding,
+ "".join(refused.keys()),
+ raw_value,
+ ),
+ )
+ if os.pathsep in raw_value:
+ raise ArgumentTypeError(
+ "destination {!r} must not contain the path separator ({}) as this would break "
+ "the activation scripts".format(raw_value, os.pathsep),
+ )
+
+ value = Path(raw_value)
+ if value.exists() and value.is_file():
+ raise ArgumentTypeError("the destination {} already exists and is a file".format(value))
+ if (3, 3) <= sys.version_info <= (3, 6):
+ # pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation
+ dest = Path(os.path.realpath(raw_value))
+ else:
+ dest = Path(os.path.abspath(str(value))).resolve() # on Windows absolute does not imply resolve so use both
+ value = dest
+ while dest:
+ if dest.exists():
+ if os.access(ensure_text(str(dest)), os.W_OK):
+ break
+ else:
+ non_write_able(dest, value)
+ base, _ = dest.parent, dest.name
+ if base == dest:
+ non_write_able(dest, value) # pragma: no cover
+ dest = base
+ return str(value)
+
+ def run(self):
+ if self.dest.exists() and self.clear:
+ logging.debug("delete %s", self.dest)
+ safe_delete(self.dest)
+ self.create()
+ self.set_pyenv_cfg()
+ if not self.no_vcs_ignore:
+ self.setup_ignore_vcs()
+
+ def set_pyenv_cfg(self):
+ self.pyenv_cfg.content = OrderedDict()
+ self.pyenv_cfg["home"] = self.interpreter.system_exec_prefix
+ self.pyenv_cfg["implementation"] = self.interpreter.implementation
+ self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info)
+ self.pyenv_cfg["virtualenv"] = __version__
+
+ def setup_ignore_vcs(self):
+ """Generate ignore instructions for version control systems."""
+ # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs
+ git_ignore = self.dest / ".gitignore"
+ if not git_ignore.exists():
+ git_ignore.write_text(
+ dedent(
+ """
+ # created by virtualenv automatically
+ *
+ """,
+ ).lstrip(),
+ )
+ # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the
+ # subinclude directive from root, at which point on might as well ignore the directory itself, see
+ # https://www.selenic.com/mercurial/hgignore.5.html for more details
+ # Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore
+ # Subversion - does not support ignore files, requires direct manipulation with the svn tool
+
+ @property
+ def debug(self):
+ """
+ :return: debug information about the virtual environment (only valid after :meth:`create` has run)
+ """
+ if self._debug is None and self.exe is not None:
+ self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data)
+ return self._debug
+
+ # noinspection PyMethodMayBeStatic
+ def debug_script(self):
+ return DEBUG_SCRIPT
+
+
+def get_env_debug_info(env_exe, debug_script, app_data):
+ env = os.environ.copy()
+ env.pop(str("PYTHONPATH"), None)
+
+ with app_data.ensure_extracted(debug_script) as debug_script:
+ cmd = [str(env_exe), str(debug_script)]
+ if WIN_CPYTHON_2:
+ cmd = [ensure_text(i) for i in cmd]
+ logging.debug(str("debug via %r"), LogCmd(cmd))
+ code, out, err = run_cmd(cmd)
+
+ # noinspection PyBroadException
+ try:
+ if code != 0:
+ result = literal_eval(out)
+ else:
+ result = json.loads(out)
+ if err:
+ result["err"] = err
+ except Exception as exception:
+ return {"out": out, "err": err, "returncode": code, "exception": repr(exception)}
+ if "sys" in result and "path" in result["sys"]:
+ del result["sys"]["path"][0]
+ return result
diff --git a/third_party/python/virtualenv/virtualenv/create/debug.py b/third_party/python/virtualenv/virtualenv/create/debug.py
new file mode 100644
index 0000000000..0cdaa49412
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/debug.py
@@ -0,0 +1,110 @@
+"""Inspect a target Python interpreter virtual environment wise"""
+import sys # built-in
+
+PYPY2_WIN = hasattr(sys, "pypy_version_info") and sys.platform != "win32" and sys.version_info[0] == 2
+
+
+def encode_path(value):
+ if value is None:
+ return None
+ if not isinstance(value, (str, bytes)):
+ if isinstance(value, type):
+ value = repr(value)
+ else:
+ value = repr(type(value))
+ if isinstance(value, bytes) and not PYPY2_WIN:
+ value = value.decode(sys.getfilesystemencoding())
+ return value
+
+
+def encode_list_path(value):
+ return [encode_path(i) for i in value]
+
+
+def run():
+ """print debug data about the virtual environment"""
+ try:
+ from collections import OrderedDict
+ except ImportError: # pragma: no cover
+ # this is possible if the standard library cannot be accessed
+ # noinspection PyPep8Naming
+ OrderedDict = dict # pragma: no cover
+ result = OrderedDict([("sys", OrderedDict())])
+ path_keys = (
+ "executable",
+ "_base_executable",
+ "prefix",
+ "base_prefix",
+ "real_prefix",
+ "exec_prefix",
+ "base_exec_prefix",
+ "path",
+ "meta_path",
+ )
+ for key in path_keys:
+ value = getattr(sys, key, None)
+ if isinstance(value, list):
+ value = encode_list_path(value)
+ else:
+ value = encode_path(value)
+ result["sys"][key] = value
+ result["sys"]["fs_encoding"] = sys.getfilesystemencoding()
+ result["sys"]["io_encoding"] = getattr(sys.stdout, "encoding", None)
+ result["version"] = sys.version
+
+ try:
+ import sysconfig
+
+ # https://bugs.python.org/issue22199
+ makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None))
+ result["makefile_filename"] = encode_path(makefile())
+ except ImportError:
+ pass
+
+ import os # landmark
+
+ result["os"] = repr(os)
+
+ try:
+ # noinspection PyUnresolvedReferences
+ import site # site
+
+ result["site"] = repr(site)
+ except ImportError as exception: # pragma: no cover
+ result["site"] = repr(exception) # pragma: no cover
+
+ try:
+ # noinspection PyUnresolvedReferences
+ import datetime # site
+
+ result["datetime"] = repr(datetime)
+ except ImportError as exception: # pragma: no cover
+ result["datetime"] = repr(exception) # pragma: no cover
+
+ try:
+ # noinspection PyUnresolvedReferences
+ import math # site
+
+ result["math"] = repr(math)
+ except ImportError as exception: # pragma: no cover
+ result["math"] = repr(exception) # pragma: no cover
+
+ # try to print out, this will validate if other core modules are available (json in this case)
+ try:
+ import json
+
+ result["json"] = repr(json)
+ except ImportError as exception:
+ result["json"] = repr(exception)
+ else:
+ try:
+ content = json.dumps(result, indent=2)
+ sys.stdout.write(content)
+ except (ValueError, TypeError) as exception: # pragma: no cover
+ sys.stderr.write(repr(exception))
+ sys.stdout.write(repr(result)) # pragma: no cover
+ raise SystemExit(1) # pragma: no cover
+
+
+if __name__ == "__main__":
+ run()
diff --git a/third_party/python/virtualenv/virtualenv/create/describe.py b/third_party/python/virtualenv/virtualenv/create/describe.py
new file mode 100644
index 0000000000..1e59aaeae0
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/describe.py
@@ -0,0 +1,117 @@
+from __future__ import absolute_import, print_function, unicode_literals
+
+from abc import ABCMeta
+from collections import OrderedDict
+
+from six import add_metaclass
+
+from virtualenv.info import IS_WIN
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_text
+
+
+@add_metaclass(ABCMeta)
+class Describe(object):
+ """Given a host interpreter tell us information about what the created interpreter might look like"""
+
+ suffix = ".exe" if IS_WIN else ""
+
+ def __init__(self, dest, interpreter):
+ self.interpreter = interpreter
+ self.dest = dest
+ self._stdlib = None
+ self._stdlib_platform = None
+ self._system_stdlib = None
+ self._conf_vars = None
+
+ @property
+ def bin_dir(self):
+ return self.script_dir
+
+ @property
+ def script_dir(self):
+ return self.dest / Path(self.interpreter.distutils_install["scripts"])
+
+ @property
+ def purelib(self):
+ return self.dest / self.interpreter.distutils_install["purelib"]
+
+ @property
+ def platlib(self):
+ return self.dest / self.interpreter.distutils_install["platlib"]
+
+ @property
+ def libs(self):
+ return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys())
+
+ @property
+ def stdlib(self):
+ if self._stdlib is None:
+ self._stdlib = Path(self.interpreter.sysconfig_path("stdlib", config_var=self._config_vars))
+ return self._stdlib
+
+ @property
+ def stdlib_platform(self):
+ if self._stdlib_platform is None:
+ self._stdlib_platform = Path(self.interpreter.sysconfig_path("platstdlib", config_var=self._config_vars))
+ return self._stdlib_platform
+
+ @property
+ def _config_vars(self):
+ if self._conf_vars is None:
+ self._conf_vars = self._calc_config_vars(ensure_text(str(self.dest)))
+ return self._conf_vars
+
+ def _calc_config_vars(self, to):
+ return {
+ k: (to if v.startswith(self.interpreter.prefix) else v) for k, v in self.interpreter.sysconfig_vars.items()
+ }
+
+ @classmethod
+ def can_describe(cls, interpreter):
+ """Knows means it knows how the output will look"""
+ return True
+
+ @property
+ def env_name(self):
+ return ensure_text(self.dest.parts[-1])
+
+ @property
+ def exe(self):
+ return self.bin_dir / "{}{}".format(self.exe_stem(), self.suffix)
+
+ @classmethod
+ def exe_stem(cls):
+ """executable name without suffix - there seems to be no standard way to get this without creating it"""
+ raise NotImplementedError
+
+ def script(self, name):
+ return self.script_dir / "{}{}".format(name, self.suffix)
+
+
+@add_metaclass(ABCMeta)
+class Python2Supports(Describe):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return interpreter.version_info.major == 2 and super(Python2Supports, cls).can_describe(interpreter)
+
+
+@add_metaclass(ABCMeta)
+class Python3Supports(Describe):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return interpreter.version_info.major == 3 and super(Python3Supports, cls).can_describe(interpreter)
+
+
+@add_metaclass(ABCMeta)
+class PosixSupports(Describe):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return interpreter.os == "posix" and super(PosixSupports, cls).can_describe(interpreter)
+
+
+@add_metaclass(ABCMeta)
+class WindowsSupports(Describe):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return interpreter.os == "nt" and super(WindowsSupports, cls).can_describe(interpreter)
diff --git a/third_party/python/virtualenv/virtualenv/create/pyenv_cfg.py b/third_party/python/virtualenv/virtualenv/create/pyenv_cfg.py
new file mode 100644
index 0000000000..1a8d824401
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/pyenv_cfg.py
@@ -0,0 +1,61 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+from collections import OrderedDict
+
+from virtualenv.util.six import ensure_text
+
+
+class PyEnvCfg(object):
+ def __init__(self, content, path):
+ self.content = content
+ self.path = path
+
+ @classmethod
+ def from_folder(cls, folder):
+ return cls.from_file(folder / "pyvenv.cfg")
+
+ @classmethod
+ def from_file(cls, path):
+ content = cls._read_values(path) if path.exists() else OrderedDict()
+ return PyEnvCfg(content, path)
+
+ @staticmethod
+ def _read_values(path):
+ content = OrderedDict()
+ for line in path.read_text(encoding="utf-8").splitlines():
+ equals_at = line.index("=")
+ key = line[:equals_at].strip()
+ value = line[equals_at + 1 :].strip()
+ content[key] = value
+ return content
+
+ def write(self):
+ logging.debug("write %s", ensure_text(str(self.path)))
+ text = ""
+ for key, value in self.content.items():
+ line = "{} = {}".format(key, value)
+ logging.debug("\t%s", line)
+ text += line
+ text += "\n"
+ self.path.write_text(text, encoding="utf-8")
+
+ def refresh(self):
+ self.content = self._read_values(self.path)
+ return self.content
+
+ def __setitem__(self, key, value):
+ self.content[key] = value
+
+ def __getitem__(self, key):
+ return self.content[key]
+
+ def __contains__(self, item):
+ return item in self.content
+
+ def update(self, other):
+ self.content.update(other)
+ return self
+
+ def __repr__(self):
+ return "{}(path={})".format(self.__class__.__name__, self.path)
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/__init__.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/_virtualenv.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/_virtualenv.py
new file mode 100644
index 0000000000..da98b827a2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/_virtualenv.py
@@ -0,0 +1,130 @@
+"""Patches that are applied at runtime to the virtual environment"""
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+VIRTUALENV_PATCH_FILE = os.path.join(__file__)
+
+
+def patch_dist(dist):
+ """
+ Distutils allows user to configure some arguments via a configuration file:
+ https://docs.python.org/3/install/index.html#distutils-configuration-files
+
+ Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.
+ """
+ # we cannot allow some install config as that would get packages installed outside of the virtual environment
+ old_parse_config_files = dist.Distribution.parse_config_files
+
+ def parse_config_files(self, *args, **kwargs):
+ result = old_parse_config_files(self, *args, **kwargs)
+ install = self.get_option_dict("install")
+
+ if "prefix" in install: # the prefix governs where to install the libraries
+ install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
+ for base in ("purelib", "platlib", "headers", "scripts", "data"):
+ key = "install_{}".format(base)
+ if key in install: # do not allow global configs to hijack venv paths
+ install.pop(key, None)
+ return result
+
+ dist.Distribution.parse_config_files = parse_config_files
+
+
+# Import hook that patches some modules to ignore configuration values that break package installation in case
+# of virtual environments.
+_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
+if sys.version_info > (3, 4):
+ # https://docs.python.org/3/library/importlib.html#setting-up-an-importer
+ from functools import partial
+ from importlib.abc import MetaPathFinder
+ from importlib.util import find_spec
+
+ class _Finder(MetaPathFinder):
+ """A meta path finder that allows patching the imported distutils modules"""
+
+ fullname = None
+
+ # lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
+ # because there are gevent-based applications that need to be first to import threading by themselves.
+ # See https://github.com/pypa/virtualenv/issues/1895 for details.
+ lock = []
+
+ def find_spec(self, fullname, path, target=None):
+ if fullname in _DISTUTILS_PATCH and self.fullname is None:
+ # initialize lock[0] lazily
+ if len(self.lock) == 0:
+ import threading
+
+ lock = threading.Lock()
+ # there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
+ # observing .lock as empty, and further going into hereby initialization. However due to the GIL,
+ # list.append() operation is atomic and this way only one of the threads will "win" to put the lock
+ # - that every thread will use - into .lock[0].
+ # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
+ self.lock.append(lock)
+
+ with self.lock[0]:
+ self.fullname = fullname
+ try:
+ spec = find_spec(fullname, path)
+ if spec is not None:
+ # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
+ is_new_api = hasattr(spec.loader, "exec_module")
+ func_name = "exec_module" if is_new_api else "load_module"
+ old = getattr(spec.loader, func_name)
+ func = self.exec_module if is_new_api else self.load_module
+ if old is not func:
+ try:
+ setattr(spec.loader, func_name, partial(func, old))
+ except AttributeError:
+ pass # C-Extension loaders are r/o such as zipimporter with <python 3.7
+ return spec
+ finally:
+ self.fullname = None
+
+ @staticmethod
+ def exec_module(old, module):
+ old(module)
+ if module.__name__ in _DISTUTILS_PATCH:
+ patch_dist(module)
+
+ @staticmethod
+ def load_module(old, name):
+ module = old(name)
+ if module.__name__ in _DISTUTILS_PATCH:
+ patch_dist(module)
+ return module
+
+ sys.meta_path.insert(0, _Finder())
+else:
+ # https://www.python.org/dev/peps/pep-0302/
+ from imp import find_module
+ from pkgutil import ImpImporter, ImpLoader
+
+ class _VirtualenvImporter(object, ImpImporter):
+ def __init__(self, path=None):
+ object.__init__(self)
+ ImpImporter.__init__(self, path)
+
+ def find_module(self, fullname, path=None):
+ if fullname in _DISTUTILS_PATCH:
+ try:
+ return _VirtualenvLoader(fullname, *find_module(fullname.split(".")[-1], path))
+ except ImportError:
+ pass
+ return None
+
+ class _VirtualenvLoader(object, ImpLoader):
+ def __init__(self, fullname, file, filename, etc):
+ object.__init__(self)
+ ImpLoader.__init__(self, fullname, file, filename, etc)
+
+ def load_module(self, fullname):
+ module = super(_VirtualenvLoader, self).load_module(fullname)
+ patch_dist(module)
+ module.__loader__ = None # distlib fallback
+ return module
+
+ sys.meta_path.append(_VirtualenvImporter())
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/api.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/api.py
new file mode 100644
index 0000000000..6f296f452d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/api.py
@@ -0,0 +1,112 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+from abc import ABCMeta
+
+from six import add_metaclass
+
+from virtualenv.info import fs_supports_symlink
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_text
+
+from ..creator import Creator, CreatorMeta
+
+
+class ViaGlobalRefMeta(CreatorMeta):
+ def __init__(self):
+ super(ViaGlobalRefMeta, self).__init__()
+ self.copy_error = None
+ self.symlink_error = None
+ if not fs_supports_symlink():
+ self.symlink_error = "the filesystem does not supports symlink"
+
+ @property
+ def can_copy(self):
+ return not self.copy_error
+
+ @property
+ def can_symlink(self):
+ return not self.symlink_error
+
+
+@add_metaclass(ABCMeta)
+class ViaGlobalRefApi(Creator):
+ def __init__(self, options, interpreter):
+ super(ViaGlobalRefApi, self).__init__(options, interpreter)
+ self.symlinks = self._should_symlink(options)
+ self.enable_system_site_package = options.system_site
+
+ @staticmethod
+ def _should_symlink(options):
+ # Priority of where the option is set to follow the order: CLI, env var, file, hardcoded.
+ # If both set at same level prefers copy over symlink.
+ copies, symlinks = getattr(options, "copies", False), getattr(options, "symlinks", False)
+ copy_src, sym_src = options.get_source("copies"), options.get_source("symlinks")
+ for level in ["cli", "env var", "file", "default"]:
+ s_opt = symlinks if sym_src == level else None
+ c_opt = copies if copy_src == level else None
+ if s_opt is True and c_opt is True:
+ return False
+ if s_opt is True:
+ return True
+ if c_opt is True:
+ return False
+ return False # fallback to copy
+
+ @classmethod
+ def add_parser_arguments(cls, parser, interpreter, meta, app_data):
+ super(ViaGlobalRefApi, cls).add_parser_arguments(parser, interpreter, meta, app_data)
+ parser.add_argument(
+ "--system-site-packages",
+ default=False,
+ action="store_true",
+ dest="system_site",
+ help="give the virtual environment access to the system site-packages dir",
+ )
+ group = parser.add_mutually_exclusive_group()
+ if not meta.can_symlink and not meta.can_copy:
+ raise RuntimeError("neither symlink or copy method supported")
+ if meta.can_symlink:
+ group.add_argument(
+ "--symlinks",
+ default=True,
+ action="store_true",
+ dest="symlinks",
+ help="try to use symlinks rather than copies, when symlinks are not the default for the platform",
+ )
+ if meta.can_copy:
+ group.add_argument(
+ "--copies",
+ "--always-copy",
+ default=not meta.can_symlink,
+ action="store_true",
+ dest="copies",
+ help="try to use copies rather than symlinks, even when symlinks are the default for the platform",
+ )
+
+ def create(self):
+ self.install_patch()
+
+ def install_patch(self):
+ text = self.env_patch_text()
+ if text:
+ pth = self.purelib / "_virtualenv.pth"
+ logging.debug("create virtualenv import hook file %s", ensure_text(str(pth)))
+ pth.write_text("import _virtualenv")
+ dest_path = self.purelib / "_virtualenv.py"
+ logging.debug("create %s", ensure_text(str(dest_path)))
+ dest_path.write_text(text)
+
+ def env_patch_text(self):
+ """Patch the distutils package to not be derailed by its configuration files"""
+ with self.app_data.ensure_extracted(Path(__file__).parent / "_virtualenv.py") as resolved_path:
+ text = resolved_path.read_text()
+ return text.replace('"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib))))
+
+ def _args(self):
+ return super(ViaGlobalRefApi, self)._args() + [("global", self.enable_system_site_package)]
+
+ def set_pyenv_cfg(self):
+ super(ViaGlobalRefApi, self).set_pyenv_cfg()
+ self.pyenv_cfg["include-system-site-packages"] = "true" if self.enable_system_site_package else "false"
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/__init__.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/builtin_way.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/builtin_way.py
new file mode 100644
index 0000000000..279ee8095a
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/builtin_way.py
@@ -0,0 +1,17 @@
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta
+
+from six import add_metaclass
+
+from virtualenv.create.creator import Creator
+from virtualenv.create.describe import Describe
+
+
+@add_metaclass(ABCMeta)
+class VirtualenvBuiltin(Creator, Describe):
+ """A creator that does operations itself without delegation, if we can create it we can also describe it"""
+
+ def __init__(self, options, interpreter):
+ Creator.__init__(self, options, interpreter)
+ Describe.__init__(self, self.dest, interpreter)
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/__init__.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/__init__.py
new file mode 100644
index 0000000000..01e6d4f49d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/__init__.py
@@ -0,0 +1 @@
+from __future__ import absolute_import, unicode_literals
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/common.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/common.py
new file mode 100644
index 0000000000..c93f9f31e6
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/common.py
@@ -0,0 +1,65 @@
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta
+from collections import OrderedDict
+
+from six import add_metaclass
+
+from virtualenv.create.describe import PosixSupports, WindowsSupports
+from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen
+from virtualenv.util.path import Path
+
+from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin
+
+
+@add_metaclass(ABCMeta)
+class CPython(ViaGlobalRefVirtualenvBuiltin):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return interpreter.implementation == "CPython" and super(CPython, cls).can_describe(interpreter)
+
+ @classmethod
+ def exe_stem(cls):
+ return "python"
+
+
+@add_metaclass(ABCMeta)
+class CPythonPosix(CPython, PosixSupports):
+ """Create a CPython virtual environment on POSIX platforms"""
+
+ @classmethod
+ def _executables(cls, interpreter):
+ host_exe = Path(interpreter.system_executable)
+ major, minor = interpreter.version_info.major, interpreter.version_info.minor
+ targets = OrderedDict(
+ (i, None) for i in ["python", "python{}".format(major), "python{}.{}".format(major, minor), host_exe.name]
+ )
+ must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA
+ yield host_exe, list(targets.keys()), must, RefWhen.ANY
+
+
+@add_metaclass(ABCMeta)
+class CPythonWindows(CPython, WindowsSupports):
+ @classmethod
+ def _executables(cls, interpreter):
+ # symlink of the python executables does not work reliably, copy always instead
+ # - https://bugs.python.org/issue42013
+ # - venv
+ host = cls.host_python(interpreter)
+ for path in (host.parent / n for n in {"python.exe", host.name}):
+ yield host, [path.name], RefMust.COPY, RefWhen.ANY
+ # for more info on pythonw.exe see https://stackoverflow.com/a/30313091
+ python_w = host.parent / "pythonw.exe"
+ yield python_w, [python_w.name], RefMust.COPY, RefWhen.ANY
+
+ @classmethod
+ def host_python(cls, interpreter):
+ return Path(interpreter.system_executable)
+
+
+def is_mac_os_framework(interpreter):
+ if interpreter.platform == "darwin":
+ framework_var = interpreter.sysconfig_vars.get("PYTHONFRAMEWORK")
+ value = "Python3" if interpreter.version_info.major == 3 else "Python"
+ return framework_var == value
+ return False
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py
new file mode 100644
index 0000000000..555b0c50fc
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py
@@ -0,0 +1,102 @@
+from __future__ import absolute_import, unicode_literals
+
+import abc
+import logging
+
+from six import add_metaclass
+
+from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
+from virtualenv.util.path import Path
+
+from ..python2.python2 import Python2
+from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework
+
+
+@add_metaclass(abc.ABCMeta)
+class CPython2(CPython, Python2):
+ """Create a CPython version 2 virtual environment"""
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(CPython2, cls).sources(interpreter):
+ yield src
+ # include folder needed on Python 2 as we don't have pyenv.cfg
+ host_include_marker = cls.host_include_marker(interpreter)
+ if host_include_marker.exists():
+ yield PathRefToDest(host_include_marker.parent, dest=lambda self, _: self.include)
+
+ @classmethod
+ def needs_stdlib_py_module(cls):
+ return False
+
+ @classmethod
+ def host_include_marker(cls, interpreter):
+ return Path(interpreter.system_include) / "Python.h"
+
+ @property
+ def include(self):
+ # the pattern include the distribution name too at the end, remove that via the parent call
+ return (self.dest / self.interpreter.distutils_install["headers"]).parent
+
+ @classmethod
+ def modules(cls):
+ return [
+ "os", # landmark to set sys.prefix
+ ]
+
+ def ensure_directories(self):
+ dirs = super(CPython2, self).ensure_directories()
+ host_include_marker = self.host_include_marker(self.interpreter)
+ if host_include_marker.exists():
+ dirs.add(self.include.parent)
+ else:
+ logging.debug("no include folders as can't find include marker %s", host_include_marker)
+ return dirs
+
+
+@add_metaclass(abc.ABCMeta)
+class CPython2PosixBase(CPython2, CPythonPosix):
+ """common to macOs framework builds and other posix CPython2"""
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(CPython2PosixBase, cls).sources(interpreter):
+ yield src
+
+ # check if the makefile exists and if so make it available under the virtual environment
+ make_file = Path(interpreter.sysconfig["makefile_filename"])
+ if make_file.exists() and str(make_file).startswith(interpreter.prefix):
+ under_prefix = make_file.relative_to(Path(interpreter.prefix))
+ yield PathRefToDest(make_file, dest=lambda self, s: self.dest / under_prefix)
+
+
+class CPython2Posix(CPython2PosixBase):
+ """CPython 2 on POSIX (excluding macOs framework builds)"""
+
+ @classmethod
+ def can_describe(cls, interpreter):
+ return is_mac_os_framework(interpreter) is False and super(CPython2Posix, cls).can_describe(interpreter)
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(CPython2Posix, cls).sources(interpreter):
+ yield src
+ # landmark for exec_prefix
+ exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload")
+ yield PathRefToDest(exec_marker_file, dest=to_path)
+
+
+class CPython2Windows(CPython2, CPythonWindows):
+ """CPython 2 on Windows"""
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(CPython2Windows, cls).sources(interpreter):
+ yield src
+ py27_dll = Path(interpreter.system_executable).parent / "python27.dll"
+ if py27_dll.exists(): # this might be global in the Windows folder in which case it's alright to be missing
+ yield PathRefToDest(py27_dll, dest=cls.to_bin)
+
+ libs = Path(interpreter.system_prefix) / "libs"
+ if libs.exists():
+ yield PathRefToDest(libs, dest=lambda self, s: self.dest / s.name)
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
new file mode 100644
index 0000000000..19385095f7
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
@@ -0,0 +1,84 @@
+from __future__ import absolute_import, unicode_literals
+
+import abc
+from textwrap import dedent
+
+from six import add_metaclass
+
+from virtualenv.create.describe import Python3Supports
+from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
+from virtualenv.create.via_global_ref.store import is_store_python
+from virtualenv.util.path import Path
+
+from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework
+
+
+@add_metaclass(abc.ABCMeta)
+class CPython3(CPython, Python3Supports):
+ """"""
+
+
+class CPython3Posix(CPythonPosix, CPython3):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return is_mac_os_framework(interpreter) is False and super(CPython3Posix, cls).can_describe(interpreter)
+
+ def env_patch_text(self):
+ text = super(CPython3Posix, self).env_patch_text()
+ if self.pyvenv_launch_patch_active(self.interpreter):
+ text += dedent(
+ """
+ # for https://github.com/python/cpython/pull/9516, see https://github.com/pypa/virtualenv/issues/1704
+ import os
+ if "__PYVENV_LAUNCHER__" in os.environ:
+ del os.environ["__PYVENV_LAUNCHER__"]
+ """,
+ )
+ return text
+
+ @classmethod
+ def pyvenv_launch_patch_active(cls, interpreter):
+ ver = interpreter.version_info
+ return interpreter.platform == "darwin" and ((3, 7, 8) > ver >= (3, 7) or (3, 8, 3) > ver >= (3, 8))
+
+
+class CPython3Windows(CPythonWindows, CPython3):
+ """"""
+
+ @classmethod
+ def setup_meta(cls, interpreter):
+ if is_store_python(interpreter): # store python is not supported here
+ return None
+ return super(CPython3Windows, cls).setup_meta(interpreter)
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(CPython3Windows, cls).sources(interpreter):
+ yield src
+ if not cls.venv_37p(interpreter):
+ for src in cls.include_dll_and_pyd(interpreter):
+ yield src
+
+ @staticmethod
+ def venv_37p(interpreter):
+ return interpreter.version_info.minor >= 7
+
+ @classmethod
+ def host_python(cls, interpreter):
+ if cls.venv_37p(interpreter):
+ # starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies
+ # it also means the wrapper must be copied to avoid bugs such as https://bugs.python.org/issue42013
+ return Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt" / "python.exe"
+ return super(CPython3Windows, cls).host_python(interpreter)
+
+ @classmethod
+ def include_dll_and_pyd(cls, interpreter):
+ dll_folder = Path(interpreter.system_prefix) / "DLLs"
+ host_exe_folder = Path(interpreter.system_executable).parent
+ for folder in [host_exe_folder, dll_folder]:
+ for file in folder.iterdir():
+ if file.suffix in (".pyd", ".dll"):
+ yield PathRefToDest(file, dest=cls.to_dll_and_pyd)
+
+ def to_dll_and_pyd(self, src):
+ return self.bin_dir / src.name
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py
new file mode 100644
index 0000000000..53f65e3418
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+"""The Apple Framework builds require their own customization"""
+import logging
+import os
+import struct
+import subprocess
+from abc import ABCMeta, abstractmethod
+from textwrap import dedent
+
+from six import add_metaclass
+
+from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefMust
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_text
+
+from .common import CPython, CPythonPosix, is_mac_os_framework
+from .cpython2 import CPython2PosixBase
+from .cpython3 import CPython3
+
+
+@add_metaclass(ABCMeta)
+class CPythonmacOsFramework(CPython):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return is_mac_os_framework(interpreter) and super(CPythonmacOsFramework, cls).can_describe(interpreter)
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(CPythonmacOsFramework, cls).sources(interpreter):
+ yield src
+ # add a symlink to the host python image
+ exe = cls.image_ref(interpreter)
+ ref = PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)
+ yield ref
+
+ def create(self):
+ super(CPythonmacOsFramework, self).create()
+
+ # change the install_name of the copied python executables
+ target = "@executable_path/../.Python"
+ current = self.current_mach_o_image_path()
+ for src in self._sources:
+ if isinstance(src, ExePathRefToDest):
+ if src.must == RefMust.COPY or not self.symlinks:
+ exes = [self.bin_dir / src.base]
+ if not self.symlinks:
+ exes.extend(self.bin_dir / a for a in src.aliases)
+ for exe in exes:
+ fix_mach_o(str(exe), current, target, self.interpreter.max_size)
+
+ @classmethod
+ def _executables(cls, interpreter):
+ for _, targets, must, when in super(CPythonmacOsFramework, cls)._executables(interpreter):
+ # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the
+ # stub executable in ${sys.prefix}/bin.
+ # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951
+ fixed_host_exe = Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python"
+ yield fixed_host_exe, targets, must, when
+
+ @abstractmethod
+ def current_mach_o_image_path(self):
+ raise NotImplementedError
+
+ @classmethod
+ def image_ref(cls, interpreter):
+ raise NotImplementedError
+
+
+class CPython2macOsFramework(CPythonmacOsFramework, CPython2PosixBase):
+ @classmethod
+ def image_ref(cls, interpreter):
+ return Path(interpreter.prefix) / "Python"
+
+ def current_mach_o_image_path(self):
+ return os.path.join(self.interpreter.prefix, "Python")
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(CPython2macOsFramework, cls).sources(interpreter):
+ yield src
+ # landmark for exec_prefix
+ exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload")
+ yield PathRefToDest(exec_marker_file, dest=to_path)
+
+ @property
+ def reload_code(self):
+ result = super(CPython2macOsFramework, self).reload_code
+ result = dedent(
+ """
+ # the bundled site.py always adds the global site package if we're on python framework build, escape this
+ import sysconfig
+ config = sysconfig.get_config_vars()
+ before = config["PYTHONFRAMEWORK"]
+ try:
+ config["PYTHONFRAMEWORK"] = ""
+ {}
+ finally:
+ config["PYTHONFRAMEWORK"] = before
+ """.format(
+ result,
+ ),
+ )
+ return result
+
+
+class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix):
+ @classmethod
+ def image_ref(cls, interpreter):
+ return Path(interpreter.prefix) / "Python3"
+
+ def current_mach_o_image_path(self):
+ return "@executable_path/../../../../Python3"
+
+ @property
+ def reload_code(self):
+ result = super(CPython3macOsFramework, self).reload_code
+ result = dedent(
+ """
+ # the bundled site.py always adds the global site package if we're on python framework build, escape this
+ import sys
+ before = sys._framework
+ try:
+ sys._framework = None
+ {}
+ finally:
+ sys._framework = before
+ """.format(
+ result,
+ ),
+ )
+ return result
+
+
+def fix_mach_o(exe, current, new, max_size):
+ """
+ https://en.wikipedia.org/wiki/Mach-O
+
+ Mach-O, short for Mach object file format, is a file format for executables, object code, shared libraries,
+ dynamically-loaded code, and core dumps. A replacement for the a.out format, Mach-O offers more extensibility and
+ faster access to information in the symbol table.
+
+ Each Mach-O file is made up of one Mach-O header, followed by a series of load commands, followed by one or more
+ segments, each of which contains between 0 and 255 sections. Mach-O uses the REL relocation format to handle
+ references to symbols. When looking up symbols Mach-O uses a two-level namespace that encodes each symbol into an
+ 'object/symbol name' pair that is then linearly searched for by first the object and then the symbol name.
+
+ The basic structure—a list of variable-length "load commands" that reference pages of data elsewhere in the file—was
+ also used in the executable file format for Accent. The Accent file format was in turn, based on an idea from Spice
+ Lisp.
+
+ With the introduction of Mac OS X 10.6 platform the Mach-O file underwent a significant modification that causes
+ binaries compiled on a computer running 10.6 or later to be (by default) executable only on computers running Mac
+ OS X 10.6 or later. The difference stems from load commands that the dynamic linker, in previous Mac OS X versions,
+ does not understand. Another significant change to the Mach-O format is the change in how the Link Edit tables
+ (found in the __LINKEDIT section) function. In 10.6 these new Link Edit tables are compressed by removing unused and
+ unneeded bits of information, however Mac OS X 10.5 and earlier cannot read this new Link Edit table format.
+ """
+ try:
+ logging.debug(u"change Mach-O for %s from %s to %s", ensure_text(exe), current, ensure_text(new))
+ _builtin_change_mach_o(max_size)(exe, current, new)
+ except Exception as e:
+ logging.warning("Could not call _builtin_change_mac_o: %s. " "Trying to call install_name_tool instead.", e)
+ try:
+ cmd = ["install_name_tool", "-change", current, new, exe]
+ subprocess.check_call(cmd)
+ except Exception:
+ logging.fatal("Could not call install_name_tool -- you must " "have Apple's development tools installed")
+ raise
+
+
+def _builtin_change_mach_o(maxint):
+ MH_MAGIC = 0xFEEDFACE
+ MH_CIGAM = 0xCEFAEDFE
+ MH_MAGIC_64 = 0xFEEDFACF
+ MH_CIGAM_64 = 0xCFFAEDFE
+ FAT_MAGIC = 0xCAFEBABE
+ BIG_ENDIAN = ">"
+ LITTLE_ENDIAN = "<"
+ LC_LOAD_DYLIB = 0xC
+
+ class FileView(object):
+ """A proxy for file-like objects that exposes a given view of a file. Modified from macholib."""
+
+ def __init__(self, file_obj, start=0, size=maxint):
+ if isinstance(file_obj, FileView):
+ self._file_obj = file_obj._file_obj
+ else:
+ self._file_obj = file_obj
+ self._start = start
+ self._end = start + size
+ self._pos = 0
+
+ def __repr__(self):
+ return "<fileview [{:d}, {:d}] {!r}>".format(self._start, self._end, self._file_obj)
+
+ def tell(self):
+ return self._pos
+
+ def _checkwindow(self, seek_to, op):
+ if not (self._start <= seek_to <= self._end):
+ msg = "{} to offset {:d} is outside window [{:d}, {:d}]".format(op, seek_to, self._start, self._end)
+ raise IOError(msg)
+
+ def seek(self, offset, whence=0):
+ seek_to = offset
+ if whence == os.SEEK_SET:
+ seek_to += self._start
+ elif whence == os.SEEK_CUR:
+ seek_to += self._start + self._pos
+ elif whence == os.SEEK_END:
+ seek_to += self._end
+ else:
+ raise IOError("Invalid whence argument to seek: {!r}".format(whence))
+ self._checkwindow(seek_to, "seek")
+ self._file_obj.seek(seek_to)
+ self._pos = seek_to - self._start
+
+ def write(self, content):
+ here = self._start + self._pos
+ self._checkwindow(here, "write")
+ self._checkwindow(here + len(content), "write")
+ self._file_obj.seek(here, os.SEEK_SET)
+ self._file_obj.write(content)
+ self._pos += len(content)
+
+ def read(self, size=maxint):
+ assert size >= 0
+ here = self._start + self._pos
+ self._checkwindow(here, "read")
+ size = min(size, self._end - here)
+ self._file_obj.seek(here, os.SEEK_SET)
+ read_bytes = self._file_obj.read(size)
+ self._pos += len(read_bytes)
+ return read_bytes
+
+ def read_data(file, endian, num=1):
+ """Read a given number of 32-bits unsigned integers from the given file with the given endianness."""
+ res = struct.unpack(endian + "L" * num, file.read(num * 4))
+ if len(res) == 1:
+ return res[0]
+ return res
+
+ def mach_o_change(at_path, what, value):
+ """Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value),
+ provided it's shorter."""
+
+ def do_macho(file, bits, endian):
+ # Read Mach-O header (the magic number is assumed read by the caller)
+ cpu_type, cpu_sub_type, file_type, n_commands, size_of_commands, flags = read_data(file, endian, 6)
+ # 64-bits header has one more field.
+ if bits == 64:
+ read_data(file, endian)
+ # The header is followed by n commands
+ for _ in range(n_commands):
+ where = file.tell()
+ # Read command header
+ cmd, cmd_size = read_data(file, endian, 2)
+ if cmd == LC_LOAD_DYLIB:
+ # The first data field in LC_LOAD_DYLIB commands is the offset of the name, starting from the
+ # beginning of the command.
+ name_offset = read_data(file, endian)
+ file.seek(where + name_offset, os.SEEK_SET)
+ # Read the NUL terminated string
+ load = file.read(cmd_size - name_offset).decode()
+ load = load[: load.index("\0")]
+ # If the string is what is being replaced, overwrite it.
+ if load == what:
+ file.seek(where + name_offset, os.SEEK_SET)
+ file.write(value.encode() + b"\0")
+ # Seek to the next command
+ file.seek(where + cmd_size, os.SEEK_SET)
+
+ def do_file(file, offset=0, size=maxint):
+ file = FileView(file, offset, size)
+ # Read magic number
+ magic = read_data(file, BIG_ENDIAN)
+ if magic == FAT_MAGIC:
+ # Fat binaries contain nfat_arch Mach-O binaries
+ n_fat_arch = read_data(file, BIG_ENDIAN)
+ for _ in range(n_fat_arch):
+ # Read arch header
+ cpu_type, cpu_sub_type, offset, size, align = read_data(file, BIG_ENDIAN, 5)
+ do_file(file, offset, size)
+ elif magic == MH_MAGIC:
+ do_macho(file, 32, BIG_ENDIAN)
+ elif magic == MH_CIGAM:
+ do_macho(file, 32, LITTLE_ENDIAN)
+ elif magic == MH_MAGIC_64:
+ do_macho(file, 64, BIG_ENDIAN)
+ elif magic == MH_CIGAM_64:
+ do_macho(file, 64, LITTLE_ENDIAN)
+
+ assert len(what) >= len(value)
+
+ with open(at_path, "r+b") as f:
+ do_file(f)
+
+ return mach_o_change
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/__init__.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/common.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/common.py
new file mode 100644
index 0000000000..cc03b4293e
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/common.py
@@ -0,0 +1,53 @@
+from __future__ import absolute_import, unicode_literals
+
+import abc
+
+from six import add_metaclass
+
+from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen
+from virtualenv.util.path import Path
+
+from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin
+
+
+@add_metaclass(abc.ABCMeta)
+class PyPy(ViaGlobalRefVirtualenvBuiltin):
+ @classmethod
+ def can_describe(cls, interpreter):
+ return interpreter.implementation == "PyPy" and super(PyPy, cls).can_describe(interpreter)
+
+ @classmethod
+ def _executables(cls, interpreter):
+ host = Path(interpreter.system_executable)
+ targets = sorted("{}{}".format(name, PyPy.suffix) for name in cls.exe_names(interpreter))
+ must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA
+ yield host, targets, must, RefWhen.ANY
+
+ @classmethod
+ def exe_names(cls, interpreter):
+ return {
+ cls.exe_stem(),
+ "python",
+ "python{}".format(interpreter.version_info.major),
+ "python{}.{}".format(*interpreter.version_info),
+ }
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(PyPy, cls).sources(interpreter):
+ yield src
+ for host in cls._add_shared_libs(interpreter):
+ yield PathRefToDest(host, dest=lambda self, s: self.bin_dir / s.name)
+
+ @classmethod
+ def _add_shared_libs(cls, interpreter):
+ # https://bitbucket.org/pypy/pypy/issue/1922/future-proofing-virtualenv
+ python_dir = Path(interpreter.system_executable).resolve().parent
+ for libname in cls._shared_libs():
+ src = python_dir / libname
+ if src.exists():
+ yield src
+
+ @classmethod
+ def _shared_libs(cls):
+ raise NotImplementedError
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py
new file mode 100644
index 0000000000..020000b342
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py
@@ -0,0 +1,121 @@
+from __future__ import absolute_import, unicode_literals
+
+import abc
+import logging
+import os
+
+from six import add_metaclass
+
+from virtualenv.create.describe import PosixSupports, WindowsSupports
+from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
+from virtualenv.util.path import Path
+
+from ..python2.python2 import Python2
+from .common import PyPy
+
+
+@add_metaclass(abc.ABCMeta)
+class PyPy2(PyPy, Python2):
+ """"""
+
+ @classmethod
+ def exe_stem(cls):
+ return "pypy"
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(PyPy2, cls).sources(interpreter):
+ yield src
+ # include folder needed on Python 2 as we don't have pyenv.cfg
+ host_include_marker = cls.host_include_marker(interpreter)
+ if host_include_marker.exists():
+ yield PathRefToDest(host_include_marker.parent, dest=lambda self, _: self.include)
+
+ @classmethod
+ def needs_stdlib_py_module(cls):
+ return True
+
+ @classmethod
+ def host_include_marker(cls, interpreter):
+ return Path(interpreter.system_include) / "PyPy.h"
+
+ @property
+ def include(self):
+ return self.dest / self.interpreter.distutils_install["headers"]
+
+ @classmethod
+ def modules(cls):
+ # pypy2 uses some modules before the site.py loads, so we need to include these too
+ return super(PyPy2, cls).modules() + [
+ "os",
+ "copy_reg",
+ "genericpath",
+ "linecache",
+ "stat",
+ "UserDict",
+ "warnings",
+ ]
+
+ @property
+ def lib_pypy(self):
+ return self.dest / "lib_pypy"
+
+ def ensure_directories(self):
+ dirs = super(PyPy2, self).ensure_directories()
+ dirs.add(self.lib_pypy)
+ host_include_marker = self.host_include_marker(self.interpreter)
+ if host_include_marker.exists():
+ dirs.add(self.include.parent)
+ else:
+ logging.debug("no include folders as can't find include marker %s", host_include_marker)
+ return dirs
+
+ @property
+ def skip_rewrite(self):
+ """
+ PyPy2 built-in imports are handled by this path entry, don't overwrite to not disable it
+ see: https://github.com/pypa/virtualenv/issues/1652
+ """
+ return 'or path.endswith("lib_pypy{}__extensions__") # PyPy2 built-in import marker'.format(os.sep)
+
+
+class PyPy2Posix(PyPy2, PosixSupports):
+ """PyPy 2 on POSIX"""
+
+ @classmethod
+ def modules(cls):
+ return super(PyPy2Posix, cls).modules() + ["posixpath"]
+
+ @classmethod
+ def _shared_libs(cls):
+ return ["libpypy-c.so", "libpypy-c.dylib"]
+
+ @property
+ def lib(self):
+ return self.dest / "lib"
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(PyPy2Posix, cls).sources(interpreter):
+ yield src
+ host_lib = Path(interpreter.system_prefix) / "lib"
+ if host_lib.exists():
+ yield PathRefToDest(host_lib, dest=lambda self, _: self.lib)
+
+
+class Pypy2Windows(PyPy2, WindowsSupports):
+ """PyPy 2 on Windows"""
+
+ @classmethod
+ def modules(cls):
+ return super(Pypy2Windows, cls).modules() + ["ntpath"]
+
+ @classmethod
+ def _shared_libs(cls):
+ return ["libpypy-c.dll"]
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(Pypy2Windows, cls).sources(interpreter):
+ yield src
+ yield PathRefToDest(Path(interpreter.system_prefix) / "libs", dest=lambda self, s: self.dest / s.name)
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py
new file mode 100644
index 0000000000..9588706786
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py
@@ -0,0 +1,63 @@
+from __future__ import absolute_import, unicode_literals
+
+import abc
+
+from six import add_metaclass
+
+from virtualenv.create.describe import PosixSupports, Python3Supports, WindowsSupports
+from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
+from virtualenv.util.path import Path
+
+from .common import PyPy
+
+
+@add_metaclass(abc.ABCMeta)
+class PyPy3(PyPy, Python3Supports):
+ @classmethod
+ def exe_stem(cls):
+ return "pypy3"
+
+ @property
+ def stdlib(self):
+ """
+ PyPy3 seems to respect sysconfig only for the host python...
+ virtual environments purelib is instead lib/pythonx.y
+ """
+ return self.dest / "lib" / "python{}".format(self.interpreter.version_release_str) / "site-packages"
+
+ @classmethod
+ def exe_names(cls, interpreter):
+ return super(PyPy3, cls).exe_names(interpreter) | {"pypy"}
+
+
+class PyPy3Posix(PyPy3, PosixSupports):
+ """PyPy 2 on POSIX"""
+
+ @classmethod
+ def _shared_libs(cls):
+ return ["libpypy3-c.so", "libpypy3-c.dylib"]
+
+ def to_lib(self, src):
+ return self.dest / "lib" / src.name
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(PyPy3Posix, cls).sources(interpreter):
+ yield src
+ host_lib = Path(interpreter.system_prefix) / "lib"
+ if host_lib.exists() and host_lib.is_dir():
+ for path in host_lib.iterdir():
+ yield PathRefToDest(path, dest=cls.to_lib)
+
+
+class Pypy3Windows(PyPy3, WindowsSupports):
+ """PyPy 2 on Windows"""
+
+ @property
+ def bin_dir(self):
+ """PyPy3 needs to fallback to pypy definition"""
+ return self.dest / "Scripts"
+
+ @classmethod
+ def _shared_libs(cls):
+ return ["libpypy3-c.dll"]
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/__init__.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/python2.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/python2.py
new file mode 100644
index 0000000000..cacd56ecfc
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/python2.py
@@ -0,0 +1,111 @@
+from __future__ import absolute_import, unicode_literals
+
+import abc
+import json
+import os
+
+from six import add_metaclass
+
+from virtualenv.create.describe import Python2Supports
+from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
+from virtualenv.info import IS_ZIPAPP
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_text
+from virtualenv.util.zipapp import read as read_from_zipapp
+
+from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin
+
+HERE = Path(os.path.abspath(__file__)).parent
+
+
+@add_metaclass(abc.ABCMeta)
+class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports):
+ def create(self):
+ """Perform operations needed to make the created environment work on Python 2"""
+ super(Python2, self).create()
+ # install a patched site-package, the default Python 2 site.py is not smart enough to understand pyvenv.cfg,
+ # so we inject a small shim that can do this, the location of this depends where it's on host
+ sys_std_plat = Path(self.interpreter.system_stdlib_platform)
+ site_py_in = (
+ self.stdlib_platform
+ if ((sys_std_plat / "site.py").exists() or (sys_std_plat / "site.pyc").exists())
+ else self.stdlib
+ )
+ site_py = site_py_in / "site.py"
+
+ custom_site = get_custom_site()
+ if IS_ZIPAPP:
+ custom_site_text = read_from_zipapp(custom_site)
+ else:
+ custom_site_text = custom_site.read_text()
+ expected = json.dumps([os.path.relpath(ensure_text(str(i)), ensure_text(str(site_py))) for i in self.libs])
+
+ custom_site_text = custom_site_text.replace("___EXPECTED_SITE_PACKAGES___", expected)
+
+ reload_code = os.linesep.join(" {}".format(i) for i in self.reload_code.splitlines()).lstrip()
+ custom_site_text = custom_site_text.replace("# ___RELOAD_CODE___", reload_code)
+
+ skip_rewrite = os.linesep.join(" {}".format(i) for i in self.skip_rewrite.splitlines()).lstrip()
+ custom_site_text = custom_site_text.replace("# ___SKIP_REWRITE____", skip_rewrite)
+
+ site_py.write_text(custom_site_text)
+
+ @property
+ def reload_code(self):
+ return 'reload(sys.modules["site"]) # noqa # call system site.py to setup import libraries'
+
+ @property
+ def skip_rewrite(self):
+ return ""
+
+ @classmethod
+ def sources(cls, interpreter):
+ for src in super(Python2, cls).sources(interpreter):
+ yield src
+ # install files needed to run site.py, either from stdlib or stdlib_platform, at least pyc, but both if exists
+ # if neither exists return the module file to trigger failure
+ mappings, needs_py_module = (
+ cls.mappings(interpreter),
+ cls.needs_stdlib_py_module(),
+ )
+ for req in cls.modules():
+ module_file, to_module, module_exists = cls.from_stdlib(mappings, "{}.py".format(req))
+ compiled_file, to_compiled, compiled_exists = cls.from_stdlib(mappings, "{}.pyc".format(req))
+ if needs_py_module or module_exists or not compiled_exists:
+ yield PathRefToDest(module_file, dest=to_module)
+ if compiled_exists:
+ yield PathRefToDest(compiled_file, dest=to_compiled)
+
+ @staticmethod
+ def from_stdlib(mappings, name):
+ for from_std, to_std in mappings:
+ src = from_std / name
+ if src.exists():
+ return src, to_std, True
+ # if not exists, fallback to first in list
+ return mappings[0][0] / name, mappings[0][1], False
+
+ @classmethod
+ def mappings(cls, interpreter):
+ mappings = [(Path(interpreter.system_stdlib_platform), cls.to_stdlib_platform)]
+ if interpreter.system_stdlib_platform != interpreter.system_stdlib:
+ mappings.append((Path(interpreter.system_stdlib), cls.to_stdlib))
+ return mappings
+
+ def to_stdlib(self, src):
+ return self.stdlib / src.name
+
+ def to_stdlib_platform(self, src):
+ return self.stdlib_platform / src.name
+
+ @classmethod
+ def needs_stdlib_py_module(cls):
+ raise NotImplementedError
+
+ @classmethod
+ def modules(cls):
+ return []
+
+
+def get_custom_site():
+ return HERE / "site.py"
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/site.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/site.py
new file mode 100644
index 0000000000..85eee842ae
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/python2/site.py
@@ -0,0 +1,164 @@
+# -*- coding: utf-8 -*-
+"""
+A simple shim module to fix up things on Python 2 only.
+
+Note: until we setup correctly the paths we can only import built-ins.
+"""
+import sys
+
+
+def main():
+ """Patch what needed, and invoke the original site.py"""
+ config = read_pyvenv()
+ sys.real_prefix = sys.base_prefix = config["base-prefix"]
+ sys.base_exec_prefix = config["base-exec-prefix"]
+ sys.base_executable = config["base-executable"]
+ global_site_package_enabled = config.get("include-system-site-packages", False) == "true"
+ rewrite_standard_library_sys_path()
+ disable_user_site_package()
+ load_host_site()
+ if global_site_package_enabled:
+ add_global_site_package()
+
+
+def load_host_site():
+ """trigger reload of site.py - now it will use the standard library instance that will take care of init"""
+ # we have a duality here, we generate the platform and pure library path based on what distutils.install specifies
+ # because this is what pip will be using; the host site.py though may contain it's own pattern for where the
+ # platform and pure library paths should exist
+
+ # notably on Ubuntu there's a patch for getsitepackages to point to
+ # - prefix + local/lib/pythonx.y/dist-packages
+ # - prefix + lib/pythonx.y/dist-packages
+ # while distutils.install.cmd still points both of these to
+ # - prefix + lib/python2.7/site-packages
+
+ # to facilitate when the two match, or not we first reload the site.py, now triggering the import of host site.py,
+ # as this will ensure that initialization code within host site.py runs
+
+ here = __file__ # the distutils.install patterns will be injected relative to this site.py, save it here
+
+ # ___RELOAD_CODE___
+
+ # and then if the distutils site packages are not on the sys.path we add them via add_site_dir; note we must add
+ # them by invoking add_site_dir to trigger the processing of pth files
+ import os
+
+ site_packages = r"""
+ ___EXPECTED_SITE_PACKAGES___
+ """
+ import json
+
+ add_site_dir = sys.modules["site"].addsitedir
+ for path in json.loads(site_packages):
+ full_path = os.path.abspath(os.path.join(here, path.encode("utf-8")))
+ add_site_dir(full_path)
+
+
+sep = "\\" if sys.platform == "win32" else "/" # no os module here yet - poor mans version
+
+
+def read_pyvenv():
+ """read pyvenv.cfg"""
+ config_file = "{}{}pyvenv.cfg".format(sys.prefix, sep)
+ with open(config_file) as file_handler:
+ lines = file_handler.readlines()
+ config = {}
+ for line in lines:
+ try:
+ split_at = line.index("=")
+ except ValueError:
+ continue # ignore bad/empty lines
+ else:
+ config[line[:split_at].strip()] = line[split_at + 1 :].strip()
+ return config
+
+
+def rewrite_standard_library_sys_path():
+ """Once this site file is loaded the standard library paths have already been set, fix them up"""
+ exe, prefix, exec_prefix = get_exe_prefixes(base=False)
+ base_exe, base_prefix, base_exec = get_exe_prefixes(base=True)
+ exe_dir = exe[: exe.rfind(sep)]
+ for at, path in enumerate(sys.path):
+ path = abs_path(path) # replace old sys prefix path starts with new
+ skip_rewrite = path == exe_dir # don't fix the current executable location, notably on Windows this gets added
+ skip_rewrite = skip_rewrite # ___SKIP_REWRITE____
+ if not skip_rewrite:
+ sys.path[at] = map_path(path, base_exe, exe_dir, exec_prefix, base_prefix, prefix, base_exec)
+
+ # the rewrite above may have changed elements from PYTHONPATH, revert these if on
+ if sys.flags.ignore_environment:
+ return
+ import os
+
+ python_paths = []
+ if "PYTHONPATH" in os.environ and os.environ["PYTHONPATH"]:
+ for path in os.environ["PYTHONPATH"].split(os.pathsep):
+ if path not in python_paths:
+ python_paths.append(path)
+ sys.path[: len(python_paths)] = python_paths
+
+
+def get_exe_prefixes(base=False):
+ return tuple(abs_path(getattr(sys, ("base_" if base else "") + i)) for i in ("executable", "prefix", "exec_prefix"))
+
+
+def abs_path(value):
+ values, keep = value.split(sep), []
+ at = len(values) - 1
+ while at >= 0:
+ if values[at] == "..":
+ at -= 1
+ else:
+ keep.append(values[at])
+ at -= 1
+ return sep.join(keep[::-1])
+
+
+def map_path(path, base_executable, exe_dir, exec_prefix, base_prefix, prefix, base_exec_prefix):
+ if path_starts_with(path, exe_dir):
+ # content inside the exe folder needs to remap to original executables folder
+ orig_exe_folder = base_executable[: base_executable.rfind(sep)]
+ return "{}{}".format(orig_exe_folder, path[len(exe_dir) :])
+ elif path_starts_with(path, prefix):
+ return "{}{}".format(base_prefix, path[len(prefix) :])
+ elif path_starts_with(path, exec_prefix):
+ return "{}{}".format(base_exec_prefix, path[len(exec_prefix) :])
+ return path
+
+
+def path_starts_with(directory, value):
+ return directory.startswith(value if value[-1] == sep else value + sep)
+
+
+def disable_user_site_package():
+ """Flip the switch on enable user site package"""
+ # sys.flags is a c-extension type, so we cannot monkeypatch it, replace it with a python class to flip it
+ sys.original_flags = sys.flags
+
+ class Flags(object):
+ def __init__(self):
+ self.__dict__ = {key: getattr(sys.flags, key) for key in dir(sys.flags) if not key.startswith("_")}
+
+ sys.flags = Flags()
+ sys.flags.no_user_site = 1
+
+
+def add_global_site_package():
+ """add the global site package"""
+ import site
+
+ # add user site package
+ sys.flags = sys.original_flags # restore original
+ site.ENABLE_USER_SITE = None # reset user site check
+ # add the global site package to the path - use new prefix and delegate to site.py
+ orig_prefixes = None
+ try:
+ orig_prefixes = site.PREFIXES
+ site.PREFIXES = [sys.base_prefix, sys.base_exec_prefix]
+ site.main()
+ finally:
+ site.PREFIXES = orig_prefixes
+
+
+main()
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/ref.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/ref.py
new file mode 100644
index 0000000000..69f243bf98
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/ref.py
@@ -0,0 +1,172 @@
+"""
+Virtual environments in the traditional sense are built as reference to the host python. This file allows declarative
+references to elements on the file system, allowing our system to automatically detect what modes it can support given
+the constraints: e.g. can the file system symlink, can the files be read, executed, etc.
+"""
+from __future__ import absolute_import, unicode_literals
+
+import os
+from abc import ABCMeta, abstractmethod
+from collections import OrderedDict
+from stat import S_IXGRP, S_IXOTH, S_IXUSR
+
+from six import add_metaclass
+
+from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink
+from virtualenv.util.path import copy, make_exe, symlink
+from virtualenv.util.six import ensure_text
+
+
+class RefMust(object):
+ NA = "NA"
+ COPY = "copy"
+ SYMLINK = "symlink"
+
+
+class RefWhen(object):
+ ANY = "ANY"
+ COPY = "copy"
+ SYMLINK = "symlink"
+
+
+@add_metaclass(ABCMeta)
+class PathRef(object):
+ """Base class that checks if a file reference can be symlink/copied"""
+
+ FS_SUPPORTS_SYMLINK = fs_supports_symlink()
+ FS_CASE_SENSITIVE = fs_is_case_sensitive()
+
+ def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY):
+ self.must = must
+ self.when = when
+ self.src = src
+ try:
+ self.exists = src.exists()
+ except OSError:
+ self.exists = False
+ self._can_read = None if self.exists else False
+ self._can_copy = None if self.exists else False
+ self._can_symlink = None if self.exists else False
+
+ def __repr__(self):
+ return "{}(src={})".format(self.__class__.__name__, self.src)
+
+ @property
+ def can_read(self):
+ if self._can_read is None:
+ if self.src.is_file():
+ try:
+ with self.src.open("rb"):
+ self._can_read = True
+ except OSError:
+ self._can_read = False
+ else:
+ self._can_read = os.access(ensure_text(str(self.src)), os.R_OK)
+ return self._can_read
+
+ @property
+ def can_copy(self):
+ if self._can_copy is None:
+ if self.must == RefMust.SYMLINK:
+ self._can_copy = self.can_symlink
+ else:
+ self._can_copy = self.can_read
+ return self._can_copy
+
+ @property
+ def can_symlink(self):
+ if self._can_symlink is None:
+ if self.must == RefMust.COPY:
+ self._can_symlink = self.can_copy
+ else:
+ self._can_symlink = self.FS_SUPPORTS_SYMLINK and self.can_read
+ return self._can_symlink
+
+ @abstractmethod
+ def run(self, creator, symlinks):
+ raise NotImplementedError
+
+ def method(self, symlinks):
+ if self.must == RefMust.SYMLINK:
+ return symlink
+ if self.must == RefMust.COPY:
+ return copy
+ return symlink if symlinks else copy
+
+
+@add_metaclass(ABCMeta)
+class ExePathRef(PathRef):
+ """Base class that checks if a executable can be references via symlink/copy"""
+
+ def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY):
+ super(ExePathRef, self).__init__(src, must, when)
+ self._can_run = None
+
+ @property
+ def can_symlink(self):
+ if self.FS_SUPPORTS_SYMLINK:
+ return self.can_run
+ return False
+
+ @property
+ def can_run(self):
+ if self._can_run is None:
+ mode = self.src.stat().st_mode
+ for key in [S_IXUSR, S_IXGRP, S_IXOTH]:
+ if mode & key:
+ self._can_run = True
+ break
+ else:
+ self._can_run = False
+ return self._can_run
+
+
+class PathRefToDest(PathRef):
+ """Link a path on the file system"""
+
+ def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY):
+ super(PathRefToDest, self).__init__(src, must, when)
+ self.dest = dest
+
+ def run(self, creator, symlinks):
+ dest = self.dest(creator, self.src)
+ method = self.method(symlinks)
+ dest_iterable = dest if isinstance(dest, list) else (dest,)
+ if not dest.parent.exists():
+ dest.parent.mkdir(parents=True, exist_ok=True)
+ for dst in dest_iterable:
+ method(self.src, dst)
+
+
+class ExePathRefToDest(PathRefToDest, ExePathRef):
+ """Link a exe path on the file system"""
+
+ def __init__(self, src, targets, dest, must=RefMust.NA, when=RefWhen.ANY):
+ ExePathRef.__init__(self, src, must, when)
+ PathRefToDest.__init__(self, src, dest, must, when)
+ if not self.FS_CASE_SENSITIVE:
+ targets = list(OrderedDict((i.lower(), None) for i in targets).keys())
+ self.base = targets[0]
+ self.aliases = targets[1:]
+ self.dest = dest
+
+ def run(self, creator, symlinks):
+ bin_dir = self.dest(creator, self.src).parent
+ dest = bin_dir / self.base
+ method = self.method(symlinks)
+ method(self.src, dest)
+ if not symlinks:
+ make_exe(dest)
+ for extra in self.aliases:
+ link_file = bin_dir / extra
+ if link_file.exists():
+ link_file.unlink()
+ if symlinks:
+ link_file.symlink_to(self.base)
+ else:
+ copy(self.src, link_file)
+ if not symlinks:
+ make_exe(link_file)
+
+ def __repr__(self):
+ return "{}(src={}, alias={})".format(self.__class__.__name__, self.src, self.aliases)
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
new file mode 100644
index 0000000000..863ae16e1d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
@@ -0,0 +1,114 @@
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta
+
+from six import add_metaclass
+
+from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, RefMust, RefWhen
+from virtualenv.util.path import ensure_dir
+
+from ..api import ViaGlobalRefApi, ViaGlobalRefMeta
+from .builtin_way import VirtualenvBuiltin
+
+
+class BuiltinViaGlobalRefMeta(ViaGlobalRefMeta):
+ def __init__(self):
+ super(BuiltinViaGlobalRefMeta, self).__init__()
+ self.sources = []
+
+
+@add_metaclass(ABCMeta)
+class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin):
+ def __init__(self, options, interpreter):
+ super(ViaGlobalRefVirtualenvBuiltin, self).__init__(options, interpreter)
+ self._sources = getattr(options.meta, "sources", None) # if we're created as a describer this might be missing
+
+ @classmethod
+ def can_create(cls, interpreter):
+ """By default all built-in methods assume that if we can describe it we can create it"""
+ # first we must be able to describe it
+ if not cls.can_describe(interpreter):
+ return None
+ meta = cls.setup_meta(interpreter)
+ if meta is not None and meta:
+ cls._sources_can_be_applied(interpreter, meta)
+ return meta
+
+ @classmethod
+ def _sources_can_be_applied(cls, interpreter, meta):
+ for src in cls.sources(interpreter):
+ if src.exists:
+ if meta.can_copy and not src.can_copy:
+ meta.copy_error = "cannot copy {}".format(src)
+ if meta.can_symlink and not src.can_symlink:
+ meta.symlink_error = "cannot symlink {}".format(src)
+ else:
+ msg = "missing required file {}".format(src)
+ if src.when == RefMust.NA:
+ meta.error = msg
+ elif src.when == RefMust.COPY:
+ meta.copy_error = msg
+ elif src.when == RefMust.SYMLINK:
+ meta.symlink_error = msg
+ if not meta.can_copy and not meta.can_symlink:
+ meta.error = "neither copy or symlink supported, copy: {} symlink: {}".format(
+ meta.copy_error,
+ meta.symlink_error,
+ )
+ if meta.error:
+ break
+ meta.sources.append(src)
+
+ @classmethod
+ def setup_meta(cls, interpreter):
+ return BuiltinViaGlobalRefMeta()
+
+ @classmethod
+ def sources(cls, interpreter):
+ for host_exe, targets, must, when in cls._executables(interpreter):
+ yield ExePathRefToDest(host_exe, dest=cls.to_bin, targets=targets, must=must, when=when)
+
+ def to_bin(self, src):
+ return self.bin_dir / src.name
+
+ @classmethod
+ def _executables(cls, interpreter):
+ raise NotImplementedError
+
+ def create(self):
+ dirs = self.ensure_directories()
+ for directory in list(dirs):
+ if any(i for i in dirs if i is not directory and directory.parts == i.parts[: len(directory.parts)]):
+ dirs.remove(directory)
+ for directory in sorted(dirs):
+ ensure_dir(directory)
+
+ self.set_pyenv_cfg()
+ self.pyenv_cfg.write()
+ true_system_site = self.enable_system_site_package
+ try:
+ self.enable_system_site_package = False
+ for src in self._sources:
+ if (
+ src.when == RefWhen.ANY
+ or (src.when == RefWhen.SYMLINK and self.symlinks is True)
+ or (src.when == RefWhen.COPY and self.symlinks is False)
+ ):
+ src.run(self, self.symlinks)
+ finally:
+ if true_system_site != self.enable_system_site_package:
+ self.enable_system_site_package = true_system_site
+ super(ViaGlobalRefVirtualenvBuiltin, self).create()
+
+ def ensure_directories(self):
+ return {self.dest, self.bin_dir, self.script_dir, self.stdlib} | set(self.libs)
+
+ def set_pyenv_cfg(self):
+ """
+ We directly inject the base prefix and base exec prefix to avoid site.py needing to discover these
+ from home (which usually is done within the interpreter itself)
+ """
+ super(ViaGlobalRefVirtualenvBuiltin, self).set_pyenv_cfg()
+ self.pyenv_cfg["base-prefix"] = self.interpreter.system_prefix
+ self.pyenv_cfg["base-exec-prefix"] = self.interpreter.system_exec_prefix
+ self.pyenv_cfg["base-executable"] = self.interpreter.system_executable
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/store.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/store.py
new file mode 100644
index 0000000000..134a535859
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/store.py
@@ -0,0 +1,26 @@
+from __future__ import absolute_import, unicode_literals
+
+from virtualenv.util.path import Path
+
+
+def handle_store_python(meta, interpreter):
+ if is_store_python(interpreter):
+ meta.symlink_error = "Windows Store Python does not support virtual environments via symlink"
+ return meta
+
+
+def is_store_python(interpreter):
+ parts = Path(interpreter.system_executable).parts
+ return (
+ len(parts) > 4
+ and parts[-4] == "Microsoft"
+ and parts[-3] == "WindowsApps"
+ and parts[-2].startswith("PythonSoftwareFoundation.Python.3.")
+ and parts[-1].startswith("python")
+ )
+
+
+__all__ = (
+ "handle_store_python",
+ "is_store_python",
+)
diff --git a/third_party/python/virtualenv/virtualenv/create/via_global_ref/venv.py b/third_party/python/virtualenv/virtualenv/create/via_global_ref/venv.py
new file mode 100644
index 0000000000..aaa67947f1
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/create/via_global_ref/venv.py
@@ -0,0 +1,83 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+from copy import copy
+
+from virtualenv.create.via_global_ref.store import handle_store_python
+from virtualenv.discovery.py_info import PythonInfo
+from virtualenv.util.error import ProcessCallFailed
+from virtualenv.util.path import ensure_dir
+from virtualenv.util.subprocess import run_cmd
+
+from .api import ViaGlobalRefApi, ViaGlobalRefMeta
+
+
+class Venv(ViaGlobalRefApi):
+ def __init__(self, options, interpreter):
+ self.describe = options.describe
+ super(Venv, self).__init__(options, interpreter)
+ self.can_be_inline = (
+ interpreter is PythonInfo.current() and interpreter.executable == interpreter.system_executable
+ )
+ self._context = None
+
+ def _args(self):
+ return super(Venv, self)._args() + ([("describe", self.describe.__class__.__name__)] if self.describe else [])
+
+ @classmethod
+ def can_create(cls, interpreter):
+ if interpreter.has_venv:
+ meta = ViaGlobalRefMeta()
+ if interpreter.platform == "win32" and interpreter.version_info.major == 3:
+ meta = handle_store_python(meta, interpreter)
+ return meta
+ return None
+
+ def create(self):
+ if self.can_be_inline:
+ self.create_inline()
+ else:
+ self.create_via_sub_process()
+ for lib in self.libs:
+ ensure_dir(lib)
+ super(Venv, self).create()
+
+ def create_inline(self):
+ from venv import EnvBuilder
+
+ builder = EnvBuilder(
+ system_site_packages=self.enable_system_site_package,
+ clear=False,
+ symlinks=self.symlinks,
+ with_pip=False,
+ )
+ builder.create(str(self.dest))
+
+ def create_via_sub_process(self):
+ cmd = self.get_host_create_cmd()
+ logging.info("using host built-in venv to create via %s", " ".join(cmd))
+ code, out, err = run_cmd(cmd)
+ if code != 0:
+ raise ProcessCallFailed(code, out, err, cmd)
+
+ def get_host_create_cmd(self):
+ cmd = [self.interpreter.system_executable, "-m", "venv", "--without-pip"]
+ if self.enable_system_site_package:
+ cmd.append("--system-site-packages")
+ cmd.append("--symlinks" if self.symlinks else "--copies")
+ cmd.append(str(self.dest))
+ return cmd
+
+ def set_pyenv_cfg(self):
+ # prefer venv options over ours, but keep our extra
+ venv_content = copy(self.pyenv_cfg.refresh())
+ super(Venv, self).set_pyenv_cfg()
+ self.pyenv_cfg.update(venv_content)
+
+ def __getattribute__(self, item):
+ describe = object.__getattribute__(self, "describe")
+ if describe is not None and hasattr(describe, item):
+ element = getattr(describe, item)
+ if not callable(element) or item in ("script",):
+ return element
+ return object.__getattribute__(self, item)
diff --git a/third_party/python/virtualenv/virtualenv/discovery/__init__.py b/third_party/python/virtualenv/virtualenv/discovery/__init__.py
new file mode 100644
index 0000000000..01e6d4f49d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/__init__.py
@@ -0,0 +1 @@
+from __future__ import absolute_import, unicode_literals
diff --git a/third_party/python/virtualenv/virtualenv/discovery/builtin.py b/third_party/python/virtualenv/virtualenv/discovery/builtin.py
new file mode 100644
index 0000000000..b66ecb1932
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/builtin.py
@@ -0,0 +1,163 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import sys
+
+from virtualenv.info import IS_WIN
+from virtualenv.util.six import ensure_str, ensure_text
+
+from .discover import Discover
+from .py_info import PythonInfo
+from .py_spec import PythonSpec
+
+
+class Builtin(Discover):
+ def __init__(self, options):
+ super(Builtin, self).__init__(options)
+ self.python_spec = options.python if options.python else [sys.executable]
+ self.app_data = options.app_data
+
+ @classmethod
+ def add_parser_arguments(cls, parser):
+ parser.add_argument(
+ "-p",
+ "--python",
+ dest="python",
+ metavar="py",
+ type=str,
+ action="append",
+ default=[],
+ help="interpreter based on what to create environment (path/identifier) "
+ "- by default use the interpreter where the tool is installed - first found wins",
+ )
+
+ def run(self):
+ for python_spec in self.python_spec:
+ result = get_interpreter(python_spec, self.app_data)
+ if result is not None:
+ return result
+ return None
+
+ def __repr__(self):
+ return ensure_str(self.__unicode__())
+
+ def __unicode__(self):
+ spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec
+ return "{} discover of python_spec={!r}".format(self.__class__.__name__, spec)
+
+
+def get_interpreter(key, app_data=None):
+ spec = PythonSpec.from_string_spec(key)
+ logging.info("find interpreter for spec %r", spec)
+ proposed_paths = set()
+ for interpreter, impl_must_match in propose_interpreters(spec, app_data):
+ key = interpreter.system_executable, impl_must_match
+ if key in proposed_paths:
+ continue
+ logging.info("proposed %s", interpreter)
+ if interpreter.satisfies(spec, impl_must_match):
+ logging.debug("accepted %s", interpreter)
+ return interpreter
+ proposed_paths.add(key)
+
+
+def propose_interpreters(spec, app_data):
+ # 1. if it's a path and exists
+ if spec.path is not None:
+ try:
+ os.lstat(spec.path) # Windows Store Python does not work with os.path.exists, but does for os.lstat
+ except OSError:
+ if spec.is_abs:
+ raise
+ else:
+ yield PythonInfo.from_exe(os.path.abspath(spec.path), app_data), True
+ if spec.is_abs:
+ return
+ else:
+ # 2. otherwise try with the current
+ yield PythonInfo.current_system(app_data), True
+
+ # 3. otherwise fallback to platform default logic
+ if IS_WIN:
+ from .windows import propose_interpreters
+
+ for interpreter in propose_interpreters(spec, app_data):
+ yield interpreter, True
+ # finally just find on path, the path order matters (as the candidates are less easy to control by end user)
+ paths = get_paths()
+ tested_exes = set()
+ for pos, path in enumerate(paths):
+ path = ensure_text(path)
+ logging.debug(LazyPathDump(pos, path))
+ for candidate, match in possible_specs(spec):
+ found = check_path(candidate, path)
+ if found is not None:
+ exe = os.path.abspath(found)
+ if exe not in tested_exes:
+ tested_exes.add(exe)
+ interpreter = PathPythonInfo.from_exe(exe, app_data, raise_on_error=False)
+ if interpreter is not None:
+ yield interpreter, match
+
+
+def get_paths():
+ path = os.environ.get(str("PATH"), None)
+ if path is None:
+ try:
+ path = os.confstr("CS_PATH")
+ except (AttributeError, ValueError):
+ path = os.defpath
+ if not path:
+ paths = []
+ else:
+ paths = [p for p in path.split(os.pathsep) if os.path.exists(p)]
+ return paths
+
+
+class LazyPathDump(object):
+ def __init__(self, pos, path):
+ self.pos = pos
+ self.path = path
+
+ def __repr__(self):
+ return ensure_str(self.__unicode__())
+
+ def __unicode__(self):
+ content = "discover PATH[{}]={}".format(self.pos, self.path)
+ if os.environ.get(str("_VIRTUALENV_DEBUG")): # this is the over the board debug
+ content += " with =>"
+ for file_name in os.listdir(self.path):
+ try:
+ file_path = os.path.join(self.path, file_name)
+ if os.path.isdir(file_path) or not os.access(file_path, os.X_OK):
+ continue
+ except OSError:
+ pass
+ content += " "
+ content += file_name
+ return content
+
+
+def check_path(candidate, path):
+ _, ext = os.path.splitext(candidate)
+ if sys.platform == "win32" and ext != ".exe":
+ candidate = candidate + ".exe"
+ if os.path.isfile(candidate):
+ return candidate
+ candidate = os.path.join(path, candidate)
+ if os.path.isfile(candidate):
+ return candidate
+ return None
+
+
+def possible_specs(spec):
+ # 4. then maybe it's something exact on PATH - if it was direct lookup implementation no longer counts
+ yield spec.str_spec, False
+ # 5. or from the spec we can deduce a name on path that matches
+ for exe, match in spec.generate_names():
+ yield exe, match
+
+
+class PathPythonInfo(PythonInfo):
+ """"""
diff --git a/third_party/python/virtualenv/virtualenv/discovery/cached_py_info.py b/third_party/python/virtualenv/virtualenv/discovery/cached_py_info.py
new file mode 100644
index 0000000000..ce79ef14b1
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/cached_py_info.py
@@ -0,0 +1,148 @@
+"""
+
+We acquire the python information by running an interrogation script via subprocess trigger. This operation is not
+cheap, especially not on Windows. To not have to pay this hefty cost every time we apply multiple levels of
+caching.
+"""
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import pipes
+import sys
+from collections import OrderedDict
+
+from virtualenv.app_data import AppDataDisabled
+from virtualenv.discovery.py_info import PythonInfo
+from virtualenv.info import PY2
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_text
+from virtualenv.util.subprocess import Popen, subprocess
+
+_CACHE = OrderedDict()
+_CACHE[Path(sys.executable)] = PythonInfo()
+
+
+def from_exe(cls, app_data, exe, raise_on_error=True, ignore_cache=False):
+ """"""
+ result = _get_from_cache(cls, app_data, exe, ignore_cache=ignore_cache)
+ if isinstance(result, Exception):
+ if raise_on_error:
+ raise result
+ else:
+ logging.info("%s", str(result))
+ result = None
+ return result
+
+
+def _get_from_cache(cls, app_data, exe, ignore_cache=True):
+ # note here we cannot resolve symlinks, as the symlink may trigger different prefix information if there's a
+ # pyenv.cfg somewhere alongside on python3.4+
+ exe_path = Path(exe)
+ if not ignore_cache and exe_path in _CACHE: # check in the in-memory cache
+ result = _CACHE[exe_path]
+ else: # otherwise go through the app data cache
+ py_info = _get_via_file_cache(cls, app_data, exe_path, exe)
+ result = _CACHE[exe_path] = py_info
+ # independent if it was from the file or in-memory cache fix the original executable location
+ if isinstance(result, PythonInfo):
+ result.executable = exe
+ return result
+
+
+def _get_via_file_cache(cls, app_data, path, exe):
+ path_text = ensure_text(str(path))
+ try:
+ path_modified = path.stat().st_mtime
+ except OSError:
+ path_modified = -1
+ if app_data is None:
+ app_data = AppDataDisabled()
+ py_info, py_info_store = None, app_data.py_info(path)
+ with py_info_store.locked():
+ if py_info_store.exists(): # if exists and matches load
+ data = py_info_store.read()
+ of_path, of_st_mtime, of_content = data["path"], data["st_mtime"], data["content"]
+ if of_path == path_text and of_st_mtime == path_modified:
+ py_info = cls._from_dict({k: v for k, v in of_content.items()})
+ else:
+ py_info_store.remove()
+ if py_info is None: # if not loaded run and save
+ failure, py_info = _run_subprocess(cls, exe, app_data)
+ if failure is None:
+ data = {"st_mtime": path_modified, "path": path_text, "content": py_info._to_dict()}
+ py_info_store.write(data)
+ else:
+ py_info = failure
+ return py_info
+
+
+def _run_subprocess(cls, exe, app_data):
+ py_info_script = Path(os.path.abspath(__file__)).parent / "py_info.py"
+ with app_data.ensure_extracted(py_info_script) as py_info_script:
+ cmd = [exe, str(py_info_script)]
+ # prevent sys.prefix from leaking into the child process - see https://bugs.python.org/issue22490
+ env = os.environ.copy()
+ env.pop("__PYVENV_LAUNCHER__", None)
+ logging.debug("get interpreter info via cmd: %s", LogCmd(cmd))
+ try:
+ process = Popen(
+ cmd,
+ universal_newlines=True,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ env=env,
+ )
+ out, err = process.communicate()
+ code = process.returncode
+ except OSError as os_error:
+ out, err, code = "", os_error.strerror, os_error.errno
+ result, failure = None, None
+ if code == 0:
+ result = cls._from_json(out)
+ result.executable = exe # keep original executable as this may contain initialization code
+ else:
+ msg = "failed to query {} with code {}{}{}".format(
+ exe,
+ code,
+ " out: {!r}".format(out) if out else "",
+ " err: {!r}".format(err) if err else "",
+ )
+ failure = RuntimeError(msg)
+ return failure, result
+
+
+class LogCmd(object):
+ def __init__(self, cmd, env=None):
+ self.cmd = cmd
+ self.env = env
+
+ def __repr__(self):
+ def e(v):
+ return v.decode("utf-8") if isinstance(v, bytes) else v
+
+ cmd_repr = e(" ").join(pipes.quote(e(c)) for c in self.cmd)
+ if self.env is not None:
+ cmd_repr += e(" env of {!r}").format(self.env)
+ if PY2:
+ return cmd_repr.encode("utf-8")
+ return cmd_repr
+
+ def __unicode__(self):
+ raw = repr(self)
+ if PY2:
+ return raw.decode("utf-8")
+ return raw
+
+
+def clear(app_data):
+ app_data.py_info_clear()
+ _CACHE.clear()
+
+
+___all___ = (
+ "from_exe",
+ "clear",
+ "LogCmd",
+)
diff --git a/third_party/python/virtualenv/virtualenv/discovery/discover.py b/third_party/python/virtualenv/virtualenv/discovery/discover.py
new file mode 100644
index 0000000000..93c3ea7ad7
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/discover.py
@@ -0,0 +1,46 @@
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta, abstractmethod
+
+from six import add_metaclass
+
+
+@add_metaclass(ABCMeta)
+class Discover(object):
+ """Discover and provide the requested Python interpreter"""
+
+ @classmethod
+ def add_parser_arguments(cls, parser):
+ """Add CLI arguments for this discovery mechanisms.
+
+ :param parser: the CLI parser
+ """
+ raise NotImplementedError
+
+ # noinspection PyUnusedLocal
+ def __init__(self, options):
+ """Create a new discovery mechanism.
+
+ :param options: the parsed options as defined within :meth:`add_parser_arguments`
+ """
+ self._has_run = False
+ self._interpreter = None
+
+ @abstractmethod
+ def run(self):
+ """Discovers an interpreter.
+
+
+ :return: the interpreter ready to use for virtual environment creation
+ """
+ raise NotImplementedError
+
+ @property
+ def interpreter(self):
+ """
+ :return: the interpreter as returned by :meth:`run`, cached
+ """
+ if self._has_run is False:
+ self._interpreter = self.run()
+ self._has_run = True
+ return self._interpreter
diff --git a/third_party/python/virtualenv/virtualenv/discovery/py_info.py b/third_party/python/virtualenv/virtualenv/discovery/py_info.py
new file mode 100644
index 0000000000..46b51df1b3
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/py_info.py
@@ -0,0 +1,490 @@
+"""
+The PythonInfo contains information about a concrete instance of a Python interpreter
+
+Note: this file is also used to query target interpreters, so can only use standard library methods
+"""
+from __future__ import absolute_import, print_function
+
+import json
+import logging
+import os
+import platform
+import re
+import sys
+import sysconfig
+from collections import OrderedDict, namedtuple
+from distutils import dist
+from distutils.command.install import SCHEME_KEYS
+from string import digits
+
+VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"])
+
+
+def _get_path_extensions():
+ return list(OrderedDict.fromkeys([""] + os.environ.get("PATHEXT", "").lower().split(os.pathsep)))
+
+
+EXTENSIONS = _get_path_extensions()
+_CONF_VAR_RE = re.compile(r"\{\w+\}")
+
+
+class PythonInfo(object):
+ """Contains information for a Python interpreter"""
+
+ def __init__(self):
+ def u(v):
+ return v.decode("utf-8") if isinstance(v, bytes) else v
+
+ def abs_path(v):
+ return None if v is None else os.path.abspath(v) # unroll relative elements from path (e.g. ..)
+
+ # qualifies the python
+ self.platform = u(sys.platform)
+ self.implementation = u(platform.python_implementation())
+ if self.implementation == "PyPy":
+ self.pypy_version_info = tuple(u(i) for i in sys.pypy_version_info)
+
+ # this is a tuple in earlier, struct later, unify to our own named tuple
+ self.version_info = VersionInfo(*list(u(i) for i in sys.version_info))
+ self.architecture = 64 if sys.maxsize > 2 ** 32 else 32
+
+ self.version = u(sys.version)
+ self.os = u(os.name)
+
+ # information about the prefix - determines python home
+ self.prefix = u(abs_path(getattr(sys, "prefix", None))) # prefix we think
+ self.base_prefix = u(abs_path(getattr(sys, "base_prefix", None))) # venv
+ self.real_prefix = u(abs_path(getattr(sys, "real_prefix", None))) # old virtualenv
+
+ # information about the exec prefix - dynamic stdlib modules
+ self.base_exec_prefix = u(abs_path(getattr(sys, "base_exec_prefix", None)))
+ self.exec_prefix = u(abs_path(getattr(sys, "exec_prefix", None)))
+
+ self.executable = u(abs_path(sys.executable)) # the executable we were invoked via
+ self.original_executable = u(abs_path(self.executable)) # the executable as known by the interpreter
+ self.system_executable = self._fast_get_system_executable() # the executable we are based of (if available)
+
+ try:
+ __import__("venv")
+ has = True
+ except ImportError:
+ has = False
+ self.has_venv = has
+ self.path = [u(i) for i in sys.path]
+ self.file_system_encoding = u(sys.getfilesystemencoding())
+ self.stdout_encoding = u(getattr(sys.stdout, "encoding", None))
+
+ self.sysconfig_paths = {u(i): u(sysconfig.get_path(i, expand=False)) for i in sysconfig.get_path_names()}
+ # https://bugs.python.org/issue22199
+ makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None))
+ self.sysconfig = {
+ u(k): u(v)
+ for k, v in [
+ # a list of content to store from sysconfig
+ ("makefile_filename", makefile()),
+ ]
+ if k is not None
+ }
+
+ config_var_keys = set()
+ for element in self.sysconfig_paths.values():
+ for k in _CONF_VAR_RE.findall(element):
+ config_var_keys.add(u(k[1:-1]))
+ config_var_keys.add("PYTHONFRAMEWORK")
+
+ self.sysconfig_vars = {u(i): u(sysconfig.get_config_var(i) or "") for i in config_var_keys}
+ if self.implementation == "PyPy" and sys.version_info.major == 2:
+ self.sysconfig_vars[u"implementation_lower"] = u"python"
+
+ self.distutils_install = {u(k): u(v) for k, v in self._distutils_install().items()}
+ confs = {k: (self.system_prefix if v.startswith(self.prefix) else v) for k, v in self.sysconfig_vars.items()}
+ self.system_stdlib = self.sysconfig_path("stdlib", confs)
+ self.system_stdlib_platform = self.sysconfig_path("platstdlib", confs)
+ self.max_size = getattr(sys, "maxsize", getattr(sys, "maxint", None))
+ self._creators = None
+
+ def _fast_get_system_executable(self):
+ """Try to get the system executable by just looking at properties"""
+ if self.real_prefix or (
+ self.base_prefix is not None and self.base_prefix != self.prefix
+ ): # if this is a virtual environment
+ if self.real_prefix is None:
+ base_executable = getattr(sys, "_base_executable", None) # some platforms may set this to help us
+ if base_executable is not None: # use the saved system executable if present
+ if sys.executable != base_executable: # we know we're in a virtual environment, cannot be us
+ return base_executable
+ return None # in this case we just can't tell easily without poking around FS and calling them, bail
+ # if we're not in a virtual environment, this is already a system python, so return the original executable
+ # note we must choose the original and not the pure executable as shim scripts might throw us off
+ return self.original_executable
+
+ @staticmethod
+ def _distutils_install():
+ # follow https://github.com/pypa/pip/blob/main/src/pip/_internal/locations.py#L95
+ # note here we don't import Distribution directly to allow setuptools to patch it
+ d = dist.Distribution({"script_args": "--no-user-cfg"}) # conf files not parsed so they do not hijack paths
+ if hasattr(sys, "_framework"):
+ sys._framework = None # disable macOS static paths for framework
+ i = d.get_command_obj("install", create=True)
+ i.prefix = os.sep # paths generated are relative to prefix that contains the path sep, this makes it relative
+ i.finalize_options()
+ result = {key: (getattr(i, "install_{}".format(key))[1:]).lstrip(os.sep) for key in SCHEME_KEYS}
+ return result
+
+ @property
+ def version_str(self):
+ return ".".join(str(i) for i in self.version_info[0:3])
+
+ @property
+ def version_release_str(self):
+ return ".".join(str(i) for i in self.version_info[0:2])
+
+ @property
+ def python_name(self):
+ version_info = self.version_info
+ return "python{}.{}".format(version_info.major, version_info.minor)
+
+ @property
+ def is_old_virtualenv(self):
+ return self.real_prefix is not None
+
+ @property
+ def is_venv(self):
+ return self.base_prefix is not None and self.version_info.major == 3
+
+ def sysconfig_path(self, key, config_var=None, sep=os.sep):
+ pattern = self.sysconfig_paths[key]
+ if config_var is None:
+ config_var = self.sysconfig_vars
+ else:
+ base = {k: v for k, v in self.sysconfig_vars.items()}
+ base.update(config_var)
+ config_var = base
+ return pattern.format(**config_var).replace(u"/", sep)
+
+ def creators(self, refresh=False):
+ if self._creators is None or refresh is True:
+ from virtualenv.run.plugin.creators import CreatorSelector
+
+ self._creators = CreatorSelector.for_interpreter(self)
+ return self._creators
+
+ @property
+ def system_include(self):
+ path = self.sysconfig_path(
+ "include",
+ {k: (self.system_prefix if v.startswith(self.prefix) else v) for k, v in self.sysconfig_vars.items()},
+ )
+ if not os.path.exists(path): # some broken packaging don't respect the sysconfig, fallback to distutils path
+ # the pattern include the distribution name too at the end, remove that via the parent call
+ fallback = os.path.join(self.prefix, os.path.dirname(self.distutils_install["headers"]))
+ if os.path.exists(fallback):
+ path = fallback
+ return path
+
+ @property
+ def system_prefix(self):
+ return self.real_prefix or self.base_prefix or self.prefix
+
+ @property
+ def system_exec_prefix(self):
+ return self.real_prefix or self.base_exec_prefix or self.exec_prefix
+
+ def __unicode__(self):
+ content = repr(self)
+ if sys.version_info == 2:
+ content = content.decode("utf-8")
+ return content
+
+ def __repr__(self):
+ return "{}({!r})".format(
+ self.__class__.__name__,
+ {k: v for k, v in self.__dict__.items() if not k.startswith("_")},
+ )
+
+ def __str__(self):
+ content = "{}({})".format(
+ self.__class__.__name__,
+ ", ".join(
+ "{}={}".format(k, v)
+ for k, v in (
+ ("spec", self.spec),
+ (
+ "system"
+ if self.system_executable is not None and self.system_executable != self.executable
+ else None,
+ self.system_executable,
+ ),
+ (
+ "original"
+ if (
+ self.original_executable != self.system_executable
+ and self.original_executable != self.executable
+ )
+ else None,
+ self.original_executable,
+ ),
+ ("exe", self.executable),
+ ("platform", self.platform),
+ ("version", repr(self.version)),
+ ("encoding_fs_io", "{}-{}".format(self.file_system_encoding, self.stdout_encoding)),
+ )
+ if k is not None
+ ),
+ )
+ return content
+
+ @property
+ def spec(self):
+ return "{}{}-{}".format(self.implementation, ".".join(str(i) for i in self.version_info), self.architecture)
+
+ @classmethod
+ def clear_cache(cls, app_data):
+ # this method is not used by itself, so here and called functions can import stuff locally
+ from virtualenv.discovery.cached_py_info import clear
+
+ clear(app_data)
+ cls._cache_exe_discovery.clear()
+
+ def satisfies(self, spec, impl_must_match):
+ """check if a given specification can be satisfied by the this python interpreter instance"""
+ if spec.path:
+ if self.executable == os.path.abspath(spec.path):
+ return True # if the path is a our own executable path we're done
+ if not spec.is_abs:
+ # if path set, and is not our original executable name, this does not match
+ basename = os.path.basename(self.original_executable)
+ spec_path = spec.path
+ if sys.platform == "win32":
+ basename, suffix = os.path.splitext(basename)
+ if spec_path.endswith(suffix):
+ spec_path = spec_path[: -len(suffix)]
+ if basename != spec_path:
+ return False
+
+ if impl_must_match:
+ if spec.implementation is not None and spec.implementation.lower() != self.implementation.lower():
+ return False
+
+ if spec.architecture is not None and spec.architecture != self.architecture:
+ return False
+
+ for our, req in zip(self.version_info[0:3], (spec.major, spec.minor, spec.micro)):
+ if req is not None and our is not None and our != req:
+ return False
+ return True
+
+ _current_system = None
+ _current = None
+
+ @classmethod
+ def current(cls, app_data=None):
+ """
+ This locates the current host interpreter information. This might be different than what we run into in case
+ the host python has been upgraded from underneath us.
+ """
+ if cls._current is None:
+ cls._current = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=False)
+ return cls._current
+
+ @classmethod
+ def current_system(cls, app_data=None):
+ """
+ This locates the current host interpreter information. This might be different than what we run into in case
+ the host python has been upgraded from underneath us.
+ """
+ if cls._current_system is None:
+ cls._current_system = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=True)
+ return cls._current_system
+
+ def _to_json(self):
+ # don't save calculated paths, as these are non primitive types
+ return json.dumps(self._to_dict(), indent=2)
+
+ def _to_dict(self):
+ data = {var: (getattr(self, var) if var not in ("_creators",) else None) for var in vars(self)}
+ # noinspection PyProtectedMember
+ data["version_info"] = data["version_info"]._asdict() # namedtuple to dictionary
+ return data
+
+ @classmethod
+ def from_exe(cls, exe, app_data=None, raise_on_error=True, ignore_cache=False, resolve_to_host=True):
+ """Given a path to an executable get the python information"""
+ # this method is not used by itself, so here and called functions can import stuff locally
+ from virtualenv.discovery.cached_py_info import from_exe
+
+ proposed = from_exe(cls, app_data, exe, raise_on_error=raise_on_error, ignore_cache=ignore_cache)
+ # noinspection PyProtectedMember
+ if isinstance(proposed, PythonInfo) and resolve_to_host:
+ try:
+ proposed = proposed._resolve_to_system(app_data, proposed)
+ except Exception as exception:
+ if raise_on_error:
+ raise exception
+ logging.info("ignore %s due cannot resolve system due to %r", proposed.original_executable, exception)
+ proposed = None
+ return proposed
+
+ @classmethod
+ def _from_json(cls, payload):
+ # the dictionary unroll here is to protect against pypy bug of interpreter crashing
+ raw = json.loads(payload)
+ return cls._from_dict({k: v for k, v in raw.items()})
+
+ @classmethod
+ def _from_dict(cls, data):
+ data["version_info"] = VersionInfo(**data["version_info"]) # restore this to a named tuple structure
+ result = cls()
+ result.__dict__ = {k: v for k, v in data.items()}
+ return result
+
+ @classmethod
+ def _resolve_to_system(cls, app_data, target):
+ start_executable = target.executable
+ prefixes = OrderedDict()
+ while target.system_executable is None:
+ prefix = target.real_prefix or target.base_prefix or target.prefix
+ if prefix in prefixes:
+ if len(prefixes) == 1:
+ # if we're linking back to ourselves accept ourselves with a WARNING
+ logging.info("%r links back to itself via prefixes", target)
+ target.system_executable = target.executable
+ break
+ for at, (p, t) in enumerate(prefixes.items(), start=1):
+ logging.error("%d: prefix=%s, info=%r", at, p, t)
+ logging.error("%d: prefix=%s, info=%r", len(prefixes) + 1, prefix, target)
+ raise RuntimeError("prefixes are causing a circle {}".format("|".join(prefixes.keys())))
+ prefixes[prefix] = target
+ target = target.discover_exe(app_data, prefix=prefix, exact=False)
+ if target.executable != target.system_executable:
+ target = cls.from_exe(target.system_executable, app_data)
+ target.executable = start_executable
+ return target
+
+ _cache_exe_discovery = {}
+
+ def discover_exe(self, app_data, prefix, exact=True):
+ key = prefix, exact
+ if key in self._cache_exe_discovery and prefix:
+ logging.debug("discover exe from cache %s - exact %s: %r", prefix, exact, self._cache_exe_discovery[key])
+ return self._cache_exe_discovery[key]
+ logging.debug("discover exe for %s in %s", self, prefix)
+ # we don't know explicitly here, do some guess work - our executable name should tell
+ possible_names = self._find_possible_exe_names()
+ possible_folders = self._find_possible_folders(prefix)
+ discovered = []
+ for folder in possible_folders:
+ for name in possible_names:
+ info = self._check_exe(app_data, folder, name, exact, discovered)
+ if info is not None:
+ self._cache_exe_discovery[key] = info
+ return info
+ if exact is False and discovered:
+ info = self._select_most_likely(discovered, self)
+ folders = os.pathsep.join(possible_folders)
+ self._cache_exe_discovery[key] = info
+ logging.debug("no exact match found, chosen most similar of %s within base folders %s", info, folders)
+ return info
+ msg = "failed to detect {} in {}".format("|".join(possible_names), os.pathsep.join(possible_folders))
+ raise RuntimeError(msg)
+
+ def _check_exe(self, app_data, folder, name, exact, discovered):
+ exe_path = os.path.join(folder, name)
+ if not os.path.exists(exe_path):
+ return None
+ info = self.from_exe(exe_path, app_data, resolve_to_host=False, raise_on_error=False)
+ if info is None: # ignore if for some reason we can't query
+ return None
+ for item in ["implementation", "architecture", "version_info"]:
+ found = getattr(info, item)
+ searched = getattr(self, item)
+ if found != searched:
+ if item == "version_info":
+ found, searched = ".".join(str(i) for i in found), ".".join(str(i) for i in searched)
+ executable = info.executable
+ logging.debug("refused interpreter %s because %s differs %s != %s", executable, item, found, searched)
+ if exact is False:
+ discovered.append(info)
+ break
+ else:
+ return info
+ return None
+
+ @staticmethod
+ def _select_most_likely(discovered, target):
+ # no exact match found, start relaxing our requirements then to facilitate system package upgrades that
+ # could cause this (when using copy strategy of the host python)
+ def sort_by(info):
+ # we need to setup some priority of traits, this is as follows:
+ # implementation, major, minor, micro, architecture, tag, serial
+ matches = [
+ info.implementation == target.implementation,
+ info.version_info.major == target.version_info.major,
+ info.version_info.minor == target.version_info.minor,
+ info.architecture == target.architecture,
+ info.version_info.micro == target.version_info.micro,
+ info.version_info.releaselevel == target.version_info.releaselevel,
+ info.version_info.serial == target.version_info.serial,
+ ]
+ priority = sum((1 << pos if match else 0) for pos, match in enumerate(reversed(matches)))
+ return priority
+
+ sorted_discovered = sorted(discovered, key=sort_by, reverse=True) # sort by priority in decreasing order
+ most_likely = sorted_discovered[0]
+ return most_likely
+
+ def _find_possible_folders(self, inside_folder):
+ candidate_folder = OrderedDict()
+ executables = OrderedDict()
+ executables[os.path.realpath(self.executable)] = None
+ executables[self.executable] = None
+ executables[os.path.realpath(self.original_executable)] = None
+ executables[self.original_executable] = None
+ for exe in executables.keys():
+ base = os.path.dirname(exe)
+ # following path pattern of the current
+ if base.startswith(self.prefix):
+ relative = base[len(self.prefix) :]
+ candidate_folder["{}{}".format(inside_folder, relative)] = None
+
+ # or at root level
+ candidate_folder[inside_folder] = None
+ return list(i for i in candidate_folder.keys() if os.path.exists(i))
+
+ def _find_possible_exe_names(self):
+ name_candidate = OrderedDict()
+ for name in self._possible_base():
+ for at in (3, 2, 1, 0):
+ version = ".".join(str(i) for i in self.version_info[:at])
+ for arch in ["-{}".format(self.architecture), ""]:
+ for ext in EXTENSIONS:
+ candidate = "{}{}{}{}".format(name, version, arch, ext)
+ name_candidate[candidate] = None
+ return list(name_candidate.keys())
+
+ def _possible_base(self):
+ possible_base = OrderedDict()
+ basename = os.path.splitext(os.path.basename(self.executable))[0].rstrip(digits)
+ possible_base[basename] = None
+ possible_base[self.implementation] = None
+ # python is always the final option as in practice is used by multiple implementation as exe name
+ if "python" in possible_base:
+ del possible_base["python"]
+ possible_base["python"] = None
+ for base in possible_base:
+ lower = base.lower()
+ yield lower
+ from virtualenv.info import fs_is_case_sensitive
+
+ if fs_is_case_sensitive():
+ if base != lower:
+ yield base
+ upper = base.upper()
+ if upper != base:
+ yield upper
+
+
+if __name__ == "__main__":
+ # dump a JSON representation of the current python
+ # noinspection PyProtectedMember
+ print(PythonInfo()._to_json())
diff --git a/third_party/python/virtualenv/virtualenv/discovery/py_spec.py b/third_party/python/virtualenv/virtualenv/discovery/py_spec.py
new file mode 100644
index 0000000000..cb63e1516b
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/py_spec.py
@@ -0,0 +1,122 @@
+"""A Python specification is an abstract requirement definition of a interpreter"""
+from __future__ import absolute_import, unicode_literals
+
+import os
+import re
+import sys
+from collections import OrderedDict
+
+from virtualenv.info import fs_is_case_sensitive
+from virtualenv.util.six import ensure_str
+
+PATTERN = re.compile(r"^(?P<impl>[a-zA-Z]+)?(?P<version>[0-9.]+)?(?:-(?P<arch>32|64))?$")
+IS_WIN = sys.platform == "win32"
+
+
+class PythonSpec(object):
+ """Contains specification about a Python Interpreter"""
+
+ def __init__(self, str_spec, implementation, major, minor, micro, architecture, path):
+ self.str_spec = str_spec
+ self.implementation = implementation
+ self.major = major
+ self.minor = minor
+ self.micro = micro
+ self.architecture = architecture
+ self.path = path
+
+ @classmethod
+ def from_string_spec(cls, string_spec):
+ impl, major, minor, micro, arch, path = None, None, None, None, None, None
+ if os.path.isabs(string_spec):
+ path = string_spec
+ else:
+ ok = False
+ match = re.match(PATTERN, string_spec)
+ if match:
+
+ def _int_or_none(val):
+ return None if val is None else int(val)
+
+ try:
+ groups = match.groupdict()
+ version = groups["version"]
+ if version is not None:
+ versions = tuple(int(i) for i in version.split(".") if i)
+ if len(versions) > 3:
+ raise ValueError
+ if len(versions) == 3:
+ major, minor, micro = versions
+ elif len(versions) == 2:
+ major, minor = versions
+ elif len(versions) == 1:
+ version_data = versions[0]
+ major = int(str(version_data)[0]) # first digit major
+ if version_data > 9:
+ minor = int(str(version_data)[1:])
+ ok = True
+ except ValueError:
+ pass
+ else:
+ impl = groups["impl"]
+ if impl == "py" or impl == "python":
+ impl = "CPython"
+ arch = _int_or_none(groups["arch"])
+
+ if not ok:
+ path = string_spec
+
+ return cls(string_spec, impl, major, minor, micro, arch, path)
+
+ def generate_names(self):
+ impls = OrderedDict()
+ if self.implementation:
+ # first consider implementation as it is
+ impls[self.implementation] = False
+ if fs_is_case_sensitive():
+ # for case sensitive file systems consider lower and upper case versions too
+ # trivia: MacBooks and all pre 2018 Windows-es were case insensitive by default
+ impls[self.implementation.lower()] = False
+ impls[self.implementation.upper()] = False
+ impls["python"] = True # finally consider python as alias, implementation must match now
+ version = self.major, self.minor, self.micro
+ try:
+ version = version[: version.index(None)]
+ except ValueError:
+ pass
+ for impl, match in impls.items():
+ for at in range(len(version), -1, -1):
+ cur_ver = version[0:at]
+ spec = "{}{}".format(impl, ".".join(str(i) for i in cur_ver))
+ yield spec, match
+
+ @property
+ def is_abs(self):
+ return self.path is not None and os.path.isabs(self.path)
+
+ def satisfies(self, spec):
+ """called when there's a candidate metadata spec to see if compatible - e.g. PEP-514 on Windows"""
+ if spec.is_abs and self.is_abs and self.path != spec.path:
+ return False
+ if spec.implementation is not None and spec.implementation.lower() != self.implementation.lower():
+ return False
+ if spec.architecture is not None and spec.architecture != self.architecture:
+ return False
+
+ for our, req in zip((self.major, self.minor, self.micro), (spec.major, spec.minor, spec.micro)):
+ if req is not None and our is not None and our != req:
+ return False
+ return True
+
+ def __unicode__(self):
+ return "{}({})".format(
+ type(self).__name__,
+ ", ".join(
+ "{}={}".format(k, getattr(self, k))
+ for k in ("implementation", "major", "minor", "micro", "architecture", "path")
+ if getattr(self, k) is not None
+ ),
+ )
+
+ def __repr__(self):
+ return ensure_str(self.__unicode__())
diff --git a/third_party/python/virtualenv/virtualenv/discovery/windows/__init__.py b/third_party/python/virtualenv/virtualenv/discovery/windows/__init__.py
new file mode 100644
index 0000000000..9063ab8df7
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/windows/__init__.py
@@ -0,0 +1,28 @@
+from __future__ import absolute_import, unicode_literals
+
+from ..py_info import PythonInfo
+from ..py_spec import PythonSpec
+from .pep514 import discover_pythons
+
+
+class Pep514PythonInfo(PythonInfo):
+ """"""
+
+
+def propose_interpreters(spec, cache_dir):
+ # see if PEP-514 entries are good
+
+ # start with higher python versions in an effort to use the latest version available
+ existing = list(discover_pythons())
+ existing.sort(key=lambda i: tuple(-1 if j is None else j for j in i[1:4]), reverse=True)
+
+ for name, major, minor, arch, exe, _ in existing:
+ # pre-filter
+ if name in ("PythonCore", "ContinuumAnalytics"):
+ name = "CPython"
+ registry_spec = PythonSpec(None, name, major, minor, None, arch, exe)
+ if registry_spec.satisfies(spec):
+ interpreter = Pep514PythonInfo.from_exe(exe, cache_dir, raise_on_error=False)
+ if interpreter is not None:
+ if interpreter.satisfies(spec, impl_must_match=True):
+ yield interpreter
diff --git a/third_party/python/virtualenv/virtualenv/discovery/windows/pep514.py b/third_party/python/virtualenv/virtualenv/discovery/windows/pep514.py
new file mode 100644
index 0000000000..048436a600
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/discovery/windows/pep514.py
@@ -0,0 +1,161 @@
+"""Implement https://www.python.org/dev/peps/pep-0514/ to discover interpreters - Windows only"""
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+import re
+from logging import basicConfig, getLogger
+
+import six
+
+if six.PY3:
+ import winreg
+else:
+ # noinspection PyUnresolvedReferences
+ import _winreg as winreg
+
+LOGGER = getLogger(__name__)
+
+
+def enum_keys(key):
+ at = 0
+ while True:
+ try:
+ yield winreg.EnumKey(key, at)
+ except OSError:
+ break
+ at += 1
+
+
+def get_value(key, value_name):
+ try:
+ return winreg.QueryValueEx(key, value_name)[0]
+ except OSError:
+ return None
+
+
+def discover_pythons():
+ for hive, hive_name, key, flags, default_arch in [
+ (winreg.HKEY_CURRENT_USER, "HKEY_CURRENT_USER", r"Software\Python", 0, 64),
+ (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_64KEY, 64),
+ (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_32KEY, 32),
+ ]:
+ for spec in process_set(hive, hive_name, key, flags, default_arch):
+ yield spec
+
+
+def process_set(hive, hive_name, key, flags, default_arch):
+ try:
+ with winreg.OpenKeyEx(hive, key, 0, winreg.KEY_READ | flags) as root_key:
+ for company in enum_keys(root_key):
+ if company == "PyLauncher": # reserved
+ continue
+ for spec in process_company(hive_name, company, root_key, default_arch):
+ yield spec
+ except OSError:
+ pass
+
+
+def process_company(hive_name, company, root_key, default_arch):
+ with winreg.OpenKeyEx(root_key, company) as company_key:
+ for tag in enum_keys(company_key):
+ spec = process_tag(hive_name, company, company_key, tag, default_arch)
+ if spec is not None:
+ yield spec
+
+
+def process_tag(hive_name, company, company_key, tag, default_arch):
+ with winreg.OpenKeyEx(company_key, tag) as tag_key:
+ version = load_version_data(hive_name, company, tag, tag_key)
+ if version is not None: # if failed to get version bail
+ major, minor, _ = version
+ arch = load_arch_data(hive_name, company, tag, tag_key, default_arch)
+ if arch is not None:
+ exe_data = load_exe(hive_name, company, company_key, tag)
+ if exe_data is not None:
+ exe, args = exe_data
+ return company, major, minor, arch, exe, args
+
+
+def load_exe(hive_name, company, company_key, tag):
+ key_path = "{}/{}/{}".format(hive_name, company, tag)
+ try:
+ with winreg.OpenKeyEx(company_key, r"{}\InstallPath".format(tag)) as ip_key:
+ with ip_key:
+ exe = get_value(ip_key, "ExecutablePath")
+ if exe is None:
+ ip = get_value(ip_key, None)
+ if ip is None:
+ msg(key_path, "no ExecutablePath or default for it")
+
+ else:
+ exe = os.path.join(ip, str("python.exe"))
+ if exe is not None and os.path.exists(exe):
+ args = get_value(ip_key, "ExecutableArguments")
+ return exe, args
+ else:
+ msg(key_path, "could not load exe with value {}".format(exe))
+ except OSError:
+ msg("{}/{}".format(key_path, "InstallPath"), "missing")
+ return None
+
+
+def load_arch_data(hive_name, company, tag, tag_key, default_arch):
+ arch_str = get_value(tag_key, "SysArchitecture")
+ if arch_str is not None:
+ key_path = "{}/{}/{}/SysArchitecture".format(hive_name, company, tag)
+ try:
+ return parse_arch(arch_str)
+ except ValueError as sys_arch:
+ msg(key_path, sys_arch)
+ return default_arch
+
+
+def parse_arch(arch_str):
+ if isinstance(arch_str, six.string_types):
+ match = re.match(r"^(\d+)bit$", arch_str)
+ if match:
+ return int(next(iter(match.groups())))
+ error = "invalid format {}".format(arch_str)
+ else:
+ error = "arch is not string: {}".format(repr(arch_str))
+ raise ValueError(error)
+
+
+def load_version_data(hive_name, company, tag, tag_key):
+ for candidate, key_path in [
+ (get_value(tag_key, "SysVersion"), "{}/{}/{}/SysVersion".format(hive_name, company, tag)),
+ (tag, "{}/{}/{}".format(hive_name, company, tag)),
+ ]:
+ if candidate is not None:
+ try:
+ return parse_version(candidate)
+ except ValueError as sys_version:
+ msg(key_path, sys_version)
+ return None
+
+
+def parse_version(version_str):
+ if isinstance(version_str, six.string_types):
+ match = re.match(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?$", version_str)
+ if match:
+ return tuple(int(i) if i is not None else None for i in match.groups())
+ error = "invalid format {}".format(version_str)
+ else:
+ error = "version is not string: {}".format(repr(version_str))
+ raise ValueError(error)
+
+
+def msg(path, what):
+ LOGGER.warning("PEP-514 violation in Windows Registry at {} error: {}".format(path, what))
+
+
+def _run():
+ basicConfig()
+ interpreters = []
+ for spec in discover_pythons():
+ interpreters.append(repr(spec))
+ print("\n".join(sorted(interpreters)))
+
+
+if __name__ == "__main__":
+ _run()
diff --git a/third_party/python/virtualenv/virtualenv/info.py b/third_party/python/virtualenv/virtualenv/info.py
new file mode 100644
index 0000000000..afe4097736
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/info.py
@@ -0,0 +1,65 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import platform
+import sys
+import tempfile
+
+IMPLEMENTATION = platform.python_implementation()
+IS_PYPY = IMPLEMENTATION == "PyPy"
+IS_CPYTHON = IMPLEMENTATION == "CPython"
+PY3 = sys.version_info[0] == 3
+PY2 = sys.version_info[0] == 2
+IS_WIN = sys.platform == "win32"
+ROOT = os.path.realpath(os.path.join(os.path.abspath(__file__), os.path.pardir, os.path.pardir))
+IS_ZIPAPP = os.path.isfile(ROOT)
+WIN_CPYTHON_2 = IS_CPYTHON and IS_WIN and PY2
+
+_CAN_SYMLINK = _FS_CASE_SENSITIVE = _CFG_DIR = _DATA_DIR = None
+
+
+def fs_is_case_sensitive():
+ global _FS_CASE_SENSITIVE
+
+ if _FS_CASE_SENSITIVE is None:
+ with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
+ _FS_CASE_SENSITIVE = not os.path.exists(tmp_file.name.lower())
+ logging.debug("filesystem is %scase-sensitive", "" if _FS_CASE_SENSITIVE else "not ")
+ return _FS_CASE_SENSITIVE
+
+
+def fs_supports_symlink():
+ global _CAN_SYMLINK
+
+ if _CAN_SYMLINK is None:
+ can = False
+ if hasattr(os, "symlink"):
+ if IS_WIN:
+ with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
+ temp_dir = os.path.dirname(tmp_file.name)
+ dest = os.path.join(temp_dir, "{}-{}".format(tmp_file.name, "b"))
+ try:
+ os.symlink(tmp_file.name, dest)
+ can = True
+ except (OSError, NotImplementedError):
+ pass
+ logging.debug("symlink on filesystem does%s work", "" if can else " not")
+ else:
+ can = True
+ _CAN_SYMLINK = can
+ return _CAN_SYMLINK
+
+
+__all__ = (
+ "IS_PYPY",
+ "IS_CPYTHON",
+ "PY3",
+ "PY2",
+ "IS_WIN",
+ "fs_is_case_sensitive",
+ "fs_supports_symlink",
+ "ROOT",
+ "IS_ZIPAPP",
+ "WIN_CPYTHON_2",
+)
diff --git a/third_party/python/virtualenv/virtualenv/report.py b/third_party/python/virtualenv/virtualenv/report.py
new file mode 100644
index 0000000000..665b293cb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/report.py
@@ -0,0 +1,57 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import sys
+
+from virtualenv.util.six import ensure_str
+
+LEVELS = {
+ 0: logging.CRITICAL,
+ 1: logging.ERROR,
+ 2: logging.WARNING,
+ 3: logging.INFO,
+ 4: logging.DEBUG,
+ 5: logging.NOTSET,
+}
+
+MAX_LEVEL = max(LEVELS.keys())
+LOGGER = logging.getLogger()
+
+
+def setup_report(verbosity, show_pid=False):
+ _clean_handlers(LOGGER)
+ if verbosity > MAX_LEVEL:
+ verbosity = MAX_LEVEL # pragma: no cover
+ level = LEVELS[verbosity]
+ msg_format = "%(message)s"
+ filelock_logger = logging.getLogger("filelock")
+ if level <= logging.DEBUG:
+ locate = "module"
+ msg_format = "%(relativeCreated)d {} [%(levelname)s %({})s:%(lineno)d]".format(msg_format, locate)
+ filelock_logger.setLevel(level)
+ else:
+ filelock_logger.setLevel(logging.WARN)
+ if show_pid:
+ msg_format = "[%(process)d] " + msg_format
+ formatter = logging.Formatter(ensure_str(msg_format))
+ stream_handler = logging.StreamHandler(stream=sys.stdout)
+ stream_handler.setLevel(level)
+ LOGGER.setLevel(logging.NOTSET)
+ stream_handler.setFormatter(formatter)
+ LOGGER.addHandler(stream_handler)
+ level_name = logging.getLevelName(level)
+ logging.debug("setup logging to %s", level_name)
+ logging.getLogger("distlib").setLevel(logging.ERROR)
+ return verbosity
+
+
+def _clean_handlers(log):
+ for log_handler in list(log.handlers): # remove handlers of libraries
+ log.removeHandler(log_handler)
+
+
+__all__ = (
+ "LEVELS",
+ "MAX_LEVEL",
+ "setup_report",
+)
diff --git a/third_party/python/virtualenv/virtualenv/run/__init__.py b/third_party/python/virtualenv/virtualenv/run/__init__.py
new file mode 100644
index 0000000000..66083df82b
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/__init__.py
@@ -0,0 +1,151 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+from functools import partial
+
+from ..app_data import make_app_data
+from ..config.cli.parser import VirtualEnvConfigParser
+from ..report import LEVELS, setup_report
+from ..run.session import Session
+from ..seed.wheels.periodic_update import manual_upgrade
+from ..version import __version__
+from .plugin.activators import ActivationSelector
+from .plugin.creators import CreatorSelector
+from .plugin.discovery import get_discover
+from .plugin.seeders import SeederSelector
+
+
+def cli_run(args, options=None, setup_logging=True):
+ """
+ Create a virtual environment given some command line interface arguments.
+
+ :param args: the command line arguments
+ :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
+ :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
+ :return: the session object of the creation (its structure for now is experimental and might change on short notice)
+ """
+ of_session = session_via_cli(args, options, setup_logging)
+ with of_session:
+ of_session.run()
+ return of_session
+
+
+def session_via_cli(args, options=None, setup_logging=True):
+ """
+ Create a virtualenv session (same as cli_run, but this does not perform the creation). Use this if you just want to
+ query what the virtual environment would look like, but not actually create it.
+
+ :param args: the command line arguments
+ :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
+ :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
+ :return: the session object of the creation (its structure for now is experimental and might change on short notice)
+ """
+ parser, elements = build_parser(args, options, setup_logging)
+ options = parser.parse_args(args)
+ creator, seeder, activators = tuple(e.create(options) for e in elements) # create types
+ of_session = Session(options.verbosity, options.app_data, parser._interpreter, creator, seeder, activators) # noqa
+ return of_session
+
+
+def build_parser(args=None, options=None, setup_logging=True):
+ parser = VirtualEnvConfigParser(options)
+ add_version_flag(parser)
+ parser.add_argument(
+ "--with-traceback",
+ dest="with_traceback",
+ action="store_true",
+ default=False,
+ help="on failure also display the stacktrace internals of virtualenv",
+ )
+ _do_report_setup(parser, args, setup_logging)
+ options = load_app_data(args, parser, options)
+ handle_extra_commands(options)
+
+ discover = get_discover(parser, args)
+ parser._interpreter = interpreter = discover.interpreter
+ if interpreter is None:
+ raise RuntimeError("failed to find interpreter for {}".format(discover))
+ elements = [
+ CreatorSelector(interpreter, parser),
+ SeederSelector(interpreter, parser),
+ ActivationSelector(interpreter, parser),
+ ]
+ options, _ = parser.parse_known_args(args)
+ for element in elements:
+ element.handle_selected_arg_parse(options)
+ parser.enable_help()
+ return parser, elements
+
+
+def build_parser_only(args=None):
+ """Used to provide a parser for the doc generation"""
+ return build_parser(args)[0]
+
+
+def handle_extra_commands(options):
+ if options.upgrade_embed_wheels:
+ result = manual_upgrade(options.app_data)
+ raise SystemExit(result)
+
+
+def load_app_data(args, parser, options):
+ parser.add_argument(
+ "--read-only-app-data",
+ action="store_true",
+ help="use app data folder in read-only mode (write operations will fail with error)",
+ )
+ options, _ = parser.parse_known_args(args, namespace=options)
+
+ # here we need a write-able application data (e.g. the zipapp might need this for discovery cache)
+ parser.add_argument(
+ "--app-data",
+ help="a data folder used as cache by the virtualenv",
+ type=partial(make_app_data, read_only=options.read_only_app_data),
+ default=make_app_data(None, read_only=options.read_only_app_data),
+ )
+ parser.add_argument(
+ "--reset-app-data",
+ action="store_true",
+ help="start with empty app data folder",
+ )
+ parser.add_argument(
+ "--upgrade-embed-wheels",
+ action="store_true",
+ help="trigger a manual update of the embedded wheels",
+ )
+ options, _ = parser.parse_known_args(args, namespace=options)
+ if options.reset_app_data:
+ options.app_data.reset()
+ return options
+
+
+def add_version_flag(parser):
+ import virtualenv
+
+ parser.add_argument(
+ "--version",
+ action="version",
+ version="%(prog)s {} from {}".format(__version__, virtualenv.__file__),
+ help="display the version of the virtualenv package and its location, then exit",
+ )
+
+
+def _do_report_setup(parser, args, setup_logging):
+ level_map = ", ".join("{}={}".format(logging.getLevelName(l), c) for c, l in sorted(list(LEVELS.items())))
+ msg = "verbosity = verbose - quiet, default {}, mapping => {}"
+ verbosity_group = parser.add_argument_group(
+ title="verbosity",
+ description=msg.format(logging.getLevelName(LEVELS[3]), level_map),
+ )
+ verbosity = verbosity_group.add_mutually_exclusive_group()
+ verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2)
+ verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0)
+ option, _ = parser.parse_known_args(args)
+ if setup_logging:
+ setup_report(option.verbosity)
+
+
+__all__ = (
+ "cli_run",
+ "session_via_cli",
+)
diff --git a/third_party/python/virtualenv/virtualenv/run/plugin/__init__.py b/third_party/python/virtualenv/virtualenv/run/plugin/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/plugin/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/run/plugin/activators.py b/third_party/python/virtualenv/virtualenv/run/plugin/activators.py
new file mode 100644
index 0000000000..dea28277f1
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/plugin/activators.py
@@ -0,0 +1,53 @@
+from __future__ import absolute_import, unicode_literals
+
+from argparse import ArgumentTypeError
+from collections import OrderedDict
+
+from .base import ComponentBuilder
+
+
+class ActivationSelector(ComponentBuilder):
+ def __init__(self, interpreter, parser):
+ self.default = None
+ possible = OrderedDict(
+ (k, v) for k, v in self.options("virtualenv.activate").items() if v.supports(interpreter)
+ )
+ super(ActivationSelector, self).__init__(interpreter, parser, "activators", possible)
+ self.parser.description = "options for activation scripts"
+ self.active = None
+
+ def add_selector_arg_parse(self, name, choices):
+ self.default = ",".join(choices)
+ self.parser.add_argument(
+ "--{}".format(name),
+ default=self.default,
+ metavar="comma_sep_list",
+ required=False,
+ help="activators to generate - default is all supported",
+ type=self._extract_activators,
+ )
+
+ def _extract_activators(self, entered_str):
+ elements = [e.strip() for e in entered_str.split(",") if e.strip()]
+ missing = [e for e in elements if e not in self.possible]
+ if missing:
+ raise ArgumentTypeError("the following activators are not available {}".format(",".join(missing)))
+ return elements
+
+ def handle_selected_arg_parse(self, options):
+ selected_activators = (
+ self._extract_activators(self.default) if options.activators is self.default else options.activators
+ )
+ self.active = {k: v for k, v in self.possible.items() if k in selected_activators}
+ self.parser.add_argument(
+ "--prompt",
+ dest="prompt",
+ metavar="prompt",
+ help="provides an alternative prompt prefix for this environment",
+ default=None,
+ )
+ for activator in self.active.values():
+ activator.add_parser_arguments(self.parser, self.interpreter)
+
+ def create(self, options):
+ return [activator_class(options) for activator_class in self.active.values()]
diff --git a/third_party/python/virtualenv/virtualenv/run/plugin/base.py b/third_party/python/virtualenv/virtualenv/run/plugin/base.py
new file mode 100644
index 0000000000..ed10fe0e27
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/plugin/base.py
@@ -0,0 +1,58 @@
+from __future__ import absolute_import, unicode_literals
+
+import sys
+from collections import OrderedDict
+
+if sys.version_info >= (3, 8):
+ from importlib.metadata import entry_points
+else:
+ from importlib_metadata import entry_points
+
+
+class PluginLoader(object):
+ _OPTIONS = None
+ _ENTRY_POINTS = None
+
+ @classmethod
+ def entry_points_for(cls, key):
+ return OrderedDict((e.name, e.load()) for e in cls.entry_points().get(key, {}))
+
+ @staticmethod
+ def entry_points():
+ if PluginLoader._ENTRY_POINTS is None:
+ PluginLoader._ENTRY_POINTS = entry_points()
+ return PluginLoader._ENTRY_POINTS
+
+
+class ComponentBuilder(PluginLoader):
+ def __init__(self, interpreter, parser, name, possible):
+ self.interpreter = interpreter
+ self.name = name
+ self._impl_class = None
+ self.possible = possible
+ self.parser = parser.add_argument_group(title=name)
+ self.add_selector_arg_parse(name, list(self.possible))
+
+ @classmethod
+ def options(cls, key):
+ if cls._OPTIONS is None:
+ cls._OPTIONS = cls.entry_points_for(key)
+ return cls._OPTIONS
+
+ def add_selector_arg_parse(self, name, choices):
+ raise NotImplementedError
+
+ def handle_selected_arg_parse(self, options):
+ selected = getattr(options, self.name)
+ if selected not in self.possible:
+ raise RuntimeError("No implementation for {}".format(self.interpreter))
+ self._impl_class = self.possible[selected]
+ self.populate_selected_argparse(selected, options.app_data)
+ return selected
+
+ def populate_selected_argparse(self, selected, app_data):
+ self.parser.description = "options for {} {}".format(self.name, selected)
+ self._impl_class.add_parser_arguments(self.parser, self.interpreter, app_data)
+
+ def create(self, options):
+ return self._impl_class(options, self.interpreter)
diff --git a/third_party/python/virtualenv/virtualenv/run/plugin/creators.py b/third_party/python/virtualenv/virtualenv/run/plugin/creators.py
new file mode 100644
index 0000000000..ef4177a595
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/plugin/creators.py
@@ -0,0 +1,77 @@
+from __future__ import absolute_import, unicode_literals
+
+from collections import OrderedDict, defaultdict, namedtuple
+
+from virtualenv.create.describe import Describe
+from virtualenv.create.via_global_ref.builtin.builtin_way import VirtualenvBuiltin
+
+from .base import ComponentBuilder
+
+CreatorInfo = namedtuple("CreatorInfo", ["key_to_class", "key_to_meta", "describe", "builtin_key"])
+
+
+class CreatorSelector(ComponentBuilder):
+ def __init__(self, interpreter, parser):
+ creators, self.key_to_meta, self.describe, self.builtin_key = self.for_interpreter(interpreter)
+ super(CreatorSelector, self).__init__(interpreter, parser, "creator", creators)
+
+ @classmethod
+ def for_interpreter(cls, interpreter):
+ key_to_class, key_to_meta, builtin_key, describe = OrderedDict(), {}, None, None
+ errors = defaultdict(list)
+ for key, creator_class in cls.options("virtualenv.create").items():
+ if key == "builtin":
+ raise RuntimeError("builtin creator is a reserved name")
+ meta = creator_class.can_create(interpreter)
+ if meta:
+ if meta.error:
+ errors[meta.error].append(creator_class)
+ else:
+ if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin):
+ builtin_key = key
+ key_to_class["builtin"] = creator_class
+ key_to_meta["builtin"] = meta
+ key_to_class[key] = creator_class
+ key_to_meta[key] = meta
+ if describe is None and issubclass(creator_class, Describe) and creator_class.can_describe(interpreter):
+ describe = creator_class
+ if not key_to_meta:
+ if errors:
+ rows = ["{} for creators {}".format(k, ", ".join(i.__name__ for i in v)) for k, v in errors.items()]
+ raise RuntimeError("\n".join(rows))
+ else:
+ raise RuntimeError("No virtualenv implementation for {}".format(interpreter))
+ return CreatorInfo(
+ key_to_class=key_to_class,
+ key_to_meta=key_to_meta,
+ describe=describe,
+ builtin_key=builtin_key,
+ )
+
+ def add_selector_arg_parse(self, name, choices):
+ # prefer the built-in venv if present, otherwise fallback to first defined type
+ choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1)
+ default_value = self._get_default(choices)
+ self.parser.add_argument(
+ "--{}".format(name),
+ choices=choices,
+ default=default_value,
+ required=False,
+ help="create environment via{}".format(
+ "" if self.builtin_key is None else " (builtin = {})".format(self.builtin_key),
+ ),
+ )
+
+ @staticmethod
+ def _get_default(choices):
+ return next(iter(choices))
+
+ def populate_selected_argparse(self, selected, app_data):
+ self.parser.description = "options for {} {}".format(self.name, selected)
+ self._impl_class.add_parser_arguments(self.parser, self.interpreter, self.key_to_meta[selected], app_data)
+
+ def create(self, options):
+ options.meta = self.key_to_meta[getattr(options, self.name)]
+ if not issubclass(self._impl_class, Describe):
+ options.describe = self.describe(options, self.interpreter)
+ return super(CreatorSelector, self).create(options)
diff --git a/third_party/python/virtualenv/virtualenv/run/plugin/discovery.py b/third_party/python/virtualenv/virtualenv/run/plugin/discovery.py
new file mode 100644
index 0000000000..3b6fc60d83
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/plugin/discovery.py
@@ -0,0 +1,32 @@
+from __future__ import absolute_import, unicode_literals
+
+from .base import PluginLoader
+
+
+class Discovery(PluginLoader):
+ """"""
+
+
+def get_discover(parser, args):
+ discover_types = Discovery.entry_points_for("virtualenv.discovery")
+ discovery_parser = parser.add_argument_group(
+ title="discovery",
+ description="discover and provide a target interpreter",
+ )
+ discovery_parser.add_argument(
+ "--discovery",
+ choices=_get_default_discovery(discover_types),
+ default=next(i for i in discover_types.keys()),
+ required=False,
+ help="interpreter discovery method",
+ )
+ options, _ = parser.parse_known_args(args)
+ discover_class = discover_types[options.discovery]
+ discover_class.add_parser_arguments(discovery_parser)
+ options, _ = parser.parse_known_args(args, namespace=options)
+ discover = discover_class(options)
+ return discover
+
+
+def _get_default_discovery(discover_types):
+ return list(discover_types.keys())
diff --git a/third_party/python/virtualenv/virtualenv/run/plugin/seeders.py b/third_party/python/virtualenv/virtualenv/run/plugin/seeders.py
new file mode 100644
index 0000000000..d182c6f731
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/plugin/seeders.py
@@ -0,0 +1,35 @@
+from __future__ import absolute_import, unicode_literals
+
+from .base import ComponentBuilder
+
+
+class SeederSelector(ComponentBuilder):
+ def __init__(self, interpreter, parser):
+ possible = self.options("virtualenv.seed")
+ super(SeederSelector, self).__init__(interpreter, parser, "seeder", possible)
+
+ def add_selector_arg_parse(self, name, choices):
+ self.parser.add_argument(
+ "--{}".format(name),
+ choices=choices,
+ default=self._get_default(),
+ required=False,
+ help="seed packages install method",
+ )
+ self.parser.add_argument(
+ "--no-seed",
+ "--without-pip",
+ help="do not install seed packages",
+ action="store_true",
+ dest="no_seed",
+ )
+
+ @staticmethod
+ def _get_default():
+ return "app-data"
+
+ def handle_selected_arg_parse(self, options):
+ return super(SeederSelector, self).handle_selected_arg_parse(options)
+
+ def create(self, options):
+ return self._impl_class(options)
diff --git a/third_party/python/virtualenv/virtualenv/run/session.py b/third_party/python/virtualenv/virtualenv/run/session.py
new file mode 100644
index 0000000000..24836d2855
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/run/session.py
@@ -0,0 +1,91 @@
+from __future__ import absolute_import, unicode_literals
+
+import json
+import logging
+
+from virtualenv.util.six import ensure_text
+
+
+class Session(object):
+ """Represents a virtual environment creation session"""
+
+ def __init__(self, verbosity, app_data, interpreter, creator, seeder, activators):
+ self._verbosity = verbosity
+ self._app_data = app_data
+ self._interpreter = interpreter
+ self._creator = creator
+ self._seeder = seeder
+ self._activators = activators
+
+ @property
+ def verbosity(self):
+ """The verbosity of the run"""
+ return self._verbosity
+
+ @property
+ def interpreter(self):
+ """Create a virtual environment based on this reference interpreter"""
+ return self._interpreter
+
+ @property
+ def creator(self):
+ """The creator used to build the virtual environment (must be compatible with the interpreter)"""
+ return self._creator
+
+ @property
+ def seeder(self):
+ """The mechanism used to provide the seed packages (pip, setuptools, wheel)"""
+ return self._seeder
+
+ @property
+ def activators(self):
+ """Activators used to generate activations scripts"""
+ return self._activators
+
+ def run(self):
+ self._create()
+ self._seed()
+ self._activate()
+ self.creator.pyenv_cfg.write()
+
+ def _create(self):
+ logging.info("create virtual environment via %s", ensure_text(str(self.creator)))
+ self.creator.run()
+ logging.debug(_DEBUG_MARKER)
+ logging.debug("%s", _Debug(self.creator))
+
+ def _seed(self):
+ if self.seeder is not None and self.seeder.enabled:
+ logging.info("add seed packages via %s", self.seeder)
+ self.seeder.run(self.creator)
+
+ def _activate(self):
+ if self.activators:
+ logging.info(
+ "add activators for %s",
+ ", ".join(type(i).__name__.replace("Activator", "") for i in self.activators),
+ )
+ for activator in self.activators:
+ activator.generate(self.creator)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._app_data.close()
+
+
+_DEBUG_MARKER = "=" * 30 + " target debug " + "=" * 30
+
+
+class _Debug(object):
+ """lazily populate debug"""
+
+ def __init__(self, creator):
+ self.creator = creator
+
+ def __unicode__(self):
+ return ensure_text(repr(self))
+
+ def __repr__(self):
+ return json.dumps(self.creator.debug, indent=2)
diff --git a/third_party/python/virtualenv/virtualenv/seed/__init__.py b/third_party/python/virtualenv/virtualenv/seed/__init__.py
new file mode 100644
index 0000000000..01e6d4f49d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/__init__.py
@@ -0,0 +1 @@
+from __future__ import absolute_import, unicode_literals
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/__init__.py b/third_party/python/virtualenv/virtualenv/seed/embed/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/base_embed.py b/third_party/python/virtualenv/virtualenv/seed/embed/base_embed.py
new file mode 100644
index 0000000000..c794e834de
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/base_embed.py
@@ -0,0 +1,118 @@
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta
+
+from six import add_metaclass
+
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_str, ensure_text
+
+from ..seeder import Seeder
+from ..wheels import Version
+
+PERIODIC_UPDATE_ON_BY_DEFAULT = True
+
+
+@add_metaclass(ABCMeta)
+class BaseEmbed(Seeder):
+ def __init__(self, options):
+ super(BaseEmbed, self).__init__(options, enabled=options.no_seed is False)
+
+ self.download = options.download
+ self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()]
+
+ self.pip_version = options.pip
+ self.setuptools_version = options.setuptools
+ self.wheel_version = options.wheel
+
+ self.no_pip = options.no_pip
+ self.no_setuptools = options.no_setuptools
+ self.no_wheel = options.no_wheel
+ self.app_data = options.app_data
+ self.periodic_update = not options.no_periodic_update
+
+ if not self.distribution_to_versions():
+ self.enabled = False
+
+ @classmethod
+ def distributions(cls):
+ return {
+ "pip": Version.bundle,
+ "setuptools": Version.bundle,
+ "wheel": Version.bundle,
+ }
+
+ def distribution_to_versions(self):
+ return {
+ distribution: getattr(self, "{}_version".format(distribution))
+ for distribution in self.distributions()
+ if getattr(self, "no_{}".format(distribution)) is False
+ }
+
+ @classmethod
+ def add_parser_arguments(cls, parser, interpreter, app_data):
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument(
+ "--no-download",
+ "--never-download",
+ dest="download",
+ action="store_false",
+ help="pass to disable download of the latest {} from PyPI".format("/".join(cls.distributions())),
+ default=True,
+ )
+ group.add_argument(
+ "--download",
+ dest="download",
+ action="store_true",
+ help="pass to enable download of the latest {} from PyPI".format("/".join(cls.distributions())),
+ default=False,
+ )
+ parser.add_argument(
+ "--extra-search-dir",
+ metavar="d",
+ type=Path,
+ nargs="+",
+ help="a path containing wheels to extend the internal wheel list (can be set 1+ times)",
+ default=[],
+ )
+ for distribution, default in cls.distributions().items():
+ parser.add_argument(
+ "--{}".format(distribution),
+ dest=distribution,
+ metavar="version",
+ help="version of {} to install as seed: embed, bundle or exact version".format(distribution),
+ default=default,
+ )
+ for distribution in cls.distributions():
+ parser.add_argument(
+ "--no-{}".format(distribution),
+ dest="no_{}".format(distribution),
+ action="store_true",
+ help="do not install {}".format(distribution),
+ default=False,
+ )
+ parser.add_argument(
+ "--no-periodic-update",
+ dest="no_periodic_update",
+ action="store_true",
+ help="disable the periodic (once every 14 days) update of the embedded wheels",
+ default=not PERIODIC_UPDATE_ON_BY_DEFAULT,
+ )
+
+ def __unicode__(self):
+ result = self.__class__.__name__
+ result += "("
+ if self.extra_search_dir:
+ result += "extra_search_dir={},".format(", ".join(ensure_text(str(i)) for i in self.extra_search_dir))
+ result += "download={},".format(self.download)
+ for distribution in self.distributions():
+ if getattr(self, "no_{}".format(distribution)):
+ continue
+ result += " {}{},".format(
+ distribution,
+ "={}".format(getattr(self, "{}_version".format(distribution), None) or "latest"),
+ )
+ return result[:-1] + ")"
+
+ def __repr__(self):
+ return ensure_str(self.__unicode__())
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/pip_invoke.py b/third_party/python/virtualenv/virtualenv/seed/embed/pip_invoke.py
new file mode 100644
index 0000000000..372e140dc4
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/pip_invoke.py
@@ -0,0 +1,56 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+from contextlib import contextmanager
+
+from virtualenv.discovery.cached_py_info import LogCmd
+from virtualenv.seed.embed.base_embed import BaseEmbed
+from virtualenv.util.subprocess import Popen
+
+from ..wheels import Version, get_wheel, pip_wheel_env_run
+
+
+class PipInvoke(BaseEmbed):
+ def __init__(self, options):
+ super(PipInvoke, self).__init__(options)
+
+ def run(self, creator):
+ if not self.enabled:
+ return
+ for_py_version = creator.interpreter.version_release_str
+ with self.get_pip_install_cmd(creator.exe, for_py_version) as cmd:
+ env = pip_wheel_env_run(self.extra_search_dir, self.app_data)
+ self._execute(cmd, env)
+
+ @staticmethod
+ def _execute(cmd, env):
+ logging.debug("pip seed by running: %s", LogCmd(cmd, env))
+ process = Popen(cmd, env=env)
+ process.communicate()
+ if process.returncode != 0:
+ raise RuntimeError("failed seed with code {}".format(process.returncode))
+ return process
+
+ @contextmanager
+ def get_pip_install_cmd(self, exe, for_py_version):
+ cmd = [str(exe), "-m", "pip", "-q", "install", "--only-binary", ":all:", "--disable-pip-version-check"]
+ if not self.download:
+ cmd.append("--no-index")
+ folders = set()
+ for dist, version in self.distribution_to_versions().items():
+ wheel = get_wheel(
+ distribution=dist,
+ version=version,
+ for_py_version=for_py_version,
+ search_dirs=self.extra_search_dir,
+ download=False,
+ app_data=self.app_data,
+ do_periodic_update=self.periodic_update,
+ )
+ if wheel is None:
+ raise RuntimeError("could not get wheel for distribution {}".format(dist))
+ folders.add(str(wheel.path.parent))
+ cmd.append(Version.as_pip_req(dist, wheel.version))
+ for folder in sorted(folders):
+ cmd.extend(["--find-links", str(folder)])
+ yield cmd
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/__init__.py b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/__init__.py b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/__init__.py
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/base.py b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/base.py
new file mode 100644
index 0000000000..a1d946d509
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/base.py
@@ -0,0 +1,158 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import re
+import zipfile
+from abc import ABCMeta, abstractmethod
+from tempfile import mkdtemp
+
+from distlib.scripts import ScriptMaker, enquote_executable
+from six import PY3, add_metaclass
+
+from virtualenv.util import ConfigParser
+from virtualenv.util.path import Path, safe_delete
+from virtualenv.util.six import ensure_text
+
+
+@add_metaclass(ABCMeta)
+class PipInstall(object):
+ def __init__(self, wheel, creator, image_folder):
+ self._wheel = wheel
+ self._creator = creator
+ self._image_dir = image_folder
+ self._extracted = False
+ self.__dist_info = None
+ self._console_entry_points = None
+
+ @abstractmethod
+ def _sync(self, src, dst):
+ raise NotImplementedError
+
+ def install(self, version_info):
+ self._extracted = True
+ # sync image
+ for filename in self._image_dir.iterdir():
+ into = self._creator.purelib / filename.name
+ if into.exists():
+ if into.is_dir() and not into.is_symlink():
+ safe_delete(into)
+ else:
+ into.unlink()
+ self._sync(filename, into)
+ # generate console executables
+ consoles = set()
+ script_dir = self._creator.script_dir
+ for name, module in self._console_scripts.items():
+ consoles.update(self._create_console_entry_point(name, module, script_dir, version_info))
+ logging.debug("generated console scripts %s", " ".join(i.name for i in consoles))
+
+ def build_image(self):
+ # 1. first extract the wheel
+ logging.debug("build install image for %s to %s", self._wheel.name, self._image_dir)
+ with zipfile.ZipFile(str(self._wheel)) as zip_ref:
+ zip_ref.extractall(str(self._image_dir))
+ self._extracted = True
+ # 2. now add additional files not present in the distribution
+ new_files = self._generate_new_files()
+ # 3. finally fix the records file
+ self._fix_records(new_files)
+
+ def _records_text(self, files):
+ record_data = "\n".join(
+ "{},,".format(os.path.relpath(ensure_text(str(rec)), ensure_text(str(self._image_dir)))) for rec in files
+ )
+ return record_data
+
+ def _generate_new_files(self):
+ new_files = set()
+ installer = self._dist_info / "INSTALLER"
+ installer.write_text("pip\n")
+ new_files.add(installer)
+ # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226
+ marker = self._image_dir / "{}.virtualenv".format(self._dist_info.stem)
+ marker.write_text("")
+ new_files.add(marker)
+ folder = mkdtemp()
+ try:
+ to_folder = Path(folder)
+ rel = os.path.relpath(ensure_text(str(self._creator.script_dir)), ensure_text(str(self._creator.purelib)))
+ version_info = self._creator.interpreter.version_info
+ for name, module in self._console_scripts.items():
+ new_files.update(
+ Path(os.path.normpath(ensure_text(str(self._image_dir / rel / i.name))))
+ for i in self._create_console_entry_point(name, module, to_folder, version_info)
+ )
+ finally:
+ safe_delete(folder)
+ return new_files
+
+ @property
+ def _dist_info(self):
+ if self._extracted is False:
+ return None # pragma: no cover
+ if self.__dist_info is None:
+ files = []
+ for filename in self._image_dir.iterdir():
+ files.append(filename.name)
+ if filename.suffix == ".dist-info":
+ self.__dist_info = filename
+ break
+ else:
+ msg = "no .dist-info at {}, has {}".format(self._image_dir, ", ".join(files)) # pragma: no cover
+ raise RuntimeError(msg) # pragma: no cover
+ return self.__dist_info
+
+ @abstractmethod
+ def _fix_records(self, extra_record_data):
+ raise NotImplementedError
+
+ @property
+ def _console_scripts(self):
+ if self._extracted is False:
+ return None # pragma: no cover
+ if self._console_entry_points is None:
+ self._console_entry_points = {}
+ entry_points = self._dist_info / "entry_points.txt"
+ if entry_points.exists():
+ parser = ConfigParser.ConfigParser()
+ with entry_points.open() as file_handler:
+ reader = getattr(parser, "read_file" if PY3 else "readfp")
+ reader(file_handler)
+ if "console_scripts" in parser.sections():
+ for name, value in parser.items("console_scripts"):
+ match = re.match(r"(.*?)-?\d\.?\d*", name)
+ if match:
+ name = match.groups(1)[0]
+ self._console_entry_points[name] = value
+ return self._console_entry_points
+
+ def _create_console_entry_point(self, name, value, to_folder, version_info):
+ result = []
+ maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name)
+ specification = "{} = {}".format(name, value)
+ new_files = maker.make(specification)
+ result.extend(Path(i) for i in new_files)
+ return result
+
+ def clear(self):
+ if self._image_dir.exists():
+ safe_delete(self._image_dir)
+
+ def has_image(self):
+ return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None
+
+
+class ScriptMakerCustom(ScriptMaker):
+ def __init__(self, target_dir, version_info, executable, name):
+ super(ScriptMakerCustom, self).__init__(None, str(target_dir))
+ self.clobber = True # overwrite
+ self.set_mode = True # ensure they are executable
+ self.executable = enquote_executable(str(executable))
+ self.version_info = version_info.major, version_info.minor
+ self.variants = {"", "X", "X.Y"}
+ self._name = name
+
+ def _write_script(self, names, shebang, script_bytes, filenames, ext):
+ names.add("{}{}.{}".format(self._name, *self.version_info))
+ super(ScriptMakerCustom, self)._write_script(names, shebang, script_bytes, filenames, ext)
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/copy.py b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/copy.py
new file mode 100644
index 0000000000..29d0bc88d1
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/copy.py
@@ -0,0 +1,35 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+
+from virtualenv.util.path import Path, copy
+from virtualenv.util.six import ensure_text
+
+from .base import PipInstall
+
+
+class CopyPipInstall(PipInstall):
+ def _sync(self, src, dst):
+ copy(src, dst)
+
+ def _generate_new_files(self):
+ # create the pyc files
+ new_files = super(CopyPipInstall, self)._generate_new_files()
+ new_files.update(self._cache_files())
+ return new_files
+
+ def _cache_files(self):
+ version = self._creator.interpreter.version_info
+ py_c_ext = ".{}-{}{}.pyc".format(self._creator.interpreter.implementation.lower(), version.major, version.minor)
+ for root, dirs, files in os.walk(ensure_text(str(self._image_dir)), topdown=True):
+ root_path = Path(root)
+ for name in files:
+ if name.endswith(".py"):
+ yield root_path / "{}{}".format(name[:-3], py_c_ext)
+ for name in dirs:
+ yield root_path / name / "__pycache__"
+
+ def _fix_records(self, new_files):
+ extra_record_data_str = self._records_text(new_files)
+ with open(ensure_text(str(self._dist_info / "RECORD")), "ab") as file_handler:
+ file_handler.write(extra_record_data_str.encode("utf-8"))
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/symlink.py b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/symlink.py
new file mode 100644
index 0000000000..f958b65451
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/pip_install/symlink.py
@@ -0,0 +1,61 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+import subprocess
+from stat import S_IREAD, S_IRGRP, S_IROTH
+
+from virtualenv.util.path import safe_delete, set_tree
+from virtualenv.util.six import ensure_text
+from virtualenv.util.subprocess import Popen
+
+from .base import PipInstall
+
+
+class SymlinkPipInstall(PipInstall):
+ def _sync(self, src, dst):
+ src_str = ensure_text(str(src))
+ dest_str = ensure_text(str(dst))
+ os.symlink(src_str, dest_str)
+
+ def _generate_new_files(self):
+ # create the pyc files, as the build image will be R/O
+ process = Popen(
+ [ensure_text(str(self._creator.exe)), "-m", "compileall", ensure_text(str(self._image_dir))],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ process.communicate()
+ # the root pyc is shared, so we'll not symlink that - but still add the pyc files to the RECORD for close
+ root_py_cache = self._image_dir / "__pycache__"
+ new_files = set()
+ if root_py_cache.exists():
+ new_files.update(root_py_cache.iterdir())
+ new_files.add(root_py_cache)
+ safe_delete(root_py_cache)
+ core_new_files = super(SymlinkPipInstall, self)._generate_new_files()
+ # remove files that are within the image folder deeper than one level (as these will be not linked directly)
+ for file in core_new_files:
+ try:
+ rel = file.relative_to(self._image_dir)
+ if len(rel.parts) > 1:
+ continue
+ except ValueError:
+ pass
+ new_files.add(file)
+ return new_files
+
+ def _fix_records(self, new_files):
+ new_files.update(i for i in self._image_dir.iterdir())
+ extra_record_data_str = self._records_text(sorted(new_files, key=str))
+ with open(ensure_text(str(self._dist_info / "RECORD")), "wb") as file_handler:
+ file_handler.write(extra_record_data_str.encode("utf-8"))
+
+ def build_image(self):
+ super(SymlinkPipInstall, self).build_image()
+ # protect the image by making it read only
+ set_tree(self._image_dir, S_IREAD | S_IRGRP | S_IROTH)
+
+ def clear(self):
+ if self._image_dir.exists():
+ safe_delete(self._image_dir)
+ super(SymlinkPipInstall, self).clear()
diff --git a/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/via_app_data.py b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/via_app_data.py
new file mode 100644
index 0000000000..1afa7978c5
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/embed/via_app_data/via_app_data.py
@@ -0,0 +1,139 @@
+"""Bootstrap"""
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import sys
+import traceback
+from contextlib import contextmanager
+from subprocess import CalledProcessError
+from threading import Lock, Thread
+
+from virtualenv.info import fs_supports_symlink
+from virtualenv.seed.embed.base_embed import BaseEmbed
+from virtualenv.seed.wheels import get_wheel
+from virtualenv.util.path import Path
+
+from .pip_install.copy import CopyPipInstall
+from .pip_install.symlink import SymlinkPipInstall
+
+
+class FromAppData(BaseEmbed):
+ def __init__(self, options):
+ super(FromAppData, self).__init__(options)
+ self.symlinks = options.symlink_app_data
+
+ @classmethod
+ def add_parser_arguments(cls, parser, interpreter, app_data):
+ super(FromAppData, cls).add_parser_arguments(parser, interpreter, app_data)
+ can_symlink = app_data.transient is False and fs_supports_symlink()
+ parser.add_argument(
+ "--symlink-app-data",
+ dest="symlink_app_data",
+ action="store_true" if can_symlink else "store_false",
+ help="{} symlink the python packages from the app-data folder (requires seed pip>=19.3)".format(
+ "" if can_symlink else "not supported - ",
+ ),
+ default=False,
+ )
+
+ def run(self, creator):
+ if not self.enabled:
+ return
+ with self._get_seed_wheels(creator) as name_to_whl:
+ pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None
+ installer_class = self.installer_class(pip_version)
+ exceptions = {}
+
+ def _install(name, wheel):
+ try:
+ logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__)
+ key = Path(installer_class.__name__) / wheel.path.stem
+ wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)
+ installer = installer_class(wheel.path, creator, wheel_img)
+ parent = self.app_data.lock / wheel_img.parent
+ with parent.non_reentrant_lock_for_key(wheel_img.name):
+ if not installer.has_image():
+ installer.build_image()
+ installer.install(creator.interpreter.version_info)
+ except Exception: # noqa
+ exceptions[name] = sys.exc_info()
+
+ threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items())
+ for thread in threads:
+ thread.start()
+ for thread in threads:
+ thread.join()
+ if exceptions:
+ messages = ["failed to build image {} because:".format(", ".join(exceptions.keys()))]
+ for value in exceptions.values():
+ exc_type, exc_value, exc_traceback = value
+ messages.append("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
+ raise RuntimeError("\n".join(messages))
+
+ @contextmanager
+ def _get_seed_wheels(self, creator):
+ name_to_whl, lock, fail = {}, Lock(), {}
+
+ def _get(distribution, version):
+ for_py_version = creator.interpreter.version_release_str
+ failure, result = None, None
+ # fallback to download in case the exact version is not available
+ for download in [True] if self.download else [False, True]:
+ failure = None
+ try:
+ result = get_wheel(
+ distribution=distribution,
+ version=version,
+ for_py_version=for_py_version,
+ search_dirs=self.extra_search_dir,
+ download=download,
+ app_data=self.app_data,
+ do_periodic_update=self.periodic_update,
+ )
+ if result is not None:
+ break
+ except Exception as exception: # noqa
+ logging.exception("fail")
+ failure = exception
+ if failure:
+ if isinstance(failure, CalledProcessError):
+ msg = "failed to download {}".format(distribution)
+ if version is not None:
+ msg += " version {}".format(version)
+ msg += ", pip download exit code {}".format(failure.returncode)
+ output = failure.output if sys.version_info < (3, 5) else (failure.output + failure.stderr)
+ if output:
+ msg += "\n"
+ msg += output
+ else:
+ msg = repr(failure)
+ logging.error(msg)
+ with lock:
+ fail[distribution] = version
+ else:
+ with lock:
+ name_to_whl[distribution] = result
+
+ threads = list(
+ Thread(target=_get, args=(distribution, version))
+ for distribution, version in self.distribution_to_versions().items()
+ )
+ for thread in threads:
+ thread.start()
+ for thread in threads:
+ thread.join()
+ if fail:
+ raise RuntimeError("seed failed due to failing to download wheels {}".format(", ".join(fail.keys())))
+ yield name_to_whl
+
+ def installer_class(self, pip_version_tuple):
+ if self.symlinks and pip_version_tuple:
+ # symlink support requires pip 19.3+
+ if pip_version_tuple >= (19, 3):
+ return SymlinkPipInstall
+ return CopyPipInstall
+
+ def __unicode__(self):
+ base = super(FromAppData, self).__unicode__()
+ msg = ", via={}, app_data_dir={}".format("symlink" if self.symlinks else "copy", self.app_data)
+ return base[:-1] + msg + base[-1]
diff --git a/third_party/python/virtualenv/virtualenv/seed/seeder.py b/third_party/python/virtualenv/virtualenv/seed/seeder.py
new file mode 100644
index 0000000000..2bcccfc727
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/seeder.py
@@ -0,0 +1,39 @@
+from __future__ import absolute_import, unicode_literals
+
+from abc import ABCMeta, abstractmethod
+
+from six import add_metaclass
+
+
+@add_metaclass(ABCMeta)
+class Seeder(object):
+ """A seeder will install some seed packages into a virtual environment."""
+
+ # noinspection PyUnusedLocal
+ def __init__(self, options, enabled):
+ """
+
+ :param options: the parsed options as defined within :meth:`add_parser_arguments`
+ :param enabled: a flag weather the seeder is enabled or not
+ """
+ self.enabled = enabled
+
+ @classmethod
+ def add_parser_arguments(cls, parser, interpreter, app_data):
+ """
+ Add CLI arguments for this seed mechanisms.
+
+ :param parser: the CLI parser
+ :param app_data: the CLI parser
+ :param interpreter: the interpreter this virtual environment is based of
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def run(self, creator):
+ """Perform the seed operation.
+
+ :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this \
+ virtual environment
+ """
+ raise NotImplementedError
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/__init__.py b/third_party/python/virtualenv/virtualenv/seed/wheels/__init__.py
new file mode 100644
index 0000000000..dbffe2e433
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/__init__.py
@@ -0,0 +1,11 @@
+from __future__ import absolute_import, unicode_literals
+
+from .acquire import get_wheel, pip_wheel_env_run
+from .util import Version, Wheel
+
+__all__ = (
+ "get_wheel",
+ "pip_wheel_env_run",
+ "Version",
+ "Wheel",
+)
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/acquire.py b/third_party/python/virtualenv/virtualenv/seed/wheels/acquire.py
new file mode 100644
index 0000000000..e63ecb67cf
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/acquire.py
@@ -0,0 +1,120 @@
+"""Bootstrap"""
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import sys
+from operator import eq, lt
+
+from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_str
+from virtualenv.util.subprocess import Popen, subprocess
+
+from .bundle import from_bundle
+from .util import Version, Wheel, discover_wheels
+
+
+def get_wheel(distribution, version, for_py_version, search_dirs, download, app_data, do_periodic_update):
+ """
+ Get a wheel with the given distribution-version-for_py_version trio, by using the extra search dir + download
+ """
+ # not all wheels are compatible with all python versions, so we need to py version qualify it
+ # 1. acquire from bundle
+ wheel = from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update)
+
+ # 2. download from the internet
+ if version not in Version.non_version and download:
+ wheel = download_wheel(
+ distribution=distribution,
+ version_spec=Version.as_version_spec(version),
+ for_py_version=for_py_version,
+ search_dirs=search_dirs,
+ app_data=app_data,
+ to_folder=app_data.house,
+ )
+ return wheel
+
+
+def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_data, to_folder):
+ to_download = "{}{}".format(distribution, version_spec or "")
+ logging.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder)
+ cmd = [
+ sys.executable,
+ "-m",
+ "pip",
+ "download",
+ "--progress-bar",
+ "off",
+ "--disable-pip-version-check",
+ "--only-binary=:all:",
+ "--no-deps",
+ "--python-version",
+ for_py_version,
+ "-d",
+ str(to_folder),
+ to_download,
+ ]
+ # pip has no interface in python - must be a new sub-process
+ env = pip_wheel_env_run(search_dirs, app_data)
+ process = Popen(cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+ out, err = process.communicate()
+ if process.returncode != 0:
+ kwargs = {"output": out}
+ if sys.version_info < (3, 5):
+ kwargs["output"] += err
+ else:
+ kwargs["stderr"] = err
+ raise subprocess.CalledProcessError(process.returncode, cmd, **kwargs)
+ result = _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out)
+ logging.debug("downloaded wheel %s", result.name)
+ return result
+
+
+def _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out):
+ for line in out.splitlines():
+ line = line.lstrip()
+ for marker in ("Saved ", "File was already downloaded "):
+ if line.startswith(marker):
+ return Wheel(Path(line[len(marker) :]).absolute())
+ # if for some reason the output does not match fallback to latest version with that spec
+ return find_compatible_in_house(distribution, version_spec, for_py_version, to_folder)
+
+
+def find_compatible_in_house(distribution, version_spec, for_py_version, in_folder):
+ wheels = discover_wheels(in_folder, distribution, None, for_py_version)
+ start, end = 0, len(wheels)
+ if version_spec is not None:
+ if version_spec.startswith("<"):
+ from_pos, op = 1, lt
+ elif version_spec.startswith("=="):
+ from_pos, op = 2, eq
+ else:
+ raise ValueError(version_spec)
+ version = Wheel.as_version_tuple(version_spec[from_pos:])
+ start = next((at for at, w in enumerate(wheels) if op(w.version_tuple, version)), len(wheels))
+
+ return None if start == end else wheels[start]
+
+
+def pip_wheel_env_run(search_dirs, app_data):
+ for_py_version = "{}.{}".format(*sys.version_info[0:2])
+ env = os.environ.copy()
+ env.update(
+ {
+ ensure_str(k): str(v) # python 2 requires these to be string only (non-unicode)
+ for k, v in {"PIP_USE_WHEEL": "1", "PIP_USER": "0", "PIP_NO_INPUT": "1"}.items()
+ },
+ )
+ wheel = get_wheel(
+ distribution="pip",
+ version=None,
+ for_py_version=for_py_version,
+ search_dirs=search_dirs,
+ download=False,
+ app_data=app_data,
+ do_periodic_update=False,
+ )
+ if wheel is None:
+ raise RuntimeError("could not find the embedded pip")
+ env[str("PYTHONPATH")] = str(wheel.path)
+ return env
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/bundle.py b/third_party/python/virtualenv/virtualenv/seed/wheels/bundle.py
new file mode 100644
index 0000000000..7c664bd389
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/bundle.py
@@ -0,0 +1,49 @@
+from __future__ import absolute_import, unicode_literals
+
+from ..wheels.embed import get_embed_wheel
+from .periodic_update import periodic_update
+from .util import Version, Wheel, discover_wheels
+
+
+def from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update):
+ """
+ Load the bundled wheel to a cache directory.
+ """
+ of_version = Version.of_version(version)
+ wheel = load_embed_wheel(app_data, distribution, for_py_version, of_version)
+
+ if version != Version.embed:
+ # 2. check if we have upgraded embed
+ if app_data.can_update:
+ wheel = periodic_update(distribution, for_py_version, wheel, search_dirs, app_data, do_periodic_update)
+
+ # 3. acquire from extra search dir
+ found_wheel = from_dir(distribution, of_version, for_py_version, search_dirs)
+ if found_wheel is not None:
+ if wheel is None:
+ wheel = found_wheel
+ elif found_wheel.version_tuple > wheel.version_tuple:
+ wheel = found_wheel
+ return wheel
+
+
+def load_embed_wheel(app_data, distribution, for_py_version, version):
+ wheel = get_embed_wheel(distribution, for_py_version)
+ if wheel is not None:
+ version_match = version == wheel.version
+ if version is None or version_match:
+ with app_data.ensure_extracted(wheel.path, lambda: app_data.house) as wheel_path:
+ wheel = Wheel(wheel_path)
+ else: # if version does not match ignore
+ wheel = None
+ return wheel
+
+
+def from_dir(distribution, version, for_py_version, directories):
+ """
+ Load a compatible wheel from a given folder.
+ """
+ for folder in directories:
+ for wheel in discover_wheels(folder, distribution, version, for_py_version):
+ return wheel
+ return None
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/__init__.py b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/__init__.py
new file mode 100644
index 0000000000..5233e48761
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/__init__.py
@@ -0,0 +1,62 @@
+from __future__ import absolute_import, unicode_literals
+
+from virtualenv.seed.wheels.util import Wheel
+from virtualenv.util.path import Path
+
+BUNDLE_FOLDER = Path(__file__).absolute().parent
+BUNDLE_SUPPORT = {
+ "3.10": {
+ "pip": "pip-20.3.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-51.0.0-py3-none-any.whl",
+ "wheel": "wheel-0.36.1-py2.py3-none-any.whl",
+ },
+ "3.9": {
+ "pip": "pip-20.3.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-51.0.0-py3-none-any.whl",
+ "wheel": "wheel-0.36.1-py2.py3-none-any.whl",
+ },
+ "3.8": {
+ "pip": "pip-20.3.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-51.0.0-py3-none-any.whl",
+ "wheel": "wheel-0.36.1-py2.py3-none-any.whl",
+ },
+ "3.7": {
+ "pip": "pip-20.3.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-51.0.0-py3-none-any.whl",
+ "wheel": "wheel-0.36.1-py2.py3-none-any.whl",
+ },
+ "3.6": {
+ "pip": "pip-20.3.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-51.0.0-py3-none-any.whl",
+ "wheel": "wheel-0.36.1-py2.py3-none-any.whl",
+ },
+ "3.5": {
+ "pip": "pip-20.3.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-50.3.2-py3-none-any.whl",
+ "wheel": "wheel-0.36.1-py2.py3-none-any.whl",
+ },
+ "3.4": {
+ "pip": "pip-19.1.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-43.0.0-py2.py3-none-any.whl",
+ "wheel": "wheel-0.33.6-py2.py3-none-any.whl",
+ },
+ "2.7": {
+ "pip": "pip-20.3.1-py2.py3-none-any.whl",
+ "setuptools": "setuptools-44.1.1-py2.py3-none-any.whl",
+ "wheel": "wheel-0.36.1-py2.py3-none-any.whl",
+ },
+}
+MAX = "3.10"
+
+
+def get_embed_wheel(distribution, for_py_version):
+ path = BUNDLE_FOLDER / (BUNDLE_SUPPORT.get(for_py_version, {}) or BUNDLE_SUPPORT[MAX]).get(distribution)
+ return Wheel.from_path(path)
+
+
+__all__ = (
+ "get_embed_wheel",
+ "BUNDLE_SUPPORT",
+ "MAX",
+ "BUNDLE_FOLDER",
+)
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl
new file mode 100644
index 0000000000..8476c11930
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl
new file mode 100644
index 0000000000..fbac5d3c90
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl
new file mode 100644
index 0000000000..733faa6a54
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl
new file mode 100644
index 0000000000..bf28513c99
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl
new file mode 100644
index 0000000000..56d1bf92d7
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl
new file mode 100644
index 0000000000..7e60e11305
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl
new file mode 100644
index 0000000000..2a71896be9
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl
new file mode 100644
index 0000000000..1f17303bf9
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl
Binary files differ
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/periodic_update.py b/third_party/python/virtualenv/virtualenv/seed/wheels/periodic_update.py
new file mode 100644
index 0000000000..fd0ff4c264
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/periodic_update.py
@@ -0,0 +1,367 @@
+"""
+Periodically update bundled versions.
+"""
+
+from __future__ import absolute_import, unicode_literals
+
+import json
+import logging
+import os
+import ssl
+import subprocess
+import sys
+from datetime import datetime, timedelta
+from itertools import groupby
+from shutil import copy2
+from textwrap import dedent
+from threading import Thread
+
+from six.moves.urllib.error import URLError
+from six.moves.urllib.request import urlopen
+
+from virtualenv.app_data import AppDataDiskFolder
+from virtualenv.info import PY2
+from virtualenv.util.path import Path
+from virtualenv.util.subprocess import CREATE_NO_WINDOW, Popen
+
+from ..wheels.embed import BUNDLE_SUPPORT
+from ..wheels.util import Wheel
+
+if PY2:
+ # on Python 2 datetime.strptime throws the error below if the import did not trigger on main thread
+ # Failed to import _strptime because the import lock is held by
+ try:
+ import _strptime # noqa
+ except ImportError: # pragma: no cov
+ pass # pragma: no cov
+
+
+def periodic_update(distribution, for_py_version, wheel, search_dirs, app_data, do_periodic_update):
+ if do_periodic_update:
+ handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data)
+
+ now = datetime.now()
+
+ u_log = UpdateLog.from_app_data(app_data, distribution, for_py_version)
+ u_log_older_than_hour = now - u_log.completed > timedelta(hours=1) if u_log.completed is not None else False
+ for _, group in groupby(u_log.versions, key=lambda v: v.wheel.version_tuple[0:2]):
+ version = next(group) # use only latest patch version per minor, earlier assumed to be buggy
+ if wheel is not None and Path(version.filename).name == wheel.name:
+ break
+ if u_log.periodic is False or (u_log_older_than_hour and version.use(now)):
+ updated_wheel = Wheel(app_data.house / version.filename)
+ logging.debug("using %supdated wheel %s", "periodically " if updated_wheel else "", updated_wheel)
+ wheel = updated_wheel
+ break
+
+ return wheel
+
+
+def handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data):
+ embed_update_log = app_data.embed_update_log(distribution, for_py_version)
+ u_log = UpdateLog.from_dict(embed_update_log.read())
+ if u_log.needs_update:
+ u_log.periodic = True
+ u_log.started = datetime.now()
+ embed_update_log.write(u_log.to_dict())
+ trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, periodic=True)
+
+
+DATETIME_FMT = "%Y-%m-%dT%H:%M:%S.%fZ"
+
+
+def dump_datetime(value):
+ return None if value is None else value.strftime(DATETIME_FMT)
+
+
+def load_datetime(value):
+ return None if value is None else datetime.strptime(value, DATETIME_FMT)
+
+
+class NewVersion(object):
+ def __init__(self, filename, found_date, release_date):
+ self.filename = filename
+ self.found_date = found_date
+ self.release_date = release_date
+
+ @classmethod
+ def from_dict(cls, dictionary):
+ return cls(
+ filename=dictionary["filename"],
+ found_date=load_datetime(dictionary["found_date"]),
+ release_date=load_datetime(dictionary["release_date"]),
+ )
+
+ def to_dict(self):
+ return {
+ "filename": self.filename,
+ "release_date": dump_datetime(self.release_date),
+ "found_date": dump_datetime(self.found_date),
+ }
+
+ def use(self, now):
+ compare_from = self.release_date or self.found_date
+ return now - compare_from >= timedelta(days=28)
+
+ def __repr__(self):
+ return "{}(filename={}), found_date={}, release_date={})".format(
+ self.__class__.__name__,
+ self.filename,
+ self.found_date,
+ self.release_date,
+ )
+
+ def __eq__(self, other):
+ return type(self) == type(other) and all(
+ getattr(self, k) == getattr(other, k) for k in ["filename", "release_date", "found_date"]
+ )
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ @property
+ def wheel(self):
+ return Wheel(Path(self.filename))
+
+
+class UpdateLog(object):
+ def __init__(self, started, completed, versions, periodic):
+ self.started = started
+ self.completed = completed
+ self.versions = versions
+ self.periodic = periodic
+
+ @classmethod
+ def from_dict(cls, dictionary):
+ if dictionary is None:
+ dictionary = {}
+ return cls(
+ load_datetime(dictionary.get("started")),
+ load_datetime(dictionary.get("completed")),
+ [NewVersion.from_dict(v) for v in dictionary.get("versions", [])],
+ dictionary.get("periodic"),
+ )
+
+ @classmethod
+ def from_app_data(cls, app_data, distribution, for_py_version):
+ raw_json = app_data.embed_update_log(distribution, for_py_version).read()
+ return cls.from_dict(raw_json)
+
+ def to_dict(self):
+ return {
+ "started": dump_datetime(self.started),
+ "completed": dump_datetime(self.completed),
+ "periodic": self.periodic,
+ "versions": [r.to_dict() for r in self.versions],
+ }
+
+ @property
+ def needs_update(self):
+ now = datetime.now()
+ if self.completed is None: # never completed
+ return self._check_start(now)
+ else:
+ if now - self.completed <= timedelta(days=14):
+ return False
+ return self._check_start(now)
+
+ def _check_start(self, now):
+ return self.started is None or now - self.started > timedelta(hours=1)
+
+
+def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, periodic):
+ wheel_path = None if wheel is None else str(wheel.path)
+ cmd = [
+ sys.executable,
+ "-c",
+ dedent(
+ """
+ from virtualenv.report import setup_report, MAX_LEVEL
+ from virtualenv.seed.wheels.periodic_update import do_update
+ setup_report(MAX_LEVEL, show_pid=True)
+ do_update({!r}, {!r}, {!r}, {!r}, {!r}, {!r})
+ """,
+ )
+ .strip()
+ .format(distribution, for_py_version, wheel_path, str(app_data), [str(p) for p in search_dirs], periodic),
+ ]
+ debug = os.environ.get(str("_VIRTUALENV_PERIODIC_UPDATE_INLINE")) == str("1")
+ pipe = None if debug else subprocess.PIPE
+ kwargs = {"stdout": pipe, "stderr": pipe}
+ if not debug and sys.platform == "win32":
+ kwargs["creationflags"] = CREATE_NO_WINDOW
+ process = Popen(cmd, **kwargs)
+ logging.info(
+ "triggered periodic upgrade of %s%s (for python %s) via background process having PID %d",
+ distribution,
+ "" if wheel is None else "=={}".format(wheel.version),
+ for_py_version,
+ process.pid,
+ )
+ if debug:
+ process.communicate() # on purpose not called to make it a background process
+
+
+def do_update(distribution, for_py_version, embed_filename, app_data, search_dirs, periodic):
+ versions = None
+ try:
+ versions = _run_do_update(app_data, distribution, embed_filename, for_py_version, periodic, search_dirs)
+ finally:
+ logging.debug("done %s %s with %s", distribution, for_py_version, versions)
+ return versions
+
+
+def _run_do_update(app_data, distribution, embed_filename, for_py_version, periodic, search_dirs):
+ from virtualenv.seed.wheels import acquire
+
+ wheel_filename = None if embed_filename is None else Path(embed_filename)
+ embed_version = None if wheel_filename is None else Wheel(wheel_filename).version_tuple
+ app_data = AppDataDiskFolder(app_data) if isinstance(app_data, str) else app_data
+ search_dirs = [Path(p) if isinstance(p, str) else p for p in search_dirs]
+ wheelhouse = app_data.house
+ embed_update_log = app_data.embed_update_log(distribution, for_py_version)
+ u_log = UpdateLog.from_dict(embed_update_log.read())
+ now = datetime.now()
+ if wheel_filename is not None:
+ dest = wheelhouse / wheel_filename.name
+ if not dest.exists():
+ copy2(str(wheel_filename), str(wheelhouse))
+ last, last_version, versions = None, None, []
+ while last is None or not last.use(now):
+ download_time = datetime.now()
+ dest = acquire.download_wheel(
+ distribution=distribution,
+ version_spec=None if last_version is None else "<{}".format(last_version),
+ for_py_version=for_py_version,
+ search_dirs=search_dirs,
+ app_data=app_data,
+ to_folder=wheelhouse,
+ )
+ if dest is None or (u_log.versions and u_log.versions[0].filename == dest.name):
+ break
+ release_date = release_date_for_wheel_path(dest.path)
+ last = NewVersion(filename=dest.path.name, release_date=release_date, found_date=download_time)
+ logging.info("detected %s in %s", last, datetime.now() - download_time)
+ versions.append(last)
+ last_wheel = Wheel(Path(last.filename))
+ last_version = last_wheel.version
+ if embed_version is not None:
+ if embed_version >= last_wheel.version_tuple: # stop download if we reach the embed version
+ break
+ u_log.periodic = periodic
+ if not u_log.periodic:
+ u_log.started = now
+ u_log.versions = versions + u_log.versions
+ u_log.completed = datetime.now()
+ embed_update_log.write(u_log.to_dict())
+ return versions
+
+
+def release_date_for_wheel_path(dest):
+ wheel = Wheel(dest)
+ # the most accurate is to ask PyPi - e.g. https://pypi.org/pypi/pip/json,
+ # see https://warehouse.pypa.io/api-reference/json/ for more details
+ content = _pypi_get_distribution_info_cached(wheel.distribution)
+ if content is not None:
+ try:
+ upload_time = content["releases"][wheel.version][0]["upload_time"]
+ return datetime.strptime(upload_time, "%Y-%m-%dT%H:%M:%S")
+ except Exception as exception:
+ logging.error("could not load release date %s because %r", content, exception)
+ return None
+
+
+def _request_context():
+ yield None
+ # fallback to non verified HTTPS (the information we request is not sensitive, so fallback)
+ yield ssl._create_unverified_context() # noqa
+
+
+_PYPI_CACHE = {}
+
+
+def _pypi_get_distribution_info_cached(distribution):
+ if distribution not in _PYPI_CACHE:
+ _PYPI_CACHE[distribution] = _pypi_get_distribution_info(distribution)
+ return _PYPI_CACHE[distribution]
+
+
+def _pypi_get_distribution_info(distribution):
+ content, url = None, "https://pypi.org/pypi/{}/json".format(distribution)
+ try:
+ for context in _request_context():
+ try:
+ with urlopen(url, context=context) as file_handler:
+ content = json.load(file_handler)
+ break
+ except URLError as exception:
+ logging.error("failed to access %s because %r", url, exception)
+ except Exception as exception:
+ logging.error("failed to access %s because %r", url, exception)
+ return content
+
+
+def manual_upgrade(app_data):
+ threads = []
+
+ for for_py_version, distribution_to_package in BUNDLE_SUPPORT.items():
+ # load extra search dir for the given for_py
+ for distribution in distribution_to_package.keys():
+ thread = Thread(target=_run_manual_upgrade, args=(app_data, distribution, for_py_version))
+ thread.start()
+ threads.append(thread)
+
+ for thread in threads:
+ thread.join()
+
+
+def _run_manual_upgrade(app_data, distribution, for_py_version):
+ start = datetime.now()
+ from .bundle import from_bundle
+
+ current = from_bundle(
+ distribution=distribution,
+ version=None,
+ for_py_version=for_py_version,
+ search_dirs=[],
+ app_data=app_data,
+ do_periodic_update=False,
+ )
+ logging.warning(
+ "upgrade %s for python %s with current %s",
+ distribution,
+ for_py_version,
+ "" if current is None else current.name,
+ )
+ versions = do_update(
+ distribution=distribution,
+ for_py_version=for_py_version,
+ embed_filename=current.path,
+ app_data=app_data,
+ search_dirs=[],
+ periodic=False,
+ )
+ msg = "upgraded %s for python %s in %s {}".format(
+ "new entries found:\n%s" if versions else "no new versions found",
+ )
+ args = [
+ distribution,
+ for_py_version,
+ datetime.now() - start,
+ ]
+ if versions:
+ args.append("\n".join("\t{}".format(v) for v in versions))
+ logging.warning(msg, *args)
+
+
+__all__ = (
+ "periodic_update",
+ "do_update",
+ "manual_upgrade",
+ "NewVersion",
+ "UpdateLog",
+ "load_datetime",
+ "dump_datetime",
+ "trigger_update",
+ "release_date_for_wheel_path",
+)
diff --git a/third_party/python/virtualenv/virtualenv/seed/wheels/util.py b/third_party/python/virtualenv/virtualenv/seed/wheels/util.py
new file mode 100644
index 0000000000..1240eb2d24
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/seed/wheels/util.py
@@ -0,0 +1,116 @@
+from __future__ import absolute_import, unicode_literals
+
+from operator import attrgetter
+from zipfile import ZipFile
+
+from virtualenv.util.six import ensure_text
+
+
+class Wheel(object):
+ def __init__(self, path):
+ # https://www.python.org/dev/peps/pep-0427/#file-name-convention
+ # The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
+ self.path = path
+ self._parts = path.stem.split("-")
+
+ @classmethod
+ def from_path(cls, path):
+ if path is not None and path.suffix == ".whl" and len(path.stem.split("-")) >= 5:
+ return cls(path)
+ return None
+
+ @property
+ def distribution(self):
+ return self._parts[0]
+
+ @property
+ def version(self):
+ return self._parts[1]
+
+ @property
+ def version_tuple(self):
+ return self.as_version_tuple(self.version)
+
+ @staticmethod
+ def as_version_tuple(version):
+ result = []
+ for part in version.split(".")[0:3]:
+ try:
+ result.append(int(part))
+ except ValueError:
+ break
+ if not result:
+ raise ValueError(version)
+ return tuple(result)
+
+ @property
+ def name(self):
+ return self.path.name
+
+ def support_py(self, py_version):
+ name = "{}.dist-info/METADATA".format("-".join(self.path.stem.split("-")[0:2]))
+ with ZipFile(ensure_text(str(self.path)), "r") as zip_file:
+ metadata = zip_file.read(name).decode("utf-8")
+ marker = "Requires-Python:"
+ requires = next((i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker)), None)
+ if requires is None: # if it does not specify a python requires the assumption is compatible
+ return True
+ py_version_int = tuple(int(i) for i in py_version.split("."))
+ for require in (i.strip() for i in requires.split(",")):
+ # https://www.python.org/dev/peps/pep-0345/#version-specifiers
+ for operator, check in [
+ ("!=", lambda v: py_version_int != v),
+ ("==", lambda v: py_version_int == v),
+ ("<=", lambda v: py_version_int <= v),
+ (">=", lambda v: py_version_int >= v),
+ ("<", lambda v: py_version_int < v),
+ (">", lambda v: py_version_int > v),
+ ]:
+ if require.startswith(operator):
+ ver_str = require[len(operator) :].strip()
+ version = tuple((int(i) if i != "*" else None) for i in ver_str.split("."))[0:2]
+ if not check(version):
+ return False
+ break
+ return True
+
+ def __repr__(self):
+ return "{}({})".format(self.__class__.__name__, self.path)
+
+ def __str__(self):
+ return str(self.path)
+
+
+def discover_wheels(from_folder, distribution, version, for_py_version):
+ wheels = []
+ for filename in from_folder.iterdir():
+ wheel = Wheel.from_path(filename)
+ if wheel and wheel.distribution == distribution:
+ if version is None or wheel.version == version:
+ if wheel.support_py(for_py_version):
+ wheels.append(wheel)
+ return sorted(wheels, key=attrgetter("version_tuple", "distribution"), reverse=True)
+
+
+class Version:
+ #: the version bundled with virtualenv
+ bundle = "bundle"
+ embed = "embed"
+ #: custom version handlers
+ non_version = (
+ bundle,
+ embed,
+ )
+
+ @staticmethod
+ def of_version(value):
+ return None if value in Version.non_version else value
+
+ @staticmethod
+ def as_pip_req(distribution, version):
+ return "{}{}".format(distribution, Version.as_version_spec(version))
+
+ @staticmethod
+ def as_version_spec(version):
+ of_version = Version.of_version(version)
+ return "" if of_version is None else "=={}".format(of_version)
diff --git a/third_party/python/virtualenv/virtualenv/util/__init__.py b/third_party/python/virtualenv/virtualenv/util/__init__.py
new file mode 100644
index 0000000000..32d02925bf
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/__init__.py
@@ -0,0 +1,11 @@
+from __future__ import absolute_import, unicode_literals
+
+import sys
+
+if sys.version_info[0] == 3:
+ import configparser as ConfigParser
+else:
+ import ConfigParser
+
+
+__all__ = ("ConfigParser",)
diff --git a/third_party/python/virtualenv/virtualenv/util/error.py b/third_party/python/virtualenv/virtualenv/util/error.py
new file mode 100644
index 0000000000..ac5aa502dd
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/error.py
@@ -0,0 +1,13 @@
+"""Errors"""
+from __future__ import absolute_import, unicode_literals
+
+
+class ProcessCallFailed(RuntimeError):
+ """Failed a process call"""
+
+ def __init__(self, code, out, err, cmd):
+ super(ProcessCallFailed, self).__init__(code, out, err, cmd)
+ self.code = code
+ self.out = out
+ self.err = err
+ self.cmd = cmd
diff --git a/third_party/python/virtualenv/virtualenv/util/lock.py b/third_party/python/virtualenv/virtualenv/util/lock.py
new file mode 100644
index 0000000000..739dc5af80
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/lock.py
@@ -0,0 +1,168 @@
+"""holds locking functionality that works across processes"""
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+from abc import ABCMeta, abstractmethod
+from contextlib import contextmanager
+from threading import Lock, RLock
+
+from filelock import FileLock, Timeout
+from six import add_metaclass
+
+from virtualenv.util.path import Path
+
+
+class _CountedFileLock(FileLock):
+ def __init__(self, lock_file):
+ parent = os.path.dirname(lock_file)
+ if not os.path.isdir(parent):
+ try:
+ os.makedirs(parent)
+ except OSError:
+ pass
+ super(_CountedFileLock, self).__init__(lock_file)
+ self.count = 0
+ self.thread_safe = RLock()
+
+ def acquire(self, timeout=None, poll_intervall=0.05):
+ with self.thread_safe:
+ if self.count == 0:
+ super(_CountedFileLock, self).acquire(timeout=timeout, poll_intervall=poll_intervall)
+ self.count += 1
+
+ def release(self, force=False):
+ with self.thread_safe:
+ if self.count == 1:
+ super(_CountedFileLock, self).release(force=force)
+ self.count = max(self.count - 1, 0)
+
+
+_lock_store = {}
+_store_lock = Lock()
+
+
+@add_metaclass(ABCMeta)
+class PathLockBase(object):
+ def __init__(self, folder):
+ path = Path(folder)
+ self.path = path.resolve() if path.exists() else path
+
+ def __repr__(self):
+ return "{}({})".format(self.__class__.__name__, self.path)
+
+ def __div__(self, other):
+ return type(self)(self.path / other)
+
+ def __truediv__(self, other):
+ return self.__div__(other)
+
+ @abstractmethod
+ def __enter__(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ raise NotImplementedError
+
+ @abstractmethod
+ @contextmanager
+ def lock_for_key(self, name, no_block=False):
+ raise NotImplementedError
+
+ @abstractmethod
+ @contextmanager
+ def non_reentrant_lock_for_key(name):
+ raise NotImplementedError
+
+
+class ReentrantFileLock(PathLockBase):
+ def __init__(self, folder):
+ super(ReentrantFileLock, self).__init__(folder)
+ self._lock = None
+
+ def _create_lock(self, name=""):
+ lock_file = str(self.path / "{}.lock".format(name))
+ with _store_lock:
+ if lock_file not in _lock_store:
+ _lock_store[lock_file] = _CountedFileLock(lock_file)
+ return _lock_store[lock_file]
+
+ @staticmethod
+ def _del_lock(lock):
+ with _store_lock:
+ if lock is not None:
+ with lock.thread_safe:
+ if lock.count == 0:
+ _lock_store.pop(lock.lock_file, None)
+
+ def __del__(self):
+ self._del_lock(self._lock)
+
+ def __enter__(self):
+ self._lock = self._create_lock()
+ self._lock_file(self._lock)
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._release(self._lock)
+
+ def _lock_file(self, lock, no_block=False):
+ # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without
+ # a lock, but that lock might then become expensive, and it's not clear where that lock should live.
+ # Instead here we just ignore if we fail to create the directory.
+ try:
+ os.makedirs(str(self.path))
+ except OSError:
+ pass
+ try:
+ lock.acquire(0.0001)
+ except Timeout:
+ if no_block:
+ raise
+ logging.debug("lock file %s present, will block until released", lock.lock_file)
+ lock.release() # release the acquire try from above
+ lock.acquire()
+
+ @staticmethod
+ def _release(lock):
+ lock.release()
+
+ @contextmanager
+ def lock_for_key(self, name, no_block=False):
+ lock = self._create_lock(name)
+ try:
+ try:
+ self._lock_file(lock, no_block)
+ yield
+ finally:
+ self._release(lock)
+ finally:
+ self._del_lock(lock)
+
+ @contextmanager
+ def non_reentrant_lock_for_key(self, name):
+ with _CountedFileLock(str(self.path / "{}.lock".format(name))):
+ yield
+
+
+class NoOpFileLock(PathLockBase):
+ def __enter__(self):
+ raise NotImplementedError
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ raise NotImplementedError
+
+ @contextmanager
+ def lock_for_key(self, name, no_block=False):
+ yield
+
+ @contextmanager
+ def non_reentrant_lock_for_key(self, name):
+ yield
+
+
+__all__ = (
+ "NoOpFileLock",
+ "ReentrantFileLock",
+ "Timeout",
+)
diff --git a/third_party/python/virtualenv/virtualenv/util/path/__init__.py b/third_party/python/virtualenv/virtualenv/util/path/__init__.py
new file mode 100644
index 0000000000..a7f71634b5
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/path/__init__.py
@@ -0,0 +1,16 @@
+from __future__ import absolute_import, unicode_literals
+
+from ._pathlib import Path
+from ._permission import make_exe, set_tree
+from ._sync import copy, copytree, ensure_dir, safe_delete, symlink
+
+__all__ = (
+ "ensure_dir",
+ "symlink",
+ "copy",
+ "copytree",
+ "Path",
+ "make_exe",
+ "set_tree",
+ "safe_delete",
+)
diff --git a/third_party/python/virtualenv/virtualenv/util/path/_pathlib/__init__.py b/third_party/python/virtualenv/virtualenv/util/path/_pathlib/__init__.py
new file mode 100644
index 0000000000..6bb045c2d8
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/path/_pathlib/__init__.py
@@ -0,0 +1,63 @@
+from __future__ import absolute_import, unicode_literals
+
+import sys
+
+import six
+
+if six.PY3:
+ from pathlib import Path
+
+ if sys.version_info[0:2] == (3, 4):
+ # no read/write text on python3.4
+ BuiltinPath = Path
+
+ class Path(type(BuiltinPath())):
+ def read_text(self, encoding=None, errors=None):
+ """
+ Open the file in text mode, read it, and close the file.
+ """
+ with self.open(mode="r", encoding=encoding, errors=errors) as f:
+ return f.read()
+
+ def read_bytes(self):
+ """
+ Open the file in bytes mode, read it, and close the file.
+ """
+ with self.open(mode="rb") as f:
+ return f.read()
+
+ def write_text(self, data, encoding=None, errors=None):
+ """
+ Open the file in text mode, write to it, and close the file.
+ """
+ if not isinstance(data, str):
+ raise TypeError("data must be str, not %s" % data.__class__.__name__)
+ with self.open(mode="w", encoding=encoding, errors=errors) as f:
+ return f.write(data)
+
+ def write_bytes(self, data):
+ """
+ Open the file in bytes mode, write to it, and close the file.
+ """
+ # type-check for the buffer interface before truncating the file
+ view = memoryview(data)
+ with self.open(mode="wb") as f:
+ return f.write(view)
+
+ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
+ try:
+ super(type(BuiltinPath()), self).mkdir(mode, parents)
+ except FileExistsError as exception:
+ if not exist_ok:
+ raise exception
+
+
+else:
+ if sys.platform == "win32":
+ # workaround for https://github.com/mcmtroffaes/pathlib2/issues/56
+ from .via_os_path import Path
+ else:
+ from pathlib2 import Path
+
+
+__all__ = ("Path",)
diff --git a/third_party/python/virtualenv/virtualenv/util/path/_pathlib/via_os_path.py b/third_party/python/virtualenv/virtualenv/util/path/_pathlib/via_os_path.py
new file mode 100644
index 0000000000..ac78d4f00a
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/path/_pathlib/via_os_path.py
@@ -0,0 +1,148 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+import platform
+from contextlib import contextmanager
+
+from virtualenv.util.six import ensure_str, ensure_text
+
+IS_PYPY = platform.python_implementation() == "PyPy"
+
+
+class Path(object):
+ def __init__(self, path):
+ if isinstance(path, Path):
+ _path = path._path
+ else:
+ _path = ensure_text(path)
+ if IS_PYPY:
+ _path = _path.encode("utf-8")
+ self._path = _path
+
+ def __repr__(self):
+ return ensure_str("Path({})".format(ensure_text(self._path)))
+
+ def __unicode__(self):
+ return ensure_text(self._path)
+
+ def __str__(self):
+ return ensure_str(self._path)
+
+ def __div__(self, other):
+ if isinstance(other, Path):
+ right = other._path
+ else:
+ right = ensure_text(other)
+ if IS_PYPY:
+ right = right.encode("utf-8")
+ return Path(os.path.join(self._path, right))
+
+ def __truediv__(self, other):
+ return self.__div__(other)
+
+ def __eq__(self, other):
+ return self._path == (other._path if isinstance(other, Path) else None)
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __hash__(self):
+ return hash(self._path)
+
+ def exists(self):
+ return os.path.exists(self._path)
+
+ @property
+ def parent(self):
+ return Path(os.path.abspath(os.path.join(self._path, os.path.pardir)))
+
+ def resolve(self):
+ return Path(os.path.realpath(self._path))
+
+ @property
+ def name(self):
+ return os.path.basename(self._path)
+
+ @property
+ def parts(self):
+ return self._path.split(os.sep)
+
+ def is_file(self):
+ return os.path.isfile(self._path)
+
+ def is_dir(self):
+ return os.path.isdir(self._path)
+
+ def mkdir(self, parents=True, exist_ok=True):
+ try:
+ os.makedirs(self._path)
+ except OSError:
+ if not exist_ok:
+ raise
+
+ def read_text(self, encoding="utf-8"):
+ return self.read_bytes().decode(encoding)
+
+ def read_bytes(self):
+ with open(self._path, "rb") as file_handler:
+ return file_handler.read()
+
+ def write_bytes(self, content):
+ with open(self._path, "wb") as file_handler:
+ file_handler.write(content)
+
+ def write_text(self, text, encoding="utf-8"):
+ self.write_bytes(text.encode(encoding))
+
+ def iterdir(self):
+ for p in os.listdir(self._path):
+ yield Path(os.path.join(self._path, p))
+
+ @property
+ def suffix(self):
+ _, ext = os.path.splitext(self.name)
+ return ext
+
+ @property
+ def stem(self):
+ base, _ = os.path.splitext(self.name)
+ return base
+
+ @contextmanager
+ def open(self, mode="r"):
+ with open(self._path, mode) as file_handler:
+ yield file_handler
+
+ @property
+ def parents(self):
+ result = []
+ parts = self.parts
+ for i in range(len(parts) - 1):
+ result.append(Path(os.sep.join(parts[0 : i + 1])))
+ return result[::-1]
+
+ def unlink(self):
+ os.remove(self._path)
+
+ def with_name(self, name):
+ return self.parent / name
+
+ def is_symlink(self):
+ return os.path.islink(self._path)
+
+ def relative_to(self, other):
+ if not self._path.startswith(other._path):
+ raise ValueError("{} does not start with {}".format(self._path, other._path))
+ return Path(os.sep.join(self.parts[len(other.parts) :]))
+
+ def stat(self):
+ return os.stat(self._path)
+
+ def chmod(self, mode):
+ os.chmod(self._path, mode)
+
+ def absolute(self):
+ return Path(os.path.abspath(self._path))
+
+
+__all__ = ("Path",)
diff --git a/third_party/python/virtualenv/virtualenv/util/path/_permission.py b/third_party/python/virtualenv/virtualenv/util/path/_permission.py
new file mode 100644
index 0000000000..73bb6e81a3
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/path/_permission.py
@@ -0,0 +1,32 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+from stat import S_IXGRP, S_IXOTH, S_IXUSR
+
+from virtualenv.util.six import ensure_text
+
+
+def make_exe(filename):
+ original_mode = filename.stat().st_mode
+ levels = [S_IXUSR, S_IXGRP, S_IXOTH]
+ for at in range(len(levels), 0, -1):
+ try:
+ mode = original_mode
+ for level in levels[:at]:
+ mode |= level
+ filename.chmod(mode)
+ break
+ except OSError:
+ continue
+
+
+def set_tree(folder, stat):
+ for root, _, files in os.walk(ensure_text(str(folder))):
+ for filename in files:
+ os.chmod(os.path.join(root, filename), stat)
+
+
+__all__ = (
+ "make_exe",
+ "set_tree",
+)
diff --git a/third_party/python/virtualenv/virtualenv/util/path/_sync.py b/third_party/python/virtualenv/virtualenv/util/path/_sync.py
new file mode 100644
index 0000000000..c3d4af78aa
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/path/_sync.py
@@ -0,0 +1,98 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import shutil
+from stat import S_IWUSR
+
+from six import PY2
+
+from virtualenv.info import IS_CPYTHON, IS_WIN
+from virtualenv.util.six import ensure_text
+
+if PY2 and IS_CPYTHON and IS_WIN: # CPython2 on Windows supports unicode paths if passed as unicode
+
+ def norm(src):
+ return ensure_text(str(src))
+
+
+else:
+ norm = str
+
+
+def ensure_dir(path):
+ if not path.exists():
+ logging.debug("create folder %s", ensure_text(str(path)))
+ os.makedirs(norm(path))
+
+
+def ensure_safe_to_do(src, dest):
+ if src == dest:
+ raise ValueError("source and destination is the same {}".format(src))
+ if not dest.exists():
+ return
+ if dest.is_dir() and not dest.is_symlink():
+ logging.debug("remove directory %s", dest)
+ safe_delete(dest)
+ else:
+ logging.debug("remove file %s", dest)
+ dest.unlink()
+
+
+def symlink(src, dest):
+ ensure_safe_to_do(src, dest)
+ logging.debug("symlink %s", _Debug(src, dest))
+ dest.symlink_to(src, target_is_directory=src.is_dir())
+
+
+def copy(src, dest):
+ ensure_safe_to_do(src, dest)
+ is_dir = src.is_dir()
+ method = copytree if is_dir else shutil.copy
+ logging.debug("copy %s", _Debug(src, dest))
+ method(norm(src), norm(dest))
+
+
+def copytree(src, dest):
+ for root, _, files in os.walk(src):
+ dest_dir = os.path.join(dest, os.path.relpath(root, src))
+ if not os.path.isdir(dest_dir):
+ os.makedirs(dest_dir)
+ for name in files:
+ src_f = os.path.join(root, name)
+ dest_f = os.path.join(dest_dir, name)
+ shutil.copy(src_f, dest_f)
+
+
+def safe_delete(dest):
+ def onerror(func, path, exc_info):
+ if not os.access(path, os.W_OK):
+ os.chmod(path, S_IWUSR)
+ func(path)
+ else:
+ raise
+
+ shutil.rmtree(ensure_text(str(dest)), ignore_errors=True, onerror=onerror)
+
+
+class _Debug(object):
+ def __init__(self, src, dest):
+ self.src = src
+ self.dest = dest
+
+ def __str__(self):
+ return "{}{} to {}".format(
+ "directory " if self.src.is_dir() else "",
+ ensure_text(str(self.src)),
+ ensure_text(str(self.dest)),
+ )
+
+
+__all__ = (
+ "ensure_dir",
+ "symlink",
+ "copy",
+ "symlink",
+ "copytree",
+ "safe_delete",
+)
diff --git a/third_party/python/virtualenv/virtualenv/util/six.py b/third_party/python/virtualenv/virtualenv/util/six.py
new file mode 100644
index 0000000000..16f1c6c95e
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/six.py
@@ -0,0 +1,50 @@
+"""Backward compatibility layer with older version of six.
+
+This is used to avoid virtualenv requring a version of six newer than what
+the system may have.
+"""
+from __future__ import absolute_import
+
+from six import PY2, PY3, binary_type, text_type
+
+try:
+ from six import ensure_text
+except ImportError:
+
+ def ensure_text(s, encoding="utf-8", errors="strict"):
+ """Coerce *s* to six.text_type.
+ For Python 2:
+ - `unicode` -> `unicode`
+ - `str` -> `unicode`
+ For Python 3:
+ - `str` -> `str`
+ - `bytes` -> decoded to `str`
+ """
+ if isinstance(s, binary_type):
+ return s.decode(encoding, errors)
+ elif isinstance(s, text_type):
+ return s
+ else:
+ raise TypeError("not expecting type '%s'" % type(s))
+
+
+try:
+ from six import ensure_str
+except ImportError:
+
+ def ensure_str(s, encoding="utf-8", errors="strict"):
+ """Coerce *s* to `str`.
+ For Python 2:
+ - `unicode` -> encoded to `str`
+ - `str` -> `str`
+ For Python 3:
+ - `str` -> `str`
+ - `bytes` -> decoded to `str`
+ """
+ if not isinstance(s, (text_type, binary_type)):
+ raise TypeError("not expecting type '%s'" % type(s))
+ if PY2 and isinstance(s, text_type):
+ s = s.encode(encoding, errors)
+ elif PY3 and isinstance(s, binary_type):
+ s = s.decode(encoding, errors)
+ return s
diff --git a/third_party/python/virtualenv/virtualenv/util/subprocess/__init__.py b/third_party/python/virtualenv/virtualenv/util/subprocess/__init__.py
new file mode 100644
index 0000000000..f5066268f8
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/subprocess/__init__.py
@@ -0,0 +1,40 @@
+from __future__ import absolute_import, unicode_literals
+
+import subprocess
+import sys
+
+import six
+
+if six.PY2 and sys.platform == "win32":
+ from . import _win_subprocess
+
+ Popen = _win_subprocess.Popen
+else:
+ Popen = subprocess.Popen
+
+
+CREATE_NO_WINDOW = 0x80000000
+
+
+def run_cmd(cmd):
+ try:
+ process = Popen(
+ cmd,
+ universal_newlines=True,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ )
+ out, err = process.communicate() # input disabled
+ code = process.returncode
+ except OSError as os_error:
+ code, out, err = os_error.errno, "", os_error.strerror
+ return code, out, err
+
+
+__all__ = (
+ "subprocess",
+ "Popen",
+ "run_cmd",
+ "CREATE_NO_WINDOW",
+)
diff --git a/third_party/python/virtualenv/virtualenv/util/subprocess/_win_subprocess.py b/third_party/python/virtualenv/virtualenv/util/subprocess/_win_subprocess.py
new file mode 100644
index 0000000000..4c4c5d0295
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/subprocess/_win_subprocess.py
@@ -0,0 +1,175 @@
+# flake8: noqa
+# fmt: off
+## issue: https://bugs.python.org/issue19264
+
+import ctypes
+import os
+import platform
+import subprocess
+from ctypes import Structure, WinError, byref, c_char_p, c_void_p, c_wchar, c_wchar_p, sizeof, windll
+from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPVOID, LPWSTR, WORD
+
+import _subprocess
+
+##
+## Types
+##
+
+CREATE_UNICODE_ENVIRONMENT = 0x00000400
+LPCTSTR = c_char_p
+LPTSTR = c_wchar_p
+LPSECURITY_ATTRIBUTES = c_void_p
+LPBYTE = ctypes.POINTER(BYTE)
+
+class STARTUPINFOW(Structure):
+ _fields_ = [
+ ("cb", DWORD), ("lpReserved", LPWSTR),
+ ("lpDesktop", LPWSTR), ("lpTitle", LPWSTR),
+ ("dwX", DWORD), ("dwY", DWORD),
+ ("dwXSize", DWORD), ("dwYSize", DWORD),
+ ("dwXCountChars", DWORD), ("dwYCountChars", DWORD),
+ ("dwFillAtrribute", DWORD), ("dwFlags", DWORD),
+ ("wShowWindow", WORD), ("cbReserved2", WORD),
+ ("lpReserved2", LPBYTE), ("hStdInput", HANDLE),
+ ("hStdOutput", HANDLE), ("hStdError", HANDLE),
+ ]
+
+LPSTARTUPINFOW = ctypes.POINTER(STARTUPINFOW)
+
+
+class PROCESS_INFORMATION(Structure):
+ _fields_ = [
+ ("hProcess", HANDLE), ("hThread", HANDLE),
+ ("dwProcessId", DWORD), ("dwThreadId", DWORD),
+ ]
+
+LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION)
+
+
+class DUMMY_HANDLE(ctypes.c_void_p):
+
+ def __init__(self, *a, **kw):
+ super(DUMMY_HANDLE, self).__init__(*a, **kw)
+ self.closed = False
+
+ def Close(self):
+ if not self.closed:
+ windll.kernel32.CloseHandle(self)
+ self.closed = True
+
+ def __int__(self):
+ return self.value
+
+
+CreateProcessW = windll.kernel32.CreateProcessW
+CreateProcessW.argtypes = [
+ LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES,
+ LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR,
+ LPSTARTUPINFOW, LPPROCESS_INFORMATION,
+]
+CreateProcessW.restype = BOOL
+
+
+##
+## Patched functions/classes
+##
+
+def CreateProcess(
+ executable, args, _p_attr, _t_attr,
+ inherit_handles, creation_flags, env, cwd,
+ startup_info,
+):
+ """Create a process supporting unicode executable and args for win32
+
+ Python implementation of CreateProcess using CreateProcessW for Win32
+
+ """
+
+ si = STARTUPINFOW(
+ dwFlags=startup_info.dwFlags,
+ wShowWindow=startup_info.wShowWindow,
+ cb=sizeof(STARTUPINFOW),
+ ## XXXvlab: not sure of the casting here to ints.
+ hStdInput=startup_info.hStdInput if startup_info.hStdInput is None else int(startup_info.hStdInput),
+ hStdOutput=startup_info.hStdOutput if startup_info.hStdOutput is None else int(startup_info.hStdOutput),
+ hStdError=startup_info.hStdError if startup_info.hStdError is None else int(startup_info.hStdError),
+ )
+
+ wenv = None
+ if env is not None:
+ ## LPCWSTR seems to be c_wchar_p, so let's say CWSTR is c_wchar
+ env = (
+ unicode("").join([
+ unicode("%s=%s\0") % (k, v)
+ for k, v in env.items()
+ ])
+ ) + unicode("\0")
+ wenv = (c_wchar * len(env))()
+ wenv.value = env
+
+ wcwd = None
+ if cwd is not None:
+ wcwd = unicode(cwd)
+
+ pi = PROCESS_INFORMATION()
+ creation_flags |= CREATE_UNICODE_ENVIRONMENT
+
+ if CreateProcessW(
+ executable, args, None, None,
+ inherit_handles, creation_flags,
+ wenv, wcwd, byref(si), byref(pi),
+ ):
+ return (
+ DUMMY_HANDLE(pi.hProcess), DUMMY_HANDLE(pi.hThread),
+ pi.dwProcessId, pi.dwThreadId,
+ )
+ raise WinError()
+
+
+class Popen(subprocess.Popen):
+ """This superseeds Popen and corrects a bug in cPython 2.7 implem"""
+
+ def _execute_child(
+ self, args, executable, preexec_fn, close_fds,
+ cwd, env, universal_newlines,
+ startupinfo, creationflags, shell, to_close,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite,
+ ):
+ """Code from part of _execute_child from Python 2.7 (9fbb65e)
+
+ There are only 2 little changes concerning the construction of
+ the the final string in shell mode: we preempt the creation of
+ the command string when shell is True, because original function
+ will try to encode unicode args which we want to avoid to be able to
+ sending it as-is to ``CreateProcess``.
+
+ """
+ if startupinfo is None:
+ startupinfo = subprocess.STARTUPINFO()
+ if not isinstance(args, subprocess.types.StringTypes):
+ args = [i if isinstance(i, bytes) else i.encode('utf-8') for i in args]
+ args = subprocess.list2cmdline(args)
+ if platform.python_implementation() == "CPython":
+ args = args.decode('utf-8')
+ startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
+ startupinfo.wShowWindow = _subprocess.SW_HIDE
+ comspec = os.environ.get("COMSPEC", unicode("cmd.exe"))
+ if (
+ _subprocess.GetVersion() >= 0x80000000 or
+ os.path.basename(comspec).lower() == "command.com"
+ ):
+ w9xpopen = self._find_w9xpopen()
+ args = unicode('"%s" %s') % (w9xpopen, args)
+ creationflags |= _subprocess.CREATE_NEW_CONSOLE
+
+ super(Popen, self)._execute_child(
+ args, executable,
+ preexec_fn, close_fds, cwd, env, universal_newlines,
+ startupinfo, creationflags, False, to_close, p2cread,
+ p2cwrite, c2pread, c2pwrite, errread, errwrite,
+ )
+
+_subprocess.CreateProcess = CreateProcess
+# fmt: on
diff --git a/third_party/python/virtualenv/virtualenv/util/zipapp.py b/third_party/python/virtualenv/virtualenv/util/zipapp.py
new file mode 100644
index 0000000000..85d9294f4d
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/util/zipapp.py
@@ -0,0 +1,33 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import zipfile
+
+from virtualenv.info import IS_WIN, ROOT
+from virtualenv.util.six import ensure_text
+
+
+def read(full_path):
+ sub_file = _get_path_within_zip(full_path)
+ with zipfile.ZipFile(ROOT, "r") as zip_file:
+ with zip_file.open(sub_file) as file_handler:
+ return file_handler.read().decode("utf-8")
+
+
+def extract(full_path, dest):
+ logging.debug("extract %s to %s", full_path, dest)
+ sub_file = _get_path_within_zip(full_path)
+ with zipfile.ZipFile(ROOT, "r") as zip_file:
+ info = zip_file.getinfo(sub_file)
+ info.filename = dest.name
+ zip_file.extract(info, ensure_text(str(dest.parent)))
+
+
+def _get_path_within_zip(full_path):
+ full_path = os.path.abspath(str(full_path))
+ sub_file = full_path[len(ROOT) + 1 :]
+ if IS_WIN:
+ # paths are always UNIX separators, even on Windows, though __file__ still follows platform default
+ sub_file = sub_file.replace(os.sep, "/")
+ return sub_file
diff --git a/third_party/python/virtualenv/virtualenv/version.py b/third_party/python/virtualenv/virtualenv/version.py
new file mode 100644
index 0000000000..7f21daf026
--- /dev/null
+++ b/third_party/python/virtualenv/virtualenv/version.py
@@ -0,0 +1,3 @@
+from __future__ import unicode_literals
+
+__version__ = "20.2.2"