From 31301b1536737e7c9c3b37c72a20a2d66cd87f0f Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Mon, 19 Mar 2018 01:06:29 -0400 Subject: [PATCH] Fix deps and testing infrastructure - Fix requirement parsing - Add appveyor config - cutover from pathlib to pathlib2 if needed - Pin pathlib2==2.1.0 to avoid scandir - Windows script runner fix - Backport `shlex.quote()` for use in `pipenv run` - Update tests and appveyor Signed-off-by: Dan Ryan --- Pipfile | 4 +- Pipfile.lock | 142 +- appveyor.yml | 77 + pipenv/cli.py | 28 +- pipenv/core.py | 19 +- pipenv/patched/pew/_utils.py | 5 +- pipenv/patched/pew/pew.py | 5 +- pipenv/requirements.py | 609 ++++++ pipenv/utils.py | 251 +-- pipenv/vendor/attr/__init__.py | 57 + pipenv/vendor/attr/_compat.py | 146 ++ pipenv/vendor/attr/_config.py | 23 + pipenv/vendor/attr/_funcs.py | 212 +++ pipenv/vendor/attr/_make.py | 1680 +++++++++++++++++ pipenv/vendor/attr/converters.py | 24 + pipenv/vendor/attr/exceptions.py | 48 + pipenv/vendor/attr/filters.py | 52 + pipenv/vendor/attr/validators.py | 166 ++ pipenv/vendor/backports/__init__.py | 3 +- pipenv/vendor/backports/shlex/__init__.py | 9 + pipenv/vendor/backports/shlex/shlex.py | 23 + pipenv/vendor/pathlib2.py | 313 ++- setup.py | 2 +- .../ibm-db-sa-py3/ibm-db-sa-py3-0.3.0.tar.gz | Bin 0 -> 24233 bytes .../ibm-db-sa-py3-0.3.1-1.tar.gz | Bin 0 -> 24174 bytes .../win-inet-pton/win_inet_pton-1.0.1.tar.gz | Bin 0 -> 2012 bytes tests/test_pipenv.py | 2 +- tests/test_requirements.py | 34 + tests/test_utils.py | 2 +- 29 files changed, 3458 insertions(+), 478 deletions(-) create mode 100644 appveyor.yml create mode 100644 pipenv/requirements.py create mode 100644 pipenv/vendor/attr/__init__.py create mode 100644 pipenv/vendor/attr/_compat.py create mode 100644 pipenv/vendor/attr/_config.py create mode 100644 pipenv/vendor/attr/_funcs.py create mode 100644 pipenv/vendor/attr/_make.py create mode 100644 pipenv/vendor/attr/converters.py create mode 100644 pipenv/vendor/attr/exceptions.py create mode 100644 pipenv/vendor/attr/filters.py create mode 100644 pipenv/vendor/attr/validators.py create mode 100644 pipenv/vendor/backports/shlex/__init__.py create mode 100644 pipenv/vendor/backports/shlex/shlex.py create mode 100644 tests/pypi/ibm-db-sa-py3/ibm-db-sa-py3-0.3.0.tar.gz create mode 100644 tests/pypi/ibm-db-sa-py3/ibm-db-sa-py3-0.3.1-1.tar.gz create mode 100644 tests/pypi/win-inet-pton/win_inet_pton-1.0.1.tar.gz create mode 100644 tests/test_requirements.py diff --git a/Pipfile b/Pipfile index d9da9c57..581b96b4 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,7 @@ pytest-xdist = "*" click = "*" pytest-pypy = {path = "./tests/pytest-pypi", editable = true} pytest-tap = "*" -stdeb = {version="*", sys_platform="== 'linux'"} +stdeb = {version="*", markers="sys_platform == 'linux'"} white = {version="*", markers="python_version >= '3.6'"} [packages] @@ -23,4 +23,4 @@ tests = "bash ./run-tests.sh" [pipenv] -allow_prereleases = true \ No newline at end of file +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index df32d972..bf38293e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8acf60476c4efca4f1be83dc747f18d4a663c3479e664e9e6865469f7ab9b958" + "sha256": "eab749ae861993f9e4b71bea88782c806a02fa82e3f45c82c4550ee459d8b886" }, "pipfile-spec": 6, "requires": {}, @@ -29,6 +29,13 @@ ], "version": "==1.4" }, + "asn1crypto": { + "hashes": [ + "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", + "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + ], + "version": "==0.24.0" + }, "attrs": { "hashes": [ "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", @@ -43,6 +50,14 @@ ], "version": "==2.5.3" }, + "black": { + "hashes": [ + "sha256:0461c7a52b5beb378936bf642753dec7a45305c96c6129d540b9c53227121a5a", + "sha256:7183263650ba3071034e90b40a1ea74abccbd32cf525cef6d7914479dbe7f2fb" + ], + "markers": "python_version > '3.5'", + "version": "==18.3a0" + }, "certifi": { "hashes": [ "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", @@ -50,6 +65,39 @@ ], "version": "==2018.1.18" }, + "cffi": { + "hashes": [ + "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", + "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", + "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", + "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", + "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", + "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", + "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + ], + "markers": "platform_python_implementation != 'pypy'", + "version": "==1.11.5" + }, "chardet": { "hashes": [ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", @@ -73,6 +121,35 @@ "markers": "sys_platform == 'win32'", "version": "==0.3.9" }, + "configparser": { + "hashes": [ + "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" + ], + "markers": "python_version < '3.2'", + "version": "==3.5.0" + }, + "cryptography": { + "hashes": [ + "sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd", + "sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04", + "sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f", + "sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd", + "sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb", + "sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2", + "sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037", + "sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd", + "sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531", + "sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63", + "sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e", + "sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351", + "sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a", + "sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563", + "sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab", + "sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471", + "sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887" + ], + "version": "==2.2.2" + }, "docutils": { "hashes": [ "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", @@ -81,6 +158,16 @@ ], "version": "==0.14" }, + "enum34": { + "hashes": [ + "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", + "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", + "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", + "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" + ], + "markers": "python_version < '3'", + "version": "==1.1.6" + }, "execnet": { "hashes": [ "sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a", @@ -103,6 +190,14 @@ ], "version": "==0.12.2" }, + "funcsigs": { + "hashes": [ + "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", + "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" + ], + "markers": "python_version < '3.0'", + "version": "==1.0.2" + }, "idna": { "hashes": [ "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", @@ -117,6 +212,13 @@ ], "version": "==1.0.0" }, + "ipaddress": { + "hashes": [ + "sha256:200d8686011d470b5e4de207d803445deee427455cd0cb7c982b68cf82524f81" + ], + "markers": "python_version < '3'", + "version": "==1.0.19" + }, "itsdangerous": { "hashes": [ "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" @@ -159,19 +261,26 @@ ], "version": "==4.1.0" }, - "pathlib": { + "ordereddict": { "hashes": [ - "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f" + "sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f" + ], + "version": "==1.1" + }, + "pathlib2": { + "hashes": [ + "sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e", + "sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c" ], "markers": "python_version < '3.4'", - "version": "==1.0.1" + "version": "==2.1.0" }, "pbr": { "hashes": [ - "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1", - "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac" + "sha256:8b9a7c3704657cb0831b3ded0a6b61377947c8235b76649bb7de4bfe2e6cfa56", + "sha256:cf66675e22ae91a4f20e4b8354f117d3e3d1de651513051d109cc39645fb3672" ], - "version": "==3.1.1" + "version": "==4.0.0" }, "pipenv": { "editable": true, @@ -204,6 +313,12 @@ ], "version": "==2.3.1" }, + "pycparser": { + "hashes": [ + "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226" + ], + "version": "==2.18" + }, "pyflakes": { "hashes": [ "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", @@ -218,6 +333,13 @@ ], "version": "==2.2.0" }, + "pyopenssl": { + "hashes": [ + "sha256:07a2de1a54de07448732a81e38a55df7da109b2f47f599f8bb35b0cbec69d4bd", + "sha256:2c10cfba46a52c0b0950118981d61e72c1e5b1aac451ca1bc77de1a679456773" + ], + "version": "==17.5.0" + }, "pytest": { "hashes": [ "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", @@ -328,10 +450,10 @@ }, "tqdm": { "hashes": [ - "sha256:05e991ecb0f874046ddcb374396a626afd046fb4d31f73633ea752b844458a7a", - "sha256:2aea9f81fdf127048667e0ba22f5fc10ebc879fb838dc52dcf055242037ec1f7" + "sha256:782aa84b61a5246c4f9e5b938875009e0b759d9a5c9d16b12e4f8deefdff7892", + "sha256:eaf9c32a2cf7b9623eb272134b934eb354ef6304299611aa71e3cf47cf83c22b" ], - "version": "==4.19.8" + "version": "==4.19.9" }, "twine": { "hashes": [ diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..d268576b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,77 @@ +build: off +version: 1.0.{build} + +skip_branch_with_pr: true + +init: + + - git config --global core.sharedRepository true + - git config --global core.longpaths true + - git config --global core.autocrlf input + +environment: + + PYPI_VENDOR_DIR: ".\\tests\\pypi\\" + GIT_ASK_YESNO: "false" + SHELL: "windows" + PYTHON_ARCH: "64" + PYTHONIOENCODING: "utf-8" + + matrix: + + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" + TEST_SUITE: "cli" + + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" + TEST_SUITE: '"dotvenv or check or unused or requirements"' + + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" + TEST_SUITE: "complex" + + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" + TEST_SUITE: '"markers or run or project or utils"' + + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" + TEST_SUITE: "install" + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + TEST_SUITE: "cli" + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + TEST_SUITE: '"dotvenv or check or unused or requirements"' + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + TEST_SUITE: "complex" + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + TEST_SUITE: '"markers or run or project or utils"' + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "2.7.x" + TEST_SUITE: "install" + +install: + - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% + - "%PYTHON%\\python.exe -m pip install --upgrade pip" + - "%PYTHON%\\python.exe -m pip install -e ." + - "%PYTHON%\\python.exe -m pipenv install -e ." + - "%PYTHON%\\python.exe -m pipenv install --dev" + - echo "Running pipenv at:" + - "%PYTHON%\\python.exe -m pipenv --venv" + - echo "Using python:" + - "%PYTHON%\\python.exe -m pipenv --py" + +cache: + - "%LocalAppData%\\pip\\cache" + +test_script: + - cmd: set PYPI_VENDOR_DIR=".\tests\pypi\" && %PYTHON%\\python.exe -m pipenv run pytest -v -n auto -m %TEST_SUITE% tests diff --git a/pipenv/cli.py b/pipenv/cli.py index 449cd3c8..7baa3c6e 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -11,7 +11,7 @@ from click_didyoumean import DYMCommandCollection from .__version__ import __version__ -from .import environments +from . import environments from .environments import * # Enable shell completion. @@ -23,7 +23,7 @@ class PipenvGroup(click.Group): """Custom Group class provides formatted main help""" def get_help_option(self, ctx): - from .import core + from . import core """Override for showing formatted main help via --help and -h options""" help_options = self.get_help_option_names(ctx) @@ -148,7 +148,7 @@ def cli( ) sys.exit(1) sys.exit(0) - from .import core + from . import core if man: if core.system_which('man'): path = os.sep.join([os.path.dirname(__file__), 'pipenv.1']) @@ -344,7 +344,7 @@ def install( keep_outdated=False, selective_upgrade=False, ): - from .import core + from . import core core.do_install( package_name=package_name, @@ -426,7 +426,7 @@ def uninstall( verbose=False, keep_outdated=False, ): - from .import core + from . import core core.do_uninstall( package_name=package_name, @@ -499,7 +499,7 @@ def lock( pre=False, keep_outdated=False, ): - from .import core + from . import core # Ensure that virtualenv is available. core.ensure_project(three=three, python=python) @@ -542,7 +542,7 @@ def lock( def shell( three=None, python=False, fancy=False, shell_args=None, anyway=False ): - from .import core + from . import core # Prevent user from activating nested environments. if 'PIPENV_ACTIVE' in os.environ: @@ -594,7 +594,7 @@ def shell( help="Specify which version of Python virtualenv should use.", ) def run(command, args, three=None, python=False): - from .import core + from . import core core.do_run(command=command, args=args, three=three, python=python) @@ -633,7 +633,7 @@ def check( style=False, args=None, ): - from .import core + from . import core core.do_check( three=three, python=python, system=system, unused=unused, args=args @@ -719,7 +719,7 @@ def update( outdated=False, more_packages=None, ): - from .import core + from . import core core.ensure_project(three=three, python=python, warn=True) if not outdated: @@ -797,7 +797,7 @@ def update( '--reverse', is_flag=True, default=False, help="Reversed dependency graph." ) def graph(bare=False, json=False, reverse=False): - from .import core + from . import core core.do_graph(bare=bare, json=json, reverse=reverse) @@ -817,7 +817,7 @@ def graph(bare=False, json=False, reverse=False): ) @click.argument('module', nargs=1) def run_open(module, three=None, python=None): - from .import core + from . import core # Ensure that virtualenv is available. core.ensure_project(three=three, python=python, validate=False) @@ -895,7 +895,7 @@ def sync( package_name=None, sequential=False, ): - from .import core + from . import core core.do_sync( ctx=ctx, @@ -952,7 +952,7 @@ def clean( user=False, verbose=False, ): - from .import core + from . import core core.do_clean( ctx=ctx, three=three, python=python, dry_run=dry_run, verbose=verbose diff --git a/pipenv/core.py b/pipenv/core.py index 9967eed0..1a0deca8 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -25,7 +25,8 @@ from blindspin import spinner from requests.packages import urllib3 from requests.packages.urllib3.exceptions import InsecureRequestWarning -from .project import Project +from.project import Project +from .requirements import PipenvRequirement from .utils import ( convert_deps_from_pip, convert_deps_to_pip, @@ -1399,9 +1400,9 @@ def pip_install( f.write(package_name) # Install dependencies when a package is a VCS dependency. try: - req = get_requirement( + req = PipenvRequirement.from_line( package_name.split('--hash')[0].split('--trusted-host')[0] - ).vcs + ).requirement.vcs except (pip9._vendor.pyparsing.ParseException, ValueError) as e: click.echo('{0}: {1}'.format(crayons.red('WARNING'), e), err=True) click.echo( @@ -2198,13 +2199,16 @@ def inline_activate_virtualenv(): def do_run_nt(command, args): """Run command by appending space-joined args to it!""" import subprocess + try: + from shlex import quote as shellquote + except ImportError: + from pipenv.vendor.backports.shlex import quote as shellquote command = project.scripts.get(command, command) + command = [command] + [shellquote(a) for a in args] # if you've passed something with crazy quoting... # ...just don't. (or put it in a script!) - p = subprocess.Popen( - ' '.join([command] + list(args)), shell=True, universal_newlines=True - ) + p = subprocess.Popen(command, shell=True, universal_newlines=True) p.communicate() sys.exit(p.returncode) @@ -2486,7 +2490,8 @@ def do_clean( ) installed_package_names = [] for installed in installed_packages: - r = get_requirement(installed) + pipenvreq = PipenvRequirement.from_line(installed) + r = pipenvreq.requirement # Ignore editable installations. if not r.editable: installed_package_names.append(r.name.lower()) diff --git a/pipenv/patched/pew/_utils.py b/pipenv/patched/pew/_utils.py index d1d8e0ac..c45c37e0 100644 --- a/pipenv/patched/pew/_utils.py +++ b/pipenv/patched/pew/_utils.py @@ -6,7 +6,10 @@ from contextlib import contextmanager from subprocess import check_call, Popen, PIPE from collections import namedtuple from functools import partial, wraps -from pathlib import Path +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path from tempfile import NamedTemporaryFile as _ntf try: from shutil import which diff --git a/pipenv/patched/pew/pew.py b/pipenv/patched/pew/pew.py index c5c3b2b5..2000ed7e 100644 --- a/pipenv/patched/pew/pew.py +++ b/pipenv/patched/pew/pew.py @@ -8,7 +8,10 @@ import random import textwrap from functools import partial from subprocess import CalledProcessError -from pathlib import Path +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path try: from shutil import get_terminal_size diff --git a/pipenv/requirements.py b/pipenv/requirements.py new file mode 100644 index 00000000..cf733f57 --- /dev/null +++ b/pipenv/requirements.py @@ -0,0 +1,609 @@ +# -*- coding=utf-8 -*- +from __future__ import absolute_import +import sys +from pipenv import PIPENV_VENDOR, PIPENV_PATCHED + +sys.path.insert(0, PIPENV_VENDOR) +sys.path.insert(0, PIPENV_PATCHED) +import hashlib +import os +import requirements +import six +from attr import attrs, attrib, Factory, validators +from collections import defaultdict +from pip9.index import Link +from pip9.download import path_to_url, url_to_path +from pip9.req.req_install import _strip_extras +from pip9._vendor.distlib.markers import Evaluator +from pipenv.utils import SCHEME_LIST, VCS_LIST, is_installable_file, is_vcs, multi_split, get_converted_relative_path, is_star, is_pinned, is_valid_url +from first import first + +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path + +HASH_STRING = ' --hash={0}' + + +def _validate_vcs(instance, attr, value): + if value not in VCS_LIST: + raise ValueError('Invalid vcs {0}'.format(value)) + +_optional_instance_of = lambda cls: validators.optional(validators.instance_of(cls)) + + +@attrs +class Source(object): + #: URL to PyPI instance + url = attrib(default='') + #: If False, skip SSL checks + verify_ssl = attrib(default=True, validator=validators.optional(validators.instance_of(bool))) + #: human name to refer to this source (can be referenced in packages or dev-packages) + name = attrib(default='') + + +@attrs +class Requires(object): + """System-level requirements - see PEP508 for more detail""" + os_name = attrib(default=None) + sys_platform = attrib(default=None) + platform_machine = attrib(default=None) + platform_python_implementation = attrib(default=None) + platform_release = attrib(default=None) + platform_system = attrib(default=None) + platform_version = attrib(default=None) + python_version = attrib(default=None) + python_full_version = attrib(default=None) + implementation_name = attrib(default=None) + implementation_version = attrib(default=None) + + +@attrs +class VCSRequirement(object): + #: vcs reference name (branch / commit / tag) + ref = attrib(default=None) + #: path to hit - without any of the VCS prefixes (like git+ / http+ / etc) + uri = attrib(default=None) + subdirectory = attrib(default=None) + vcs = attrib(validator=validators.optional(_validate_vcs), default=None) + + +@attrs +class PipfileRequirement(object): + path = attrib(default=None) + uri = attrib(default=None) + name = attrib(default=None) + extras = attrib(default=Factory(list)) + markers = attrib(default='') + editable = attrib(default=False) + vcs = attrib(validator=validators.optional(_validate_vcs), default=None) + version = attrib(default='') + index = attrib(default=None) + _hash = attrib(default=None) + hashes = attrib(default=Factory(list)) + ref = attrib(default=None) + subdirectory = attrib(default=None) + _link = attrib() + + @_link.default + def _init_link(self): + if not self.vcs: + return None + + uri = self.uri if self.uri else path_to_url(os.path.abspath(self.path)) + return build_vcs_link( + self.vcs, + uri, + name=self.name, + ref=self.ref, + subdirectory=self.subdirectory, + ) + + def __attrs_post_init__(self): + if self._hash and not self.hashes: + self.hashes = [self._hash] + if self.vcs and self.uri and self._link: + self.uri = _strip_ssh_from_git_uri(self._link.url) + + @classmethod + def create(cls, name, pipfile): + _pipfile = {} + if hasattr(pipfile, 'keys'): + _pipfile = dict(pipfile).copy() + _pipfile['name'] = name + _pipfile['version'] = cls._get_version(pipfile) + editable = _pipfile.pop( + 'editable' + ) if 'editable' in _pipfile else False + vcs_type = first([vcs for vcs in VCS_LIST if vcs in _pipfile]) + vcs = _pipfile.pop(vcs_type) if vcs_type else None + _pipfile_vcs_key = None + if vcs: + _pipfile_vcs_key = 'uri' if is_valid_url(vcs) else 'path' + _pipfile['editable'] = editable + _pipfile['vcs'] = vcs_type + if _pipfile_vcs_key and not _pipfile.get(_pipfile_vcs_key): + _pipfile[_pipfile_vcs_key] = vcs + markers = _pipfile.get('markers') + _extra_markers = [k for k in _pipfile.keys() if k in Evaluator.allowed_values.keys()] + if _extra_markers: + markers = list(markers) if markers else [] + for marker in _extra_markers: + markers.append(_pipfile.pop(marker)) + _pipfile['markers'] = ' and '.join(markers) + return cls(**_pipfile) + + @staticmethod + def _get_version(pipfile_entry): + if str(pipfile_entry) == '{}' or is_star(pipfile_entry): + return '' + + elif isinstance(pipfile_entry, six.string_types): + return pipfile_entry + + return pipfile_entry.get('version', '') + + @property + def pip_version(self): + if is_star(self.version): + return self.name + + return '{0}{1}'.format(self.name, self.version) + + @property + def requirement(self): + req_uri = self.uri + if self.editable and self.path and not self.uri: + req_uri = path_to_url(os.path.abspath(self.path)) + return PipenvRequirement._create_requirement( + name=self.pip_version, + path=self.path, + uri=req_uri, + markers=self.markers, + extras=self.extras, + index=self.index, + hashes=self.hashes, + vcs=self.vcs, + editable=self.editable, + link=self._link, + line=None, + ) + + +@attrs +class PipenvRequirement(object): + """Requirement for Pipenv Use + + Provides the following methods: + - as_pipfile + - as_lockfile + - as_requirement + - from_line + - from_pipfile + - resolve + """ + _editable_prefix = '-e ' + path = attrib(default=None) + uri = attrib(default=None) + name = attrib(default=None) + extras = attrib(default=Factory(list)) + markers = attrib(default='') + editable = attrib(default=False) + vcs = attrib(validator=validators.optional(_validate_vcs), default=None) + link = attrib(default=None) + line = attrib(default=None) + requirement = attrib(default=None) + index = attrib(default=Factory(list)) + specs = attrib(default='') + hashes = attrib(default=Factory(list)) + + @classmethod + def create(cls, req): + creation_attrs = {'requirement': req} + for prop in [ + 'name', + 'extras', + 'markers', + 'line', + 'link', + 'vcs', + 'editable', + 'uri', + 'path', + 'hashes', + 'index', + ]: + creation_attrs[prop] = getattr(req, prop, None) + return cls(**creation_attrs) + + @property + def original_line(self): + _editable = '' + if self.editable: + _editable += self._editable_prefix + if self.line and (self.path or self.uri): + # original_line adds in -e if necessary + if self.line.startswith(self._editable_prefix): + return self.line + + return '{0}{1}'.format(_editable, self.line) + + return self.constructed_line + + @property + def constructed_line(self): + _editable = '' + if self.editable: + _editable += self._editable_prefix + line = '' + if self.link: + line = '{0}{1}'.format(_editable, self.link.url) + elif self.path or self.uri: + line = '{0}{1}'.format(_editable, self.path or self.uri) + else: + line += self.name + if not self.vcs: + line = '{0}{1}{2}{3}{4}'.format( + line, + self.extras_as_pip, + self.specifiers_as_pip, + self.markers_as_pip, + self.hashes_as_pip, + ) + else: + line = self.line + if _editable == self._editable_prefix and not self.line.startswith( + _editable + ): + line = '{0}{1}'.format(_editable, self.line) + line = '{0}{1}{2}{3}'.format( + line, + self.extras_as_pip, + self.markers_as_pip, + self.hashes_as_pip, + ) + return line + + @property + def extras_as_pip(self): + if self.extras: + return '[{0}]'.format(','.join(self.extras)) + + return '' + + @property + def markers_as_pip(self): + if self.markers: + return '; {0}'.format(self.markers) + + return '' + + @property + def specifiers_as_pip(self): + if hasattr(self.requirement, 'specs'): + return ','.join([''.join(spec) for spec in self.requirement.specs]) + + return '' + + @property + def hashes_as_pip(self): + if self.hashes: + if isinstance(self.hashes, six.string_types): + return HASH_STRING.format(self.hashes) + + return ''.join([HASH_STRING.format(h) for h in self.hashes]) + + return '' + + @classmethod + def from_pipfile(cls, name, indexes, pipfile_entry): + pipfile = PipfileRequirement.create(name, pipfile_entry) + return cls.create(pipfile.requirement) + + @classmethod + def from_line(cls, line): + """Pre-clean requirement strings passed to the requirements parser. + + Ensures that we can accept both local and relative paths, file and VCS URIs, + remote URIs, and package names, and that we pass only valid requirement strings + to the requirements parser. Performs necessary modifications to requirements + object if the user input was a local relative path. + + :param str dep: A requirement line + :returns: :class:`requirements.Requirement` object + """ + hashes = None + if '--hash=' in line: + hashes = line.split(' --hash=') + line, hashes = hashes[0], hashes[1:] + editable = False + original_line = line + _editable = '' + if line.startswith('-e '): + editable = True + _editable += cls._editable_prefix + line = line.split(' ', 1)[1] + line, markers = cls._split_markers(line) + line, extras = _strip_extras(line) + req_dict = defaultdict(None) + vcs = None + if is_installable_file(line): + req_dict = cls._prep_path(line) + req_dict['original_line'] = '{0}{1}'.format( + _editable, req_dict['original_line'] + ) + elif is_vcs(line): + req_dict = cls._prep_vcs(line) + vcs = first( + _split_vcs_method( + req_dict.get('uri', req_dict.get('path', line)) + ) + ) + req_dict['original_line'] = '{0}{1}'.format( + _editable, req_dict['original_line'] + ) + else: + req_dict = { + 'line': line, + 'original_line': original_line, + 'name': multi_split(line, '!=<>~')[0], + } + return cls.create( + cls._create_requirement( + line=req_dict['original_line'], + name=req_dict.get('name'), + path=req_dict.get('path'), + uri=req_dict.get('uri'), + link=req_dict.get('link'), + hashes=hashes, + markers=markers, + extras=extras, + editable=editable, + vcs=vcs, + ) + ) + + def as_pipfile(self): + """"Converts a requirement to a Pipfile-formatted one.""" + req_dict = {} + req = self.requirement + req_dict = {} + if req.local_file: + hashable_path = req.uri or req.path + dict_key = 'file' if req.uri else 'path' + hashed_path = hashlib.sha256( + hashable_path.encode('utf-8') + ).hexdigest( + ) + req_dict[dict_key] = hashable_path + req_dict['name'] = hashed_path[ + len(hashed_path) - 7: + ] if not req.vcs else req.name + elif req.vcs: + if req.name is None: + raise ValueError( + 'pipenv requires an #egg fragment for version controlled ' + 'dependencies. Please install remote dependency ' + 'in the form {0}#egg=.'.format(req.uri) + ) + + if req.uri and req.uri.startswith('{0}+'.format(req.vcs)): + if req_dict.get('uri'): + # req_dict['uri'] = req.uri[len(req.vcs) + 1:] + del req_dict['uri'] + req_dict.update( + { + req.vcs: req.uri[ + len(req.vcs) + 1: + ] if req.uri else req.path + } + ) + if req.subdirectory: + req_dict.update({'subdirectory': req.subdirectory}) + if req.revision: + req_dict.update({'ref': req.revision}) + elif req.specs: + # Comparison operators: e.g. Django>1.10 + specs = ','.join([''.join(spec) for spec in req.specs]) + req_dict.update({'version': specs}) + else: + req_dict.update({'version': '*'}) + if self.extras: + req_dict.update({'extras': self.extras}) + if req.editable: + req_dict.update({'editable': req.editable}) + if self.hashes: + hash_key = 'hashes' + hashes = self.hashes + if isinstance(hashes, six.string_types) or len(hashes) == 1: + hash_key = 'hash' + if len(hashes) == 1: + hashes = first(hashes) + req_dict.update({hash_key: hashes}) + if len(req_dict.keys()) == 1 and req_dict.get('version'): + return {req.name: req_dict.get('version')} + + return {req.name: req_dict} + + def as_requirement(self, project=None, include_index=False): + """Creates a requirements.txt compatible output of the current dependency. + + :param project: Pipenv Project, defaults to None + :param project: :class:`pipenv.project.Project`, optional + :param include_index: Whether to include the resolved index, defaults to False + :param include_index: bool, optional + """ + line = self.constructed_line + if include_index and not (self.requirement.local_file or self.vcs): + from .utils import prepare_pip_source_args + + if self.index: + pip_src_args = [project.get_source(self.index)] + else: + pip_src_args = project.sources + index_string = ' '.join(prepare_pip_source_args(pip_src_args)) + line = '{0} {1}'.format(line, index_string) + return line + + @staticmethod + def _split_markers(line): + """Split markers from a dependency""" + if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST): + marker_sep = ';' + else: + marker_sep = '; ' + markers = None + if marker_sep in line: + line, markers = line.split(marker_sep, 1) + markers = markers.strip() if markers else None + return line, markers + + @staticmethod + def _prep_path(line): + _path = Path(line) + link = Link(_path.absolute().as_uri()) + if _path.is_absolute() or _path.as_posix() == '.': + path = _path.as_posix() + else: + path = get_converted_relative_path(line) + name_or_url = link.egg_fragment if link.egg_fragment else link.url_without_fragment + name = link.egg_fragment or link.show_url or link.filename + return { + 'link': link, + 'path': path, + 'line': name_or_url, + 'original_line': line, + 'name': name, + } + + @staticmethod + def _prep_vcs(line): + # Generate a Link object for parsing egg fragments + link = Link(line) + # Save the original path to store in the pipfile + original_uri = link.url + # Construct the requirement using proper git+ssh:// replaced uris or names if available + formatted_line = _clean_git_uri(line) + return { + 'link': link, + 'uri': formatted_line, + 'line': original_uri, + 'original_line': line, + 'name': link.egg_fragment, + } + + @classmethod + def _create_requirement( + cls, + line=None, + name=None, + path=None, + uri=None, + extras=None, + markers=None, + editable=False, + vcs=None, + link=None, + hashes=None, + index=None, + ): + _editable = cls._editable_prefix if editable else '' + _line = line or uri or path or name + # We don't want to only use the name on properly + # formatted VCS inputs + if vcs or is_vcs(_line): + _line = uri or path or line + _line = '{0}{1}'.format(_editable, _line) + elif link and path and not uri: + _line = link.url + req = first(requirements.parse(_line)) + req.line = _line + if editable: + req.editable = True + if req.name and not any( + getattr(req, prop) for prop in ['uri', 'path'] + ): + if link and link.scheme.startswith('file') and path: + req.path = path + req.local_file = True + elif link and uri: + req.uri = link.url_without_fragment + elif req.local_file and path and not req.vcs: + req.uri = None + req.path = path + elif req.vcs and not req.local_file and uri != link.url: + req.uri = _strip_ssh_from_git_uri(req.uri) + req.line = line or _strip_ssh_from_git_uri(req.line) + if markers: + req.markers = markers + if extras: + # Bizarrely this is also what pip does... + req.extras = first( + requirements.parse( + 'fakepkg{0}'.format(_extras_to_string(extras)) + ) + ).extras + req.link = link + if hashes: + req.hashes = hashes + if index: + req.index = index + return req + + +def _strip_ssh_from_git_uri(uri): + """Return git+ssh:// formatted URI to git+git@ format""" + if isinstance(uri, six.string_types): + uri = uri.replace('git+ssh://', 'git+') + return uri + + +def _clean_git_uri(uri): + """Cleans VCS uris from pip9 format""" + if isinstance(uri, six.string_types): + # Add scheme for parsing purposes, this is also what pip does + if uri.startswith('git+') and '://' not in uri: + uri = uri.replace('git+', 'git+ssh://') + return uri + + +def _split_vcs_method(uri): + """Split a vcs+uri formatted uri into (vcs, uri)""" + vcs_start = '{0}+' + vcs = first( + [vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))] + ) + if vcs: + vcs, uri = uri.split('+', 1) + return vcs, uri + + +def _extras_to_string(extras): + """Turn a list of extras into a string""" + if isinstance(extras, six.string_types): + if extras.startswith('['): + return extras + + else: + extras = [extras] + return '[{0}]'.format(','.join(extras)) + + +def build_vcs_link( + vcs, uri, name=None, ref=None, subdirectory=None, extras= [] +): + vcs_start = '{0}+'.format(vcs) + if not uri.startswith(vcs_start): + uri = '{0}{1}'.format(vcs_start, uri) + uri = _clean_git_uri(uri) + if ref: + uri = '{0}@{1}'.format(uri, ref) + if name: + uri = '{0}#egg={1}'.format(uri, name) + if extras: + extras = _extras_to_string(extras) + uri = '{0}{1}'.format(uri, extras) + if subdirectory: + uri = '{0}&subdirectory={1}'.format(uri, subdirectory) + return Link(uri) diff --git a/pipenv/utils.py b/pipenv/utils.py index 7d60e440..3ab9b945 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -65,8 +65,6 @@ requests = requests.Session() def get_requirement(dep): - from pip9.req.req_install import _strip_extras - import requirements """Pre-clean requirement strings passed to the requirements parser. @@ -78,79 +76,8 @@ def get_requirement(dep): :param str dep: A requirement line :returns: :class:`requirements.Requirement` object """ - path = None - uri = None - cleaned_uri = None - editable = False - dep_link = None - # check for editable dep / vcs dep - if dep.startswith('-e '): - editable = True - # Use the user supplied path as the written dependency - dep = dep.split(' ', 1)[1] - # Split out markers if they are present - similar to how pip does it - # See pip9.req.req_install.InstallRequirement.from_line - if not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST): - marker_sep = ';' - else: - marker_sep = '; ' - if marker_sep in dep: - dep, markers = dep.split(marker_sep, 1) - markers = markers.strip() - if not markers: - markers = None - else: - markers = None - # Strip extras from the requirement so we can make a properly parseable req - dep, extras = _strip_extras(dep) - # Only operate on local, existing, non-URI formatted paths which are installable - if is_installable_file(dep): - dep_path = Path(dep) - dep_link = Link(dep_path.absolute().as_uri()) - if dep_path.is_absolute() or dep_path.as_posix() == '.': - path = dep_path.as_posix() - else: - path = get_converted_relative_path(dep) - dep = dep_link.egg_fragment if dep_link.egg_fragment else dep_link.url_without_fragment - elif is_vcs(dep): - # Generate a Link object for parsing egg fragments - dep_link = Link(dep) - # Save the original path to store in the pipfile - uri = dep_link.url - # Construct the requirement using proper git+ssh:// replaced uris or names if available - cleaned_uri = clean_git_uri(dep) - dep = cleaned_uri - if editable: - dep = '-e {0}'.format(dep) - req = [r for r in requirements.parse(dep)][0] - # if all we built was the requirement name and still need everything else - if req.name and not any([req.uri, req.path]): - if dep_link: - if dep_link.scheme.startswith('file') and path and not req.path: - req.path = path - req.local_file = True - req.uri = None - else: - req.uri = dep_link.url_without_fragment - # If the result is a local file with a URI and we have a local path, unset the URI - # and set the path instead -- note that local files may have 'path' set by accident - elif req.local_file and path and not req.vcs: - req.path = path - req.uri = None - elif req.vcs and req.uri and cleaned_uri and cleaned_uri != uri: - req.uri = strip_ssh_from_git_uri(req.uri) - req.line = strip_ssh_from_git_uri(req.line) - req.editable = editable - if markers: - req.markers = markers - if extras: - # Bizarrely this is also what pip does... - req.extras = [ - r for r in requirements.parse('fakepkg{0}'.format(extras)) - ][ - 0 - ].extras - return req + from .requirements import PipenvRequirement + return PipenvRequirement.from_line(dep).requirement def cleanup_toml(tml): @@ -359,7 +286,7 @@ def actually_resolve_reps( def venv_resolve_deps( deps, which, project, pre=False, verbose=False, clear=False ): - from .import resolver + from . import resolver import json resolver = escape_grouped_arguments(resolver.__file__.rstrip('co')) @@ -514,73 +441,10 @@ def multi_split(s, split): def convert_deps_from_pip(dep): """"Converts a pip-formatted dependency to a Pipfile-formatted one.""" + from .requirements import PipenvRequirement dependency = {} - req = get_requirement(dep) - extras = {'extras': req.extras} - # File installs. - if (req.uri or req.path or is_installable_file(req.name)) and not req.vcs: - # Assign a package name to the file, last 7 of it's sha256 hex digest. - if not req.uri and not req.path: - req.path = os.path.abspath(req.name) - hashable_path = req.uri if req.uri else req.path - req.name = hashlib.sha256(hashable_path.encode('utf-8')).hexdigest() - req.name = req.name[len(req.name) - 7:] - # {path: uri} TOML (spec 4 I guess...) - if req.uri: - dependency[req.name] = {'file': hashable_path} - else: - dependency[req.name] = {'path': hashable_path} - if req.extras: - dependency[req.name].update(extras) - # Add --editable if applicable - if req.editable: - dependency[req.name].update({'editable': True}) - # VCS Installs. - elif req.vcs: - if req.name is None: - raise ValueError( - 'pipenv requires an #egg fragment for version controlled ' - 'dependencies. Please install remote dependency ' - 'in the form {0}#egg=.'.format(req.uri) - ) - - # Crop off the git+, etc part. - if req.uri.startswith('{0}+'.format(req.vcs)): - req.uri = req.uri[len(req.vcs) + 1:] - dependency.setdefault(req.name, {}).update({req.vcs: req.uri}) - # Add --editable, if it's there. - if req.editable: - dependency[req.name].update({'editable': True}) - # Add subdirectory, if it's there - if req.subdirectory: - dependency[req.name].update({'subdirectory': req.subdirectory}) - # Add the specifier, if it was provided. - if req.revision: - dependency[req.name].update({'ref': req.revision}) - # Extras: e.g. #egg=requests[security] - if req.extras: - dependency[req.name].update({'extras': req.extras}) - elif req.extras or req.specs: - specs = None - # Comparison operators: e.g. Django>1.10 - if req.specs: - r = multi_split(dep, '!=<>~') - specs = dep[len(r[0]):] - dependency[req.name] = specs - # Extras: e.g. requests[socks] - if req.extras: - dependency[req.name] = extras - if specs: - dependency[req.name].update({'version': specs}) - # Bare dependencies: e.g. requests - else: - dependency[dep] = '*' - # Cleanup when there's multiple values, e.g. -e. - if len(dependency) > 1: - for key in dependency.copy(): - if not hasattr(dependency[key], 'keys'): - del dependency[key] - return dependency + req = PipenvRequirement.from_line(dep) + return req.as_pipfile() def is_star(val): @@ -593,95 +457,16 @@ def is_pinned(val): def convert_deps_to_pip(deps, project=None, r=True, include_index=False): """"Converts a Pipfile-formatted dependency to a pip-formatted one.""" + from .requirements import PipenvRequirement dependencies = [] - for dep in deps.keys(): - # Default (e.g. '>1.10'). - extra = deps[dep] if isinstance(deps[dep], six.string_types) else '' - version = '' - index = '' - # Get rid of '*'. - if is_star(deps[dep]) or str(extra) == '{}': - extra = '' - hash = '' - # Support for single hash (spec 1). - if 'hash' in deps[dep]: - hash = ' --hash={0}'.format(deps[dep]['hash']) - # Support for multiple hashes (spec 2). - if 'hashes' in deps[dep]: - hash = '{0} '.format( - ''.join( - [' --hash={0} '.format(h) for h in deps[dep]['hashes']] - ) - ) - # Support for extras (e.g. requests[socks]) - if 'extras' in deps[dep]: - extra = '[{0}]'.format(','.join(deps[dep]['extras'])) - if 'version' in deps[dep]: - if not is_star(deps[dep]['version']): - version = deps[dep]['version'] - # For lockfile format. - if 'markers' in deps[dep]: - specs = '; {0}'.format(deps[dep]['markers']) - else: - # For pipfile format. - specs = [] - for specifier in specifiers: - if specifier in deps[dep]: - if not is_star(deps[dep][specifier]): - specs.append( - '{0} {1}'.format(specifier, deps[dep][specifier]) - ) - if specs: - specs = '; {0}'.format(' and '.join(specs)) - else: - specs = '' - if include_index and not is_file(deps[dep]) and not is_vcs(deps[dep]): - pip_src_args = [] - if 'index' in deps[dep]: - pip_src_args = [project.get_source(deps[dep]['index'])] - else: - pip_src_args = project.sources - pip_args = prepare_pip_source_args(pip_src_args) - index = ' '.join(pip_args) - # Support for version control - maybe_vcs = [vcs for vcs in VCS_LIST if vcs in deps[dep]] - vcs = maybe_vcs[0] if maybe_vcs else None - # Support for files. - if 'file' in deps[dep]: - extra = '{1}{0}'.format(extra, deps[dep]['file']).strip() - # Flag the file as editable if it is a local relative path - if 'editable' in deps[dep]: - dep = '-e ' - else: - dep = '' - # Support for paths. - elif 'path' in deps[dep]: - extra = '{1}{0}'.format(extra, deps[dep]['path']).strip() - # Flag the file as editable if it is a local relative path - if 'editable' in deps[dep]: - dep = '-e ' - else: - dep = '' - if vcs: - extra = '{0}+{1}'.format(vcs, deps[dep][vcs]) - # Support for @refs. - if 'ref' in deps[dep]: - extra += '@{0}'.format(deps[dep]['ref']) - extra += '#egg={0}'.format(dep) - # Support for subdirectory - if 'subdirectory' in deps[dep]: - extra += '&subdirectory={0}'.format(deps[dep]['subdirectory']) - # Support for editable. - if 'editable' in deps[dep]: - # Support for --egg. - dep = '-e ' - else: - dep = '' - s = '{0}{1}{2}{3}{4} {5}'.format( - dep, extra, version, specs, hash, index - ).strip( - ) - dependencies.append(s) + for dep_name, dep in deps.items(): + indexes = project.sources if hasattr(project, 'sources') else None + if hasattr(dep, 'keys') and dep.get('index'): + indexes = project.get_source(dep['index']) + new_dep = PipenvRequirement.from_pipfile(dep_name, indexes, dep) + req = new_dep.as_requirement(project=project, include_index=include_index) + req = req.strip() + dependencies.append(req) if not r: return dependencies @@ -754,7 +539,7 @@ def is_editable(pipfile_entry): def is_vcs(pipfile_entry): - import requirements + from pipenv.vendor import requirements """Determine if dictionary entry from Pipfile is for a vcs dependency.""" if hasattr(pipfile_entry, 'keys'): @@ -1019,6 +804,10 @@ def find_windows_executable(bin_path, exe_name): return find_executable(exe_name) +def path_to_url(path): + return Path(normalize_drive(os.path.abspath(path))).as_uri() + + def get_converted_relative_path(path, relative_to=os.curdir): """Given a vague relative path, return the path relative to the given location""" return os.path.join('.', os.path.relpath(path, start=relative_to)) diff --git a/pipenv/vendor/attr/__init__.py b/pipenv/vendor/attr/__init__.py new file mode 100644 index 00000000..ea330007 --- /dev/null +++ b/pipenv/vendor/attr/__init__.py @@ -0,0 +1,57 @@ +from __future__ import absolute_import, division, print_function + +from functools import partial + +from . import converters, exceptions, filters, validators +from ._config import get_run_validators, set_run_validators +from ._funcs import asdict, assoc, astuple, evolve, has +from ._make import ( + NOTHING, Attribute, Factory, attrib, attrs, fields, fields_dict, + make_class, validate +) + + +__version__ = "18.1.0.dev0" + +__title__ = "attrs" +__description__ = "Classes Without Boilerplate" +__uri__ = "http://www.attrs.org/" +__doc__ = __description__ + " <" + __uri__ + ">" + +__author__ = "Hynek Schlawack" +__email__ = "hs@ox.cx" + +__license__ = "MIT" +__copyright__ = "Copyright (c) 2015 Hynek Schlawack" + + +s = attributes = attrs +ib = attr = attrib +dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + +__all__ = [ + "Attribute", + "Factory", + "NOTHING", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "converters", + "evolve", + "exceptions", + "fields", + "fields_dict", + "filters", + "get_run_validators", + "has", + "ib", + "make_class", + "s", + "set_run_validators", + "validate", + "validators", +] diff --git a/pipenv/vendor/attr/_compat.py b/pipenv/vendor/attr/_compat.py new file mode 100644 index 00000000..42a91ee5 --- /dev/null +++ b/pipenv/vendor/attr/_compat.py @@ -0,0 +1,146 @@ +from __future__ import absolute_import, division, print_function + +import platform +import sys +import types +import warnings + + +PY2 = sys.version_info[0] == 2 +PYPY = platform.python_implementation() == "PyPy" + + +if PYPY or sys.version_info[:2] >= (3, 6): + ordered_dict = dict +else: + from collections import OrderedDict + ordered_dict = OrderedDict + + +if PY2: + from UserDict import IterableUserDict + + # We 'bundle' isclass instead of using inspect as importing inspect is + # fairly expensive (order of 10-15 ms for a modern machine in 2016) + def isclass(klass): + return isinstance(klass, (type, types.ClassType)) + + # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. + TYPE = "type" + + def iteritems(d): + return d.iteritems() + + # Python 2 is bereft of a read-only dict proxy, so we make one! + class ReadOnlyDict(IterableUserDict): + """ + Best-effort read-only dict wrapper. + """ + + def __setitem__(self, key, val): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError("'mappingproxy' object does not support item " + "assignment") + + def update(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'update'") + + def __delitem__(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError("'mappingproxy' object does not support item " + "deletion") + + def clear(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'clear'") + + def pop(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'pop'") + + def popitem(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'popitem'") + + def setdefault(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'setdefault'") + + def __repr__(self): + # Override to be identical to the Python 3 version. + return "mappingproxy(" + repr(self.data) + ")" + + def metadata_proxy(d): + res = ReadOnlyDict() + res.data.update(d) # We blocked update, so we have to do it like this. + return res + +else: + def isclass(klass): + return isinstance(klass, type) + + TYPE = "class" + + def iteritems(d): + return d.items() + + def metadata_proxy(d): + return types.MappingProxyType(dict(d)) + + +def import_ctypes(): + """ + Moved into a function for testability. + """ + import ctypes + return ctypes + + +if not PY2: + def just_warn(*args, **kw): + """ + We only warn on Python 3 because we are not aware of any concrete + consequences of not setting the cell on Python 2. + """ + warnings.warn( + "Missing ctypes. Some features like bare super() or accessing " + "__class__ will not work with slots classes.", + RuntimeWarning, + stacklevel=2, + ) +else: + def just_warn(*args, **kw): # pragma: nocover + """ + We only warn on Python 3 because we are not aware of any concrete + consequences of not setting the cell on Python 2. + """ + + +def make_set_closure_cell(): + """ + Moved into a function for testability. + """ + if PYPY: # pragma: no cover + def set_closure_cell(cell, value): + cell.__setstate__((value,)) + else: + try: + ctypes = import_ctypes() + + set_closure_cell = ctypes.pythonapi.PyCell_Set + set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object) + set_closure_cell.restype = ctypes.c_int + except Exception: + # We try best effort to set the cell, but sometimes it's not + # possible. For example on Jython or on GAE. + set_closure_cell = just_warn + return set_closure_cell + + +set_closure_cell = make_set_closure_cell() diff --git a/pipenv/vendor/attr/_config.py b/pipenv/vendor/attr/_config.py new file mode 100644 index 00000000..8ec92096 --- /dev/null +++ b/pipenv/vendor/attr/_config.py @@ -0,0 +1,23 @@ +from __future__ import absolute_import, division, print_function + + +__all__ = ["set_run_validators", "get_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + """ + if not isinstance(run, bool): + raise TypeError("'run' must be bool.") + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + """ + return _run_validators diff --git a/pipenv/vendor/attr/_funcs.py b/pipenv/vendor/attr/_funcs.py new file mode 100644 index 00000000..798043af --- /dev/null +++ b/pipenv/vendor/attr/_funcs.py @@ -0,0 +1,212 @@ +from __future__ import absolute_import, division, print_function + +import copy + +from ._compat import iteritems +from ._make import NOTHING, _obj_setattr, fields +from .exceptions import AttrsAttributeNotFoundError + + +def asdict(inst, recurse=True, filter=None, dict_factory=dict, + retain_collection_types=False): + """ + Return the ``attrs`` attribute values of *inst* as a dict. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the :class:`attr.Attribute` as the first argument and the + value as the second argument. + :param callable dict_factory: A callable to produce dictionaries from. For + example, to produce ordered dictionaries instead of normal Python + dictionaries, pass in ``collections.OrderedDict``. + :param bool retain_collection_types: Do not convert to ``list`` when + encountering an attribute whose type is ``tuple`` or ``set``. Only + meaningful if ``recurse`` is ``True``. + + :rtype: return type of *dict_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv[a.name] = asdict(v, recurse=True, filter=filter, + dict_factory=dict_factory) + elif isinstance(v, (tuple, list, set)): + cf = v.__class__ if retain_collection_types is True else list + rv[a.name] = cf([ + asdict(i, recurse=True, filter=filter, + dict_factory=dict_factory) + if has(i.__class__) else i + for i in v + ]) + elif isinstance(v, dict): + df = dict_factory + rv[a.name] = df(( + asdict(kk, dict_factory=df) if has(kk.__class__) else kk, + asdict(vv, dict_factory=df) if has(vv.__class__) else vv) + for kk, vv in iteritems(v)) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def astuple(inst, recurse=True, filter=None, tuple_factory=tuple, + retain_collection_types=False): + """ + Return the ``attrs`` attribute values of *inst* as a tuple. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the :class:`attr.Attribute` as the first argument and the + value as the second argument. + :param callable tuple_factory: A callable to produce tuples from. For + example, to produce lists instead of tuples. + :param bool retain_collection_types: Do not convert to ``list`` + or ``dict`` when encountering an attribute which type is + ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is + ``True``. + + :rtype: return type of *tuple_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv.append(astuple(v, recurse=True, filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain)) + elif isinstance(v, (tuple, list, set)): + cf = v.__class__ if retain is True else list + rv.append(cf([ + astuple(j, recurse=True, filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain) + if has(j.__class__) else j + for j in v + ])) + elif isinstance(v, dict): + df = v.__class__ if retain is True else dict + rv.append(df( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain + ) if has(kk.__class__) else kk, + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain + ) if has(vv.__class__) else vv + ) + for kk, vv in iteritems(v))) + else: + rv.append(v) + else: + rv.append(v) + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with ``attrs`` attributes. + + :param type cls: Class to introspect. + :raise TypeError: If *cls* is not a class. + + :rtype: :class:`bool` + """ + return getattr(cls, "__attrs_attrs__", None) is not None + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't + be found on *cls*. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. deprecated:: 17.1.0 + Use :func:`evolve` instead. + """ + import warnings + warnings.warn("assoc is deprecated and will be removed after 2018/01.", + DeprecationWarning, stacklevel=2) + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in iteritems(changes): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + raise AttrsAttributeNotFoundError( + "{k} is not an attrs attribute on {cl}." + .format(k=k, cl=new.__class__) + ) + _obj_setattr(new, k, v) + return new + + +def evolve(inst, **changes): + """ + Create a new instance, based on *inst* with *changes* applied. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise TypeError: If *attr_name* couldn't be found in the class + ``__init__``. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 17.1.0 + """ + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = attr_name if attr_name[0] != "_" else attr_name[1:] + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + return cls(**changes) diff --git a/pipenv/vendor/attr/_make.py b/pipenv/vendor/attr/_make.py new file mode 100644 index 00000000..ebe12078 --- /dev/null +++ b/pipenv/vendor/attr/_make.py @@ -0,0 +1,1680 @@ +from __future__ import absolute_import, division, print_function + +import hashlib +import linecache +import sys +import threading +import warnings + +from operator import itemgetter + +from . import _config +from ._compat import ( + PY2, isclass, iteritems, metadata_proxy, ordered_dict, set_closure_cell +) +from .exceptions import ( + DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError, + UnannotatedAttributeError +) + + +# This is used at least twice, so cache it here. +_obj_setattr = object.__setattr__ +_init_converter_pat = "__attr_converter_{}" +_init_factory_pat = "__attr_factory_{}" +_tuple_property_pat = " {attr_name} = property(itemgetter({index}))" +_empty_metadata_singleton = metadata_proxy({}) + + +class _Nothing(object): + """ + Sentinel class to indicate the lack of a value when ``None`` is ambiguous. + + All instances of `_Nothing` are equal. + """ + def __copy__(self): + return self + + def __deepcopy__(self, _): + return self + + def __eq__(self, other): + return other.__class__ == _Nothing + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return "NOTHING" + + def __hash__(self): + return 0xc0ffee + + +NOTHING = _Nothing() +""" +Sentinel to indicate the lack of a value when ``None`` is ambiguous. +""" + + +def attrib(default=NOTHING, validator=None, + repr=True, cmp=True, hash=None, init=True, + convert=None, metadata=None, type=None, converter=None, + factory=None): + """ + Create a new attribute on a class. + + .. warning:: + + Does *not* do anything unless the class is also decorated with + :func:`attr.s`! + + :param default: A value that is used if an ``attrs``-generated ``__init__`` + is used and no value is passed while instantiating or the attribute is + excluded using ``init=False``. + + If the value is an instance of :class:`Factory`, its callable will be + used to construct a new value (useful for mutable data types like lists + or dicts). + + If a default is not set (or set manually to ``attr.NOTHING``), a value + *must* be supplied when instantiating; otherwise a :exc:`TypeError` + will be raised. + + The default can also be set using decorator notation as shown below. + + :type default: Any value. + + :param callable factory: Syntactic sugar for + ``default=attr.Factory(callable)``. + + :param validator: :func:`callable` that is called by ``attrs``-generated + ``__init__`` methods after the instance has been initialized. They + receive the initialized instance, the :class:`Attribute`, and the + passed value. + + The return value is *not* inspected so the validator has to throw an + exception itself. + + If a ``list`` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + :func:`get_run_validators`. + + The validator can also be set using decorator notation as shown below. + + :type validator: ``callable`` or a ``list`` of ``callable``\ s. + + :param bool repr: Include this attribute in the generated ``__repr__`` + method. + :param bool cmp: Include this attribute in the generated comparison methods + (``__eq__`` et al). + :param hash: Include this attribute in the generated ``__hash__`` + method. If ``None`` (default), mirror *cmp*'s value. This is the + correct behavior according the Python spec. Setting this value to + anything else than ``None`` is *discouraged*. + :type hash: ``bool`` or ``None`` + :param bool init: Include this attribute in the generated ``__init__`` + method. It is possible to set this to ``False`` and set a default + value. In that case this attributed is unconditionally initialized + with the specified default value or factory. + :param callable converter: :func:`callable` that is called by + ``attrs``-generated ``__init__`` methods to converter attribute's value + to the desired format. It is given the passed-in value, and the + returned value will be used as the new value of the attribute. The + value is converted before being passed to the validator, if any. + :param metadata: An arbitrary mapping, to be used by third-party + components. See :ref:`extending_metadata`. + :param type: The type of the attribute. In Python 3.6 or greater, the + preferred method to specify the type is using a variable annotation + (see `PEP 526 `_). + This argument is provided for backward compatibility. + Regardless of the approach used, the type will be stored on + ``Attribute.type``. + + .. versionadded:: 15.2.0 *convert* + .. versionadded:: 16.3.0 *metadata* + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is ``None`` and therefore mirrors *cmp* by default. + .. versionadded:: 17.3.0 *type* + .. deprecated:: 17.4.0 *convert* + .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated + *convert* to achieve consistency with other noun-based arguments. + .. versionadded:: 18.1.0 + ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. + """ + if hash is not None and hash is not True and hash is not False: + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + + if convert is not None: + if converter is not None: + raise RuntimeError( + "Can't pass both `convert` and `converter`. " + "Please use `converter` only." + ) + warnings.warn( + "The `convert` argument is deprecated in favor of `converter`. " + "It will be removed after 2019/01.", + DeprecationWarning, stacklevel=2 + ) + converter = convert + + if factory is not None: + if default is not NOTHING: + raise ValueError( + "The `default` and `factory` arguments are mutually " + "exclusive." + ) + if not callable(factory): + raise ValueError( + "The `factory` argument must be a callable." + ) + default = Factory(factory) + + if metadata is None: + metadata = {} + + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=cmp, + hash=hash, + init=init, + converter=converter, + metadata=metadata, + type=type, + ) + + +def _make_attr_tuple_class(cls_name, attr_names): + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = "{}Attributes".format(cls_name) + attr_class_template = [ + "class {}(tuple):".format(attr_class_name), + " __slots__ = ()", + ] + if attr_names: + for i, attr_name in enumerate(attr_names): + attr_class_template.append(_tuple_property_pat.format( + index=i, + attr_name=attr_name, + )) + else: + attr_class_template.append(" pass") + globs = {"itemgetter": itemgetter} + eval(compile("\n".join(attr_class_template), "", "exec"), globs) + return globs[attr_class_name] + + +# Tuple class for extracted attributes from a class definition. +# `super_attrs` is a subset of `attrs`. +_Attributes = _make_attr_tuple_class("_Attributes", [ + "attrs", # all attributes to build dunder methods for + "super_attrs", # attributes that have been inherited + "super_attrs_map", # map inherited attributes to their originating classes +]) + + +def _is_class_var(annot): + """ + Check whether *annot* is a typing.ClassVar. + + The implementation is gross but importing `typing` is slow and there are + discussions to remove it from the stdlib alltogether. + """ + return str(annot).startswith("typing.ClassVar") + + +def _get_annotations(cls): + """ + Get annotations for *cls*. + """ + anns = getattr(cls, "__annotations__", None) + if anns is None: + return {} + + # Verify that the annotations aren't merely inherited. + for super_cls in cls.__mro__[1:]: + if anns is getattr(super_cls, "__annotations__", None): + return {} + + return anns + + +def _counter_getter(e): + """ + Key function for sorting to avoid re-creating a lambda for every class. + """ + return e[1].counter + + +def _transform_attrs(cls, these, auto_attribs): + """ + Transform all `_CountingAttr`s on a class into `Attribute`s. + + If *these* is passed, use that and don't look for them on the class. + + Return an `_Attributes`. + """ + cd = cls.__dict__ + anns = _get_annotations(cls) + + if these is not None: + ca_list = [ + (name, ca) + for name, ca + in iteritems(these) + ] + + if not isinstance(these, ordered_dict): + ca_list.sort(key=_counter_getter) + elif auto_attribs is True: + ca_names = { + name + for name, attr + in cd.items() + if isinstance(attr, _CountingAttr) + } + ca_list = [] + annot_names = set() + for attr_name, type in anns.items(): + if _is_class_var(type): + continue + annot_names.add(attr_name) + a = cd.get(attr_name, NOTHING) + if not isinstance(a, _CountingAttr): + if a is NOTHING: + a = attrib() + else: + a = attrib(default=a) + ca_list.append((attr_name, a)) + + unannotated = ca_names - annot_names + if len(unannotated) > 0: + raise UnannotatedAttributeError( + "The following `attr.ib`s lack a type annotation: " + + ", ".join(sorted( + unannotated, + key=lambda n: cd.get(n).counter + )) + "." + ) + else: + ca_list = sorted(( + (name, attr) + for name, attr + in cd.items() + if isinstance(attr, _CountingAttr) + ), key=lambda e: e[1].counter) + + own_attrs = [ + Attribute.from_counting_attr( + name=attr_name, + ca=ca, + type=anns.get(attr_name), + ) + for attr_name, ca + in ca_list + ] + + super_attrs = [] + super_attr_map = {} # A dictionary of superattrs to their classes. + taken_attr_names = {a.name: a for a in own_attrs} + + # Traverse the MRO and collect attributes. + for super_cls in cls.__mro__[1:-1]: + sub_attrs = getattr(super_cls, "__attrs_attrs__", None) + if sub_attrs is not None: + for a in sub_attrs: + prev_a = taken_attr_names.get(a.name) + # Only add an attribute if it hasn't been defined before. This + # allows for overwriting attribute definitions by subclassing. + if prev_a is None: + super_attrs.append(a) + taken_attr_names[a.name] = a + super_attr_map[a.name] = super_cls + + attr_names = [a.name for a in super_attrs + own_attrs] + + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + attrs = AttrsClass( + super_attrs + [ + Attribute.from_counting_attr( + name=attr_name, + ca=ca, + type=anns.get(attr_name) + ) + for attr_name, ca + in ca_list + ] + ) + + had_default = False + for a in attrs: + if had_default is True and a.default is NOTHING and a.init is True: + raise ValueError( + "No mandatory attributes allowed after an attribute with a " + "default value or factory. Attribute in question: {a!r}" + .format(a=a) + ) + elif had_default is False and \ + a.default is not NOTHING and \ + a.init is not False: + had_default = True + + return _Attributes((attrs, super_attrs, super_attr_map)) + + +def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + raise FrozenInstanceError() + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + raise FrozenInstanceError() + + +class _ClassBuilder(object): + """ + Iteratively build *one* class. + """ + __slots__ = ( + "_cls", "_cls_dict", "_attrs", "_super_names", "_attr_names", "_slots", + "_frozen", "_has_post_init", "_delete_attribs", "_super_attr_map", + ) + + def __init__(self, cls, these, slots, frozen, auto_attribs): + attrs, super_attrs, super_map = _transform_attrs( + cls, these, auto_attribs + ) + + self._cls = cls + self._cls_dict = dict(cls.__dict__) if slots else {} + self._attrs = attrs + self._super_names = set(a.name for a in super_attrs) + self._super_attr_map = super_map + self._attr_names = tuple(a.name for a in attrs) + self._slots = slots + self._frozen = frozen or _has_frozen_superclass(cls) + self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) + self._delete_attribs = not bool(these) + + self._cls_dict["__attrs_attrs__"] = self._attrs + + if frozen: + self._cls_dict["__setattr__"] = _frozen_setattrs + self._cls_dict["__delattr__"] = _frozen_delattrs + + def __repr__(self): + return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__) + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used anymore after calling this method. + """ + if self._slots is True: + return self._create_slots_class() + else: + return self._patch_original_class() + + def _patch_original_class(self): + """ + Apply accumulated methods and return the class. + """ + cls = self._cls + super_names = self._super_names + + # Clean class of attribute definitions (`attr.ib()`s). + if self._delete_attribs: + for name in self._attr_names: + if name not in super_names and \ + getattr(cls, name, None) is not None: + delattr(cls, name) + + # Attach our dunder methods. + for name, value in self._cls_dict.items(): + setattr(cls, name, value) + + return cls + + def _create_slots_class(self): + """ + Build and return a new class with a `__slots__` attribute. + """ + super_names = self._super_names + cd = { + k: v + for k, v in iteritems(self._cls_dict) + if k not in tuple(self._attr_names) + ("__dict__",) + } + + # We only add the names of attributes that aren't inherited. + # Settings __slots__ to inherited attributes wastes memory. + cd["__slots__"] = tuple( + name + for name in self._attr_names + if name not in super_names + ) + + qualname = getattr(self._cls, "__qualname__", None) + if qualname is not None: + cd["__qualname__"] = qualname + + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return tuple( + getattr(self, name) + for name in state_attr_names + ) + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + + # slots and frozen require __getstate__/__setstate__ to work + cd["__getstate__"] = slots_getstate + cd["__setstate__"] = slots_setstate + + # Create new class based on old class and our methods. + cls = type(self._cls)( + self._cls.__name__, + self._cls.__bases__, + cd, + ) + + # The following is a fix for + # https://github.com/python-attrs/attrs/issues/102. On Python 3, + # if a method mentions `__class__` or uses the no-arg super(), the + # compiler will bake a reference to the class in the method itself + # as `method.__closure__`. Since we replace the class with a + # clone, we rewrite these references so it keeps working. + for item in cls.__dict__.values(): + if isinstance(item, (classmethod, staticmethod)): + # Class- and staticmethods hide their functions inside. + # These might need to be rewritten as well. + closure_cells = getattr(item.__func__, "__closure__", None) + else: + closure_cells = getattr(item, "__closure__", None) + + if not closure_cells: # Catch None or the empty list. + continue + for cell in closure_cells: + if cell.cell_contents is self._cls: + set_closure_cell(cell, cls) + + return cls + + def add_repr(self, ns): + self._cls_dict["__repr__"] = self._add_method_dunders( + _make_repr(self._attrs, ns=ns) + ) + return self + + def add_str(self): + repr = self._cls_dict.get("__repr__") + if repr is None: + raise ValueError( + "__str__ can only be generated if a __repr__ exists." + ) + + def __str__(self): + return self.__repr__() + + self._cls_dict["__str__"] = self._add_method_dunders(__str__) + return self + + def make_unhashable(self): + self._cls_dict["__hash__"] = None + return self + + def add_hash(self): + self._cls_dict["__hash__"] = self._add_method_dunders( + _make_hash(self._attrs) + ) + + return self + + def add_init(self): + self._cls_dict["__init__"] = self._add_method_dunders( + _make_init( + self._attrs, + self._has_post_init, + self._frozen, + self._slots, + self._super_attr_map, + ) + ) + + return self + + def add_cmp(self): + cd = self._cls_dict + + cd["__eq__"], cd["__ne__"], cd["__lt__"], cd["__le__"], cd["__gt__"], \ + cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_cmp(self._attrs) + ) + + return self + + def _add_method_dunders(self, method): + """ + Add __module__ and __qualname__ to a *method* if possible. + """ + try: + method.__module__ = self._cls.__module__ + except AttributeError: + pass + + try: + method.__qualname__ = ".".join( + (self._cls.__qualname__, method.__name__,) + ) + except AttributeError: + pass + + return method + + +def attrs(maybe_cls=None, these=None, repr_ns=None, + repr=True, cmp=True, hash=None, init=True, + slots=False, frozen=False, str=False, auto_attribs=False): + r""" + A class decorator that adds `dunder + `_\ -methods according to the + specified attributes using :func:`attr.ib` or the *these* argument. + + :param these: A dictionary of name to :func:`attr.ib` mappings. This is + useful to avoid the definition of your attributes within the class body + because you can't (e.g. if you want to add ``__repr__`` methods to + Django models) or don't want to. + + If *these* is not ``None``, ``attrs`` will *not* search the class body + for attributes and will *not* remove any attributes from it. + + If *these* is an ordered dict (:class:`dict` on Python 3.6+, + :class:`collections.OrderedDict` otherwise), the order is deduced from + the order of the attributes inside *these*. Otherwise the order + of the definition of the attributes is used. + + :type these: :class:`dict` of :class:`str` to :func:`attr.ib` + + :param str repr_ns: When using nested classes, there's no way in Python 2 + to automatically detect that. Therefore it's possible to set the + namespace explicitly for a more meaningful ``repr`` output. + :param bool repr: Create a ``__repr__`` method with a human readable + representation of ``attrs`` attributes.. + :param bool str: Create a ``__str__`` method that is identical to + ``__repr__``. This is usually not necessary except for + :class:`Exception`\ s. + :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``, + ``__gt__``, and ``__ge__`` methods that compare the class as if it were + a tuple of its ``attrs`` attributes. But the attributes are *only* + compared, if the type of both classes is *identical*! + :param hash: If ``None`` (default), the ``__hash__`` method is generated + according how *cmp* and *frozen* are set. + + 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. + 2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to + None, marking it unhashable (which it is). + 3. If *cmp* is False, ``__hash__`` will be left untouched meaning the + ``__hash__`` method of the superclass will be used (if superclass is + ``object``, this means it will fall back to id-based hashing.). + + Although not recommended, you can decide for yourself and force + ``attrs`` to create one (e.g. if the class is immutable even though you + didn't freeze it programmatically) by passing ``True`` or not. Both of + these cases are rather special and should be used carefully. + + See the `Python documentation \ + `_ + and the `GitHub issue that led to the default behavior \ + `_ for more details. + :type hash: ``bool`` or ``None`` + :param bool init: Create a ``__init__`` method that initializes the + ``attrs`` attributes. Leading underscores are stripped for the + argument name. If a ``__attrs_post_init__`` method exists on the + class, it will be called after the class is fully initialized. + :param bool slots: Create a slots_-style class that's more + memory-efficient. See :ref:`slots` for further ramifications. + :param bool frozen: Make instances immutable after initialization. If + someone attempts to modify a frozen instance, + :exc:`attr.exceptions.FrozenInstanceError` is raised. + + Please note: + + 1. This is achieved by installing a custom ``__setattr__`` method + on your class so you can't implement an own one. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance :ref:`impact + ` when initializing new instances. In other words: + ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You can + circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + .. _slots: https://docs.python.org/3/reference/datamodel.html#slots + :param bool auto_attribs: If True, collect `PEP 526`_-annotated attributes + (Python 3.6 and later only) from the class body. + + In this case, you **must** annotate every field. If ``attrs`` + encounters a field that is set to an :func:`attr.ib` but lacks a type + annotation, an :exc:`attr.exceptions.UnannotatedAttributeError` is + raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't + want to set a type. + + If you assign a value to those attributes (e.g. ``x: int = 42``), that + value becomes the default value like if it were passed using + ``attr.ib(default=42)``. Passing an instance of :class:`Factory` also + works as expected. + + Attributes annotated as :data:`typing.ClassVar` are **ignored**. + + .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/ + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str* + .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. + .. versionchanged:: 17.1.0 + *hash* supports ``None`` as value which is also the default now. + .. versionadded:: 17.3.0 *auto_attribs* + .. versionchanged:: 18.1.0 + If *these* is passed, no attributes are deleted from the class body. + .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. + """ + def wrap(cls): + if getattr(cls, "__class__", None) is None: + raise TypeError("attrs only works with new-style classes.") + + builder = _ClassBuilder(cls, these, slots, frozen, auto_attribs) + + if repr is True: + builder.add_repr(repr_ns) + if str is True: + builder.add_str() + if cmp is True: + builder.add_cmp() + + if hash is not True and hash is not False and hash is not None: + # Can't use `hash in` because 1 == True for example. + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + elif hash is False or (hash is None and cmp is False): + pass + elif hash is True or (hash is None and cmp is True and frozen is True): + builder.add_hash() + else: + builder.make_unhashable() + + if init is True: + builder.add_init() + + return builder.build_class() + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +_attrs = attrs +""" +Internal alias so we can use it in functions that take an argument called +*attrs*. +""" + + +if PY2: + def _has_frozen_superclass(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return ( + getattr( + cls.__setattr__, "__module__", None + ) == _frozen_setattrs.__module__ and + cls.__setattr__.__name__ == _frozen_setattrs.__name__ + ) +else: + def _has_frozen_superclass(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ == _frozen_setattrs + + +def _attrs_to_tuple(obj, attrs): + """ + Create a tuple of all values of *obj*'s *attrs*. + """ + return tuple(getattr(obj, a.name) for a in attrs) + + +def _make_hash(attrs): + attrs = tuple( + a + for a in attrs + if a.hash is True or (a.hash is None and a.cmp is True) + ) + + # We cache the generated hash methods for the same kinds of attributes. + sha1 = hashlib.sha1() + sha1.update(repr(attrs).encode("utf-8")) + unique_filename = "" % (sha1.hexdigest(),) + type_hash = hash(unique_filename) + lines = [ + "def __hash__(self):", + " return hash((", + " %d," % (type_hash,), + ] + for a in attrs: + lines.append(" self.%s," % (a.name)) + + lines.append(" ))") + + script = "\n".join(lines) + globs = {} + locs = {} + bytecode = compile(script, unique_filename, "exec") + eval(bytecode, globs, locs) + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[unique_filename] = ( + len(script), + None, + script.splitlines(True), + unique_filename, + ) + + return locs["__hash__"] + + +def _add_hash(cls, attrs): + """ + Add a hash method to *cls*. + """ + cls.__hash__ = _make_hash(attrs) + return cls + + +def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or return the result + negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + +def _make_cmp(attrs): + attrs = [a for a in attrs if a.cmp] + + # We cache the generated eq methods for the same kinds of attributes. + sha1 = hashlib.sha1() + sha1.update(repr(attrs).encode("utf-8")) + unique_filename = "" % (sha1.hexdigest(),) + lines = [ + "def __eq__(self, other):", + " if other.__class__ is not self.__class__:", + " return NotImplemented", + ] + # We can't just do a big self.x = other.x and... clause due to + # irregularities like nan == nan is false but (nan,) == (nan,) is true. + if attrs: + lines.append(" return (") + others = [ + " ) == (", + ] + for a in attrs: + lines.append(" self.%s," % (a.name,)) + others.append(" other.%s," % (a.name,)) + + lines += others + [" )"] + else: + lines.append(" return True") + + script = "\n".join(lines) + globs = {} + locs = {} + bytecode = compile(script, unique_filename, "exec") + eval(bytecode, globs, locs) + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[unique_filename] = ( + len(script), + None, + script.splitlines(True), + unique_filename, + ) + eq = locs["__eq__"] + ne = __ne__ + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return _attrs_to_tuple(obj, attrs) + + def __lt__(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) < attrs_to_tuple(other) + else: + return NotImplemented + + def __le__(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) <= attrs_to_tuple(other) + else: + return NotImplemented + + def __gt__(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) > attrs_to_tuple(other) + else: + return NotImplemented + + def __ge__(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) >= attrs_to_tuple(other) + else: + return NotImplemented + + return eq, ne, __lt__, __le__, __gt__, __ge__ + + +def _add_cmp(cls, attrs=None): + """ + Add comparison methods to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__eq__, cls.__ne__, cls.__lt__, cls.__le__, cls.__gt__, cls.__ge__ = \ + _make_cmp(attrs) + + return cls + + +_already_repring = threading.local() + + +def _make_repr(attrs, ns): + """ + Make a repr method for *attr_names* adding *ns* to the full name. + """ + attr_names = tuple( + a.name + for a in attrs + if a.repr + ) + + def __repr__(self): + """ + Automatically created by attrs. + """ + try: + working_set = _already_repring.working_set + except AttributeError: + working_set = set() + _already_repring.working_set = working_set + + if id(self) in working_set: + return "..." + real_cls = self.__class__ + if ns is None: + qualname = getattr(real_cls, "__qualname__", None) + if qualname is not None: + class_name = qualname.rsplit(">.", 1)[-1] + else: + class_name = real_cls.__name__ + else: + class_name = ns + "." + real_cls.__name__ + + # Since 'self' remains on the stack (i.e.: strongly referenced) for the + # duration of this call, it's safe to depend on id(...) stability, and + # not need to track the instance and therefore worry about properties + # like weakref- or hash-ability. + working_set.add(id(self)) + try: + result = [class_name, "("] + first = True + for name in attr_names: + if first: + first = False + else: + result.append(", ") + result.extend((name, "=", repr(getattr(self, name, NOTHING)))) + return "".join(result) + ")" + finally: + working_set.remove(id(self)) + return __repr__ + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__repr__ = _make_repr(attrs, ns) + return cls + + +def _make_init(attrs, post_init, frozen, slots, super_attr_map): + attrs = [ + a + for a in attrs + if a.init or a.default is not NOTHING + ] + + # We cache the generated init methods for the same kinds of attributes. + sha1 = hashlib.sha1() + sha1.update(repr(attrs).encode("utf-8")) + unique_filename = "".format( + sha1.hexdigest() + ) + + script, globs = _attrs_to_init_script( + attrs, + frozen, + slots, + post_init, + super_attr_map, + ) + locs = {} + bytecode = compile(script, unique_filename, "exec") + attr_dict = dict((a.name, a) for a in attrs) + globs.update({ + "NOTHING": NOTHING, + "attr_dict": attr_dict, + }) + if frozen is True: + # Save the lookup overhead in __init__ if we need to circumvent + # immutability. + globs["_cached_setattr"] = _obj_setattr + eval(bytecode, globs, locs) + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[unique_filename] = ( + len(script), + None, + script.splitlines(True), + unique_filename, + ) + + return locs["__init__"] + + +def _add_init(cls, frozen): + """ + Add a __init__ method to *cls*. If *frozen* is True, make it immutable. + """ + cls.__init__ = _make_init( + cls.__attrs_attrs__, + getattr(cls, "__attrs_post_init__", False), + frozen, + _is_slot_cls(cls), + {}, + ) + return cls + + +def fields(cls): + """ + Return the tuple of ``attrs`` attributes for a class. + + The tuple also allows accessing the fields by their names (see below for + examples). + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: tuple (with name accessors) of :class:`attr.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + """ + if not isclass(cls): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError( + "{cls!r} is not an attrs-decorated class.".format(cls=cls) + ) + return attrs + + +def fields_dict(cls): + """ + Return an ordered dictionary of ``attrs`` attributes for a class, whose + keys are the attribute names. + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: an ordered dict where keys are attribute names and values are + :class:`attr.Attribute`\ s. This will be a :class:`dict` if it's + naturally ordered like on Python 3.6+ or an + :class:`~collections.OrderedDict` otherwise. + + .. versionadded:: 18.1.0 + """ + if not isclass(cls): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError( + "{cls!r} is not an attrs-decorated class.".format(cls=cls) + ) + return ordered_dict(((a.name, a) for a in attrs)) + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + :param inst: Instance of a class with ``attrs`` attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _is_slot_cls(cls): + return "__slots__" in cls.__dict__ + + +def _is_slot_attr(a_name, super_attr_map): + """ + Check if the attribute name comes from a slot class. + """ + return a_name in super_attr_map and _is_slot_cls(super_attr_map[a_name]) + + +def _attrs_to_init_script(attrs, frozen, slots, post_init, super_attr_map): + """ + Return a script of an initializer for *attrs* and a dict of globals. + + The globals are expected by the generated script. + + If *frozen* is True, we cannot set the attributes directly so we use + a cached ``object.__setattr__``. + """ + lines = [] + any_slot_ancestors = any( + _is_slot_attr(a.name, super_attr_map) + for a in attrs + ) + if frozen is True: + if slots is True: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. + "_setattr = _cached_setattr.__get__(self, self.__class__)" + ) + + def fmt_setter(attr_name, value_var): + return "_setattr('%(attr_name)s', %(value_var)s)" % { + "attr_name": attr_name, + "value_var": value_var, + } + + def fmt_setter_with_converter(attr_name, value_var): + conv_name = _init_converter_pat.format(attr_name) + return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % { + "attr_name": attr_name, + "value_var": value_var, + "conv": conv_name, + } + else: + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + lines.append( + "_inst_dict = self.__dict__" + ) + if any_slot_ancestors: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup + # per assignment. + "_setattr = _cached_setattr.__get__(self, self.__class__)" + ) + + def fmt_setter(attr_name, value_var): + if _is_slot_attr(attr_name, super_attr_map): + res = "_setattr('%(attr_name)s', %(value_var)s)" % { + "attr_name": attr_name, + "value_var": value_var, + } + else: + res = "_inst_dict['%(attr_name)s'] = %(value_var)s" % { + "attr_name": attr_name, + "value_var": value_var, + } + return res + + def fmt_setter_with_converter(attr_name, value_var): + conv_name = _init_converter_pat.format(attr_name) + if _is_slot_attr(attr_name, super_attr_map): + tmpl = "_setattr('%(attr_name)s', %(c)s(%(value_var)s))" + else: + tmpl = "_inst_dict['%(attr_name)s'] = %(c)s(%(value_var)s)" + return tmpl % { + "attr_name": attr_name, + "value_var": value_var, + "c": conv_name, + } + else: + # Not frozen. + def fmt_setter(attr_name, value): + return "self.%(attr_name)s = %(value)s" % { + "attr_name": attr_name, + "value": value, + } + + def fmt_setter_with_converter(attr_name, value_var): + conv_name = _init_converter_pat.format(attr_name) + return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % { + "attr_name": attr_name, + "value_var": value_var, + "conv": conv_name, + } + + args = [] + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + attr_name = a.name + arg_name = a.name.lstrip("_") + has_factory = isinstance(a.default, Factory) + if has_factory and a.default.takes_self: + maybe_self = "self" + else: + maybe_self = "" + if a.init is False: + if has_factory: + init_factory_name = _init_factory_pat.format(a.name) + if a.converter is not None: + lines.append(fmt_setter_with_converter( + attr_name, + init_factory_name + "({0})".format(maybe_self))) + conv_name = _init_converter_pat.format(a.name) + names_for_globals[conv_name] = a.converter + else: + lines.append(fmt_setter( + attr_name, + init_factory_name + "({0})".format(maybe_self) + )) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.converter is not None: + lines.append(fmt_setter_with_converter( + attr_name, + "attr_dict['{attr_name}'].default" + .format(attr_name=attr_name) + )) + conv_name = _init_converter_pat.format(a.name) + names_for_globals[conv_name] = a.converter + else: + lines.append(fmt_setter( + attr_name, + "attr_dict['{attr_name}'].default" + .format(attr_name=attr_name) + )) + elif a.default is not NOTHING and not has_factory: + args.append( + "{arg_name}=attr_dict['{attr_name}'].default".format( + arg_name=arg_name, + attr_name=attr_name, + ) + ) + if a.converter is not None: + lines.append(fmt_setter_with_converter(attr_name, arg_name)) + names_for_globals[_init_converter_pat.format(a.name)] = ( + a.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name)) + elif has_factory: + args.append("{arg_name}=NOTHING".format(arg_name=arg_name)) + lines.append("if {arg_name} is not NOTHING:" + .format(arg_name=arg_name)) + init_factory_name = _init_factory_pat.format(a.name) + if a.converter is not None: + lines.append(" " + fmt_setter_with_converter( + attr_name, arg_name + )) + lines.append("else:") + lines.append(" " + fmt_setter_with_converter( + attr_name, + init_factory_name + "({0})".format(maybe_self) + )) + names_for_globals[_init_converter_pat.format(a.name)] = ( + a.converter + ) + else: + lines.append(" " + fmt_setter(attr_name, arg_name)) + lines.append("else:") + lines.append(" " + fmt_setter( + attr_name, + init_factory_name + "({0})".format(maybe_self) + )) + names_for_globals[init_factory_name] = a.default.factory + else: + args.append(arg_name) + if a.converter is not None: + lines.append(fmt_setter_with_converter(attr_name, arg_name)) + names_for_globals[_init_converter_pat.format(a.name)] = ( + a.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name)) + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_{}".format(a.name) + attr_name = "__attr_{}".format(a.name) + lines.append(" {}(self, {}, self.{})".format( + val_name, attr_name, a.name)) + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + if post_init: + lines.append("self.__attrs_post_init__()") + + return """\ +def __init__(self, {args}): + {lines} +""".format( + args=", ".join(args), + lines="\n ".join(lines) if lines else "pass", + ), names_for_globals + + +class Attribute(object): + """ + *Read-only* representation of an attribute. + + :attribute name: The name of the attribute. + + Plus *all* arguments of :func:`attr.ib`. + + For the version history of the fields, see :func:`attr.ib`. + """ + __slots__ = ( + "name", "default", "validator", "repr", "cmp", "hash", "init", + "metadata", "type", "converter", + ) + + def __init__(self, name, default, validator, repr, cmp, hash, init, + convert=None, metadata=None, type=None, converter=None): + # Cache this descriptor here to speed things up later. + bound_setattr = _obj_setattr.__get__(self, Attribute) + + # Despite the big red warning, people *do* instantiate `Attribute` + # themselves. + if convert is not None: + if converter is not None: + raise RuntimeError( + "Can't pass both `convert` and `converter`. " + "Please use `converter` only." + ) + warnings.warn( + "The `convert` argument is deprecated in favor of `converter`." + " It will be removed after 2019/01.", + DeprecationWarning, stacklevel=2 + ) + converter = convert + + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("cmp", cmp) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("converter", converter) + bound_setattr("metadata", ( + metadata_proxy(metadata) if metadata + else _empty_metadata_singleton + )) + bound_setattr("type", type) + + def __setattr__(self, name, value): + raise FrozenInstanceError() + + @property + def convert(self): + warnings.warn( + "The `convert` attribute is deprecated in favor of `converter`. " + "It will be removed after 2019/01.", + DeprecationWarning, stacklevel=2, + ) + return self.converter + + @classmethod + def from_counting_attr(cls, name, ca, type=None): + # type holds the annotated value. deal with conflicts: + if type is None: + type = ca.type + elif ca.type is not None: + raise ValueError( + "Type annotation and type argument cannot both be present" + ) + inst_dict = { + k: getattr(ca, k) + for k + in Attribute.__slots__ + if k not in ( + "name", "validator", "default", "type", "convert", + ) # exclude methods and deprecated alias + } + return cls( + name=name, validator=ca._validator, default=ca._default, type=type, + **inst_dict + ) + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) if name != "metadata" + else dict(self.metadata) + for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in zip(self.__slots__, state): + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr(name, metadata_proxy(value) if value else + _empty_metadata_singleton) + + +_a = [ + Attribute(name=name, default=NOTHING, validator=None, + repr=True, cmp=True, hash=(name != "metadata"), init=True) + for name in Attribute.__slots__ + if name != "convert" # XXX: remove once `convert` is gone +] + +Attribute = _add_hash( + _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), + attrs=[a for a in _a if a.hash] +) + + +class _CountingAttr(object): + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init", + "metadata", "_validator", "converter", "type") + __attrs_attrs__ = tuple( + Attribute(name=name, default=NOTHING, validator=None, + repr=True, cmp=True, hash=True, init=True) + for name + in ("counter", "_default", "repr", "cmp", "hash", "init",) + ) + ( + Attribute(name="metadata", default=None, validator=None, + repr=True, cmp=True, hash=False, init=True), + ) + cls_counter = 0 + + def __init__(self, default, validator, repr, cmp, hash, init, converter, + metadata, type): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + # If validator is a list/tuple, wrap it using helper validator. + if validator and isinstance(validator, (list, tuple)): + self._validator = and_(*validator) + else: + self._validator = validator + self.repr = repr + self.cmp = cmp + self.hash = hash + self.init = init + self.converter = converter + self.metadata = metadata + self.type = type + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + :raises DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError() + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_cmp(_add_repr(_CountingAttr)) + + +@attrs(slots=True, init=False, hash=True) +class Factory(object): + """ + Stores a factory callable. + + If passed as the default value to :func:`attr.ib`, the factory is used to + generate a new value. + + :param callable factory: A callable that takes either none or exactly one + mandatory positional argument depending on *takes_self*. + :param bool takes_self: Pass the partially initialized instance that is + being initialized as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + factory = attrib() + takes_self = attrib() + + def __init__(self, factory, takes_self=False): + """ + `Factory` is part of the default machinery so if we want a default + value here, we have to implement it ourselves. + """ + self.factory = factory + self.takes_self = takes_self + + +def make_class(name, attrs, bases=(object,), **attributes_arguments): + """ + A quick way to create a new class called *name* with *attrs*. + + :param name: The name for the new class. + :type name: str + + :param attrs: A list of names or a dictionary of mappings of names to + attributes. + + If *attrs* is a list or an ordered dict (:class:`dict` on Python 3.6+, + :class:`collections.OrderedDict` otherwise), the order is deduced from + the order of the names or attributes inside *attrs*. Otherwise the + order of the definition of the attributes is used. + :type attrs: :class:`list` or :class:`dict` + + :param tuple bases: Classes that the new class will subclass. + + :param attributes_arguments: Passed unmodified to :func:`attr.s`. + + :return: A new class with *attrs*. + :rtype: type + + .. versionadded:: 17.1.0 *bases* + .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + """ + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = dict((a, attrib()) for a in attrs) + else: + raise TypeError("attrs argument must be a dict or a list.") + + post_init = cls_dict.pop("__attrs_post_init__", None) + type_ = type( + name, + bases, + {} if post_init is None else {"__attrs_post_init__": post_init} + ) + # For pickling to work, the __module__ variable needs to be set to the + # frame where the class is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + try: + type_.__module__ = sys._getframe(1).f_globals.get( + "__name__", "__main__", + ) + except (AttributeError, ValueError): + pass + + return _attrs(these=cls_dict, **attributes_arguments)(type_) + + +# These are required by within this module so we define them here and merely +# import into .validators. + + +@attrs(slots=True, hash=True) +class _AndValidator(object): + """ + Compose many validators to a single one. + """ + _validators = attrib() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + :param validators: Arbitrary number of validators. + :type validators: callables + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals)) diff --git a/pipenv/vendor/attr/converters.py b/pipenv/vendor/attr/converters.py new file mode 100644 index 00000000..3b3bac92 --- /dev/null +++ b/pipenv/vendor/attr/converters.py @@ -0,0 +1,24 @@ +""" +Commonly useful converters. +""" + +from __future__ import absolute_import, division, print_function + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to ``None``. + + :param callable converter: the converter that is used for non-``None`` + values. + + .. versionadded:: 17.1.0 + """ + + def optional_converter(val): + if val is None: + return None + return converter(val) + + return optional_converter diff --git a/pipenv/vendor/attr/exceptions.py b/pipenv/vendor/attr/exceptions.py new file mode 100644 index 00000000..f949f3c9 --- /dev/null +++ b/pipenv/vendor/attr/exceptions.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import, division, print_function + + +class FrozenInstanceError(AttributeError): + """ + A frozen/immutable instance has been attempted to be modified. + + It mirrors the behavior of ``namedtuples`` by using the same error message + and subclassing :exc:`AttributeError`. + + .. versionadded:: 16.1.0 + """ + msg = "can't set attribute" + args = [msg] + + +class AttrsAttributeNotFoundError(ValueError): + """ + An ``attrs`` function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-``attrs`` class has been passed into an ``attrs`` function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set using ``attr.ib()`` and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """ + + +class UnannotatedAttributeError(RuntimeError): + """ + A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type + annotation. + + .. versionadded:: 17.3.0 + """ diff --git a/pipenv/vendor/attr/filters.py b/pipenv/vendor/attr/filters.py new file mode 100644 index 00000000..d1bad35e --- /dev/null +++ b/pipenv/vendor/attr/filters.py @@ -0,0 +1,52 @@ +""" +Commonly useful filters for :func:`attr.asdict`. +""" + +from __future__ import absolute_import, division, print_function + +from ._compat import isclass +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isclass(cls)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Whitelist *what*. + + :param what: What to whitelist. + :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s + + :rtype: :class:`callable` + """ + cls, attrs = _split_what(what) + + def include_(attribute, value): + return value.__class__ in cls or attribute in attrs + + return include_ + + +def exclude(*what): + """ + Blacklist *what*. + + :param what: What to blacklist. + :type what: :class:`list` of classes or :class:`attr.Attribute`\ s. + + :rtype: :class:`callable` + """ + cls, attrs = _split_what(what) + + def exclude_(attribute, value): + return value.__class__ not in cls and attribute not in attrs + + return exclude_ diff --git a/pipenv/vendor/attr/validators.py b/pipenv/vendor/attr/validators.py new file mode 100644 index 00000000..f8892fcd --- /dev/null +++ b/pipenv/vendor/attr/validators.py @@ -0,0 +1,166 @@ +""" +Commonly useful validators. +""" + +from __future__ import absolute_import, division, print_function + +from ._make import _AndValidator, and_, attrib, attrs + + +__all__ = [ + "and_", + "in_", + "instance_of", + "optional", + "provides", +] + + +@attrs(repr=False, slots=True, hash=True) +class _InstanceOfValidator(object): + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + raise TypeError( + "'{name}' must be {type!r} (got {value!r} that is a " + "{actual!r})." + .format(name=attr.name, type=self.type, + actual=value.__class__, value=value), + attr, self.type, value, + ) + + def __repr__(self): + return ( + "" + .format(type=self.type) + ) + + +def instance_of(type): + """ + A validator that raises a :exc:`TypeError` if the initializer is called + with a wrong type for this particular attribute (checks are performed using + :func:`isinstance` therefore it's also valid to pass a tuple of types). + + :param type: The type to check for. + :type type: type or tuple of types + + :raises TypeError: With a human readable error message, the attribute + (of type :class:`attr.Attribute`), the expected type, and the value it + got. + """ + return _InstanceOfValidator(type) + + +@attrs(repr=False, slots=True, hash=True) +class _ProvidesValidator(object): + interface = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.interface.providedBy(value): + raise TypeError( + "'{name}' must provide {interface!r} which {value!r} " + "doesn't." + .format(name=attr.name, interface=self.interface, value=value), + attr, self.interface, value, + ) + + def __repr__(self): + return ( + "" + .format(interface=self.interface) + ) + + +def provides(interface): + """ + A validator that raises a :exc:`TypeError` if the initializer is called + with an object that does not provide the requested *interface* (checks are + performed using ``interface.providedBy(value)`` (see `zope.interface + `_). + + :param zope.interface.Interface interface: The interface to check for. + + :raises TypeError: With a human readable error message, the attribute + (of type :class:`attr.Attribute`), the expected interface, and the + value it got. + """ + return _ProvidesValidator(interface) + + +@attrs(repr=False, slots=True, hash=True) +class _OptionalValidator(object): + validator = attrib() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return ( + "" + .format(what=repr(self.validator)) + ) + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to ``None`` in addition to satisfying the requirements of + the sub-validator. + + :param validator: A validator (or a list of validators) that is used for + non-``None`` values. + :type validator: callable or :class:`list` of callables. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + """ + if isinstance(validator, list): + return _OptionalValidator(_AndValidator(validator)) + return _OptionalValidator(validator) + + +@attrs(repr=False, slots=True, hash=True) +class _InValidator(object): + options = attrib() + + def __call__(self, inst, attr, value): + if value not in self.options: + raise ValueError( + "'{name}' must be in {options!r} (got {value!r})" + .format(name=attr.name, options=self.options, value=value) + ) + + def __repr__(self): + return ( + "" + .format(options=self.options) + ) + + +def in_(options): + """ + A validator that raises a :exc:`ValueError` if the initializer is called + with a value that does not belong in the options provided. The check is + performed using ``value in options``. + + :param options: Allowed options. + :type options: list, tuple, :class:`enum.Enum`, ... + + :raises ValueError: With a human readable error message, the attribute (of + type :class:`attr.Attribute`), the expected options, and the value it + got. + + .. versionadded:: 17.1.0 + """ + return _InValidator(options) diff --git a/pipenv/vendor/backports/__init__.py b/pipenv/vendor/backports/__init__.py index b5ddec96..0eea729c 100644 --- a/pipenv/vendor/backports/__init__.py +++ b/pipenv/vendor/backports/__init__.py @@ -2,4 +2,5 @@ from pkgutil import extend_path __path__ = extend_path(__path__, __name__) from . import shutil_get_terminal_size -from . import weakref \ No newline at end of file +from . import weakref +from . import shlex diff --git a/pipenv/vendor/backports/shlex/__init__.py b/pipenv/vendor/backports/shlex/__init__.py new file mode 100644 index 00000000..942430d7 --- /dev/null +++ b/pipenv/vendor/backports/shlex/__init__.py @@ -0,0 +1,9 @@ +""" +Partial backport of python 3's shlex module. + +Include's only `shlex.quote()` for backwards compatible functionality. +""" + +__all__ = ['quote'] + +from .shlex import quote diff --git a/pipenv/vendor/backports/shlex/shlex.py b/pipenv/vendor/backports/shlex/shlex.py new file mode 100644 index 00000000..2a06d2ee --- /dev/null +++ b/pipenv/vendor/backports/shlex/shlex.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import re +import sys + +__all__ = ['quote'] + +if sys.version_info >= (3, 1): + _find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search +else: + _find_unsafe = re.compile(r'[^\w@%+=:,./-]').search + + +def quote(s): + """Return a shell-escaped version of the string *s*.""" + if not s: + return "''" + if _find_unsafe(s) is None: + return s + + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + return "'" + s.replace("'", "'\"'\"'") + "'" diff --git a/pipenv/vendor/pathlib2.py b/pipenv/vendor/pathlib2.py index b67649c6..87917ff9 100644 --- a/pipenv/vendor/pathlib2.py +++ b/pipenv/vendor/pathlib2.py @@ -1,7 +1,3 @@ -# 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 @@ -13,7 +9,8 @@ import re import six import sys from collections import Sequence -from errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES +from contextlib import contextmanager +from errno import EINVAL, ENOENT, ENOTDIR, EEXIST from operator import attrgetter from stat import ( S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO) @@ -27,22 +24,23 @@ try: intern = intern except NameError: intern = sys.intern +try: + basestring = basestring +except NameError: + basestring = str supports_symlinks = True -if os.name == 'nt': +try: import nt +except ImportError: + nt = None +else: 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", @@ -61,15 +59,12 @@ def _py2_fsencode(parts): else part for part in parts] -def _try_except_fileexistserror(try_func, except_func, else_func=None): +def _try_except_fileexistserror(try_func, except_func): 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() @@ -78,45 +73,6 @@ def _try_except_fileexistserror(try_func, except_func, else_func=None): 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) - else: - try: - try_func() - 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): @@ -264,7 +220,10 @@ class _WindowsFlavour(_Flavour): is_supported = (os.name == 'nt') - drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') + drive_letters = ( + set(chr(x) for x in range(ord('a'), ord('z') + 1)) | + set(chr(x) for x in range(ord('A'), ord('Z') + 1)) + ) ext_namespace_prefix = '\\\\?\\' reserved_names = ( @@ -324,28 +283,12 @@ class _WindowsFlavour(_Flavour): def casefold_parts(self, parts): return [p.lower() for p in parts] - def resolve(self, path, strict=False): + def resolve(self, path): 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 = [] - while True: - try: - s = self._ext_to_normal(_getfinalpathname(s)) - except FileNotFoundError: - previous_s = s - s, tail = os.path.split(s) - tail_parts.append(tail) - if previous_s == s: - return path - else: - return os.path.join(s, *reversed(tail_parts)) + return self._ext_to_normal(_getfinalpathname(s)) # Means fallback on absolute return None @@ -450,7 +393,7 @@ class _PosixFlavour(_Flavour): def casefold_parts(self, parts): return parts - def resolve(self, path, strict=False): + def resolve(self, path): sep = self.sep accessor = path._accessor seen = {} @@ -481,10 +424,9 @@ class _PosixFlavour(_Flavour): try: target = accessor.readlink(newpath) except OSError as e: - if e.errno != EINVAL and strict: + if e.errno != EINVAL: raise - # Not a symlink, or non-strict mode. We just leave the path - # untouched. + # Not a symlink path = newpath else: seen[newpath] = None # not resolved symlink @@ -521,7 +463,6 @@ class _PosixFlavour(_Flavour): raise RuntimeError("Can't determine home directory " "for %r" % username) - _windows_flavour = _WindowsFlavour() _posix_flavour = _PosixFlavour() @@ -554,8 +495,6 @@ class _NormalAccessor(_Accessor): listdir = _wrap_strfunc(os.listdir) - scandir = _wrap_strfunc(os_scandir) - chmod = _wrap_strfunc(os.chmod) if hasattr(os, "lchmod"): @@ -602,6 +541,27 @@ _normal_accessor = _NormalAccessor() # Globbing helpers # +@contextmanager +def _cached(func): + try: + func.__cached__ + yield func + except AttributeError: + cache = {} + + def wrapper(*args): + try: + return cache[args] + except KeyError: + value = cache[args] = func(*args) + return value + wrapper.__cached__ = True + try: + yield wrapper + finally: + cache.clear() + + def _make_selector(pattern_parts): pat = pattern_parts[0] child_parts = pattern_parts[1:] @@ -616,7 +576,6 @@ def _make_selector(pattern_parts): cls = _PreciseSelector return cls(pat, child_parts) - if hasattr(functools, "lru_cache"): _make_selector = functools.lru_cache()(_make_selector) @@ -630,10 +589,8 @@ class _Selector: 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 @@ -641,15 +598,13 @@ class _Selector: 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) + listdir = parent_path._accessor.listdir + return self._select_from(parent_path, is_dir, exists, listdir) class _TerminatingSelector: - def _select_from(self, parent_path, is_dir, exists, scandir): + def _select_from(self, parent_path, is_dir, exists, listdir): yield parent_path @@ -659,20 +614,14 @@ class _PreciseSelector(_Selector): 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): + def _select_from(self, parent_path, is_dir, exists, listdir): + if not is_dir(parent_path): return - yield - - for x in _try_except_permissionerror_iter(try_iter, except_iter): - yield x + path = parent_path._make_child_relpath(self.name) + if exists(path): + for p in self.successor._select_from( + path, is_dir, exists, listdir): + yield p class _WildcardSelector(_Selector): @@ -681,26 +630,17 @@ class _WildcardSelector(_Selector): 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): + def _select_from(self, parent_path, is_dir, exists, listdir): + if not is_dir(parent_path): return - yield - - for x in _try_except_permissionerror_iter(try_iter, except_iter): - yield x + cf = parent_path._flavour.casefold + for name in listdir(parent_path): + 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, listdir): + yield p class _RecursiveWildcardSelector(_Selector): @@ -708,46 +648,31 @@ class _RecursiveWildcardSelector(_Selector): def __init__(self, pat, child_parts): _Selector.__init__(self, child_parts) - def _iterate_directories(self, parent_path, is_dir, scandir): + def _iterate_directories(self, parent_path, is_dir, listdir): yield parent_path + for name in listdir(parent_path): + path = parent_path._make_child_relpath(name) + if is_dir(path): + for p in self._iterate_directories(path, is_dir, listdir): + yield p - def try_iter(): - entries = list(scandir(parent_path)) - for entry in entries: - 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): + def _select_from(self, parent_path, is_dir, exists, listdir): + if not is_dir(parent_path): 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(): + with _cached(listdir) as listdir: yielded = set() try: successor_select = self.successor._select_from for starting_point in self._iterate_directories( - parent_path, is_dir, scandir): + parent_path, is_dir, listdir): for p in successor_select( - starting_point, is_dir, exists, scandir): + starting_point, is_dir, exists, listdir): 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 @@ -818,25 +743,13 @@ class PurePath(object): for a in args: if isinstance(a, PurePath): parts += a._parts + elif isinstance(a, basestring): + # Force-cast str subclasses to str (issue #21127) + parts.append(str(a)) 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 - parts.append(a.encode(sys.getfilesystemencoding())) - else: - raise TypeError( - "argument should be a str object or an os.PathLike " - "object returning str, not %r" - % type(a)) + raise TypeError( + "argument should be a path or str object, not %r" + % type(a)) return cls._flavour.parse_parts(parts) @classmethod @@ -870,7 +783,7 @@ class PurePath(object): return cls._flavour.join(parts) def _init(self): - # Overridden in concrete Path + # Overriden in concrete Path pass def _make_child(self, args): @@ -889,9 +802,6 @@ class PurePath(object): 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.""" @@ -1072,7 +982,7 @@ class PurePath(object): 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}" + raise ValueError("{!r} does not start with {!r}" .format(str(self), str(formatted))) return self._from_parsed_parts('', root if n == 1 else '', abs_parts[n:]) @@ -1160,12 +1070,6 @@ class PurePath(object): 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__ = () @@ -1252,8 +1156,8 @@ class Path(PurePath): 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()). + """Return whether `other_file` is the same or not as this file. + (as returned by os.path.samefile(file, other_file)). """ if hasattr(os.path, "samestat"): st = self.stat() @@ -1287,8 +1191,6 @@ class Path(PurePath): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given 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: @@ -1327,7 +1229,7 @@ class Path(PurePath): obj._init(template=self) return obj - def resolve(self, strict=False): + def resolve(self): """ Make the path absolute, resolving all symlinks on the way and also normalizing it (for example turning slashes into backslashes under @@ -1335,7 +1237,7 @@ class Path(PurePath): """ if self._closed: self._raise_closed() - s = self._flavour.resolve(self, strict=strict) + s = self._flavour.resolve(self) if s is None: # No symlink resolution => for consistency, raise an error if # the path doesn't exist or is forbidden @@ -1405,7 +1307,7 @@ class Path(PurePath): if not isinstance(data, six.binary_type): raise TypeError( 'data must be %s, not %s' % - (six.binary_type.__name__, data.__class__.__name__)) + (six.binary_type.__class__.__name__, data.__class__.__name__)) with self.open(mode='wb') as f: return f.write(data) @@ -1416,7 +1318,7 @@ class Path(PurePath): if not isinstance(data, six.text_type): raise TypeError( 'data must be %s, not %s' % - (six.text_type.__name__, data.__class__.__name__)) + (six.text_type.__class__.__name__, data.__class__.__name__)) with self.open(mode='w', encoding=encoding, errors=errors) as f: return f.write(data) @@ -1444,26 +1346,27 @@ class Path(PurePath): os.close(fd) def mkdir(self, mode=0o777, parents=False, exist_ok=False): - """ - Create a new directory at this given path. - """ + + def helper(exc): + if not exist_ok or not self.is_dir(): + raise exc + 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: - if not exist_ok or not self.is_dir(): - raise + if not parents: + _try_except_fileexistserror( + lambda: self._accessor.mkdir(self, mode), + helper) + else: + try: + _try_except_fileexistserror( + lambda: self._accessor.mkdir(self, mode), + helper) + except OSError as e: + if e.errno != ENOENT: + raise + self.parent.mkdir(parents=True) + self._accessor.mkdir(self, mode) def chmod(self, mode): """ @@ -1661,9 +1564,3 @@ class PosixPath(Path, PurePosixPath): class WindowsPath(Path, PureWindowsPath): __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") diff --git a/setup.py b/setup.py index 27ef93b8..f4ebf98b 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ required = [ 'setuptools>=36.2.1', 'virtualenv-clone>=0.2.5', 'virtualenv', - 'pathlib;python_version<"3.4"', + 'pathlib2==2.1.0;python_version<"3.4"', 'requests[security];python_version<"3.0"', 'ordereddict;python_version<"3.0"', ] diff --git a/tests/pypi/ibm-db-sa-py3/ibm-db-sa-py3-0.3.0.tar.gz b/tests/pypi/ibm-db-sa-py3/ibm-db-sa-py3-0.3.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..dd4e4a88a1a9fefbd95f6e8db289c68677942644 GIT binary patch literal 24233 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 Kb3#BzLiisWgSG$w literal 0 HcmV?d00001 diff --git a/tests/pypi/ibm-db-sa-py3/ibm-db-sa-py3-0.3.1-1.tar.gz b/tests/pypi/ibm-db-sa-py3/ibm-db-sa-py3-0.3.1-1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..7770d23e95946e5250d8dc572fa9508abd85b6d8 GIT binary patch literal 24174 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 E4++)8C;$Ke literal 0 HcmV?d00001 diff --git a/tests/pypi/win-inet-pton/win_inet_pton-1.0.1.tar.gz b/tests/pypi/win-inet-pton/win_inet_pton-1.0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..dfb778879649dd5410d6991487f95972d148e4ad GIT binary patch literal 2012 zcmV<22P60&iwFn=g#lCo|72-%bT4;lZeM9`WprO~bZ>4gF)lDJF)nmrascgGZEqqu z63*xT72f0nu!0WF>mE{c7tLgL+08J~lG%J%jd~1iz)H8>vfUu=*I(KF0!?5@?ii%o z^`i)AyUJBAm&@g@tSIv-Bg&5zXARG7y59B0iO~e~`(6F*^}EgO?;ICitJ7?@oABAW zXnNglvvq;G7iT~s6&46xEb&}mrVi2_>(3{WfB&CgwP*eBhwtu2xc^!P^ko+Bno)9CD$E7 zL7V`=(3XfP0p@_985fb}!xG_qxByKk!vdtubPrpDR1tF!=m14T#FXMtGcoY!O-mg} zfpu*-B7&K8FD&-$e@RplyYc!=``>Ff+eQ5MI?(R*TVC7l|8rm_cw}~EqG%}HDTgRP zqnHbYj2k$P`9f6(0T_-5xa96~`d0bQc&1)V+y!@ zOiBJV*R5JYMjQ}16*N}a1V&iVB^V-?XF`X0g18uK1BE0aOku;I!_dPTq%j14Pm~3G zB~f(+(?C6`Gh^3@63TD-oGi@4` z_hd;z9&7n%3@RiN4F;%-8t6fQrKeh_Yv`DSB$H~`hD;F_K=iy#0!kP}Sm4UW0vX$+ zpP&Jz#^z|emWo80^3xa%fj!n^Fm;UjBb^GagS&pnEV9i5n!qfcf= z_|HNRn zY})+q{Oj-BY{PL*9|j%Ac_&Cf6?%efkD%;fDAD_bIE{mF9FwTVc>1g(1^k?tJQW0) zJW(yMLMzA;xuA2%4#RMbR)Q)Bc{=6WbF#oW5IsY-snr;45lH2}q`L0>qt|P;eggHM z7VCdHsY*k{2+!7yQn zR+;f+66>SbMME^B^8}0qxirc7JF$nycJ(i9rjYehjxIAD1L2Z0$eBCzm* zMA@}LayYjY9Cs4|nY!zI{rc6(hADeoo@*F-NH)y3Tl805*R7RWwdbX+Fs6;)-y{B) z+D>o}I1>N6?WR}c|8NFez-PbRwekNPxI!m-18{}@_jdqS=p=6euFxCb12{WjD0@l| zt^A2g2#zKxH)Ig_L^Y*w$4Wrj4dJ;qFiEKoS$h~rU+3-BONsp%j%c{PtY<^IQy)UF zP_<2ho1=Q^`!No@ZQA58F^!iUU#qfBKUsrme?*-_9KAyvUhl9*t&&C|XY<+@u-gnR zu*6E`wy2xSVF0(V%ob1MTQCnCNss&%q!KA_!7SNGDXF`XEx+Az_mJj8WY3R0CHU45 zyQB^YB^hM5ycsxy^0VW$nyIKEFokPbdV#qUf{SX&5SK%?go7Q<$`C8~ud2QTS}FXmtzE zg#Pj{1&uH1zsbhhQ#cq@?)JKi&x``KnqyeNANI_%Y|BEyvajvTqtrKokW+5SRG6#< z$uv09Zzht?y@r0cLCxK@&yoyc7C&SE04t4E>JM8}EUSucZgS?^5B_j;_qa7Qfv~`G zZPID`SQ(AnV)NEvufBHd-s=bNWrM@EPF3GC(MrOe8DFtnE=uKAYTJHha;{xJ)44ba zd>WtG&q^+|#UTh*By093$!tbM)ocB;z9AK*>{fmaKd!=*|eeEIs|O3m5v{fI;nUy>KTR;X6E9SPs&cyE|v@4R#Dy_}== z{&bT0G4S@SunBFu!MXaTs8o3RvG8^vMwZ4e`}8S7TNY4?f&UzoWw>w#CImH*2S@)E z0$b=U2#Fd}D_&fH4_pFuuE3?6zCyl16{aewp1XHG*qxjE;VyxB@o=*VOOCz5o&5gy z@Y~Og;poGsQ`Ucaz0Se+e_gM$Q~&Sv;09&ye`ml)qHur}Zv2s!ZwAP7J!b@)bg=WL zW{Uuv#2F`11iN~GzQaqxUwA|sF`k!R%pI!PN4sMV0uD6r)PC$E#6C$@i6`N=&b+ zN0m5U7NnfNO8igjKd1ixuW!|VnnnG`Yw7xbuidxxe_Q{t^`E~={l__V^~YxJ78Vv3 u78Vv378Vv378Vv378Vv378Vv378Vv378Vv378VwNcl-y`O*z51.10', {'django': '>1.10'}], + ['requests[socks]>1.10', {'requests': {'extras': ['socks'], 'version': '>1.10'}}], + ['-e git+git://github.com/pinax/pinax.git@1.4#egg=pinax', {'pinax': {'git': 'git://github.com/pinax/pinax.git', 'ref': '1.4', 'editable': True}}], + ['git+git://github.com/pinax/pinax.git@1.4#egg=pinax', {'pinax': {'git': 'git://github.com/pinax/pinax.git', 'ref': '1.4'}}], + ['FooProject==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', {'FooProject': {'version': '==1.2', 'hash': 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'}}], + ['FooProject[stuff]==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', {'FooProject': {'version': '==1.2', 'extras': ['stuff'], 'hash': 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'}}], + ['git+https://github.com/requests/requests.git@master#egg=requests[security]', {'requests': {'git': 'https://github.com/requests/requests.git', 'ref': 'master', 'extras': ['security']}}], + ['-e svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject', {u'MyProject': {u'svn': u'svn://svn.myproject.org/svn/MyProject', 'editable': True}}], + ['hg+http://hg.myproject.org/MyProject@da39a3ee5e6b#egg=MyProject', {'MyProject': {'hg': 'http://hg.myproject.org/MyProject', 'ref': 'da39a3ee5e6b'}}] + ] + ) + def test_pip_requirements(self, line, pipfile): + from_line = requirements.PipenvRequirement.from_line(line) + pipfile_pkgname = first([k for k in pipfile.keys()]) + pipfile_entry = pipfile[pipfile_pkgname] + from_pipfile = requirements.PipenvRequirement.from_pipfile(pipfile_pkgname, [], pipfile_entry) + assert from_line.as_pipfile() == pipfile + assert from_pipfile.as_requirement() == line diff --git a/tests/test_utils.py b/tests/test_utils.py index 26db37fb..6d99b74e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -86,7 +86,7 @@ class TestUtils: # requests[socks] dep = 'requests[socks]' dep = pipenv.utils.convert_deps_from_pip(dep) - assert dep == {'requests': {'extras': ['socks']}} + assert dep == {'requests': {'extras': ['socks'], 'version': '*'}} # requests[socks] w/ version dep = 'requests[socks]==1.10' dep = pipenv.utils.convert_deps_from_pip(dep)