diff --git a/MANIFEST.in b/MANIFEST.in index c72c731..0a4b4b2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ include README.rst include requirements*.txt # package data included in source distribution and installation +include neurotic/global_config_template.txt include neurotic/example/example-notebook.ipynb include neurotic/example/metadata.yml include neurotic/tests/metadata-for-tests.yml diff --git a/README.rst b/README.rst index ab1ae56..3ae762d 100644 --- a/README.rst +++ b/README.rst @@ -254,10 +254,12 @@ The command line interface accepts other arguments too: .. code-block:: - usage: neurotic [-h] [-V] [--debug] [--no-lazy] [--thick-traces] - [--show-datetime] [--ui-scale {tiny,small,medium,large,huge}] + usage: neurotic [-h] [-V] [--debug | --no-debug] [--lazy | --no-lazy] + [--thick-traces | --no-thick-traces] + [--show-datetime | --no-show-datetime] + [--ui-scale {tiny,small,medium,large,huge}] [--theme {light,dark,original,printer-friendly}] - [--launch-example-notebook] + [--use-factory-defaults] [--launch-example-notebook] [file] [dataset] neurotic lets you curate, visualize, annotate, and share your behavioral ephys @@ -274,23 +276,34 @@ The command line interface accepts other arguments too: -h, --help show this help message and exit -V, --version show program's version number and exit --debug enable detailed log messages for debugging - --no-lazy do not use fast loading (default: use fast loading) + --no-debug disable detailed log messages for debugging (default) + --lazy enable fast loading (default) + --no-lazy disable fast loading --thick-traces enable support for traces with thick lines, which has - a performance cost (default: disable thick line - support) + a performance cost + --no-thick-traces disable support for traces with thick lines (default) --show-datetime display the real-world date and time, which may be inaccurate depending on file type and acquisition - software (default: do not display) + software + --no-show-datetime do not display the real-world date and time (default) --ui-scale {tiny,small,medium,large,huge} the scale of user interface elements, such as text (default: medium) --theme {light,dark,original,printer-friendly} a color theme for the GUI (default: light) + --use-factory-defaults + start with "factory default" settings, ignoring other + args and your global config file + + alternative modes: --launch-example-notebook launch Jupyter with an example notebook instead of starting the standalone app (other args will be ignored) + Defaults for arguments and options can be changed in a global config file, + .neurotic\neurotic-config.txt, located in your home directory. + Citing *neurotic* ----------------- diff --git a/docs/globalconfig.rst b/docs/globalconfig.rst new file mode 100644 index 0000000..867428d --- /dev/null +++ b/docs/globalconfig.rst @@ -0,0 +1,21 @@ +.. _global-config: + +Changing Default Behavior +========================= + +Default parameters used by the command line interface for launching the app, +such as which metadata file to open initially, can be configured using global +configuration settings located in ``.neurotic/neurotic-config.txt`` in your +home directory: + + - Windows: ``C:\Users\\.neurotic\neurotic-config.txt`` + - macOS: ``/Users//.neurotic/neurotic-config.txt`` + - Linux: ``/home//.neurotic/neurotic-config.txt`` + +The file can be opened easily using the "View global config file" menu action. + +If this file does not exist when *neurotic* is launched, the following template +is created for you: + +.. literalinclude:: ../neurotic/global_config_template.txt + :language: toml diff --git a/docs/index.rst b/docs/index.rst index bf69ec9..690683c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -28,6 +28,7 @@ at the same datasets! citations metadata examples + globalconfig api releasenotes diff --git a/neurotic/__init__.py b/neurotic/__init__.py index 3270b81..a97f6b5 100644 --- a/neurotic/__init__.py +++ b/neurotic/__init__.py @@ -5,14 +5,25 @@ Curate, visualize, annotate, and share your behavioral ephys data using Python """ -from .version import version as __version__ -from .version import git_revision as __git_revision__ - - import os import sys +import shutil +import copy +import pkg_resources +import collections.abc import logging import logging.handlers +import toml + +from .version import version as __version__ +from .version import git_revision as __git_revision__ + + +# set the user's directory for global settings file, logs, and more +neurotic_dir = os.path.join(os.path.expanduser('~'), '.neurotic') +if not os.path.exists(neurotic_dir): + os.mkdir(neurotic_dir) + class FileLoggingFormatter(logging.Formatter): """ @@ -42,10 +53,7 @@ def format(self, record): # set the file path for logging -log_dir = os.path.join(os.path.expanduser('~'), '.neurotic') -if not os.path.exists(log_dir): - os.mkdir(log_dir) -log_file = os.path.join(log_dir, 'neurotic-log.txt') +log_file = os.path.join(neurotic_dir, 'neurotic-log.txt') # set the default level for logging to INFO unless it was set to a custom level # before importing the package @@ -69,6 +77,77 @@ def format(self, record): logger.addHandler(logger_streamhandler) +global_config = { + 'defaults': { + # defaults used by the command line interface + 'file': None, + 'dataset': None, + 'debug': False, + 'lazy': True, + 'thick_traces': False, + 'show_datetime': False, + 'ui_scale': 'medium', + 'theme': 'light', + }, +} + +# keep a copy of the original config before it is modified +_global_config_factory_defaults = copy.deepcopy(global_config) + +# the global config file is a text file in TOML format owned by the user that +# allows alternate defaults to be specified to replace those in global_config +global_config_file = os.path.join(neurotic_dir, 'neurotic-config.txt') + +if not os.path.exists(global_config_file): + # copy a template global config file containing commented-out defaults + shutil.copy( + pkg_resources.resource_filename( + 'neurotic', 'global_config_template.txt'), + global_config_file) + +def update_dict(d, d_new): + """ + Recursively update the contents of a dictionary. Unlike dict.update(), this + function preserves items in inner dictionaries that are absent from d_new. + + For example, if given + + >>> d = {'x': 0, 'inner': {'a': 1, 'b': 2}} + >>> d_new = {'inner': {'c': 3}} + + then using d.update(d_new) will entirely replace d['inner'] with + d_new['inner']: + + >>> d.update(d_new) + >>> d == {'x': 0, 'inner': {'c': 3}} + + In contrast, update_dict(d, d_new) will preserve items found in d['inner'] + but not in d_new['inner']: + + >>> update_dict(d, d_new) + >>> d == {'x': 0, 'inner': {'a': 1, 'b': 2, 'c': 3}} + """ + for k_new, v_new in d_new.items(): + if isinstance(v_new, collections.abc.Mapping): + d[k_new] = update_dict(d.get(k_new, {}), v_new) + else: + d[k_new] = v_new + return d + +def update_global_config_from_file(file=global_config_file): + """ + Update the global_config dictionary with data from the global config file, + using recursion to traverse nested dictionaries. + """ + with open(file, 'r') as f: + update_dict(global_config, toml.loads(f.read())) + +try: + update_global_config_from_file() +except Exception as e: + logger.error(f'Ignoring global config file due to parsing error ({global_config_file}): {e}') + + from .datasets import * from .gui import * from .scripts import * diff --git a/neurotic/global_config_template.txt b/neurotic/global_config_template.txt new file mode 100644 index 0000000..8366b0a --- /dev/null +++ b/neurotic/global_config_template.txt @@ -0,0 +1,25 @@ +# --- neurotic global config -------------------------------------------------- +# Use this file to configure the way neurotic behaves. This file uses the TOML +# format. + +[defaults] +# When the app is launched, the following customizable defaults are used unless +# overridden by command line arguments. Example uses include: +# - Uncomment the "file" parameter and insert the path to your favorite +# metadata file so that it always opens automatically +# - Uncomment the "lazy" parameter and set it to false to always disable fast +# loading, ensuring that expensive procedures like spike detection and +# filtering are performed by default +# To open the example metadata file by default, either leave the "file" +# parameter commented or set it to "example". To initially select the first +# dataset in the file, either leave the "dataset" parameter commented or set it +# to "none". + +# file = "example" +# dataset = "none" +# debug = false +# lazy = true +# thick_traces = false +# show_datetime = false +# ui_scale = "medium" +# theme = "light" diff --git a/neurotic/gui/standalone.py b/neurotic/gui/standalone.py index fe39d53..72841fd 100644 --- a/neurotic/gui/standalone.py +++ b/neurotic/gui/standalone.py @@ -18,7 +18,7 @@ import neo from ephyviewer import QT, QT_MODE -from .. import __version__, _elephant_tools, default_log_level, log_file +from .. import __version__, _elephant_tools, global_config_file, default_log_level, log_file from ..datasets import MetadataSelector, load_dataset from ..datasets.metadata import _selector_labels from ..gui.config import EphyviewerConfigurator, available_themes, available_ui_scales @@ -221,6 +221,11 @@ def create_menus(self): do_toggle_show_datetime.setChecked(self.show_datetime) do_toggle_show_datetime.triggered.connect(self.toggle_show_datetime) + options_menu.addSeparator() + + do_view_global_config_file = options_menu.addAction('View global &config file') + do_view_global_config_file.triggered.connect(self.view_global_config_file) + appearance_menu = menu_bar.addMenu(self.tr('&Appearance')) ui_scale_group = QT.QActionGroup(appearance_menu) @@ -424,6 +429,19 @@ def on_load_dataset_finished(self): self.metadata_selector.setEnabled(True) self.stacked_layout.setCurrentIndex(0) # show metadata selector + def view_global_config_file(self): + """ + Open the global config file in an editor. + """ + + try: + open_path_with_default_program(global_config_file) + except FileNotFoundError as e: + logger.error(f'The global config file was not found: {e}') + self.statusBar().showMessage('ERROR: The global config file could ' + 'not be found', msecs=5000) + return + def toggle_debug_logging(self, checked): """ Toggle log filtering level between its original level and debug mode diff --git a/neurotic/scripts.py b/neurotic/scripts.py index 8069911..5068590 100644 --- a/neurotic/scripts.py +++ b/neurotic/scripts.py @@ -10,6 +10,7 @@ .. autofunction:: launch_example_notebook """ +import os import sys import argparse import subprocess @@ -17,7 +18,7 @@ from ephyviewer import QT, mkQApp -from . import __version__ +from . import __version__, global_config, _global_config_factory_defaults, global_config_file, default_log_level from .datasets.data import load_dataset from .gui.config import EphyviewerConfigurator, available_themes, available_ui_scales from .gui.standalone import MainWindow @@ -35,45 +36,116 @@ def parse_args(argv): neurotic lets you curate, visualize, annotate, and share your behavioral ephys data. """ - parser = argparse.ArgumentParser(description=description) - parser.add_argument('file', nargs='?', default=None, - help='the path to a metadata YAML file (default: an ' \ - 'example file)') - parser.add_argument('dataset', nargs='?', default=None, - help='the name of a dataset in the metadata file to ' \ - 'select initially (default: the first entry in ' \ - 'the metadata file)') + epilog = f""" + Defaults for arguments and options can be changed in a global config file, + {os.path.relpath(global_config_file, os.path.expanduser('~'))}, located in + your home directory. + """ + + parser = argparse.ArgumentParser(description=description, epilog=epilog) + + defaults = global_config['defaults'] + parser.set_defaults(**defaults) + + parser.add_argument('file', nargs='?', + help='the path to a metadata YAML file' + f' (default: {"an example file" if defaults["file"] is None else defaults["file"] + "; to force the example, use: example"})') - parser.add_argument('-V', '--version', action='version', + parser.add_argument('dataset', nargs='?', + help='the name of a dataset in the metadata file to ' + 'select initially' + f' (default: {"the first entry in the metadata file" if defaults["dataset"] is None else defaults["dataset"] + "; to force the first, use: - none"})') + + parser.add_argument('-V', '--version', + action='version', version='neurotic {}'.format(__version__)) - parser.add_argument('--debug', action='store_true', dest='debug', - help='enable detailed log messages for debugging') - parser.add_argument('--no-lazy', action='store_false', dest='lazy', - help='do not use fast loading (default: use fast ' \ - 'loading)') - parser.add_argument('--thick-traces', action='store_true', dest='thick', - help='enable support for traces with thick lines, ' \ - 'which has a performance cost (default: ' \ - 'disable thick line support)') - parser.add_argument('--show-datetime', action='store_true', dest='datetime', - help='display the real-world date and time, which ' \ - 'may be inaccurate depending on file type and ' \ - 'acquisition software (default: do not display)') - parser.add_argument('--ui-scale', dest='ui_scale', - choices=available_ui_scales, default='medium', - help='the scale of user interface elements, such as ' \ - 'text (default: medium)') - parser.add_argument('--theme', choices=available_themes, default='light', - help='a color theme for the GUI (default: light)') - parser.add_argument('--launch-example-notebook', action='store_true', - help='launch Jupyter with an example notebook ' \ - 'instead of starting the standalone app (other ' \ - 'args will be ignored)') + group = parser.add_mutually_exclusive_group() + group.add_argument('--debug', dest='debug', + action='store_true', + help='enable detailed log messages for debugging' + f'{" (default)" if defaults["debug"] else ""}') + group.add_argument('--no-debug', dest='debug', + action='store_false', + help='disable detailed log messages for debugging' + f'{" (default)" if not defaults["debug"] else ""}') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--lazy', dest='lazy', + action='store_true', + help='enable fast loading' + f'{" (default)" if defaults["lazy"] else ""}') + group.add_argument('--no-lazy', dest='lazy', + action='store_false', + help='disable fast loading' + f'{" (default)" if not defaults["lazy"] else ""}') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--thick-traces', dest='thick_traces', + action='store_true', + help='enable support for traces with thick lines, ' + 'which has a performance cost' + f'{" (default)" if defaults["thick_traces"] else ""}') + group.add_argument('--no-thick-traces', dest='thick_traces', + action='store_false', + help='disable support for traces with thick lines' + f'{" (default)" if not defaults["thick_traces"] else ""}') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--show-datetime', dest='show_datetime', + action='store_true', + help='display the real-world date and time, which ' + 'may be inaccurate depending on file type and ' + 'acquisition software' + f'{" (default)" if defaults["show_datetime"] else ""}') + group.add_argument('--no-show-datetime', dest='show_datetime', + action='store_false', + help='do not display the real-world date and time' + f'{" (default)" if not defaults["show_datetime"] else ""}') + + parser.add_argument('--ui-scale', dest='ui_scale', + choices=available_ui_scales, + help='the scale of user interface elements, such as ' + 'text' + f' (default: {defaults["ui_scale"]})') + + parser.add_argument('--theme', dest='theme', + choices=available_themes, + help='a color theme for the GUI' + f' (default: {defaults["theme"]})') + + parser.add_argument('--use-factory-defaults', + action='store_true', + help='start with "factory default" settings, ignoring ' + 'other args and your global config file') + + group = parser.add_argument_group('alternative modes') + group.add_argument('--launch-example-notebook', + action='store_true', + help='launch Jupyter with an example notebook ' + 'instead of starting the standalone app (other ' + 'args will be ignored)') args = parser.parse_args(argv[1:]) + if args.use_factory_defaults: + # replace every argument with the factory default + for k, v in _global_config_factory_defaults['defaults'].items(): + setattr(args, k, v) + + # these special values for the positional arguments can be used to override + # the defaults set in the global config file with the defaults normally set + # in the absence of global config file settings + if args.file == '-': + args.file = defaults['file'] + elif args.file == 'example': + args.file = None + if args.dataset == '-': + args.dataset = defaults['dataset'] + elif args.dataset == 'none': + args.dataset = None + if args.debug: logger.parent.setLevel(logging.DEBUG) @@ -86,6 +158,20 @@ def parse_args(argv): # not carry over into the kernel started by Jupyter logger.debug('Debug messages enabled') + else: + # this should only be necessary if parse_args is called with --no-debug + # after having previously enabled debug messages in the current session + # (such as during unit testing) + + logger.parent.setLevel(default_log_level) + + # raise the threshold for PyAV messages printed to the console from + # warning to critical + logging.getLogger('libav').setLevel(logging.CRITICAL) + + logger.debug(f'Global config: {global_config}') + logger.debug(f'Parsed arguments: {args}') + return args def win_from_args(args): @@ -95,8 +181,8 @@ def win_from_args(args): win = MainWindow(file=args.file, initial_selection=args.dataset, lazy=args.lazy, theme=args.theme, ui_scale=args.ui_scale, - support_increased_line_width=args.thick, - show_datetime=args.datetime) + support_increased_line_width=args.thick_traces, + show_datetime=args.show_datetime) return win def launch_example_notebook(): diff --git a/neurotic/tests/test_cli.py b/neurotic/tests/test_cli.py index 7ac4c69..6ceb547 100644 --- a/neurotic/tests/test_cli.py +++ b/neurotic/tests/test_cli.py @@ -10,6 +10,8 @@ import tempfile import pkg_resources import copy +import fileinput +import re import yaml import unittest @@ -24,19 +26,31 @@ class CLITestCase(unittest.TestCase): def setUp(self): - self.default_file = pkg_resources.resource_filename( - 'neurotic', 'example/metadata.yml') - self.default_dataset = 'Aplysia feeding' - - # make a copy of the default file in a temp directory self.temp_dir = tempfile.TemporaryDirectory(prefix='neurotic-') - self.temp_file = shutil.copy(self.default_file, self.temp_dir.name) - # add a second dataset + # save the current global config settings so they can be restored + # during tear-down + self.original_config = copy.deepcopy(neurotic.global_config) + + # reset global config settings to their factory defaults (use deepcopy + # so that _global_config_factory_defaults is not changed during tests) + neurotic.global_config.clear() + neurotic.global_config.update(copy.deepcopy( + neurotic._global_config_factory_defaults)) + + # get parameters for the example + self.example_file = pkg_resources.resource_filename( + 'neurotic', 'example/metadata.yml') + self.example_dataset = 'Aplysia feeding' + + # make a copy of the example metadata file in the temp directory and + # then add an additional dataset to it + self.temp_file = shutil.copy(self.example_file, self.temp_dir.name) with open(self.temp_file) as f: metadata = yaml.safe_load(f) - metadata['zzz_alphabetically_last'] = copy.deepcopy( - metadata['Aplysia feeding']) + self.duplicate_dataset = 'zzz_alphabetically_last' + metadata[self.duplicate_dataset] = copy.deepcopy( + metadata[self.example_dataset]) with open(self.temp_file, 'w') as f: f.write(yaml.dump(metadata)) @@ -44,6 +58,38 @@ def tearDown(self): # remove the temp directory self.temp_dir.cleanup() + # restore the original global config + neurotic.global_config.clear() + neurotic.global_config.update(self.original_config) + + def test_global_config_template(self): + """Test that the global config template matches the factory defaults""" + + # load the template global config file by first copying it to the temp + # directory, uncommenting every parameter, and then updating + # global_config using the modified file + self.template_global_config_file = pkg_resources.resource_filename( + 'neurotic', 'global_config_template.txt') + self.temp_global_config_file = shutil.copy( + self.template_global_config_file, self.temp_dir.name) + with fileinput.input(self.temp_global_config_file, inplace=True) as f: + for line in f: + if re.match('#.*=.*', line): + line = line[1:] + print(line, end='') # stdout is redirected into the file + neurotic.update_global_config_from_file(self.temp_global_config_file) + + # substitute special values + if neurotic.global_config['defaults']['file'] == 'example': + neurotic.global_config['defaults']['file'] = None + if neurotic.global_config['defaults']['dataset'] == 'none': + neurotic.global_config['defaults']['dataset'] = None + + self.assertEqual(neurotic.global_config, + neurotic._global_config_factory_defaults, + 'global_config loaded from template global config ' + 'file differs from factory defaults') + def test_cli_installed(self): """Test that the command line interface is installed""" self.assertIsNotNone(shutil.which('neurotic'), 'path to cli not found') @@ -63,26 +109,34 @@ def test_version(self): 'version\'s stdout has unexpected content') def test_cli_defaults(self): - """Test CLI default values""" + """Test CLI default values match factory defaults""" argv = ['neurotic'] args = neurotic.parse_args(argv) app = mkQApp() win = neurotic.win_from_args(args) - self.assertFalse(win.do_toggle_debug_logging.isChecked(), - 'debug logging enabled without --debug') - self.assertTrue(win.lazy, 'lazy loading disabled without --no-lazy') - self.assertFalse(win.support_increased_line_width, - 'thick traces enabled without --thick-traces') - self.assertFalse(win.show_datetime, - 'datetime enabled without --show-datetime') - self.assertEqual(win.ui_scale, 'medium', - 'ui_scale changed without --ui-scale') - self.assertEqual(win.theme, 'light', 'theme changed without --theme') - self.assertEqual(win.metadata_selector.file, self.default_file, - 'file was not set to default') + + # should match factory defaults because setUp() explicitly reset the + # defaults to the factory defaults + factory_defaults = neurotic._global_config_factory_defaults['defaults'] + self.assertEqual(win.do_toggle_debug_logging.isChecked(), + factory_defaults['debug'], + 'debug setting has unexpected default') + self.assertEqual(win.lazy, factory_defaults['lazy'], + 'lazy setting has unexpected default') + self.assertEqual(win.support_increased_line_width, + factory_defaults['thick_traces'], + 'thick traces setting has unexpected default') + self.assertEqual(win.show_datetime, factory_defaults['show_datetime'], + 'show_datetime has unexpected default') + self.assertEqual(win.ui_scale, factory_defaults['ui_scale'], + 'ui_scale has unexpected default') + self.assertEqual(win.theme, factory_defaults['theme'], + 'theme has unexpected default') + self.assertEqual(win.metadata_selector.file, self.example_file, + 'file has unexpected default') self.assertEqual(win.metadata_selector._selection, - self.default_dataset, - 'dataset was not set to default dataset') + self.example_dataset, + 'dataset has unexpected default') def test_debug(self): """Test that --debug enables logging of debug messages""" @@ -93,6 +147,23 @@ def test_debug(self): self.assertTrue(win.do_toggle_debug_logging.isChecked(), 'debug logging disabled with --debug') + def test_no_debug(self): + """Test that --no-debug disables logging of debug messages""" + argv = ['neurotic', '--no-debug'] + args = neurotic.parse_args(argv) + app = mkQApp() + win = neurotic.win_from_args(args) + self.assertFalse(win.do_toggle_debug_logging.isChecked(), + 'debug logging enabled with --no-debug') + + def test_lazy(self): + """Test that --lazy enables lazy loading""" + argv = ['neurotic', '--lazy'] + args = neurotic.parse_args(argv) + app = mkQApp() + win = neurotic.win_from_args(args) + self.assertTrue(win.lazy, 'lazy loading disbled with --lazy') + def test_no_lazy(self): """Test that --no-lazy disables lazy loading""" argv = ['neurotic', '--no-lazy'] @@ -110,6 +181,15 @@ def test_thick_traces(self): self.assertTrue(win.support_increased_line_width, 'thick traces disabled with --thick-traces') + def test_no_thick_traces(self): + """Test that --no-thick-traces disables support for thick traces""" + argv = ['neurotic', '--no-thick-traces'] + args = neurotic.parse_args(argv) + app = mkQApp() + win = neurotic.win_from_args(args) + self.assertFalse(win.support_increased_line_width, + 'thick traces enabled with --no-thick-traces') + def test_show_datetime(self): """Test that --show-datetime enables display of real-world datetime""" argv = ['neurotic', '--show-datetime'] @@ -119,6 +199,15 @@ def test_show_datetime(self): self.assertTrue(win.show_datetime, 'datetime not displayed with --show-datetime') + def test_no_show_datetime(self): + """Test that --no-show-datetime hides the real-world datetime""" + argv = ['neurotic', '--no-show-datetime'] + args = neurotic.parse_args(argv) + app = mkQApp() + win = neurotic.win_from_args(args) + self.assertFalse(win.show_datetime, + 'datetime displayed with --no-show-datetime') + def test_ui_scale(self): """Test that --ui-scale changes the ui_scale""" app = mkQApp() @@ -139,6 +228,16 @@ def test_theme(self): win = neurotic.win_from_args(args) self.assertEqual(win.theme, theme, 'unexpected theme') + def test_use_factory_defaults(self): + """Test that --use-factory-defaults resets all defaults""" + for k in neurotic.global_config['defaults']: + neurotic.global_config['defaults'][k] = 'bad value' + argv = ['neurotic', '--use-factory-defaults'] + args = neurotic.parse_args(argv) + for k, v in neurotic._global_config_factory_defaults['defaults'].items(): + self.assertEqual(getattr(args, k), v, + f'args.{k} was not reset to factory default') + def test_file(self): """Test that metadata file can be set""" argv = ['neurotic', self.temp_file] @@ -149,11 +248,24 @@ def test_file(self): def test_dataset(self): """Test that dataset can be set""" - argv = ['neurotic', self.temp_file, 'zzz_alphabetically_last'] + argv = ['neurotic', self.temp_file, self.duplicate_dataset] + args = neurotic.parse_args(argv) + win = neurotic.win_from_args(args) + self.assertEqual(win.metadata_selector._selection, + self.duplicate_dataset, + 'dataset was not changed correctly') + + def test_example_file_and_first_dataset_overrides(self): + """Test that 'example' and 'none' open first dataset in example file""" + neurotic.global_config['defaults']['file'] = 'some other file' + neurotic.global_config['defaults']['dataset'] = 'some other dataset' + argv = ['neurotic', 'example', 'none'] args = neurotic.parse_args(argv) win = neurotic.win_from_args(args) + self.assertEqual(win.metadata_selector.file, self.example_file, + 'file was not changed correctly') self.assertEqual(win.metadata_selector._selection, - 'zzz_alphabetically_last', + self.example_dataset, 'dataset was not changed correctly') if __name__ == '__main__': diff --git a/requirements.txt b/requirements.txt index f425f86..ecbc119 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ quantities requests scipy setuptools +toml tqdm