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']
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
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
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
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
102
103
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('"', '""')
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
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
221 if self.after:
222 self.after.join()
223 self.after = None
224 for handler in self.handlers:
225 handler(*self.args, **self.kwargs)
226
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
358 _EventNames = []
359
361 """Initializes the object.
362 """
363
364
365 self._EventThreads = weakref.WeakValueDictionary()
366 self._EventHandlerObject = None
367 self._DefaultEventHandlers = {}
368 self._EventHandlers = {}
369 self.__Logger = logging.getLogger('Skype4Py.utils.EventHandlingBase')
370
371
372 for event in self._EventNames:
373 self._EventHandlers[event] = []
374
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
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
403 if handlers:
404
405 after = self._EventThreads.get(Event, None)
406
407 thread = EventSchedulerThread(Event, after, handlers, Args, KwArgs)
408
409 self._EventThreads[Event] = thread
410
411 thread.start()
412
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
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
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
475 try:
476 return self._DefaultEventHandlers[Event]
477 except KeyError:
478 return None
479
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
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
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
524
525
526
527
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
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
554
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
562 """Prepares the object for use as an owner for other cached objects.
563 """
564 self._CreateOwner(self)
565
566 @staticmethod
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
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
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
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
603 return len(self._Handles)
604
606 if isinstance(Key, slice):
607 return self.__class__(self._Owner, self._Handles[Key])
608 return self._CachedType(self._Owner, self._Handles[Key])
609
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
622 del self._Handles[Key]
623
625 for handle in self._Handles:
626 yield self._CachedType(self._Owner, handle)
627
629 try:
630 self._AssertItem(Item)
631 except TypeError:
632 return False
633 return (Item._Handle in self._Handles)
634
636 self._AssertCollection(Other)
637 return self.__class__(self._Owner, self._Handles +
638 Other._Handles)
639
641 self._AssertCollection(Other)
642 self._Handles += Other._Handles
643 return self
644
646 return self.__class__(self._Owner, self._Handles * Times)
647 __rmul__ = __mul__
648
650 self._Handles *= Times
651 return self
652
654 obj = self.__class__(self._Owner)
655 obj._Handles = self._Handles[:]
656 return obj
657
659 """
660 """
661 self._AssertItem(item)
662 self._Handles.append(item._Handle)
663
665 """
666 """
667 self._AssertItem(item)
668 return self._Handles.count(item._Handle)
669
671 """
672 """
673 self._AssertItem(item)
674 return self._Handles.index(item._Handle)
675
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
693 """
694 """
695 self._AssertItem(item)
696 self._Handles.remove(item._Handle)
697
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):
716
718 """
719 """
720 del self[Index]
721
723 """
724 """
725 del self[:]
726
727 - def Item(self, Index):
728 """
729 """
730 return self[Index]
731
734
735 Count = property(_GetCount,
736 doc="""
737 """)
738