Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example_data/example_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"policy:path": "section2/{beta}/{gamma:a}",
"gamma": "#range(3.0, 7.0, 0.5)",
"zeta": "#repeat(@Counter, 4)",
"eta": "Multiplier * gamma"
"eta": "$Multiplier * !gamma"
}
}
}
7 changes: 4 additions & 3 deletions spawn.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[spawn]
plugins=nrel:spawn.plugins.wind.nrel.plugin
type=nrel
; Use this section to point to plugins
; [spawn]
; plugins=nrel:spawn.plugins.wind.nrel.plugin
; type=nrel

[nrel]
turbsim_exe=./example_data/TurbSim.exe
Expand Down
3 changes: 3 additions & 0 deletions spawn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@
from ._version import get_versions
__version__ = get_versions()['version']
del get_versions

# Default `run` and `inspect` should be local
from .interface import run_local as run, inspect_local as inspect, write_inspection
64 changes: 20 additions & 44 deletions spawn/cli/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@
import luigi.interface

from spawn import __name__ as APP_NAME
from spawn.config import (CommandLineConfiguration, CompositeConfiguration,
DefaultConfiguration, IniFileConfiguration)
from spawn.parsers import SpecificationFileReader, SpecificationParser
from spawn.plugins import PluginLoader
from spawn.interface import LocalInterface, spawn_config, write_inspection
from spawn.schedulers import LuigiScheduler
from spawn.specification import DictSpecificationConverter
from spawn.util import configure_logging, prettyspec
from spawn.util import configure_logging

# Prevent luigi from setting up it's own logging
#pylint: disable=protected-access
Expand Down Expand Up @@ -60,7 +56,7 @@
def cli(ctx, **kwargs):
"""Command Line Interface
"""
config = _get_config(**kwargs)
config = spawn_config(**kwargs)
configure_logging(config.get(APP_NAME, 'log_level'), ctx.invoked_subcommand, config.get(APP_NAME, 'log_console'))
ctx.obj = kwargs

Expand All @@ -69,7 +65,7 @@ def cli(ctx, **kwargs):
def check_config(config):
"""Check the configuration. Parses the current configuration and prints to stdout
"""
_print_config(_get_config(**config))
_print_config(spawn_config(**config))

@cli.command()
@_pass_config
Expand All @@ -82,25 +78,19 @@ def check_config(config):
def inspect(config, **kwargs):
"""Expand and write to console the contents of the SPECFILE
"""
config = _get_config(**{**config, **kwargs})
config = spawn_config(**{**config, **kwargs})
specfile = config.get(APP_NAME, 'specfile')
outfile = config.get(APP_NAME, 'outfile')
click.echo('Inspecing input file "{}":'.format(click.format_filename(specfile)))
reader = SpecificationFileReader(specfile)
parser = SpecificationParser(reader, PluginLoader(config))
spec = parser.parse()
spec_dict = DictSpecificationConverter().convert(spec)
click.echo('Number of leaves: {}'.format(len(spec.root_node.leaves)))
if outfile is not None:
format_ = config.get(APP_NAME, 'format')
with open(outfile, 'w') as f:
if format_ == 'txt':
prettyspec(spec_dict, f)
elif format_ == 'json':
json.dump(spec_dict, f, indent=2)
click.echo('Specification details written to {}'.format(f.name))
else:
prettyspec(spec_dict)
interface = LocalInterface(config)
with open(specfile) as fp:
obj = json.load(fp)
spec_dict = interface.inspect(obj)
spec_stats = interface.stats(obj)
click.echo('Stats: {}'.format('; '.join('{}={}'.format(k, v) for k, v in spec_stats.items())))
write_inspection(spec_dict, outfile or click.get_text_stream('stdout'), config.get(APP_NAME, 'format'))
if outfile:
click.echo('Specification details written to {}'.format(outfile))

@cli.command()
@_pass_config
Expand All @@ -114,35 +104,21 @@ def inspect(config, **kwargs):
def run(config, **kwargs):
"""Runs the SPECFILE contents and write output to OUTDIR
"""
config = _get_config(**{**config, **kwargs})
reader = SpecificationFileReader(config.get(APP_NAME, 'specfile'))
plugin_loader = PluginLoader(config)
spec = SpecificationParser(reader, plugin_loader).parse()
plugin_type = config.get(APP_NAME, 'type') or spec.metadata.spec_type
if not plugin_type:
raise ValueError((
'No plugin type defined - please specify the --type argument ' +
'or add a type property in the spec file'
))
spawner = plugin_loader.create_spawner(plugin_type)
scheduler = LuigiScheduler(config)
scheduler.run(spawner, spec)
config = spawn_config(**{**config, **kwargs})
interface = LocalInterface(config)
with open(config.get(APP_NAME, 'specfile')) as fp:
spec_dict = json.load(fp)
interface.run(spec_dict)

@cli.command()
@_pass_config
def work(config):
"""Adds a worker to a remote scheduler
"""
config = _get_config(**{**config, 'local': False})
config = spawn_config(**{**config, 'local': False})
scheduler = LuigiScheduler(config)
scheduler.add_worker()

def _get_config(**kwargs):
command_line_config = CommandLineConfiguration(**kwargs)
ini_file_config = IniFileConfiguration(command_line_config.get(APP_NAME, 'config_file'))
default_config = DefaultConfiguration()
return CompositeConfiguration(command_line_config, ini_file_config, default_config)

def _print_config(config, ):
name_col_width = 0
names_values = []
Expand Down
9 changes: 7 additions & 2 deletions spawn/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Defines the base class for configuration implementations
"""
from abc import abstractmethod
from abc import ABC, abstractmethod

DEFAULT_CATEGORY = 'spawn'

LIST_DELIMITER = ','

class ConfigurationBase:
class ConfigurationBase(ABC):
"""Base class for configuration implementations.
"""

default_category = DEFAULT_CATEGORY

def get(self, category, key, parameter_type=str, default=None):
"""Gets the configuration value corresponding to the category and key.

Expand Down
8 changes: 3 additions & 5 deletions spawn/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Implementation of :class:`ConfigurationBase` returning default configuration values
"""
from spawn import __name__ as DEFAULT_CATEGORY

from .base import ConfigurationBase

DEFAULT_CONFIGURATION = {
DEFAULT_CATEGORY: {
ConfigurationBase.default_category: {
'workers': 4,
'config_file': DEFAULT_CATEGORY + '.ini',
'config_file': ConfigurationBase.default_category + '.ini',
'runner_type': 'process',
'prereq_outdir': 'prerequisites'
},
Expand Down Expand Up @@ -58,7 +56,7 @@ def keys(self, category):
return list(DEFAULT_CONFIGURATION.get(category, {}).keys())

@classmethod
def set_default(cls, key, value, category=DEFAULT_CATEGORY):
def set_default(cls, key, value, category=ConfigurationBase.default_category):
"""Set a default value

:param key: The key to set
Expand Down
2 changes: 1 addition & 1 deletion spawn/config/ini_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, ini_file):
:param ini_file: The file to read
:type ini_file: path-like
"""
self._config = configparser.ConfigParser() if path.isfile(ini_file) else None
self._config = configparser.ConfigParser() if ini_file and path.isfile(ini_file) else None
if self._config:
self._config.read(ini_file)

Expand Down
22 changes: 22 additions & 0 deletions spawn/interface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# spawn
# Copyright (C) 2018-2019, Simmovation Ltd.
#
# This program 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.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Defines objects that implement the spawn interface
"""
from .spawn import SpawnInterface
from .local import LocalInterface, run as run_local, inspect as inspect_local
from .config import spawn_config
from .util import write_inspection
32 changes: 32 additions & 0 deletions spawn/interface/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# spawn
# Copyright (C) 2018-2019, Simmovation Ltd.
#
# This program 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.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Interface method for creating a spawn config object
"""
from spawn.config import (
CommandLineConfiguration, CompositeConfiguration,
DefaultConfiguration, IniFileConfiguration
)

def spawn_config(**kwargs):
"""Create a spawn config object with extra kwargs
"""
command_line_config = CommandLineConfiguration(**kwargs)
ini_file_config = IniFileConfiguration(
command_line_config.get(CommandLineConfiguration.default_category, 'config_file')
)
default_config = DefaultConfiguration()
return CompositeConfiguration(command_line_config, ini_file_config, default_config)
120 changes: 120 additions & 0 deletions spawn/interface/local.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# spawn
# Copyright (C) 2018-2019, Simmovation Ltd.
#
# This program 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.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Defines the local implementation of :class:`SpawnInterface`
"""
import logging
from os import path
import json

from spawn.plugins import PluginLoader
from spawn.parsers import SpecificationParser, DictSpecificationProvider
from spawn.specification import DictSpecificationConverter
from spawn.schedulers import LuigiScheduler

from .spawn import SpawnInterface
from .config import spawn_config

LOGGER = logging.getLogger(__name__)

class LocalInterface(SpawnInterface):
"""Defines the :class:`LocalInterface`

A local implementation of the spawn interface
"""

def __init__(self, config):
self._config = config
self._plugin_loader = PluginLoader(config)

def inspect(self, spec_dict):
"""Inspect the object

:param spec_dict: The specfile object
:type spec_dict: dict

:returns: An expanded inspection dict
:rtype: dict
"""
spec = self._spec_dict_to_spec(spec_dict)
return self._spec_to_spec_dict(spec)

def stats(self, spec_dict):
"""Calculate stats for the spec

:param spec_dict: The specfile object
:type spec_dict: dict

:returns: An dict containing stats about the object
:rtype: dict
"""
spec = self._spec_dict_to_spec(spec_dict)
return {
'leaf_count': len(spec.root_node.leaves)
}

def run(self, spec_dict):
"""Run the spec object on the luigi scheduler

:param spec_dict: The specfile object
:type spec_dict: dict
"""
spec = self._spec_dict_to_spec(spec_dict)
plugin_type = self._config.get(self._config.default_category, 'type') or spec.metadata.spec_type
if not plugin_type:
raise ValueError((
'No plugin type defined - please specify the --type argument ' +
'or add a type property in the spec file'
))
spawner = self._plugin_loader.create_spawner(plugin_type)
scheduler = LuigiScheduler(self._config)
inspection_file = path.join(self._config.get(self._config.default_category, 'outfile'), 'spawn.json')
LOGGER.info('Writing inspection to %s', inspection_file)
with open(inspection_file, 'w') as fp:
json.dump(self._spec_to_spec_dict(spec), fp, indent=2)
scheduler.run(spawner, spec)

def _spec_dict_to_spec(self, spec_dict):
reader = DictSpecificationProvider(spec_dict)
return SpecificationParser(reader, self._plugin_loader).parse()

@staticmethod
def _spec_to_spec_dict(spec):
return DictSpecificationConverter().convert(spec)

def run(spec_dict, config=None):
"""Run the spec_dict

:param spec_dict: The spec dict
:type spec_dict: dict
:param config: The config
:type config: dict or :class:`ConfigurationBase`
"""
config = spawn_config(**config) if isinstance(config, dict) else spawn_config() if config is None else config

LocalInterface(config).run(spec_dict)

def inspect(spec_dict, config=None):
"""Inspect the spec_dict

:param spec_dict: The spec dict
:type spec_dict: dict
:param config: The config
:type config: dict or :class:`ConfigurationBase`
"""
config = spawn_config(**config) if isinstance(config, dict) else spawn_config() if config is None else config

return LocalInterface(config).inspect(spec_dict)
Loading