Allow pip install with entrypoint names with colons (#3901)

This commit is contained in:
Francesco Montesano 2016-11-19 00:27:16 +01:00 committed by Xavier Fernandez
parent 0964c9797c
commit d64b871d97
5 changed files with 95 additions and 59 deletions

View File

@ -1,5 +1,7 @@
**9.1.0 (UNRELEASED)** **9.1.0 (UNRELEASED)**
* Use pkg_resources to parse the entry points file to allow names with
colons (:pull:`3901`)
**9.0.1 (2016-11-06)** **9.0.1 (2016-11-06)**
@ -111,7 +113,6 @@
* Restore the ability to use inline comments in requirements files passed to * Restore the ability to use inline comments in requirements files passed to
``pip freeze`` (:issue:`3680`). ``pip freeze`` (:issue:`3680`).
**8.1.2 (2016-05-10)** **8.1.2 (2016-05-10)**
* Fix a regression on systems with uninitialized locale (:issue:`3575`). * Fix a regression on systems with uninitialized locale (:issue:`3575`).

View File

@ -20,7 +20,6 @@ from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import Version, parse as parse_version from pip._vendor.packaging.version import Version, parse as parse_version
from pip._vendor.six.moves import configparser
import pip.wheel import pip.wheel
@ -35,7 +34,7 @@ from pip.locations import (
from pip.utils import ( from pip.utils import (
display_path, rmtree, ask_path_exists, backup_dir, is_installable_dir, display_path, rmtree, ask_path_exists, backup_dir, is_installable_dir,
dist_in_usersite, dist_in_site_packages, egg_link_path, dist_in_usersite, dist_in_site_packages, egg_link_path,
call_subprocess, read_text_file, FakeFile, _make_build_dir, ensure_dir, call_subprocess, read_text_file, _make_build_dir, ensure_dir,
get_installed_version, normalize_path, dist_is_local, get_installed_version, normalize_path, dist_is_local,
) )
@ -724,36 +723,41 @@ class InstallRequirement(object):
paths_to_remove.add(os.path.join(bin_dir, script) + '.bat') paths_to_remove.add(os.path.join(bin_dir, script) + '.bat')
# find console_scripts # find console_scripts
if dist.has_metadata('entry_points.txt'): _scripts_to_remove = []
if six.PY2: console_scripts = dist.get_entry_map(group='console_scripts')
options = {} for name in console_scripts.keys():
else: _scripts_to_remove.extend(self._script_names(dist, name, False))
options = {"delimiters": ('=', )} # find gui_scripts
config = configparser.SafeConfigParser(**options) gui_scripts = dist.get_entry_map(group='gui_scripts')
config.readfp( for name in gui_scripts.keys():
FakeFile(dist.get_metadata_lines('entry_points.txt')) _scripts_to_remove.extend(self._script_names(dist, name, True))
)
if config.has_section('console_scripts'): for s in _scripts_to_remove:
for name, value in config.items('console_scripts'): paths_to_remove.add(s)
if dist_in_usersite(dist):
bin_dir = bin_user
else:
bin_dir = bin_py
paths_to_remove.add(os.path.join(bin_dir, name))
if WINDOWS:
paths_to_remove.add(
os.path.join(bin_dir, name) + '.exe'
)
paths_to_remove.add(
os.path.join(bin_dir, name) + '.exe.manifest'
)
paths_to_remove.add(
os.path.join(bin_dir, name) + '-script.py'
)
paths_to_remove.remove(auto_confirm) paths_to_remove.remove(auto_confirm)
self.uninstalled = paths_to_remove self.uninstalled = paths_to_remove
def _script_names(self, dist, name, is_gui):
'''Create the fully qualified name of the files created by
{console,gui}_scripts for the given ``dist``. Returns the list of file
names'''
if dist_in_usersite(dist):
bin_dir = bin_user
else:
bin_dir = bin_py
exe_name = os.path.join(bin_dir, name)
paths_to_remove = [exe_name, ]
if WINDOWS:
paths_to_remove.add(exe_name + '.exe')
paths_to_remove.add(exe_name + '.exe.manifest')
if is_gui:
paths_to_remove.add(exe_name + '-script.pyw')
else:
paths_to_remove.add(exe_name + '-script.py')
return paths_to_remove
def rollback_uninstall(self): def rollback_uninstall(self):
if self.uninstalled: if self.uninstalled:
self.uninstalled.rollback() self.uninstalled.rollback()

View File

@ -39,7 +39,6 @@ from pip.utils.setuptools_build import SETUPTOOLS_SHIM
from pip._vendor.distlib.scripts import ScriptMaker from pip._vendor.distlib.scripts import ScriptMaker
from pip._vendor import pkg_resources from pip._vendor import pkg_resources
from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.six.moves import configparser
wheel_ext = '.whl' wheel_ext = '.whl'
@ -224,16 +223,19 @@ def get_entrypoints(filename):
data.write("\n") data.write("\n")
data.seek(0) data.seek(0)
cp = configparser.RawConfigParser() # get the entry points and then the script names
cp.optionxform = lambda option: option entry_points = pkg_resources.EntryPoint.parse_map(data)
cp.readfp(data) console = entry_points.get('console_scripts', {})
gui = entry_points.get('gui_scripts', {})
console = {} def _split_ep(s):
gui = {} """get the string representation of EntryPoint, remove space and split
if cp.has_section('console_scripts'): on '='"""
console = dict(cp.items('console_scripts')) return str(s).replace(" ", "").split("=")
if cp.has_section('gui_scripts'):
gui = dict(cp.items('gui_scripts')) # convert the EntryPoint objects into strings with module:function
console = dict(_split_ep(v) for v in console.values())
gui = dict(_split_ep(v) for v in gui.values())
return console, gui return console, gui

View File

@ -9,6 +9,7 @@ import pretend
from os.path import join, normpath from os.path import join, normpath
from tempfile import mkdtemp from tempfile import mkdtemp
from tests.lib import assert_all_changes, pyversion from tests.lib import assert_all_changes, pyversion
from tests.lib import create_test_package_with_setup
from tests.lib.local_repos import local_repo, local_checkout from tests.lib.local_repos import local_repo, local_checkout
from pip.req import InstallRequirement from pip.req import InstallRequirement
@ -154,32 +155,54 @@ def test_uninstall_overlapping_package(script, data):
assert_all_changes(result2, result3, []) assert_all_changes(result2, result3, [])
def test_uninstall_entry_point(script): @pytest.mark.parametrize("console_scripts",
["test_ = distutils_install",
"test_:test_ = distutils_install"])
def test_uninstall_entry_point(script, console_scripts):
""" """
Test uninstall package with two or more entry points in the same section, Test uninstall package with two or more entry points in the same section,
whose name contain a colon. whose name contain a colon.
""" """
script.scratch_path.join("ep_install").mkdir() pkg_name = 'ep_install'
pkg_path = script.scratch_path / 'ep_install' pkg_path = create_test_package_with_setup(
pkg_path.join("setup.py").write(textwrap.dedent(""" script,
from setuptools import setup name=pkg_name,
setup( version='0.1',
name='ep-install', entry_points={"console_scripts": [console_scripts, ],
version='0.1', "pip_test.ep":
entry_points={"pip_test.ep": ["ep:name1 = distutils_install",
["ep:name1 = distutils_install", "ep:name2 = distutils_install"]
"ep:name2 = distutils_install"] }
} )
) script_name = script.bin_path.join(console_scripts.split('=')[0].strip())
"""))
result = script.pip('install', pkg_path) result = script.pip('install', pkg_path)
assert script_name.exists
result = script.pip('list', '--format=legacy') result = script.pip('list', '--format=legacy')
assert "ep-install (0.1)" in result.stdout assert "ep-install (0.1)" in result.stdout
script.pip('uninstall', 'ep_install', '-y') script.pip('uninstall', 'ep_install', '-y')
assert not script_name.exists
result2 = script.pip('list', '--format=legacy') result2 = script.pip('list', '--format=legacy')
assert "ep-install (0.1)" not in result2.stdout assert "ep-install (0.1)" not in result2.stdout
def test_uninstall_gui_scripts(script):
"""
Make sure that uninstall removes gui scripts
"""
pkg_name = "gui_pkg"
pkg_path = create_test_package_with_setup(
script,
name=pkg_name,
version='0.1',
entry_points={"gui_scripts": ["test_ = distutils_install", ], }
)
script_name = script.bin_path.join('test_')
script.pip('install', pkg_path)
assert script_name.exists
script.pip('uninstall', pkg_name, '-y')
assert not script_name.exists
@pytest.mark.network @pytest.mark.network
def test_uninstall_console_scripts(script): def test_uninstall_console_scripts(script):
""" """

View File

@ -11,15 +11,21 @@ from pip.exceptions import InvalidWheelFilename, UnsupportedWheel
from pip.utils import unpack_file from pip.utils import unpack_file
def test_get_entrypoints(tmpdir): @pytest.mark.parametrize("console_scripts",
with open(str(tmpdir.join("entry_points.txt")), "w") as fp: ["pip = pip.main:pip", "pip:pip = pip.main:pip"])
def test_get_entrypoints(tmpdir, console_scripts):
entry_points = tmpdir.join("entry_points.txt")
with open(str(entry_points), "w") as fp:
fp.write(""" fp.write("""
[console_scripts] [console_scripts]
pip = pip.main:pip {0}
""") [section]
common:one = module:func
common:two = module:other_func
""".format(console_scripts))
assert wheel.get_entrypoints(str(tmpdir.join("entry_points.txt"))) == ( assert wheel.get_entrypoints(str(entry_points)) == (
{"pip": "pip.main:pip"}, dict([console_scripts.split(' = ')]),
{}, {},
) )