Package Skype4Py :: Module utils
[frames] | no frames]

Source Code for Module Skype4Py.utils

  1  """Utility functions and classes used internally by Skype4Py. 
  2  """ 
  3  __docformat__ = 'restructuredtext en' 
  4   
  5   
  6  import sys 
  7  import weakref 
  8  import threading 
  9  import logging 
 10  from new import instancemethod 
 11   
 12   
 13  __all__ = ['tounicode', 'path2unicode', 'unicode2path', 'chop', 'args2dict', 'quote', 
 14             'split', 'cndexp', 'EventHandlingBase', 'Cached', 'CachedCollection'] 
15 16 17 -def tounicode(s):
18 """Converts a string to a unicode string. Accepts two types or arguments. An UTF-8 encoded 19 byte string or a unicode string (in the latter case, no conversion is performed). 20 21 :Parameters: 22 s : str or unicode 23 String to convert to unicode. 24 25 :return: A unicode string being the result of the conversion. 26 :rtype: unicode 27 """ 28 if isinstance(s, unicode): 29 return s 30 return str(s).decode('utf-8')
31
32 33 -def path2unicode(path):
34 """Decodes a file/directory path from the current file system encoding to unicode. 35 36 :Parameters: 37 path : str 38 Encoded path. 39 40 :return: Decoded path. 41 :rtype: unicode 42 """ 43 return path.decode(sys.getfilesystemencoding())
44
45 46 -def unicode2path(path):
47 """Encodes a file/directory path from unicode to the current file system encoding. 48 49 :Parameters: 50 path : unicode 51 Decoded path. 52 53 :return: Encoded path. 54 :rtype: str 55 """ 56 return path.encode(sys.getfilesystemencoding())
57
58 59 -def chop(s, n=1, d=None):
60 """Chops initial words from a string and returns a list of them and the rest of the string. 61 The returned list is guaranteed to be n+1 long. If too little words are found in the string, 62 a ValueError exception is raised. 63 64 :Parameters: 65 s : str or unicode 66 String to chop from. 67 n : int 68 Number of words to chop. 69 d : str or unicode 70 Optional delimiter. Any white-char by default. 71 72 :return: A list of n first words from the string followed by the rest of the string (``[w1, w2, 73 ..., wn, rest_of_string]``). 74 :rtype: list of: str or unicode 75 """ 76 77 spl = s.split(d, n) 78 if len(spl) == n: 79 spl.append(s[:0]) 80 if len(spl) != n + 1: 81 raise ValueError('chop: Could not chop %d words from \'%s\'' % (n, s)) 82 return spl
83
84 85 -def args2dict(s):
86 """Converts a string or comma-separated 'ARG="a value"' or 'ARG=value2' strings 87 into a dictionary. 88 89 :Parameters: 90 s : str or unicode 91 Input string. 92 93 :return: ``{'ARG': 'value'}`` dictionary. 94 :rtype: dict 95 """ 96 97 d = {} 98 while s: 99 t, s = chop(s, 1, '=') 100 if s.startswith('"'): 101 # XXX: This function is used to parse strings from Skype. The question is, 102 # how does Skype escape the double-quotes. The code below implements the 103 # VisualBasic technique ("" -> "). 104 i = 0 105 while True: 106 i = s.find('"', i+1) 107 try: 108 if s[i+1] != '"': 109 break 110 else: 111 i += 1 112 except IndexError: 113 break 114 if i > 0: 115 d[t] = s[1:i].replace('""', '"') 116 if s[i+1:i+3] == ', ': 117 i += 2 118 s = s[i+1:] 119 else: 120 d[t] = s 121 break 122 else: 123 i = s.find(', ') 124 if i >= 0: 125 d[t] = s[:i] 126 s = s[i+2:] 127 else: 128 d[t] = s 129 break 130 return d
131
132 133 -def quote(s, always=False):
134 """Adds double-quotes to string if it contains spaces. 135 136 :Parameters: 137 s : str or unicode 138 String to add double-quotes to. 139 always : bool 140 If True, adds quotes even if the input string contains no spaces. 141 142 :return: If the given string contains spaces or <always> is True, returns the string enclosed in 143 double-quotes. Otherwise returns the string unchanged. 144 :rtype: str or unicode 145 """ 146 147 if always or ' ' in s: 148 return '"%s"' % s.replace('"', '""') # VisualBasic double-quote escaping. 149 return s
150
151 152 -def split(s, d=None):
153 """Splits a string. 154 155 :Parameters: 156 s : str or unicode 157 String to split. 158 d : str or unicode 159 Optional delimiter. Any white-char by default. 160 161 :return: A list of words or ``[]`` if the string was empty. 162 :rtype: list of str or unicode 163 164 :note: This function works like ``s.split(d)`` except that it always returns an empty list 165 instead of ``['']`` for empty strings. 166 """ 167 168 if s: 169 return s.split(d) 170 return []
171
172 173 -def cndexp(condition, truevalue, falsevalue):
174 """Simulates a conditional expression known from C or Python 2.5. 175 176 :Parameters: 177 condition : any 178 Tells what should be returned. 179 truevalue : any 180 Value returned if condition evaluates to True. 181 falsevalue : any 182 Value returned if condition evaluates to False. 183 184 :return: Either truevalue or falsevalue depending on condition. 185 :rtype: same as type of truevalue or falsevalue 186 """ 187 188 if condition: 189 return truevalue 190 return falsevalue
191
192 193 -class EventSchedulerThread(threading.Thread):
194 - def __init__(self, name, after, handlers, args, kwargs):
195 """Initializes the object. 196 197 :Parameters: 198 name : str 199 Event name. 200 after : threading.Thread or None 201 If not None, a thread that needs to end before this 202 one starts. 203 handlers : iterable 204 Iterable of callable event handlers. 205 args : tuple 206 Positional arguments for the event handlers. 207 kwargs : dict 208 Keyword arguments for the event handlers. 209 210 :note: When the thread is started (using the ``start`` method), it iterates over 211 the handlers and calls them with the supplied arguments. 212 """ 213 threading.Thread.__init__(self, name='Skype4Py %s event scheduler' % name) 214 self.setDaemon(False) 215 self.after = after 216 self.handlers = handlers 217 self.args = args 218 self.kwargs = kwargs
219
220 - def run(self):
221 if self.after: 222 self.after.join() 223 self.after = None # Remove the reference. 224 for handler in self.handlers: 225 handler(*self.args, **self.kwargs)
226
227 228 -class EventHandlingBase(object):
229 """This class is used as a base by all classes implementing event handlers. 230 231 Look at known subclasses (above in epydoc) to see which classes will allow you to 232 attach your own callables (event handlers) to certain events occurring in them. 233 234 Read the respective classes documentations to learn what events are provided by them. The 235 events are always defined in a class whose name consist of the name of the class it provides 236 events for followed by ``Events``). For example class `Skype` provides events defined in 237 `SkypeEvents`. The events class is always defined in the same module as the main class. 238 239 The events class tells you what events you can assign your event handlers to, when do they 240 occur and what arguments should your event handlers accept. 241 242 There are three ways of attaching an event handler to an event. 243 244 ``Events`` object 245 ================= 246 247 Write your event handlers as methods of a class. The superclass of your class 248 is not important for Skype4Py, it will just look for methods with appropriate names. 249 The names of the methods and their arguments lists can be found in respective events 250 classes (see above). 251 252 Pass an instance of this class as the ``Events`` argument to the constructor of 253 a class whose events you are interested in. For example: 254 255 .. python:: 256 257 import Skype4Py 258 259 class MySkypeEvents: 260 def UserStatus(self, Status): 261 print 'The status of the user changed' 262 263 skype = Skype4Py.Skype(Events=MySkypeEvents()) 264 265 If your application is build around a class, you may want to use is for Skype4Py 266 events too. For example: 267 268 .. python:: 269 270 import Skype4Py 271 272 class MyApplication: 273 def __init__(self): 274 self.skype = Skype4Py.Skype(Events=self) 275 276 def UserStatus(self, Status): 277 print 'The status of the user changed' 278 279 This lets you access the `Skype` object (``self.skype``) without using global 280 variables. 281 282 In both examples, the ``UserStatus`` method will be called when the status of the 283 user currently logged into Skype is changed. 284 285 ``On...`` properties 286 ==================== 287 288 This method lets you use any callables as event handlers. Simply assign them to ``On...`` 289 properties (where "``...``" is the name of the event) of the object whose events you are 290 interested in. For example: 291 292 .. python:: 293 294 import Skype4Py 295 296 def user_status(Status): 297 print 'The status of the user changed' 298 299 skype = Skype4Py.Skype() 300 skype.OnUserStatus = user_status 301 302 The ``user_status`` function will be called when the status of the user currently logged 303 into Skype is changed. 304 305 The names of the events and their arguments lists can be found in respective events 306 classes (see above). Note that there is no ``self`` argument (which can be seen in the events 307 classes) simply because our event handler is a function, not a method. 308 309 ``RegisterEventHandler`` / ``UnregisterEventHandler`` methods 310 ============================================================= 311 312 This method, like the second one, also lets you use any callables as event handlers. However, 313 it also lets you assign many event handlers to a single event. This may be useful if for 314 example you need to momentarily attach an event handler without disturbing other parts of 315 your code already using one of the above two methods. 316 317 In this case, you use `RegisterEventHandler` and `UnregisterEventHandler` methods 318 of the object whose events you are interested in. For example: 319 320 .. python:: 321 322 import Skype4Py 323 324 def user_status(Status): 325 print 'The status of the user changed' 326 327 skype = Skype4Py.Skype() 328 skype.RegisterEventHandler('UserStatus', user_status) 329 330 The ``user_status`` function will be called when the status of the user currently logged 331 into Skype is changed. 332 333 The names of the events and their arguments lists should be taken from respective events 334 classes (see above). Note that there is no ``self`` argument (which can be seen in the events 335 classes) simply because our event handler is a function, not a method. 336 337 All handlers attached to a single event will be called serially in the order they were 338 registered. 339 340 Multithreading warning 341 ====================== 342 343 All event handlers are called on separate threads, never on the main one. At any given time, 344 there is at most one thread per event calling your handlers. This means that when many events 345 of the same type occur at once, the handlers will be called one after another. Different events 346 will be handled simultaneously. 347 348 Cyclic references note 349 ====================== 350 351 Prior to Skype4Py 1.0.32.0, the library used weak references to the handlers. This was removed 352 to avoid confusion and simplify/speed up the code. If cyclic references do occur, they are 353 expected to be removed by the Python's garbage collector which should always be present as 354 the library is expected to work in a relatively resource rich environment which is needed 355 by the Skype client anyway. 356 """ 357 # Initialized by the _AddEvents() class method. 358 _EventNames = [] 359
360 - def __init__(self):
361 """Initializes the object. 362 """ 363 # Event -> EventSchedulerThread object mapping. Use WeakValueDictionary to let the 364 # threads be freed after they are finished. 365 self._EventThreads = weakref.WeakValueDictionary() 366 self._EventHandlerObject = None # Current "Events" object. 367 self._DefaultEventHandlers = {} # "On..." handlers. 368 self._EventHandlers = {} # "RegisterEventHandler" handlers. 369 self.__Logger = logging.getLogger('Skype4Py.utils.EventHandlingBase') 370 371 # Initialize the _EventHandlers mapping. 372 for event in self._EventNames: 373 self._EventHandlers[event] = []
374
375 - def _CallEventHandler(self, Event, *Args, **KwArgs):
376 """Calls all event handlers defined for given Event, additional parameters 377 will be passed unchanged to event handlers, all event handlers are fired on 378 separate threads. 379 380 :Parameters: 381 Event : str 382 Name of the event. 383 Args 384 Positional arguments for the event handlers. 385 KwArgs 386 Keyword arguments for the event handlers. 387 """ 388 if Event not in self._EventHandlers: 389 raise ValueError('%s is not a valid %s event name' % (Event, self.__class__.__name__)) 390 args = map(repr, Args) + ['%s=%s' % (key, repr(value)) for key, value in KwArgs.items()] 391 self.__Logger.debug('calling %s: %s', Event, ', '.join(args)) 392 # Get a list of handlers for this event. 393 try: 394 handlers = [self._DefaultEventHandlers[Event]] 395 except KeyError: 396 handlers = [] 397 try: 398 handlers.append(getattr(self._EventHandlerObject, Event)) 399 except AttributeError: 400 pass 401 handlers.extend(self._EventHandlers[Event]) 402 # Proceed only if there are handlers. 403 if handlers: 404 # Get the last thread for this event. 405 after = self._EventThreads.get(Event, None) 406 # Create a new thread, pass the last one to it so it can wait until it is finished. 407 thread = EventSchedulerThread(Event, after, handlers, Args, KwArgs) 408 # Store a weak reference to the new thread for this event. 409 self._EventThreads[Event] = thread 410 # Start the thread. 411 thread.start()
412
413 - def RegisterEventHandler(self, Event, Target):
414 """Registers any callable as an event handler. 415 416 :Parameters: 417 Event : str 418 Name of the event. For event names, see the respective ``...Events`` class. 419 Target : callable 420 Callable to register as the event handler. 421 422 :return: True is callable was successfully registered, False if it was already registered. 423 :rtype: bool 424 425 :see: `UnregisterEventHandler` 426 """ 427 if not callable(Target): 428 raise TypeError('%s is not callable' % repr(Target)) 429 if Event not in self._EventHandlers: 430 raise ValueError('%s is not a valid %s event name' % (Event, self.__class__.__name__)) 431 if Target in self._EventHandlers[Event]: 432 return False 433 self._EventHandlers[Event].append(Target) 434 self.__Logger.info('registered %s: %s', Event, repr(Target)) 435 return True
436
437 - def UnregisterEventHandler(self, Event, Target):
438 """Unregisters an event handler previously registered with `RegisterEventHandler`. 439 440 :Parameters: 441 Event : str 442 Name of the event. For event names, see the respective ``...Events`` class. 443 Target : callable 444 Callable to unregister. 445 446 :return: True if callable was successfully unregistered, False if it wasn't registered 447 first. 448 :rtype: bool 449 450 :see: `RegisterEventHandler` 451 """ 452 if not callable(Target): 453 raise TypeError('%s is not callable' % repr(Target)) 454 if Event not in self._EventHandlers: 455 raise ValueError('%s is not a valid %s event name' % (Event, self.__class__.__name__)) 456 if Target in self._EventHandlers[Event]: 457 self._EventHandlers[Event].remove(Target) 458 self.__Logger.info('unregistered %s: %s', Event, repr(Target)) 459 return True 460 return False
461
462 - def _SetDefaultEventHandler(self, Event, Target):
463 if Target: 464 if not callable(Target): 465 raise TypeError('%s is not callable' % repr(Target)) 466 self._DefaultEventHandlers[Event] = Target 467 self.__Logger.info('set default %s: %s', Event, repr(Target)) 468 else: 469 try: 470 del self._DefaultEventHandlers[Event] 471 except KeyError: 472 pass
473
474 - def _GetDefaultEventHandler(self, Event):
475 try: 476 return self._DefaultEventHandlers[Event] 477 except KeyError: 478 return None
479
480 - def _SetEventHandlerObject(self, Object):
481 """Registers an object as events handler, object should contain methods with names 482 corresponding to event names, only one object may be registered at a time. 483 484 :Parameters: 485 Object 486 Object to register. May be None in which case the currently registered object 487 will be unregistered. 488 """ 489 self._EventHandlerObject = Object 490 self.__Logger.info('set object: %s', repr(Object))
491 492 @classmethod
493 - def _AddEvents(cls, Class):
494 """Adds events based on the attributes of the given ``...Events`` class. 495 496 :Parameters: 497 Class : class 498 An `...Events` class whose methods define events that may occur in the 499 instances of the current class. 500 """ 501 def make_event(event): 502 return property(lambda self: self._GetDefaultEventHandler(event), 503 lambda self, Value: self._SetDefaultEventHandler(event, Value))
504 for event in dir(Class): 505 if not event.startswith('_'): 506 setattr(cls, 'On%s' % event, make_event(event)) 507 cls._EventNames.append(event)
508
509 510 -class Cached(object):
511 """Base class for all cached objects. 512 513 Every object has an owning object a handle. Owning object is where the cache is 514 maintained, handle identifies an object of given type. 515 516 Thanks to the caching, trying to create two objects with the same owner and handle 517 yields exactly the same object. The cache itself is based on weak references so 518 not referenced objects are automatically removed from the cache. 519 520 Because the ``__init__`` method will be called no matter if the object already 521 existed or not, it is recommended to use the `_Init` method instead. 522 """ 523 # Subclasses have to define a type/classmethod/staticmethod called 524 # _ValidateHandle(Handle) 525 # which is called by classmethod__new__ to validate the handle passed to 526 # it before it is stored in the instance. 527
528 - def __new__(cls, Owner, Handle):
529 Handle = cls._ValidateHandle(Handle) 530 key = (cls, Handle) 531 try: 532 return Owner._ObjectCache[key] 533 except KeyError: 534 obj = object.__new__(cls) 535 Owner._ObjectCache[key] = obj 536 obj._Owner = Owner 537 obj._Handle = Handle 538 obj._Init() 539 return obj 540 except AttributeError: 541 raise TypeError('%s is not a cached objects owner' % repr(Owner))
542
543 - def _Init(self):
544 """Initializes the cached object. Receives all the arguments passed to the 545 constructor The default implementation stores the ``Owner`` in 546 ``self._Owner`` and ``Handle`` in ``self._Handle``. 547 548 This method should be used instead of ``__init__`` to prevent double 549 initialization. 550 """
551
552 - def __copy__(self):
553 return self
554
555 - def __repr__(self, *Attrs):
556 if not Attrs: 557 Attrs = ['_Handle'] 558 return '<%s.%s with %s>' % (self.__class__.__module__, self.__class__.__name__, 559 ', '.join('%s=%s' % (name, repr(getattr(self, name))) for name in Attrs))
560
561 - def _MakeOwner(self):
562 """Prepares the object for use as an owner for other cached objects. 563 """ 564 self._CreateOwner(self)
565 566 @staticmethod
567 - def _CreateOwner(Object):
568 """Prepares any object for use as an owner for cached objects. 569 570 :Parameters: 571 Object 572 Object that should be turned into a cached objects owner. 573 """ 574 Object._ObjectCache = weakref.WeakValueDictionary()
575
576 577 -class CachedCollection(object):
578 """ 579 """ 580 _CachedType = Cached 581
582 - def __init__(self, Owner, Handles=[], Items=[]):
583 self._Owner = Owner 584 self._Handles = map(self._CachedType._ValidateHandle, Handles) 585 for item in Items: 586 self.append(item)
587
588 - def _AssertItem(self, Item):
589 if not isinstance(Item, self._CachedType): 590 raise TypeError('expected %s instance' % repr(self._CachedType)) 591 if self._Owner is not Item._Owner: 592 raise TypeError('expected %s owned item' % repr(self._Owner))
593
594 - def _AssertCollection(self, Col):
595 if not isinstance(Col, self.__class__): 596 raise TypeError('expected %s instance' % repr(self.__class__)) 597 if self._CachedType is not Col._CachedType: 598 raise TypeError('expected collection of %s' % repr(self._CachedType)) 599 if self._Owner is not Col._Owner: 600 raise TypeError('expected %s owned collection' % repr(self._Owner))
601
602 - def __len__(self):
603 return len(self._Handles)
604
605 - def __getitem__(self, Key):
606 if isinstance(Key, slice): 607 return self.__class__(self._Owner, self._Handles[Key]) 608 return self._CachedType(self._Owner, self._Handles[Key])
609
610 - def __setitem__(self, Key, Item):
611 if isinstance(Key, slice): 612 handles = [] 613 for it in Item: 614 self._AssertItem(it) 615 handles.append(it._Handle) 616 self._Handlers[Key] = handles 617 else: 618 self._AssertItem(Item) 619 self._Handles[Key] = Item._Handle
620
621 - def __delitem__(self, Key):
622 del self._Handles[Key]
623
624 - def __iter__(self):
625 for handle in self._Handles: 626 yield self._CachedType(self._Owner, handle)
627
628 - def __contains__(self, Item):
629 try: 630 self._AssertItem(Item) 631 except TypeError: 632 return False 633 return (Item._Handle in self._Handles)
634
635 - def __add__(self, Other):
636 self._AssertCollection(Other) 637 return self.__class__(self._Owner, self._Handles + 638 Other._Handles)
639
640 - def __iadd__(self, Other):
641 self._AssertCollection(Other) 642 self._Handles += Other._Handles 643 return self
644
645 - def __mul__(self, Times):
646 return self.__class__(self._Owner, self._Handles * Times)
647 __rmul__ = __mul__ 648
649 - def __imul__(self, Times):
650 self._Handles *= Times 651 return self
652
653 - def __copy__(self):
654 obj = self.__class__(self._Owner) 655 obj._Handles = self._Handles[:] 656 return obj
657
658 - def append(self, item):
659 """ 660 """ 661 self._AssertItem(item) 662 self._Handles.append(item._Handle)
663
664 - def count(self, item):
665 """ 666 """ 667 self._AssertItem(item) 668 return self._Handles.count(item._Handle)
669
670 - def index(self, item):
671 """ 672 """ 673 self._AssertItem(item) 674 return self._Handles.index(item._Handle)
675
676 - def extend(self, seq):
677 """ 678 """ 679 self.__iadd__(seq)
680
681 - def insert(self, index, item):
682 """ 683 """ 684 self._AssertItem(item) 685 self._Handles.insert(index, item._Handle)
686
687 - def pop(self, pos=-1):
688 """ 689 """ 690 return self._CachedType(self._Owner, self._Handles.pop(pos))
691
692 - def remove(self, item):
693 """ 694 """ 695 self._AssertItem(item) 696 self._Handles.remove(item._Handle)
697
698 - def reverse(self):
699 """ 700 """ 701 self._Handles.reverse()
702
703 - def sort(self, cmp=None, key=None, reverse=False):
704 """ 705 """ 706 if key is None: 707 wrapper = lambda x: self._CachedType(self._Owner, x) 708 else: 709 wrapper = lambda x: key(self._CachedType(self._Owner, x)) 710 self._Handles.sort(cmp, wrapper, reverse)
711
712 - def Add(self, Item):
713 """ 714 """ 715 self.append(Item)
716
717 - def Remove(self, Index):
718 """ 719 """ 720 del self[Index]
721
722 - def RemoveAll(self):
723 """ 724 """ 725 del self[:]
726
727 - def Item(self, Index):
728 """ 729 """ 730 return self[Index]
731
732 - def _GetCount(self):
733 return len(self)
734 735 Count = property(_GetCount, 736 doc=""" 737 """)
738