diff --git a/Makefile b/Makefile index 74fe2a82..0d8ecfe9 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,22 @@ venv_file := $(CURDIR)/.test_venv get_venv_path =$(file < $(venv_file)) # This is how we will build tag-specific wheels, e.g. py36 or py37 PY_VERSIONS:= 2.7 3.5 3.6 3.7 3.8 +BACKSLASH = '\\' # This is how we will build generic wheels, e.g. py2 or py3 INSTALL_TARGETS := $(addprefix install-py,$(PY_VERSIONS)) CLEAN_TARGETS := $(addprefix clean-py,$(PY_VERSIONS)) DATE_STRING := $(shell date +%Y.%m.%d) THIS_MONTH_DATE := $(shell date +%Y.%m.01) NEXT_MONTH_DATE := $(shell date -d "+1 month" +%Y.%m.01) - +PATCHED_PIP_VERSION := $(shell awk '/__version__/{gsub(/"/,"",$$3); print $$3}' pipenv/patched/notpip/__init__.py) +PATCHED_PIPTOOLS_VERSION := $(shell awk -F "=" '/pip-tools/ {print $$3}' pipenv/patched/patched.txt) +GITDIR_STAMPFILE := $(CURDIR)/.git-checkout-dir +create_git_tmpdir = $(shell mktemp -dt pipenv-vendor-XXXXXXXX 2>/dev/null || mktemp -d 2>/dev/null) +write_git_tmpdir = $(file > $(GITDIR_STAMPFILE),$(create_git_tmpdir)) +get_checkout_dir = $(file < $(GITDIR_STAMPFILE)) +get_checkout_subdir = $(addprefix $(get_checkout_dir), $(1)) +pip-checkout-dir = $(get_checkout_dir)/patch-pip +piptools-checkout-dir = $(get_checkout_dir)/patch-piptools format: black pipenv/*.py @@ -19,10 +28,9 @@ test: .PHONY: install install: - pip install -e . && pipenv install --dev + pip install -e . -install.stamp: - [ ! -e install.stamp ] && $(MAKE) install +install.stamp: install @touch install.stamp .PHONY: install-py% @@ -75,12 +83,8 @@ retest: virtualenv submodules test-install . $(get_venv_path)/bin/activate && pipenv run pytest -ra -k 'test_check_unused or test_install_editable_git_tag or test_get_vcs_refs or test_skip_requirements_when_pipfile or test_editable_vcs_install or test_basic_vcs_install or test_git_vcs_install or test_ssh_vcs_install or test_vcs_can_use_markers' -vvv --full-trace --tb=long .PHONY: build -build: install.stamp - pipenv run python setup.py sdist bdist_wheel - -build.stamp: - [ ! -e build.stamp ] && $(MAKE) build - @touch build.stamp +build: install-virtualenvs.stamp install.stamp + PIPENV_PYTHON=3.7 pipenv run python setup.py sdist bdist_wheel .PHONY: update-version update-version: @@ -121,5 +125,56 @@ cleanbuild: @rm -rf build.stamp .PHONY: clean -clean: cleanbuild - rm -rf install.stamp build.stamp install-virtualenvs.stamp +clean: + rm -rf install.stamp build.stamp install-virtualenvs.stamp .git-checkout-dir + +.PHONY: gitclean +gitclean: + @echo "Cleaning up git trees..." + @rm -rf $(file < .git-checkout-dir) + @echo "Cleaning up git checkout stamp" + @rm -rf .git-checkout-dir + +.git-checkout-dir: + @echo "Creating git repo temp file" + @echo "Creating git checkout stamp file at .git-checkout-dir" + @echo $(file > $(CURDIR)/.git-checkout-dir,$(shell mktemp -dt pipenv-vendor-XXXXXXXX 2>/dev/null || mktemp -d 2>/dev/null)) + +.PHONY: clone-pip +clone-pip: .git-checkout-dir + [ -e $(pip-checkout-dir) ] && echo "Pip already exists, moving on!" || git clone https://github.com/pypa/pip.git $(pip-checkout-dir) -b $(PATCHED_PIP_VERSION) + +.PHONY: clone-piptools +clone-piptools: .git-checkout-dir + [ -e $(piptools-checkout-dir) ] && echo "Piptools already exists, moving on!" || git clone https://github.com/jazzband/pip-tools.git $(piptools-checkout-dir) -b $(PATCHED_PIPTOOLS_VERSION) + +.PHONY: patch-pip +patch-pip: clone-pip + @find $(CURDIR)/tasks/vendoring/patches/patched/ -regex ".*/pip[0-9]+.patch" -exec cp {} $(pip-checkout-dir) \; + @sed -i -r 's:([a-b]\/)pipenv/patched/:\1src/:g' $(pip-checkout-dir)/*.patch + @find $(CURDIR)/tasks/vendoring/patches/patched/ -regex ".*/_post-pip-[^/\.]*.patch" -exec cp {} $(pip-checkout-dir)/ \; + @sed -i -r 's:([a-b]\/)pipenv/patched/not:\1src/:g' $(pip-checkout-dir)/_post-*.patch + @cd $(pip-checkout-dir)/ && git apply --ignore-whitespace --verbose pip*.patch + @echo "Head to $(pip-checkout-dir) to update the pip patches to the latest version" + +.PHONY: patch-piptools +patch-piptools: clone-piptools + @find $(CURDIR)/tasks/vendoring/patches/patched/ -regex ".*/piptools[^/\.]*.patch" -exec cp {} $(piptools-checkout-dir)/ \; + @sed -i -r 's:([a-b]\/)pipenv/patched/:\1/:g' $(piptools-checkout-dir)/*.patch + @cd $(piptools-checkout-dir)/ && git apply --ignore-whitespace --verbose piptools*.patch + @echo "Head to $(piptools-checkout-dir) to update the piptools patches to the latest version" + +.PHONY: patches +patches: patch-pip patch-piptools + +.PHONY: reimport-pip-patch +reimport-pip-patch: + @sed -i -r 's:([a-b]\/)src/:\1pipenv/patched/not:g' $(pip-checkout-dir)/_post-*.patch + @sed -i -r 's:([a-b]\/)src/:\1pipenv/patched/:g' $(pip-checkout-dir)/pip*.patch + @find $(pip-checkout-dir) -maxdepth 1 -regex ".*/pip[0-9]+.patch" -exec cp {} $(CURDIR)/tasks/vendoring/patches/patched/ \; + @find $(pip-checkout-dir) -maxdepth 1 -regex ".*/_post-pip-[^/\.]*.patch" -exec cp {} $(CURDIR)/tasks/vendoring/patches/patched/ \; + +.PHONY: reimport-piptools-patch +reimport-piptools-patch: + @sed -i -r 's:([a-b]\/):\1pipenv/patched/:g' $(piptools-checkout-dir)/*.patch + @find $(piptools-checkout-dir)/ -maxdepth 1 -regex ".*/piptools[^/\.]*.patch" -exec cp {} $(CURDIR)/tasks/vendoring/patches/patched/ \; diff --git a/news/4271.bugfix.rst b/news/4271.bugfix.rst new file mode 100644 index 00000000..593643d7 --- /dev/null +++ b/news/4271.bugfix.rst @@ -0,0 +1 @@ +``+`` signs in URL authentication fragments will no longer be incorrectly replaced with space ( `` `` ) characters. diff --git a/news/4273.bugfix.rst b/news/4273.bugfix.rst index a6140178..9e6ce192 100644 --- a/news/4273.bugfix.rst +++ b/news/4273.bugfix.rst @@ -1 +1 @@ -Fix a bug that Pipenv rejects to work under the root directory. +Fixed a regression which caused Pipenv to fail when running under ``/``. diff --git a/news/4274.bugfix.rst b/news/4274.bugfix.rst new file mode 100644 index 00000000..4f097861 --- /dev/null +++ b/news/4274.bugfix.rst @@ -0,0 +1 @@ +``setup.py`` files with ``version`` variables read from ``os.environ`` are now able to be parsed successfully. diff --git a/news/4276.bugfix.rst b/news/4276.bugfix.rst index 960ab08b..ee473344 100644 --- a/news/4276.bugfix.rst +++ b/news/4276.bugfix.rst @@ -1 +1 @@ -Fix a bug that system-wide packages will be considered when inside a venv. +Fixed a bug which caused Pipenv to fail to install packages in a virtual environment if those packages were already present in the system global environment. diff --git a/news/4279.trivial.rst b/news/4279.trivial.rst index 3d57e01a..3f5ab55e 100644 --- a/news/4279.trivial.rst +++ b/news/4279.trivial.rst @@ -1,2 +1 @@ -Remove expection of ``version`` key in ``test_ssh_vcs_install`` to prevent it -from failing. +Remove expection of ``version`` key in ``test_ssh_vcs_install`` to prevent it from failing. diff --git a/news/4295.bugfix.rst b/news/4295.bugfix.rst new file mode 100644 index 00000000..064488de --- /dev/null +++ b/news/4295.bugfix.rst @@ -0,0 +1 @@ +Fixed a regression with installing the current directory, or ``.``, inside a ``venv`` based virtual environment. diff --git a/news/4296.bugfix.rst b/news/4296.bugfix.rst new file mode 100644 index 00000000..99ae428a --- /dev/null +++ b/news/4296.bugfix.rst @@ -0,0 +1 @@ +Fixed a bug with the discovery of python paths on Windows which could prevent installation of environments during ``pipenv install``. diff --git a/news/4298.bugfix.rst b/news/4298.bugfix.rst new file mode 100644 index 00000000..d6700a6f --- /dev/null +++ b/news/4298.bugfix.rst @@ -0,0 +1 @@ +Fixed an issue in the ``requirementslib`` AST parser which prevented parsing of ``setup.py`` files for dependency metadata. diff --git a/news/4302.vendor.rst b/news/4302.vendor.rst index daf2ca20..de4cf833 100644 --- a/news/4302.vendor.rst +++ b/news/4302.vendor.rst @@ -1,4 +1,3 @@ -- Updated vendored dependencies: - - - **pythonfinder**: ``1.2.2`` => ``1.2.4`` - - **requirementslib**: ``1.5.9`` => ``1.5.10`` +Updated vendored dependencies: + - **pythonfinder**: ``1.2.2`` => ``1.2.4`` + - **requirementslib**: ``1.5.9`` => ``1.5.10`` diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 599b5239..811807bb 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -357,7 +357,7 @@ def lock( dev_only=dev_only, emit_requirements=emit_requirements, pypi_mirror=state.pypi_mirror, - pre=state.installstate.pre, + pre=pre, ) elif state.lockoptions.dev_only: raise PipenvOptionsError( @@ -368,7 +368,7 @@ def lock( do_lock( ctx=ctx, clear=state.clear, - pre=state.installstate.pre, + pre=pre, keep_outdated=state.installstate.keep_outdated, pypi_mirror=state.pypi_mirror, write=not state.quiet, diff --git a/pipenv/cli/options.py b/pipenv/cli/options.py index 30a6882f..b96a03f6 100644 --- a/pipenv/cli/options.py +++ b/pipenv/cli/options.py @@ -83,12 +83,14 @@ class InstallState(object): self.packages = [] self.editables = [] + class LockOptions(object): def __init__(self): self.dev_only = False self.emit_requirements = False self.emit_requirements_header = False + pass_state = make_pass_decorator(State, ensure=True) @@ -329,6 +331,7 @@ def emit_requirements_flag(f): return option("--requirements", "-r", default=False, is_flag=True, expose_value=False, help="Generate output in requirements.txt format.", callback=callback)(f) + def emit_requirements_header_flag(f): def callback(ctx, param, value): state = ctx.ensure_object(State) @@ -338,6 +341,7 @@ def emit_requirements_header_flag(f): return option("--header/--no-header", default=True, is_flag=True, expose_value=False, help="Add header to generated requirements", callback=callback)(f) + def dev_only_flag(f): def callback(ctx, param, value): state = ctx.ensure_object(State) @@ -347,6 +351,7 @@ def dev_only_flag(f): return option("--dev-only", default=False, is_flag=True, expose_value=False, help="Emit development dependencies *only* (overrides --dev)", callback=callback)(f) + def code_option(f): def callback(ctx, param, value): state = ctx.ensure_object(State) diff --git a/pipenv/core.py b/pipenv/core.py index 040db059..02971f22 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1616,10 +1616,10 @@ def which_pip(allow_global=False): def system_which(command, mult=False): """Emulates the system's which. Returns None if not found.""" _which = "which -a" if not os.name == "nt" else "where" - os.environ = { + os.environ.update({ vistir.compat.fs_str(k): vistir.compat.fs_str(val) for k, val in os.environ.items() - } + }) result = None try: c = delegator.run("{0} {1}".format(_which, command)) diff --git a/pipenv/environment.py b/pipenv/environment.py index d47b2037..fbfe9615 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -26,15 +26,27 @@ from .utils import normalize_path, make_posix if False: - from typing import Optional + import pip_shims.shims + import tomlkit + from typing import ContextManager, Dict, Generator, List, Optional, Set, Union + from types import ModuleType + from .project import TSource, TPipfile, Project + from .vendor.packaging.version import Version BASE_WORKING_SET = pkg_resources.WorkingSet(sys.path) # TODO: Unittests for this class class Environment(object): - def __init__(self, prefix=None, is_venv=False, base_working_set=None, pipfile=None, - sources=None, project=None): + def __init__( + self, + prefix=None, # type: Optional[str] + is_venv=False, # type: bool + base_working_set=None, # type: pkg_resources.WorkingSet + pipfile=None, # type: Optional[Union[tomlkit.toml_document.TOMLDocument, TPipfile]] + sources=None, # type: Optional[List[TSource]] + project=None # type: Optional[Project] + ): super(Environment, self).__init__() self._modules = {'pkg_resources': pkg_resources, 'pipenv': pipenv} self.base_working_set = base_working_set if base_working_set else BASE_WORKING_SET @@ -58,6 +70,7 @@ class Environment(object): self.sys_paths = get_paths() def safe_import(self, name): + # type: (str) -> ModuleType """Helper utility for reimporting previously imported modules while inside the env""" module = None if name not in self._modules: @@ -77,10 +90,12 @@ class Environment(object): except TypeError: del sys.modules[name] sys.modules[name] = self._modules[name] + return self._modules[name] return module @classmethod def resolve_dist(cls, dist, working_set): + # type: (pkg_resources.Distribution, pkg_resources.WorkingSet) -> Set[pkg_resources.Distribution] """Given a local distribution and a working set, returns all dependencies from the set. :param dist: A single distribution to find the dependencies of @@ -104,28 +119,33 @@ class Environment(object): return deps def extend_dists(self, dist): + # type: (pkg_resources.Distribution) -> None extras = self.resolve_dist(dist, self.base_working_set) self.extra_dists.append(dist) if extras: self.extra_dists.extend(extras) def add_dist(self, dist_name): + # type: (str) -> None dist = pkg_resources.get_distribution(pkg_resources.Requirement(dist_name)) self.extend_dists(dist) @cached_property def python_version(self): + # type: () -> str with self.activated(): sysconfig = self.safe_import("sysconfig") py_version = sysconfig.get_python_version() return py_version def find_libdir(self): + # type: () -> Optional[vistir.compat.Path] libdir = self.prefix / "lib" return next(iter(list(libdir.iterdir())), None) @property def python_info(self): + # type: () -> Dict[str, str] include_dir = self.prefix / "include" if not os.path.exists(include_dir): include_dirs = self.get_include_path() @@ -142,6 +162,7 @@ class Environment(object): return {} def _replace_parent_version(self, path, replace_version): + # type: (str, str) -> str if not os.path.exists(path): base, leaf = os.path.split(path) base, parent = os.path.split(base) @@ -153,6 +174,7 @@ class Environment(object): @cached_property def base_paths(self): + # type: () -> Dict[str, str] """ Returns the context appropriate paths for the environment. @@ -231,6 +253,7 @@ class Environment(object): @cached_property def script_basedir(self): + # type: () -> str """Path to the environment scripts dir""" prefix = make_posix(self.prefix.as_posix()) install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix' @@ -242,6 +265,7 @@ class Environment(object): @property def python(self): + # type: () -> str """Path to the environment python""" py = vistir.compat.Path(self.script_basedir).joinpath("python").absolute().as_posix() if not py: @@ -250,6 +274,7 @@ class Environment(object): @cached_property def sys_path(self): + # type: () -> List[str] """ The system path inside the environment @@ -272,6 +297,7 @@ class Environment(object): return path def build_command(self, python_lib=False, python_inc=False, scripts=False, py_version=False): + # type: (bool, bool, bool, bool) -> str """Build the text for running a command in the given environment :param python_lib: Whether to include the python lib dir commands, defaults to False @@ -313,6 +339,7 @@ class Environment(object): return py_command def get_paths(self): + # type: () -> Optional[Dict[str, str]] """ Get the paths for the environment by running a subcommand @@ -343,6 +370,7 @@ class Environment(object): return None def get_lib_paths(self): + # type: () -> Dict[str, str] """Get the include path for the environment :return: The python include path for the environment @@ -390,6 +418,7 @@ class Environment(object): return {} def get_include_path(self): + # type: () -> Optional[Dict[str, str]] """Get the include path for the environment :return: The python include path for the environment @@ -424,6 +453,7 @@ class Environment(object): @cached_property def sys_prefix(self): + # type: () -> str """ The prefix run inside the context of the environment @@ -438,6 +468,7 @@ class Environment(object): @cached_property def paths(self): + # type: () -> Dict[str, str] paths = {} with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path(): os.environ["PYTHONIOENCODING"] = vistir.compat.fs_str("utf-8") @@ -451,10 +482,12 @@ class Environment(object): @property def scripts_dir(self): + # type: () -> str return self.paths["scripts"] @property def libdir(self): + # type: () -> str purelib = self.paths.get("purelib", None) if purelib and os.path.exists(purelib): return "purelib", purelib @@ -462,6 +495,7 @@ class Environment(object): @property def pip_version(self): + # type: () -> Version """ Get the pip version in the environment. Useful for knowing which args we can use when installing. @@ -475,6 +509,7 @@ class Environment(object): return parse_version("20.2") def expand_egg_links(self): + # type: () -> None """ Expand paths specified in egg-link files to prevent pip errors during reinstall @@ -497,6 +532,7 @@ class Environment(object): pth.write_text("\n".join(contents)) def get_distributions(self): + # type: () -> Generator[pkg_resources.Distribution, None, None] """ Retrives the distributions installed on the library path of the environment @@ -511,6 +547,7 @@ class Environment(object): yield dist def find_egg(self, egg_dist): + # type: (pkg_resources.Distribution) -> str """Find an egg by name in the given environment""" site_packages = self.libdir[1] search_filename = "{0}.egg-link".format(egg_dist.project_name) @@ -525,6 +562,7 @@ class Environment(object): return egg def locate_dist(self, dist): + # type: (pkg_resources.Distribution) -> str """Given a distribution, try to find a corresponding egg link first. If the egg - link doesn 't exist, return the supplied distribution.""" @@ -533,6 +571,7 @@ class Environment(object): return location or dist.location def dist_is_in_project(self, dist): + # type: (pkg_resources.Distribution) -> bool """Determine whether the supplied distribution is in the environment.""" from .project import _normalized prefixes = [ @@ -546,6 +585,7 @@ class Environment(object): return any(location.startswith(prefix) for prefix in prefixes) def get_installed_packages(self): + # type: () -> List[pkg_resources.Distribution] """Returns all of the installed packages in a given environment""" workingset = self.get_working_set() packages = [ @@ -556,6 +596,7 @@ class Environment(object): @contextlib.contextmanager def get_finder(self, pre=False): + # type: (bool) -> ContextManager[pip_shims.shims.PackageFinder] from .vendor.pip_shims.shims import ( InstallCommand, get_package_finder ) @@ -571,7 +612,8 @@ class Environment(object): yield finder def get_package_info(self, pre=False): - from .vendor.pip_shims.shims import pip_version, parse_version, CandidateEvaluator + # type: (bool) -> Generator[pkg_resources.Distribution, None, None] + from .vendor.pip_shims.shims import pip_version, parse_version dependency_links = [] packages = self.get_installed_packages() # This code is borrowed from pip's current implementation @@ -611,6 +653,7 @@ class Environment(object): yield dist def get_outdated_packages(self, pre=False): + # type: (bool) -> List[pkg_resources.Distribution] return [ pkg for pkg in self.get_package_info(pre=pre) if pkg.latest_version._key > pkg.parsed_version._key diff --git a/pipenv/installers.py b/pipenv/installers.py index f16cdacd..18abdd74 100644 --- a/pipenv/installers.py +++ b/pipenv/installers.py @@ -1,6 +1,7 @@ import os import operator import re +import six from abc import ABCMeta, abstractmethod @@ -63,8 +64,8 @@ class InstallerError(RuntimeError): self.err = c.err +@six.add_metaclass(ABCMeta) class Installer(object): - __metaclass__ = ABCMeta def __init__(self): self.cmd = self._find_installer() diff --git a/pipenv/project.py b/pipenv/project.py index 796489f7..6ae4e6ac 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -37,8 +37,14 @@ from .utils import ( ) if is_type_checking(): - from typing import Dict, Text, Union + import pkg_resources + from typing import Dict, List, Optional, Set, Text, Tuple, Union TSource = Dict[Text, Union[Text, bool]] + TPackageEntry = Dict[str, Union[bool, str, List[str]]] + TPackage = Dict[str, TPackageEntry] + TScripts = Dict[str, str] + TPipenv = Dict[str, bool] + TPipfile = Dict[str, Union[TPackage, TScripts, TPipenv, List[TSource]]] def _normalized(p): @@ -158,6 +164,7 @@ class Project(object): pass def path_to(self, p): + # type: (str) -> str """Returns the absolute path to a given relative path.""" if os.path.isabs(p): return p @@ -219,16 +226,19 @@ class Project(object): @property def name(self): + # type: () -> str if self._name is None: self._name = self.pipfile_location.split(os.sep)[-2] return self._name @property def pipfile_exists(self): + # type: () -> bool return os.path.isfile(self.pipfile_location) @property def required_python_version(self): + # type: () -> str if self.pipfile_exists: required = self.parsed_pipfile.get("requires", {}).get( "python_full_version" @@ -240,13 +250,16 @@ class Project(object): @property def project_directory(self): + # type: () -> str return os.path.abspath(os.path.join(self.pipfile_location, os.pardir)) @property def requirements_exists(self): + # type: () -> bool return bool(self.requirements_location) def is_venv_in_project(self): + # type: () -> bool return PIPENV_VENV_IN_PROJECT or ( self.project_directory and os.path.isdir(os.path.join(self.project_directory, ".venv")) @@ -254,6 +267,7 @@ class Project(object): @property def virtualenv_exists(self): + # type: () -> bool if os.path.exists(self.virtualenv_location): if os.name == "nt": extra = ["Scripts", "activate.bat"] @@ -264,6 +278,7 @@ class Project(object): return False def get_location_for_virtualenv(self): + # type: () -> str # If there's no project yet, set location based on config. if not self.project_directory: if self.is_venv_in_project(): @@ -295,6 +310,7 @@ class Project(object): @property def working_set(self): + # type: () -> pkg_resources.WorkingSet from .utils import load_path sys_path = load_path(self.which("python")) import pkg_resources @@ -306,10 +322,12 @@ class Project(object): @property def installed_package_names(self): + # type: () -> List[str] return get_canonical_names([pkg.key for pkg in self.installed_packages]) @property def lockfile_package_names(self): + # type: () -> Dict[str, Set[str]] dev_keys = get_canonical_names(self.lockfile_content["develop"].keys()) default_keys = get_canonical_names(self.lockfile_content["default"].keys()) return { @@ -320,6 +338,7 @@ class Project(object): @property def pipfile_package_names(self): + # type: () -> Dict[str, Set[str]] dev_keys = get_canonical_names(self.dev_packages.keys()) default_keys = get_canonical_names(self.packages.keys()) return { @@ -349,16 +368,19 @@ class Project(object): @property def environment(self): + # type: () -> Environment if not self._environment: allow_global = os.environ.get("PIPENV_USE_SYSTEM", PIPENV_USE_SYSTEM) self._environment = self.get_environment(allow_global=allow_global) return self._environment def get_outdated_packages(self): + # type: () -> List[pkg_resources.Distribution] return self.environment.get_outdated_packages(pre=self.pipfile.get("pre", False)) @classmethod def _sanitize(cls, name): + # type: (str) -> Tuple[str, str] # Replace dangerous characters into '_'. The length of the sanitized # project name is limited as 42 because of the limit of linux kernel # @@ -374,6 +396,7 @@ class Project(object): return re.sub(r'[ $`!*@"\\\r\n\t]', "_", name)[0:42] def _get_virtualenv_hash(self, name): + # type: (str) -> str """Get the name of the virtualenv adjusted for windows if needed Returns (name, encoded_hash) @@ -416,6 +439,7 @@ class Project(object): @property def virtualenv_name(self): + # type: () -> str sanitized, encoded_hash = self._get_virtualenv_hash(self.name) suffix = "-{0}".format(PIPENV_PYTHON) if PIPENV_PYTHON else "" # If the pipfile was located at '/home/user/MY_PROJECT/Pipfile', @@ -424,6 +448,7 @@ class Project(object): @property def virtualenv_location(self): + # type: () -> str # if VIRTUAL_ENV is set, use that. virtualenv_env = os.getenv("VIRTUAL_ENV") if ( @@ -439,6 +464,7 @@ class Project(object): @property def virtualenv_src_location(self): + # type: () -> str if self.virtualenv_location: loc = os.sep.join([self.virtualenv_location, "src"]) else: @@ -448,6 +474,7 @@ class Project(object): @property def download_location(self): + # type: () -> str if self._download_location is None: loc = os.sep.join([self.virtualenv_location, "downloads"]) self._download_location = loc @@ -457,6 +484,7 @@ class Project(object): @property def proper_names_db_path(self): + # type: () -> str if self._proper_names_db_path is None: self._proper_names_db_path = vistir.compat.Path( self.virtualenv_location, "pipenv-proper-names.txt" @@ -466,16 +494,19 @@ class Project(object): @property def proper_names(self): + # type: () -> str with self.proper_names_db_path.open() as f: return f.read().splitlines() def register_proper_name(self, name): + # type: (str) -> None """Registers a proper name to the database.""" with self.proper_names_db_path.open("a") as f: f.write(u"{0}\n".format(name)) @property def pipfile_location(self): + # type: () -> str if PIPENV_PIPFILE: return PIPENV_PIPFILE @@ -489,6 +520,7 @@ class Project(object): @property def requirements_location(self): + # type: () -> Optional[str] if self._requirements_location is None: try: loc = find_requirements(max_depth=PIPENV_MAX_DEPTH) @@ -499,6 +531,7 @@ class Project(object): @property def parsed_pipfile(self): + # type: () -> Union[tomlkit.toml_document.TOMLDocument, TPipfile] """Parse Pipfile into a TOMLFile and cache it (call clear_pipfile_cache() afterwards if mutating)""" @@ -511,6 +544,7 @@ class Project(object): return _pipfile_cache[cache_key] def read_pipfile(self): + # type: () -> str # Open the pipfile, read it into memory. if not self.pipfile_exists: return "" @@ -521,10 +555,12 @@ class Project(object): return contents def clear_pipfile_cache(self): + # type: () -> None """Clear pipfile cache (e.g., so we can mutate parsed pipfile)""" _pipfile_cache.clear() def _parse_pipfile(self, contents): + # type: () -> Union[tomlkit.toml_document.TOMLDocument, TPipfile] try: return tomlkit.parse(contents) except Exception: @@ -533,6 +569,7 @@ class Project(object): return toml.loads(contents) def _read_pyproject(self): + # type: () -> None pyproject = self.path_to("pyproject.toml") if os.path.exists(pyproject): self._pyproject = toml.load(pyproject) @@ -547,24 +584,29 @@ class Project(object): @property def build_requires(self): + # type: () -> List[str] return self._build_system.get("requires", ["setuptools>=40.8.0", "wheel"]) @property def build_backend(self): + # type: () -> str return self._build_system.get("build-backend", get_default_pyproject_backend()) @property def settings(self): + # type: () -> Union[tomlkit.items.Table, Dict[str, Union[str, bool]]] """A dictionary of the settings added to the Pipfile.""" return self.parsed_pipfile.get("pipenv", {}) def has_script(self, name): + # type: (str) -> bool try: return name in self.parsed_pipfile["scripts"] except KeyError: return False def build_script(self, name, extra_args=None): + # type: (str, Optional[List[str]]) try: script = Script.parse(self.parsed_pipfile["scripts"][name]) except KeyError: @@ -574,6 +616,7 @@ class Project(object): return script def update_settings(self, d): + # type: (Dict[str, Union[str, bool]]) -> None settings = self.settings changed = False for new in d: @@ -1004,7 +1047,7 @@ class Project(object): if expand_env_vars: # Expand environment variables in Pipfile.lock at runtime. - for i, source in enumerate(j["_meta"]["sources"][:]): + for i, _ in enumerate(j["_meta"]["sources"][:]): j["_meta"]["sources"][i]["url"] = os.path.expandvars( j["_meta"]["sources"][i]["url"] ) diff --git a/pipenv/utils.py b/pipenv/utils.py index ce1354cd..4e428224 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -1877,7 +1877,6 @@ def get_vcs_deps( # sys.path = [repo.checkout_directory, "", ".", get_python_lib(plat_specific=0)] commit_hash = repo.get_commit_hash() name = requirement.normalized_name - version = requirement._specifiers = "=={0}".format(requirement.req.setup_info.version) lockfile[name] = requirement.pipfile_entry[1] lockfile[name]['ref'] = commit_hash result.append(requirement)