Package tdi :: Module util
[frames] | no frames]

Source Code for Module tdi.util

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2006 - 2013 
  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  ================ 
 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 
36 37 38 -def _make_parse_content_type():
39 """ 40 Make content type parser 41 42 :Return: parse_content_type 43 :Rtype: ``callable`` 44 """ 45 # These are a bit more lenient than RFC 2045. 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): # pylint: disable = W0621 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()
101 102 103 -class Version(tuple):
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 # pylint: disable = W0613 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 # pylint: disable = W0613 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
177 - def __repr__(self):
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
192 - def __str__(self):
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
204 - def __unicode__(self):
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
213 214 -def find_public(space):
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
229 230 -def Property(func): # pylint: disable = C0103
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
254 255 -def decorating(decorated, extra=None):
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 # pylint: disable = R0912 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 # assign a name for the proxy function. 295 # Make sure it's not already used for something else (function 296 # name or argument) 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 # Compile wrapper function 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 # pylint: disable = W0122 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
333 334 -class Deprecator(object):
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 # pylint: disable = W0613 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) # pylint: disable = E1102
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
408 - def __getattr__(self, name):
409 """ Get attribute with deprecation warning """ 410 self.__warn() 411 return getattr(self.__todeprecate, name)
412
413 - def __iter__(self):
414 """ Get iterator with deprecation warning """ 415 self.__warn() 416 return iter(self.__todeprecate)
417
418 419 -class CallableDeprecator(Deprecator):
420 """ Callable proxy deprecation class """ 421
422 - def __call__(self, *args, **kwargs):
423 """ Call with deprecation warning """ 424 self._Deprecator__warn() 425 return self._Deprecator__todeprecate(*args, **kwargs)
426
427 428 -def load_dotted(name):
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
465 466 -def make_dotted(name):
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