Source code for node_report_apply

# This file is part of Sympathy for Data.
# Copyright (c) 2014, 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 os
import sys
import json
import tempfile
import subprocess

from sympathy.api import node as synode
from sympathy.api import adaf, table
from sympathy.api.nodeconfig import Ports, Port, Tag, Tags
from sympathy.api import report
from sylib.report import gui_signal_mapping


COMMON_DOC = """
Signal map
==========
The signal map maps input signal names to template signal names. If the input
signal field is empty the template signal name will be used. Input signals are
chosen using a combo box in which the length and the name of the signal are
shown. The signal name is always presented as "table_name.column_name". There
is currently no check to ensure that the same signal length is used for
template signals which require same length (a plot is only meaningful if e.g.
the length of the x-coordinates and the length of the y-coordinates match).
"""


class DataType:
    adaf = 'adafs'
    table = 'tables'


def file_type(data_type):
    if data_type == 'adafs':
        return adaf.FileList
    elif data_type == 'tables':
        return table.FileList
    else:
        assert False


def generate_widgets(node, node_context, data_type, filenames=None):
    node.set_progress(0)

    # Read files and parameters
    parameter_root = node_context.parameters
    file_format = parameter_root['file_format'].selected
    if 'save_path' in parameter_root.keys():
        save_path = parameter_root['save_path'].value
    else:
        save_path = None
    if 'filename_prefix' in parameter_root.keys():
        prefix = parameter_root['filename_prefix'].value
    else:
        prefix = None
    template = node_context.input[0].get()
    input_data = node_context.input[1]
    signal_mapping = parameter_root['signal_mapping'].value

    node.set_progress(15)

    # Write input data to a file accessible to the qt process.
    temp_session = os.environ['SY_TEMP_SESSION']

    transfer_file = tempfile.NamedTemporaryFile(
        dir=temp_session,
        suffix='.sydata', delete=False)
    transfer_file.close()
    with file_type(data_type)(filename=transfer_file.name, mode='w') as f:
        f.source(input_data)

    node.set_progress(20)

    # Create the widgets in a different process.
    from sylib.report import write_reports
    python_file = write_reports.__file__

    sys_path = sys.path

    function_args = [
        sys_path, template, signal_mapping, transfer_file.name, data_type,
        file_format, save_path, prefix,
        filenames]

    json_function_args = json.dumps(function_args)
    args = [sys.executable, python_file, json_function_args]

    proc = subprocess.Popen(
        args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, errors = proc.communicate()

    if isinstance(output, bytes):
        output = output.decode('utf8')
        errors = errors.decode('utf8')

    results = output.split(write_reports.marker)
    filenames = []

    if len(results) == 1:
        output = results[0]
        result = None
    else:
        output, result = results

    if output:
        sys.stdout.write(output)

    if errors:
        sys.stderr.write(errors)

    if result:
        filenames = json.loads(result)
        os.remove(transfer_file.name)

    return filenames


[docs]class ReportApplyTables(synode.Node): __doc__ = COMMON_DOC name = 'Report Apply Tables' nodeid = 'org.sysess.sympathy.report.apply.tables' author = 'Stefan Larsson' icon = 'report-apply.svg' tags = Tags(Tag.Visual.Report) description = "Apply new data to report template and exports it." related = ['org.sysess.sympathy.report.template.tables', 'org.sysess.sympathy.report.apply.adafs'] inputs = Ports([ report.ReportPort( 'Document template for visualization of data', name='template'), Port.Tables( 'List of tables to use as data sources for the document template', name='tables')]) outputs = Ports([ Port.Datasources('Output datasources', name='dsrc')]) parameters = synode.parameters() parameters.set_string( 'save_path', value='', label='Save Path', description='Path to save report pages to.', editor=synode.editors.directory_editor()) parameters.set_string( 'filename_prefix', value='', label='Filename', description='Prefix of saved files.') parameters.set_list( 'file_format', value=[0], plist=['png', 'jpg', 'pdf'], label='File Format', editor=synode.editors.combo_editor(), description='File format of exported pages.') parameters.set_string( 'signal_mapping', value='{}', label='Signal Mapping', description='Mapping of incoming signal names to template signal ' 'names.') def execute(self, node_context): filenames = generate_widgets(self, node_context, DataType.table) output = node_context.output['dsrc'] for f in filenames: dsrc = output.create() dsrc.encode_path(f) output.append(dsrc) def exec_parameter_view(self, node_context): document = None input_tables = None if (node_context.input[0].is_valid() and node_context.input[1].is_valid()): document = json.loads(node_context.input[0].get()) input_tables = node_context.input[1] return gui_signal_mapping.SignalMappingWidget(node_context.parameters, document, input_tables, DataType.table)
[docs]class ReportApplyADAFs(synode.Node): __doc__ = COMMON_DOC name = 'Report Apply ADAFs' nodeid = 'org.sysess.sympathy.report.apply.adafs' author = 'Stefan Larsson' icon = 'report-apply.svg' description = "Apply new data to report template and exports it." related = ['org.sysess.sympathy.report.apply.adafswithdsrcs', 'org.sysess.sympathy.report.apply.tables', 'org.sysess.sympathy.report.template.adafs'] inputs = Ports([ report.ReportPort( 'Document template for visualization of data', name='template'), Port.ADAFs( 'List of ADAFs to use as data sources for the document template', name='adafs')]) outputs = Ports([ Port.Datasources('Output datasources', name='dsrc')]) tags = Tags(Tag.Visual.Report) parameters = synode.parameters() parameters.set_string( 'save_path', value='', label='Save Path', description='Path to save report pages to.', editor=synode.editors.directory_editor()) parameters.set_string( 'filename_prefix', value='', label='Filename Prefix', description='Prefix of saved files.') parameters.set_list( 'file_format', value=[0], plist=['png', 'jpg', 'pdf'], label='File Format', editor=synode.editors.combo_editor(), description='File format of exported pages.') parameters.set_string( 'signal_mapping', value='{}', label='Signal Mapping', description='Mapping of incoming signal names to template signal ' 'names.') def execute(self, node_context): filenames = generate_widgets(self, node_context, DataType.adaf) output = node_context.output['dsrc'] for f in filenames: dsrc = output.create() dsrc.encode_path(f) output.append(dsrc) def exec_parameter_view(self, node_context): document = None input_data = None if (node_context.input[0].is_valid() and node_context.input[1].is_valid()): document = json.loads(node_context.input[0].get()) input_data = node_context.input[1] return gui_signal_mapping.SignalMappingWidget(node_context.parameters, document, input_data, DataType.adaf)
[docs]class ReportApplyADAFsWithDsrc(synode.Node): __doc__ = COMMON_DOC name = 'Report Apply ADAFs with Datasources' nodeid = 'org.sysess.sympathy.report.apply.adafswithdsrcs' author = 'Magnus Sandén' icon = 'report-apply.svg' description = ( "Apply new data to report template and exports it to datasources " "recieved on the input ports.") inputs = Ports([ report.ReportPort('Report Template', name='template'), Port.ADAFs('Input ADAFs', name='adafs'), Port.Datasources('Save path', name='dsrc')]) outputs = Ports([ Port.Datasources('Output files', name='dsrc')]) tags = Tags(Tag.Visual.Report) related = ['org.sysess.sympathy.report.apply.adafs', 'org.sysess.sympathy.report.template.adafs'] parameters = synode.parameters() parameters.set_list( 'file_format', value=[0], plist=['png', 'jpg', 'pdf'], label='File Format', editor=synode.editors.combo_editor(), description='File format of exported pages.') parameters.set_string( 'signal_mapping', value='{}', label='Signal Mapping', description='Mapping of incoming signal names to template signal ' 'names.') def execute(self, node_context): dsrcs = node_context.input['dsrc'] input_filenames = [ os.path.abspath(dsrc.decode_path()) for dsrc in dsrcs] filenames = generate_widgets(self, node_context, DataType.adaf, filenames=input_filenames) output = node_context.output['dsrc'] for f in filenames: dsrc = output.create() dsrc.encode_path(f) output.append(dsrc) def exec_parameter_view(self, node_context): document = None input_data = None if (node_context.input[0].is_valid() and node_context.input[1].is_valid()): document = json.loads(node_context.input[0].get()) input_data = node_context.input[1] return gui_signal_mapping.SignalMappingWidget(node_context.parameters, document, input_data, DataType.adaf)