Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pyramid/path.py : 55%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import os
2import pkg_resources
3import sys
4import imp
6from zope.interface import implementer
8from pyramid.interfaces import IAssetDescriptor
10from pyramid.compat import string_types
12ignore_types = [imp.C_EXTENSION, imp.C_BUILTIN]
13init_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]
20def caller_path(path, level=2):
21 if not os.path.isabs(path):
22 module = caller_module(level + 1)
23 prefix = package_path(module)
24 path = os.path.join(prefix, path)
25 return path
28def caller_module(level=2, sys=sys):
29 module_globals = sys._getframe(level).f_globals
30 module_name = module_globals.get('__name__') or '__main__'
31 module = sys.modules[module_name]
32 return module
35def package_name(pkg_or_module):
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."""
40 if pkg_or_module is None or pkg_or_module.__name__ == '__main__':
41 return '__main__'
42 pkg_name = pkg_or_module.__name__
43 pkg_filename = getattr(pkg_or_module, '__file__', None)
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
48 splitted = os.path.split(pkg_filename)
49 if splitted[-1] in init_names:
50 # it's a package
51 return pkg_name
52 return pkg_name.rsplit('.', 1)[0]
55def package_of(pkg_or_module):
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]
62def caller_package(level=2, caller_module=caller_module):
63 # caller_module in arglist for tests
64 module = caller_module(level + 1)
65 f = getattr(module, '__file__', '')
66 if ('__init__.py' in f) or ('__init__$py' in f): # empty at >>>
67 # Module is a package
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]
74def package_path(package):
75 # computing the abspath is actually kinda expensive so we memoize
76 # the result
77 prefix = getattr(package, '__abspath__', None)
78 if prefix is None:
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:
84 package.__abspath__ = prefix
85 except Exception:
86 # this is only an optimization, ignore any error
87 pass
88 return prefix
91class _CALLER_PACKAGE(object):
92 def __repr__(self): # pragma: no cover (for docs)
93 return 'pyramid.path.CALLER_PACKAGE'
96CALLER_PACKAGE = _CALLER_PACKAGE()
99class Resolver(object):
100 def __init__(self, package=CALLER_PACKAGE):
101 if package in (None, CALLER_PACKAGE):
102 self.package = package
103 else:
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,)
110 )
111 package = sys.modules[package]
112 self.package = package_of(package)
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
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
129class AssetResolver(Resolver):
130 """ A class used to resolve an :term:`asset specification` to an
131 :term:`asset descriptor`.
133 .. versionadded:: 1.3
135 The constructor accepts a single argument named ``package`` which may be
136 any of:
138 - A fully qualified (not relative) dotted name to a module or package
140 - a Python module or package object
142 - The value ``None``
144 - The constant value :attr:`pyramid.path.CALLER_PACKAGE`.
146 The default value is :attr:`pyramid.path.CALLER_PACKAGE`.
148 The ``package`` is used when a relative asset specification is supplied
149 to the :meth:`~pyramid.path.AssetResolver.resolve` method. An asset
150 specification without a colon in it is treated as relative.
152 If ``package`` is ``None``, the resolver will
153 only be able to resolve fully qualified (not relative) asset
154 specifications. Any attempt to resolve a relative asset specification
155 will result in an :exc:`ValueError` exception.
157 If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`,
158 the resolver will treat relative asset specifications as
159 relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve`
160 method.
162 If ``package`` is a *module* or *module name* (as opposed to a package or
163 package name), its containing package is computed and this
164 package is used to derive the package name (all names are resolved relative
165 to packages, never to modules). For example, if the ``package`` argument
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.
172 If ``package`` is a *package* or *package name* (as opposed to a module or
173 module name), this package will be used to compute relative
174 asset specifications. For example, if the ``package`` argument to this
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 """
180 def resolve(self, spec):
181 """
182 Resolve the asset spec named as ``spec`` to an object that has the
183 attributes and methods described in
184 :class:`pyramid.interfaces.IAssetDescriptor`.
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.
192 If ``spec`` is a *relative* asset specification (an asset
193 specification without a ``:`` in it, e.g. ``templates/foo.pt``), the
194 ``package`` argument of the constructor is used as the package
195 portion of the asset spec. For example:
197 .. code-block:: python
199 a = AssetResolver('myproject')
200 resolver = a.resolve('templates/foo.pt')
201 print(resolver.abspath())
202 # -> /path/to/myproject/templates/foo.pt
204 If the AssetResolver is constructed without a ``package`` argument of
205 ``None``, and a relative asset specification is passed to
206 ``resolve``, an :exc:`ValueError` exception is raised.
207 """
208 if os.path.isabs(spec):
209 return FSAssetDescriptor(spec)
210 path = spec
211 if ':' in path:
212 package_name, path = spec.split(':', 1)
213 else:
214 if self.package is CALLER_PACKAGE:
215 package_name = caller_package().__name__
216 else:
217 package_name = getattr(self.package, '__name__', None)
218 if package_name is None:
219 raise ValueError(
220 'relative spec %r irresolveable without package' % (spec,)
221 )
222 return PkgResourcesAssetDescriptor(package_name, path)
225class DottedNameResolver(Resolver):
226 """ A class used to resolve a :term:`dotted Python name` to a package or
227 module object.
229 .. versionadded:: 1.3
231 The constructor accepts a single argument named ``package`` which may be
232 any of:
234 - A fully qualified (not relative) dotted name to a module or package
236 - a Python module or package object
238 - The value ``None``
240 - The constant value :attr:`pyramid.path.CALLER_PACKAGE`.
242 The default value is :attr:`pyramid.path.CALLER_PACKAGE`.
244 The ``package`` is used when a relative dotted name is supplied to the
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.
249 If ``package`` is ``None``, the resolver will only be able to resolve
250 fully qualified (not relative) names. Any attempt to resolve a
251 relative name will result in an :exc:`ValueError` exception.
253 If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`,
254 the resolver will treat relative dotted names as relative to
255 the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve`
256 method.
258 If ``package`` is a *module* or *module name* (as opposed to a package or
259 package name), its containing package is computed and this
260 package used to derive the package name (all names are resolved relative
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.
268 If ``package`` is a *package* or *package name* (as opposed to a module or
269 module name), this package will be used to relative compute
270 dotted names. For example, if the ``package`` argument to this type was
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 """
276 def resolve(self, dotted):
277 """
278 This method resolves a dotted name reference to a global Python
279 object (an object which can be imported) to the object itself.
281 Two dotted name styles are supported:
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``.
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``.
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.
296 If the ``dotted`` argument passed to this method is not a string, a
297 :exc:`ValueError` will be raised.
299 When a dotted name cannot be resolved, a :exc:`ValueError` error is
300 raised.
302 Example:
304 .. code-block:: python
306 r = DottedNameResolver()
307 v = r.resolve('xml') # v is the xml module
309 """
310 if not isinstance(dotted, string_types):
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)
317 def maybe_resolve(self, dotted):
318 """
319 This method behaves just like
320 :meth:`~pyramid.path.DottedNameResolver.resolve`, except if the
321 ``dotted`` value passed is not a string, it is simply returned. For
322 example:
324 .. code-block:: python
326 import xml
327 r = DottedNameResolver()
328 v = r.maybe_resolve(xml)
329 # v is the xml module; no exception raised
330 """
331 if isinstance(dotted, string_types):
332 package = self.package
333 if package is CALLER_PACKAGE:
334 package = caller_package()
335 return self._resolve(dotted, package)
336 return dotted
338 def _resolve(self, dotted, package):
339 if ':' in dotted:
340 return self._pkg_resources_style(dotted, package)
341 else:
342 return self._zope_dottedname_style(dotted, package)
344 def _pkg_resources_style(self, value, package):
345 """ package.module:attr style """
346 if value.startswith(('.', ':')):
347 if not package:
348 raise ValueError(
349 'relative name %r irresolveable without package' % (value,)
350 )
351 if value in ['.', ':']:
352 value = package.__name__
353 else:
354 value = package.__name__ + value
355 # Calling EntryPoint.load with an argument is deprecated.
356 # See https://pythonhosted.org/setuptools/history.html#id8
357 ep = pkg_resources.EntryPoint.parse('x=%s' % value)
358 if hasattr(ep, 'resolve'):
359 # setuptools>=10.2
360 return ep.resolve() # pragma: NO COVER
361 else:
362 return ep.load(False) # pragma: NO COVER
364 def _zope_dottedname_style(self, value, package):
365 """ package.module.attr style """
366 module = getattr(package, '__name__', None) # package may be None
367 if not module:
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,)
382 )
383 module = module.split('.')
384 name.pop(0)
385 while not name[0]:
386 module.pop()
387 name.pop(0)
388 name = module + name
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)
398 found = getattr(found, n) # pragma: no cover
400 return found
403@implementer(IAssetDescriptor)
404class PkgResourcesAssetDescriptor(object):
405 pkg_resources = pkg_resources
407 def __init__(self, pkg_name, path):
408 self.pkg_name = pkg_name
409 self.path = path
411 def absspec(self):
412 return '%s:%s' % (self.pkg_name, self.path)
414 def abspath(self):
415 return os.path.abspath(
416 self.pkg_resources.resource_filename(self.pkg_name, self.path)
417 )
419 def stream(self):
420 return self.pkg_resources.resource_stream(self.pkg_name, self.path)
422 def isdir(self):
423 return self.pkg_resources.resource_isdir(self.pkg_name, self.path)
425 def listdir(self):
426 return self.pkg_resources.resource_listdir(self.pkg_name, self.path)
428 def exists(self):
429 return self.pkg_resources.resource_exists(self.pkg_name, self.path)
432@implementer(IAssetDescriptor)
433class FSAssetDescriptor(object):
434 def __init__(self, path):
435 self.path = os.path.abspath(path)
437 def absspec(self):
438 raise NotImplementedError
440 def abspath(self):
441 return self.path
443 def stream(self):
444 return open(self.path, 'rb')
446 def isdir(self):
447 return os.path.isdir(self.path)
449 def listdir(self):
450 return os.listdir(self.path)
452 def exists(self):
453 return os.path.exists(self.path)