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 |
|