From b5e3261cc7e5842fd2aa95fd3be922af332cc95c Mon Sep 17 00:00:00 2001 From: Luke Hogan Date: Thu, 18 Feb 2016 15:12:30 -0800 Subject: [PATCH 1/2] Patch to support looking up python path directly from version number (no more need to nest call to `locate_python`) --- pew/pew.py | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/pew/pew.py b/pew/pew.py index e8d854b..6a66fc7 100644 --- a/pew/pew.py +++ b/pew/pew.py @@ -25,6 +25,8 @@ from pythonz.installer.pythoninstaller import PythonInstaller, AlreadyInstalledError from pythonz.commands.list import ListCommand as ListPythons from pythonz.commands.locate import LocateCommand as LocatePython + from pythonz.define import PATH_PYTHONS + from pythonz.util import Package, is_installed else: # Pythonz does not support windows InstallCommand = ListPythons = LocatePython = \ @@ -177,10 +179,38 @@ def shell(env, cwd=None): fork_shell(env, [shell], cwd) +def locate_python_by_version(version, pytype): + """ + Save the user a little typing and attempt to find the installed + Python path given just the version number. + E.g., 3.4.3 -> /home/user/.pythonz/pythons/CPython-3.4.3/bin/python3 + + Only intended to find pythons installed by Pythonz. + Windows is therefore unsupported. + + :param version: numerical version (e.g., 2.7.10, 3.5.1) + :param pytype: Python implementation (e.g., cpython, pypy) + :returns: path string or None + """ + path = None + if windows: + return path + pkg = Package(version, pytype) + if is_installed(pkg): + for bin in ('python3', 'python', 'pypy3', 'pypy'): + path = os.path.join(PATH_PYTHONS, pkg.name, 'bin', bin) + if os.path.exists(path): + break + return path + + def mkvirtualenv(envname, python=None, packages=[], project=None, - requirements=None, rest=[]): + requirements=None, pytype='cpython', rest=[]): if python: + version_path = locate_python_by_version(python, pytype) + if version_path: + python = version_path rest = ["--python=%s" % python] + rest try: @@ -200,6 +230,8 @@ def mkvirtualenv(envname, python=None, packages=[], project=None, def mkvirtualenv_argparser(): parser = argparse.ArgumentParser() parser.add_argument('-p', '--python') + parser.add_argument('--type', default='cpython', help='Type of Python \ +version: cpython, stackless, pypy, pypy3 or jython.') parser.add_argument('-i', action='append', dest='packages', help='Install \ a package after the environment is created. This option may be repeated.') parser.add_argument('-r', dest='requirements', help='Provide a pip \ @@ -220,9 +252,8 @@ def new_cmd(argv): parser.add_argument('envname') args, rest = parser.parse_known_args(argv) project = expandpath(args.project) if args.project else None - mkvirtualenv(args.envname, args.python, args.packages, project, - args.requirements, rest) + args.requirements, args.type, rest) if args.activate: shell(args.envname) @@ -490,7 +521,7 @@ def mkproject_cmd(argv): sys.exit('Project %s already exists.' % args.envname) mkvirtualenv(args.envname, args.python, args.packages, project.absolute(), - args.requirements, rest) + args.requirements, args.type, rest) project.mkdir() @@ -511,7 +542,7 @@ def mktmpenv_cmd(argv): args, rest = parser.parse_known_args(argv) mkvirtualenv(env, args.python, args.packages, requirements=args.requirements, - rest=rest) + pytype=args.type, rest=rest) print('This is a temporary environment. It will be deleted when you exit') try: if args.activate: From 052928d188a7527ddb9d624f3de5b883e88d6b3a Mon Sep 17 00:00:00 2001 From: Luke Hogan Date: Thu, 18 Feb 2016 22:07:49 -0800 Subject: [PATCH 2/2] Unit tests and bug fix for path resolution --- pew/pew.py | 5 ++-- tests/test_mkvirtualenv.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/pew/pew.py b/pew/pew.py index 6a66fc7..327d74f 100644 --- a/pew/pew.py +++ b/pew/pew.py @@ -198,8 +198,9 @@ def locate_python_by_version(version, pytype): pkg = Package(version, pytype) if is_installed(pkg): for bin in ('python3', 'python', 'pypy3', 'pypy'): - path = os.path.join(PATH_PYTHONS, pkg.name, 'bin', bin) - if os.path.exists(path): + guess = os.path.join(PATH_PYTHONS, pkg.name, 'bin', bin) + if os.path.exists(guess): + path = guess break return path diff --git a/tests/test_mkvirtualenv.py b/tests/test_mkvirtualenv.py index 78c8924..deab5b9 100644 --- a/tests/test_mkvirtualenv.py +++ b/tests/test_mkvirtualenv.py @@ -1,3 +1,5 @@ +import re +import sys from os import unlink from subprocess import check_call, CalledProcessError from pathlib import Path @@ -12,6 +14,10 @@ import pytest from pew._utils import invoke_pew as invoke +from utils import skip_windows + +if not sys.platform == 'win32': + from pythonz.define import PATH_PYTHONS def are_we_connected(): @@ -26,6 +32,48 @@ def are_we_connected(): reason="An internet connection is required") +def pythonz_python_found(): + python_path = Path(PATH_PYTHONS) + found = python_path.exists() + if found: + found = sum(1 for __ in python_path.iterdir()) > 0 + return found + + +pythonz_python_required = pytest.mark.skipif(not pythonz_python_found(), + reason='Pythonz is required') + +def get_pythonz_python_version(): + python_path = Path(PATH_PYTHONS) + pattern = '^\w+-(\d+\.?)+?' + for f in python_path.iterdir(): + if re.match(pattern, f.name): + return f.name.split('-')[1] + + +@skip_windows(reason="Pythonz is unsupported") +@pythonz_python_required +def test_good_version_resolved_correctly(workon_home): + version = get_pythonz_python_version() + returncode = invoke('new', 'env', '-d', '-p', version).returncode + invoke('rm', 'env') + assert returncode == 0 + + +@skip_windows(reason="Pythonz is unsupported") +@pythonz_python_required +def test_bad_version_not_resolved(workon_home): + version = get_pythonz_python_version() + ".broken" + returncode = invoke('new', 'env', '-d', '-p', version).returncode + assert returncode != 0 + + +def test_explict_path_accepted(workon_home): + returncode = invoke('new', 'env', '-d', '-p', 'python').returncode + invoke('rm', 'env') + assert returncode == 0 + + def test_create(workon_home): envs = set(invoke('ls').out.split()) invoke('new', 'env', '-d')