aboutsummaryrefslogtreecommitdiffstats
path: root/pkg_resources.py
diff options
context:
space:
mode:
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()