diff --git a/pipenv/project.py b/pipenv/project.py index bf86b10c..e81182b0 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -20,7 +20,7 @@ except ImportError: from pathlib2 import Path from .cmdparse import Script -from .vendor.requirementslib.requirements import Requirement +from .vendor.requirementslib import Requirement from .utils import ( atomic_open_for_write, mkdir_p, @@ -46,7 +46,6 @@ from .environments import ( PIPENV_PYTHON, PIPENV_DEFAULT_PYTHON_VERSION, ) -from .vendor.first import first def _normalized(p): diff --git a/pipenv/utils.py b/pipenv/utils.py index 56a387c8..8427b7b0 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -248,10 +248,10 @@ def actually_resolve_deps( dep, url = dep.split(' -i ') req = Requirement.from_line(dep) - # req.as_line() is theoratically the same as dep, but is guarenteed to + # req.as_line() is theoratically the same as dep, but is guaranteed to # be normalized. This is safer than passing in dep. # TODO: Stop passing dep lines around; just use requirement objects. - constraints.append(req.as_line()) + constraints.append(req.as_line(sources=None)) # extra_constraints = [] if url: @@ -509,12 +509,9 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): 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 = Requirement.from_pipfile(dep_name, indexes, dep) + new_dep = Requirement.from_pipfile(dep_name, dep) req = new_dep.as_line( - project=project, - include_index=include_index + sources=indexes if include_index else None ).strip() dependencies.append(req) if not r: @@ -1191,7 +1188,7 @@ def get_vcs_deps( pipfile_name = vcs_uri_map[_vcs_match]["name"] pipfile_rev = vcs_uri_map[_vcs_match]["ref"] - pipfile_req = Requirement.from_pipfile(pipfile_name, [], packages[pipfile_name]) + pipfile_req = Requirement.from_pipfile(pipfile_name, packages[pipfile_name]) names = {pipfile_name} backend = vcs_registry()._registry.get(pipfile_req.vcs) # TODO: Why doesn't pip freeze list 'git+git://' formatted urls? diff --git a/pipenv/vendor/requirementslib/__init__.py b/pipenv/vendor/requirementslib/__init__.py index 11e09675..52550ba2 100644 --- a/pipenv/vendor/requirementslib/__init__.py +++ b/pipenv/vendor/requirementslib/__init__.py @@ -1,4 +1,6 @@ # -*- coding=utf-8 -*- -__version__ = "0.0.7.dev0" +__version__ = "0.2.0" -from .requirements import Requirement + +from .exceptions import RequirementError +from .models import Requirement, Lockfile, Pipfile diff --git a/pipenv/vendor/requirementslib/_compat.py b/pipenv/vendor/requirementslib/_compat.py index e6de02bc..b4584d8e 100644 --- a/pipenv/vendor/requirementslib/_compat.py +++ b/pipenv/vendor/requirementslib/_compat.py @@ -1,6 +1,25 @@ # -*- coding=utf-8 -*- -# -*- coding=utf-8 -*- import importlib +import six + +# Use these imports as compatibility imports +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path + +try: + from urllib.parse import urlparse, unquote +except ImportError: + from urlparse import urlparse, unquote + +if six.PY2: + + class FileNotFoundError(IOError): + pass +else: + class FileNotFoundError(FileNotFoundError): + pass def do_import(module_path, subimport=None, old_path=None): @@ -38,3 +57,4 @@ get_installed_distributions = do_import( is_installable_file = do_import("utils.misc", "is_installable_file", old_path="utils") is_installable_dir = do_import("utils.misc", "is_installable_dir", old_path="utils") PyPI = do_import("models.index", "PyPI") +make_abstract_dist = do_import("operations.prepare", "make_abstract_dist", old_path="req.req_set") diff --git a/pipenv/vendor/requirementslib/_vendor/Makefile b/pipenv/vendor/requirementslib/_vendor/Makefile new file mode 100644 index 00000000..5c44fea4 --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/Makefile @@ -0,0 +1,14 @@ +# Taken from pip: https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/src/pip/_vendor/Makefile +all: clean vendor + +clean: + @# Delete vendored items + find . -maxdepth 1 -mindepth 1 -type d -exec rm -rf {} \; + +vendor: + @# Install vendored libraries + pip install -t . -r vendor.txt + + @# Cleanup .egg-info directories + rm -rf *.egg-info + rm -rf *.dist-info diff --git a/pipenv/vendor/requirementslib/_vendor/__init__.py b/pipenv/vendor/requirementslib/_vendor/__init__.py new file mode 100644 index 00000000..a6bcbda1 --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/__init__.py @@ -0,0 +1 @@ +# -*- coding=utf-8 -*- diff --git a/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE b/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE new file mode 100644 index 00000000..6f62d44e --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made +under the terms of *both* these licenses. diff --git a/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE.APACHE b/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE.APACHE new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE.APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE.BSD b/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE.BSD new file mode 100644 index 00000000..698fc43e --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/pipfile/LICENSE.BSD @@ -0,0 +1,23 @@ +Copyright (c) Kenneth Reitz and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/requirementslib/_vendor/pipfile/__about__.py b/pipenv/vendor/requirementslib/_vendor/pipfile/__about__.py new file mode 100644 index 00000000..3ba72191 --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/pipfile/__about__.py @@ -0,0 +1,21 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", +] + +__title__ = "pipfile" +__summary__ = "" +__uri__ = "https://github.com/pypa/pipfile" + +__version__ = "0.0.2" + +__author__ = "Kenneth Reitz and individual contributors" +__email__ = "me@kennethreitz.org" + +__license__ = "BSD or Apache License, Version 2.0" +__copyright__ = "Copyright 2017 %s" % __author__ diff --git a/pipenv/vendor/requirementslib/_vendor/pipfile/__init__.py b/pipenv/vendor/requirementslib/_vendor/pipfile/__init__.py new file mode 100644 index 00000000..fddd4f90 --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/pipfile/__init__.py @@ -0,0 +1,11 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +from .__about__ import ( + __author__, __copyright__, __email__, __license__, __summary__, __title__, + __uri__, __version__ +) + +from .api import load, Pipfile diff --git a/pipenv/vendor/requirementslib/_vendor/pipfile/api.py b/pipenv/vendor/requirementslib/_vendor/pipfile/api.py new file mode 100644 index 00000000..e8fa0277 --- /dev/null +++ b/pipenv/vendor/requirementslib/_vendor/pipfile/api.py @@ -0,0 +1,230 @@ +import toml + +import codecs +import json +import hashlib +import platform +import six +import sys +import os + + +DEFAULT_SOURCE = { + u'url': u'https://pypi.org/simple', + u'verify_ssl': True, + u'name': u'pypi', +} + + +def format_full_version(info): + version = '{0.major}.{0.minor}.{0.micro}'.format(info) + kind = info.releaselevel + if kind != 'final': + version += kind[0] + str(info.serial) + return version + + +def walk_up(bottom): + """mimic os.walk, but walk 'up' instead of down the directory tree. + From: https://gist.github.com/zdavkeos/1098474 + """ + + bottom = os.path.realpath(bottom) + + # get files in current dir + try: + names = os.listdir(bottom) + except Exception: + return + + dirs, nondirs = [], [] + for name in names: + if os.path.isdir(os.path.join(bottom, name)): + dirs.append(name) + else: + nondirs.append(name) + + yield bottom, dirs, nondirs + + new_path = os.path.realpath(os.path.join(bottom, '..')) + + # see if we are at the top + if new_path == bottom: + return + + for x in walk_up(new_path): + yield x + + +class PipfileParser(object): + def __init__(self, filename='Pipfile'): + self.filename = filename + self.sources = [] + self.groups = { + 'default': [], + 'develop': [] + } + self.group_stack = ['default'] + self.requirements = [] + + def __repr__(self): + return ' 1: for k in collisions[1:]: - _ = pipfile_dict.pop(k) + pipfile_dict.pop(k) return {name: pipfile_dict} -@attrs +@attr.s class VCSRequirement(FileRequirement): - editable = attrib(default=None) - uri = attrib(default=None) - path = attrib(default=None, validator=validators.optional(_validate_path)) - vcs = attrib(validator=validators.optional(_validate_vcs), default=None) + editable = attr.ib(default=None) + uri = attr.ib(default=None) + path = attr.ib(default=None, validator=attr.validators.optional(validate_path)) + vcs = attr.ib(validator=attr.validators.optional(validate_vcs), default=None) # : vcs reference name (branch / commit / tag) - ref = attrib(default=None) - subdirectory = attrib(default=None) - name = attrib() - link = attrib() - req = attrib() + ref = attr.ib(default=None) + subdirectory = attr.ib(default=None) + name = attr.ib() + link = attr.ib() + req = attr.ib() _INCLUDE_FIELDS = ( - "editable", "uri", "path", "vcs", "ref", "subdirectory", "name", "link", "req" + "editable", + "uri", + "path", + "vcs", + "ref", + "subdirectory", + "name", + "link", + "req", ) @link.default def get_link(self): return build_vcs_link( self.vcs, - _clean_git_uri(self.uri), + add_ssh_scheme_to_git_uri(self.uri), name=self.name, ref=self.ref, subdirectory=self.subdirectory, @@ -451,7 +326,11 @@ class VCSRequirement(FileRequirement): @name.default def get_name(self): - return self.link.egg_fragment or self.req.name if self.req else "" + return ( + self.link.egg_fragment or self.req.name + if self.req + else super(VCSRequirement, self).get_name() + ) @property def vcs_uri(self): @@ -476,8 +355,8 @@ class VCSRequirement(FileRequirement): 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) + req.line = strip_ssh_from_git_uri(req.line) + req.uri = strip_ssh_from_git_uri(req.uri) if not req.name: raise ValueError( "pipenv requires an #egg fragment for version controlled " @@ -502,11 +381,9 @@ class VCSRequirement(FileRequirement): for key in pipfile_keys: if key in VCS_LIST: creation_args["vcs"] = key - composed_uri = _clean_git_uri( + composed_uri = add_ssh_scheme_to_git_uri( "{0}+{1}".format(key, pipfile.get(key)) - ).lstrip( - "{0}+".format(key) - ) + ).lstrip("{0}+".format(key)) is_url = is_valid_url(pipfile.get(key)) or is_valid_url(composed_uri) target_key = "uri" if is_url else "path" creation_args[target_key] = pipfile.get(key) @@ -521,8 +398,8 @@ class VCSRequirement(FileRequirement): if line.startswith("-e "): editable = True line = line.split(" ", 1)[1] - vcs_line = _clean_git_uri(line) - vcs_method, vcs_location = _split_vcs_method(vcs_line) + vcs_line = add_ssh_scheme_to_git_uri(line) + vcs_method, vcs_location = split_vcs_method_from_uri(vcs_line) if not is_valid_url(vcs_location) and os.path.exists(vcs_location): path = get_converted_relative_path(vcs_location) vcs_location = path_to_url(os.path.abspath(vcs_location)) @@ -530,7 +407,7 @@ class VCSRequirement(FileRequirement): name = link.egg_fragment uri = link.url_without_fragment if "git+git@" in line: - uri = _strip_ssh_from_git_uri(uri) + uri = strip_ssh_from_git_uri(uri) subdirectory = link.subdirectory_fragment ref = None if "@" in link.show_url: @@ -562,32 +439,32 @@ class VCSRequirement(FileRequirement): if src_keys: chosen_key = first(src_keys) vcs_type = pipfile.pop("vcs") - _, pipfile_url = _split_vcs_method(pipfile.get(chosen_key)) + _, pipfile_url = split_vcs_method_from_uri(pipfile.get(chosen_key)) pipfile[vcs_type] = pipfile_url for removed in src_keys: - _ = pipfile.pop(removed) + pipfile.pop(removed) return pipfile @property def pipfile_part(self): - pipfile_dict = attr.asdict(self, filter=_filter_none).copy() + pipfile_dict = attr.asdict(self, filter=filter_none).copy() if "vcs" in pipfile_dict: pipfile_dict = self._choose_vcs_source(pipfile_dict) name = pipfile_dict.pop("name") return {name: pipfile_dict} -@attrs +@attr.s class Requirement(object): - name = attrib() - 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=Factory(list), converter=list) - extras = attrib(default=Factory(list)) + name = attr.ib() + vcs = attr.ib(default=None, validator=attr.validators.optional(validate_vcs)) + req = attr.ib(default=None, validator=optional_instance_of(BaseRequirement)) + markers = attr.ib(default=None) + specifiers = attr.ib(validator=attr.validators.optional(validate_specifiers)) + index = attr.ib(default=None) + editable = attr.ib(default=None) + hashes = attr.ib(default=attr.Factory(list), converter=list) + extras = attr.ib(default=attr.Factory(list)) _ireq = None _INCLUDE_FIELDS = ("name", "markers", "index", "editable", "hashes", "extras") @@ -623,7 +500,7 @@ class Requirement(object): @specifiers.default def get_specifiers(self): if self.req and self.req.req.specifier: - return _specs_to_string(self.req.req.specs) + return specs_to_string(self.req.req.specs) return @property @@ -640,9 +517,7 @@ class Requirement(object): @property def normalized_name(self): - if not self.is_vcs and not self.is_file_or_url: - return pep423_name(self.name) - return self.name + return pep423_name(self.name) @classmethod def from_line(cls, line): @@ -651,14 +526,15 @@ class Requirement(object): hashes = line.split(" --hash=") line, hashes = hashes[0], hashes[1:] editable = line.startswith("-e ") - stripped_line = line.split(" ", 1)[1] if editable else line - line, markers = _split_markers(line) + line, markers = split_markers_from_line(line) line, extras = _strip_extras(line) + stripped_line = line.split(" ", 1)[1] if editable else line vcs = None # Installable local files and installable non-vcs urls are handled # as files, generally speaking if ( is_installable_file(stripped_line) + or is_installable_file(line) or (is_valid_url(stripped_line) and not is_vcs(stripped_line)) ): r = FileRequirement.from_line(line) @@ -672,7 +548,7 @@ class Requirement(object): r = NamedRequirement.from_line(stripped_line) if extras: extras = first( - requirements.parse("fakepkg{0}".format(_extras_to_string(extras))) + requirements.parse("fakepkg{0}".format(extras_to_string(extras))) ).extras r.req.extras = extras if markers: @@ -691,11 +567,11 @@ class Requirement(object): return cls(**args) @classmethod - def from_pipfile(cls, name, indexes, pipfile): + def from_pipfile(cls, name, pipfile): _pipfile = {} if hasattr(pipfile, "keys"): _pipfile = dict(pipfile).copy() - _pipfile["version"] = _get_version(pipfile) + _pipfile["version"] = get_version(pipfile) vcs = first([vcs for vcs in VCS_LIST if vcs in _pipfile]) if vcs: _pipfile["vcs"] = vcs @@ -717,7 +593,15 @@ class Requirement(object): args["hashes"] = _pipfile.get("hashes", [pipfile.get("hash")]) return cls(**args) - def as_line(self, include_index=False, project=None): + def as_line(self, sources=None): + """Format this requirement as a line in requirements.txt. + + If `sources` provided, it should be an sequence of mappings, containing + all possible sources to be used for this requirement. + + If `sources` is omitted or falsy, no index information will be included + in the requirement line. + """ line = "{0}{1}{2}{3}{4}".format( self.req.line_part, self.extras_as_pip, @@ -725,24 +609,27 @@ class Requirement(object): self.markers_as_pip, self.hashes_as_pip, ) - if include_index and not (self.requirement.local_file or self.vcs): - from .utils import prepare_pip_source_args + if sources 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)) + sources = [s for s in sources if s.get("name") == self.index] + index_string = " ".join(prepare_pip_source_args(sources)) line = "{0} {1}".format(line, index_string) return line - def as_pipfile(self, include_index=False): + def as_pipfile(self): good_keys = ( - "hashes", "extras", "markers", "editable", "version", "index" + "hashes", + "extras", + "markers", + "editable", + "version", + "index", ) + VCS_LIST req_dict = { k: v - for k, v in attr.asdict(self, recurse=False, filter=_filter_none).items() + for k, v in attr.asdict(self, recurse=False, filter=filter_none).items() if k in good_keys } name = self.name @@ -756,7 +643,7 @@ class Requirement(object): if "file" in base_dict and any(k in base_dict for k in conflicting_keys[1:]): conflicts = [k for k in (conflicting_keys[1:],) if k in base_dict] for k in conflicts: - _ = base_dict.pop(k) + base_dict.pop(k) if "hashes" in base_dict and len(base_dict["hashes"]) == 1: base_dict["hash"] = base_dict.pop("hashes")[0] if len(base_dict.keys()) == 1 and "version" in base_dict: @@ -777,56 +664,3 @@ class Requirement(object): else: self._ireq = InstallRequirement.from_line(ireq_line) return self._ireq - - -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 _specs_to_string(specs): - """Turn a list of specifier tuples into a string""" - if specs: - if isinstance(specs, six.string_types): - return specs - return ",".join(["".join(spec) for spec in specs]) - return "" - - -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 hasattr(pipfile_entry, "keys") and "version" in pipfile_entry: - if is_star(pipfile_entry.get("version")): - return "" - return pipfile_entry.get("version", "") - - if isinstance(pipfile_entry, six.string_types): - return pipfile_entry - return "" diff --git a/pipenv/vendor/requirementslib/models/utils.py b/pipenv/vendor/requirementslib/models/utils.py new file mode 100644 index 00000000..2336bffc --- /dev/null +++ b/pipenv/vendor/requirementslib/models/utils.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +import os +import six +from attr import validators +from first import first +from packaging.markers import Marker, InvalidMarker +from packaging.specifiers import SpecifierSet, InvalidSpecifier +from .._compat import Link +from ..utils import ( + SCHEME_LIST, + VCS_LIST, + is_star, +) + + +HASH_STRING = " --hash={0}" + + +def filter_none(k, v): + if v: + return True + return False + + +def optional_instance_of(cls): + return validators.optional(validators.instance_of(cls)) + + +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 specs_to_string(specs): + """Turn a list of specifier tuples into a string""" + if specs: + if isinstance(specs, six.string_types): + return specs + return ",".join(["".join(spec) for spec in specs]) + return "" + + +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 = add_ssh_scheme_to_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 hasattr(pipfile_entry, "keys") and "version" in pipfile_entry: + if is_star(pipfile_entry.get("version")): + return "" + return pipfile_entry.get("version", "") + + if isinstance(pipfile_entry, six.string_types): + return pipfile_entry + return "" + + +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 add_ssh_scheme_to_git_uri(uri): + """Cleans VCS uris from pip 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_markers_from_line(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 + + +def split_vcs_method_from_uri(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_.name, value)) + except InvalidMarker: + raise ValueError("Invalid Marker {0}{1}".format(attr_, value)) + + +def validate_specifiers(instance, attr_, value): + if value == "": + return True + try: + SpecifierSet(value) + except (InvalidMarker, InvalidSpecifier): + raise ValueError("Invalid Specifiers {0}".format(value)) diff --git a/pipenv/vendor/requirementslib/utils.py b/pipenv/vendor/requirementslib/utils.py index 5302630a..a5b15a26 100644 --- a/pipenv/vendor/requirementslib/utils.py +++ b/pipenv/vendor/requirementslib/utils.py @@ -1,5 +1,6 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import +import logging import os import six @@ -17,9 +18,22 @@ VCS_LIST = ("git", "svn", "hg", "bzr") SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://") +def setup_logger(): + logger = logging.getLogger("requirementslib") + loglevel = logging.DEBUG + handler = logging.StreamHandler() + handler.setLevel(loglevel) + logger.addHandler(handler) + logger.setLevel(loglevel) + return logger + + +log = setup_logger() + + def is_vcs(pipfile_entry): import requirements - from .requirements import _clean_git_uri + from .models.utils import add_ssh_scheme_to_git_uri """Determine if dictionary entry from Pipfile is for a vcs dependency.""" if hasattr(pipfile_entry, "keys"): @@ -27,7 +41,7 @@ def is_vcs(pipfile_entry): elif isinstance(pipfile_entry, six.string_types): return bool( - requirements.requirement.VCS_REGEX.match(_clean_git_uri(pipfile_entry)) + requirements.requirement.VCS_REGEX.match(add_ssh_scheme_to_git_uri(pipfile_entry)) ) return False @@ -36,7 +50,7 @@ def is_vcs(pipfile_entry): def get_converted_relative_path(path, relative_to=os.curdir): """Given a vague relative path, return the path relative to the given location""" relpath = os.path.relpath(path, start=relative_to) - if os.name == 'nt': + if os.name == "nt": return os.altsep.join([".", relpath]) return os.path.join(".", relpath) @@ -57,9 +71,8 @@ def is_installable_file(path): from ._compat import is_installable_dir, is_archive_file from packaging import specifiers - if ( - hasattr(path, "keys") - and any(key for key in path.keys() if key in ["file", "path"]) + if hasattr(path, "keys") and any( + key for key in path.keys() if key in ["file", "path"] ): path = urlparse(path["file"]).path if "file" in path else path["path"] if not isinstance(path, six.string_types) or path == "*": @@ -77,7 +90,7 @@ def is_installable_file(path): return False parsed = urlparse(path) - if parsed.scheme == 'file': + if parsed.scheme == "file": path = parsed.path if not os.path.exists(os.path.abspath(path)): @@ -115,25 +128,17 @@ def prepare_pip_source_args(sources, pip_args=None): pip_args = [] if sources: # Add the source to pip9. - pip_args.extend(['-i', sources[0]['url']]) + pip_args.extend(["-i", sources[0]["url"]]) # Trust the host if it's not verified. - if not sources[0].get('verify_ssl', True): + if not sources[0].get("verify_ssl", True): pip_args.extend( - [ - '--trusted-host', - urlparse(sources[0]['url']).netloc.split(':')[0], - ] + ["--trusted-host", urlparse(sources[0]["url"]).netloc.split(":")[0]] ) # Add additional sources as extra indexes. if len(sources) > 1: for source in sources[1:]: - pip_args.extend(['--extra-index-url', source['url']]) + pip_args.extend(["--extra-index-url", source["url"]]) # Trust the host if it's not verified. - if not source.get('verify_ssl', True): - pip_args.extend( - [ - '--trusted-host', - urlparse(source['url']).hostname, - ] - ) + if not source.get("verify_ssl", True): + pip_args.extend(["--trusted-host", urlparse(source["url"]).hostname]) return pip_args diff --git a/tests/pypi/ruamel-ordereddict/ruamel.ordereddict-0.4.9-cp27-cp27mu-manylinux1_x86_64.whl b/tests/pypi/ruamel-ordereddict/ruamel.ordereddict-0.4.9-cp27-cp27mu-manylinux1_x86_64.whl new file mode 100644 index 00000000..26e63a8e Binary files /dev/null and b/tests/pypi/ruamel-ordereddict/ruamel.ordereddict-0.4.9-cp27-cp27mu-manylinux1_x86_64.whl differ