Merge pull request #33 from pikers/wl_builtins

Watchlist builtins
kivy_mainline_and_py3.8
goodboy 2018-04-10 22:31:50 -04:00 committed by GitHub
commit d4eb5ffb59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 84 deletions

View File

@ -49,15 +49,21 @@ async def quote(brokermod: ModuleType, tickers: [str]) -> dict:
async def wait_for_network(get_quotes, sleep=1): async def wait_for_network(get_quotes, sleep=1):
"""Wait until the network comes back up. """Wait until the network comes back up.
""" """
down = False
while True: while True:
try: try:
with trio.move_on_after(1) as cancel_scope: with trio.move_on_after(1) as cancel_scope:
return await get_quotes() quotes = await get_quotes()
if down:
log.warn("Network is back up")
return quotes
if cancel_scope.cancelled_caught: if cancel_scope.cancelled_caught:
log.warn("Quote query timed out") log.warn("Quote query timed out")
continue continue
except socket.gaierror: except socket.gaierror:
log.warn(f"Network is down waiting for reestablishment...") if not down: # only report/log network down once
log.warn(f"Network is down waiting for re-establishment...")
down = True
await trio.sleep(sleep) await trio.sleep(sleep)

View File

@ -120,35 +120,15 @@ def watch(loglevel, broker, rate, name):
from .ui.watchlist import _async_main from .ui.watchlist import _async_main
log = get_console_log(loglevel) # activate console logging log = get_console_log(loglevel) # activate console logging
brokermod = get_brokermod(broker) brokermod = get_brokermod(broker)
watchlists_base = {
'cannabis': [
'EMH.VN', 'LEAF.TO', 'HVT.VN', 'HMMJ.TO', 'APH.TO',
'CBW.VN', 'TRST.CN', 'VFF.TO', 'ACB.TO', 'ABCN.VN',
'APH.TO', 'MARI.CN', 'WMD.VN', 'LEAF.TO', 'THCX.VN',
'WEED.TO', 'NINE.VN', 'RTI.VN', 'SNN.CN', 'ACB.TO',
'OGI.VN', 'IMH.VN', 'FIRE.VN', 'EAT.CN',
'WMD.VN', 'HEMP.VN', 'CALI.CN', 'RQB.CN', 'MPX.CN',
'SEED.TO', 'HMJR.TO', 'CMED.TO', 'PAS.VN',
'CRON',
],
'dad': ['GM', 'TSLA', 'DOL.TO', 'CIM', 'SPY', 'SHOP.TO'],
'pharma': ['ATE.VN'],
'indexes': ['SPY', 'DAX', 'QQQ', 'DIA'],
}
watchlist_from_file = wl.ensure_watchlists(_watchlists_data_path) watchlist_from_file = wl.ensure_watchlists(_watchlists_data_path)
watchlists = wl.merge_watchlist(watchlist_from_file, watchlists_base) watchlists = wl.merge_watchlist(watchlist_from_file, wl._builtins)
# broker_conf_path = os.path.join(
# click.get_app_dir('piker'), 'watchlists.json')
# from piker.testing import _quote_streamer as brokermod
broker_limit = getattr(brokermod, '_rate_limit', float('inf')) broker_limit = getattr(brokermod, '_rate_limit', float('inf'))
if broker_limit < rate: if broker_limit < rate:
rate = broker_limit rate = broker_limit
log.warn(f"Limiting {brokermod.__name__} query rate to {rate}/sec") log.warn(f"Limiting {brokermod.__name__} query rate to {rate}/sec")
trio.run(_async_main, name, watchlists[name], brokermod, rate) trio.run(_async_main, name, watchlists[name], brokermod, rate)
# broker_conf_path = os.path.join(
# click.get_app_dir('piker'), 'watchlists.json')
# from piker.testing import _quote_streamer as brokermod
@cli.group() @cli.group()
@ -169,7 +149,7 @@ def watchlists(ctx, loglevel, config_dir):
@click.argument('name', nargs=1, required=False) @click.argument('name', nargs=1, required=False)
@click.pass_context @click.pass_context
def show(ctx, name): def show(ctx, name):
watchlist = ctx.obj['watchlist'] watchlist = wl.merge_watchlist(ctx.obj['watchlist'], wl._builtins)
click.echo(colorize_json( click.echo(colorize_json(
watchlist if name is None else watchlist[name])) watchlist if name is None else watchlist[name]))
@ -179,7 +159,7 @@ def show(ctx, name):
@click.pass_context @click.pass_context
def load(ctx, data): def load(ctx, data):
try: try:
wl.write_sorted_json(json.loads(data), ctx.obj['path']) wl.write_to_file(json.loads(data), ctx.obj['path'])
except (json.JSONDecodeError, IndexError): except (json.JSONDecodeError, IndexError):
click.echo('You have passed an invalid text respresentation of a ' click.echo('You have passed an invalid text respresentation of a '
'JSON object. Try again.') 'JSON object. Try again.')
@ -192,7 +172,7 @@ def load(ctx, data):
def add(ctx, name, ticker_name): def add(ctx, name, ticker_name):
watchlist = wl.add_ticker(name, ticker_name, watchlist = wl.add_ticker(name, ticker_name,
ctx.obj['watchlist']) ctx.obj['watchlist'])
wl.write_sorted_json(watchlist, ctx.obj['path']) wl.write_to_file(watchlist, ctx.obj['path'])
@watchlists.command(help='remove ticker from watchlist') @watchlists.command(help='remove ticker from watchlist')
@ -200,8 +180,18 @@ def add(ctx, name, ticker_name):
@click.argument('ticker_name', nargs=1, required=True) @click.argument('ticker_name', nargs=1, required=True)
@click.pass_context @click.pass_context
def remove(ctx, name, ticker_name): def remove(ctx, name, ticker_name):
watchlist = wl.remove_ticker(name, ticker_name, ctx.obj['watchlist']) try:
wl.write_sorted_json(watchlist, ctx.obj['path']) watchlist = wl.remove_ticker(name, ticker_name, ctx.obj['watchlist'])
except KeyError:
log.error(f"No watchlist with name `{name}` could be found?")
except ValueError:
if name in wl._builtins and ticker_name in wl._builtins[name]:
log.error(f"Can not remove ticker `{ticker_name}` from built-in "
f"list `{name}`")
else:
log.error(f"Ticker `{ticker_name}` not found in list `{name}`")
else:
wl.write_to_file(watchlist, ctx.obj['path'])
@watchlists.command(help='delete watchlist group') @watchlists.command(help='delete watchlist group')
@ -209,7 +199,7 @@ def remove(ctx, name, ticker_name):
@click.pass_context @click.pass_context
def delete(ctx, name): def delete(ctx, name):
watchlist = wl.delete_group(name, ctx.obj['watchlist']) watchlist = wl.delete_group(name, ctx.obj['watchlist'])
wl.write_sorted_json(watchlist, ctx.obj['path']) wl.write_to_file(watchlist, ctx.obj['path'])
@watchlists.command(help='merge a watchlist from another user') @watchlists.command(help='merge a watchlist from another user')
@ -218,7 +208,7 @@ def delete(ctx, name):
def merge(ctx, watchlist_to_merge): def merge(ctx, watchlist_to_merge):
merged_watchlist = wl.merge_watchlist(json.loads(watchlist_to_merge), merged_watchlist = wl.merge_watchlist(json.loads(watchlist_to_merge),
ctx.obj['watchlist']) ctx.obj['watchlist'])
wl.write_sorted_json(merged_watchlist, ctx.obj['path']) wl.write_to_file(merged_watchlist, ctx.obj['path'])
@watchlists.command(help='dump text respresentation of a watchlist to console') @watchlists.command(help='dump text respresentation of a watchlist to console')

View File

@ -6,12 +6,16 @@ from .log import get_logger
log = get_logger(__name__) log = get_logger(__name__)
_builtins = {
'indexes': ['SPY', 'DAX', 'QQQ', 'DIA'],
}
def write_sorted_json(watchlist, path):
def write_to_file(watchlist, path):
for key in watchlist: for key in watchlist:
watchlist[key] = sorted(list(set(watchlist[key]))) watchlist[key] = sorted(list(set(watchlist[key])))
with open(path, 'w') as f: with open(path, 'w') as f:
json.dump(watchlist, f, sort_keys=True) json.dump(watchlist, f, sort_keys=True, indent=4)
def make_config_dir(dir_path): def make_config_dir(dir_path):
@ -32,10 +36,9 @@ def add_ticker(name, ticker_name, watchlist):
def remove_ticker(name, ticker_name, watchlist): def remove_ticker(name, ticker_name, watchlist):
if name in watchlist: watchlist[name].remove(str(ticker_name).upper())
watchlist[name].remove(str(ticker_name).upper()) if watchlist[name] == []:
if watchlist[name] == []: del watchlist[name]
del watchlist[name]
return watchlist return watchlist

View File

@ -6,11 +6,8 @@ import subprocess
import pytest import pytest
import tempfile import tempfile
import os.path import os.path
import logging
import piker.watchlists as wl import piker.watchlists as wl
import piker.cli as cli
from piker.log import colorize_json
def run(cmd, *args): def run(cmd, *args):
@ -104,71 +101,77 @@ def temp_dir():
@pytest.fixture @pytest.fixture
def piker_dir(temp_dir): def ex_watchlists():
"""Made up watchlists to use for expected outputs.
"""
watchlists = {
'dad': list(sorted(['GM', 'TSLA', 'DOL.TO', 'CIM', 'SPY', 'SHOP.TO'])),
'pharma': ['ATE.VN'],
}
return watchlists
@pytest.fixture
def ex_watchlists_wbi(ex_watchlists):
"""Made up watchlists + built-in list(s) to use for expected outputs.
"""
with_builtins = ex_watchlists.copy()
with_builtins.update(wl._builtins)
return with_builtins
@pytest.fixture
def piker_dir(temp_dir, ex_watchlists):
wl.make_config_dir(temp_dir) wl.make_config_dir(temp_dir)
json_file_path = os.path.join(temp_dir, 'watchlists.json') json_file_path = os.path.join(temp_dir, 'watchlists.json')
watchlist = { # push test watchlists to file without built-ins
'dad': ['GM', 'TSLA', 'DOL.TO', 'CIM', 'SPY', 'SHOP.TO'], to_write = ex_watchlists.copy()
'pharma': ['ATE.VN'], wl.write_to_file(to_write, json_file_path)
'indexes': ['SPY', 'DAX', 'QQQ', 'DIA'],
}
wl.write_sorted_json(watchlist, json_file_path)
yield json_file_path yield json_file_path
def test_show_watchlists(capfd, piker_dir): def test_show_watchlists(capfd, piker_dir, ex_watchlists_wbi):
"""Ensure a watchlist is printed. """Ensure all watchlists are printed as json to stdout.
(Can't seem to get pretty formatting to work, pytest thing?)
""" """
expected_out = json.dumps({ expected_out = json.dumps(ex_watchlists_wbi, indent=4, sort_keys=True)
'dad': ['CIM', 'DOL.TO', 'GM', 'SHOP.TO', 'SPY', 'TSLA'],
'indexes': ['DAX', 'DIA', 'QQQ', 'SPY'],
'pharma': ['ATE.VN'],
}, indent=4)
run(f"piker watchlists -d {piker_dir} show") run(f"piker watchlists -d {piker_dir} show")
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert out.strip() == expected_out assert out.strip() == expected_out
def test_dump_watchlists(capfd, piker_dir): def test_dump_watchlists(capfd, piker_dir, ex_watchlists):
"""Ensure watchlist is dumped. """Ensure watchlist is dumped without built-in lists.
""" """
expected_out = json.dumps({ expected_out = json.dumps(ex_watchlists)
'dad': ['CIM', 'DOL.TO', 'GM', 'SHOP.TO', 'SPY', 'TSLA'],
'indexes': ['DAX', 'DIA', 'QQQ', 'SPY'],
'pharma': ['ATE.VN'],
})
run(f"piker watchlists -d {piker_dir} dump") run(f"piker watchlists -d {piker_dir} dump")
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert out.strip() == expected_out assert out.strip() == expected_out
def test_ticker_added_to_watchlists(capfd, piker_dir): def test_ticker_added_to_watchlists(capfd, piker_dir, ex_watchlists):
expected_out = { ex_watchlists['pharma'].append('CRACK')
'dad': ['CIM', 'DOL.TO', 'GM', 'SHOP.TO', 'SPY', 'TSLA'],
'indexes': ['DAX', 'DIA', 'QQQ', 'SPY'],
'pharma': ['ATE.VN', 'CRACK'],
}
run(f"piker watchlists -d {piker_dir} add pharma CRACK") run(f"piker watchlists -d {piker_dir} add pharma CRACK")
out = wl.ensure_watchlists(piker_dir) out = wl.ensure_watchlists(piker_dir)
assert out == expected_out assert out == ex_watchlists
def test_ticker_removed_from_watchlists(capfd, piker_dir): def test_ticker_removed_from_watchlists(capfd, piker_dir, ex_watchlists):
expected_out = { expected_out = ex_watchlists.copy()
'dad': ['CIM', 'DOL.TO', 'GM', 'SHOP.TO', 'SPY', 'TSLA'], expected_out['dad'].remove('SPY')
'indexes': ['DAX', 'DIA', 'SPY'], run(f"piker watchlists -d {piker_dir} remove dad SPY")
'pharma': ['ATE.VN'],
}
run(f"piker watchlists -d {piker_dir} remove indexes QQQ")
out = wl.ensure_watchlists(piker_dir) out = wl.ensure_watchlists(piker_dir)
assert out == expected_out assert out == expected_out
# removing a non-entry should be a no-op
run(f"piker watchlists -d {piker_dir} remove dad SPY")
out = wl.ensure_watchlists(piker_dir)
def test_group_deleted_from_watchlists(capfd, piker_dir):
expected_out = { def test_group_deleted_from_watchlists(capfd, piker_dir, ex_watchlists):
'dad': ['CIM', 'DOL.TO', 'GM', 'SHOP.TO', 'SPY', 'TSLA'], expected_out = ex_watchlists.copy()
'indexes': ['DAX', 'DIA', 'QQQ', 'SPY'], expected_out.pop('pharma')
}
run(f"piker watchlists -d {piker_dir} delete pharma") run(f"piker watchlists -d {piker_dir} delete pharma")
out = wl.ensure_watchlists(piker_dir) out = wl.ensure_watchlists(piker_dir)
assert out == expected_out assert out == expected_out
@ -201,7 +204,7 @@ def test_watchlists_are_merged(capfd, piker_dir):
'pharma': ['ATE.VN', 'MALI', 'PERCOCET'], 'pharma': ['ATE.VN', 'MALI', 'PERCOCET'],
'drugs': ['CRACK'] 'drugs': ['CRACK']
} }
wl.write_sorted_json(orig_watchlist, piker_dir) wl.write_to_file(orig_watchlist, piker_dir)
run(f"piker watchlists -d {piker_dir} merge", list_to_merge) run(f"piker watchlists -d {piker_dir} merge", list_to_merge)
out = wl.ensure_watchlists(piker_dir) out = wl.ensure_watchlists(piker_dir)
assert out == expected_out assert out == expected_out

View File

@ -30,7 +30,7 @@ def test_watchlist_is_sorted_no_dups_and_saved_to_file(piker_dir):
wl_temp = {'test': ['TEST.CN', 'AAA'], 'AA': ['TEST.CN', 'TEST.CN'], wl_temp = {'test': ['TEST.CN', 'AAA'], 'AA': ['TEST.CN', 'TEST.CN'],
'AA': ['TEST.CN']} 'AA': ['TEST.CN']}
wl_sort = {'AA': ['TEST.CN'], 'test': ['AAA', 'TEST.CN']} wl_sort = {'AA': ['TEST.CN'], 'test': ['AAA', 'TEST.CN']}
wl.write_sorted_json(wl_temp, piker_dir) wl.write_to_file(wl_temp, piker_dir)
temp_sorted = wl.ensure_watchlists(piker_dir) temp_sorted = wl.ensure_watchlists(piker_dir)
assert temp_sorted == wl_sort assert temp_sorted == wl_sort
@ -60,7 +60,7 @@ def test_watchlist_is_read_from_file(piker_dir):
wl_temp = wl.ensure_watchlists(piker_dir) wl_temp = wl.ensure_watchlists(piker_dir)
assert wl_temp == {} assert wl_temp == {}
wl_temp2 = {"AA": ["TEST.CN"]} wl_temp2 = {"AA": ["TEST.CN"]}
wl.write_sorted_json(wl_temp2, piker_dir) wl.write_to_file(wl_temp2, piker_dir)
assert wl_temp2 == wl.ensure_watchlists(piker_dir) assert wl_temp2 == wl.ensure_watchlists(piker_dir)
@ -83,6 +83,14 @@ def test_ticker_is_removed():
assert wl_temp == {'test': ['TEST2.CN']} assert wl_temp == {'test': ['TEST2.CN']}
assert not wl_temp.get('test2') assert not wl_temp.get('test2')
# verify trying to remove from nonexistant list
with pytest.raises(KeyError):
wl.remove_ticker('doggy', 'TEST.CN', wl_temp)
# verify trying to remove non-existing ticker
with pytest.raises(ValueError):
wl.remove_ticker('test', 'TEST.CN', wl_temp)
def test_group_is_deleted(): def test_group_is_deleted():
"""Check that watchlist group is removed. """Check that watchlist group is removed.