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