Chris McDonough
2011-07-12 f55b54a16def0bb0c463ee302dd12eefaa3638ad
commit | author | age
a9f17c 1 import pkg_resources
CM 2 import sys
3
4 from pyramid.exceptions import ConfigurationError
5 from pyramid.path import package_of
6
7 class DottedNameResolver(object):
8     """ This class resolves dotted name references to 'global' Python
9     objects (objects which can be imported) to those objects.
10
11     Two dotted name styles are supported during deserialization:
12
13     - ``pkg_resources``-style dotted names where non-module attributes
14       of a package are separated from the rest of the path using a ':'
15       e.g. ``package.module:attr``.
16
17     - ``zope.dottedname``-style dotted names where non-module
18       attributes of a package are separated from the rest of the path
19       using a '.' e.g. ``package.module.attr``.
20
21     These styles can be used interchangeably.  If the serialization
22     contains a ``:`` (colon), the ``pkg_resources`` resolution
23     mechanism will be chosen, otherwise the ``zope.dottedname``
24     resolution mechanism will be chosen.
25
26     The constructor accepts a single argument named ``package`` which
27     should be a one of:
28
29     - a Python module or package object
30
31     - A fully qualified (not relative) dotted name to a module or package
32
33     - The value ``None``
34
35     The ``package`` is used when relative dotted names are supplied to
36     the resolver's ``resolve`` and ``maybe_resolve`` methods.  A
37     dotted name which has a ``.`` (dot) or ``:`` (colon) as its first
38     character is treated as relative.
39
40     If the value ``None`` is supplied as the package name, the
41     resolver will only be able to resolve fully qualified (not
42     relative) names.  Any attempt to resolve a relative name when the
43     ``package`` is ``None`` will result in an
aff443 44     :exc:`pyramid.config.ConfigurationError` exception.
a9f17c 45
CM 46     If a *module* or *module name* (as opposed to a package or package
47     name) is supplied as ``package``, its containing package is
48     computed and this package used to derive the package name (all
49     names are resolved relative to packages, never to modules).  For
50     example, if the ``package`` argument to this type was passed the
51     string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to
52     the ``resolve`` method, the resulting import would be for
53     ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module
54     object, not a package object.
55
56     If a *package* or *package name* (as opposed to a module or module
57     name) is supplied as ``package``, this package will be used to
58     relative compute dotted names.  For example, if the ``package``
59     argument to this type was passed the string ``xml.dom``, and
60     ``.minidom`` is supplied to the ``resolve`` method, the resulting
61     import would be for ``xml.minidom``.
62
63     When a dotted name cannot be resolved, a
64     :class:`pyramid.exceptions.ConfigurationError` error is raised.
65     """
66     def __init__(self, package):
67         if package is None:
68             self.package_name = None
69             self.package = None
70         else:
71             if isinstance(package, basestring):
72                 try:
73                     __import__(package)
74                 except ImportError:
75                     raise ConfigurationError(
76                         'The dotted name %r cannot be imported' % (package,))
77                 package = sys.modules[package]
78             self.package = package_of(package)
79             self.package_name = self.package.__name__
80
81     def _pkg_resources_style(self, value):
82         """ package.module:attr style """
83         if value.startswith('.') or value.startswith(':'):
84             if not self.package_name:
85                 raise ConfigurationError(
86                     'relative name %r irresolveable without '
87                     'package_name' % (value,))
88             if value in ['.', ':']:
89                 value = self.package_name
90             else:
91                 value = self.package_name + value
92         return pkg_resources.EntryPoint.parse(
93             'x=%s' % value).load(False)
94
95     def _zope_dottedname_style(self, value):
96         """ package.module.attr style """
e093fd 97         module = self.package_name
CM 98         if not module:
99             module = None
a9f17c 100         if value == '.':
e093fd 101             if module is None:
a9f17c 102                 raise ConfigurationError(
CM 103                     'relative name %r irresolveable without package' % (value,)
104                 )
105             name = module.split('.')
106         else:
107             name = value.split('.')
108             if not name[0]:
109                 if module is None:
110                     raise ConfigurationError(
111                         'relative name %r irresolveable without '
112                         'package' % (value,)
113                         )
114                 module = module.split('.')
115                 name.pop(0)
116                 while not name[0]:
117                     module.pop()
118                     name.pop(0)
119                 name = module + name
120
121         used = name.pop(0)
122         found = __import__(used)
123         for n in name:
124             used += '.' + n
125             try:
126                 found = getattr(found, n)
127             except AttributeError:
128                 __import__(used)
129                 found = getattr(found, n) # pragma: no cover
130
131         return found
132
133     def resolve(self, dotted):
134         if not isinstance(dotted, basestring):
135             raise ConfigurationError('%r is not a string' % (dotted,))
136         return self.maybe_resolve(dotted)
137
138     def maybe_resolve(self, dotted):
139         if isinstance(dotted, basestring):
140             if ':' in dotted:
141                 return self._pkg_resources_style(dotted)
142             else:
143                 return self._zope_dottedname_style(dotted)
144         return dotted
145
146