diff options
author | PJ Eby <distutils-sig@python.org> | 2005-08-14 01:45:38 +0000 |
---|---|---|
committer | PJ Eby <distutils-sig@python.org> | 2005-08-14 01:45:38 +0000 |
commit | 15233b3d39a81fb7164465f65b182eee65983b56 (patch) | |
tree | 83e46285d0fadf68ee4aa8e7fc46efb27d6e85c0 | |
parent | 8672fe00fe29ded78363f55a358c168774c7d9b4 (diff) | |
download | external_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.py | 110 | ||||
-rwxr-xr-x | pkg_resources.txt | 104 | ||||
-rwxr-xr-x | setuptools.txt | 6 | ||||
-rwxr-xr-x | setuptools/package_index.py | 4 | ||||
-rw-r--r-- | setuptools/tests/test_resources.py | 12 |
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")) |