commit | author | age
|
434c05
|
1 |
import os |
d9a76e
|
2 |
import pkg_resources |
423b85
|
3 |
import sys |
3c2ed7
|
4 |
import imp |
CM |
5 |
|
56df90
|
6 |
from zope.interface import implementer |
CM |
7 |
|
|
8 |
from pyramid.interfaces import IAssetDescriptor |
|
9 |
|
|
10 |
from pyramid.compat import string_types |
|
11 |
|
3c2ed7
|
12 |
ignore_types = [ imp.C_EXTENSION, imp.C_BUILTIN ] |
CM |
13 |
init_names = [ '__init__%s' % x[0] for x in imp.get_suffixes() if |
|
14 |
x[0] and x[2] not in ignore_types ] |
434c05
|
15 |
|
d9a76e
|
16 |
def caller_path(path, level=2): |
434c05
|
17 |
if not os.path.isabs(path): |
25c64c
|
18 |
module = caller_module(level + 1) |
d9a76e
|
19 |
prefix = package_path(module) |
434c05
|
20 |
path = os.path.join(prefix, path) |
CM |
21 |
return path |
|
22 |
|
a56564
|
23 |
def caller_module(level=2, sys=sys): |
d9a76e
|
24 |
module_globals = sys._getframe(level).f_globals |
a56564
|
25 |
module_name = module_globals.get('__name__') or '__main__' |
d9a76e
|
26 |
module = sys.modules[module_name] |
CM |
27 |
return module |
434c05
|
28 |
|
6efd81
|
29 |
def package_name(pkg_or_module): |
CM |
30 |
""" If this function is passed a module, return the dotted Python |
|
31 |
package name of the package in which the module lives. If this |
|
32 |
function is passed a package, return the dotted Python package |
|
33 |
name of the package itself.""" |
d79d13
|
34 |
if pkg_or_module is None or pkg_or_module.__name__ == '__main__': |
45d08c
|
35 |
return '__main__' |
6efd81
|
36 |
pkg_name = pkg_or_module.__name__ |
c062d5
|
37 |
pkg_filename = getattr(pkg_or_module, '__file__', None) |
DH |
38 |
if pkg_filename is None: |
|
39 |
# Namespace packages do not have __init__.py* files, |
|
40 |
# and so have no __file__ attribute |
|
41 |
return pkg_name |
6efd81
|
42 |
splitted = os.path.split(pkg_filename) |
3c2ed7
|
43 |
if splitted[-1] in init_names: |
6efd81
|
44 |
# it's a package |
CM |
45 |
return pkg_name |
|
46 |
return pkg_name.rsplit('.', 1)[0] |
|
47 |
|
39480c
|
48 |
def package_of(pkg_or_module): |
CM |
49 |
""" Return the package of a module or return the package itself """ |
|
50 |
pkg_name = package_name(pkg_or_module) |
|
51 |
__import__(pkg_name) |
|
52 |
return sys.modules[pkg_name] |
|
53 |
|
5f4b80
|
54 |
def caller_package(level=2, caller_module=caller_module): |
CM |
55 |
# caller_module in arglist for tests |
25c64c
|
56 |
module = caller_module(level + 1) |
bfee0a
|
57 |
f = getattr(module, '__file__', '') |
CM |
58 |
if (('__init__.py' in f) or ('__init__$py' in f)): # empty at >>> |
5f4b80
|
59 |
# Module is a package |
CM |
60 |
return module |
|
61 |
# Go up one level to get package |
|
62 |
package_name = module.__name__.rsplit('.', 1)[0] |
|
63 |
return sys.modules[package_name] |
|
64 |
|
d9a76e
|
65 |
def package_path(package): |
CM |
66 |
# computing the abspath is actually kinda expensive so we memoize |
|
67 |
# the result |
cba2e1
|
68 |
prefix = getattr(package, '__abspath__', None) |
d9a76e
|
69 |
if prefix is None: |
CM |
70 |
prefix = pkg_resources.resource_filename(package.__name__, '') |
|
71 |
# pkg_resources doesn't care whether we feed it a package |
|
72 |
# name or a module name within the package, the result |
|
73 |
# will be the same: a directory name to the package itself |
|
74 |
try: |
cba2e1
|
75 |
package.__abspath__ = prefix |
c0ca03
|
76 |
except Exception: |
d9a76e
|
77 |
# this is only an optimization, ignore any error |
CM |
78 |
pass |
|
79 |
return prefix |
600ea3
|
80 |
|
078859
|
81 |
class _CALLER_PACKAGE(object): |
660225
|
82 |
def __repr__(self): # pragma: no cover (for docs) |
078859
|
83 |
return 'pyramid.path.CALLER_PACKAGE' |
CM |
84 |
|
|
85 |
CALLER_PACKAGE = _CALLER_PACKAGE() |
|
86 |
|
56df90
|
87 |
class Resolver(object): |
078859
|
88 |
def __init__(self, package=CALLER_PACKAGE): |
CM |
89 |
if package in (None, CALLER_PACKAGE): |
|
90 |
self.package = package |
56df90
|
91 |
else: |
CM |
92 |
if isinstance(package, string_types): |
|
93 |
try: |
|
94 |
__import__(package) |
|
95 |
except ImportError: |
|
96 |
raise ValueError( |
|
97 |
'The dotted name %r cannot be imported' % (package,) |
|
98 |
) |
|
99 |
package = sys.modules[package] |
|
100 |
self.package = package_of(package) |
078859
|
101 |
|
CM |
102 |
def get_package_name(self): |
|
103 |
if self.package is CALLER_PACKAGE: |
|
104 |
package_name = caller_package().__name__ |
|
105 |
else: |
|
106 |
package_name = self.package.__name__ |
|
107 |
return package_name |
|
108 |
|
|
109 |
def get_package(self): |
|
110 |
if self.package is CALLER_PACKAGE: |
|
111 |
package = caller_package() |
|
112 |
else: |
|
113 |
package = self.package |
|
114 |
return package |
|
115 |
|
56df90
|
116 |
|
CM |
117 |
class AssetResolver(Resolver): |
|
118 |
""" A class used to resolve an :term:`asset specification` to an |
|
119 |
:term:`asset descriptor`. |
|
120 |
|
d633c8
|
121 |
.. versionadded:: 1.3 |
56df90
|
122 |
|
CM |
123 |
The constructor accepts a single argument named ``package`` which may be |
|
124 |
any of: |
|
125 |
|
|
126 |
- A fully qualified (not relative) dotted name to a module or package |
|
127 |
|
|
128 |
- a Python module or package object |
|
129 |
|
|
130 |
- The value ``None`` |
|
131 |
|
078859
|
132 |
- The constant value :attr:`pyramid.path.CALLER_PACKAGE`. |
CM |
133 |
|
|
134 |
The default value is :attr:`pyramid.path.CALLER_PACKAGE`. |
|
135 |
|
56df90
|
136 |
The ``package`` is used when a relative asset specification is supplied |
CM |
137 |
to the :meth:`~pyramid.path.AssetResolver.resolve` method. An asset |
|
138 |
specification without a colon in it is treated as relative. |
|
139 |
|
eb81eb
|
140 |
If ``package`` is ``None``, the resolver will |
56df90
|
141 |
only be able to resolve fully qualified (not relative) asset |
CM |
142 |
specifications. Any attempt to resolve a relative asset specification |
eb81eb
|
143 |
will result in an :exc:`ValueError` exception. |
078859
|
144 |
|
eb81eb
|
145 |
If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, |
TL |
146 |
the resolver will treat relative asset specifications as |
078859
|
147 |
relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve` |
CM |
148 |
method. |
56df90
|
149 |
|
eb81eb
|
150 |
If ``package`` is a *module* or *module name* (as opposed to a package or |
TL |
151 |
package name), its containing package is computed and this |
|
152 |
package is used to derive the package name (all names are resolved relative |
56df90
|
153 |
to packages, never to modules). For example, if the ``package`` argument |
CM |
154 |
to this type was passed the string ``xml.dom.expatbuilder``, and |
|
155 |
``template.pt`` is supplied to the |
|
156 |
:meth:`~pyramid.path.AssetResolver.resolve` method, the resulting absolute |
|
157 |
asset spec would be ``xml.minidom:template.pt``, because |
|
158 |
``xml.dom.expatbuilder`` is a module object, not a package object. |
|
159 |
|
eb81eb
|
160 |
If ``package`` is a *package* or *package name* (as opposed to a module or |
TL |
161 |
module name), this package will be used to compute relative |
56df90
|
162 |
asset specifications. For example, if the ``package`` argument to this |
CM |
163 |
type was passed the string ``xml.dom``, and ``template.pt`` is supplied |
|
164 |
to the :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting |
|
165 |
absolute asset spec would be ``xml.minidom:template.pt``. |
|
166 |
""" |
|
167 |
def resolve(self, spec): |
|
168 |
""" |
|
169 |
Resolve the asset spec named as ``spec`` to an object that has the |
|
170 |
attributes and methods described in |
18659c
|
171 |
:class:`pyramid.interfaces.IAssetDescriptor`. |
56df90
|
172 |
|
CM |
173 |
If ``spec`` is an absolute filename |
|
174 |
(e.g. ``/path/to/myproject/templates/foo.pt``) or an absolute asset |
|
175 |
spec (e.g. ``myproject:templates.foo.pt``), an asset descriptor is |
|
176 |
returned without taking into account the ``package`` passed to this |
|
177 |
class' constructor. |
|
178 |
|
|
179 |
If ``spec`` is a *relative* asset specification (an asset |
|
180 |
specification without a ``:`` in it, e.g. ``templates/foo.pt``), the |
08c221
|
181 |
``package`` argument of the constructor is used as the package |
56df90
|
182 |
portion of the asset spec. For example: |
CM |
183 |
|
|
184 |
.. code-block:: python |
|
185 |
|
|
186 |
a = AssetResolver('myproject') |
|
187 |
resolver = a.resolve('templates/foo.pt') |
edfc4f
|
188 |
print(resolver.abspath()) |
56df90
|
189 |
# -> /path/to/myproject/templates/foo.pt |
CM |
190 |
|
078859
|
191 |
If the AssetResolver is constructed without a ``package`` argument of |
CM |
192 |
``None``, and a relative asset specification is passed to |
|
193 |
``resolve``, an :exc:`ValueError` exception is raised. |
56df90
|
194 |
""" |
CM |
195 |
if os.path.isabs(spec): |
|
196 |
return FSAssetDescriptor(spec) |
|
197 |
path = spec |
|
198 |
if ':' in path: |
078859
|
199 |
package_name, path = spec.split(':', 1) |
56df90
|
200 |
else: |
078859
|
201 |
if self.package is CALLER_PACKAGE: |
CM |
202 |
package_name = caller_package().__name__ |
|
203 |
else: |
|
204 |
package_name = getattr(self.package, '__name__', None) |
|
205 |
if package_name is None: |
56df90
|
206 |
raise ValueError( |
CM |
207 |
'relative spec %r irresolveable without package' % (spec,) |
|
208 |
) |
078859
|
209 |
return PkgResourcesAssetDescriptor(package_name, path) |
56df90
|
210 |
|
CM |
211 |
class DottedNameResolver(Resolver): |
|
212 |
""" A class used to resolve a :term:`dotted Python name` to a package or |
|
213 |
module object. |
|
214 |
|
d633c8
|
215 |
.. versionadded:: 1.3 |
56df90
|
216 |
|
CM |
217 |
The constructor accepts a single argument named ``package`` which may be |
|
218 |
any of: |
|
219 |
|
|
220 |
- A fully qualified (not relative) dotted name to a module or package |
|
221 |
|
|
222 |
- a Python module or package object |
|
223 |
|
|
224 |
- The value ``None`` |
|
225 |
|
078859
|
226 |
- The constant value :attr:`pyramid.path.CALLER_PACKAGE`. |
CM |
227 |
|
|
228 |
The default value is :attr:`pyramid.path.CALLER_PACKAGE`. |
|
229 |
|
56df90
|
230 |
The ``package`` is used when a relative dotted name is supplied to the |
CM |
231 |
:meth:`~pyramid.path.DottedNameResolver.resolve` method. A dotted name |
|
232 |
which has a ``.`` (dot) or ``:`` (colon) as its first character is |
|
233 |
treated as relative. |
|
234 |
|
eb81eb
|
235 |
If ``package`` is ``None``, the resolver will only be able to resolve |
TL |
236 |
fully qualified (not relative) names. Any attempt to resolve a |
|
237 |
relative name will result in an :exc:`ValueError` exception. |
078859
|
238 |
|
eb81eb
|
239 |
If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, |
TL |
240 |
the resolver will treat relative dotted names as relative to |
078859
|
241 |
the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve` |
CM |
242 |
method. |
56df90
|
243 |
|
eb81eb
|
244 |
If ``package`` is a *module* or *module name* (as opposed to a package or |
TL |
245 |
package name), its containing package is computed and this |
56df90
|
246 |
package used to derive the package name (all names are resolved relative |
CM |
247 |
to packages, never to modules). For example, if the ``package`` argument |
|
248 |
to this type was passed the string ``xml.dom.expatbuilder``, and |
|
249 |
``.mindom`` is supplied to the |
|
250 |
:meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting |
|
251 |
import would be for ``xml.minidom``, because ``xml.dom.expatbuilder`` is |
|
252 |
a module object, not a package object. |
|
253 |
|
eb81eb
|
254 |
If ``package`` is a *package* or *package name* (as opposed to a module or |
TL |
255 |
module name), this package will be used to relative compute |
56df90
|
256 |
dotted names. For example, if the ``package`` argument to this type was |
CM |
257 |
passed the string ``xml.dom``, and ``.minidom`` is supplied to the |
|
258 |
:meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting |
|
259 |
import would be for ``xml.minidom``. |
|
260 |
""" |
078859
|
261 |
def resolve(self, dotted): |
56df90
|
262 |
""" |
CM |
263 |
This method resolves a dotted name reference to a global Python |
|
264 |
object (an object which can be imported) to the object itself. |
|
265 |
|
|
266 |
Two dotted name styles are supported: |
|
267 |
|
|
268 |
- ``pkg_resources``-style dotted names where non-module attributes |
|
269 |
of a package are separated from the rest of the path using a ``:`` |
|
270 |
e.g. ``package.module:attr``. |
|
271 |
|
|
272 |
- ``zope.dottedname``-style dotted names where non-module |
|
273 |
attributes of a package are separated from the rest of the path |
|
274 |
using a ``.`` e.g. ``package.module.attr``. |
|
275 |
|
|
276 |
These styles can be used interchangeably. If the supplied name |
|
277 |
contains a ``:`` (colon), the ``pkg_resources`` resolution |
|
278 |
mechanism will be chosen, otherwise the ``zope.dottedname`` |
|
279 |
resolution mechanism will be chosen. |
|
280 |
|
078859
|
281 |
If the ``dotted`` argument passed to this method is not a string, a |
56df90
|
282 |
:exc:`ValueError` will be raised. |
078859
|
283 |
|
CM |
284 |
When a dotted name cannot be resolved, a :exc:`ValueError` error is |
|
285 |
raised. |
|
286 |
|
|
287 |
Example: |
|
288 |
|
|
289 |
.. code-block:: python |
|
290 |
|
|
291 |
r = DottedNameResolver() |
|
292 |
v = r.resolve('xml') # v is the xml module |
|
293 |
|
56df90
|
294 |
""" |
078859
|
295 |
if not isinstance(dotted, string_types): |
CM |
296 |
raise ValueError('%r is not a string' % (dotted,)) |
|
297 |
package = self.package |
|
298 |
if package is CALLER_PACKAGE: |
|
299 |
package = caller_package() |
|
300 |
return self._resolve(dotted, package) |
56df90
|
301 |
|
CM |
302 |
def maybe_resolve(self, dotted): |
|
303 |
""" |
|
304 |
This method behaves just like |
|
305 |
:meth:`~pyramid.path.DottedNameResolver.resolve`, except if the |
078859
|
306 |
``dotted`` value passed is not a string, it is simply returned. For |
56df90
|
307 |
example: |
CM |
308 |
|
|
309 |
.. code-block:: python |
|
310 |
|
|
311 |
import xml |
|
312 |
r = DottedNameResolver() |
078859
|
313 |
v = r.maybe_resolve(xml) |
56df90
|
314 |
# v is the xml module; no exception raised |
CM |
315 |
""" |
|
316 |
if isinstance(dotted, string_types): |
078859
|
317 |
package = self.package |
CM |
318 |
if package is CALLER_PACKAGE: |
|
319 |
package = caller_package() |
|
320 |
return self._resolve(dotted, package) |
56df90
|
321 |
return dotted |
CM |
322 |
|
078859
|
323 |
def _resolve(self, dotted, package): |
CM |
324 |
if ':' in dotted: |
|
325 |
return self._pkg_resources_style(dotted, package) |
|
326 |
else: |
|
327 |
return self._zope_dottedname_style(dotted, package) |
56df90
|
328 |
|
078859
|
329 |
def _pkg_resources_style(self, value, package): |
56df90
|
330 |
""" package.module:attr style """ |
0f9823
|
331 |
if value.startswith(('.', ':')): |
078859
|
332 |
if not package: |
56df90
|
333 |
raise ValueError( |
078859
|
334 |
'relative name %r irresolveable without package' % (value,) |
CM |
335 |
) |
56df90
|
336 |
if value in ['.', ':']: |
078859
|
337 |
value = package.__name__ |
56df90
|
338 |
else: |
078859
|
339 |
value = package.__name__ + value |
e30c3b
|
340 |
# Calling EntryPoint.load with an argument is deprecated. |
JD |
341 |
# See https://pythonhosted.org/setuptools/history.html#id8 |
|
342 |
ep = pkg_resources.EntryPoint.parse('x=%s' % value) |
|
343 |
if hasattr(ep, 'resolve'): |
c04115
|
344 |
# setuptools>=10.2 |
JD |
345 |
return ep.resolve() # pragma: NO COVER |
e30c3b
|
346 |
else: |
c04115
|
347 |
return ep.load(False) # pragma: NO COVER |
56df90
|
348 |
|
078859
|
349 |
def _zope_dottedname_style(self, value, package): |
56df90
|
350 |
""" package.module.attr style """ |
078859
|
351 |
module = getattr(package, '__name__', None) # package may be None |
56df90
|
352 |
if not module: |
CM |
353 |
module = None |
|
354 |
if value == '.': |
|
355 |
if module is None: |
|
356 |
raise ValueError( |
|
357 |
'relative name %r irresolveable without package' % (value,) |
|
358 |
) |
|
359 |
name = module.split('.') |
|
360 |
else: |
|
361 |
name = value.split('.') |
|
362 |
if not name[0]: |
|
363 |
if module is None: |
|
364 |
raise ValueError( |
|
365 |
'relative name %r irresolveable without ' |
|
366 |
'package' % (value,) |
|
367 |
) |
|
368 |
module = module.split('.') |
|
369 |
name.pop(0) |
|
370 |
while not name[0]: |
|
371 |
module.pop() |
|
372 |
name.pop(0) |
|
373 |
name = module + name |
|
374 |
|
|
375 |
used = name.pop(0) |
|
376 |
found = __import__(used) |
|
377 |
for n in name: |
|
378 |
used += '.' + n |
|
379 |
try: |
|
380 |
found = getattr(found, n) |
|
381 |
except AttributeError: |
|
382 |
__import__(used) |
|
383 |
found = getattr(found, n) # pragma: no cover |
|
384 |
|
|
385 |
return found |
|
386 |
|
|
387 |
@implementer(IAssetDescriptor) |
|
388 |
class PkgResourcesAssetDescriptor(object): |
|
389 |
pkg_resources = pkg_resources |
|
390 |
|
|
391 |
def __init__(self, pkg_name, path): |
|
392 |
self.pkg_name = pkg_name |
|
393 |
self.path = path |
|
394 |
|
|
395 |
def absspec(self): |
|
396 |
return '%s:%s' % (self.pkg_name, self.path) |
|
397 |
|
|
398 |
def abspath(self): |
f7171c
|
399 |
return os.path.abspath( |
MM |
400 |
self.pkg_resources.resource_filename(self.pkg_name, self.path)) |
56df90
|
401 |
|
CM |
402 |
def stream(self): |
|
403 |
return self.pkg_resources.resource_stream(self.pkg_name, self.path) |
|
404 |
|
|
405 |
def isdir(self): |
|
406 |
return self.pkg_resources.resource_isdir(self.pkg_name, self.path) |
|
407 |
|
|
408 |
def listdir(self): |
|
409 |
return self.pkg_resources.resource_listdir(self.pkg_name, self.path) |
|
410 |
|
|
411 |
def exists(self): |
|
412 |
return self.pkg_resources.resource_exists(self.pkg_name, self.path) |
|
413 |
|
|
414 |
@implementer(IAssetDescriptor) |
|
415 |
class FSAssetDescriptor(object): |
|
416 |
|
|
417 |
def __init__(self, path): |
|
418 |
self.path = os.path.abspath(path) |
|
419 |
|
|
420 |
def absspec(self): |
|
421 |
raise NotImplementedError |
|
422 |
|
|
423 |
def abspath(self): |
|
424 |
return self.path |
|
425 |
|
|
426 |
def stream(self): |
|
427 |
return open(self.path, 'rb') |
|
428 |
|
|
429 |
def isdir(self): |
|
430 |
return os.path.isdir(self.path) |
|
431 |
|
|
432 |
def listdir(self): |
|
433 |
return os.listdir(self.path) |
|
434 |
|
|
435 |
def exists(self): |
|
436 |
return os.path.exists(self.path) |