Allow wl app to spawn a broker daemon in a subprocess
parent
90e8dd911c
commit
2973b40946
69
piker/cli.py
69
piker/cli.py
|
@ -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()
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
Loading…
Reference in New Issue