aboutsummaryrefslogtreecommitdiffstats
path: root/pkg_resources.py
diff options
context:
space:
mode:
authorPJ Eby <distutils-sig@python.org>2005-07-24 22:47:06 +0000
committerPJ Eby <distutils-sig@python.org>2005-07-24 22:47:06 +0000
commit1c40632b88d76aea178e751483645ec3d32c81d9 (patch)
tree2cc3fa1af58200d5199b30771053c527ef91bd93 /pkg_resources.py
parent8618cfa8ac93431ffcede4f3987b559449bbbcb8 (diff)
downloadexternal_python_setuptools-1c40632b88d76aea178e751483645ec3d32c81d9.tar.gz
external_python_setuptools-1c40632b88d76aea178e751483645ec3d32c81d9.tar.bz2
external_python_setuptools-1c40632b88d76aea178e751483645ec3d32c81d9.zip
Implement "entry points" for dynamic discovery of drivers and plugins.
Change setuptools to discover setup commands using an entry point group called "distutils.commands". Thanks to Ian Bicking for the suggestion that led to designing this super-cool feature. --HG-- branch : setuptools extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041152
Diffstat (limited to 'pkg_resources.py')
-rw-r--r--pkg_resources.py287
1 files changed, 247 insertions, 40 deletions
diff --git a/pkg_resources.py b/pkg_resources.py
index 16604b8b..1171b761 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -12,21 +12,6 @@ The package resource API is designed to work with normal filesystem packages,
.zip files and with custom PEP 302 loaders that support the ``get_data()``
method.
"""
-__all__ = [
- 'register_loader_type', 'get_provider', 'IResourceProvider','PathMetadata',
- 'ResourceManager', 'AvailableDistributions', 'require', 'resource_string',
- 'resource_stream', 'resource_filename', 'set_extraction_path', 'EGG_DIST',
- 'cleanup_resources', 'parse_requirements', 'ensure_directory','SOURCE_DIST',
- 'compatible_platforms', 'get_platform', 'IMetadataProvider','parse_version',
- 'ResolutionError', 'VersionConflict', 'DistributionNotFound','EggMetadata',
- 'InvalidOption', 'Distribution', 'Requirement', 'yield_lines',
- 'get_importer', 'find_distributions', 'find_on_path', 'register_finder',
- 'split_sections', 'declare_namespace', 'register_namespace_handler',
- 'safe_name', 'safe_version', 'run_main', 'BINARY_DIST', 'run_script',
- 'get_default_cache', 'EmptyProvider', 'empty_provider', 'normalize_path',
- 'WorkingSet', 'working_set', 'add_activation_listener', 'CHECKOUT_DIST',
- 'list_resources', 'resource_exists', 'resource_isdir',
-]
import sys, os, zipimport, time, re, imp
from sets import ImmutableSet
@@ -39,6 +24,62 @@ from sets import ImmutableSet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+__all__ = [
+ # Basic resource access and distribution/entry point discovery
+ 'require', 'run_script', 'get_provider', 'get_distribution',
+ 'load_entry_point', 'get_entry_map', 'get_entry_info',
+ 'resource_string', 'resource_stream', 'resource_filename',
+ 'resource_listdir', 'resource_exists', 'resource_isdir',
+
+ # Environmental control
+ 'declare_namespace', 'working_set', 'add_activation_listener',
+ 'find_distributions', 'set_extraction_path', 'cleanup_resources',
+ 'get_default_cache',
+
+ # Primary implementation classes
+ 'AvailableDistributions', 'WorkingSet', 'ResourceManager',
+ 'Distribution', 'Requirement', 'EntryPoint',
+
+ # Exceptions
+ 'ResolutionError','VersionConflict','DistributionNotFound','UnknownExtra',
+
+ # Parsing functions and string utilities
+ 'parse_requirements', 'parse_version', 'safe_name', 'safe_version',
+ 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',
+
+ # filesystem utilities
+ 'ensure_directory', 'normalize_path',
+
+ # Distribution "precedence" constants
+ 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST',
+
+ # "Provider" interfaces, implementations, and registration/lookup APIs
+ 'IMetadataProvider', 'IResourceProvider',
+ 'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider',
+ 'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider',
+ 'register_finder', 'register_namespace_handler', 'register_loader_type',
+ 'fixup_namespace_packages', 'get_importer',
+
+ # Deprecated/backward compatibility only
+ 'run_main',
+]
+
+
class ResolutionError(Exception):
"""Abstract base for dependency resolution errors"""
@@ -48,8 +89,8 @@ class VersionConflict(ResolutionError):
class DistributionNotFound(ResolutionError):
"""A requested distribution was not found"""
-class InvalidOption(ResolutionError):
- """Invalid or unrecognized option name for a distribution"""
+class UnknownExtra(ResolutionError):
+ """Distribution doesn't have an "extra feature" of the given name"""
_provider_factories = {}
PY_MAJOR = sys.version[:3]
@@ -172,7 +213,6 @@ def compatible_platforms(provided,required):
return False
-
def run_script(dist_spec, script_name):
"""Locate distribution `dist_spec` and run its `script_name` script"""
ns = sys._getframe(1).f_globals
@@ -183,24 +223,25 @@ def run_script(dist_spec, script_name):
run_main = run_script # backward compatibility
+def get_distribution(dist):
+ """Return a current distribution object for a Requirement or string"""
+ if isinstance(dist,basestring): dist = Requirement.parse(dist)
+ if isinstance(dist,Requirement): dist = get_provider(dist)
+ if not isintance(dist,Distribution):
+ raise TypeError("Expected string, Requirement, or Distribution", dist)
+ return dist
+def load_entry_point(dist, kind, name):
+ """Return the `name` entry point of `kind` for dist or raise ImportError"""
+ return get_distribution(dist).load_entry_point(dist, kind, name)
+
+def get_entry_map(dist, kind=None):
+ """Return the entry point map for `kind`, or the full entry map"""
+ return get_distribution(dist).get_entry_map(dist, kind)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+def get_entry_info(dist, kind, name):
+ """Return the EntryPoint object for `kind`+`name`, or ``None``"""
+ return get_distribution(dist).get_entry_info(dist, kind, name)
class IMetadataProvider:
@@ -647,7 +688,7 @@ class ResourceManager:
self, resource_name
)
- def list_resources(self, package_name, resource_name):
+ def resource_listdir(self, package_name, resource_name):
return get_provider(package_name).resource_listdir(resource_name)
@@ -1008,9 +1049,9 @@ class ZipProvider(EggProvider):
return fspath[len(self.egg_root)+1:].split(os.sep)
raise AssertionError(
"%s is not a subpath of %s" % (fspath,self.egg_root)
- )
+ )
- def get_resource_filename(self, manager, resource_name):
+ def get_resource_filename(self, manager, resource_name):
if not self.egg_name:
raise NotImplementedError(
"resource_filename() only supported for .egg, not .zip"
@@ -1493,7 +1534,7 @@ VERSION = re.compile(r"\s*(<=?|>=?|==|!=)\s*((\w|\.)+)").match # version info
COMMA = re.compile(r"\s*,").match # comma between items
OBRACKET = re.compile(r"\s*\[").match
CBRACKET = re.compile(r"\s*\]").match
-
+MODULE = re.compile(r"\w+(\.\w+)*$").match
EGG_NAME = re.compile(
r"(?P<name>[^-]+)"
r"( -(?P<ver>[^-]+) (-py(?P<pyver>[^-]+) (-(?P<plat>.+))? )? )?",
@@ -1556,6 +1597,131 @@ def parse_version(s):
+class EntryPoint(object):
+ """Object representing an importable location"""
+
+ def __init__(self, name, module_name, attrs=(), extras=()):
+ if not MODULE(module_name):
+ raise ValueError("Invalid module name", module_name)
+ self.name = name
+ self.module_name = module_name
+ self.attrs = tuple(attrs)
+ self.extras = Requirement.parse(
+ ("x[%s]" % ','.join(extras)).lower()
+ ).extras
+
+ def __str__(self):
+ s = "%s = %s" % (self.name, self.module_name)
+ if self.attrs:
+ s += ':' + '.'.join(self.attrs)
+ if self.extras:
+ s += ' [%s]' % ','.join(self.extras)
+ return s
+
+ def __repr__(self):
+ return "EntryPoint.parse(%r)" % str(self)
+
+ def load(self):
+ entry = __import__(self.module_name, globals(),globals(), ['__name__'])
+ for attr in self.attrs:
+ try:
+ entry = getattr(entry,attr)
+ except AttributeError:
+ raise ImportError("%r has no %r attribute" % (entry,attr))
+ return entry
+
+
+
+
+
+
+
+
+
+ #@classmethod
+ def parse(cls, src):
+ """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
+ """
+ try:
+ attrs = extras = ()
+ name,value = src.split('=',1)
+ if '[' in value:
+ value,extras = value.split('[',1)
+ req = Requirement.parse("x["+extras)
+ if req.specs: raise ValueError
+ extras = req.extras
+ if ':' in value:
+ value,attrs = value.split(':',1)
+ if not MODULE(attrs.rstrip()):
+ raise ValueError
+ attrs = attrs.rstrip().split('.')
+ except ValueError:
+ raise ValueError(
+ "EntryPoint must be in 'name=module:attrs [extras]' format",
+ src
+ )
+ else:
+ return cls(name.strip(), value.lstrip(), attrs, extras)
+
+ parse = classmethod(parse)
+
+
+
+
+
+
+
+
+ #@classmethod
+ def parse_list(cls, section, contents):
+ if not MODULE(section):
+ raise ValueError("Invalid section name", section)
+ this = {}
+ for ep in map(cls.parse, yield_lines(contents)):
+ if ep.name in this:
+ raise ValueError("Duplicate entry point",section,ep.name)
+ this[ep.name]=ep
+ return this
+
+ parse_list = classmethod(parse_list)
+
+ #@classmethod
+ def parse_map(cls, data):
+ 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:
+ 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)
+ return maps
+
+ parse_map = classmethod(parse_map)
+
+
+
+
+
+
+
+
+
+
+
class Distribution(object):
"""Wrap an actual or potential sys.path entry w/metadata"""
def __init__(self,
@@ -1660,7 +1826,7 @@ class Distribution(object):
try:
deps.extend(dm[ext.lower()])
except KeyError:
- raise InvalidOption(
+ raise UnknownExtra(
"%s has no such extra feature %r" % (self, ext)
)
return deps
@@ -1720,6 +1886,47 @@ class Distribution(object):
+ def load_entry_point(self, kind, name):
+ """Return the `name` entry point of `kind` or raise ImportError"""
+ ep = self.get_entry_info(kind,name)
+ if ep is None:
+ raise ImportError("Entry point %r not found" % ((kind,name),))
+ if ep.extras:
+ # Ensure any needed extras get added to the working set
+ map(working_set.add, working_set.resolve(self.requires(ep.extras)))
+ return ep.load()
+
+ def get_entry_map(self,kind=None):
+ """Return the entry point map for `kind`, or the full entry map"""
+ try:
+ ep_map = self._ep_map
+ except AttributeError:
+ ep_map = self._ep_map = EntryPoint.parse_map(
+ self._get_metadata('entry_points.txt')
+ )
+ if kind is not None:
+ return ep_map.get(kind,{})
+ return ep_map
+
+ def get_entry_info(self, kind, name):
+ """Return the EntryPoint object for `kind`+`name`, or ``None``"""
+ return self.get_entry_map(kind).get(name)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
def parse_requirements(strs):
"""Yield ``Requirement`` objects for each specification in `strs`
@@ -1885,6 +2092,7 @@ def _find_adapter(registry, ob):
def ensure_directory(path):
+ """Ensure that the parent directory of `path` exists"""
dirname = os.path.dirname(path)
if not os.path.isdir(dirname):
os.makedirs(dirname)
@@ -1924,7 +2132,6 @@ def split_sections(s):
-
# Set up global resource manager
_manager = ResourceManager()