Fix all tests and split out requirements refactor

- includes missing artifacts
- fixes cmdprse script
- Remove extraneous changes
- Fix tests
This commit is contained in:
Dan Ryan
2018-04-03 23:01:04 -04:00
parent ccc0311362
commit 61418fda20
30 changed files with 297 additions and 3654 deletions
+1 -4
View File
@@ -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
Generated
+15 -114
View File
@@ -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": [
+3 -1
View File
@@ -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
+4 -6
View File
@@ -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())
File diff suppressed because it is too large Load Diff
+237 -15
View File
@@ -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=<package-name>.'.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
-57
View File
@@ -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",
]
-146
View File
@@ -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()
-23
View File
@@ -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
-212
View File
@@ -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)
-1680
View File
File diff suppressed because it is too large Load Diff
-24
View File
@@ -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
-48
View File
@@ -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
"""
-52
View File
@@ -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_
-166
View File
@@ -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 (
"<instance_of validator for type {type!r}>"
.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 (
"<provides validator for interface {interface!r}>"
.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
<https://zopeinterface.readthedocs.io/en/latest/>`_).
: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 (
"<optional validator for {what} or None>"
.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 (
"<in_ validator with options {options!r}>"
.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)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7
View File
@@ -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',
+29 -11
View File
@@ -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'
-34
View File
@@ -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
+1 -1
View File
@@ -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)