Hide keyboard shortcuts

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 

5 

6from zope.interface import implementer 

7 

8from pyramid.interfaces import IAssetDescriptor 

9 

10from pyramid.compat import string_types 

11 

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] 

18 

19 

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 

26 

27 

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 

33 

34 

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] 

53 

54 

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] 

60 

61 

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] 

72 

73 

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 

89 

90 

91class _CALLER_PACKAGE(object): 

92 def __repr__(self): # pragma: no cover (for docs) 

93 return 'pyramid.path.CALLER_PACKAGE' 

94 

95 

96CALLER_PACKAGE = _CALLER_PACKAGE() 

97 

98 

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) 

113 

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 

128 

129class AssetResolver(Resolver): 

130 """ A class used to resolve an :term:`asset specification` to an 

131 :term:`asset descriptor`. 

132 

133 .. versionadded:: 1.3 

134 

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 

144 - The constant value :attr:`pyramid.path.CALLER_PACKAGE`. 

145 

146 The default value is :attr:`pyramid.path.CALLER_PACKAGE`. 

147 

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. 

151 

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. 

156 

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. 

161 

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. 

171 

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

179 

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

185 

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 

194 ``package`` argument of the constructor is used as the package 

195 portion of the asset spec. For example: 

196 

197 .. code-block:: python 

198 

199 a = AssetResolver('myproject') 

200 resolver = a.resolve('templates/foo.pt') 

201 print(resolver.abspath()) 

202 # -> /path/to/myproject/templates/foo.pt 

203 

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) 

223 

224 

225class DottedNameResolver(Resolver): 

226 """ A class used to resolve a :term:`dotted Python name` to a package or 

227 module object. 

228 

229 .. versionadded:: 1.3 

230 

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 

240 - The constant value :attr:`pyramid.path.CALLER_PACKAGE`. 

241 

242 The default value is :attr:`pyramid.path.CALLER_PACKAGE`. 

243 

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. 

248 

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. 

252 

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. 

257 

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. 

267 

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

275 

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. 

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 

296 If the ``dotted`` argument passed to this method is not a string, a 

297 :exc:`ValueError` will be raised. 

298 

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 

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) 

316 

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: 

323 

324 .. code-block:: python 

325 

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 

337 

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) 

343 

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 

363 

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 

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) 

398 found = getattr(found, n) # pragma: no cover 

399 

400 return found 

401 

402 

403@implementer(IAssetDescriptor) 

404class 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): 

415 return os.path.abspath( 

416 self.pkg_resources.resource_filename(self.pkg_name, self.path) 

417 ) 

418 

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 

431 

432@implementer(IAssetDescriptor) 

433class 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)