Skip to content

Commit

Permalink
Merge branch 'easyconfig'
Browse files Browse the repository at this point in the history
  • Loading branch information
mikakoi committed Jun 7, 2018
2 parents abd7746 + d31d637 commit 621e1ad
Show file tree
Hide file tree
Showing 31 changed files with 1,002 additions and 236 deletions.
21 changes: 1 addition & 20 deletions dexbot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,4 @@
import pathlib
import os
from appdirs import user_config_dir

APP_NAME = "dexbot"
VERSION = '0.3.9'
VERSION = '0.4.0'
AUTHOR = "codaone"
__version__ = VERSION


config_dir = user_config_dir(APP_NAME, appauthor=AUTHOR)
config_file = os.path.join(config_dir, "config.yml")

default_config = """
node: wss://bitshares.openledger.info/ws
workers: {}
"""

if not os.path.isfile(config_file):
pathlib.Path(config_dir).mkdir(parents=True, exist_ok=True)
with open(config_file, 'w') as f:
f.write(default_config)
print("Created default config file at {}".format(config_file))
45 changes: 43 additions & 2 deletions dexbot/basestrategy.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import logging
import collections
import time
import math

from .storage import Storage
from .statemachine import StateMachine
from .config import Config

from events import Events
import bitsharesapi
Expand All @@ -17,6 +19,22 @@

MAX_TRIES = 3

ConfigElement = collections.namedtuple('ConfigElement', 'key type default description extra')
# Bots need to specify their own configuration values
# I want this to be UI-agnostic so a future web or GUI interface can use it too
# so each bot can have a class method 'configure' which returns a list of ConfigElement
# named tuples. Tuple fields as follows.
# Key: the key in the bot config dictionary that gets saved back to config.yml
# Type: one of "int", "float", "bool", "string", "choice"
# Default: the default value. must be right type.
# Description: comments to user, full sentences encouraged
# Extra:
# For int & float: a (min, max) tuple
# For string: a regular expression, entries must match it, can be None which equivalent to .*
# For bool, ignored
# For choice: a list of choices, choices are in turn (tag, label) tuples.
# labels get presented to user, and tag is used as the value saved back to the config dict


class BaseStrategy(Storage, StateMachine, Events):
""" Base Strategy and methods available in all Sub Classes that
Expand Down Expand Up @@ -66,10 +84,29 @@ class BaseStrategy(Storage, StateMachine, Events):
'onUpdateCallOrder',
]

@classmethod
def configure(cls):
"""
Return a list of ConfigElement objects defining the configuration values for
this class
User interfaces should then generate widgets based on this values, gather
data and save back to the config dictionary for the worker.
NOTE: when overriding you almost certainly will want to call the ancestor
and then add your config values to the list.
"""
# these configs are common to all bots
return [
ConfigElement("account", "string", "", "BitShares account name for the bot to operate with", ""),
ConfigElement("market", "string", "USD:BTS",
"BitShares market to operate on, in the format ASSET:OTHERASSET, for example \"USD:BTS\"",
"[A-Z]+[:\/][A-Z]+")
]

def __init__(
self,
config,
name,
config=None,
onAccount=None,
onOrderMatched=None,
onOrderPlaced=None,
Expand Down Expand Up @@ -108,7 +145,11 @@ def __init__(
# Redirect this event to also call order placed and order matched
self.onMarketUpdate += self._callbackPlaceFillOrders

self.config = config
if config:
self.config = config
else:
self.config = config = Config.get_worker_config_file(name)

self.worker = config["workers"][name]
self._account = Account(
self.worker["account"],
Expand Down
79 changes: 59 additions & 20 deletions dexbot/cli.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
#!/usr/bin/env python3
import logging
import os
import os.path
import signal
import sys

from dexbot import config_file
from dexbot.config import Config, DEFAULT_CONFIG_FILE
from dexbot.ui import (
verbose,
chain,
unlock,
configfile
)
from dexbot.worker import WorkerInfrastructure
import dexbot.errors as errors
from .worker import WorkerInfrastructure
from .cli_conf import configure_dexbot, dexbot_service_running
from . import errors
from . import helper

# We need to do this before importing click
if "LANG" not in os.environ:
os.environ['LANG'] = 'C.UTF-8'
import click


log = logging.getLogger(__name__)

# Initial logging before proper setup.
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s'
Expand All @@ -27,14 +35,19 @@
@click.group()
@click.option(
"--configfile",
default=config_file,
default=DEFAULT_CONFIG_FILE,
)
@click.option(
'--verbose',
'-v',
type=int,
default=3,
help='Verbosity (0-15)')
@click.option(
'--systemd/--no-systemd',
'-d',
default=False,
help='Run as a daemon from systemd')
@click.option(
'--pidfile',
'-p',
Expand All @@ -61,26 +74,52 @@ def run(ctx):
with open(ctx.obj['pidfile'], 'w') as fd:
fd.write(str(os.getpid()))
try:
worker = WorkerInfrastructure(ctx.config)
# Set up signalling. do it here as of no relevance to GUI
kill_workers = worker_job(worker, lambda: worker.stop(pause=True))
# These first two UNIX & Windows
signal.signal(signal.SIGTERM, kill_workers)
signal.signal(signal.SIGINT, kill_workers)
try:
worker = WorkerInfrastructure(ctx.config)
# Set up signalling. do it here as of no relevance to GUI
kill_workers = worker_job(worker, lambda: worker.stop(pause=True))
# These first two UNIX & Windows
signal.signal(signal.SIGTERM, kill_workers)
signal.signal(signal.SIGINT, kill_workers)
# These signals are UNIX-only territory, will ValueError here on Windows
signal.signal(signal.SIGHUP, kill_workers)
# TODO: reload config on SIGUSR1
# signal.signal(signal.SIGUSR1, lambda x, y: worker.do_next_tick(worker.reread_config))
except ValueError:
log.debug("Cannot set all signals -- not available on this platform")
if ctx.obj['systemd']:
try:
# These signals are UNIX-only territory, will ValueError here on Windows
signal.signal(signal.SIGHUP, kill_workers)
# TODO: reload config on SIGUSR1
# signal.signal(signal.SIGUSR1, lambda x, y: worker.do_next_tick(worker.reread_config))
except AttributeError:
log.debug("Cannot set all signals -- not available on this platform")
worker.run()
finally:
if ctx.obj['pidfile']:
os.unlink(ctx.obj['pidfile'])
import sdnotify # A soft dependency on sdnotify -- don't crash on non-systemd systems
n = sdnotify.SystemdNotifier()
n.notify("READY=1")
except BaseException:
log.debug("sdnotify not available")
worker.run()
except errors.NoWorkersAvailable:
sys.exit(70) # 70= "Software error" in /usr/include/sysexts.h
finally:
if ctx.obj['pidfile']:
helper.remove(ctx.obj['pidfile'])


@main.command()
@click.pass_context
def configure(ctx):
""" Interactively configure dexbot
"""
# Make sure the dexbot service isn't running while we do the config edits
if dexbot_service_running():
click.echo("Stopping dexbot daemon")
os.system('systemctl --user stop dexbot')

config = Config(path=ctx.obj['configfile'])
configure_dexbot(config)
config.save_config()

click.echo("New configuration saved")
if config.get('systemd_status', 'disabled') == 'enabled':
click.echo("Starting dexbot daemon")
os.system("systemctl --user start dexbot")


def worker_job(worker, job):
Expand Down

0 comments on commit 621e1ad

Please sign in to comment.