diff --git a/news/3254.bugfix.rst b/news/3254.bugfix.rst new file mode 100644 index 0000000000..11b18201f5 --- /dev/null +++ b/news/3254.bugfix.rst @@ -0,0 +1 @@ +Updated ``requirementslib`` and ``pythonfinder`` for multiple bugfixes. diff --git a/pipenv/vendor/pythonfinder/environment.py b/pipenv/vendor/pythonfinder/environment.py index ec4a760fac..0ea0910992 100644 --- a/pipenv/vendor/pythonfinder/environment.py +++ b/pipenv/vendor/pythonfinder/environment.py @@ -7,7 +7,7 @@ PYENV_INSTALLED = bool(os.environ.get("PYENV_SHELL")) or bool( os.environ.get("PYENV_ROOT") ) -ASDF_INSTALLED = bool(os.environ.get("ASDF_DATA_DIR")) +ASDF_INSTALLED = bool(os.environ.get("ASDF_DIR")) PYENV_ROOT = os.path.expanduser( os.path.expandvars(os.environ.get("PYENV_ROOT", "~/.pyenv")) ) diff --git a/pipenv/vendor/pythonfinder/models/path.py b/pipenv/vendor/pythonfinder/models/path.py index 7fae331208..42608cde8f 100644 --- a/pipenv/vendor/pythonfinder/models/path.py +++ b/pipenv/vendor/pythonfinder/models/path.py @@ -484,7 +484,14 @@ def _gen_children(self): for child in self._filter_children(): if any(shim in normalize_path(str(child)) for shim in SHIM_PATHS): continue - yield (child.as_posix(), PathEntry.create(path=child, **pass_args)) + if self.only_python: + try: + entry = PathEntry.create(path=child, **pass_args) + except (InvalidPythonVersion, ValueError): + continue + else: + entry = PathEntry.create(path=child, **pass_args) + yield (child.as_posix(), entry) return @cached_property @@ -508,7 +515,7 @@ def get_py_version(self): if self.is_python: try: py_version = PythonVersion.from_path(path=self, name=self.name) - except InvalidPythonVersion: + except (InvalidPythonVersion, ValueError): py_version = None except Exception: if not IGNORE_UNSUPPORTED: diff --git a/pipenv/vendor/pythonfinder/models/python.py b/pipenv/vendor/pythonfinder/models/python.py index eac856c51b..d7de3e0553 100644 --- a/pipenv/vendor/pythonfinder/models/python.py +++ b/pipenv/vendor/pythonfinder/models/python.py @@ -25,6 +25,7 @@ is_in_path, parse_pyenv_version_order, parse_asdf_version_order, + parse_python_version, ) logger = logging.getLogger(__name__) @@ -361,19 +362,32 @@ def parse(cls, version): try: version = parse_version(str(version)) except TypeError: - raise ValueError("Unable to parse version: %s" % version) - if not version or not version.release: - raise ValueError("Not a valid python version: %r" % version) - return - if len(version.release) >= 3: - major, minor, patch = version.release[:3] - elif len(version.release) == 2: - major, minor = version.release - patch = None + try: + version_dict = parse_python_version(str(version)) + except Exception: + raise ValueError("Unable to parse version: %s" % version) + else: + if not version_dict: + raise ValueError("Not a valid python version: %r" % version) + major = int(version_dict.get("major")) + minor = int(version_dict.get("minor")) + patch = version_dict.get("patch") + if patch: + patch = int(patch) + version = ".".join([v for v in [major, minor, patch] if v is not None]) + version = parse_version(version) else: - major = version.release[0] - minor = None - patch = None + if not version or not version.release: + raise ValueError("Not a valid python version: %r" % version) + if len(version.release) >= 3: + major, minor, patch = version.release[:3] + elif len(version.release) == 2: + major, minor = version.release + patch = None + else: + major = version.release[0] + minor = None + patch = None return { "major": major, "minor": minor, diff --git a/pipenv/vendor/pythonfinder/utils.py b/pipenv/vendor/pythonfinder/utils.py index 2debd80ee0..f8ec197226 100644 --- a/pipenv/vendor/pythonfinder/utils.py +++ b/pipenv/vendor/pythonfinder/utils.py @@ -8,6 +8,7 @@ import attr import io +import re import six import vistir @@ -24,6 +25,9 @@ from backports.functools_lru_cache import lru_cache +version_re = re.compile(r"(?P[0-9]+)\.(?P[0-9]+)\.?(?P(?<=\.)[0-9]+)") + + PYTHON_IMPLEMENTATIONS = ( "python", "ironpython", "jython", "pypy", "anaconda", "miniconda", "stackless", "activepython", "micropython" @@ -46,13 +50,13 @@ ) -@lru_cache(maxsize=128) +@lru_cache(maxsize=1024) def get_python_version(path): """Get python version string using subprocess from a given path.""" version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"] try: c = vistir.misc.run(version_cmd, block=True, nospin=True, return_object=True, - combine_stderr=False) + combine_stderr=False, write_to_stdout=False) except OSError: raise InvalidPythonVersion("%s is not a valid python path" % path) if not c.out: @@ -60,6 +64,14 @@ def get_python_version(path): return c.out.strip() +@lru_cache(maxsize=1024) +def parse_python_version(version_str): + m = version_re.match(version_str) + if not m: + raise InvalidPythonVersion("%s is not a python version" % version_str) + return m.groupdict() + + def optional_instance_of(cls): return attr.validators.optional(attr.validators.instance_of(cls)) @@ -151,6 +163,7 @@ def parse_pyenv_version_order(filename="version"): contents = fh.read() version_order = [v for v in contents.splitlines()] return version_order + return [] def parse_asdf_version_order(filename=".tool-versions"): @@ -165,6 +178,7 @@ def parse_asdf_version_order(filename=".tool-versions"): python_key, _, versions = python_section.partition(" ") if versions: return versions.split() + return [] # TODO: Reimplement in vistir diff --git a/pipenv/vendor/requirementslib/models/lockfile.py b/pipenv/vendor/requirementslib/models/lockfile.py index 9d19edaf7c..54b2761c25 100644 --- a/pipenv/vendor/requirementslib/models/lockfile.py +++ b/pipenv/vendor/requirementslib/models/lockfile.py @@ -160,7 +160,7 @@ def load_projectfile(cls, path, create=True, data=None): path = os.curdir path = Path(path).absolute() project_path = path if path.is_dir() else path.parent - lockfile_path = project_path / "Pipfile.lock" + lockfile_path = path if path.is_file() else project_path / "Pipfile.lock" if not project_path.exists(): raise OSError("Project does not exist: %s" % project_path.as_posix()) elif not lockfile_path.exists() and not create: @@ -168,7 +168,12 @@ def load_projectfile(cls, path, create=True, data=None): projectfile = cls.read_projectfile(lockfile_path.as_posix()) if not lockfile_path.exists(): if not data: - lf = cls.lockfile_from_pipfile(project_path.joinpath("Pipfile")) + path_str = lockfile_path.as_posix() + if path_str[-5:] == ".lock": + pipfile = Path(path_str[:-5]) + else: + pipfile = project_path.joinpath("Pipfile") + lf = cls.lockfile_from_pipfile(pipfile) else: lf = plette.lockfiles.Lockfile(data) projectfile.model = lf @@ -212,7 +217,7 @@ def from_data(cls, path, data, meta_from_project=True): def load(cls, path, create=True): """Create a new lockfile instance. - :param project_path: Path to project root + :param project_path: Path to project root or lockfile :type project_path: str or :class:`pathlib.Path` :param str lockfile_name: Name of the lockfile in the project root directory :param pipfile_path: Path to the project pipfile @@ -225,9 +230,9 @@ def load(cls, path, create=True): projectfile = cls.load_projectfile(path, create=create) except JSONDecodeError: path = os.path.abspath(path) - if not os.path.isdir(path): - path = os.path.dirname(path) - path = Path(os.path.join(path, "Pipfile.lock")) + path = Path( + os.path.join(path, "Pipfile.lock") if os.path.isdir(path) else path + ) formatted_path = path.as_posix() backup_path = "%s.bak" % formatted_path LockfileCorruptException.show(formatted_path, backup_path=backup_path) diff --git a/pipenv/vendor/requirementslib/models/pipfile.py b/pipenv/vendor/requirementslib/models/pipfile.py index 0f6de6bfaf..e3d353d9a1 100644 --- a/pipenv/vendor/requirementslib/models/pipfile.py +++ b/pipenv/vendor/requirementslib/models/pipfile.py @@ -187,7 +187,7 @@ def load_projectfile(cls, path, create=False): raise RuntimeError("Must pass a path to classmethod 'Pipfile.load'") if not isinstance(path, Path): path = Path(path).absolute() - pipfile_path = path if path.name == "Pipfile" else path.joinpath("Pipfile") + pipfile_path = path if path.is_file() else path.joinpath("Pipfile") project_path = pipfile_path.parent if not project_path.exists(): raise FileNotFoundError("%s is not a valid project path!" % path) diff --git a/pipenv/vendor/requirementslib/models/setup_info.py b/pipenv/vendor/requirementslib/models/setup_info.py index 006ee60969..01e7ce67a5 100644 --- a/pipenv/vendor/requirementslib/models/setup_info.py +++ b/pipenv/vendor/requirementslib/models/setup_info.py @@ -103,7 +103,7 @@ def iter_egginfos(path, pkg_name=None): if not entry.name.endswith("egg-info"): for dir_entry in iter_egginfos(entry.path, pkg_name=pkg_name): yield dir_entry - elif pkg_name is None or entry.name.startswith(pkg_name): + elif pkg_name is None or entry.name.startswith(pkg_name.replace("-", "_")): yield entry @@ -224,7 +224,7 @@ def run_setup(self): target_cwd = self.setup_py.parent.as_posix() with cd(target_cwd), _suppress_distutils_logs(): script_name = self.setup_py.as_posix() - args = ["egg_info", "--egg-base", self.base_dir] + args = ["egg_info", self.base_dir] g = {"__file__": script_name, "__name__": "__main__"} local_dict = {} if sys.version_info < (3, 5): @@ -247,7 +247,8 @@ def run_setup(self): except NameError: python = os.environ.get('PIP_PYTHON_PATH', sys.executable) out, _ = run([python, "setup.py"] + args, cwd=target_cwd, block=True, - combine_stderr=False, return_object=False, nospin=True) + combine_stderr=False, return_object=False, nospin=True, + write_to_stdout=False) finally: _setup_stop_after = None sys.argv = save_argv