August'24: Kamaelia is in maintenance mode and will recieve periodic updates, about twice a year, primarily targeted around Python 3 and ecosystem compatibility. PRs are always welcome. Latest Release: 1.14.32 (2024/3/24)
Documenting Components
Some hopefully helpful guidelines!
These are the current guidelines for documenting Kamaelia components, covering:
It is better for there to be some documentation than none at all ... even if it breaks these guidelines or is incomplete. Don't let these guidelines stop you writing! Documentation can be developed iteratively, just like code. No documentation is perfect. Much of our existing documentation probably breaks many of these guidelines! Think of this as documentation of our current best practice. This is a descriptive set of guidelines explaining "how we have done it and what works for us". This is not a prescriptive demand that "things must be documented this way".
In these guidelines, examples of good ways to write are green. Examples of hownotto write are red.
Why document? Who is the Reader?
Documentation should serve two different sets of readers:
Because most components are small and lightweight, the main focus of these guidelines is on documenting for usage. Pretend you are the reader: what questions would you ask if you were seeing this component for the first time? What would a developer need to know?
Writing style
Aim to write in the following way:
Write as if you are talking to the reader
Use consistent, short, clear and unambiguous statements.
Use the present tense
Use the imperative - tell the reader what to do:
Refer to inboxes, outboxes and components by name
A reasonably good example:
It could have been much unnecessarily wordy:
This is clear and explains precisely what happens:
But this is much more ambiguous (what is a shutdown message? where do I send it?):
This can often simply be a matter of taste of course. Terse is often good, but unnecessarily terse is bad. Always aim for clarity above all else.
Structure and format
The formatting style being used is broadly the reStructured Text format. For an overview of this, see the primer. This has been chosen because of its simplicity and readability, but also because it can be machine processed to automate the generation of, for example, website documentation.
Document component(s) within a sourcefile by writing:
Write a detailed docstring at the top, containing:
A docstring for the class, containing:
Constructor syntax:
MyComponent(arg1,arg2[,optarg3]) -> new MyComponent component.
One sentence description:
Component that does X.
Description of initializer arguments:
Keyword arguments:
- arg1 -- what this is
- arg2 -- what this is
- optarg3 -- None, or what value it takes (default=None)
A docstring for each inbox and outbox, as the value of the key:value pair
Inboxes = { "inbox" : "Items to be xxxx'ed",
"control" : "Shutdown signalling",
}
unused boxes should be documented
as such
A standard docstring for the initalizer method:
"""x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""
Docstrings for other methods and comments within the code, to assist developers
See the example(s) and template below for the recommended syntax for these parts.
If your sourcefile contains more than one component, then concatenate the detailed docstrings for each component into one. Preface it with a top level heading and one or two paragraphs of introduction.
Some general consistency rules:
Wordwrap at 80 columns to aid readability
Leave 3 blank lines between the end of a section and the heading for the next.
Write python data structures in the language's own syntax. For example, a python list:
[ "moveto", width, height ]
Example
The best examples are within the Kamaelia codebase itself. Here is a snapshot (April 2006) of Kamaelia/Internet/TCPClient.py containing the TCPClient component. The documentation is highlighted in green:
"""\
=================
Simple TCP Client
=================
This component is for making a TCP connection to a server. Send to its "inbox"
inbox to send data to the server. Pick up data received from the server on its
"outbox" outbox.
Example Usage
-------------
Sending the contents of a file to a server at address 1.2.3.4 on port 1000::
pipeline( RateControlledFileReader("myfile", rate=100000),
TCPClient("1.2.3.4", 1000),
).activate()
How does it work?
-----------------
TCPClient opens a socket connection to the specified server on the specified
port. Data received over the connection appears at the component's "outbox"
outbox as strings. Data can be sent as strings by sending it to the "inbox"
inbox.
An optional delay (between component activation and attempting to connect) can
be specified. The default is no delay.
It creates a ConnectedSocketAdapter (CSA) to handle the socket connection and
registers it with a selectorComponent so it is notified of incoming data. The
selectorComponent is obtained by calling
selectorComponent.getSelectorService(...) to look it up with the local
Coordinating Assistant Tracker (CAT).
TCPClient wires itself to the "FactoryFeedback" outbox of the CSA. It also wires
its "inbox" inbox to pass data straight through to the CSA's "DataSend" inbox,
and its "outbox" outbox to pass through data from the CSA's "outbox" outbox.
Socket errors (after the connection has been successfully established) may be
sent to the "signal" outbox.
This component will terminate if the CSA sends a socketShutdown message to its
"FactoryFeedback" outbox.
Messages sent to the "control" inbox are ignored - users of this component
cannot ask it to close the connection.
"""
class TCPClient(component):
"""\
TCPClient(host,port[,delay]) -> new TCPClient component.
Establishes a TCP connection to the specified server.
Keyword arguments:
- host -- address of the server to connect to (string)
- port -- port number to connect on
- delay -- delay (seconds) after activation before connecting (default=0)
""" Inboxes = { "inbox" : "data to send to the socket",
"_socketFeedback" : "notifications from the ConnectedSocketAdapter",
"control" : "NOT USED"
}
Outboxes = { "outbox" : "data received from the socket",
"signal" : "socket errors",
"_selectorSignal" : "communicating with a selectorComponent",
}
def __init__(self,host,port,delay=0):
"""x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""
super(TCPClient, self).__init__()
self.host = host
self.port = port
self.delay=delay
...
A template
The following 'template' points out some of the formatting and stylistic points already described:
#!/usr/bin/env python
# <>
"""\
============
What it does
============
Short, one paragraph description of what this component does, or the task it achieves.
Example Usage
-------------
What this shows followed by double colon::
def func():
print "really really simple minimal code fragment"
Indicate any runtime user input with a python prompt:: >>> func()
really really simple minimal code fragment
Optional comment on any particularly important thing to note about the above
example.
How does it work?
-----------------
Statements, written in the present tense, describing in more detail what the
component does.
Explicitly refer to "named" inbox an "named" outbox to avoid ambiguity.
Does the component terminate? What are the conditions?
If the 'xxxx' argument is set to yyy during initialization, then something
different happens.
What does the component *not* do?
A subheading for a subtopic
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lists of important items might be needed, such as commands:
the item
A description of the item, what it is and what it does, and maybe
consequences of that.
another item
A description of the item, what it is and what it does, and maybe
consequences of that.
You may also want bullet lists:
- first item
- second item
Optional extra topics
---------------------
May be necessary to describe something separately, eg. a complex data structure
the component expects to receive, or the GUI interface it provides.
"""
class TheComponent(...):
"""\
TheComponent(arg[,optarg]) -> new TheComponent component.
Component that does something.
Keyword arguments:
- arg -- specifys something
- optarg -- None, or something else (default=None)
"""
Inboxes = { "inbox" : "What you send to here",
"control" : "Shutdown signalling",
}
Outboxes = { "outbox" : "What comes out of here,
"something" : "NOT USED",
"signal" : "Shutdown signalling",
}
def __init__(self, ...):
"""x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""
...
...rest of the component...