diff --git a/Pipfile b/Pipfile index 581b96b4..0d784504 100644 --- a/Pipfile +++ b/Pipfile @@ -1,5 +1,4 @@ [dev-packages] - pipenv = {path = ".", editable = true} "flake8" = ">=3.3.0,<4" pytest = "*" @@ -13,14 +12,12 @@ pytest-pypy = {path = "./tests/pytest-pypi", editable = true} pytest-tap = "*" stdeb = {version="*", markers="sys_platform == 'linux'"} white = {version="*", markers="python_version >= '3.6'"} +flaky = "*" [packages] - [scripts] - tests = "bash ./run-tests.sh" [pipenv] - allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index 90f508ed..8a9f4829 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "eab749ae861993f9e4b71bea88782c806a02fa82e3f45c82c4550ee459d8b886" + "sha256": "5941b36256503b9729e927f3249a366748da5c353b59c1e32ca7fd919b31a4d6" }, "pipfile-spec": 6, "requires": {}, @@ -29,13 +29,6 @@ ], "version": "==1.4" }, - "asn1crypto": { - "hashes": [ - "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", - "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" - ], - "version": "==0.24.0" - }, "attrs": { "hashes": [ "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", @@ -57,39 +50,6 @@ ], "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", @@ -113,35 +73,6 @@ "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", @@ -150,16 +81,6 @@ ], "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", @@ -175,6 +96,14 @@ "index": "pypi", "version": "==3.5.0" }, + "flaky": { + "hashes": [ + "sha256:4ad7880aef8c35a34ddb394d4fa33047765bca1e3d67d182bf6eba9c8eabf3a2", + "sha256:d0533f473a46b916e6db6e84e20b06d8a70656600a0c14e819b0760b63f70226" + ], + "index": "pypi", + "version": "==3.4.0" + }, "flask": { "hashes": [ "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", @@ -182,14 +111,6 @@ ], "version": "==0.12.2" }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "markers": "python_version < '3.0'", - "version": "==1.0.2" - }, "idna": { "hashes": [ "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", @@ -204,13 +125,6 @@ ], "version": "==1.0.0" }, - "ipaddress": { - "hashes": [ - "sha256:200d8686011d470b5e4de207d803445deee427455cd0cb7c982b68cf82524f81" - ], - "markers": "python_version < '3'", - "version": "==1.0.19" - }, "itsdangerous": { "hashes": [ "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" @@ -263,10 +177,10 @@ }, "pbr": { "hashes": [ - "sha256:8b9a7c3704657cb0831b3ded0a6b61377947c8235b76649bb7de4bfe2e6cfa56", - "sha256:cf66675e22ae91a4f20e4b8354f117d3e3d1de651513051d109cc39645fb3672" + "sha256:56b7a8ba7d64bf6135a9dfefb85a80d95924b3fde5ed6343a1a1d464a040dae3", + "sha256:de75cf1d510542c746beeff66b52241eb12c8f95f2ef846ee50ed5d72392caa4" ], - "version": "==4.0.0" + "version": "==4.0.1" }, "pipenv": { "editable": true, @@ -299,12 +213,6 @@ ], "version": "==2.3.1" }, - "pycparser": { - "hashes": [ - "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226" - ], - "version": "==2.18" - }, "pyflakes": { "hashes": [ "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", @@ -319,13 +227,6 @@ ], "version": "==2.2.0" }, - "pyopenssl": { - "hashes": [ - "sha256:07a2de1a54de07448732a81e38a55df7da109b2f47f599f8bb35b0cbec69d4bd", - "sha256:2c10cfba46a52c0b0950118981d61e72c1e5b1aac451ca1bc77de1a679456773" - ], - "version": "==17.5.0" - }, "pytest": { "hashes": [ "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", @@ -436,10 +337,10 @@ }, "tqdm": { "hashes": [ - "sha256:782aa84b61a5246c4f9e5b938875009e0b759d9a5c9d16b12e4f8deefdff7892", - "sha256:eaf9c32a2cf7b9623eb272134b934eb354ef6304299611aa71e3cf47cf83c22b" + "sha256:4f2eb1d14804caf7095500fe11da0e481a47af912e7b57c93f886ac3c40a49dd", + "sha256:91ac47ec2ba6bb92b7ba37706f4dea37019ddd784b22fd279a4b12d93327191d" ], - "version": "==4.19.9" + "version": "==4.20.0" }, "twine": { "hashes": [ diff --git a/pipenv/cmdparse.py b/pipenv/cmdparse.py index 6240f0ba..d7e349da 100644 --- a/pipenv/cmdparse.py +++ b/pipenv/cmdparse.py @@ -35,7 +35,7 @@ class Script(object): def extend(self, extra_args): self._parts.extend(extra_args) - def cmdify(self): + def cmdify(self, extra_args=None): """Encode into a cmd-executable string. This re-implements CreateProcess's quoting logic to turn a list of @@ -54,6 +54,8 @@ class Script(object): See also: https://docs.python.org/3/library/subprocess.html#converting-argument-sequence """ + if extra_args: + self.extend(extra_args) return ' '.join( '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', arg)) for arg in self._parts diff --git a/pipenv/core.py b/pipenv/core.py index b42892eb..89a167ba 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -25,8 +25,7 @@ from blindspin import spinner from requests.packages import urllib3 from requests.packages.urllib3.exceptions import InsecureRequestWarning -from.project import Project -from .requirements import PipenvRequirement +from .project import Project from .utils import ( convert_deps_from_pip, convert_deps_to_pip, @@ -1400,9 +1399,9 @@ def pip_install( f.write(package_name) # Install dependencies when a package is a VCS dependency. try: - req = PipenvRequirement.from_line( + req = get_requirement( package_name.split('--hash')[0].split('--trusted-host')[0] - ).requirement.vcs + ).vcs except (pip9._vendor.pyparsing.ParseException, ValueError) as e: click.echo('{0}: {1}'.format(crayons.red('WARNING'), e), err=True) click.echo( @@ -2472,8 +2471,7 @@ def do_clean( ) installed_package_names = [] for installed in installed_packages: - pipenvreq = PipenvRequirement.from_line(installed) - r = pipenvreq.requirement + r = get_requirement(installed) # Ignore editable installations. if not r.editable: installed_package_names.append(r.name.lower()) diff --git a/pipenv/requirements.py b/pipenv/requirements.py deleted file mode 100644 index 96eb32c1..00000000 --- a/pipenv/requirements.py +++ /dev/null @@ -1,1060 +0,0 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import -import abc -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 -import attr -from attr import attrs, attrib, Factory, validators -from pip9.index import Link -from pip9.download import path_to_url -from pip9.req.req_install import _strip_extras -from pip9._vendor.distlib.markers import Evaluator -from pip9._vendor.packaging.markers import Marker, InvalidMarker -from pip9._vendor.packaging.specifiers import SpecifierSet, InvalidSpecifier -from pipenv.utils import SCHEME_LIST, VCS_LIST, is_installable_file, is_vcs, multi_split, get_converted_relative_path, is_star, is_valid_url -from first import first - -try: - from pathlib import Path -except ImportError: - from pathlib2 import Path -HASH_STRING = ' --hash={0}' - - -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 _validate_vcs(instance, attr_, value): - if value not in VCS_LIST: - raise ValueError('Invalid vcs {0!r}'.format(value)) - - -def _validate_path(instance, attr_, value): - if not os.path.exists(value): - raise ValueError('Invalid path {0!r}', format(value)) - - -def _validate_markers(instance, attr_, value): - try: - Marker('{0}{1}'.format(attr_, value)) - except InvalidMarker: - raise ValueError('Invalid Marker {0}{1}'.format(attr_, value)) - - -def _validate_specifiers(instance, attr_, value): - try: - SpecifierSet(value) - except InvalidMarker: - raise ValueError('Invalid Specifiers {0}'.format(value)) - - -def _optional_instance_of(cls): - return 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='') - - -class BaseRequirement(abc.ABCMeta): - - @classmethod - def from_line(cls, line): - """Returns a requirement from a requirements.txt or pip-compatible line""" - raise NotImplementedError - - @abc.abstractmethod - def as_line(self): - """Returns the current requirement as a pip-compatible line""" - - @classmethod - def from_pipfile(cls, name, pipfile): - """Returns a requirement from a pipfile entry""" - raise NotImplementedError - - @abc.abstractmethod - def pipfile_part(self): - """Returns the current requirement as a pipfile entry""" - - -@attrs -class PipenvMarkers(BaseRequirement): - """System-level requirements - see PEP508 for more detail""" - os_name = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - sys_platform = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - platform_machine = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - platform_python_implementation = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - platform_release = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - platform_system = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - platform_version = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - python_version = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - python_full_version = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - implementation_name = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - implementation_version = attrib( - default=None, validator=validators.optional(_validate_markers) - ) - - @property - def line_part(self): - return ' and '.join( - ['{0} {1}'.format(k, v) for k, v in self.__dict__.items() if v] - ) - - @property - def pipfile_part(self): - return {'markers': self.as_line} - - -@attrs -class NamedRequirement(BaseRequirement): - name = attrib() - version = attrib(validator=_validate_specifiers) - req = attrib(default=None) - - @req.default - def get_requirement(self): - return requirements.parse('{0}'.format(self.line_part)) - - @classmethod - def from_line(cls, line): - req = requirements.parse(line) - return cls(name=req.name, version=req.specifier, req=req) - - @classmethod - def from_pipfile(cls, name, pipfile): - creation_args = {k: v for k, v in pipfile.items()} - creation_args['name'] = name - creation_args['version'] = _get_version(pipfile) - return cls(**creation_args) - - @property - def line_part(self): - return '{self.name}{self.version}'.format(self=self) - - @property - def pipfile_part(self): - pipfile_dict = attr.asdict(self) - name = pipfile_dict.pop('name') - return {name: pipfile_dict} - - -@attrs -class FileRequirement(BaseRequirement): - """File requirements for tar.gz installable files or wheels or setup.py - containing directories.""" - path = attrib(default=None, validator=validators.optional(_validate_path)) - # : path to hit - without any of the VCS prefixes (like git+ / http+ / etc) - uri = attrib() - name = attrib() - link = attrib() - editable = attrib(default=None) - req = attrib() - - @uri.default - def get_uri(self): - if self.path and not self.uri: - self.uri = path_to_url(os.path.abspath(self.path)) - - @name.default - def get_name(self): - loc = self.path or self.uri - hashed_loc = hashlib.sha256(loc.encode('utf-8')).hexdigest() - hash_fragment = hashed_loc[-7:] - return hash_fragment - - @link.default - def get_link(self): - target = '{0}#egg={1}'.format(self.uri, self.name) - return Link(self.uri) - - @classmethod - def from_line(cls, line): - link = None - path = None - editable = line.startswith('-e ') - line = line.split(' ', 1)[1] if editable else line - if not any([is_installable_file(line), is_valid_url(line)]): - raise ValueError( - 'Supplied requirement is not installable: {0!r}'.format(line) - ) - - if is_valid_url(line): - link = Link(line) - else: - _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) - return cls( - name=link.egg_fragment, - path=path, - uri=link.url_without_fragment, - link=link, - editable=editable, - ) - - @classmethod - def from_pipfile(cls, name, pipfile): - path = pipfile.get('path') - uri = pipfile.get('uri') - editable = pipfile.get('editable') - return cls(name=name, path=path, uri=uri, editable=editable) - - @req.default - def get_requirement(self): - base = '{0}'.format(self.link) - req = first(requirements.parse(base)) - if self.editable: - req.editable = True - if self.link and self.link.scheme.startswith('file') and self.path: - req.path = self.path - req.local_file = True - req.uri = None - req.link = self.link - return req - - @property - def line_part(self): - seed = self.path or self.link.url or self.uri - editable = '-e ' if self.editable else '' - return '{0}{1}'.format(editable, seed) - - @property - def pipfile_part(self): - pipfile_dict = {k: v for k, v in self.__dict__.items() if v} - name = pipfile_dict.pop('name') - if self.path: - pipfile_dict.pop('uri') - return {name: pipfile_dict} - - -@attrs -class VCSRequirement(FileRequirement): - link = attrib() - name = attrib() - req = attrib() - # : vcs reference name (branch / commit / tag) - ref = attrib(default=None) - subdirectory = attrib(default=None) - vcs = attrib(validator=validators.optional(_validate_vcs), default=None) - uri = attrib(default=None) - - @link.default - def get_link(self): - return build_vcs_link(self.vcs, _clean_git_uri(self.uri), self.name, self.subdirectory) - - @name.default - def get_name(self): - return self.link.egg_fragment or self.req.name if self.req else self.link.filename - - @property - def vcs_uri(self): - uri = self.uri - if not any(uri.startswith('{0}+'.format(vcs)) for vcs in VCS_LIST): - uri = '{0}+{1}'.format(self.vcs, uri) - return uri - - @req.default - def get_requirement(self): - req = first(requirements.parse(self.line_part)) - if self.path and self.link and self.link.scheme.startswith('file'): - req.local_file = True - req.path = self.path - if self.editable: - req.editable = True - req.link = self.link - if self.uri != self.link.url and 'git+ssh://' in self.link.url and 'git+git@' in self.uri: - req.line = _strip_ssh_from_git_uri(req.line) - req.uri = _strip_ssh_from_git_uri(req.uri) - return req - - @classmethod - def from_pipfile(cls, name, pipfile): - creation_args = {} - pipfile_keys = [ - k - for k in ( - 'ref', 'vcs', 'subdirectory', 'path', 'editable', 'file', 'uri' - ) + - VCS_LIST - if k in pipfile - ] - for key in pipfile_keys: - creation_args[key] = pipfile.get(key) - creation_args['name'] = name - return cls(**creation_args) - - @classmethod - def from_line(cls, line, editable=None): - if line.startswith('-e '): - editable = True - line = line.split(' ', 1)[1] - if not is_valid_url(line): - line = path_to_url(line) - link = Link(line) - name = link.egg_fragment - uri = link.url_without_fragment - subdirectory = link.subdirectory_fragment - vcs, uri = _split_vcs_method(uri) - ref = None - if '@' in uri: - uri, ref = uri.rsplit('@', 1) - return cls( - name=name, - ref=ref, - vcs=vcs, - subdirectory=subdirectory, - link=link, - path=path, - editable=editable, - uri=uri, - ) - - @property - def line_part(self): - """requirements.txt compatible line part sans-extras""" - base = '{0}'.format(self.link) - if self.editable: - base = '-e {0}'.format(base) - return base - - @property - def pipfile_part(self): - pipfile_dict = {k: v for k, v in self.__dict__.items() if v} - name = pipfile_dict.pop('name') - if self.path: - pipfile_dict.pop('uri') - return {name: pipfile_dict} - - -@attrs -class NewRequirement(object): - name = attrib(default='') - vcs = attrib(default=None, validator=validators.optional(_validate_vcs)) - req = attrib( - default=None, validator=_optional_instance_of(BaseRequirement) - ) - markers = attrib(default=None) - specifiers = attrib(validator=validators.optional(_validate_specifiers)) - index = attrib(default=None) - editable = attrib(default=None) - hashes = attrib(default=None, converter=list) - extras = attrib(default=Factory(list)) - - @name.default - def get_name(self): - return self.req.name - - @property - def requirement(self): - return self.req.req - - @property - def hashes_as_pip(self): - if self.hashes: - return ''.join([HASH_STRING.format(h) for h in self.hashes]) - - return '' - - @property - def markers_as_pip(self): - if self.markers: - return '; {0}'.format(self.markers) - - return '' - - @property - def extras_as_pip(self): - if self.extras: - return '[{0}]'.format(','.join(self.extras)) - - return '' - - @specifiers.default - def get_specifiers(self): - if self.req and self.req.req.specifier: - return self.req.req.specifier - return - - @classmethod - def from_line(cls, line): - hashes = None - if '--hash=' in line: - hashes = line.split(' --hash=') - line, hashes = hashes[0], hashes[1:] - original_line = line - editable = line.startswith('-e ') - line = line.split(' 'm 1) if editable else line - line, markers = PipenvRequirement._split_markers(line) - line, extras = _strip_extras(line) - vcs = None - if is_installable_file(line): - r = FileRequirement(path=line) - elif is_vcs(line): - r = VCSRequirement.from_line(line) - else: - r = NamedRequirement.from_line(line) - r.extras = first( - requirements.parse('fakepkg{0}'.format(_extras_to_string(extras))) - ) - return cls( - name=r.req.name, - vcs=vcs, - req=r, - markers=markers, - extras=extras, - editable=editable, - hashes=hashes, - ) - - @property - def as_line(self): - return '{0}{1}{2}{3}{4} {5}'.format( - self.req.line_part, - self.extras_as_pip, - self.requirement.specs, - self.markers_as_pip, - self.hashes_as_pip, - self.index, - ) - - @property - def as_pipfile(self): - good_keys = ('hashes', 'path', 'uri', 'file', 'extras', 'markers', 'editable', 'vcs', 'version', 'index', 'ref', 'subdirectory') + VCS_LIST - req_dict = attr.asdict(self, recurse=False) - name = first(self.req.pipfile_part.keys()) - base_dict = self.req.pipfile_part['name'].copy() - base_dict.update({k: v for k, v in req_dict if k in good_keys and v is not None}) - return {name: base_dict} - - -@attrs -class PipfileRequirement(object): - path = attrib(default=None) - uri = attrib(default=None) - file = 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: - marker = marker.strip() - marker_value = _pipfile.pop(marker).strip() - marker_string = '{0}{1}'.format(marker, marker_value) - markers.append(marker_string) - _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.path and not self.uri: - req_uri = path_to_url(os.path.abspath(self.path)) - if self._link: - line = self._link.url - elif req_uri: - line = req_uri - elif self.file: - line = self.file - else: - line = self.pip_version - 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=line, - ) - - -@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) - 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 = {} - # hash paths of local files or remote zip/tarballs in non-editable mode - if req.local_file or (req.uri and not any([req.vcs, req.name, req.link])): - 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 'name' not in req_dict: - req_dict['name'] = req.name or req.line or req.link.egg_fragment - name = req_dict.pop('name') - if len(req_dict.keys()) == 1 and req_dict.get('version'): - return {name: req_dict.get('version')} - - return {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 link and not vcs: - _line = link.url - elif vcs or is_vcs(_line): - _line = uri or path or line or _clean_git_uri(link.url) - _line = '{0}{1}'.format(_editable, _line) - req = first(requirements.parse(_line)) - req.line = line or path or uri or getattr(link, 'url', req.line) - if editable: - req.editable = True - if req.name and not any( - getattr(req, prop) for prop in ['uri', 'path'] - ): - ### This is the stuff I still need to reimplement - 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=None -): - if extras is 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) - - -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', '') diff --git a/pipenv/utils.py b/pipenv/utils.py index d3802703..70e1666c 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -65,7 +65,8 @@ 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. Ensures that we can accept both local and relative paths, file and VCS URIs, @@ -76,8 +77,79 @@ def get_requirement(dep): :param str dep: A requirement line :returns: :class:`requirements.Requirement` object """ - from .requirements import PipenvRequirement - return PipenvRequirement.from_line(dep).requirement + 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 def cleanup_toml(tml): @@ -441,9 +513,81 @@ 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 - req = PipenvRequirement.from_line(dep) - return req.as_pipfile() + 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 or req.markers: + specs = None + keys = [k for k in ['specs', 'extras', 'markers'] if getattr(req, k, None)] + + # Comparison operators: e.g. Django>1.10 + if req.specs: + r = multi_split(dep, '!=<>~') + specs = dep[len(r[0]):] + + if len(keys) > 1: + dependency[req.name] = {} + for k in keys: + entry_key = 'version' if k == 'specs' else k + entry = {entry_key: getattr(req, k)} if k != 'extras' else extras + dependency[req.name].update(entry) + else: + entry_key = 'version' if keys[0] == 'specs' else keys[0] + entry = {entry_key: getattr(req, keys[0])} if entry_key != 'extras' else extras + dependency[req.name] = entry + + # 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 def is_star(val): @@ -456,16 +600,94 @@ 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_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) + 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) if not r: return dependencies diff --git a/pipenv/vendor/attr/__init__.py b/pipenv/vendor/attr/__init__.py deleted file mode 100644 index ea330007..00000000 --- a/pipenv/vendor/attr/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index 42a91ee5..00000000 --- a/pipenv/vendor/attr/_compat.py +++ /dev/null @@ -1,146 +0,0 @@ -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 deleted file mode 100644 index 8ec92096..00000000 --- a/pipenv/vendor/attr/_config.py +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 798043af..00000000 --- a/pipenv/vendor/attr/_funcs.py +++ /dev/null @@ -1,212 +0,0 @@ -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 deleted file mode 100644 index ebe12078..00000000 --- a/pipenv/vendor/attr/_make.py +++ /dev/null @@ -1,1680 +0,0 @@ -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 deleted file mode 100644 index 3b3bac92..00000000 --- a/pipenv/vendor/attr/converters.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -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 deleted file mode 100644 index f949f3c9..00000000 --- a/pipenv/vendor/attr/exceptions.py +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index d1bad35e..00000000 --- a/pipenv/vendor/attr/filters.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -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 deleted file mode 100644 index f8892fcd..00000000 --- a/pipenv/vendor/attr/validators.py +++ /dev/null @@ -1,166 +0,0 @@ -""" -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/tests/pypi/colorama/colorama-0.3.9-py2.py3-none-any.whl b/tests/pypi/colorama/colorama-0.3.9-py2.py3-none-any.whl new file mode 100644 index 00000000..29b83f06 Binary files /dev/null and b/tests/pypi/colorama/colorama-0.3.9-py2.py3-none-any.whl differ diff --git a/tests/pypi/pandas/pandas-0.22.0-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl new file mode 100644 index 00000000..6230a732 Binary files /dev/null and b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl differ diff --git a/tests/pypi/pandas/pandas-0.22.0-cp27-cp27m-win_amd64.whl b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27m-win_amd64.whl new file mode 100644 index 00000000..bf879e90 Binary files /dev/null and b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27m-win_amd64.whl differ diff --git a/tests/pypi/pandas/pandas-0.22.0-cp27-cp27mu-manylinux1_i686.whl b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27mu-manylinux1_i686.whl new file mode 100644 index 00000000..d01d7706 Binary files /dev/null and b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27mu-manylinux1_i686.whl differ diff --git a/tests/pypi/pandas/pandas-0.22.0-cp27-cp27mu-manylinux1_x86_64.whl b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27mu-manylinux1_x86_64.whl new file mode 100644 index 00000000..878bb1bd Binary files /dev/null and b/tests/pypi/pandas/pandas-0.22.0-cp27-cp27mu-manylinux1_x86_64.whl differ diff --git a/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl b/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl new file mode 100644 index 00000000..19a474d9 Binary files /dev/null and b/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl differ diff --git a/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-manylinux1_x86_64.whl b/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-manylinux1_x86_64.whl new file mode 100644 index 00000000..6ef88537 Binary files /dev/null and b/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-manylinux1_x86_64.whl differ diff --git a/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-win_amd64.whl b/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-win_amd64.whl new file mode 100644 index 00000000..871d5094 Binary files /dev/null and b/tests/pypi/pandas/pandas-0.22.0-cp36-cp36m-win_amd64.whl differ diff --git a/tests/pypi/py/py-1.5.3-py2.py3-none-any.whl b/tests/pypi/py/py-1.5.3-py2.py3-none-any.whl new file mode 100644 index 00000000..127d886e Binary files /dev/null and b/tests/pypi/py/py-1.5.3-py2.py3-none-any.whl differ diff --git a/tests/pypi/pytest/pytest-3.1.0-py2.py3-none-any.whl b/tests/pypi/pytest/pytest-3.1.0-py2.py3-none-any.whl new file mode 100644 index 00000000..dfaf980a Binary files /dev/null and b/tests/pypi/pytest/pytest-3.1.0-py2.py3-none-any.whl differ diff --git a/tests/pypi/pytest/pytest-3.1.1-py2.py3-none-any.whl b/tests/pypi/pytest/pytest-3.1.1-py2.py3-none-any.whl new file mode 100644 index 00000000..aaab63cd Binary files /dev/null and b/tests/pypi/pytest/pytest-3.1.1-py2.py3-none-any.whl differ diff --git a/tests/test_cmdparse.py b/tests/test_cmdparse.py index 776ef61f..4e72ac5b 100644 --- a/tests/test_cmdparse.py +++ b/tests/test_cmdparse.py @@ -1,20 +1,27 @@ import textwrap from pipenv.cmdparse import Script +import pytest +@pytest.mark.run +@pytest.mark.script def test_parse(): script = Script.parse(['python', '-c', "print('hello')"]) assert script.command == 'python' assert script.args == ['-c', "print('hello')"], script +@pytest.mark.run +@pytest.mark.script def test_cmdify(): script = Script.parse(['python', '-c', "print('hello')"]) cmd = script.cmdify(['--verbose']) assert cmd == '"python" "-c" "print(\'hello\')" "--verbose"', script +@pytest.mark.run +@pytest.mark.script def test_cmdify_complex(): script = Script.parse(' '.join([ '"C:\\Program Files\\Python36\\python.exe" -c', diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index 7fe953ab..82641945 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -15,6 +15,7 @@ from pipenv.vendor import requests from pipenv.patched import pipfile from pipenv.project import Project from pipenv.vendor.six import PY2 +from flaky import flaky if PY2: class ResourceWarning(Warning): pass @@ -237,8 +238,16 @@ class TestPipenv: @pytest.mark.install def test_install_parse_error(self, pypi): with PipenvInstance(pypi=pypi) as p: + # Make sure unparseable packages don't wind up in the pipfile # Escape $ for shell input + with open(p.pipfile_path, 'w') as f: + contents = """ +[packages] + +[dev-packages] + """.strip() + f.write(contents) c = p.pipenv('install requests u/\\/p@r\$34b13+pkg') assert c.return_code != 0 assert 'u/\\/p@r$34b13+pkg' not in p.pipfile['packages'] @@ -299,9 +308,10 @@ class TestPipenv: @pytest.mark.dev @pytest.mark.install + @flaky def test_install_without_dev(self, pypi): """Ensure that running `pipenv install` doesn't install dev packages""" - with PipenvInstance(pypi=pypi) as p: + with PipenvInstance(pypi=pypi, chdir=True) as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -419,8 +429,9 @@ tablib = "*" @pytest.mark.extras @pytest.mark.install + @flaky def test_extras_install(self, pypi): - with PipenvInstance(pypi=pypi) as p: + with PipenvInstance(pypi=pypi, chdir=True) as p: c = p.pipenv('install requests[socks]') assert c.return_code == 0 assert 'requests' in p.pipfile['packages'] @@ -469,8 +480,9 @@ setup( @pytest.mark.vcs @pytest.mark.install @needs_internet + @flaky def test_basic_vcs_install(self, pip_src_dir, pypi): - with PipenvInstance(pypi=pypi) as p: + with PipenvInstance(pypi=pypi, chdir=True) as p: c = p.pipenv('install git+https://github.com/requests/requests.git#egg=requests') assert c.return_code == 0 # edge case where normal package starts with VCS name shouldn't be flagged as vcs @@ -515,11 +527,12 @@ tablib = "<0.12" @pytest.mark.run @pytest.mark.install + @flaky def test_multiprocess_bug_and_install(self, pypi): with temp_environ(): os.environ['PIPENV_MAX_SUBPROCESS'] = '2' - with PipenvInstance(pypi=pypi) as p: + with PipenvInstance(pypi=pypi, chdir=True) as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -545,9 +558,10 @@ tpfd = "*" @pytest.mark.sequential @pytest.mark.install + @flaky def test_sequential_mode(self, pypi): - with PipenvInstance(pypi=pypi) as p: + with PipenvInstance(pypi=pypi, chdir=True) as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -575,6 +589,7 @@ tpfd = "*" @pytest.mark.resolver @pytest.mark.backup_resolver @needs_internet + @flaky def test_backup_resolver(self): with PipenvInstance() as p: with open(p.pipfile_path, 'w') as f: @@ -632,6 +647,7 @@ requests = {version = "*", os_name = "== 'splashwear'"} @pytest.mark.markers @pytest.mark.install + @flaky def test_top_level_overrides_environment_markers(self, pypi): """Top-level environment markers should take precedence. """ @@ -1079,9 +1095,10 @@ requests = "==2.14.0" @pytest.mark.files @pytest.mark.urls @needs_internet - def test_urls_work(self): + @flaky + def test_urls_work(self, pypi): - with PipenvInstance(chdir=True) as p: + with PipenvInstance(chdir=True, pypi=pypi) as p: c = p.pipenv('install https://github.com/divio/django-cms/archive/release/3.4.x.zip') assert c.return_code == 0 @@ -1096,7 +1113,7 @@ requests = "==2.14.0" @pytest.mark.files @pytest.mark.resolver @pytest.mark.eggs - def test_local_package(self, pip_src_dir): + def test_local_package(self, pip_src_dir, pypi): """This test ensures that local packages (directories with a setup.py) installed in editable mode have their dependencies resolved as well""" file_name = 'tablib-0.12.1.tar.gz' @@ -1104,7 +1121,7 @@ requests = "==2.14.0" # Not sure where travis/appveyor run tests from test_dir = os.path.dirname(os.path.abspath(__file__)) source_path = os.path.abspath(os.path.join(test_dir, 'test_artifacts', file_name)) - with PipenvInstance(chdir=True) as p: + with PipenvInstance(chdir=True, pypi=pypi) as p: # This tests for a bug when installing a zipfile in the current dir copy_to = os.path.join(p.path, file_name) shutil.copy(source_path, copy_to) @@ -1117,13 +1134,13 @@ requests = "==2.14.0" @pytest.mark.install @pytest.mark.files - def test_local_zipfiles(self): + def test_local_zipfiles(self, pypi): file_name = 'tablib-0.12.1.tar.gz' # Not sure where travis/appveyor run tests from test_dir = os.path.dirname(os.path.abspath(__file__)) source_path = os.path.abspath(os.path.join(test_dir, 'test_artifacts', file_name)) - with PipenvInstance(chdir=True) as p: + with PipenvInstance(chdir=True, pypi=pypi) as p: # This tests for a bug when installing a zipfile in the current dir shutil.copy(source_path, os.path.join(p.path, file_name)) @@ -1183,6 +1200,7 @@ requests = "==2.14.0" @pytest.mark.install @pytest.mark.local_file + @flaky def test_install_local_file_collision(self, pypi): with PipenvInstance(pypi=pypi) as p: target_package = 'alembic' diff --git a/tests/test_requirements.py b/tests/test_requirements.py deleted file mode 100644 index 38742f23..00000000 --- a/tests/test_requirements.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding=utf-8 -*- -import os -import pytest -from first import first -from pipenv import requirements -from pipenv.utils import get_requirement, convert_deps_from_pip, convert_deps_to_pip - - -class TestRequirements: - - @pytest.mark.requirement - @pytest.mark.parametrize( - 'line, pipfile', - [ - ['requests', {'requests': '*'}], - ['requests[socks]', {'requests': {'extras': ['socks'], 'version': '*'}}], - ['django>1.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 6d99b74e..26db37fb 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'], 'version': '*'}} + assert dep == {'requests': {'extras': ['socks']}} # requests[socks] w/ version dep = 'requests[socks]==1.10' dep = pipenv.utils.convert_deps_from_pip(dep)