浏览代码

pythonlib/pydispatch: API documentation

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@55390 15284696-431f-4ddb-bdfa-cd5b030d7da7
Vaclav Petras 12 年之前
父节点
当前提交
cd32788de1
共有 3 个文件被更改,包括 115 次插入11 次删除
  1. 3 1
      lib/python/pydispatch/dispatcher.py
  2. 4 4
      lib/python/pydispatch/pydispatchlib.dox
  3. 108 6
      lib/python/pydispatch/signal.py

+ 3 - 1
lib/python/pydispatch/dispatcher.py

@@ -368,7 +368,9 @@ def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
 def _removeReceiver(receiver):
 def _removeReceiver(receiver):
 	"""Remove receiver from connections."""
 	"""Remove receiver from connections."""
 	if not sendersBack or not connections:
 	if not sendersBack or not connections:
-		# During module cleanup the mapping will be replaced with None
+		# During module cleanup the objects will be replaced with None
+           # The order of replacing many chnage, so both variables need
+           # to be checked.
 		return False
 		return False
 	backKey = id(receiver)
 	backKey = id(receiver)
 	try:
 	try:

+ 4 - 4
lib/python/pydispatch/pydispatchlib.dox

@@ -1,4 +1,4 @@
-/*! \page pydispatch Customized pydispatcher library
+/*! \page pydispatch Customized PyDispatcher library
 
 
 by GRASS Development Team (http://grass.osgeo.org)
 by GRASS Development Team (http://grass.osgeo.org)
 
 
@@ -8,7 +8,7 @@ by GRASS Development Team (http://grass.osgeo.org)
 \section pydispatchIntro Introduction
 \section pydispatchIntro Introduction
 
 
 Files dispatcher.py, errors.py, robustapply.py, robust.py and saferef.py are
 Files dispatcher.py, errors.py, robustapply.py, robust.py and saferef.py are
-part of the original pydispatcher library.
+part of the original PyDispatcher library.
 File signal.py is the object-based easy-to-use interface created for GRASS.
 File signal.py is the object-based easy-to-use interface created for GRASS.
 
 
 Signals as a concept (API) does not depend on PyDispatcher,
 Signals as a concept (API) does not depend on PyDispatcher,
@@ -188,9 +188,9 @@ because if you don't rely on these features, you usually write a less tangled co
 
 
 \section pydispatchAlternatives Alternatives
 \section pydispatchAlternatives Alternatives
 
 
-There is wxPython' pubsub module which provides similar functionality to pydispatcher. However, it has two incompatible APIs which depends on the wxPython version and in the time of writing, GRASS would have to support both these APIs. The only possibility is to include pubsub into GRASS. Whether to use pydispatcher or pubsub usually depends on personal taste.
+There is wxPython's pubsub module which provides similar functionality to PyDispatcher. However, it has two incompatible APIs which depends on the wxPython version and in the time of writing, GRASS would have to support both these APIs. The only possibility is to include pubsub into GRASS. Whether to use PyDispatcher or pubsub usually depends on personal taste.
 
 
-There are several forks of pydispatcher, e.g. the fork in Django framework.
+There are several forks of PyDispatcher, e.g. the fork in Django framework.
 They provide the same or similar functionality.
 They provide the same or similar functionality.
 
 
 Of course it is also possible to implement this system from the scratch using e.g., observer pattern.
 Of course it is also possible to implement this system from the scratch using e.g., observer pattern.

+ 108 - 6
lib/python/pydispatch/signal.py

@@ -10,7 +10,8 @@ from grass.pydispatch import dispatcher
 
 
 
 
 def _islambda(function):
 def _islambda(function):
-    """Tests if object is a lambda function.
+    """
+    Tests if object is a lambda function.
 
 
     Should work on the most of Python implementations where name of lambda
     Should work on the most of Python implementations where name of lambda
     function is not unique.
     function is not unique.
@@ -26,22 +27,47 @@ def _islambda(function):
 
 
 class Signal(object):
 class Signal(object):
     """
     """
+
+    The signal object is created usually as a instance attribute.
+    However, it can be created anywhere.
+
     >>> signal1 = Signal('signal1')
     >>> signal1 = Signal('signal1')
+
+    The function has to be connected to a signal in order to be called when
+    the signal is emitted. The connection can be done where the function is
+    defined (e. g., a class) but also on some other place, typically,
+    user of a class connects some signal to the method of some other class.
+
     >>> def handler1():
     >>> def handler1():
     ...     print "from handler1"
     ...     print "from handler1"
     >>> signal1.connect(handler1)
     >>> signal1.connect(handler1)
 
 
-    >>> signal2 = Signal('signal2')
-    >>> def handler2(text):
-    ...     print "handler2: %s" % text
-    >>> signal2.connect(handler2)
+    Emitting of the signal is done usually only in the class which has the
+    signal as a instance attribute. Again, generally, it can be emitted
+    anywhere.
 
 
     >>> signal1.emit()
     >>> signal1.emit()
     from handler1
     from handler1
 
 
+    The signal can have parameters. These parameters are specified when
+    emitting but should be documented together with the signal (e.g., in the
+    class documentation). Parameters should be keyword arguments and handlers
+    must use these names (if the names cannot be the same, lambda function
+    can be used to overcome this problem).
+
+    >>> signal2 = Signal('signal2')
+    >>> def handler2(text):
+    ...     print "handler2: %s" % text
+    >>> signal2.connect(handler2)
     >>> signal2.emit(text="Hello")
     >>> signal2.emit(text="Hello")
     handler2: Hello
     handler2: Hello
 
 
+    Do not emit the same signal with different parameters when emitting at
+    different places.
+
+    A handler is the standard function, lambda function, method or any other
+    callable object.
+
     >>> import sys
     >>> import sys
     >>> signal2.connect(lambda text:
     >>> signal2.connect(lambda text:
     ...                 sys.stdout.write('lambda handler: %s\\n' % text))
     ...                 sys.stdout.write('lambda handler: %s\\n' % text))
@@ -49,6 +75,9 @@ class Signal(object):
     handler2: Hi
     handler2: Hi
     lambda handler: Hi
     lambda handler: Hi
 
 
+    The handler function can have only some of the signal parameters or no
+    parameters at all even if the signal has some.
+
     >>> def handler3():
     >>> def handler3():
     ...     print "from handler3"
     ...     print "from handler3"
     >>> signal2.connect(handler3)
     >>> signal2.connect(handler3)
@@ -57,6 +86,10 @@ class Signal(object):
     lambda handler: Ciao
     lambda handler: Ciao
     from handler3
     from handler3
 
 
+    It is possible to use signal as a handler. By this, signals can be
+    forwarded from one object to another. In other words, one object can
+    expose signal of some object.
+
     >>> signal3 = Signal('signal3')
     >>> signal3 = Signal('signal3')
     >>> signal3.connect(handler3)
     >>> signal3.connect(handler3)
     >>> signal1.connect(signal3)
     >>> signal1.connect(signal3)
@@ -64,6 +97,8 @@ class Signal(object):
     from handler1
     from handler1
     from handler3
     from handler3
 
 
+    It is possible to disconnect a particular handler.
+
     >>> signal3.disconnect(handler3)
     >>> signal3.disconnect(handler3)
     >>> signal1.emit()
     >>> signal1.emit()
     from handler1
     from handler1
@@ -72,11 +107,32 @@ class Signal(object):
     >>> signal2.emit(text='Hello')
     >>> signal2.emit(text='Hello')
     lambda handler: Hello
     lambda handler: Hello
     """
     """
+    # TODO: use the name for debugging
     def __init__(self, name):
     def __init__(self, name):
+        """Creates a signal object.
+
+        The parameter name is used for debugging.
+        """
         self._name = name
         self._name = name
 
 
     def connect(self, handler, weak=None):
     def connect(self, handler, weak=None):
         """
         """
+        Connects handler to a signal.
+
+        Typically, a signal is defined in some class and the user of this
+        class connects to the signal::
+
+            from module import SomeClass
+            ...
+            self.someObject = SomeClass()
+            self.someObject.connect(self.someMethod)
+
+        Usually, it is not needed to set the weak parameter. This method
+        creates weak references for all handlers but for lambda functions, it
+        automaticaly creates (standard) references (otherwise, lambdas would be
+        garbage collected. If you want to force some behaviour, specify the
+        weak parameter.
+
         >>> signal1 = Signal('signal1')
         >>> signal1 = Signal('signal1')
         >>> import sys
         >>> import sys
         >>> signal1.connect(lambda: sys.stdout.write('will print\\n'))
         >>> signal1.connect(lambda: sys.stdout.write('will print\\n'))
@@ -95,14 +151,29 @@ class Signal(object):
 
 
     def disconnect(self, handler, weak=True):
     def disconnect(self, handler, weak=True):
         """
         """
-        
+        Disconnects a specified handler.
+
+        It is not necessary to disconnect object when it is deleted.
+        Underlying PyDispatcher will take care of connections to deleted
+        objects.
+
         >>> signal1 = Signal('signal1')
         >>> signal1 = Signal('signal1')
         >>> import sys
         >>> import sys
         >>> signal1.connect(sys.stdout.write)
         >>> signal1.connect(sys.stdout.write)
         >>> signal1.disconnect(sys.stdout.write)
         >>> signal1.disconnect(sys.stdout.write)
+
+        The weak parameter of must have the same value as for connection.
+        If you not specified the parameter when connecting,
+        you don't have to specify it when disconnecting.
+
+        Disconnecting the not-connected handler will result in error.
+
         >>> signal1.disconnect(sys.stdout.flush)  #doctest: +ELLIPSIS
         >>> signal1.disconnect(sys.stdout.flush)  #doctest: +ELLIPSIS
         Traceback (most recent call last):
         Traceback (most recent call last):
         DispatcherKeyError: 'No receivers found for signal <__main__.Signal object at 0x...> from sender _Any'
         DispatcherKeyError: 'No receivers found for signal <__main__.Signal object at 0x...> from sender _Any'
+
+        Disconnecting the non-exiting or unknown handler will result in error.
+        
         >>> signal1.disconnect(some_function)
         >>> signal1.disconnect(some_function)
         Traceback (most recent call last):
         Traceback (most recent call last):
         NameError: name 'some_function' is not defined
         NameError: name 'some_function' is not defined
@@ -111,8 +182,37 @@ class Signal(object):
         dispatcher.disconnect(receiver=handler, signal=self, weak=weak)
         dispatcher.disconnect(receiver=handler, signal=self, weak=weak)
 
 
     # TODO: remove args?, make it work for args?
     # TODO: remove args?, make it work for args?
+    # TODO: where to put documentation
     def emit(self, *args, **kwargs):
     def emit(self, *args, **kwargs):
         """
         """
+        Emits the signal which means that all connected handlers will be
+        called.
+
+        It is advised to have signals as instance attributes and emit signals
+        only in the class which owns the signal::
+
+            class Abc(object):
+                def __init__(self):
+                    self.colorChanged = Signal('Abc.colorChanged')
+                    ...
+                def setColor(self, color):
+                    ...
+                    self.colorChanged.emit(oldColor=self.Color, newColor=color)
+                    ...
+
+        Documentation of an signal should be placed to the class documentation
+        or to the code (this need to be more specified).
+
+        Calling a signal from outside the class is usually not good
+        practice. The only case when it is permitted is when signal is the part
+        of some globaly shared object and permision to emit is stayed in the
+        documentation.
+
+        The parameters of the emit function must be the same as the parameters
+        of the handlers. However, handler can ommit some parameters.
+        The associated parameters shall be documented for each Signal instance.
+        Use only keyword arguments when emitting.
+
         >>> signal1 = Signal('signal1')
         >>> signal1 = Signal('signal1')
         >>> def mywrite(text):
         >>> def mywrite(text):
         ...     print text
         ...     print text
@@ -128,6 +228,7 @@ class Signal(object):
         """
         """
         dispatcher.send(signal=self, *args, **kwargs)
         dispatcher.send(signal=self, *args, **kwargs)
 
 
+    # TODO: remove args?
     def __call__(self, *args, **kwargs):
     def __call__(self, *args, **kwargs):
         """Allows to emit signal with function call syntax.
         """Allows to emit signal with function call syntax.
 
 
@@ -157,6 +258,7 @@ class Signal(object):
         name for parameter when using signals), the error is much more readable
         name for parameter when using signals), the error is much more readable
         when using emit than function call. Concluding remark is that
         when using emit than function call. Concluding remark is that
         emit behaves more predictable.
         emit behaves more predictable.
+
         >>> signal1.emit(signal='Hello')
         >>> signal1.emit(signal='Hello')
         Traceback (most recent call last):
         Traceback (most recent call last):
         TypeError: send() got multiple values for keyword argument 'signal'
         TypeError: send() got multiple values for keyword argument 'signal'