diff --git a/HISTORY.txt b/HISTORY.txt index b19b333b..2c849e8a 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,3 +1,5 @@ +3.6.1: + - pipenv install now works if only a requirements.txt is present. 3.6.0: - Make --two/--three handling more consistent. - Update vendored delegator.py. diff --git a/pipenv/cli.py b/pipenv/cli.py index 7a523a97..c6e1a223 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -38,6 +38,9 @@ else: # |_| |_|| _/|___>|_|_||__/ # |_| +# Packages that should be ignored later. +BAD_PACKAGES = ['setuptools', 'pip', 'wheel', 'six', 'packaging', 'pyparsing', 'appdirs'] + # Enable shell completion. click_completion.init() @@ -90,10 +93,33 @@ def ensure_pipfile(validate=True): # Assert Pipfile exists. if not project.pipfile_exists: - click.echo(crayons.yellow('Creating a Pipfile for this project...'), err=True) + # If there's a requirements file, but no Pipfile... + if project.requirements_exists: + click.echo(crayons.yellow('Requirements file found, instead of Pipfile! Converting...')) + + # Create a Pipfile... + project.create_pipfile() - # Create the pipfile if it doesn't exist. - project.create_pipfile() + click.echo(crayons.yellow('Installing dependencies from \'requirements.txt\'...')) + + # Install everything from the requirements.txt. + delegator.run('{0} install -r {1}'.format(which('pip'), project.requirements_location)) + installed = delegator.run('{0} freeze'.format(which('pip'))).out.split('\n') + + # Remove setuptools and friends from installed, if present. + for package_name in BAD_PACKAGES: + for i, package in enumerate(installed): + if package.startswith(package_name): + del installed[i] + + for package in installed: + if package: + project.add_package_to_pipfile(package) + + else: + click.echo(crayons.yellow('Creating a Pipfile for this project...'), err=True) + # Create the pipfile if it doesn't exist. + project.create_pipfile() # Validate the Pipfile's contents. if validate and project.virtualenv_exists: @@ -530,7 +556,6 @@ def do_activate_virtualenv(bare=False): else: click.echo(activate_virtualenv()) - def do_purge(bare=False, downloads=False, allow_global=False): """Executes the purge functionality.""" @@ -544,7 +569,7 @@ def do_purge(bare=False, downloads=False, allow_global=False): installed = freeze.split() # Remove setuptools and friends from installed, if present. - for package_name in ['setuptools', 'pip', 'wheel', 'six', 'packaging', 'pyparsing', 'appdirs']: + for package_name in BAD_PACKAGES: for i, package in enumerate(installed): if package.startswith(package_name): del installed[i] diff --git a/pipenv/project.py b/pipenv/project.py index ab103aaa..855532d4 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -12,7 +12,7 @@ import delegator from requests.compat import OrderedDict from .utils import (format_toml, mkdir_p, convert_deps_from_pip, - pep423_name, recase_file) + pep423_name, recase_file, find_requirements) from .environments import PIPENV_MAX_DEPTH, PIPENV_VENV_IN_PROJECT @@ -25,6 +25,7 @@ class Project(object): self._download_location = None self._proper_names_location = None self._pipfile_location = None + self._requirements_location = None @property def name(self): @@ -36,6 +37,10 @@ class Project(object): def pipfile_exists(self): return bool(self.pipfile_location) + @property + def requirements_exists(self): + return bool(self.requirements_location) + @property def virtualenv_exists(self): # TODO: Decouple project from existence of Pipfile. @@ -128,6 +133,17 @@ class Project(object): return self._pipfile_location + @property + def requirements_location(self): + if self._requirements_location is None: + try: + loc = find_requirements(max_depth=PIPENV_MAX_DEPTH) + except RuntimeError: + loc = None + self._requirements_location = loc + + return self._requirements_location + @property def parsed_pipfile(self): with open(self.pipfile_location) as f: diff --git a/pipenv/utils.py b/pipenv/utils.py index 6822a942..d1da2faf 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -235,3 +235,49 @@ def recase_file(file_dict): file_section[cased_key] = file_section.pop(key) return file_dict + + +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 as e: + 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 + + +def find_requirements(max_depth=3): + """Returns the path of a Pipfile in parent directories.""" + i = 0 + for c, d, f in walk_up(os.getcwd()): + i += 1 + + if i < max_depth: + if 'requirements.txt': + r = os.path.join(c, 'requirements.txt') + if os.path.isfile(r): + return r + raise RuntimeError('No requirements.txt found!')