Allow wl app to spawn a broker daemon in a subprocess

kivy_mainline_and_py3.8
Tyler Goodlet 2018-04-19 01:32:21 -04:00
parent 90e8dd911c
commit 2973b40946
2 changed files with 118 additions and 82 deletions

View File

@ -1,20 +1,23 @@
""" """
Console interface to broker client/daemons. Console interface to broker client/daemons.
""" """
from collections import defaultdict
from functools import partial from functools import partial
from importlib import import_module from importlib import import_module
import os from multiprocessing import Process
from collections import defaultdict
import json import json
import os
import signal
import time
import click import click
import trio
import pandas as pd import pandas as pd
import trio
from .log import get_console_log, colorize_json, get_logger
from . import watchlists as wl from . import watchlists as wl
from .brokers import core, get_brokermod from .brokers import core, get_brokermod
from .brokers.core import _daemon_main
from .log import get_console_log, colorize_json, get_logger
log = get_logger('cli') log = get_logger('cli')
DEFAULT_BROKER = 'robinhood' DEFAULT_BROKER = 'robinhood'
@ -36,15 +39,11 @@ def run(main, loglevel='info'):
@click.command() @click.command()
@click.option('--broker', '-b', default=DEFAULT_BROKER,
help='Broker backend to use')
@click.option('--loglevel', '-l', default='warning', help='Logging level') @click.option('--loglevel', '-l', default='warning', help='Logging level')
def pikerd(broker, loglevel): def pikerd(loglevel):
"""Spawn the piker daemon. """Spawn the piker daemon.
""" """
from piker.brokers.core import _daemon_main run(_daemon_main, loglevel)
brokermod = get_brokermod(broker)
run(partial(_daemon_main, brokermod), loglevel)
@click.group() @click.group()
@ -134,7 +133,53 @@ def watch(loglevel, broker, rate, name):
brokermod = get_brokermod(broker) brokermod = get_brokermod(broker)
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, wl._builtins) watchlists = wl.merge_watchlist(watchlist_from_file, wl._builtins)
trio.run(_async_main, name, watchlists[name], brokermod, rate) tickers = watchlists[name]
# setup ticker stream
from .brokers.core import Client
async def main(timeout=1):
async def subscribe(client):
# initial request for symbols price streams
await client.send((brokermod.name, tickers))
client = Client(('127.0.0.1', 1616), subscribe)
start = time.time()
while True:
try:
await client.connect()
break
except OSError as oserr:
log.info("Waiting on daemon to come up...")
await trio.sleep(0.1)
if time.time() - start > timeout:
raise
continue
async with trio.open_nursery() as nursery:
nursery.start_soon(
_async_main, name, client, tickers,
brokermod, rate
)
try:
trio.run(main)
except OSError as oserr:
log.exception(oserr)
answer = input(
"\nWould you like to spawn a broker daemon locally? [Y/n]")
if answer is not 'n':
child = Process(
target=run,
args=(_daemon_main, loglevel),
daemon=True,
)
child.daemon = True
child.start()
trio.run(main, 5)
# trio dies with a keyboard interrupt
os.kill(child.pid, signal.SIGINT)
child.join()
@cli.group() @cli.group()

View File

@ -382,86 +382,77 @@ async def run_kivy(root, nursery):
nursery.cancel_scope.cancel() # cancel all other tasks that may be running nursery.cancel_scope.cancel() # cancel all other tasks that may be running
async def _async_main(name, tickers, brokermod, rate): async def _async_main(name, client, tickers, brokermod, rate):
'''Launch kivy app + all other related tasks. '''Launch kivy app + all other related tasks.
This is started with cli command `piker watch`. This is started with cli command `piker watch`.
''' '''
# setup ticker stream # get initial symbol data
from ..brokers.core import Client async with brokermod.get_client() as bclient:
# get long term data including last days close price
sd = await bclient.symbol_data(tickers)
async def subscribe(client): async with trio.open_nursery() as nursery:
# initial request for symbols price streams # get first quotes response
await client.send((brokermod.name, tickers)) quotes = await client.recv()
first_quotes = [
brokermod.format_quote(quote, symbol_data=sd)[0]
for quote in quotes.values()]
async with Client(('127.0.0.1', 1616), subscribe) as client: if first_quotes[0].get('last') is None:
log.error("Broker API is down temporarily")
nursery.cancel_scope.cancel()
return
# get initial symbol data # build out UI
async with brokermod.get_client() as bclient: Window.set_title(f"watchlist: {name}\t(press ? for help)")
# get long term data including last days close price Builder.load_string(_kv)
sd = await bclient.symbol_data(tickers) box = BoxLayout(orientation='vertical', padding=5, spacing=5)
async with trio.open_nursery() as nursery: # define bid-ask "stacked" cells
# get first quotes response # (TODO: needs some rethinking and renaming for sure)
quotes = await client.recv() bidasks = brokermod._bidasks
first_quotes = [
brokermod.format_quote(quote, symbol_data=sd)[0]
for quote in quotes.values()]
if first_quotes[0].get('last') is None: # add header row
log.error("Broker API is down temporarily") headers = first_quotes[0].keys()
nursery.cancel_scope.cancel() header = Row(
return {key: key for key in headers},
headers=headers,
bidasks=bidasks,
is_header_row=True,
size_hint=(1, None),
)
box.add_widget(header)
# build out UI # build grid
Window.set_title(f"watchlist: {name}\t(press ? for help)") grid = TickerTable(
Builder.load_string(_kv) cols=1,
box = BoxLayout(orientation='vertical', padding=5, spacing=5) size_hint=(1, None),
)
for ticker_record in first_quotes:
grid.append_row(ticker_record, bidasks=bidasks)
# associate the col headers row with the ticker table even though
# they're technically wrapped separately in containing BoxLayout
header.table = grid
# define bid-ask "stacked" cells # mark the initial sorted column header as bold and underlined
# (TODO: needs some rethinking and renaming for sure) sort_cell = header.get_cell(grid.sort_key)
bidasks = brokermod._bidasks sort_cell.bold = sort_cell.underline = True
grid.last_clicked_col_cell = sort_cell
# add header row # set up a pager view for large ticker lists
headers = first_quotes[0].keys() grid.bind(minimum_height=grid.setter('height'))
header = Row( pager = PagerView(box, grid, nursery)
{key: key for key in headers}, box.add_widget(pager)
headers=headers,
bidasks=bidasks,
is_header_row=True,
size_hint=(1, None),
)
box.add_widget(header)
# build grid widgets = {
grid = TickerTable( # 'anchor': anchor,
cols=1, 'root': box,
size_hint=(1, None), 'grid': grid,
) 'box': box,
for ticker_record in first_quotes: 'header': header,
grid.append_row(ticker_record, bidasks=bidasks) 'pager': pager,
# associate the col headers row with the ticker table even though }
# they're technically wrapped separately in containing BoxLayout nursery.start_soon(run_kivy, widgets['root'], nursery)
header.table = grid nursery.start_soon(
update_quotes, nursery, brokermod, widgets, client, sd, quotes)
# mark the initial sorted column header as bold and underlined
sort_cell = header.get_cell(grid.sort_key)
sort_cell.bold = sort_cell.underline = True
grid.last_clicked_col_cell = sort_cell
# set up a pager view for large ticker lists
grid.bind(minimum_height=grid.setter('height'))
pager = PagerView(box, grid, nursery)
box.add_widget(pager)
widgets = {
# 'anchor': anchor,
'root': box,
'grid': grid,
'box': box,
'header': header,
'pager': pager,
}
nursery.start_soon(run_kivy, widgets['root'], nursery)
nursery.start_soon(
update_quotes, nursery, brokermod, widgets, client, sd, quotes)