Package wtf :: Module util
[hide private]
[frames] | no frames]

Source Code for Module wtf.util

   1  # -*- coding: ascii -*- 
   2  # 
   3  # Copyright 2006-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  Common Utilities 
  19  ================ 
  20   
  21  Certain utilities to make the life more easy. 
  22  """ 
  23  __author__ = u"Andr\xe9 Malo" 
  24  __docformat__ = "restructuredtext en" 
  25   
  26  import imp as _imp 
  27  import inspect as _inspect 
  28  import keyword as _keyword 
  29  import os as _os 
  30  import re as _re 
  31  import sys as _sys 
  32  import traceback as _traceback 
  33  import warnings as _warnings 
  34  import weakref as _weakref 
  35   
  36  from wtf import WtfWarning 
  37   
  38   
39 -class ImportWarning(WtfWarning): # pylint: disable = W0622
40 """ A package import failed, but is configured to not be fatal """ 41 42
43 -def decorating(decorated, extra=None, skip=0):
44 """ 45 Create decorator for designating decorators. 46 47 :Parameters: 48 `decorated` : function 49 Function to decorate 50 51 `extra` : ``dict`` 52 Dict of consumed keyword parameters (not existing in the originally 53 decorated function), mapping to their defaults. If omitted or 54 ``None``, no extra keyword parameters are consumed. The arguments 55 must be consumed by the actual decorator function. 56 57 `skip` : ``int`` 58 Skip positional parameters at the beginning 59 60 :Return: Decorator 61 :Rtype: ``callable`` 62 """ 63 # pylint: disable = R0912 64 idmatch = _re.compile(r'[a-zA-Z_][a-zA-Z_\d]*$').match 65 def flat_names(args): 66 """ Create flat list of argument names """ 67 for arg in args: 68 if isinstance(arg, basestring): 69 yield arg 70 else: 71 for arg in flat_names(arg): 72 yield arg
73 try: 74 name = decorated.__name__ 75 except AttributeError: 76 if isinstance(decorated, type): 77 name = decorated.__class__.__name__ 78 decorated = decorated.__init__ 79 else: 80 name = decorated.__class__.__name__ 81 decorated = decorated.__call__ 82 oname = name 83 if not idmatch(name) or _keyword.iskeyword(name): 84 name = 'unknown_name' 85 86 try: 87 dargspec = argspec = _inspect.getargspec(decorated) 88 except TypeError: 89 dargspec = argspec = ([], 'args', 'kwargs', None) 90 if skip: 91 argspec[0][:skip] = [] 92 if extra: 93 keys = extra.keys() 94 argspec[0].extend(keys) 95 defaults = list(argspec[3] or ()) 96 for key in keys: 97 defaults.append(extra[key]) 98 argspec = (argspec[0], argspec[1], argspec[2], defaults) 99 100 # assign a name for the proxy function. 101 # Make sure it's not already used for something else (function 102 # name or argument) 103 counter, proxy_name = -1, 'proxy' 104 names = dict.fromkeys(flat_names(argspec[0])) 105 names[name] = None 106 while proxy_name in names: 107 counter += 1 108 proxy_name = 'proxy%s' % counter 109 110 def inner(decorator): 111 """ Actual decorator """ 112 # Compile wrapper function 113 space = {proxy_name: decorator} 114 if argspec[3]: 115 kwnames = argspec[0][-len(argspec[3]):] 116 else: 117 kwnames = None 118 passed = _inspect.formatargspec(argspec[0], argspec[1], argspec[2], 119 kwnames, formatvalue=lambda value: '=' + value 120 ) 121 # pylint: disable = W0122 122 exec "def %s%s: return %s%s" % ( 123 name, _inspect.formatargspec(*argspec), proxy_name, passed 124 ) in space 125 wrapper = space[name] 126 wrapper.__dict__ = decorated.__dict__ 127 wrapper.__doc__ = decorated.__doc__ 128 if extra and decorated.__doc__ is not None: 129 if not decorated.__doc__.startswith('%s(' % oname): 130 wrapper.__doc__ = "%s%s\n\n%s" % ( 131 oname, 132 _inspect.formatargspec(*dargspec), 133 decorated.__doc__, 134 ) 135 return wrapper 136 return inner 137 138
139 -def make_setarg(arg, func):
140 """ 141 Find argument position and create arg setter: 142 143 The signature of the setter function is:: 144 145 def setarg(args, kwargs, value_func): 146 ''' 147 Set argument 148 149 The argument is set only if the `value_func` function says so:: 150 151 def value_func(oldval): 152 ''' 153 Determine argument value 154 155 :Parameters: 156 `oldval` : any 157 Value passed in 158 159 :Return: A tuple containing a boolean if the value is 160 new and should be set (vs. to leave the 161 passed-in value) and the final value 162 :Rtype: ``tuple`` 163 ''' 164 165 :Parameters: 166 `args` : sequence 167 Positional arguments 168 169 `kwargs` : ``dict`` 170 Keyword arguments 171 172 `value_func` : ``callable`` 173 Value function 174 175 :Return: A tuple containing `value_func`'s return value, the 176 new args and the new kwargs. 177 :Rtype: ``tuple`` 178 ''' 179 180 :Parameters: 181 `arg` : ``str`` 182 Argument name 183 184 `func` : ``callable`` 185 Function to call 186 187 :Return: arg setter function(args, kwargs, value_func) 188 :Rtype: ``callable`` 189 """ 190 try: 191 spec = _inspect.getargspec(func) 192 except TypeError: 193 try: 194 if isinstance(func, type): 195 func = func.__init__ 196 else: 197 func = func.__call__ 198 except AttributeError: 199 spec = None 200 try: 201 spec = _inspect.getargspec(func) 202 except TypeError: 203 spec = None 204 205 if spec is not None and arg in spec[0]: 206 idx = spec[0].index(arg) 207 else: 208 idx = None 209 210 def setarg(args, kwargs, value_func): 211 """ 212 Set argument 213 214 The argument is set only if the `value_func` function says so:: 215 216 def value_func(oldval): 217 ''' 218 Determine argument value 219 220 :Parameters: 221 `oldval` : any 222 Value passed in 223 224 :Return: A tuple containing a boolean if the value is new and 225 should be set (vs. to leave the passed-in value) and 226 the final value 227 :Rtype: ``tuple`` 228 ''' 229 230 :Parameters: 231 `args` : sequence 232 Positional arguments 233 234 `kwargs` : ``dict`` 235 Keyword arguments 236 237 `value_func` : ``callable`` 238 Value function 239 240 :Return: A tuple containing `value_func`'s return value, the new args 241 and the new kwargs. 242 :Rtype: ``tuple`` 243 """ 244 if idx is None or idx >= len(args): 245 kwargs = kwargs.copy() 246 created, value = value_func(kwargs.get(arg)) 247 if created: 248 kwargs[arg] = value 249 else: 250 created, value = value_func(args[idx]) 251 if created: 252 args = list(args) 253 args[idx] = value 254 args = tuple(args) 255 return (created, value), args, kwargs
256 257 return setarg 258 259
260 -def load_dotted(name):
261 """ 262 Load a dotted name 263 264 The dotted name can be anything, which is passively resolvable 265 (i.e. without the invocation of a class to get their attributes or 266 the like). For example, `name` could be 'wtf.util.load_dotted' 267 and would return this very function. It's assumed that the first 268 part of the `name` is always is a module. 269 270 :Parameters: 271 - `name`: The dotted name to load 272 273 :Types: 274 - `name`: ``str`` 275 276 :return: The loaded object 277 :rtype: any 278 279 :Exceptions: 280 - `ImportError`: A module in the path could not be loaded 281 """ 282 components = name.split('.') 283 path = [components.pop(0)] 284 obj = __import__(path[0]) 285 while components: 286 comp = components.pop(0) 287 path.append(comp) 288 try: 289 obj = getattr(obj, comp) 290 except AttributeError: 291 __import__('.'.join(path)) 292 try: 293 obj = getattr(obj, comp) 294 except AttributeError: 295 raise ImportError('.'.join(path)) 296 297 return obj
298 299
300 -def make_dotted(name):
301 """ 302 Generate a dotted module 303 304 :Parameters: 305 - `name`: Fully qualified module name (like `wtf.services`) 306 307 :Types: 308 - `name`: ``str`` 309 310 :return: The module object of the last part and the information whether 311 the last part was newly added (``(module, bool)``) 312 :rtype: ``tuple`` 313 314 :Exceptions: 315 - `ImportError`: The module name was horribly invalid 316 """ 317 sofar, parts = [], name.split('.') 318 oldmod = None 319 for part in parts: 320 if not part: 321 raise ImportError("Invalid module name %r" % (name,)) 322 partname = ".".join(sofar + [part]) 323 try: 324 fresh, mod = False, load_dotted(partname) 325 except ImportError: 326 mod = _imp.new_module(partname) 327 mod.__path__ = [] 328 fresh = mod == _sys.modules.setdefault(partname, mod) 329 if oldmod is not None: 330 setattr(oldmod, part, mod) 331 oldmod = mod 332 sofar.append(part) 333 334 return mod, fresh
335 336
337 -def walk_package(package, errors='ignore'):
338 """ 339 Collect all modules and subpackages of `package` recursively 340 341 :Parameters: 342 - `package`: The package to inspect, if it's a string the string 343 is interpreted as a python package name and imported first. A failed 344 import of this package cannot be suppressed. 345 - `errors`: What should happen on ``ImportError``s during the crawling 346 process? The following values are recognized: 347 ``ignore``, ``warn``, ``error`` 348 349 :Types: 350 - `package`: ``module`` or ``str`` 351 - `errors`: ``str`` 352 353 :return: Iterator over the modules/packages (including the root package) 354 :rtype: ``iterable`` 355 356 :Exceptions: 357 - `ImportError`: some import failed 358 - `ValueError`: The `errors` value could nto be recognized 359 - `OSError`: Something bad happened while accessing the file system 360 """ 361 # pylint: disable = E0102, E1101, R0912 362 363 self = walk_package 364 365 if isinstance(package, basestring): 366 package = load_dotted(package) 367 368 if errors == 'ignore': 369 errors = lambda: None 370 elif errors == 'warn': 371 def errors(): 372 """ Emit an import warning """ 373 _warnings.warn(''.join( 374 _traceback.format_exception_only(*_sys.exc_info()[:2]), 375 category=ImportWarning 376 ))
377 elif errors == 'error': 378 def errors(): 379 """ raise the import error """ 380 raise 381 else: 382 raise ValueError("`errors` value not recognized") 383 384 modre, pkgre = self.matchers 385 exts = [item[0] for item in _imp.get_suffixes()][::-1] 386 seen = set() 387 def collect(package): 388 """ Collect the package recursively alphabetically """ 389 try: 390 paths = package.__path__ 391 except AttributeError: 392 try: 393 paths = [_os.path.dirname(package.__file__)] 394 except AttributeError: 395 paths = [] 396 yield package 397 398 for basedir in paths: 399 for name in sorted(_os.listdir(basedir)): 400 fullname = _os.path.join(basedir, name) 401 402 # Found a package? 403 if _os.path.isdir(fullname): 404 match = pkgre(name) 405 if not match: 406 continue 407 pkgname = "%s.%s" % ( 408 package.__name__, match.group('name')) 409 if pkgname in seen: 410 continue 411 seen.add(pkgname) 412 # prevent __init__ to be considered a dedicated module 413 seen.add('%s.__init__' % pkgname) 414 try: 415 _imp.find_module('__init__', [fullname]) 416 except ImportError: 417 # no package at all, so no error here 418 continue 419 else: 420 try: 421 pkg = __import__(pkgname, {}, {}, ['*']) 422 except ImportError: 423 errors() 424 continue 425 else: 426 for item in collect(pkg): 427 yield item 428 429 # Found a module? 430 elif _os.path.isfile(fullname): 431 match = modre(name) 432 if match: 433 modname = match.group('name') 434 for ext in exts: 435 if modname.endswith(ext): 436 modname = "%s.%s" % ( 437 package.__name__, modname[:-len(ext)] 438 ) 439 break 440 else: 441 continue 442 if modname in seen: 443 continue 444 seen.add(modname) 445 try: 446 mod = __import__(modname, {}, {}, ['*']) 447 except ImportError: 448 errors() 449 continue 450 else: 451 yield mod 452 return collect(package) 453 walk_package.matchers = ( # pylint: disable = W0612 454 _re.compile( 455 r'(?P<name>[a-zA-Z_][a-zA-Z\d_]*(?:\.[^.]+)?)$' 456 ).match, 457 _re.compile(r'(?P<name>[a-zA-Z_][a-zA-Z\d_]*)$').match, 458 ) 459 460 461 hpre = ( 462 _re.compile(ur'(?P<ip>[^:]+|\[[^\]]+])(?::(?P<port>\d+))?$').match, 463 _re.compile(ur'(?:(?P<ip>[^:]+|\[[^\]]+]|\*):)?(?P<port>\d+)$').match, 464 _re.compile(ur'(?P<ip>[^:]+|\[[^\]]+]|\*)(?::(?P<port>\d+))?$').match, 465 )
466 -def parse_socket_spec(spec, default_port=None, any=False, _hpre=hpre):
467 """ 468 Parse a socket specification 469 470 This is either ``u'host:port'`` or ``u'/foo/bar'``. The latter (`spec` 471 containing a slash) specifies a UNIX domain socket and will be 472 transformed to a string according to the inherited locale setting. 473 It may be a string initially, too. 474 475 For internet sockets, the port is optional (will be `default_port` then). 476 If `any` is true, the host may point to ``ANY``. That is: the host is 477 ``u'*'`` or the port stands completely alone (e.g. ``u'80'``). 478 Hostnames will be IDNA encoded. 479 480 :Parameters: 481 - `spec`: The socket spec 482 - `default_port`: The default port to apply 483 - `any`: Allow host resolve to ``ANY``? 484 485 :Types: 486 - `spec`: ``basestring`` 487 - `default_port`: ``int`` 488 - `any`: ``bool`` 489 490 :return: The determined spec. It may be a string (for UNIX sockets) or a 491 tuple of host and port for internet sockets. (``('host', port)``) 492 :rtype: ``tuple`` or ``str`` 493 494 :Exceptions: 495 - `ValueError`: Unparsable spec 496 """ 497 # pylint: disable = W0622 498 499 if isinstance(spec, str): 500 if '/' in spec: 501 return spec 502 spec = spec.decode('ascii') 503 elif u'/' in spec: 504 encoding = _sys.getfilesystemencoding() or 'utf-8' 505 return spec.encode(encoding) 506 507 match = _hpre[bool(any)](spec) 508 if match is None and any: 509 match = _hpre[2](spec) 510 if match is None: 511 raise ValueError("Unrecognized socket spec: %r" % (spec,)) 512 host, port = match.group('ip', 'port') 513 if any: 514 if not host or host == u'*': 515 host = None 516 if host is not None: 517 host = host.encode('idna') 518 if host.startswith('[') and host.endswith(']'): # IPv6 519 host = host[1:-1] 520 if not port: 521 port = default_port 522 else: 523 port = int(port) 524 return (host, port)
525 526 del hpre 527 528
529 -class BaseDecorator(object):
530 """ 531 Base decorator class 532 533 Implement the `__call__` method in order to add some action. 534 535 :IVariables: 536 - `_func`: The decorated function 537 - `__name__`: The "official" name of the function with decorator 538 - `__doc__`: Function's doc string 539 540 :Types: 541 - `_func`: ``callable`` 542 - `__name__`: ``str`` 543 - `__doc__`: ``basestring`` 544 """ 545
546 - def __init__(self, func):
547 """ 548 Initialization 549 550 :Parameters: 551 - `func`: The callable to decorate 552 553 :Types: 554 - `func`: ``callable`` 555 """ 556 self._func = func 557 try: 558 name = func.__name__ 559 except AttributeError: 560 name = repr(func) 561 self.__name__ = "@%s(%s)" % (self.__class__.__name__, name) 562 self.__doc__ = func.__doc__
563
564 - def __get__(self, inst, owner):
565 """ 566 Generic attribute getter (descriptor protocol) 567 568 :Parameters: 569 - `inst`: Object instance 570 - `owner`: Object owner 571 572 :Types: 573 - `inst`: ``object`` 574 - `owner`: ``type`` 575 576 :return: Proxy function which acts for the wrapped method 577 :rtype: ``callable`` 578 """ 579 def proxy(*args, **kwargs): 580 """ Method proxy """ 581 return self(inst, *args, **kwargs)
582 proxy.__name__ = self.__name__ # pylint: disable = W0622 583 proxy.__dict__ = self._func.__dict__ 584 try: 585 proxy.__doc__ = self._func.__doc__ # pylint: disable = W0622 586 except AttributeError: 587 pass 588 return proxy
589
590 - def __getattr__(self, name):
591 """ 592 Pass attribute requests to the function 593 594 :Parameters: 595 - `name`: The name of the attribute 596 597 :Types: 598 - `name`: ``str`` 599 600 :return: The function attribute 601 :rtype: any 602 603 :Exceptions: 604 - `AttributeError`: The attribute was not found 605 """ 606 return getattr(self._func, name)
607
608 - def __call__(self, *args, **kwargs):
609 """ 610 Actual decorating entry point 611 612 :Parameters: 613 - `args`: Positioned parameters 614 - `kwargs`: named parameters 615 616 :Types: 617 - `args`: ``tuple`` 618 - `kwargs`: ``dict`` 619 620 :return: The return value of the decorated function (maybe modified 621 or replaced by the decorator) 622 :rtype: any 623 """ 624 raise NotImplementedError()
625 626
627 -class PooledInterface(object):
628 """ Interface for pooled objects """ 629
630 - def destroy(self):
631 """ 632 Destroy the object 633 634 The method has to advise the pool to forget it. It should call the 635 ``del_obj`` method of the pool for that purpose. In order to achieve 636 that the object needs to store a reference to pool internally. In 637 order to avoid circular references it is wise to store the pool as 638 a weak reference (see ``weakref`` module). 639 """
640 641
642 -class BasePool(object):
643 """ 644 Abstract pool of arbitrary objects 645 646 :CVariables: 647 - `_LOCK`: Locking class. By default this is ``threading.Lock``. However, 648 if the pooled object's initializer or destructor (``.destroy``) are 649 calling back into get_conn or put_conn, it should be ``RLock``. 650 - `_FORK_PROTECT`: Clear the pool, when the PID changes? 651 652 :IVariables: 653 - `_not_empty`: "not empty" condition 654 - `_not_full`: "not full" condition 655 - `_pool`: actual object pool 656 - `_maxout`: Hard maximum number of pooled objects to be handed out 657 - `_maxcached`: Maximum number of pooled objects to be cached 658 - `_obj`: References to handed out objects, Note that the objects need to 659 be hashable 660 - `_pid`: PID of currently handed out and cached objects 661 662 :Types: 663 - `_LOCK`: ``NoneType`` 664 - `_FORK_PROTECT`: ``bool`` 665 - `_not_empty`: ``threading.Condition`` 666 - `_not_full`: ``threading.Condition`` 667 - `_pool`: ``collections.deque`` 668 - `_maxout`: ``int`` 669 - `_maxcached`: ``int`` 670 - `_obj`: ``dict`` 671 - `_pid`: ``int`` 672 """ 673 _LOCK = None 674 _FORK_PROTECT, _pid = False, None 675
676 - def __init__(self, maxout, maxcached):
677 """ Initialization """ 678 import collections as _collections 679 import threading as _threading 680 681 if self._LOCK is None: 682 lock = _threading.Lock() 683 else: 684 lock = self._LOCK() # pylint: disable = E1102 685 self._not_empty = _threading.Condition(lock) 686 self._not_full = _threading.Condition(lock) 687 self._pool = _collections.deque() 688 maxout = max(0, int(maxout)) 689 if maxout: 690 self._maxout = max(1, maxout) 691 self._maxcached = max(0, min(self._maxout, int(maxcached))) 692 else: 693 self._maxout = maxout 694 self._maxcached = max(0, int(maxcached)) 695 self._obj = {} 696 if self._FORK_PROTECT: 697 self._pid = _os.getpid() 698 else: 699 self._fork_protect = lambda: None
700
701 - def __del__(self):
702 """ Destruction """ 703 self.shutdown()
704
705 - def _create(self):
706 """ 707 Create a pooled object 708 709 This method must be implemented by subclasses. 710 711 :return: A new object 712 :rtype: ``PooledInterface`` 713 """ 714 # pylint: disable = E0202 715 716 raise NotImplementedError()
717
718 - def get_obj(self):
719 """ 720 Get a object from the pool 721 722 :return: The new object. If no objects are available in the pool, a 723 new one is created (by calling `_create`). If no object 724 can be created (because of limits), the method blocks. 725 :rtype: `PooledInterface` 726 """ 727 self._not_empty.acquire() 728 try: 729 self._fork_protect() 730 while not self._pool: 731 if not self._maxout or len(self._obj) < self._maxout: 732 obj = self._create() # pylint: disable = E1111 733 try: 734 proxy = _weakref.proxy(obj) 735 except TypeError: 736 proxy = obj 737 self._obj[id(obj)] = proxy 738 break 739 self._not_empty.wait() 740 else: 741 obj = self._pool.pop() 742 self._not_full.notify() 743 return obj 744 finally: 745 self._not_empty.release()
746
747 - def put_obj(self, obj):
748 """ 749 Put an object back into the pool 750 751 If the pool is full, the object is destroyed instead. If the object 752 does not come from this pool, it is an error (``assert``). 753 754 :Parameters: 755 - `obj`: The object to put back 756 757 :Types: 758 - `obj`: `PooledInterface` 759 """ 760 self._not_full.acquire() 761 try: 762 self._fork_protect() 763 if id(obj) in self._obj: 764 if len(self._pool) >= self._maxcached: 765 obj.destroy() 766 else: 767 self._pool.appendleft(obj) 768 self._not_empty.notify() 769 else: 770 obj.destroy() 771 finally: 772 self._not_full.release()
773
774 - def del_obj(self, obj):
775 """ 776 Remove object from pool 777 778 If the object original came not from this pool, this is not an error. 779 780 :Parameters: 781 - `obj`: The object to remove 782 783 :Types: 784 - `obj`: `PooledInterface` 785 """ 786 try: 787 del self._obj[id(obj)] 788 except KeyError: 789 pass
790
791 - def clear(self):
792 """ 793 Clear the currently cached connections 794 795 Connections handed out are not affected. This just empties the cached 796 ones. 797 """ 798 self._not_empty.acquire() 799 try: 800 while self._pool: 801 self._pool.pop().destroy() 802 finally: 803 self._not_empty.release()
804
805 - def shutdown(self):
806 """ 807 Shutdown this pool 808 809 The queue will be emptied and all objects destroyed. No more 810 objects will be created in this pool. Waiting consumers of the pool 811 will get an ``AssertionError``, because they shouldn't consume anymore 812 anyway. 813 """ 814 self._not_full.acquire() 815 try: 816 self._maxcached = 0 817 def create(): 818 """ Raise error for new consumers """ 819 raise AssertionError("Shutdown in progress")
820 self._create = create 821 self._pool.clear() 822 self._obj, obj = {}, self._obj.values() 823 while obj: 824 obj.pop().destroy() 825 self._not_empty.notifyAll() 826 finally: 827 self._not_full.release()
828
829 - def _fork_protect(self): # pylint: disable = E0202
830 """ 831 Check if the current PID differs from the stored one 832 833 If they actually differed, we forked and clear the pool. This 834 function should only be called within a locked environment. 835 """ 836 pid = _os.getpid() 837 if pid != self._pid: 838 while self._pool: 839 self._pool.pop().destroy() 840 self._obj.clear() 841 self._pid = pid 842 843
844 -def hash32(s):
845 """ 846 Replacement for ``str.__hash__`` 847 848 The function is supposed to give identical results on 32 and 64 bit 849 systems. 850 851 :Parameters: 852 - `s`: The string to hash 853 854 :Types: 855 - `s`: ``str`` 856 857 :return: The hash value 858 :rtype: ``int`` 859 """ 860 # pylint: disable = W0613, C0103 861 862 raise NotImplementedError()
863 864
865 -def Property(func): # pylint: disable = C0103
866 """ 867 Property with improved docs handling 868 869 :Parameters: 870 `func` : ``callable`` 871 The function providing the property parameters. It takes no arguments 872 as returns a dict containing the keyword arguments to be defined for 873 ``property``. The documentation is taken out the function by default, 874 but can be overridden in the returned dict. 875 876 :Return: The requested property 877 :Rtype: ``property`` 878 """ 879 kwargs = func() 880 kwargs.setdefault('doc', func.__doc__) 881 kwargs = kwargs.get 882 return property( 883 fget=kwargs('fget'), 884 fset=kwargs('fset'), 885 fdel=kwargs('fdel'), 886 doc=kwargs('doc'), 887 ) 888 889
890 -def find_public(space):
891 """ 892 Determine all public names in space 893 894 :Parameters: 895 `space` : ``dict`` 896 Name space to inspect 897 898 :Return: List of public names 899 :Rtype: ``list`` 900 """ 901 if space.has_key('__all__'): 902 return list(space['__all__']) 903 return [key for key in space.keys() if not key.startswith('_')]
904 905
906 -class Version(tuple):
907 """ 908 Represents the package version 909 910 :IVariables: 911 `major` : ``int`` 912 The major version number 913 914 `minor` : ``int`` 915 The minor version number 916 917 `patch` : ``int`` 918 The patch level version number 919 920 `is_dev` : ``bool`` 921 Is it a development version? 922 923 `revision` : ``int`` 924 SVN Revision 925 """ 926
927 - def __new__(cls, versionstring, is_dev, revision):
928 """ 929 Construction 930 931 :Parameters: 932 `versionstring` : ``str`` 933 The numbered version string (like ``"1.1.0"``) 934 It should contain at least three dot separated numbers 935 936 `is_dev` : ``bool`` 937 Is it a development version? 938 939 `revision` : ``int`` 940 SVN Revision 941 942 :Return: New version instance 943 :Rtype: `version` 944 """ 945 # pylint: disable = W0613 946 947 tup = [] 948 versionstring = versionstring.strip() 949 if versionstring: 950 for item in versionstring.split('.'): 951 try: 952 item = int(item) 953 except ValueError: 954 pass 955 tup.append(item) 956 while len(tup) < 3: 957 tup.append(0) 958 return tuple.__new__(cls, tup)
959
960 - def __init__(self, versionstring, is_dev, revision):
961 """ 962 Initialization 963 964 :Parameters: 965 `versionstring` : ``str`` 966 The numbered version string (like ``1.1.0``) 967 It should contain at least three dot separated numbers 968 969 `is_dev` : ``bool`` 970 Is it a development version? 971 972 `revision` : ``int`` 973 SVN Revision 974 """ 975 # pylint: disable = W0613 976 977 super(Version, self).__init__() 978 self.major, self.minor, self.patch = self[:3] 979 self.is_dev = bool(is_dev) 980 self.revision = int(revision)
981
982 - def __repr__(self):
983 """ 984 Create a development string representation 985 986 :Return: The string representation 987 :Rtype: ``str`` 988 """ 989 return "%s.%s(%r, is_dev=%r, revision=%r)" % ( 990 self.__class__.__module__, 991 self.__class__.__name__, 992 ".".join(map(str, self)), 993 self.is_dev, 994 self.revision, 995 )
996
997 - def __str__(self):
998 """ 999 Create a version like string representation 1000 1001 :Return: The string representation 1002 :Rtype: ``str`` 1003 """ 1004 return "%s%s" % ( 1005 ".".join(map(str, self)), 1006 ("", "-dev-r%d" % self.revision)[self.is_dev], 1007 )
1008
1009 - def __unicode__(self):
1010 """ 1011 Create a version like unicode representation 1012 1013 :Return: The unicode representation 1014 :Rtype: ``unicode`` 1015 """ 1016 return str(self).decode('ascii')
1017 1018 1019 from wtf import c_override 1020 cimpl = c_override('_wtf_cutil') 1021 if cimpl is not None: 1022 # pylint: disable = E1103 1023 hash32 = cimpl.hash32 1024 del c_override, cimpl 1025