From 9ae180600df90ca1dc924c68519ef236a7a999fc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 12 Sep 2017 22:48:41 -0400 Subject: [PATCH 1/7] system_which() Signed-off-by: Kenneth Reitz --- pipenv/cli.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index 0a13b074..613b3415 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -263,12 +263,16 @@ def ensure_pipfile(validate=True): project.write_toml(p) +def ensure_python(three=None, python=None): + if + def ensure_virtualenv(three=None, python=None): """Creates a virtualenv, if one doesn't exist.""" if not project.virtualenv_exists: try: ensure_environment() + ensure_python(three=three, python=python) do_create_virtualenv(three=three, python=python) except KeyboardInterrupt: cleanup_virtualenv(bare=False) @@ -873,6 +877,17 @@ def which_pip(allow_global=False): return which('pip') +def system_which(command): + """Emulates the system's which. Returns None is not found.""" + + c = delegator.run('{0} {1}'.format('which' if not os.name == 'nt' else 'where', command)) + try: + assert c.return_code == 0 + except AssertionError: + return None + return c.out.strip() + + def format_help(help): """Formats the help string.""" help = help.replace('Options:', str(crayons.white('Options:', bold=True))) @@ -1350,8 +1365,6 @@ def run(command, args, three=None, python=False): for __c in reversed(_c): args.insert(0, __c) - _which = 'which' if not os.name == 'nt' else 'where' - # Activate virtualenv under the current interpreter's environment try: activate_this = which('activate_this.py') @@ -1372,10 +1385,8 @@ def run(command, args, three=None, python=False): p.communicate() sys.exit(p.returncode) else: - c = delegator.run('{0} {1}'.format(_which, command)) - try: - assert c.return_code == 0 - except AssertionError: + command_path = system_which(command) + if not command_path: click.echo( '{0}: the command {1} could not be found within {2}.' ''.format( @@ -1386,7 +1397,7 @@ def run(command, args, three=None, python=False): ) sys.exit(1) - command_path = c.out.strip() + # Execute the comand. os.execl(command_path, command_path, *args) pass From 863bc9cff349c218b4f6890dffc26ea08d865af9 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 13 Sep 2017 01:01:53 -0400 Subject: [PATCH 2/7] this is a nightmare Signed-off-by: Kenneth Reitz --- pipenv/cli.py | 208 +++++++++++++++++++++++++++++++++++----------- pipenv/project.py | 5 ++ 2 files changed, 166 insertions(+), 47 deletions(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index 613b3415..e2005b2a 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -263,8 +263,114 @@ def ensure_pipfile(validate=True): project.write_toml(p) +def python_version(path_to_python): + try: + c = delegator.run('{0} --version'.format(path_to_python), block=False) + c.return_code == 0 + except Exception: + return None + + return str(c.out.strip() or c.err.strip()) + + +def pythonz_which(python, just_resolve=False): + MAP = { + '3.6': '3.6.2', + '3.5': '3.5.3', + '3.4': '3.4.7', + '3.3': '3.3.6', + '2.7': '2.7.13', + '2.6': '2.6.9', + 'python2': '2.7.13', + 'python3': '3.6.2' + } + + if python in MAP: + python = MAP[python] + + if just_resolve: + return python + + c = delegator.run('{0} locate {1}'.format(system_which('pythonz'), python)) + try: + assert c.return_code == 0 + except AssertionError: + return None + + return c.out.strip() + + +def find_a_system_python(python): + possibilities = [ + 'python', + 'python{0}'.format(python[0]), + 'python{0}{1}'.format(python[0], python[2]), + 'python{0}.{1}'.format(python[0], python[2]) + ] + for possibility in possibilities: + version = python_version(system_which(possibility)) + if version: + if python in version: + return system_which(possibility) + + def ensure_python(three=None, python=None): - if + + # Find out which python is desired. + if not python: + python = convert_three_to_python(three, python) + + if not python: + python = project.required_python_version + if python: + path_to_python = find_a_system_python(python) + + # Find the path to Python. + if not path_to_python: + path_to_python = pythonz_which(python) or system_which(python) + + if not path_to_python: + # We need to install Python. + click.echo( + '{0}: {1} {2} {3}'.format( + crayons.red('Warning', bold=True), + crayons.blue(python), + crayons.white('was not found on your system… '), + "We'll take care of the rest." + ) + ) + + # yes/no prompt. + click.confirm('Do you want to continue?', default=True, abort=True) + + with spinner(): + click.echo( + crayons.white('Installing Python {0}…'.format(python), bold=True) + ) + + os.system( + system_which('pythonz'), + 'install', pythonz_which(python, just_resolve=True) + ) + + if project.required_python_version and (three is not None): + + # Warn that they're doing something dumb. + click.echo( + '{0}: Your Pipfile requires {1} {2}, ' + 'but you specified {3} ({4}).'.format( + crayons.red('Warning', bold=True), + crayons.white('python_version', bold=True), + crayons.blue(project.requested_python_version), + crayons.blue(python_version(path_to_python)), + crayons.green(path_to_python), + ) + ) + click.echo( + 'We will carry on, as requested. {0} will likely fail.' + ''.format(crayons.red('$ pipenv check'))) + + return path_to_python def ensure_virtualenv(three=None, python=None): """Creates a virtualenv, if one doesn't exist.""" @@ -272,8 +378,8 @@ def ensure_virtualenv(three=None, python=None): if not project.virtualenv_exists: try: ensure_environment() - ensure_python(three=three, python=python) - do_create_virtualenv(three=three, python=python) + python = ensure_python(three=three, python=python) + do_create_virtualenv(python=python) except KeyboardInterrupt: cleanup_virtualenv(bare=False) sys.exit(1) @@ -475,7 +581,41 @@ def do_install_dependencies( )) -def do_create_virtualenv(three=None, python=None): +def convert_three_to_python(three, python): + """Converts a Three flag into a Python flag, and raises customer warnings + in the process, if needed. + """ + if not python: + if three is False: + if os.name == 'nt': + click.echo( + '{0} If you are running on Windows, you should use ' + 'the {1} option, instead.' + ''.format( + crayons.red('Warning!', bold=True), + crayons.green('--python') + ) + ) + + return 'python2' + + elif three is True: + if os.name == 'nt': + click.echo( + '{0} If you are running on Windows, you should use ' + 'the {1} option, instead.' + ''.format( + crayons.red('Warning!', bold=True), + crayons.green('--python') + ) + ) + + return 'python3' + else: + return python + + +def do_create_virtualenv(python=None): """Creates a virtualenv.""" click.echo(crayons.white(u'Creating a virtualenv for this project…', bold=True), err=True) @@ -493,33 +633,9 @@ def do_create_virtualenv(three=None, python=None): crayons.red(python, bold=True), crayons.white(u'to create virtualenv…', bold=True) )) - else: - if three is False: - if os.name == 'nt': - click.echo( - '{0} If you are running on Windows, you should use ' - 'the {1} option, instead.' - ''.format( - crayons.red('Warning!', bold=True), - crayons.green('--python') - ) - ) - python = 'python2' - elif three is True: - if os.name == 'nt': - click.echo( - '{0} If you are running on Windows, you should use ' - 'the {1} option, instead.' - ''.format( - crayons.red('Warning!', bold=True), - crayons.green('--python') - ) - ) - - python = 'python3' - if python: - cmd = cmd + ['-p', python] + # Use virutalenv's -p python. + cmd = cmd + ['-p', python] # Actually create the virtualenv. with spinner(): @@ -1259,12 +1375,6 @@ def shell(three=None, python=False, compat=False, shell_args=None): crayons.white('already activated.', bold=True) )) - # Activate virtualenv under the current interpreter's environment - # activate_this = which('activate_this.py') - # with open(activate_this) as f: - # code = compile(f.read(), activate_this, 'exec') - # exec(code, dict(__file__=activate_this)) - # Set an environment variable, so we know we're in the environment. os.environ['PIPENV_ACTIVE'] = '1' @@ -1344,6 +1454,20 @@ def shell(three=None, python=False, compat=False, shell_args=None): sys.exit(c.exitstatus) +def inline_activate_virtualenv(): + try: + activate_this = which('activate_this.py') + with open(activate_this) as f: + code = compile(f.read(), activate_this, 'exec') + exec(code, dict(__file__=activate_this)) + # Catch all errors, just in case. + except Exception: + click.echo( + '{0}: There was an unexpected error while activating your virtualenv. Continuing anyway…' + ''.format(crayons.red('Warning', bold=True)) + ) + + @click.command(help="Spawns a command installed into the virtualenv.", context_settings=dict( ignore_unknown_options=True, allow_extra_args=True @@ -1366,17 +1490,7 @@ def run(command, args, three=None, python=False): args.insert(0, __c) # Activate virtualenv under the current interpreter's environment - try: - activate_this = which('activate_this.py') - with open(activate_this) as f: - code = compile(f.read(), activate_this, 'exec') - exec(code, dict(__file__=activate_this)) - # Catch all errors, just in case. - except Exception: - click.echo( - '{0}: There was an unexpected error while activating your virtualenv. Continuing anyway…' - ''.format(crayons.red('Warning', bold=True)) - ) + inline_activate_virtualenv() # Windows! if os.name == 'nt': diff --git a/pipenv/project.py b/pipenv/project.py index f09b0924..de5e6adf 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -49,6 +49,11 @@ class Project(object): def pipfile_exists(self): return bool(self.pipfile_location) + @property + def required_python_version(self): + if self.pipfile_exists: + return self.parsed_pipfile.get('requires').get('python_version') + @property def project_directory(self): return os.path.abspath(os.path.join(self.pipfile_location, os.pardir)) From e1e145d8a1ff13cba8527042466405ea728a0fd0 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 13 Sep 2017 01:33:49 -0400 Subject: [PATCH 3/7] much better Signed-off-by: Kenneth Reitz --- pipenv/cli.py | 87 +++++++++++++++++---------------------------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index e2005b2a..c77ed771 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -273,34 +273,10 @@ def python_version(path_to_python): return str(c.out.strip() or c.err.strip()) -def pythonz_which(python, just_resolve=False): - MAP = { - '3.6': '3.6.2', - '3.5': '3.5.3', - '3.4': '3.4.7', - '3.3': '3.3.6', - '2.7': '2.7.13', - '2.6': '2.6.9', - 'python2': '2.7.13', - 'python3': '3.6.2' - } - - if python in MAP: - python = MAP[python] - - if just_resolve: - return python - - c = delegator.run('{0} locate {1}'.format(system_which('pythonz'), python)) - try: - assert c.return_code == 0 - except AssertionError: - return None - - return c.out.strip() - - def find_a_system_python(python): + if python in ('python2', 'python3'): + return system_which(python) + else: possibilities = [ 'python', 'python{0}'.format(python[0]), @@ -316,59 +292,52 @@ def find_a_system_python(python): def ensure_python(three=None, python=None): + path_to_python = None + # Find out which python is desired. if not python: python = convert_three_to_python(three, python) if not python: python = project.required_python_version - if python: - path_to_python = find_a_system_python(python) - # Find the path to Python. - if not path_to_python: - path_to_python = pythonz_which(python) or system_which(python) + if python: + path_to_python = find_a_system_python(python) if not path_to_python: # We need to install Python. click.echo( - '{0}: {1} {2} {3}'.format( + '{0}: Python {1} {2}'.format( crayons.red('Warning', bold=True), crayons.blue(python), - crayons.white('was not found on your system… '), - "We'll take care of the rest." + u'was not found on your system… ', ) ) - - # yes/no prompt. - click.confirm('Do you want to continue?', default=True, abort=True) - - with spinner(): - click.echo( - crayons.white('Installing Python {0}…'.format(python), bold=True) - ) - - os.system( - system_which('pythonz'), - 'install', pythonz_which(python, just_resolve=True) + click.echo( + 'You can specify specific versions of Python with:\n {0}'.format( + crayons.red('$ pipenv --python /path/to/python') ) + ) + sys.exit(1) if project.required_python_version and (three is not None): # Warn that they're doing something dumb. - click.echo( - '{0}: Your Pipfile requires {1} {2}, ' - 'but you specified {3} ({4}).'.format( - crayons.red('Warning', bold=True), - crayons.white('python_version', bold=True), - crayons.blue(project.requested_python_version), - crayons.blue(python_version(path_to_python)), - crayons.green(path_to_python), + if project.required_python_version not in python_version(path_to_python): + click.echo( + '{0}: Your Pipfile requires {1} {2}, ' + 'but you specified {3} ({4}).'.format( + crayons.red('Warning', bold=True), + crayons.white('python_version', bold=True), + crayons.blue(project.required_python_version), + crayons.blue(python_version(path_to_python)), + crayons.green(path_to_python), + ) + ) + click.echo( + 'We will carry on, as requested. {0} will likely fail.' + ''.format(crayons.red('$ pipenv check')) ) - ) - click.echo( - 'We will carry on, as requested. {0} will likely fail.' - ''.format(crayons.red('$ pipenv check'))) return path_to_python From 5ab08aaae79d813e3a16e4eae10ae2523381ee2b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 13 Sep 2017 01:39:49 -0400 Subject: [PATCH 4/7] smooth sailing Signed-off-by: Kenneth Reitz --- pipenv/cli.py | 5 +++-- pipenv/project.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index c77ed771..695c6a0f 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -304,7 +304,7 @@ def ensure_python(three=None, python=None): if python: path_to_python = find_a_system_python(python) - if not path_to_python: + if not path_to_python and python is not None: # We need to install Python. click.echo( '{0}: Python {1} {2}'.format( @@ -604,7 +604,8 @@ def do_create_virtualenv(python=None): )) # Use virutalenv's -p python. - cmd = cmd + ['-p', python] + if python: + cmd = cmd + ['-p', python] # Actually create the virtualenv. with spinner(): diff --git a/pipenv/project.py b/pipenv/project.py index de5e6adf..e496844a 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -52,7 +52,7 @@ class Project(object): @property def required_python_version(self): if self.pipfile_exists: - return self.parsed_pipfile.get('requires').get('python_version') + return self.parsed_pipfile.get('requires', {}).get('python_version') @property def project_directory(self): From a5f945cbaf354f8f3e1b2344f541abfca2462e0d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 13 Sep 2017 01:40:49 -0400 Subject: [PATCH 5/7] appease flake8 Signed-off-by: Kenneth Reitz --- pipenv/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pipenv/cli.py b/pipenv/cli.py index 695c6a0f..30f2719f 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -341,6 +341,7 @@ def ensure_python(three=None, python=None): return path_to_python + def ensure_virtualenv(three=None, python=None): """Creates a virtualenv, if one doesn't exist.""" From 7c3ab556cdbe7b543403da01765649cb19764a83 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 13 Sep 2017 01:45:36 -0400 Subject: [PATCH 6/7] try to appease the tests Signed-off-by: Kenneth Reitz --- pipenv/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index 30f2719f..7d86d705 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -274,7 +274,7 @@ def python_version(path_to_python): def find_a_system_python(python): - if python in ('python2', 'python3'): + if python.startswith('python'): return system_which(python) else: possibilities = [ From 96f3c6d0f91fc95daacb25b140a1744aa9183d31 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 13 Sep 2017 01:47:22 -0400 Subject: [PATCH 7/7] also return absolute paths Signed-off-by: Kenneth Reitz --- pipenv/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index 7d86d705..b0f6c340 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -274,8 +274,10 @@ def python_version(path_to_python): def find_a_system_python(python): - if python.startswith('python'): + if python.startswith('py'): return system_which(python) + elif os.path.isabs(python): + return python else: possibilities = [ 'python',