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