Commit c7214855 authored by PJ Eby's avatar PJ Eby

Massive API refactoring; see setuptools.txt changelog for details. Also,

add ``#egg=project-version`` link support, and docs on how to make your
package available for EasyInstall to find.

--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041135
parent 61a0e710
......@@ -623,6 +623,22 @@ Known Issues
Made ``easy-install.pth`` work in platform-specific alternate site
directories (e.g. ``~/Library/Python/2.x/site-packages``).
* If you manually delete the current version of a package, the next run of
EasyInstall against the target directory will now remove the stray entry
from the ``easy-install.pth``file.
* EasyInstall now recognizes URLs with a ``#egg=project_name`` fragment ID
as pointing to the named project's source checkout. Such URLs have a lower
match precedence than any other kind of distribution, so they'll only be
used if they have a higher version number than any other available
distribution. (Future versions may allow you to specify that you want to
use source checkouts instead of other kinds of distributions.) The ``#egg``
fragment can contain a version if it's formatted as ``#egg=proj-ver``,
where ``proj`` is the project name, and ``ver`` is the version number. You
*must* use the format for these values that the ``bdist_egg`` command uses;
i.e., all non-alphanumeric runs must be condensed to single underscore
characters.
0.5a12
* Fix ``python -m easy_install`` not working due to setuptools being installed
as a zipfile. Update safety scanner to check for modules that might be used
......
Pluggable Distributions of Python Software
==========================================
Distributions
-------------
A "Distribution" is a collection of files that represent a "Release" of a
"Project" as of a particular point in time, denoted by a
"Version"::
>>> import sys, pkg_resources
>>> from pkg_resources import Distribution
>>> Distribution(project_name="Foo", version="1.2")
Foo 1.2
Distributions have a location, which can be a filename, URL, or really anything
else you care to use::
>>> dist = Distribution(
... location="http://example.com/something",
... project_name="Bar", version="0.9"
... )
>>> dist
Bar 0.9 (http://example.com/something)
Distributions have various introspectable attributes::
>>> dist.location
'http://example.com/something'
>>> dist.project_name
'Bar'
>>> dist.version
'0.9'
>>> dist.py_version == sys.version[:3]
True
>>> print dist.platform
None
Including various computed attributes::
>>> from pkg_resources import parse_version
>>> dist.parsed_version == parse_version(dist.version)
True
>>> dist.key # case-insensitive form of the project name
'bar'
Distributions are compared (and hashed) by version first::
>>> Distribution(version='1.0') == Distribution(version='1.0')
True
>>> Distribution(version='1.0') == Distribution(version='1.1')
False
>>> Distribution(version='1.0') < Distribution(version='1.1')
True
but also by project name (case-insensitive), platform, Python version,
location, etc.::
>>> Distribution(project_name="Foo",version="1.0") == \
... Distribution(project_name="Foo",version="1.0")
True
>>> Distribution(project_name="Foo",version="1.0") == \
... Distribution(project_name="foo",version="1.0")
True
>>> Distribution(project_name="Foo",version="1.0") == \
... Distribution(project_name="Foo",version="1.1")
False
>>> Distribution(project_name="Foo",py_version="2.3",version="1.0") == \
... Distribution(project_name="Foo",py_version="2.4",version="1.0")
False
>>> Distribution(location="spam",version="1.0") == \
... Distribution(location="spam",version="1.0")
True
>>> Distribution(location="spam",version="1.0") == \
... Distribution(location="baz",version="1.0")
False
Hash and compare distribution by prio/plat
Get version from metadata
provider capabilities
egg_name()
as_requirement()
from_location, from_filename (w/path normalization)
Releases may have zero or more "Requirements", which indicate
what releases of another project the release requires in order to
function. A Requirement names the other project, expresses some criteria
as to what releases of that project are acceptable, and lists any "Extras"
that the requiring release may need from that project. (An Extra is an
optional feature of a Release, that can only be used if its additional
Requirements are satisfied.)
The Working Set
---------------
A collection of active distributions is called a Working Set. Note that a
Working Set can contain any importable distribution, not just pluggable ones.
For example, the Python standard library is an importable distribution that
will usually be part of the Working Set, even though it is not pluggable.
Similarly, when you are doing development work on a project, the files you are
editing are also a Distribution. (And, with a little attention to the
directory names used, and including some additional metadata, such a
"development distribution" can be made pluggable as well.)
>>> from pkg_resources import WorkingSet
A working set's entries are the sys.path entries that correspond to the active
distributions. By default, the working set's entries are the items on
``sys.path``::
>>> ws = WorkingSet()
>>> ws.entries == sys.path
True
But you can also create an empty working set explicitly, and add distributions
to it::
>>> ws = WorkingSet([])
>>> ws.add(dist)
>>> ws.entries
['http://example.com/something']
>>> dist in ws
True
>>> Distribution('foo') in ws
False
And you can iterate over its distributions::
>>> list(ws)
[Bar 0.9 (http://example.com/something)]
Adding the same distribution more than once is a no-op::
>>> ws.add(dist)
>>> list(ws)
[Bar 0.9 (http://example.com/something)]
For that matter, adding multiple distributions for the same project also does
nothing, because a working set can only hold one active distribution per
project -- the first one added to it::
>>> ws.add(
... Distribution(
... 'http://example.com/something', project_name="Bar",
... version="7.2"
... )
... )
>>> list(ws)
[Bar 0.9 (http://example.com/something)]
You can append a path entry to a working set using ``add_entry()``::
>>> ws.entries
['http://example.com/something']
>>> ws.add_entry(pkg_resources.__file__)
>>> ws.entries
['http://example.com/something', '...pkg_resources.py...']
Multiple additions result in multiple entries, even if the entry is already in
the working set (because ``sys.path`` can contain the same entry more than
once)::
>>> ws.add_entry(pkg_resources.__file__)
>>> ws.entries
['...example.com...', '...pkg_resources...', '...pkg_resources...']
And you can specify the path entry a distribution was found under, using the
optional second parameter to ``add()``
>>> ws.add(dist,"foo")
>>> ws.add(dist,"bar")
>>> ws.entries
['http://example.com/something', ..., 'foo', 'bar']
But even if a distribution is found under multiple path entries, it still only
shows up once when iterating the working set:
>>> list(ws)
[Bar 0.9 (http://example.com/something)]
You can ask a WorkingSet to ``find()`` a distribution matching a requirement::
>>> from pkg_resources import Requirement
>>> print ws.find(Requirement.parse("Foo==1.0")) # no match, return None
None
>>> ws.find(Requirement.parse("Bar==0.9")) # match, return distribution
Bar 0.9 (http://example.com/something)
Note that asking for a conflicting version of a distribution already in a
working set triggers a ``pkg_resources.VersionConflict`` error:
>>> ws.find(Requirement.parse("Bar==1.0")) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
VersionConflict: (Bar 0.9 (http://example.com/something),
Requirement.parse('Bar==1.0'))
You can subscribe a callback function to receive notifications whenever a new
distribution is added to a working set. The callback is immediately invoked
once for each existing distribution in the working set, and then is called
again for new distributions added thereafter::
>>> def added(dist): print "Added", dist
>>> ws.subscribe(added)
Added Bar 0.9
>>> foo12 = Distribution(project_name="Foo", version="1.2")
>>> ws.add(foo12)
Added Foo 1.2
Note, however, that only the first distribution added for a given project name
will trigger a callback, even during the initial ``subscribe()`` callback::
>>> foo14 = Distribution(project_name="Foo", version="1.4")
>>> ws.add(foo14) # no callback, because Foo 1.2 is already active
>>> ws = WorkingSet([])
>>> ws.add(foo12)
>>> ws.add(foo14)
>>> ws.subscribe(added)
Added Foo 1.2
And adding a callback more than once has no effect, either::
>>> ws.subscribe(added) # no callbacks
# and no double-callbacks on subsequent additions, either
>>> ws.add(Distribution(project_name="JustATest", version="0.99"))
Added JustATest 0.99
This diff is collapsed.
......@@ -732,6 +732,58 @@ might consider submitting a patch to the ``setuptools.command.sdist`` module
so we can include support for it, too.)
Making your package available for EasyInstall
---------------------------------------------
If you use the ``register`` command (``setup.py register``) to register your
package with PyPI, that's most of the battle right there. (See the
`docs for the register command`_ for more details.)
.. _docs for the register command: http://docs.python.org/dist/package-index.html
If you also use the `upload`_ command to upload actual distributions of your
package, that's even better, because EasyInstall will be able to find and
download them directly from your project's PyPI page.
However, there may be reasons why you don't want to upload distributions to
PyPI, and just want your existing distributions (or perhaps a Subversion
checkout) to be used instead.
So here's what you need to do before running the ``register`` command. There
are three ``setup()`` arguments that affect EasyInstall:
``url`` and ``download_url``
These become links on your project's PyPI page. EasyInstall will examine
them to see if they link to a package ("primary links"), or whether they are
HTML pages. If they're HTML pages, EasyInstall scans all HREF's on the
page for primary links
``long_description``
EasyInstall will check any URLs contained in this argument to see if they
are primary links.
A URL is considered a "primary link" if it is a link to a .tar.gz, .tgz, .zip,
.egg, .egg.zip, .tar.bz2, or .exe file, or if it has an ``#egg=project`` or
``#egg=project-version`` fragment identifier attached to it. EasyInstall
attempts to determine a project name and optional version number from the text
of a primary link *without* downloading it. When it has found all the primary
links, EasyInstall will select the best match based on requested version,
platform compatibility, and other criteria.
So, if your ``url`` or ``download_url`` point either directly to a downloadable
source distribution, or to HTML page(s) that have direct links to such, then
EasyInstall will be able to locate downloads automatically. If you want to
make Subversion checkouts available, then you should create links with either
``#egg=project`` or ``#egg=project-version`` added to the URL (replacing
``project`` and ``version`` with appropriate values).
Note that Subversion checkout links are of lower precedence than other kinds
of distributions, so EasyInstall will not select a Subversion checkout for
downloading unless it has a version included in the ``#egg=`` suffix, and
it's a higher version than EasyInstall has seen in any other links for your
project.
Distributing Extensions compiled with Pyrex
-------------------------------------------
......@@ -1342,7 +1394,7 @@ Release Notes/Change History
* Fixed ``pkg_resources.resource_exists()`` not working correctly.
* Many ``pkg_resources`` API changes:
* Many ``pkg_resources`` API changes and enhancements:
* ``Distribution`` objects now implement the ``IResourceProvider`` and
``IMetadataProvider`` interfaces, so you don't need to reference the (no
......@@ -1354,11 +1406,35 @@ Release Notes/Change History
* The ``path`` attribute of ``Distribution`` objects is now ``location``,
because it isn't necessarily a filesystem path (and hasn't been for some
time now).
time now). The ``location`` of ``Distribution`` objects in the filesystem
should always be normalized using ``pkg_resources.normalize_path()``; all
of the setuptools and EasyInstall code that generates distributions from
the filesystem (including ``Distribution.from_filename()``) ensure this
invariant, but if you use a more generic API like ``Distribution()`` or
``Distribution.from_location()`` you should take care that you don't
create a distribution with an un-normalized filesystem path.
* ``Distribution`` objects now have an ``as_requirement()`` method that
returns a ``Requirement`` for the distribution's project name and version.
* Distribution objects no longer have an ``installed_on()`` method, and the
``install_on()`` method is now ``activate()`` (but may go away altogether
soon).
* ``find_distributions()`` now takes an additional argument called ``only``,
that tells it to only yield distributions whose location is the passed-in
path. (It defaults to False, so that the default behavior is unchanged.)
* The ``resolve()`` method of ``AvailableDistributions`` is now a method of
``WorkingSet`` instead, and the ``best_match()`` method now uses a working
set instead of a path list as its second argument.
* There is a new ``pkg_resources.add_activation_listener()`` API that lets
you register a callback for notifications about distributions added to
``sys.path`` (including the distributions already on it). This is
basically a hook for extensible applications and frameworks to be able to
search for plugin metadata in distributions added at runtime.
0.5a13
* Fixed a bug in resource extraction from nested packages in a zipped egg.
......
from setuptools.command.easy_install import easy_install
from distutils.util import convert_path
from pkg_resources import Distribution, PathMetadata
from pkg_resources import Distribution, PathMetadata, normalize_path
from distutils import log
import sys, os
......@@ -49,7 +49,7 @@ class develop(easy_install):
# Make a distribution for the package's source
self.dist = Distribution(
self.egg_path,
normalize_path(self.egg_path),
PathMetadata(self.egg_path, os.path.abspath(ei.egg_info)),
project_name = ei.egg_name
)
......
......@@ -139,7 +139,7 @@ class easy_install(Command):
)
# default --record from the install command
self.set_undefined_options('install', ('record', 'record'))
normpath = map(normalize,sys.path)
normpath = map(normalize_path, sys.path)
self.all_site_dirs = get_site_dirs()
if self.site_dirs is not None:
site_dirs = [
......@@ -148,15 +148,15 @@ class easy_install(Command):
for d in site_dirs:
if not os.path.isdir(d):
log.warn("%s (in --site-dirs) does not exist", d)
elif normalize(d) not in normpath:
elif normalize_path(d) not in normpath:
raise DistutilsOptionError(
d+" (in --site-dirs) is not on sys.path"
)
else:
self.all_site_dirs.append(normalize(d))
self.all_site_dirs.append(normalize_path(d))
instdir = self.install_dir or self.all_site_dirs[-1]
if normalize(instdir) in self.all_site_dirs:
instdir = normalize_path(self.install_dir or self.all_site_dirs[-1])
if instdir in self.all_site_dirs:
if self.pth_file is None:
self.pth_file = PthDistributions(
os.path.join(instdir,'easy-install.pth')
......@@ -171,12 +171,12 @@ class easy_install(Command):
"Can't do single-version installs outside 'site-package' dirs"
)
self.install_dir = normalize(instdir)
self.install_dir = instdir
self.index_url = self.index_url or "http://www.python.org/pypi"
self.shadow_path = map(normalize,sys.path)
for path_item in self.install_dir, self.script_dir:
if normalize(path_item) not in self.shadow_path:
self.shadow_path.insert(0, normalize(path_item))
self.shadow_path = self.all_site_dirs[:]
for path_item in self.install_dir, normalize_path(self.script_dir):
if path_item not in self.shadow_path:
self.shadow_path.insert(0, path_item)
if self.package_index is None:
self.package_index = self.create_index(
self.index_url, search_path = self.shadow_path
......@@ -354,8 +354,8 @@ class easy_install(Command):
if dist in requirement:
log.info("Processing dependencies for %s", requirement)
try:
self.local_index.resolve(
[requirement], self.shadow_path, self.easy_install
WorkingSet(self.shadow_path).resolve(
[requirement], self.local_index, self.easy_install
)
except DistributionNotFound, e:
raise DistutilsError(
......@@ -711,7 +711,7 @@ similar to one of these examples, in order to select the desired version:
pkg_resources.require("%(name)s==%(version)s") # this exact version
pkg_resources.require("%(name)s>=%(version)s") # this version or higher
"""
if self.install_dir not in map(normalize,sys.path):
if self.install_dir not in map(normalize_path,sys.path):
msg += """
Note also that the installation directory must be on sys.path at runtime for
......@@ -782,14 +782,14 @@ PYTHONPATH, or by being added to sys.path by your code.)
return
for d in self.pth_file.get(dist.key,()): # drop old entries
if self.multi_version or normalize(d.location) != normalize(dist.location):
if self.multi_version or d.location != dist.location:
log.info("Removing %s from easy-install.pth file", d)
self.pth_file.remove(d)
if normalize(d.location) in self.shadow_path:
if d.location in self.shadow_path:
self.shadow_path.remove(d.location)
if not self.multi_version:
if normalize(dist.location) in map(normalize,self.pth_file.paths):
if dist.location in map(normalize_path,self.pth_file.paths):
log.info(
"%s is already the active version in easy-install.pth",
dist
......@@ -797,8 +797,8 @@ PYTHONPATH, or by being added to sys.path by your code.)
else:
log.info("Adding %s to easy-install.pth file", dist)
self.pth_file.add(dist) # add new entry
if normalize(dist.location) not in self.shadow_path:
self.shadow_path.append(normalize(dist.location))
if dist.location not in self.shadow_path:
self.shadow_path.append(dist.location)
self.pth_file.save()
......@@ -894,8 +894,8 @@ def get_site_dirs():
'site-packages'))
sitedirs = filter(os.path.isdir, sitedirs)
sitedirs = map(normalize, sitedirs)
return sitedirs or [normalize(get_python_lib())] # ensure at least one
sitedirs = map(normalize_path, sitedirs)
return sitedirs or [normalize_path(get_python_lib())] # ensure at least one
......@@ -906,7 +906,7 @@ def expand_paths(inputs):
seen = {}
for dirname in inputs:
dirname = normalize(dirname)
dirname = normalize_path(dirname)
if dirname in seen:
continue
......@@ -933,7 +933,7 @@ def expand_paths(inputs):
# Yield existing non-dupe, non-import directory lines from it
for line in lines:
if not line.startswith("import"):
line = normalize(line.rstrip())
line = normalize_path(line.rstrip())
if line not in seen:
seen[line] = 1
if not os.path.isdir(line):
......@@ -1027,7 +1027,6 @@ class PthDistributions(AvailableDistributions):
"""A .pth file with Distribution paths in it"""
dirty = False
def __init__(self, filename):
self.filename = filename; self._load()
AvailableDistributions.__init__(
......@@ -1039,14 +1038,17 @@ class PthDistributions(AvailableDistributions):
if os.path.isfile(self.filename):
self.paths = [line.rstrip() for line in open(self.filename,'rt')]
while self.paths and not self.paths[-1].strip(): self.paths.pop()
# delete non-existent paths, in case somebody deleted a package
# manually:
for line in list(yield_lines(self.paths)):
if not os.path.exists(line):
self.paths.remove(line); self.dirty = True
def save(self):
"""Write changed .pth file back to disk"""
if self.dirty:
data = '\n'.join(self.paths+[''])
f = open(self.filename,'wt')
f.write(data)
f.close()
f = open(self.filename,'wt'); f.write(data); f.close()
self.dirty = False
def add(self,dist):
......@@ -1062,15 +1064,13 @@ class PthDistributions(AvailableDistributions):
AvailableDistributions.remove(self,dist)
def main(argv, **kw):
from setuptools import setup
setup(script_args = ['-q','easy_install', '-v']+argv, **kw)
def normalize(path):
return os.path.normcase(os.path.realpath(path))
......
......@@ -5,7 +5,8 @@ from pkg_resources import *
from distutils import log
from distutils.errors import DistutilsError
HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I)
EGG_FRAGMENT = re.compile('^egg=(\\w+(-\\w+)?)$')
HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I)
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):',re.I).match
EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
......@@ -38,28 +39,34 @@ def parse_bdist_wininst(name):
def distros_for_url(url, metadata=None):
"""Yield egg or source distribution objects that might be found at a URL"""
path = urlparse.urlparse(url)[2]
scheme, server, path, parameters, query, fragment = urlparse.urlparse(url)
base = urllib2.unquote(path.split('/')[-1])
return distros_for_filename(url, base, metadata)
dists = distros_for_location(url, base, metadata)
if fragment and not dists:
match = EGG_FRAGMENT.match(fragment)
if match:
return interpret_distro_name(
url, match.group(1), metadata, precedence = CHECKOUT_DIST
)
return dists
def distros_for_filename(url_or_path, basename, metadata=None):
def distros_for_location(location, basename, metadata=None):
"""Yield egg or source distribution objects based on basename"""
if basename.endswith('.egg.zip'):
basename = basename[:-4] # strip the .zip
if basename.endswith('.egg'): # only one, unambiguous interpretation
return [Distribution.from_location(url_or_path, basename, metadata)]
return [Distribution.from_location(location, basename, metadata)]
if basename.endswith('.exe'):
win_base, py_ver = parse_bdist_wininst(basename)
if win_base is not None:
return interpret_distro_name(
url_or_path, win_base, metadata, py_ver, BINARY_DIST, "win32"
location, win_base, metadata, py_ver, BINARY_DIST, "win32"
)
# Try source distro extensions (.zip, .tgz, etc.)
......@@ -67,22 +74,28 @@ def distros_for_filename(url_or_path, basename, metadata=None):
for ext in EXTENSIONS:
if basename.endswith(ext):
basename = basename[:-len(ext)]
return interpret_distro_name(url_or_path, basename, metadata)
return interpret_distro_name(location, basename, metadata)
return [] # no extension matched
def distros_for_filename(filename, metadata=None):
"""Yield possible egg or source distribution objects based on a filename"""
return distros_for_location(
normalize_path(filename), os.path.basename(filename), metadata
)
def interpret_distro_name(url_or_path, basename, metadata,
py_version=None, distro_type=SOURCE_DIST, platform=None
def interpret_distro_name(location, basename, metadata,
py_version=None, precedence=SOURCE_DIST, platform=None
):
"""Generate alternative interpretations of a source distro name
Note: if `location` is a filesystem filename, you should call
``pkg_resources.normalize_path()`` on it before passing it to this
routine!
"""
# Generate alternative interpretations of a source distro name
# Because some packages are ambiguous as to name/versions split
......@@ -99,8 +112,8 @@ def interpret_distro_name(url_or_path, basename, metadata,
parts = basename.split('-')
for p in range(1,len(parts)+1):
yield Distribution(
url_or_path, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]),
py_version=py_version, distro_type = distro_type,
location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]),
py_version=py_version, precedence = precedence,
platform = platform
)
......@@ -108,19 +121,6 @@ def interpret_distro_name(url_or_path, basename, metadata,
class PackageIndex(AvailableDistributions):
"""A distribution index that scans web pages for download URLs"""
......@@ -142,11 +142,7 @@ class PackageIndex(AvailableDistributions):
if not URL_SCHEME(url):
# process filenames or directories
if os.path.isfile(url):
dists = list(
distros_for_filename(
os.path.realpath(url), os.path.basename(url)
)
)
dists = list(distros_for_filename(url))
elif os.path.isdir(url):
url = os.path.realpath(url)
for item in os.listdir(url):
......@@ -160,8 +156,6 @@ class PackageIndex(AvailableDistributions):
if dists:
self.debug("Found link: %s", url)
if dists or not retrieve or url in self.fetched_urls:
for dist in dists:
self.add(dist)
......@@ -199,6 +193,12 @@ class PackageIndex(AvailableDistributions):
......@@ -344,11 +344,11 @@ class PackageIndex(AvailableDistributions):
if force_scan:
self.find_packages(requirement)
dist = self.best_match(requirement, []) # XXX
dist = self.best_match(requirement, WorkingSet([])) # XXX
if dist is not None:
self.info("Best match: %s", dist)
return self.download(dist.path, tmpdir)
return self.download(dist.location, tmpdir)
self.warn(
"No local packages or download links found for %s", requirement
......@@ -475,8 +475,8 @@ class PackageIndex(AvailableDistributions):
file.close()
raise DistutilsError("Unexpected HTML page found at "+url)
def _download_svn(self, url, filename):
url = url.split('#',1)[0] # remove any fragment for svn's sake
self.info("Doing subversion checkout from %s to %s", url, filename)
os.system("svn checkout -q %s %s" % (url, filename))
return filename
......
......@@ -408,8 +408,15 @@ class TestCommandTests(TestCase):
def test_api():
import doctest
return doctest.DocFileSuite(
'api_tests.txt', optionflags=doctest.ELLIPSIS, package='pkg_resources',
)
testClasses = (DependsTests, DistroTests, FeatureTests, TestCommandTests)
testNames = ["setuptools.tests.test_resources"]
testNames = ["setuptools.tests.test_resources", "setuptools.tests.test_api"]
def test_suite():
return TestSuite(
......@@ -434,13 +441,6 @@ def test_suite():
......
This diff is collapsed.
......@@ -54,23 +54,23 @@ class DistroTests(TestCase):
[dist.version for dist in ad['FooPkg']], ['1.9','1.4','1.2']
)
path = []
ws = WorkingSet([])
foo12 = Distribution.from_filename("FooPkg-1.2-py2.4.egg")
foo14 = Distribution.from_filename("FooPkg-1.4-py2.4-win32.egg")
req, = parse_requirements("FooPkg>=1.3")
# Nominal case: no distros on path, should yield all applicable
self.assertEqual(ad.best_match(req,path).version, '1.9')
self.assertEqual(ad.best_match(req,ws).version, '1.9')
# If a matching distro is already installed, should return only that
path.append("FooPkg-1.4-py2.4-win32.egg")
self.assertEqual(ad.best_match(req,path).version, '1.4')
ws.add(foo14); self.assertEqual(ad.best_match(req,ws).version, '1.4')
# If the first matching distro is unsuitable, it's a version conflict
path.insert(0,"FooPkg-1.2-py2.4.egg")
self.assertRaises(VersionConflict, ad.best_match, req, path)
ws = WorkingSet([]); ws.add(foo12); ws.add(foo14)
self.assertRaises(VersionConflict, ad.best_match, req, ws)
# If more than one match on the path, the first one takes precedence
path.insert(0,"FooPkg-1.4-py2.4-win32.egg")
self.assertEqual(ad.best_match(req,path).version, '1.4')
ws = WorkingSet([]); ws.add(foo14); ws.add(foo12); ws.add(foo14);
self.assertEqual(ad.best_match(req,ws).version, '1.4')
def checkFooPkg(self,d):
self.assertEqual(d.project_name, "FooPkg")
......@@ -86,8 +86,6 @@ class DistroTests(TestCase):
project_name="FooPkg",version="1.3-1",py_version="2.4",platform="win32"
)
self.checkFooPkg(d)
self.failUnless(d.installed_on(["/some/path"]))
self.failIf(d.installed_on([]))
d = Distribution("/some/path")
self.assertEqual(d.py_version, sys.version[:3])
......@@ -121,15 +119,17 @@ class DistroTests(TestCase):
self.checkDepends(self.distDepends(v), v)
def testResolve(self):
ad = AvailableDistributions([])
ad = AvailableDistributions([]); ws = WorkingSet([])
# Resolving no requirements -> nothing to install
self.assertEqual( list(ad.resolve([],[])), [] )
self.assertEqual( list(ws.resolve([],ad)), [] )
# Request something not in the collection -> DistributionNotFound
self.assertRaises(
DistributionNotFound, ad.resolve, parse_requirements("Foo"), []
DistributionNotFound, ws.resolve, parse_requirements("Foo"), ad
)
Foo = Distribution.from_filename(
......@@ -138,28 +138,28 @@ class DistroTests(TestCase):
)
ad.add(Foo)
# Request thing(s) that are available -> list to install
# Request thing(s) that are available -> list to activate
self.assertEqual(
list(ad.resolve(parse_requirements("Foo"),[])), [Foo]
list(ws.resolve(parse_requirements("Foo"), ad)), [Foo]
)
# Request an option that causes an unresolved dependency for "Baz"
# Request an extra that causes an unresolved dependency for "Baz"
self.assertRaises(
DistributionNotFound, ad.resolve,parse_requirements("Foo[bar]"),[]
DistributionNotFound, ws.resolve,parse_requirements("Foo[bar]"), ad
)
Baz = Distribution.from_filename(
"/foo_dir/Baz-2.1.egg", metadata=Metadata(('depends.txt', "Foo"))
)
ad.add(Baz)
# Install list now includes resolved dependency
# Activation list now includes resolved dependency
self.assertEqual(
list(ad.resolve(parse_requirements("Foo[bar]"),[])), [Foo,Baz]
list(ws.resolve(parse_requirements("Foo[bar]"), ad)), [Foo,Baz]
)
# Requests for conflicting versions produce VersionConflict
self.assertRaises(
VersionConflict,
ad.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), []
ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad
)
def testDistroDependsOptions(self):
......@@ -208,7 +208,7 @@ class RequirementsTests(TestCase):
def testBasics(self):
r = Requirement.parse("Twisted>=1.2")
self.assertEqual(str(r),"Twisted>=1.2")
self.assertEqual(repr(r),"Requirement('Twisted', [('>=', '1.2')], ())")
self.assertEqual(repr(r),"Requirement.parse('Twisted>=1.2')")
self.assertEqual(r, Requirement("Twisted", [('>=','1.2')]))
self.assertEqual(r, Requirement("twisTed", [('>=','1.2')]))
self.assertNotEqual(r, Requirement("Twisted", [('>=','2.0')]))
......@@ -284,13 +284,6 @@ class RequirementsTests(TestCase):
class ParseTests(TestCase):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment