1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 ================
19 Misc Utilities
20 ================
21
22 Misc utilities.
23 """
24 __author__ = u"Andr\xe9 Malo"
25 __docformat__ = "restructuredtext en"
26
27 import imp as _imp
28 import inspect as _inspect
29 import os as _os
30 import re as _re
31 import sys as _sys
32 import types as _types
33 import warnings as _warnings
34
35 from tdi import _exceptions
39 """
40 Make content type parser
41
42 :Return: parse_content_type
43 :Rtype: ``callable``
44 """
45
46 tokenres = r'[^\000-\040()<>@,;:\\"/[\]?=]+'
47 qcontent = r'[^\000\\"]'
48 qsres = r'"%(qc)s*(?:\\"%(qc)s*)*"' % {'qc': qcontent}
49 valueres = r'(?:%(token)s|%(quoted-string)s)' % {
50 'token': tokenres, 'quoted-string': qsres,
51 }
52
53 typere = _re.compile(
54 r'\s*([^;/\s]+/[^;/\s]+)((?:\s*;\s*%(key)s\s*=\s*%(val)s)*)\s*$' %
55 {'key': tokenres, 'val': valueres,}
56 )
57 pairre = _re.compile(r'\s*;\s*(%(key)s)\s*=\s*(%(val)s)' % {
58 'key': tokenres, 'val': valueres
59 })
60 stripre = _re.compile(r'\r?\n')
61
62 def parse_content_type(value):
63 """
64 Parse a content type
65
66 :Warning: comments are not recognized (yet?)
67
68 :Parameters:
69 `value` : ``basestring``
70 The value to parse - must be ascii compatible
71
72 :Return: The parsed header (``(value, {key, [value, value, ...]})``)
73 or ``None``
74 :Rtype: ``tuple``
75 """
76 try:
77 if isinstance(value, unicode):
78 value.encode('ascii')
79 else:
80 value.decode('ascii')
81 except (AttributeError, UnicodeError):
82 return None
83
84 match = typere.match(value)
85 if not match:
86 return None
87
88 parsed = (match.group(1).lower(), {})
89 match = match.group(2)
90 if match:
91 for key, val in pairre.findall(match):
92 if val[:1] == '"':
93 val = stripre.sub(r'', val[1:-1]).replace(r'\"', '"')
94 parsed[1].setdefault(key.lower(), []).append(val)
95
96 return parsed
97
98 return parse_content_type
99
100 parse_content_type = _make_parse_content_type()
104 """
105 Represents the package version
106
107 :IVariables:
108 `major` : ``int``
109 The major version number
110
111 `minor` : ``int``
112 The minor version number
113
114 `patch` : ``int``
115 The patch level version number
116
117 `is_dev` : ``bool``
118 Is it a development version?
119
120 `revision` : ``int``
121 SVN Revision
122 """
123
124 - def __new__(cls, versionstring, is_dev, revision):
125 """
126 Construction
127
128 :Parameters:
129 `versionstring` : ``str``
130 The numbered version string (like ``"1.1.0"``)
131 It should contain at least three dot separated numbers
132
133 `is_dev` : ``bool``
134 Is it a development version?
135
136 `revision` : ``int``
137 SVN Revision
138
139 :Return: New version instance
140 :Rtype: `version`
141 """
142
143 tup = []
144 versionstring = versionstring.strip()
145 if versionstring:
146 for item in versionstring.split('.'):
147 try:
148 item = int(item)
149 except ValueError:
150 pass
151 tup.append(item)
152 while len(tup) < 3:
153 tup.append(0)
154 return tuple.__new__(cls, tup)
155
156 - def __init__(self, versionstring, is_dev, revision):
157 """
158 Initialization
159
160 :Parameters:
161 `versionstring` : ``str``
162 The numbered version string (like ``1.1.0``)
163 It should contain at least three dot separated numbers
164
165 `is_dev` : ``bool``
166 Is it a development version?
167
168 `revision` : ``int``
169 SVN Revision
170 """
171
172 super(Version, self).__init__()
173 self.major, self.minor, self.patch = self[:3]
174 self.is_dev = bool(is_dev)
175 self.revision = int(revision)
176
178 """
179 Create a development string representation
180
181 :Return: The string representation
182 :Rtype: ``str``
183 """
184 return "%s.%s(%r, is_dev=%r, revision=%r)" % (
185 self.__class__.__module__,
186 self.__class__.__name__,
187 ".".join(map(str, self)),
188 self.is_dev,
189 self.revision,
190 )
191
193 """
194 Create a version like string representation
195
196 :Return: The string representation
197 :Rtype: ``str``
198 """
199 return "%s%s" % (
200 ".".join(map(str, self)),
201 ("", "-dev-r%d" % self.revision)[self.is_dev],
202 )
203
205 """
206 Create a version like unicode representation
207
208 :Return: The unicode representation
209 :Rtype: ``unicode``
210 """
211 return str(self).decode('ascii')
212
215 """
216 Determine all public names in space
217
218 :Parameters:
219 `space` : ``dict``
220 Name space to inspect
221
222 :Return: List of public names
223 :Rtype: ``list``
224 """
225 if space.has_key('__all__'):
226 return list(space['__all__'])
227 return [key for key in space.keys() if not key.startswith('_')]
228
231 """
232 Property with improved docs handling
233
234 :Parameters:
235 `func` : ``callable``
236 The function providing the property parameters. It takes no arguments
237 as returns a dict containing the keyword arguments to be defined for
238 ``property``. The documentation is taken out the function by default,
239 but can be overridden in the returned dict.
240
241 :Return: The requested property
242 :Rtype: ``property``
243 """
244 kwargs = func()
245 kwargs.setdefault('doc', func.__doc__)
246 kwargs = kwargs.get
247 return property(
248 fget=kwargs('fget'),
249 fset=kwargs('fset'),
250 fdel=kwargs('fdel'),
251 doc=kwargs('doc'),
252 )
253
256 """
257 Create decorator for designating decorators.
258
259 :Parameters:
260 `decorated` : function
261 Function to decorate
262
263 `extra` : ``dict``
264 Dict of consumed keyword parameters (not existing in the originally
265 decorated function), mapping to their defaults. If omitted or
266 ``None``, no extra keyword parameters are consumed. The arguments
267 must be consumed by the actual decorator function.
268
269 :Return: Decorator
270 :Rtype: ``callable``
271 """
272
273 def flat_names(args):
274 """ Create flat list of argument names """
275 for arg in args:
276 if isinstance(arg, basestring):
277 yield arg
278 else:
279 for arg in flat_names(arg):
280 yield arg
281 name = decorated.__name__
282 try:
283 dargspec = argspec = _inspect.getargspec(decorated)
284 except TypeError:
285 dargspec = argspec = ([], 'args', 'kwargs', None)
286 if extra:
287 keys = extra.keys()
288 argspec[0].extend(keys)
289 defaults = list(argspec[3] or ())
290 for key in keys:
291 defaults.append(extra[key])
292 argspec = (argspec[0], argspec[1], argspec[2], defaults)
293
294
295
296
297 counter, proxy_name = -1, 'proxy'
298 names = dict.fromkeys(flat_names(argspec[0]))
299 names[name] = None
300 while proxy_name in names:
301 counter += 1
302 proxy_name = 'proxy%s' % counter
303
304 def inner(decorator):
305 """ Actual decorator """
306
307 space = {proxy_name: decorator}
308 if argspec[3]:
309 kwnames = argspec[0][-len(argspec[3]):]
310 else:
311 kwnames = None
312 passed = _inspect.formatargspec(argspec[0], argspec[1], argspec[2],
313 kwnames, formatvalue=lambda value: '=' + value
314 )
315
316 exec "def %s%s: return %s%s" % (
317 name, _inspect.formatargspec(*argspec), proxy_name, passed
318 ) in space
319 wrapper = space[name]
320 wrapper.__dict__ = decorated.__dict__
321 wrapper.__doc__ = decorated.__doc__
322 if extra and decorated.__doc__ is not None:
323 if not decorated.__doc__.startswith('%s(' % name):
324 wrapper.__doc__ = "%s%s\n\n%s" % (
325 name,
326 _inspect.formatargspec(*dargspec),
327 decorated.__doc__,
328 )
329 return wrapper
330
331 return inner
332
335 """
336 Deprecation proxy class
337
338 The class basically emits a deprecation warning on access.
339
340 :IVariables:
341 `__todeprecate` : any
342 Object to deprecate
343
344 `__warn` : ``callable``
345 Warn function
346 """
347 - def __new__(cls, todeprecate, message=None):
348 """
349 Construct
350
351 :Parameters:
352 `todeprecate` : any
353 Object to deprecate
354
355 `message` : ``str``
356 Custom message. If omitted or ``None``, a default message is
357 generated.
358
359 :Return: Deprecator instance
360 :Rtype: `Deprecator`
361 """
362
363 if type(todeprecate) is _types.MethodType:
364 call = cls(todeprecate.im_func, message=message)
365 @decorating(todeprecate.im_func)
366 def func(*args, **kwargs):
367 """ Wrapper to build a new method """
368 return call(*args, **kwargs)
369 return _types.MethodType(func, None, todeprecate.im_class)
370 elif cls == Deprecator and callable(todeprecate):
371 res = CallableDeprecator(todeprecate, message=message)
372 if type(todeprecate) is _types.FunctionType:
373 res = decorating(todeprecate)(res)
374 return res
375 return object.__new__(cls)
376
377 - def __init__(self, todeprecate, message=None):
378 """
379 Initialization
380
381 :Parameters:
382 `todeprecate` : any
383 Object to deprecate
384
385 `message` : ``str``
386 Custom message. If omitted or ``None``, a default message is
387 generated.
388 """
389 self.__todeprecate = todeprecate
390 if message is None:
391 if type(todeprecate) is _types.FunctionType:
392 name = todeprecate.__name__
393 else:
394 name = todeprecate.__class__.__name__
395 message = "%s.%s is deprecated." % (todeprecate.__module__, name)
396 if _os.environ.get('EPYDOC_INSPECTOR') == '1':
397 def warn():
398 """ Dummy to not clutter epydoc output """
399 pass
400 else:
401 def warn():
402 """ Emit the message """
403 _warnings.warn(message,
404 category=_exceptions.DeprecationWarning, stacklevel=3
405 )
406 self.__warn = warn
407
409 """ Get attribute with deprecation warning """
410 self.__warn()
411 return getattr(self.__todeprecate, name)
412
414 """ Get iterator with deprecation warning """
415 self.__warn()
416 return iter(self.__todeprecate)
417
420 """ Callable proxy deprecation class """
421
423 """ Call with deprecation warning """
424 self._Deprecator__warn()
425 return self._Deprecator__todeprecate(*args, **kwargs)
426
429 """
430 Load a dotted name
431
432 The dotted name can be anything, which is passively resolvable
433 (i.e. without the invocation of a class to get their attributes or
434 the like). For example, `name` could be 'tdi.util.load_dotted'
435 and would return this very function. It's assumed that the first
436 part of the `name` is always is a module.
437
438 :Parameters:
439 `name` : ``str``
440 The dotted name to load
441
442 :Return: The loaded object
443 :Rtype: any
444
445 :Exceptions:
446 - `ImportError` : A module in the path could not be loaded
447 """
448 components = name.split('.')
449 path = [components.pop(0)]
450 obj = __import__(path[0])
451 while components:
452 comp = components.pop(0)
453 path.append(comp)
454 try:
455 obj = getattr(obj, comp)
456 except AttributeError:
457 __import__('.'.join(path))
458 try:
459 obj = getattr(obj, comp)
460 except AttributeError:
461 raise ImportError('.'.join(path))
462
463 return obj
464
467 """
468 Generate a dotted module
469
470 :Parameters:
471 `name` : ``str``
472 Fully qualified module name (like ``tdi.util``)
473
474 :Return: The module object of the last part and the information whether
475 the last part was newly added (``(module, bool)``)
476 :Rtype: ``tuple``
477
478 :Exceptions:
479 - `ImportError` : The module name was horribly invalid
480 """
481 sofar, parts = [], name.split('.')
482 oldmod = None
483 for part in parts:
484 if not part:
485 raise ImportError("Invalid module name %r" % (name,))
486 partname = ".".join(sofar + [part])
487 try:
488 fresh, mod = False, load_dotted(partname)
489 except ImportError:
490 mod = _imp.new_module(partname)
491 mod.__path__ = []
492 fresh = mod == _sys.modules.setdefault(partname, mod)
493 if oldmod is not None:
494 setattr(oldmod, part, mod)
495 oldmod = mod
496 sofar.append(part)
497
498 return mod, fresh
499