Package wtf :: Package app :: Package services :: Module resources
[hide private]
[frames] | no frames]

Source Code for Module wtf.app.services.resources

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2007-2012 
  4  # Andr\xe9 Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 17  """ 
 18  Resource Resolver 
 19  ================= 
 20   
 21  This service provides global access to resources on disk, set up in 
 22  the configuration. 
 23  """ 
 24  __author__ = u"Andr\xe9 Malo" 
 25  __docformat__ = "restructuredtext en" 
 26   
 27  import datetime as _datetime 
 28  import errno as _errno 
 29  import os as _os 
 30  import sys as _sys 
 31   
 32  from wtf.config import ConfigurationError 
 33  from wtf import services as _services 
 34  from wtf import stream as _stream 
 35  from wtf import util as _util 
36 37 38 -class BuiltinModuleError(ConfigurationError):
39 """ A builtin module was specified for relative reference """
40
41 42 -class GlobalResources(object):
43 """ 44 Globally visible resource service object 45 46 :IVariables: 47 - `__resources`: Resource mapping (``{'name': Resource}``) 48 49 :Types: 50 - `__resources`: ``dict`` 51 """ 52
53 - def __init__(self, locator, resources):
54 """ 55 Initialization 56 57 :Parameters: 58 - `resources`: Resource mapping (``{'name': Resource}``) 59 60 :Types: 61 - `resources`: ``dict`` 62 """ 63 self.__locate = locator 64 self.__resources = resources
65
66 - def __call__(self, *args, **kwargs):
67 return self.__locate(*args, **kwargs)
68
69 - def __getitem__(self, name):
70 """ 71 Dict like resource getter 72 73 :Parameters: 74 - `name`: Name to look up 75 76 :Types: 77 - `name`: ``str`` 78 79 :Exceptions: 80 - `KeyError`: Resource not found 81 """ 82 return self.__resources[name]
83
84 - def __getattr__(self, name):
85 """ 86 Attribute like resource getter 87 88 :Parameters: 89 - `name`: Name to look up 90 91 :Types: 92 - `name`: ``str`` 93 94 :Exceptions: 95 - `AttributeError`: Resource not found 96 """ 97 try: 98 return self[name] 99 except KeyError: 100 raise AttributeError(name)
101
102 - def __contains__(self, name):
103 """ 104 Check for availability of resource `name` 105 106 :Parameters: 107 - `name`: The resource name to look up 108 109 :Types: 110 - `name`: ``str`` 111 112 :return: Is the resource available (aka configured)? 113 :rtype: ``bool`` 114 """ 115 return name in self.__resources
116
117 118 -class ResourceService(object):
119 """ 120 Resource Resolver 121 122 This service provides global access to resources on disk. 123 """ 124 __implements__ = [_services.ServiceInterface] 125
126 - def __init__(self, config, opts, args):
127 """ 128 Initialization 129 130 :See: `wtf.services.ServiceInterface.__init__` 131 """ 132 self._locate = Locator(config) 133 self._rsc = dict((key, [self._locate(item) for item in value]) 134 for key, value in config.resources)
135
136 - def shutdown(self):
137 """ :See: `wtf.services.ServiceInterface.shutdown` """ 138 pass
139
140 - def global_service(self):
141 """ :See: `wtf.services.ServiceInterface.global_service` """ 142 return 'wtf.resource', GlobalResources(self._locate, self._rsc)
143
144 - def middleware(self, func):
145 """ :See: `wtf.services.ServiceInterface.middleware` """ 146 return func
147
148 149 -class Locator(object):
150 """ 151 Resource locator 152 153 :IVariables: 154 - `_config`: Configuration 155 156 :Types: 157 - `_config`: `wtf.config.Config` 158 """ 159
160 - def __init__(self, config):
161 """ 162 Initialization 163 164 :Parameters: 165 - `config`: Configuration 166 167 :Types: 168 - `config`: `wtf.config.Config` 169 """ 170 self._config = config
171
172 - def __call__(self, spec, isfile=False):
173 """ 174 Locate a particular resource 175 176 :Parameters: 177 - `spec`: The specification 178 - `isfile`: Does spec refer to a file? 179 180 :Types: 181 - `spec`: ``str`` 182 - `isfile`: ``bool`` 183 184 :return: A resource Container 185 :rtype: `Resource` 186 """ 187 if spec.startswith('pkg:') or spec.startswith('mod:'): 188 rsc = Resource.frommodule(spec[4:], isfile) 189 elif spec.startswith('dir:'): 190 rsc = Resource(spec[4:], self._config.ROOT, isfile) 191 else: 192 try: 193 rsc = Resource.frommodule(spec, isfile) 194 except (IOError, ImportError): 195 rsc = Resource(spec, self._config.ROOT, isfile) 196 return rsc
197
198 199 -class Resource(object):
200 """ 201 Base resource container 202 203 :IVariables: 204 - `_base`: Base directory which is represented by this object 205 206 :Types: 207 - `_base`: ``unicode`` 208 """ 209 _encoding = _sys.getfilesystemencoding() or 'latin-1' 210
211 - def __new__(cls, base, root=None, isfile=False):
212 """ 213 Construction 214 215 :Parameters: 216 - `base`: Base directory to represent 217 - `root`: Root directory in case that `base` is relative. If it's 218 ``None``, the current working directory is taken. 219 - `isfile`: Does it refer to a file? 220 221 :Types: 222 - `base`: ``unicode`` 223 - `root`: ``str`` 224 - `isfile`: ``bool`` 225 226 :Exceptions: 227 - `IOError`: path not found 228 """ 229 if root is None: 230 root = _os.getcwd().decode(cls._encoding) 231 base = base.encode('utf-8') 232 base = _os.path.normpath( 233 _os.path.join(root.encode('utf-8'), base.encode('utf-8')) 234 ).decode('utf-8') 235 if isfile: 236 basename = _os.path.basename(base) 237 base = _os.path.normpath(_os.path.dirname(base)) 238 if not _os.path.isdir(base): 239 raise IOError(_errno.ENOENT, base) 240 241 self = super(Resource, cls).__new__(cls) 242 self._base = base 243 244 if isfile: 245 return self.resolve(basename) 246 return self
247 248 @classmethod
249 - def frommodule(cls, base, isfile=False):
250 """ 251 Determine a resource relative to a module 252 253 :Parameters: 254 - `base`: The combined module and path. Like: 255 ``wtf.app.sample:static``. This resolves to the static subdirectory 256 of the wtf.app.sample package. If the last part is a module, the 257 directory is treated as parallel to the module, Like: 258 ``wtf.app.sample.index:static`` resolves to the static subdir of 259 wtf.app.sample, too; parallel to index.py. 260 - `isfile`: Does it refer to a file? 261 262 :Types: 263 - `base`: ``unicode`` 264 - `isfile`: Does it refer to a file? 265 266 :return: New resource instance 267 :rtype: `Resource` 268 269 :Exceptions: 270 - `ImportError`: Module could not be imported 271 - `BuiltinModuleError`: Module doesn't have a __file__ attribute 272 - `UnicodeError`: Recoding according to the locale failed. 273 """ 274 base = unicode(base) 275 tup = base.split(u':', 1) 276 if len(tup) == 1: 277 modname, reldir = base, None 278 else: 279 modname, reldir = tup 280 281 modname = modname.encode('ascii') 282 mod = _util.load_dotted(modname) 283 try: 284 modfile = mod.__file__ 285 except AttributeError: 286 raise BuiltinModuleError( 287 "Cannot take builtin module %r as relative path reference" % 288 (modname,) 289 ) 290 291 tup = [_os.path.normpath(_os.path.dirname( 292 modfile.decode(cls._encoding).encode('utf-8')))] 293 if reldir is not None: 294 reldir = _os.path.normpath(reldir.encode('utf-8')) 295 root = _os.path.normpath('/') 296 while reldir.startswith(root): 297 reldir = reldir[1:] 298 tup.append(reldir) 299 return cls(_os.path.join(*tup).decode('utf-8'), isfile=isfile)
300
301 - def list(self):
302 """ 303 List the directory 304 305 :return: List of strings (as returned by ``os.listdir``) 306 :rtype: ``list`` 307 """ 308 # pylint: disable = E1101 309 310 return _os.listdir(self._base.encode(self._encoding))
311
312 - def resolve(self, name):
313 """ 314 Resolve a filename relative to this directory 315 316 :Parameters: 317 - `name`: filename to resolve 318 319 :Types: 320 - `name`: ``unicode`` 321 322 :return: resolved filename in system encoding 323 :rtype: ``str`` 324 325 :Exceptions: 326 - `UnicodeError`: Recoding failed 327 """ 328 # pylint: disable = E1101 329 330 root = _os.path.normpath('/') 331 resolved = _os.path.splitdrive(_os.path.normpath( 332 _os.path.join(root, unicode(name).encode('utf-8')) 333 ))[1] 334 while resolved.startswith(root): 335 resolved = resolved[1:] 336 resolved = _os.path.normpath( 337 _os.path.join(self._base, resolved) 338 ).decode('utf-8') 339 return FileResource(self, name, resolved.encode(self._encoding))
340
341 - def open(self, name, mode='rb', buffering=-1, blockiter=1):
342 """ 343 Open a file relative to this directory 344 345 :Parameters: 346 - `name`: relative filename 347 - `mode`: File open mode 348 - `buffering`: buffer spec (``-1`` == default, ``0`` == unbuffered) 349 - `blockiter`: Iterator mode 350 (``<= 0: Default block size, 1: line, > 1: This block size``) 351 352 :Types: 353 - `name`: ``unicode`` 354 - `mode`: ``str`` 355 - `buffering`: ``int`` 356 - `blockiter`: ``int`` 357 358 :return: open stream 359 :rtype: `ResourceStream` 360 361 :Exceptions: 362 - `IOError`: Error opening file 363 """ 364 return self.resolve(name).open(mode, buffering, blockiter)
365
366 367 -class FileResource(object):
368 """ 369 Resource representing a file 370 371 :IVariables: 372 - `directory`: The directory resource 373 - `resource`: The file resource name 374 - `filename`: The full file name 375 376 :Types: 377 - `directory`: `Resource` 378 - `resource`: ``unicode`` 379 - `filename`: ``str`` 380 """
381 - def __init__(self, directory, resource, filename):
382 """ 383 Initialization 384 385 :Parameters: 386 - `directory`: The directory resource 387 - `resource`: The file resource name 388 - `filename`: The full file name 389 390 :Types: 391 - `directory`: `Resource` 392 - `resource`: ``unicode`` 393 - `filename`: ``str`` 394 """ 395 self.directory = directory 396 self.resource = resource 397 self.filename = filename
398
399 - def open(self, mode='rb', buffering=-1, blockiter=1):
400 """ 401 Open the file 402 403 :Parameters: 404 - `mode`: The opening mode 405 - `buffering`: Buffering spec 406 - `blockiter`: Iterator mode 407 (``1: Line, <= 0: Default chunk size, > 1: This chunk size``) 408 409 :Types: 410 - `mode`: ``str`` 411 - `buffering`: ``int`` 412 - `blockiter`: ``int`` 413 414 :return: The open stream 415 :rtype: `ResourceStream` 416 """ 417 return ResourceStream( 418 self.resource, self.filename, mode, buffering, blockiter 419 )
420
421 422 -class ResourceStream(_stream.GenericStream):
423 """ 424 Extended generic stream, which provides more info about the resource 425 426 :IVariables: 427 - `_length`: Resource size in bytes. Retrieve it via len``(r)`` 428 - `resource`: The name, which the resource was resolved from 429 - `last_modified`: Last modification time 430 431 :Types: 432 - `_length`: ``int`` 433 - `resource`: ``str`` 434 - `last_modified`: ``datetime.datetime`` 435 """ 436
437 - def __new__(cls, resource, fullname, mode, buffering, blockiter):
438 """ 439 Initialization 440 441 :Parameters: 442 - `resource`: Resource name (just for making it available) 443 - `fullname`: Full name of the file to open 444 - `mode`: File opening mode 445 - `buffering`: Buffering spec 446 - `blockiter`: Iterator mode 447 448 :Types: 449 - `resource`: ``str`` 450 - `fullname`: ``str`` 451 - `mode`: ``str`` 452 - `buffering`: ``int`` 453 - `blockiter`: ``int`` 454 """ 455 # pylint: disable = W0221 456 457 stream = file(fullname, mode) 458 self = super(ResourceStream, cls).__new__( 459 cls, stream, buffering, blockiter, read_exact=True 460 ) 461 self.resource = resource 462 stres = _os.fstat(stream.fileno()) 463 self.last_modified = _datetime.datetime.utcfromtimestamp( 464 stres.st_mtime 465 ) 466 self._length = stres.st_size 467 return self
468
469 - def __len__(self):
470 """ 471 Determine the size of the resource in bytes 472 473 :return: The resource size 474 :rtype: ``int`` 475 """ 476 return self._length # pylint: disable = E1101
477