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 

4 

5from zope.interface import implementer 

6 

7from pyramid.interfaces import IPackageOverrides, PHASE1_CONFIG 

8 

9from pyramid.exceptions import ConfigurationError 

10from pyramid.threadlocal import get_current_registry 

11 

12from pyramid.config.actions import action_method 

13 

14 

15class OverrideProvider(pkg_resources.DefaultProvider): 

16 def __init__(self, module): 

17 pkg_resources.DefaultProvider.__init__(self, module) 

18 self.module_name = module.__name__ 

19 

20 def _get_overrides(self): 

21 reg = get_current_registry() 

22 overrides = reg.queryUtility(IPackageOverrides, self.module_name) 

23 return overrides 

24 

25 def get_resource_filename(self, manager, resource_name): 

26 """ Return a true filesystem path for resource_name, 

27 co-ordinating the extraction with manager, if the resource 

28 must be unpacked to the filesystem. 

29 """ 

30 overrides = self._get_overrides() 

31 if overrides is not None: 

32 filename = overrides.get_filename(resource_name) 

33 if filename is not None: 

34 return filename 

35 return pkg_resources.DefaultProvider.get_resource_filename( 

36 self, manager, resource_name 

37 ) 

38 

39 def get_resource_stream(self, manager, resource_name): 

40 """ Return a readable file-like object for resource_name.""" 

41 overrides = self._get_overrides() 

42 if overrides is not None: 

43 stream = overrides.get_stream(resource_name) 

44 if stream is not None: 

45 return stream 

46 return pkg_resources.DefaultProvider.get_resource_stream( 

47 self, manager, resource_name 

48 ) 

49 

50 def get_resource_string(self, manager, resource_name): 

51 """ Return a string containing the contents of resource_name.""" 

52 overrides = self._get_overrides() 

53 if overrides is not None: 

54 string = overrides.get_string(resource_name) 

55 if string is not None: 

56 return string 

57 return pkg_resources.DefaultProvider.get_resource_string( 

58 self, manager, resource_name 

59 ) 

60 

61 def has_resource(self, resource_name): 

62 overrides = self._get_overrides() 

63 if overrides is not None: 

64 result = overrides.has_resource(resource_name) 

65 if result is not None: 

66 return result 

67 return pkg_resources.DefaultProvider.has_resource(self, resource_name) 

68 

69 def resource_isdir(self, resource_name): 

70 overrides = self._get_overrides() 

71 if overrides is not None: 

72 result = overrides.isdir(resource_name) 

73 if result is not None: 

74 return result 

75 return pkg_resources.DefaultProvider.resource_isdir( 

76 self, resource_name 

77 ) 

78 

79 def resource_listdir(self, resource_name): 

80 overrides = self._get_overrides() 

81 if overrides is not None: 

82 result = overrides.listdir(resource_name) 

83 if result is not None: 

84 return result 

85 return pkg_resources.DefaultProvider.resource_listdir( 

86 self, resource_name 

87 ) 

88 

89 

90@implementer(IPackageOverrides) 

91class PackageOverrides(object): 

92 # pkg_resources arg in kw args below for testing 

93 def __init__(self, package, pkg_resources=pkg_resources): 

94 loader = self._real_loader = getattr(package, '__loader__', None) 

95 if isinstance(loader, self.__class__): 

96 self._real_loader = None 

97 # We register ourselves as a __loader__ *only* to support the 

98 # setuptools _find_adapter adapter lookup; this class doesn't 

99 # actually support the PEP 302 loader "API". This is 

100 # excusable due to the following statement in the spec: 

101 # ... Loader objects are not 

102 # required to offer any useful functionality (any such functionality, 

103 # such as the zipimport get_data() method mentioned above, is 

104 # optional)... 

105 # A __loader__ attribute is basically metadata, and setuptools 

106 # uses it as such. 

107 package.__loader__ = self 

108 # we call register_loader_type for every instantiation of this 

109 # class; that's OK, it's idempotent to do it more than once. 

110 pkg_resources.register_loader_type(self.__class__, OverrideProvider) 

111 self.overrides = [] 

112 self.overridden_package_name = package.__name__ 

113 

114 def insert(self, path, source): 

115 if not path or path.endswith('/'): 

116 override = DirectoryOverride(path, source) 

117 else: 

118 override = FileOverride(path, source) 

119 self.overrides.insert(0, override) 

120 return override 

121 

122 def filtered_sources(self, resource_name): 

123 for override in self.overrides: 

124 o = override(resource_name) 

125 if o is not None: 

126 yield o 

127 

128 def get_filename(self, resource_name): 

129 for source, path in self.filtered_sources(resource_name): 

130 result = source.get_filename(path) 

131 if result is not None: 

132 return result 

133 

134 def get_stream(self, resource_name): 

135 for source, path in self.filtered_sources(resource_name): 

136 result = source.get_stream(path) 

137 if result is not None: 

138 return result 

139 

140 def get_string(self, resource_name): 

141 for source, path in self.filtered_sources(resource_name): 

142 result = source.get_string(path) 

143 if result is not None: 

144 return result 

145 

146 def has_resource(self, resource_name): 

147 for source, path in self.filtered_sources(resource_name): 

148 if source.exists(path): 

149 return True 

150 

151 def isdir(self, resource_name): 

152 for source, path in self.filtered_sources(resource_name): 

153 result = source.isdir(path) 

154 if result is not None: 

155 return result 

156 

157 def listdir(self, resource_name): 

158 for source, path in self.filtered_sources(resource_name): 

159 result = source.listdir(path) 

160 if result is not None: 

161 return result 

162 

163 @property 

164 def real_loader(self): 

165 if self._real_loader is None: 

166 raise NotImplementedError() 

167 return self._real_loader 

168 

169 def get_data(self, path): 

170 """ See IPEP302Loader. 

171 """ 

172 return self.real_loader.get_data(path) 

173 

174 def is_package(self, fullname): 

175 """ See IPEP302Loader. 

176 """ 

177 return self.real_loader.is_package(fullname) 

178 

179 def get_code(self, fullname): 

180 """ See IPEP302Loader. 

181 """ 

182 return self.real_loader.get_code(fullname) 

183 

184 def get_source(self, fullname): 

185 """ See IPEP302Loader. 

186 """ 

187 return self.real_loader.get_source(fullname) 

188 

189 

190class DirectoryOverride: 

191 def __init__(self, path, source): 

192 self.path = path 

193 self.pathlen = len(self.path) 

194 self.source = source 

195 

196 def __call__(self, resource_name): 

197 if resource_name.startswith(self.path): 

198 new_path = resource_name[self.pathlen :] 

199 return self.source, new_path 

200 

201 

202class FileOverride: 

203 def __init__(self, path, source): 

204 self.path = path 

205 self.source = source 

206 

207 def __call__(self, resource_name): 

208 if resource_name == self.path: 

209 return self.source, '' 

210 

211 

212class PackageAssetSource(object): 

213 """ 

214 An asset source relative to a package. 

215 

216 If this asset source is a file, then we expect the ``prefix`` to point 

217 to the new name of the file, and the incoming ``resource_name`` will be 

218 the empty string, as returned by the ``FileOverride``. 

219 

220 """ 

221 

222 def __init__(self, package, prefix): 

223 self.package = package 

224 if hasattr(package, '__name__'): 

225 self.pkg_name = package.__name__ 

226 else: 

227 self.pkg_name = package 

228 self.prefix = prefix 

229 

230 def get_path(self, resource_name): 

231 return '%s%s' % (self.prefix, resource_name) 

232 

233 def get_filename(self, resource_name): 

234 path = self.get_path(resource_name) 

235 if pkg_resources.resource_exists(self.pkg_name, path): 

236 return pkg_resources.resource_filename(self.pkg_name, path) 

237 

238 def get_stream(self, resource_name): 

239 path = self.get_path(resource_name) 

240 if pkg_resources.resource_exists(self.pkg_name, path): 

241 return pkg_resources.resource_stream(self.pkg_name, path) 

242 

243 def get_string(self, resource_name): 

244 path = self.get_path(resource_name) 

245 if pkg_resources.resource_exists(self.pkg_name, path): 

246 return pkg_resources.resource_string(self.pkg_name, path) 

247 

248 def exists(self, resource_name): 

249 path = self.get_path(resource_name) 

250 if pkg_resources.resource_exists(self.pkg_name, path): 

251 return True 

252 

253 def isdir(self, resource_name): 

254 path = self.get_path(resource_name) 

255 if pkg_resources.resource_exists(self.pkg_name, path): 

256 return pkg_resources.resource_isdir(self.pkg_name, path) 

257 

258 def listdir(self, resource_name): 

259 path = self.get_path(resource_name) 

260 if pkg_resources.resource_exists(self.pkg_name, path): 

261 return pkg_resources.resource_listdir(self.pkg_name, path) 

262 

263 

264class FSAssetSource(object): 

265 """ 

266 An asset source relative to a path in the filesystem. 

267 

268 """ 

269 

270 def __init__(self, prefix): 

271 self.prefix = prefix 

272 

273 def get_path(self, resource_name): 

274 if resource_name: 

275 path = os.path.join(self.prefix, resource_name) 

276 else: 

277 path = self.prefix 

278 return path 

279 

280 def get_filename(self, resource_name): 

281 path = self.get_path(resource_name) 

282 if os.path.exists(path): 

283 return path 

284 

285 def get_stream(self, resource_name): 

286 path = self.get_filename(resource_name) 

287 if path is not None: 

288 return open(path, 'rb') 

289 

290 def get_string(self, resource_name): 

291 stream = self.get_stream(resource_name) 

292 if stream is not None: 

293 with stream: 

294 return stream.read() 

295 

296 def exists(self, resource_name): 

297 path = self.get_filename(resource_name) 

298 if path is not None: 

299 return True 

300 

301 def isdir(self, resource_name): 

302 path = self.get_filename(resource_name) 

303 if path is not None: 

304 return os.path.isdir(path) 

305 

306 def listdir(self, resource_name): 

307 path = self.get_filename(resource_name) 

308 if path is not None: 

309 return os.listdir(path) 

310 

311 

312class AssetsConfiguratorMixin(object): 

313 def _override( 

314 self, package, path, override_source, PackageOverrides=PackageOverrides 

315 ): 

316 pkg_name = package.__name__ 

317 override = self.registry.queryUtility(IPackageOverrides, name=pkg_name) 

318 if override is None: 

319 override = PackageOverrides(package) 

320 self.registry.registerUtility( 

321 override, IPackageOverrides, name=pkg_name 

322 ) 

323 override.insert(path, override_source) 

324 

325 @action_method 

326 def override_asset(self, to_override, override_with, _override=None): 

327 """ Add a :app:`Pyramid` asset override to the current 

328 configuration state. 

329 

330 ``to_override`` is an :term:`asset specification` to the 

331 asset being overridden. 

332 

333 ``override_with`` is an :term:`asset specification` to the 

334 asset that is performing the override. This may also be an absolute 

335 path. 

336 

337 See :ref:`assets_chapter` for more 

338 information about asset overrides.""" 

339 if to_override == override_with: 

340 raise ConfigurationError( 

341 'You cannot override an asset with itself' 

342 ) 

343 

344 package = to_override 

345 path = '' 

346 if ':' in to_override: 

347 package, path = to_override.split(':', 1) 

348 

349 # *_isdir = override is package or directory 

350 overridden_isdir = path == '' or path.endswith('/') 

351 

352 if os.path.isabs(override_with): 

353 override_source = FSAssetSource(override_with) 

354 if not os.path.exists(override_with): 

355 raise ConfigurationError( 

356 'Cannot override asset with an absolute path that does ' 

357 'not exist' 

358 ) 

359 override_isdir = os.path.isdir(override_with) 

360 override_package = None 

361 override_prefix = override_with 

362 else: 

363 override_package = override_with 

364 override_prefix = '' 

365 if ':' in override_with: 

366 override_package, override_prefix = override_with.split(':', 1) 

367 

368 __import__(override_package) 

369 to_package = sys.modules[override_package] 

370 override_source = PackageAssetSource(to_package, override_prefix) 

371 

372 override_isdir = override_prefix == '' or override_with.endswith( 

373 '/' 

374 ) 

375 

376 if overridden_isdir and (not override_isdir): 

377 raise ConfigurationError( 

378 'A directory cannot be overridden with a file (put a ' 

379 'slash at the end of override_with if necessary)' 

380 ) 

381 

382 if (not overridden_isdir) and override_isdir: 

383 raise ConfigurationError( 

384 'A file cannot be overridden with a directory (put a ' 

385 'slash at the end of to_override if necessary)' 

386 ) 

387 

388 override = _override or self._override # test jig 

389 

390 def register(): 

391 __import__(package) 

392 from_package = sys.modules[package] 

393 override(from_package, path, override_source) 

394 

395 intr = self.introspectable( 

396 'asset overrides', 

397 (package, override_package, path, override_prefix), 

398 '%s -> %s' % (to_override, override_with), 

399 'asset override', 

400 ) 

401 intr['to_override'] = to_override 

402 intr['override_with'] = override_with 

403 self.action( 

404 None, register, introspectables=(intr,), order=PHASE1_CONFIG 

405 ) 

406 

407 override_resource = override_asset # bw compat