From f73385adc573ce77dcc49986582966db3c6eda77 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Tue, 22 Mar 2011 16:13:04 -0700 Subject: [PATCH] Allow requirements.txt URLs to have egg= definitions. --- docs/requirement-format.txt | 9 +++++++++ pip/index.py | 16 ++++++++++------ pip/req.py | 36 +++++++++++++++++++++--------------- tests/test_requirements.py | 17 ++++++++++++++++- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/docs/requirement-format.txt b/docs/requirement-format.txt index ed31fb403..d326ea63e 100644 --- a/docs/requirement-format.txt +++ b/docs/requirement-format.txt @@ -24,6 +24,15 @@ The ``#egg=MyProject`` part is important, because while you can install simply given the svn location, the project name is useful in other places. +You can also specify the egg name for a non-editable url. This is useful to +point to HEAD locations on the local filesystem: + + file:///path/to/your/lib/project#egg=MyProject + +or relative paths: + + file:../../lib/project#egg=MyProject + If you need to give pip (and by association easy_install) hints about where to find a package, you can use the ``-f`` (``--find-links``) option, like:: diff --git a/pip/index.py b/pip/index.py index 1325387c1..1b8a52b4f 100644 --- a/pip/index.py +++ b/pip/index.py @@ -578,13 +578,9 @@ class Link(object): @property def filename(self): - url = self.url - url = url.split('#', 1)[0] - url = url.split('?', 1)[0] - url = url.rstrip('/') + url = self.url_fragment name = posixpath.basename(url) - assert name, ( - 'URL %r produced no filename' % url) + assert name, ('URL %r produced no filename' % url) return name @property @@ -598,6 +594,14 @@ class Link(object): def splitext(self): return splitext(posixpath.basename(self.path.rstrip('/'))) + @property + def url_fragment(self): + url = self.url + url = url.split('#', 1)[0] + url = url.split('?', 1)[0] + url = url.rstrip('/') + return url + _egg_fragment_re = re.compile(r'#egg=([^&]*)') @property diff --git a/pip/req.py b/pip/req.py index ad4172b78..9a7cd2d24 100644 --- a/pip/req.py +++ b/pip/req.py @@ -73,28 +73,34 @@ class InstallRequirement(object): """ url = None name = name.strip() - req = name + req = None path = os.path.normpath(os.path.abspath(name)) + link = None if is_url(name): - url = name - ## FIXME: I think getting the requirement here is a bad idea: - #req = get_requirement_from_url(url) - req = None + link = Link(name) elif os.path.isdir(path) and (os.path.sep in name or name.startswith('.')): if not is_installable_dir(path): - raise InstallationError("Directory %r is not installable. File 'setup.py' not found." - % name) - url = path_to_url(name) - #req = get_requirement_from_url(url) - req = None + raise InstallationError("Directory %r is not installable. File 'setup.py' not found.", name) + link = Link(path_to_url(name)) elif is_archive_file(path): if not os.path.isfile(path): - logger.warn('Requirement %r looks like a filename, but the file does not exist' - % name) - url = path_to_url(name) - #req = get_requirement_from_url(url) - req = None + logger.warn('Requirement %r looks like a filename, but the file does not exist', name) + link = Link(path_to_url(name)) + + # If the line has an egg= definition, but isn't editable, pull the requirement out. + # Otherwise, assume the name is the req for the non URL/path/archive case. + if link and req is None: + url = link.url_fragment + req = link.egg_fragment + + # Handle relative file URLs + if link.scheme == 'file' and re.search(r'\.\./', url): + url = path_to_url(os.path.normpath(os.path.abspath(link.path))) + + else: + req = name + return cls(req, comes_from, url=url) def __str__(self): diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 8c8439d56..73daec464 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -1,7 +1,9 @@ import textwrap +from pip.backwardcompat import urllib from pip.req import Requirements -from tests.test_pip import reset_env, run_pip, write_file, pyversion +from tests.test_pip import reset_env, run_pip, write_file, pyversion, here from tests.local_repos import local_checkout +from tests.path import Path def test_requirements_file(): """ @@ -22,6 +24,19 @@ def test_requirements_file(): fn = '%s-%s-py%s.egg-info' % (other_lib_name, other_lib_version, pyversion) assert result.files_created[env.site_packages/fn].dir +def test_relative_requirements_file(): + """ + Test installing from a requirements file with a relative path with an egg= definition.. + + """ + url = 'file://' + str(urllib.quote(Path(here).abspath + '/packages/../packages/FSPkg') + '#egg=FSPkg').replace('\\', '/') + env = reset_env() + write_file('file-egg-req.txt', textwrap.dedent("""\ + %s + """ % url)) + result = run_pip('install', '-vvv', '-r', env.scratch_path / 'file-egg-req.txt') + assert (env.site_packages/'FSPkg-0.1dev-py%s.egg-info' % pyversion) in result.files_created, str(result) + assert (env.site_packages/'fspkg') in result.files_created, str(result.stdout) def test_multiple_requirements_files(): """