Skip to content

Commit

Permalink
A working CLI implementation (thouska#139, thouska#143) with test
Browse files Browse the repository at this point in the history
  • Loading branch information
philippkraft committed Aug 28, 2018
1 parent 7b158e4 commit 578ef1b
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 16 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ install:
- pip install matplotlib
- pip install scipy
- pip install pandas
- pip install click
- pip install pytest pytest-pep8 pytest-cov
# Use docutils to generate html describe
- pip install docutils
Expand Down
17 changes: 9 additions & 8 deletions spotpy/cli.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from __future__ import division, print_function, unicode_literals

import click
import inspect
from . import algorithms, database, describe
import click
import inspect
import io
import os


def get_config_from_file():
"""
Gets the spotpy configuration from a config file 'spotpy.conf'.
Expand Down Expand Up @@ -47,7 +44,6 @@ def use(cl):
return click.Choice([n for n, m in members if not n.startswith('_')])



@click.group(context_settings=dict(help_option_names=['-h', '--help']))
def cli():
pass
Expand All @@ -63,15 +59,20 @@ def cli():
@click.option('--parallel', '-p', type=click.Choice(['seq', 'mpc', 'mpi']), default='seq',
help='Parallelization: seq = no parallelization, mpi = MPI (for clusters), mpc = multiprocessing')
@click.option('--runs', '-n', type=click.INT, default=1, help='Number of runs')
@click.option('--config', '-c', is_flag=True,
help='Print only the configuration, can be used to create a config file with your_model.py > spotpy.conf')
def run(ctx, **kwargs):
"""
Runs a sampler for automatic calibration
"""
setup = ctx.obj
sampler_class = get_sampler_from_string(kwargs.pop('sampler'))
runs = kwargs.pop('runs')
sampler = sampler_class(setup, **kwargs)
sampler.sample(runs)
if kwargs.pop('config', None):
click.echo('\n'.join('{} = {}'.format(k, v) for k, v in kwargs.items()))
else:
sampler_class = get_sampler_from_string(kwargs.pop('sampler'))
runs = kwargs.pop('runs')
sampler = sampler_class(setup, **kwargs)
sampler.sample(runs)


@cli.command()
Expand Down
101 changes: 96 additions & 5 deletions spotpy/examples/cli_cmf_lumped.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,107 @@
"""
Shows the usage of the matplotlib GUI
Shows the usage of the command line interface CLI
Needs at least Python 3.5
"""

from __future__ import division, print_function, unicode_literals


from spotpy.cli import main
#from spotpy.examples.spot_setup_cmf_lumped import SingleStorage as spot_setup
from spotpy.examples.spot_setup_rosenbrock import spot_setup
from . import algorithms, database, describe
import click
import inspect
import io
import os

def get_config_from_file():
"""
Gets the spotpy configuration from a config file 'spotpy.conf'.
Example:
sampler = mc
dbtype = csv
parallel = seq
# This is a comment
runs = 10
"""
config = {}
if os.path.exists('spotpy.conf'):
with io.open('spotpy.conf') as f:
for line in f:
if not line.strip().startswith('#'):
try:
k, v = line.split('=', 1)
config[k.strip()] = v.strip()
except ValueError:
pass
return config


def get_sampler_from_string(sampler_name):
return getattr(algorithms, sampler_name)


def make_type_from_module(module, *exclude):

def use(cl):
# Check if class name starts with an exclusion term
return inspect.isclass(cl) and not any([cl.__name__.startswith(ex) for ex in ('_', ) + exclude])
members = inspect.getmembers(module, use)
return click.Choice([n for n, m in members if not n.startswith('_')])


@click.group(context_settings=dict(help_option_names=['-h', '--help']))
def cli():
pass


@cli.command()
@click.pass_context
@click.option('--sampler', '-s', type=make_type_from_module(algorithms), default='mc',
help='Select the spotpy sampler')
@click.option('--dbformat', type=make_type_from_module(database, 'Pick'), default='ram',
help='The type of the database')
@click.option('--dbname', type=click.STRING, help='The name of the database, leave open for ram')
@click.option('--parallel', '-p', type=click.Choice(['seq', 'mpc', 'mpi']), default='seq',
help='Parallelization: seq = no parallelization, mpi = MPI (for clusters), mpc = multiprocessing')
@click.option('--runs', '-n', type=click.INT, default=1, help='Number of runs')
@click.option('--config', '-c', is_flag=True,
help='Print only the configuration, can be used to create a config file with your_model.py > spotpy.conf')
def run(ctx, **kwargs):
"""
Runs a sampler for automatic calibration
"""
setup = ctx.obj
if kwargs.pop('config', None):
click.echo('\n'.join('{} = {}'.format(k, v) for k, v in kwargs.items()))
else:
sampler_class = get_sampler_from_string(kwargs.pop('sampler'))
runs = kwargs.pop('runs')
sampler = sampler_class(setup, **kwargs)
sampler.sample(runs)


@cli.command()
@click.pass_context
def gui(ctx):
"""
Shows a GUI for manual calibration
"""
from spotpy.gui.mpl import GUI
setup = ctx.obj
gui = GUI(setup)
gui.show()


def main(setup):
# Prevent help text from wrapping
cli.help = '\b\n' + describe.setup(setup).replace('\n\n', '\n\b\n')
config = get_config_from_file()
cli(obj=setup, auto_envvar_prefix='SPOTPY', default_map=config)



if __name__ == '__main__':
setup = spot_setup()
main(setup)
main(setup)
6 changes: 3 additions & 3 deletions spotpy/examples/spot_setup_cmf_lumped.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ class DataProvider(object):

def __init__(self):
# Load data from file using numpy magic
data = np.recfromcsv('cmf_data/fulda_climate.csv')
data = np.recfromcsv('cmf_data/fulda_climate.csv', encoding='utf-8')

def bstr2date(bs):
"""Helper function to convert date byte string to datetime object"""
return datetime.datetime.strptime(bs.decode(), '%d.%m.%Y')
return datetime.datetime.strptime(bs, '%d.%m.%Y')

# Get begin, step and end from the date column
self.begin = bstr2date(data.date[0])
Expand Down Expand Up @@ -146,7 +146,7 @@ def setparameters(self, par=None):
"""
Sets the parameters of the model by creating the connections
"""
par = par or spotpy.parameter.create_set(self, random=False)
par = par or spotpy.parameter.create_set(self, valuetype='optguess')

# Some shortcuts to gain visibility
c = self.project[0]
Expand Down
20 changes: 20 additions & 0 deletions spotpy/unittests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from __future__ import division, print_function, unicode_literals

import click
from click.testing import CliRunner
from spotpy.cli import main, cli
from spotpy.examples.spot_setup_rosenbrock import spot_setup
import unittest
import sys

class TestCLI(unittest.TestCase):
def test_cli(self):

runner = CliRunner()
result = runner.invoke(cli, ['run', '--config'])
self.assertEqual(result.exit_code, 0)
cli.


if __name__ == '__main__':
unittest.main()

0 comments on commit 578ef1b

Please sign in to comment.