1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 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 
 39      """ A builtin module was specified for relative reference """ 
  40   
 43      """ 
 44      Globally visible resource service object 
 45   
 46      :IVariables: 
 47       - `__resources`: Resource mapping (``{'name': Resource}``) 
 48   
 49      :Types: 
 50       - `__resources`: ``dict`` 
 51      """ 
 52   
 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   
 67          return self.__locate(*args, **kwargs) 
  68   
 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   
 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   
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   
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   
137          """ :See: `wtf.services.ServiceInterface.shutdown` """ 
138          pass 
 139   
141          """ :See: `wtf.services.ServiceInterface.global_service` """ 
142          return 'wtf.resource', GlobalResources(self._locate, self._rsc) 
 143   
145          """ :See: `wtf.services.ServiceInterface.middleware` """ 
146          return func 
  147   
150      """ 
151      Resource locator 
152   
153      :IVariables: 
154       - `_config`: Configuration 
155   
156      :Types: 
157       - `_config`: `wtf.config.Config` 
158      """ 
159   
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   
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 
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   
302          """ 
303          List the directory 
304   
305          :return: List of strings (as returned by ``os.listdir``) 
306          :rtype: ``list`` 
307          """ 
308           
309   
310          return _os.listdir(self._base.encode(self._encoding)) 
 311   
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           
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   
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   
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           
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   
470          """ 
471          Determine the size of the resource in bytes 
472   
473          :return: The resource size 
474          :rtype: ``int`` 
475          """ 
476          return self._length  
  477