diff --git a/piker/cli.py b/piker/cli.py index 2a5b663a..3259785e 100644 --- a/piker/cli.py +++ b/piker/cli.py @@ -154,9 +154,8 @@ def watch(loglevel, broker, rate, name): @click.option('--loglevel', '-l', default='warning', help='Logging level') @click.pass_context def watchlists(ctx, loglevel): - """Watchlists cl commands and operations + """Watchlists commands and operations """ - # import pdb; pdb.set_trace() get_console_log(loglevel) # activate console logging wl.make_config_dir(_config_dir) ctx.obj = wl.ensure_watchlists(_watchlists_data_path) @@ -168,7 +167,7 @@ def watchlists(ctx, loglevel): def show(ctx, name): watchlist = ctx.obj click.echo(colorize_json( - watchlist if name is None else watchlist[name])) + watchlist if name is None else watchlist[name])) @watchlists.command(help='load passed in watchlist') @@ -176,17 +175,10 @@ def show(ctx, name): @click.pass_context def load(ctx, data): try: - wl.load_watchlists(data, _watchlists_data_path) + wl.write_watchlists(data, _watchlists_data_path) except (json.JSONDecodeError, IndexError): - click.echo('You must pass in a text respresentation of a json object. Try again.') - - -@watchlists.command(help='add a new watchlist') -@click.argument('name', nargs=1, required=True) -@click.pass_context -def new(ctx, name): - watchlist = ctx.obj - wl.new_group(name, watchlist, _watchlists_data_path) + click.echo('You have passed an invalid text respresentation of a ' + 'JSON object. Try again.') @watchlists.command(help='add ticker to watchlist') @@ -207,7 +199,7 @@ def remove(ctx, name, ticker_name): wl.remove_ticker(name, ticker_name, watchlist, _watchlists_data_path) -@watchlists.command(help='delete watchlist') +@watchlists.command(help='delete watchlist group') @click.argument('name', nargs=1, required=True) @click.pass_context def delete(ctx, name): @@ -223,9 +215,9 @@ def merge(ctx, watchlist_to_merge): wl.merge_watchlist(watchlist_to_merge, watchlist, _watchlists_data_path) -@watchlists.command(help='dump a text respresentation of a watchlist to console') +@watchlists.command(help='dump text respresentation of a watchlist to console') @click.argument('name', nargs=1, required=False) @click.pass_context def dump(ctx, name): watchlist = ctx.obj - print(json.dumps(watchlist)) + click.echo(json.dumps(watchlist)) diff --git a/piker/watchlists.py b/piker/watchlists.py index b2c95295..ad95ac06 100644 --- a/piker/watchlists.py +++ b/piker/watchlists.py @@ -6,11 +6,10 @@ from .log import get_logger log = get_logger(__name__) + def write_sorted_json(watchlist, path): for key in watchlist: - s = set(watchlist[key]) - watchlist[key] = list(s) - watchlist[key].sort() + watchlist[key] = sorted(list(set(watchlist[key]))) with open(path, 'w') as f: json.dump(watchlist, f, sort_keys=True) @@ -24,29 +23,23 @@ def make_config_dir(dir_path): def ensure_watchlists(file_path): mode = 'r' if os.path.isfile(file_path) else 'w' with open(file_path, mode) as f: - data = json.load(f) if not os.stat(file_path).st_size == 0 else {} - return data + return json.load(f) if not os.stat(file_path).st_size == 0 else {} -def load_watchlists(watchlist, path): - watchlist = json.loads(watchlist) - write_sorted_json(watchlist, path) - - -def new_group(name, watchlist, path): - watchlist.setdefault(name, []) - write_sorted_json(watchlist, path) +def write_watchlists(watchlist, path): + write_sorted_json(json.loads(watchlist), path) def add_ticker(name, ticker_name, watchlist, path): - if name in watchlist: - watchlist[name].append(str(ticker_name).upper()) + watchlist.setdefault(name, []).append(str(ticker_name).upper()) write_sorted_json(watchlist, path) def remove_ticker(name, ticker_name, watchlist, path): if name in watchlist: watchlist[name].remove(str(ticker_name).upper()) + if watchlist[name] == []: + del watchlist[name] write_sorted_json(watchlist, path) diff --git a/tests/test_cli.py b/tests/test_cli.py index 819aa021..35be3a01 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,11 +4,9 @@ CLI testing, dawg. import json import subprocess import pytest -import tempfile import os.path import logging -import piker.watchlists as wl def run(cmd): """Run cmd and check for zero return code. @@ -89,109 +87,3 @@ def test_api_method_not_found(nyse_tickers, capfd): out, err = capfd.readouterr() assert 'null' in out assert f'No api method `{bad_meth}` could be found?' in err - - -@pytest.fixture -def temp_dir(): - #Create temporary directory - with tempfile.TemporaryDirectory() as tempdir: - config_dir = os.path.join(tempdir, 'piker') - yield config_dir - - -@pytest.fixture -def piker_dir(temp_dir): - wl.make_config_dir(temp_dir) - path = os.path.join(temp_dir, 'watchlists.json') - yield path - - -def test_watchlist_is_sorted_and_saved_to_file(piker_dir): - """Ensure that watchlist is sorted and saved to file - """ - wl_temp = {'test': ['TEST.CN', 'AAA'], 'AA': ['TEST.CN']} - wl_sort = {'AA': ['TEST.CN'], 'test': ['AAA', 'TEST.CN']} - wl.write_sorted_json(wl_temp, piker_dir) - temp_sorted = wl.ensure_watchlists(piker_dir) - assert temp_sorted == wl_sort - - -def test_watchlists_config_dir_created(caplog, temp_dir): - """Ensure that a config directory is created - """ - with caplog.at_level(logging.DEBUG): - wl.make_config_dir(temp_dir) - assert len(caplog.records) == 1 - record = caplog.records[0] - assert record.levelname == 'DEBUG' - assert record.message == f"Creating config dir {temp_dir}" - assert os.path.isdir(temp_dir) - #Test that there is no error and that a log message is not generatd - #when trying to create a directory that already exists - with caplog.at_level(logging.DEBUG): - wl.make_config_dir(temp_dir) - assert len(caplog.records) == 1 - - -def test_watchlist_is_read_from_file(piker_dir): - """Ensure json info is read from file or an empty dict is generated - """ - wl_temp = wl.ensure_watchlists(piker_dir) - assert wl_temp == {} - wl_temp2 = '{"AA": ["TEST.CN"]}' - wl.load_watchlists(wl_temp2, piker_dir) - assert json.loads(wl_temp2) == wl.ensure_watchlists(piker_dir) - - -def test_watchlist_loaded(piker_dir): - """Ensure that text respresentation of a watchlist is loaded to file - """ - wl_temp = '{"test": ["TEST.CN"]}' - wl.load_watchlists(wl_temp, piker_dir) - wl_temp2 = wl.ensure_watchlists(piker_dir) - assert wl_temp == json.dumps(wl_temp2) - - -def test_new_watchlist_group_added(piker_dir): - """Ensure that a new watchlist key is added to the watchlists dictionary - """ - wl_temp = {} - wl.new_group('test', wl_temp, piker_dir) - wl_temp = wl.ensure_watchlists(piker_dir) - assert len(wl_temp.keys()) == 1 - - -def test_new_ticker_added(piker_dir): - """Ensure that a new ticker is added to a watchlist - """ - wl_temp = {'test': []} - wl.add_ticker('test', 'TEST.CN', wl_temp, piker_dir) - wl_temp = wl.ensure_watchlists(piker_dir) - assert len(wl_temp['test']) == 1 - - -def test_ticker_is_removed(piker_dir): - """Verify that passed in ticker is removed - """ - wl_temp = {'test': ['TEST.CN']} - wl.remove_ticker('test', 'TEST.CN', wl_temp, piker_dir) - wl_temp = wl.ensure_watchlists(piker_dir) - assert wl_temp == {'test': []} - -def test_group_is_deleted(piker_dir): - """Check that watchlist group is removed - """ - wl_temp = {'test': ['TEST.CN']} - wl.delete_group('test', wl_temp, piker_dir) - wl_temp = wl.ensure_watchlists(piker_dir) - assert wl_temp.get('test') == None - - -def test_watchlist_is_merged(piker_dir): - """Ensure that watchlist is merged - """ - wl_temp = {'test': ['TEST.CN']} - wl_temp2 = '{"test2": ["TEST2.CN"]}' - wl.merge_watchlist(wl_temp2, wl_temp, piker_dir) - wl_temp3 = wl.ensure_watchlists(piker_dir) - assert wl_temp3 == {'test': ['TEST.CN'], 'test2': ['TEST2.CN']} diff --git a/tests/test_watchlists.py b/tests/test_watchlists.py new file mode 100644 index 00000000..5325b81e --- /dev/null +++ b/tests/test_watchlists.py @@ -0,0 +1,109 @@ +""" +Watchlists testing. +""" +import json +import pytest +import tempfile +import os.path +import logging + +import piker.watchlists as wl + + +@pytest.fixture +def temp_dir(): + """Creates a path to a pretend config dir in a temporary directory for + + testing. + """ + with tempfile.TemporaryDirectory() as tempdir: + config_dir = os.path.join(tempdir, 'piker') + yield config_dir + + +@pytest.fixture +def piker_dir(temp_dir): + wl.make_config_dir(temp_dir) + yield os.path.join(temp_dir, 'watchlists.json') + + +def test_watchlist_is_sorted_no_dups_and_saved_to_file(piker_dir): + wl_temp = {'test': ['TEST.CN', 'AAA'], 'AA': ['TEST.CN', 'TEST.CN'], + 'AA': ['TEST.CN']} + wl_sort = {'AA': ['TEST.CN'], 'test': ['AAA', 'TEST.CN']} + wl.write_sorted_json(wl_temp, piker_dir) + temp_sorted = wl.ensure_watchlists(piker_dir) + assert temp_sorted == wl_sort + + +def test_watchlists_config_dir_created(caplog, temp_dir): + """Ensure that a config directory is created. + """ + with caplog.at_level(logging.DEBUG): + wl.make_config_dir(temp_dir) + assert len(caplog.records) == 1 + record = caplog.records[0] + assert record.levelname == 'DEBUG' + assert record.message == f"Creating config dir {temp_dir}" + assert os.path.isdir(temp_dir) + # Test that there is no error and that a log message is not generatd + # when trying to create a directory that already exists + with caplog.at_level(logging.DEBUG): + wl.make_config_dir(temp_dir) + # There should be no additional log message. + assert len(caplog.records) == 1 + + +def test_watchlist_is_read_from_file(piker_dir): + """Ensure json info is read from file or an empty dict is generated + + and that text respresentation of a watchlist is saved to file. + """ + wl_temp = wl.ensure_watchlists(piker_dir) + assert wl_temp == {} + wl_temp2 = '{"AA": ["TEST.CN"]}' + wl.write_watchlists(wl_temp2, piker_dir) + assert json.loads(wl_temp2) == wl.ensure_watchlists(piker_dir) + + +def test_new_ticker_added(piker_dir): + """Ensure that a new ticker is added to a watchlist for both cases. + """ + wl.add_ticker('test', 'TEST.CN', {'test': ['TEST2.CN']}, piker_dir) + wl_temp = wl.ensure_watchlists(piker_dir) + assert len(wl_temp['test']) == 2 + wl.add_ticker('test2', 'TEST.CN', wl_temp, piker_dir) + wl_temp = wl.ensure_watchlists(piker_dir) + assert wl_temp['test2'] + + +def test_ticker_is_removed(piker_dir): + """Verify that passed in ticker is removed and that a group is removed + + if no tickers left. + """ + wl_temp = {'test': ['TEST.CN', 'TEST2.CN'], 'test2': ['TEST.CN']} + wl.remove_ticker('test', 'TEST.CN', wl_temp, piker_dir) + wl.remove_ticker('test2', 'TEST.CN', wl_temp, piker_dir) + wl_temp = wl.ensure_watchlists(piker_dir) + assert wl_temp == {'test': ['TEST2.CN']} + assert not wl_temp.get('test2') + + +def test_group_is_deleted(piker_dir): + """Check that watchlist group is removed. + """ + wl_temp = {'test': ['TEST.CN']} + wl.delete_group('test', wl_temp, piker_dir) + wl_temp = wl.ensure_watchlists(piker_dir) + assert not wl_temp.get('test') + + +def test_watchlist_is_merged(piker_dir): + """Ensure that watchlist is merged. + """ + wl_temp = {'test': ['TEST.CN']} + wl_temp2 = '{"test2": ["TEST2.CN"]}' + wl.merge_watchlist(wl_temp2, wl_temp, piker_dir) + wl_temp3 = wl.ensure_watchlists(piker_dir) + assert wl_temp3 == {'test': ['TEST.CN'], 'test2': ['TEST2.CN']}