Commit 53cf2db1 authored by PJ Eby's avatar PJ Eby

Added docs for main EntryPoint APIs, and cleaned up the API itself a bit.

Also fixed a few bugs.

--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041192
parent b8878fdb
......@@ -138,7 +138,7 @@ to it::
['http://example.com/something']
>>> dist in ws
True
>>> Distribution('foo') in ws
>>> Distribution('foo',version="") in ws
False
And you can iterate over its distributions::
......
......@@ -1598,7 +1598,7 @@ def parse_version(s):
class EntryPoint(object):
"""Object representing an importable location"""
"""Object representing an advertised importable object"""
def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
if not MODULE(module_name):
......@@ -1680,35 +1680,37 @@ class EntryPoint(object):
#@classmethod
def parse_list(cls, section, contents, dist=None):
if not MODULE(section):
raise ValueError("Invalid section name", section)
def parse_group(cls, group, lines, dist=None):
"""Parse an entry point group"""
if not MODULE(group):
raise ValueError("Invalid group name", group)
this = {}
for line in yield_lines(contents):
for line in yield_lines(lines):
ep = cls.parse(line, dist)
if ep.name in this:
raise ValueError("Duplicate entry point",section,ep.name)
raise ValueError("Duplicate entry point", group, ep.name)
this[ep.name]=ep
return this
parse_list = classmethod(parse_list)
parse_group = classmethod(parse_group)
#@classmethod
def parse_map(cls, data, dist=None):
"""Parse a map of entry point groups"""
if isinstance(data,dict):
data = data.items()
else:
data = split_sections(data)
maps = {}
for section, contents in data:
if section is None:
if not contents:
for group, lines in data:
if group is None:
if not lines:
continue
raise ValueError("Entry points must be listed in sections")
section = section.strip()
if section in maps:
raise ValueError("Duplicate section name", section)
maps[section] = cls.parse_list(section, contents, dist)
raise ValueError("Entry points must be listed in groups")
group = group.strip()
if group in maps:
raise ValueError("Duplicate group name", group)
maps[group] = cls.parse_group(group, lines, dist)
return maps
parse_map = classmethod(parse_map)
......@@ -1718,8 +1720,6 @@ class EntryPoint(object):
class Distribution(object):
"""Wrap an actual or potential sys.path entry w/metadata"""
def __init__(self,
......@@ -1862,7 +1862,9 @@ class Distribution(object):
return str(self)
def __str__(self):
version = getattr(self,'version',None) or "[unknown version]"
try: version = getattr(self,'version',None)
except ValueError: version = None
version = version or "[unknown version]"
return "%s %s" % (self.project_name,version)
def __getattr__(self,attr):
......@@ -1882,8 +1884,6 @@ class Distribution(object):
return Requirement.parse('%s==%s' % (self.project_name, self.version))
def load_entry_point(self, group, name):
"""Return the `name` entry point of `group` or raise ImportError"""
ep = self.get_entry_info(group,name)
......
......@@ -46,11 +46,12 @@ Developer's Guide
API Reference
-------------
'require', 'run_script',
Namespace Package Support
=========================
XXX
declare_namespace, fixup_namespace_packages, register_namespace_handler
``WorkingSet`` Objects
......@@ -65,24 +66,188 @@ Listeners
XXX
``EntryPoint`` Objects
======================
XXX
``Requirement`` Objects
=======================
XXX Syntax, parse_requirments, Requirement.parse, etc.
Entry Points
============
Entry points are a simple way for distributions to "advertise" Python objects
(such as functions or classes) for use by other distributions. Extensible
applications and frameworks can search for entry points with a particular name
or group, either from a specific distribution or from all active distributions
on sys.path, and then inspect or load the advertised objects at will.
Entry points belong to "groups" which are named with a dotted name similar to
a Python package or module name. For example, the ``setuptools`` package uses
an entry point named ``distutils.commands`` in order to find commands defined
by distutils extensions. ``setuptools`` treats the names of entry points
defined in that group as the acceptable commands for a setup script.
In a similar way, other packages can define their own entry point groups,
either using dynamic names within the group (like ``distutils.commands``), or
possibly using predefined names within the group. For example, a blogging
framework that offers various pre- or post-publishing hooks might define an
entry point group and look for entry points named "pre_process" and
"post_process" within that group.
To advertise an entry point, a project needs to use ``setuptools`` and provide
an ``entry_points`` argument to ``setup()`` in its setup script, so that the
entry points will be included in the distribution's metadata. For more
details, see the ``setuptools`` documentation. (XXX link here to setuptools)
Each project distribution can advertise at most one entry point of a given
name within the same entry point group. For example, a distutils extension
could advertise two different ``distutils.commands`` entry points, as long as
they had different names. However, there is nothing that prevents *different*
projects from advertising entry points of the same name in the same group. In
some cases, this is a desirable thing, since the application or framework that
uses the entry points may be calling them as hooks, or in some other way
combining them. It is up to the application or framework to decide what to do
if multiple distributions advertise an entry point; some possibilities include
using both entry points, displaying an error message, using the first one found
in sys.path order, etc.
Convenience API
---------------
In the following functions, the `dist` argument can be a ``Distribution``
instance, a ``Requirement`` instance, or a string specifying a requirement
(i.e. project name, version, etc.). If the argument is a string or
``Requirement``, the specified distribution is located (and added to sys.path
if not already present). An error will be raised if a matching distribution is
not available.
The `group` argument should be a string containing a dotted identifier,
identifying an entry point group. If you are defining an entry point group,
you should include some portion of your package's name in the group name so as
to avoid collision with other packages' entry point groups.
``load_entry_point(dist, group, name)``
Load the named entry point from the specified distribution, or raise
``ImportError``.
``get_entry_info(dist, group, name)``
Return an ``EntryPoint`` object for the given `group` and `name` from
the specified distribution. Returns ``None`` if the distribution has not
advertised a matching entry point.
``get_entry_map(dist, group=None)
Return the distribution's entry point map for `group`, or the full entry
map for the distribution. This function always returns a dictionary,
even if the distribution advertises no entry points. If `group` is given,
the dictionary maps entry point names to the corresponding ``EntryPoint``
object. If `group` is None, the dictionary maps group names to
dictionaries that then map entry point names to the corresponding
``EntryPoint`` instance in that group.
``iter_entry_points(group, name=None)``
Yield entry point objects from `group` matching `name`
If `name` is None, yields all entry points in `group` from all
distributions in the working set on sys.path, otherwise only ones matching
both `group` and `name` are yielded. Entry points are yielded from
the active distributions in the order that the distributions appear on
sys.path. (Within entry points for a particular distribution, however,
there is no particular ordering.)
Creating and Parsing
--------------------
``EntryPoint(name, module_name, attrs=(), extras=(), dist=None)``
Create an ``EntryPoint`` instance. `name` is the entry point name. The
`module_name` is the (dotted) name of the module containing the advertised
object. `attrs` is an optional tuple of names to look up from the
module to obtain the advertised object. For example, an `attrs` of
``("foo","bar")`` and a `module_name` of ``"baz"`` would mean that the
advertised object could be obtained by the following code::
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`
argument to find out what other distributions may need to be activated
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`
Entry point syntax follows the form::
name = some.module:some.attr [extra1,extra2]
The entry name and module name are required, but the ``:attrs`` and
``[extras]`` parts are optional, as is the whitespace shown between
some of the items. The `dist` argument is passed through to the
``EntryPoint()`` constructor, along with the other values parsed from
`src`.
``EntryPoint.parse_group(group, lines, dist=None)`` (classmethod)
Parse `lines` (a string or sequence of lines) to create a dictionary
mapping entry point names to ``EntryPoint`` objects. ``ValueError`` is
raised if entry point names are duplicated, if `group` is not a valid
entry point group name, or if there are any syntax errors. (Note: the
`group` parameter is used only for validation and to create more
informative error messages.) If `dist` is provided, it will be used to
set the ``dist`` attribute of the created ``EntryPoint`` objects.
``EntryPoint.parse_map(data, dist=None)`` (classmethod)
Parse `data` into a dictionary mapping group names to dictionaries mapping
entry point names to ``EntryPoint`` objects. If `data` is a dictionary,
then the keys are used as group names and the values are passed to
``parse_group()`` as the `lines` argument. If `data` is a string or
sequence of lines, it is first split into .ini-style sections (using
the ``split_sections()`` utility function) and the section names are used
as group names. In either case, the `dist` argument is passed through to
``parse_group()`` so that the entry points will be linked to the specified
distribution.
``EntryPoint`` Objects
----------------------
For simple introspection, ``EntryPoint`` objects have attributes that
correspond exactly to the constructor argument names: ``name``,
``module_name``, ``attrs``, ``extras``, and ``dist`` are all available. In
addition, the following methods are provided:
``load(require=True, env=None, installer=None)``
Load the entry point, returning the advertised Python object, or raise
``ImportError`` if it cannot be obtained. If `require` is a true value,
then ``require(env, installer)`` is called before attempting the import.
``require(env=None, installer=None)``
Ensure that any "extras" needed by the entry point are available on
sys.path. ``UnknownExtra`` is raised if the ``EntryPoint`` has ``extras``,
but no ``dist``, or if the named extras are not defined by the
distribution. If `env` is supplied, it must be an ``Environment``, and it
will be used to search for needed distributions if they are not already
present on sys.path. If `installer` is supplied, it must be a callable
taking a ``Requirement`` instance and returning a matching importable
``Distribution`` instance or None.
``Distribution`` Objects
========================
Factories: get_provider, get_distribution, find_distributions; see also
WorkingSet and Environment APIs.
register_finder
register_loader_type
'load_entry_point', 'get_entry_map', 'get_entry_info'
``ResourceManager`` API
=======================
......@@ -459,6 +624,21 @@ Platform Utilities
set.
PEP 302 Utilities
-----------------
``get_importer(path_item)``
Retrieve a PEP 302 "importer" for the given path item (which need not
actually be on ``sys.path``). This routine simulates the PEP 302 protocol
for obtaining an "importer" object. It first checks for an importer for
the path item in ``sys.path_importer_cache``, and if not found it calls
each of the ``sys.path_hooks`` and caches the result if a good importer is
found. If no importer is found, this routine returns an ``ImpWrapper``
instance that wraps the builtin import machinery as a PEP 302-compliant
"importer" object. This ``ImpWrapper`` is *not* cached; instead a new
instance is returned each time.
File/Path Utilities
-------------------
......
......@@ -219,7 +219,7 @@ def write_entries(cmd, basename, filename):
data = []
for section, contents in ep.items():
if not isinstance(contents,basestring):
contents = EntryPoint.parse_list(section, contents)
contents = EntryPoint.parse_group(section, contents)
contents = '\n'.join(map(str,contents.values()))
data.append('[%s]\n%s\n\n' % (section,contents))
data = ''.join(data)
......
......@@ -269,9 +269,9 @@ class EntryPointTests(TestCase):
"""
def testParseList(self):
self.checkSubMap(EntryPoint.parse_list("xyz", self.submap_str))
self.assertRaises(ValueError, EntryPoint.parse_list, "x a", "foo=bar")
self.assertRaises(ValueError, EntryPoint.parse_list, "x",
self.checkSubMap(EntryPoint.parse_group("xyz", self.submap_str))
self.assertRaises(ValueError, EntryPoint.parse_group, "x a", "foo=bar")
self.assertRaises(ValueError, EntryPoint.parse_group, "x",
["foo=baz", "foo=bar"])
def testParseMap(self):
......@@ -397,7 +397,7 @@ class ParseTests(TestCase):
"""
)
),
[(None,["x"]), ("y",["z","a"]), ("b",["c"]), ("d",[]), ("q",["v"])]
[(None,["x"]), ("Y",["z","a"]), ("b",["c"]), ("d",[]), ("q",["v"])]
)
self.assertRaises(ValueError,list,pkg_resources.split_sections("[foo"))
......
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