Source code for tool_templates
######################################################################################################################
# Copyright (C) 2017 - 2019 Spine project consortium
# This file is part of Spine Toolbox.
# Spine Toolbox is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program 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 Lesser General
# Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
######################################################################################################################
"""
Tool template classes.
:authors: P. Savolainen (VTT), E. Rinne (VTT)
:date: 24.1.2018
"""
import os
import logging
from collections import OrderedDict
from metaobject import MetaObject
from config import REQUIRED_KEYS, OPTIONAL_KEYS, LIST_REQUIRED_KEYS
[docs]class ToolTemplate(MetaObject):
"""Super class for various tool templates.
Attributes:
toolbox (ToolBoxUI): QMainWindow instance
name (str): Name of the tool
description (str): Short description of the tool
path (str): Path to tool
includes (str): List of files belonging to the tool template (relative to 'path') # TODO: Change to src_files
inputfiles (list): List of required data files
inputfiles_opt (list, optional): List of optional data files (wildcards may be used)
outputfiles (list, optional): List of output files (wildcards may be used)
cmdline_args (str, optional): Tool command line arguments (read from tool definition file)
execute_in_work (bool): Execute in work folder?
"""
def __init__(
self,
toolbox,
name,
tooltype,
path,
includes,
description=None,
inputfiles=None,
inputfiles_opt=None,
outputfiles=None,
cmdline_args=None,
execute_in_work=True,
):
"""Class constructor."""
super().__init__(name, description)
self._toolbox = toolbox
self.tooltype = tooltype
if not os.path.exists(path):
pass
else:
self.path = path
self.includes = includes
self.cmdline_args = cmdline_args
self.inputfiles = set(inputfiles) if inputfiles else set()
self.inputfiles_opt = set(inputfiles_opt) if inputfiles_opt else set()
self.outputfiles = set(outputfiles) if outputfiles else set()
self.return_codes = {}
self.def_file_path = '' # JSON tool definition file path
self.execute_in_work = execute_in_work
[docs] def set_return_code(self, code, description):
"""Set a return code and associated text description for the tool.
Args:
code (int): Return code
description (str): Description
"""
self.return_codes[code] = description
[docs] def set_def_path(self, path):
"""Set definition file path for tool.
Args:
path (str): Absolute path to the definition file.
"""
self.def_file_path = path
@staticmethod
[docs] def check_definition(ui, data):
"""Check that a tool template definition contains
the required keys and that it is in correct format.
Args:
ui (ToolboxUI): QMainWindow instance
data (dict): Tool template definition
Returns:
Dictionary or None if there was a problem in the tool definition.
"""
kwargs = dict()
for p in REQUIRED_KEYS + OPTIONAL_KEYS:
try:
kwargs[p] = data[p]
except KeyError:
if p in REQUIRED_KEYS:
ui.msg_error.emit("Required keyword '{0}' missing".format(p))
return None
# Check that some values are lists
if p in LIST_REQUIRED_KEYS:
try:
if not isinstance(data[p], list):
ui.msg_error.emit("Keyword '{0}' value must be a list".format(p), 2)
return None
except KeyError:
pass
return kwargs
[docs]class GAMSTool(ToolTemplate):
"""Class for GAMS tool templates.
Attributes:
name (str): GAMS Tool name
description (str): GAMS Tool description
path (str): Path to model main file
includes (str): List of files belonging to the tool (relative to 'path'). # TODO: Change to src_files
First file in the list is the main GAMS program.
inputfiles (list): List of required data files
inputfiles_opt (list, optional): List of optional data files (wildcards may be used)
outputfiles (list, optional): List of output files (wildcards may be used)
cmdline_args (str, optional): GAMS tool command line arguments (read from tool definition file)
"""
def __init__(
self,
toolbox,
name,
tooltype,
path,
includes,
description=None,
inputfiles=None,
inputfiles_opt=None,
outputfiles=None,
cmdline_args=None,
execute_in_work=True,
):
"""Class constructor."""
super().__init__(
toolbox,
name,
tooltype,
path,
includes,
description,
inputfiles,
inputfiles_opt,
outputfiles,
cmdline_args,
execute_in_work,
)
main_file = includes[0]
# Add .lst file to list of output files
self.lst_file = os.path.splitext(main_file)[0] + '.lst'
self.outputfiles.add(self.lst_file)
# Split main_prgm to main_dir and main_prgm
# because GAMS needs to run in the directory of the main program
# TODO: This does not work because main_file is always just file name
self.main_dir, self.main_prgm = os.path.split(main_file)
self.gams_options = OrderedDict()
self.return_codes = {
0: "Normal return",
1: "Solver is to be called the system should never return this number", # ??
2: "There was a compilation error",
3: "There was an execution error",
4: "System limits were reached",
5: "There was a file error",
6: "There was a parameter error",
7: "There was a licensing error",
8: "There was a GAMS system error",
9: "GAMS could not be started",
10: "Out of memory",
11: "Out of disk",
62097: "Simulation interrupted by user", # Not official
}
[docs] def __repr__(self):
"""Remove this if not necessary."""
return "GAMSTool('{}')".format(self.name)
[docs] def update_gams_options(self, key, value):
"""Update GAMS command line options. Only 'cerr and 'logoption' keywords supported.
Args:
key: Option name
value: Option value
"""
# Supported GAMS logoption values
# 3 writes LOG output to standard output
# 4 writes LOG output to a file and standard output [Not supported in GAMS v24.0]
if key in ['logoption', 'cerr']:
self.gams_options[key] = "{0}={1}".format(key, value)
else:
logging.error("Updating GAMS options failed. Unknown key: %s", key)
@staticmethod
[docs] def load(toolbox, path, data):
"""Create a GAMSTool according to a tool definition.
Args:
toolbox (ToolboxUI): QMainWindow instance
path (str): Base path to tool files
data (dict): Dictionary of tool definitions
Returns:
GAMSTool instance or None if there was a problem in the tool definition file.
"""
kwargs = GAMSTool.check_definition(toolbox, data)
if kwargs is not None:
# Return an executable model instance
return GAMSTool(toolbox=toolbox, path=path, **kwargs)
return None
[docs]class JuliaTool(ToolTemplate):
"""Class for Julia tool templates.
Attributes:
name (str): Julia Tool name
description (str): Julia Tool description
path (str): Path to model main file
includes (str): List of files belonging to the tool (relative to 'path'). # TODO: Change to src_files
First file in the list is the main Julia program.
inputfiles (list): List of required data files
inputfiles_opt (list, optional): List of optional data files (wildcards may be used)
outputfiles (list, optional): List of output files (wildcards may be used)
cmdline_args (str, optional): Julia tool command line arguments (read from tool definition file)
"""
def __init__(
self,
toolbox,
name,
tooltype,
path,
includes,
description=None,
inputfiles=None,
inputfiles_opt=None,
outputfiles=None,
cmdline_args=None,
execute_in_work=True,
):
"""Class constructor."""
super().__init__(
toolbox,
name,
tooltype,
path,
includes,
description,
inputfiles,
inputfiles_opt,
outputfiles,
cmdline_args,
execute_in_work,
)
main_file = includes[0]
self.main_dir, self.main_prgm = os.path.split(main_file)
self.julia_options = OrderedDict()
self.return_codes = {0: "Normal return"} # Not official
[docs] def __repr__(self):
"""Remove this if not necessary."""
return "JuliaTool('{}')".format(self.name)
"""Update Julia command line options.
Args:
key: Option name
value: Option value
"""
@staticmethod
[docs] def load(toolbox, path, data):
"""Create a JuliaTool according to a tool definition.
Args:
toolbox (ToolboxUI): QMainWindow instance
path (str): Base path to tool files
data (dict): Dictionary of tool definitions
Returns:
JuliaTool instance or None if there was a problem in the tool definition file.
"""
kwargs = JuliaTool.check_definition(toolbox, data)
if kwargs is not None:
# Return an executable model instance
return JuliaTool(toolbox=toolbox, path=path, **kwargs)
return None
[docs]class PythonTool(ToolTemplate):
"""Class for Python tool templates.
Attributes:
name (str): Python Tool name
description (str): Python Tool description
path (str): Path to model main file
includes (str): List of files belonging to the tool (relative to 'path'). # TODO: Change to src_files
First file in the list is the main Python program.
inputfiles (list): List of required data files
inputfiles_opt (list, optional): List of optional data files (wildcards may be used)
outputfiles (list, optional): List of output files (wildcards may be used)
cmdline_args (str, optional): Python tool command line arguments (read from tool definition file)
"""
def __init__(
self,
toolbox,
name,
tooltype,
path,
includes,
description=None,
inputfiles=None,
inputfiles_opt=None,
outputfiles=None,
cmdline_args=None,
execute_in_work=True,
):
"""Class constructor."""
super().__init__(
toolbox,
name,
tooltype,
path,
includes,
description,
inputfiles,
inputfiles_opt,
outputfiles,
cmdline_args,
execute_in_work,
)
main_file = includes[0]
self.main_dir, self.main_prgm = os.path.split(main_file)
self.python_options = OrderedDict()
self.return_codes = {0: "Normal return"} # Not official
[docs] def __repr__(self):
"""Remove this if not necessary."""
return "PythonTool('{}')".format(self.name)
"""Update Python command line options.
Args:
key: Option name
value: Option value
"""
@staticmethod
[docs] def load(toolbox, path, data):
"""Create a PythonTool according to a tool definition.
Args:
toolbox (ToolboxUI): QMainWindow instance
path (str): Base path to tool files
data (dict): Dictionary of tool definitions
Returns:
PythonTool instance or None if there was a problem in the tool definition file.
"""
kwargs = PythonTool.check_definition(toolbox, data)
if kwargs is not None:
# Return an executable model instance
return PythonTool(toolbox=toolbox, path=path, **kwargs)
return None
[docs]class ExecutableTool(ToolTemplate):
"""Class for Executable tool templates.
Attributes:
name (str): Tool name
description (str): Tool description
path (str): Path to main script file
includes (str): List of files belonging to the tool (relative to 'path'). # TODO: Change to src_files
First file in the list is the main script file.
inputfiles (list): List of required data files
inputfiles_opt (list, optional): List of optional data files (wildcards may be used)
outputfiles (list, optional): List of output files (wildcards may be used)
cmdline_args (str, optional): Tool command line arguments (read from tool definition file)
"""
def __init__(
self,
toolbox,
name,
tooltype,
path,
includes,
description=None,
inputfiles=None,
inputfiles_opt=None,
outputfiles=None,
cmdline_args=None,
execute_in_work=True,
):
"""Class constructor."""
super().__init__(
toolbox,
name,
tooltype,
path,
includes,
description,
inputfiles,
inputfiles_opt,
outputfiles,
cmdline_args,
execute_in_work,
)
main_file = includes[0]
# TODO: This does not do anything because main_file is always just file name
self.main_dir, self.main_prgm = os.path.split(main_file)
self.options = OrderedDict()
self.return_codes = {0: "Normal exit", 1: "Error happened"}
[docs] def __repr__(self):
"""Remove this if not necessary."""
return "ExecutableTool('{}')".format(self.name)
@staticmethod
[docs] def load(toolbox, path, data):
"""Create an ExecutableTool according to a tool specification.
Args:
toolbox (ToolboxUI): QMainWindow instance
path (str): Base path to tool files
data (dict): Tool specification
Returns:
ExecutableTool instance or None if there was a problem in the tool specification.
"""
kwargs = ExecutableTool.check_definition(toolbox, data)
if kwargs is not None:
# Return an executable model instance
return ExecutableTool(toolbox=toolbox, path=path, **kwargs)
return None