aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPJ Eby <distutils-sig@python.org>2005-08-14 01:45:38 +0000
committerPJ Eby <distutils-sig@python.org>2005-08-14 01:45:38 +0000
commit15233b3d39a81fb7164465f65b182eee65983b56 (patch)
tree83e46285d0fadf68ee4aa8e7fc46efb27d6e85c0
parent8672fe00fe29ded78363f55a358c168774c7d9b4 (diff)
downloadexternal_python_setuptools-15233b3d39a81fb7164465f65b182eee65983b56.tar.gz
external_python_setuptools-15233b3d39a81fb7164465f65b182eee65983b56.tar.bz2
external_python_setuptools-15233b3d39a81fb7164465f65b182eee65983b56.zip
Document the "Environment" class, and simplify its API.
--HG-- branch : setuptools extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041194
-rw-r--r--pkg_resources.py110
-rwxr-xr-xpkg_resources.txt104
-rwxr-xr-xsetuptools.txt6
-rwxr-xr-xsetuptools/package_index.py4
-rw-r--r--setuptools/tests/test_resources.py12
5 files changed, 163 insertions, 73 deletions
diff --git a/pkg_resources.py b/pkg_resources.py
index 4f75fdef..f619e183 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -234,7 +234,7 @@ def get_distribution(dist):
def load_entry_point(dist, group, name):
"""Return `name` entry point of `group` for `dist` or raise ImportError"""
return get_distribution(dist).load_entry_point(group, name)
-
+
def get_entry_map(dist, group=None):
"""Return the entry point map for `group`, or the full entry map"""
return get_distribution(dist).get_entry_map(group)
@@ -537,8 +537,8 @@ class Environment(object):
def __init__(self,search_path=None,platform=get_platform(),python=PY_MAJOR):
"""Snapshot distributions available on a search path
- Any distributions found on `search_path` are added to the distribution
- map. `search_path` should be a sequence of ``sys.path`` items. If not
+ Any distributions found on `search_path` are added to the environment.
+ `search_path` should be a sequence of ``sys.path`` items. If not
supplied, ``sys.path`` is used.
`platform` is an optional string specifying the name of the platform
@@ -558,31 +558,24 @@ class Environment(object):
self.scan(search_path)
def can_add(self, dist):
- """Is distribution `dist` acceptable for this collection?"""
+ """Is distribution `dist` acceptable for this environment?
+
+ The distribution must match the platform and python version
+ requirements specified when this environment was created, or False
+ is returned.
+ """
return (self.python is None or dist.py_version is None
or dist.py_version==self.python) \
and compatible_platforms(dist.platform,self.platform)
- def __iter__(self):
- """Iterate over distribution keys"""
- return iter(self._distmap.keys())
-
- def __contains__(self,name):
- """Has a distribution named `name` ever been added to this map?"""
- return name.lower() in self._distmap
-
-
- def get(self,key,default=None):
- """Return ``self[key]`` if `key` in self, otherwise return `default`"""
- if key in self:
- return self[key]
- else:
- return default
+ def remove(self, dist):
+ """Remove `dist` from the environment"""
+ self._distmap[dist.key].remove(dist)
def scan(self, search_path=None):
- """Scan `search_path` for distributions usable on `platform`
+ """Scan `search_path` for distributions usable in this environment
- Any distributions found are added to the distribution map.
+ Any distributions found are added to the environment.
`search_path` should be a sequence of ``sys.path`` items. If not
supplied, ``sys.path`` is used. Only distributions conforming to
the platform/python version defined at initialization are added.
@@ -594,66 +587,73 @@ class Environment(object):
for dist in find_distributions(item):
self.add(dist)
- def __getitem__(self,key):
- """Return a newest-to-oldest list of distributions for the given key
-
- The returned list may be modified in-place, e.g. for narrowing down
- usable distributions.
+ def __getitem__(self,project_name):
+ """Return a newest-to-oldest list of distributions for `project_name`
"""
try:
- return self._cache[key]
+ return self._cache[project_name]
except KeyError:
- key = key.lower()
- if key not in self._distmap:
- raise
+ project_name = project_name.lower()
+ if project_name not in self._distmap:
+ return []
- if key not in self._cache:
- dists = self._cache[key] = self._distmap[key]
+ if project_name not in self._cache:
+ dists = self._cache[project_name] = self._distmap[project_name]
_sort_dists(dists)
- return self._cache[key]
+ return self._cache[project_name]
def add(self,dist):
- """Add `dist` to the distribution map, only if it's suitable"""
+ """Add `dist` if we ``can_add()`` it and it isn't already added"""
if self.can_add(dist):
- self._distmap.setdefault(dist.key,[]).append(dist)
- if dist.key in self._cache:
- _sort_dists(self._cache[dist.key])
+ dists = self._distmap.setdefault(dist.key,[])
+ if dist not in dists:
+ dists.append(dist)
+ if dist.key in self._cache:
+ _sort_dists(self._cache[dist.key])
- def remove(self,dist):
- """Remove `dist` from the distribution map"""
- self._distmap[dist.key].remove(dist)
def best_match(self, req, working_set, installer=None):
"""Find distribution best matching `req` and usable on `working_set`
- If a distribution that's already active in `working_set` is unsuitable,
- a VersionConflict is raised. If one or more suitable distributions are
- already active, the leftmost distribution (i.e., the one first in
- the search path) is returned. Otherwise, the available distribution
- with the highest version number is returned. If nothing is available,
- returns ``obtain(req,installer)`` or ``None`` if no distribution can
- be obtained.
+ This calls the ``find(req)`` method of the `working_set` to see if a
+ suitable distribution is already active. (This may raise
+ ``VersionConflict`` if an unsuitable version of the project is already
+ active in the specified `working_set`.) If a suitable distribution
+ isn't active, this method returns the newest distribution in the
+ environment that meets the ``Requirement`` in `req`. If no suitable
+ distribution is found, and `installer` is supplied, then the result of
+ calling the environment's ``obtain(req, installer)`` method will be
+ returned.
"""
dist = working_set.find(req)
if dist is not None:
return dist
-
- for dist in self.get(req.key, ()):
+ for dist in self[req.key]:
if dist in req:
return dist
-
return self.obtain(req, installer) # try and download/install
def obtain(self, requirement, installer=None):
- """Obtain a distro that matches requirement (e.g. via download)"""
+ """Obtain a distribution matching `requirement` (e.g. via download)
+
+ Obtain a distro that matches requirement (e.g. via download). In the
+ base ``Environment`` class, this routine just returns
+ ``installer(requirement)``, unless `installer` is None, in which case
+ None is returned instead. This method is a hook that allows subclasses
+ to attempt other ways of obtaining a distribution before falling back
+ to the `installer` argument."""
if installer is not None:
return installer(requirement)
- def __len__(self): return len(self._distmap)
+ def __iter__(self):
+ """Yield the unique project names of the available distributions"""
+ for key in self._distmap.keys():
+ if self[key]: yield key
AvailableDistributions = Environment # XXX backward compatibility
+
class ResourceManager:
"""Manage resource extraction and packages"""
extraction_path = None
@@ -744,7 +744,7 @@ class ResourceManager:
is based on the ``PYTHON_EGG_CACHE`` environment variable, with various
platform-specific fallbacks. See that routine's documentation for more
details.)
-
+
Resources are extracted to subdirectories of this path based upon
information given by the ``IResourceProvider``. You may set this to a
temporary directory, but then you must call ``cleanup_resources()`` to
@@ -1369,7 +1369,7 @@ def find_on_path(importer, path_item, only=False):
)
else:
# scan for .egg and .egg-info in directory
- for entry in os.listdir(path_item):
+ for entry in os.listdir(path_item):
lower = entry.lower()
if lower.endswith('.egg-info'):
fullpath = os.path.join(path_item, entry)
@@ -1637,7 +1637,7 @@ class EntryPoint(object):
raise UnknownExtra("Can't require() without a distribution", self)
map(working_set.add,
working_set.resolve(self.dist.requires(self.extras),env,installer))
-
+
#@classmethod
def parse(cls, src, dist=None):
"""Parse a single entry point from string `src`
diff --git a/pkg_resources.txt b/pkg_resources.txt
index 65877434..46366616 100755
--- a/pkg_resources.txt
+++ b/pkg_resources.txt
@@ -58,12 +58,91 @@ declare_namespace, fixup_namespace_packages, register_namespace_handler
======================
Listeners
+working_set
``Environment`` Objects
=======================
-XXX
+An "environment" is a collection of ``Distribution`` objects, usually ones
+that are present and potentially importable on the current platform.
+``Environment`` objects are used by ``pkg_resources`` to index available
+distributions during dependency resolution.
+
+``Environment(search_path=None, platform=get_platform(), python=PY_MAJOR)``
+ Create an environment snapshot by scanning `search_path` for distributions
+ compatible with `platform` and `python`. `search_path` should be a
+ sequence of strings such as might be used on ``sys.path``. If a
+ `search_path` isn't supplied, ``sys.path`` is used.
+
+ `platform` is an optional string specifying the name of the platform
+ that platform-specific distributions must be compatible with. If
+ unspecified, it defaults to the current platform. `python` is an
+ optional string naming the desired version of Python (e.g. ``'2.4'``);
+ it defaults to the currently-running version.
+
+ You may explicitly set `platform` (and/or `python`) to ``None`` if you
+ wish to include *all* distributions, not just those compatible with the
+ running platform or Python version.
+
+ Note that `search_path` is scanned immediately for distributions, and the
+ resulting ``Environment`` is a snapshot of the found distributions. It
+ is not automatically updated if the system's state changes due to e.g.
+ installation or removal of distributions.
+
+``__getitem__(project_name)``
+ Returns a list of distributions for the given project name, ordered
+ from newest to oldest version. (And highest to lowest format precedence
+ for distributions that contain the same version of the project.) If there
+ are no distributions for the project, returns an empty list.
+
+``__iter__()``
+ Yield the unique project names of the distributions in this environment.
+ The yielded names are always in lower case.
+
+``add(dist)``
+ Add `dist` to the environment if it matches the platform and python version
+ specified at creation time, and only if the distribution hasn't already
+ been added. (i.e., adding the same distribution more than once is a no-op.)
+
+``remove(dist)``
+ Remove `dist` from the environment.
+
+``can_add(dist)``
+ Is distribution `dist` acceptable for this environment? If it's not
+ compatible with the platform and python version specified at creation of
+ the environment, False is returned.
+
+``best_match(req, working_set, installer=None)``
+ Find distribution best matching `req` and usable on `working_set`
+
+ This calls the ``find(req)`` method of the `working_set` to see if a
+ suitable distribution is already active. (This may raise
+ ``VersionConflict`` if an unsuitable version of the project is already
+ active in the specified `working_set`.) If a suitable distribution isn't
+ active, this method returns the newest distribution in the environment
+ that meets the ``Requirement`` in `req`. If no suitable distribution is
+ found, and `installer` is supplied, then the result of calling
+ the environment's ``obtain(req, installer)`` method will be returned.
+
+``obtain(requirement, installer=None)``
+ Obtain a distro that matches requirement (e.g. via download). In the
+ base ``Environment`` class, this routine just returns
+ ``installer(requirement)``, unless `installer` is None, in which case
+ None is returned instead. This method is a hook that allows subclasses
+ to attempt other ways of obtaining a distribution before falling back
+ to the `installer` argument.
+
+``scan(search_path=None)``
+ Scan `search_path` for distributions usable on `platform`
+
+ Any distributions found are added to the environment. `search_path` should
+ be a sequence of strings such as might be used on ``sys.path``. If not
+ supplied, ``sys.path`` is used. Only distributions conforming to
+ the platform/python version defined at initialization are added. This
+ method is a shortcut for using the ``find_distributions()`` function to
+ find the distributions from each item in `search_path`, and then calling
+ ``add()`` to add each one to the environment.
``Requirement`` Objects
@@ -75,18 +154,21 @@ some purpose. These objects (or their string form) are used by various
distribution needs.
-Requirements Parsing
+Requirements Parsing
--------------------
``parse_requirements(s)``
- Yield ``Requirement`` objects for a string or list of lines. Each
+ Yield ``Requirement`` objects for a string or iterable of lines. Each
requirement must start on a new line. See below for syntax.
``Requirement.parse(s)``
- Create a ``Requirement`` object from a string or list of lines. A
+ Create a ``Requirement`` object from a string or iterable of lines. A
``ValueError`` is raised if the string or lines do not contain a valid
- requirement specifier. The syntax of a requirement specifier can be
- defined in EBNF as follows::
+ requirement specifier, or if they contain more than one specifier. (To
+ parse multiple specifiers from a string or iterable of strings, use
+ ``parse_requirements()`` instead.)
+
+ The syntax of a requirement specifier can be defined in EBNF as follows::
requirement ::= project_name versionspec? extras?
versionspec ::= comparison version (',' comparison version)*
@@ -100,7 +182,7 @@ Requirements Parsing
Tokens can be separated by whitespace, and a requirement can be continued
over multiple lines using a backslash (``\\``). Line-end comments (using
``#``) are also allowed.
-
+
Some examples of valid requirement specifiers::
FooProject >= 1.2
@@ -258,7 +340,7 @@ Creating and Parsing
import baz
advertised_object = baz.foo.bar
-
+
The `extras` are an optional tuple of "extra feature" names that the
distribution needs in order to provide this entry point. When the
entry point is loaded, these extra features are looked up in the `dist`
@@ -266,7 +348,7 @@ Creating and Parsing
on sys.path; see the ``load()`` method for more details. The `extras`
argument is only meaningful if `dist` is specified. `dist` must be
a ``Distribution`` instance.
-
+
``EntryPoint.parse(src, dist=None)`` (classmethod)
Parse a single entry point from string `src`
@@ -324,6 +406,10 @@ addition, the following methods are provided:
taking a ``Requirement`` instance and returning a matching importable
``Distribution`` instance or None.
+``__str__()``
+ The string form of an ``EntryPoint`` is a string that could be passed to
+ ``EntryPoint.parse()`` to yield an equivalent ``EntryPoint``.
+
``Distribution`` Objects
========================
diff --git a/setuptools.txt b/setuptools.txt
index 1beb8327..5808ec6f 100755
--- a/setuptools.txt
+++ b/setuptools.txt
@@ -1852,7 +1852,11 @@ Release Notes/Change History
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.)
- * ``AvailableDistributions`` is now called ``Environment``
+ * ``AvailableDistributions`` is now called ``Environment``, and the
+ ``get()``, ``__len__()``, and ``__contains__()`` methods were removed,
+ because they weren't particularly useful. ``__getitem__()`` no longer
+ raises ``KeyError``; it just returns an empty list if there are no
+ distributions for the named project.
* The ``resolve()`` method of ``Environment`` is now a method of
``WorkingSet`` instead, and the ``best_match()`` method now uses a working
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index f2731a1e..95b57178 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -264,7 +264,7 @@ class PackageIndex(Environment):
def obtain(self, requirement, installer=None):
self.find_packages(requirement)
- for dist in self.get(requirement.key, ()):
+ for dist in self[requirement.key]:
if dist in requirement:
return dist
self.debug("%s does not match %s", requirement, dist)
@@ -344,7 +344,7 @@ class PackageIndex(Environment):
self.info("Searching for %s", requirement)
def find(req):
- for dist in self.get(req.key, ()):
+ for dist in self[req.key]:
if dist in req and (dist.precedence<=SOURCE_DIST or not source):
self.info("Best match: %s", dist)
return self.download(dist.location, tmpdir)
diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py
index ed651675..ee3de5c5 100644
--- a/setuptools/tests/test_resources.py
+++ b/setuptools/tests/test_resources.py
@@ -25,19 +25,19 @@ class DistroTests(TestCase):
# empty path should produce no distributions
ad = Environment([], python=None)
self.assertEqual(list(ad), [])
- self.assertEqual(len(ad),0)
- self.assertEqual(ad.get('FooPkg'),None)
- self.failIf('FooPkg' in ad)
+ self.assertEqual(ad['FooPkg'],[])
+
ad.add(Distribution.from_filename("FooPkg-1.3_1.egg"))
ad.add(Distribution.from_filename("FooPkg-1.4-py2.4-win32.egg"))
ad.add(Distribution.from_filename("FooPkg-1.2-py2.4.egg"))
# Name is in there now
- self.failUnless('FooPkg' in ad)
+ self.failUnless(ad['FooPkg'])
# But only 1 package
self.assertEqual(list(ad), ['foopkg'])
- self.assertEqual(len(ad),1)
+
+
# Distributions sort by version
self.assertEqual(
@@ -46,7 +46,7 @@ class DistroTests(TestCase):
# Removing a distribution leaves sequence alone
ad.remove(ad['FooPkg'][1])
self.assertEqual(
- [dist.version for dist in ad.get('FooPkg')], ['1.4','1.2']
+ [dist.version for dist in ad['FooPkg']], ['1.4','1.2']
)
# And inserting adds them in order
ad.add(Distribution.from_filename("FooPkg-1.9.egg"))