Source code for node_create_json

# This file is part of Sympathy for Data.
# Copyright (c) 2017, Combine Control Systems AB
#
# 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, version 3 of the License.
#
# 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 <http://www.gnu.org/licenses/>.
import json

import numpy as np
import sys

from sympathy.api import node as synode
from sympathy.api import node_helper
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags
from sympathy.api import exceptions
from sylib.util import base_eval


def _update_group(conf_group, data_group):
    old = list(conf_group)
    new = list(data_group)
    add = set(new).difference(old)
    remove = set(old).difference(new)

    for key in remove:
        del conf_group[key]

    for key in new:
        if key in add:
            conf_group[key] = data_group[key]
            continue

        conf_item = conf_group[key]
        data_item = data_group[key]
        if conf_item.type != data_item.type:
            # Replace.
            conf_group[key] = data_group[key]
        else:
            for k in ('order', 'label', 'description', 'editor'):
                setattr(conf_item, k, getattr(data_item, k))
            if conf_item.type in ['group', 'page']:
                _update_group(conf_item, data_item)
            elif conf_item.type == 'list':
                list_ = data_item.list
                conf_item.list = list_


def _prune(param):
    def prune_names(param):
        return set(param).difference(
            ['editor', 'order', 'description'])

    if not isinstance(param, dict):
        return param
    elif param['type'] in ['group', 'page']:
        return {k: _prune(param[k]) for k in prune_names(param.keys())}
    else:
        return {k: param[k] for k in prune_names(param.keys())}


[docs]class CreateParameters(synode.Node): """ Manually Create a Sympathy parameter structure by writing a python expression which modifies the parameters variable using the sympathy parameter api (same class as nodes use to create their parameters). Example: .. code-block:: python parameters.set_integer( 'number', description='Description of the number parameter.', value=1) parameters.set_string( 'string', description='Description of the string parameter.', value="1") parameters.set_integer( 'bounded_number', label='Bounded number', description='Description of the bounded_nummber parameter.', value=0, editor=synode.editors.bounded_lineedit_editor( 0, 4)) In order to create editors and doing some other operations, synode is defined when the code is evaluated. Optional input port, named arg, can be used in the code. Have a look at the :ref:`Data type APIs<datatypeapis>` to see what methods and attributes are available on the data type that you are working with. The evaluation context contains ``parameters`` of type :class:`ParameterRoot`, ``synode`` - which is the module obtained from :mod:`sympathy.api.node`, and optionally ``arg`` which is an instance of some sympathy :ref:`datatype<datatypeapis>` corresponding to the data type connected to the input port. """ name = 'Create Json Parameters' author = 'Erik der Hagopian' icon = 'create_json.svg' tags = Tags(Tag.Generic.Configuration) nodeid = 'org.sysess.sympathy.create.createparameters' inputs = Ports([Port.Custom('<a>', 'Input', name='arg', n=(0, 1))]) outputs = Ports([Port.Json('Output', name='output')]) parameters = synode.parameters() parameters.set_string( 'code', label='Parameters:', description='Python code that modifies the parameter structure.', value='', editor=synode.editors.code_editor()) def execute(self, node_context): inputs = node_context.input.group('arg') arg = inputs[0] if inputs else None parameters = synode.parameters() env = {'arg': arg, 'synode': synode, 'parameters': parameters} try: base_eval(node_context.parameters['code'].value, env, mode='exec') except Exception: raise exceptions.SyUserCodeError(sys.exc_info()) node_context.output[0].set(parameters.to_dict())
[docs]class ConfigureJsonParameters(synode.Node): """ Configure Json parameters. The input parameters are likely created by :ref:`Create Json Parameters` and follows the serialization format used for Sympathy parameters. .. warning:: The intended scope for this node, and where it will be most useful, is to produce static configurations. To allow configuring a set of parameters with static options. All the basic parameter types can be used so long as the choices are not intended to be dynamic and built from input data. For dynamic cases, where choices depend on input data, we instead recommend that you create a new library node. Add the parameters that you need and then add the Configuration output port to the node. If you also want to exclude it from the list of library nodes, set: .. code-block:: python tags = Tags(Hidden.Gui) to exclude it from the library. The node stores configured changes and uses them to modify the parameter structure from the input port. When data from the input port is unavailable, the stored parameters will be used in full. Execute simply outputs the resulting parameters as Json, and by adding the optional output *Table Parameters*, scalar parameters from the output can be made available in a flat (single row) table. """ name = 'Configure Json Parameters' author = 'Erik der Hagopian' icon = 'create_json.svg' tags = Tags(Tag.Generic.Configuration) nodeid = 'org.sysess.sympathy.create.configureparameters' inputs = Ports([Port.Json('Json Parameters', name='json_parameters')]) outputs = Ports( [Port.Json('Json Parameters', name='json_parameters'), Port.Custom('table', 'Table Parameters', name='table_parameters', n=(0, 1, 0))]) parameters = synode.parameters() def _updated_parameters(self, node_context): parameters = node_context.parameters try: port_parameters_data = node_context.input['json_parameters'].get() if port_parameters_data is None: raise exceptions.NoDataError('Missing input parameters') except exceptions.NoDataError: # Keep stored parameters. pass else: # Update port_parameters with stored parameters and # store updated parameters. port_parameters = synode.parameters(port_parameters_data) _update_group(parameters, port_parameters) def exec_parameter_view(self, node_context): self._updated_parameters(node_context) parameters = node_context.parameters gui = parameters.gui() return gui def execute(self, node_context): def flat_parameter_values(parameter_dict): def inner(param, name): if param['type'] in ['group', 'page']: for k, v in param.items(): param_ = param.get(k) if param_ and isinstance(param_, dict): inner(param_, k) else: for k, v in param.items(): if k == 'value': res[name] = v res = {} inner(parameter_dict, None) return sorted(res.items()) self._updated_parameters(node_context) parameters = node_context.parameters json_parameters = node_context.output.group('json_parameters') table_parameters = node_context.output.group('table_parameters') if json_parameters: json_parameters[0].set(_prune(parameters.to_dict())) if table_parameters: for key, value in flat_parameter_values(parameters.to_dict()): table_parameters[0][key] = np.array([value])
[docs]class CreateJSON(synode.Node): """ Create Json by writing a python expression which evaluates to normal python values, that is, dictionaries, lists, floats, integers, strings and booleans. Optional input port, named arg, can be used in the expression. Have a look at the :ref:`Data type APIs<datatypeapis>` to see what methods and attributes are available on the data type that you are working with. """ name = 'Create Json' author = 'Erik der Hagopian' icon = 'create_json.svg' tags = Tags(Tag.Generic.Configuration) related = ['org.sysess.sympathy.jsoncalculator'] nodeid = 'org.sysess.sympathy.create.createjson' inputs = Ports([Port.Custom('<a>', 'Input', name='arg', n=(0, 1))]) outputs = Ports([ Port.Custom('json', 'Output', name='output', preview=True)]) parameters = synode.parameters() parameters.set_string( 'code', description='Python expression that evaluates to a ' 'json-serilizable object.', value='{} # Empty dictionary.', editor=synode.editors.code_editor()) def execute(self, node_context): inputs = node_context.input.group('arg') arg = inputs[0] if inputs else None env = {'arg': arg} try: dict_ = base_eval(node_context.parameters['code'].value, env) node_context.output[0].set(dict_) except Exception: raise exceptions.SyUserCodeError(sys.exc_info())
[docs]class JSONtoText(synode.Node): name = 'Json to Text' description = 'Convert Json to Text using standard Json encoding.' author = 'Erik der Hagopian' icon = 'json_to_text.svg' tags = Tags(Tag.DataProcessing.Convert) related = [ 'org.sysess.sympathy.convert.jsonstotexts', 'org.sysess.sympathy.convert.texttojson', ] nodeid = 'org.sysess.sympathy.convert.jsontotext' inputs = Ports([Port.Json('Input', name='input')]) outputs = Ports([Port.Text('Output', name='output')]) parameters = synode.parameters() def execute(self, node_context): node_context.output[0].set( json.dumps(node_context.input[0].get()))
[docs]@node_helper.list_node_decorator(['input'], ['output']) class JSONstoTexts(JSONtoText): name = 'Jsons to Texts' nodeid = 'org.sysess.sympathy.convert.jsonstotexts'
[docs]class TexttoJSON(synode.Node): name = 'Text to Json' author = 'Erik der Hagopian' icon = 'text_to_json.svg' description = 'Convert Json-compliant Text to Json.' tags = Tags(Tag.DataProcessing.Convert) related = [ 'org.sysess.sympathy.convert.textstojsons', 'org.sysess.sympathy.convert.jsontotext', ] nodeid = 'org.sysess.sympathy.convert.texttojson' inputs = Ports([Port.Text('Input', name='input')]) outputs = Ports([Port.Json('Output', name='output')]) parameters = synode.parameters() def execute(self, node_context): text = node_context.input[0].get() try: data = json.loads(text) except json.decoder.JSONDecodeError as e: raise exceptions.SyDataError( f'Unsupported json content in text. {e}.' ) node_context.output[0].set(data)
[docs]@node_helper.list_node_decorator(['input'], ['output']) class TextstoJSONs(TexttoJSON): name = 'Texts to Jsons' nodeid = 'org.sysess.sympathy.convert.textstojsons'