.. This file is part of Sympathy for Data.
..
.. Copyright (c) 2010-2012 System Engineering Software Society
..
.. Sympathy for Data is free software: you can redistribute it and/or modify
.. it under the terms of the GNU General Public License as published by
.. the Free Software Foundation, either version 3 of the License, or
.. (at your option) any later version.
..
.. Sympathy for Data is distributed in the hope that it will be useful,
.. but WITHOUT ANY WARRANTY; without even the implied warranty of
.. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
.. GNU General Public License for more details.
.. You should have received a copy of the GNU General Public License
.. along with Sympathy for Data. If not, see .
How to create reusable nodes
============================
Follow these simple guidelines to make sure that your node is as reusable as
possible.
- Break down the task into the smallest parts that are useful by themselves and
write nodes for each of those, instead of writing one monolithic "fix
everything" node. Take some inspiration from the Unix philosophy, that every
node should "do only one thing, and do it well".
- Try to work on the most natural data type for the problem that you are trying
to solve. When in doubt go with Table since it is the simplest and most
widely applicable data type.
- Don't hard code site specific stuff into your nodes. Instead add
preprocessing steps or configuration options as needed.
- Add documentation for your node, describing what the node does, what the
configuration options are, and whether there any constraints on the input
data.
- When you write the code for your node, remember that how you write it can
make a huge difference. If others can read and easily understand what your
code does it can continue to be developed by others. As a starting point you
should try to follow the Python style guide (PEP8_) as much as possible.
.. _PEP8: http://legacy.python.org/dev/peps/pep-0008/
If your nodes are very useful and don't include any secrets you may be able to
donate it to SysESS_ for inclusion in the standard library. This is only
possible if the node is considered reusable.
.. _SysESS: http://www.sysess.org
Add extra modules to your library
---------------------------------
If your node code is starting to become too big to keep it all in a single file
or if you created some nice utility functions that you want to use in several
different node files you can place them in the subfolder to the folder *Common*
that we created way back in :ref:`library_structure`. But first we need to make
a package out of that subfolder by placing an empty *__init__.py* file in it::
> touch boblib/Common/boblib/__init__.py
Now you can add modules to the package by adding the python files to the folder::
> spyder boblib/Common/boblib/mymodule.py
The *Common* folder will automatically be added to ``sys.path`` so you will now
be able to import modules from that package in your node code::
from boblib import mymodule
.. _sy12_compat:
Library compatibility between 1.2 and 1.3
-----------------------------------------
It is not difficult to write nodes compatible with both Sympathy version 1.2
and version 1.3.
* Your nodes should subclass the class :class:`synode.ManagedNode` instead of
:class:`synode.Node`, and override the methods called :meth:`execute_managed`
and the likes (see :ref:`overridable_node_methods`).
* When using ``node_context.parameters`` in any node method, be sure to wrap it
with a call to :func:`synode.parameters`. This will make sure that you are
always working with a ``ParameterRoot`` object regardless of Sympathy
version.
* To be compatible with 1.2 you should refrain from using
:ref:`custom ports` and instead rely on the other port types
available in :class:`Port`.
* Whenever you use :meth:`adjust_parameters_managed` you should return the
modified ``node_context``, since this is required in 1.2.
* Make sure to add a library tag so the node shows up in the right place in the
library in 1.3.
Here is an example of a node written to work just as well in Sympathy 1.2 as in
1.3. The comments highlight the areas of the code where extra care has to be
taken::
from sympathy.api import node as synode
from sympathy.api.exceptions import SyDataError, SyConfigurationError
from sympathy.api.nodeconfig import Ports, Port, Tags, Tag
class ImproveColumnNode(synode.ManagedNode): # Use ManagedNode.
"""
Improves one of the columns of a Table by increasing it by one.
This node demonstrates how to write a node that is compatible with both
Sympathy 1.2 and 1.3.
"""
name = 'Improve column'
nodeid = 'com.example.boblib.improvecolumn'
author = 'Bob '
copyright = '(C) 2015 Example Organization'
version = '1.0'
tags = Tags(Tag.Development.Example) # Always add tags.
# Don't use any CustomPort ports.
inputs = Ports([Port.Table('Input Table', name='in')])
outputs = Ports([Port.Table('Improved Table', name='out')])
parameters = {} # Set parameters to a dictionary.
parameter_root = synode.parameters(parameters)
parameter_root.set_list(
'col', label='Select column to be improved.',
description='The selected column will be improved by adding one.',
editor=synode.Util.combo_editor().value())
# Use adjust_parameters_managed instead of adjust_parameters.
def adjust_parameters_managed(self, node_context):
# Wrap parameters in synode.parameters before using them.
parameter_root = synode.parameters(node_context.parameters)
if node_context.input['in'].is_valid():
new_columns = node_context.input['in'].column_names()
else:
new_columns = []
if parameter_root['col'].selected not in new_columns:
new_columns.insert(0, parameter_root['col'].selected)
parameter_root['col'].list = new_columns
return node_context # Return the modified node_context
# Use execute_managed instead of execute.
def execute_managed(self, node_context):
inputfile = node_context.input['in']
outputfile = node_context.output['out']
# Wrap parameters in synode.parameters before using them.
parameter_root = synode.parameters(node_context.parameters)
col_name = parameter_root['col'].selected
if col_name is None:
raise SyConfigurationError('Please select a column to improve.')
if col_name not in inputfile.column_names():
raise SyDataError('No column called {} available in '
'input table'.format(col_name))
if inputfile.column_type(col_name).kind not in 'uifc':
raise SyDataError(
'Column {} is not a numeric type.'.format(col_name))
col = inputfile.get_column_to_array(col_name)
outputfile.source(inputfile)
outputfile.set_column_from_array(col_name, col + 1)