save rpc instances only in registered_modules, add some abstract methods

This commit is contained in:
gcarq 2018-06-08 03:49:09 +02:00
parent 13ba68acc6
commit cddb062db5
6 changed files with 95 additions and 104 deletions

View File

@ -2,21 +2,21 @@
This module contains class to define a RPC communications
"""
import logging
from abc import abstractmethod
from datetime import datetime, timedelta, date
from decimal import Decimal
from typing import Dict, Tuple, Any
import arrow
import sqlalchemy as sql
from pandas import DataFrame
from numpy import mean, nan_to_num
from pandas import DataFrame
from freqtrade import exchange
from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade
from freqtrade.state import State
logger = logging.getLogger(__name__)
@ -32,6 +32,19 @@ class RPC(object):
"""
self.freqtrade = freqtrade
@abstractmethod
def cleanup(self) -> str:
""" Cleanup pending module resources """
@property
@abstractmethod
def name(self) -> None:
""" Returns the lowercase name of this module """
@abstractmethod
def send_msg(self, msg: str) -> None:
""" Sends a message to all registered rpc modules """
def rpc_trade_status(self) -> Tuple[bool, Any]:
"""
Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is

View File

@ -1,12 +1,12 @@
"""
This module contains class to manage RPC communications (Telegram, Slack, ...)
"""
from typing import Any, List
import logging
from typing import List
from freqtrade.rpc.rpc import RPC
from freqtrade.rpc.telegram import Telegram
logger = logging.getLogger(__name__)
@ -15,36 +15,21 @@ class RPCManager(object):
Class to manage RPC objects (Telegram, Slack, ...)
"""
def __init__(self, freqtrade) -> None:
"""
Initializes all enabled rpc modules
:param config: config to use
:return: None
"""
self.freqtrade = freqtrade
""" Initializes all enabled rpc modules """
self.registered_modules: List[RPC] = []
self.registered_modules: List[str] = []
self.telegram: Any = None
self._init()
def _init(self) -> None:
"""
Init RPC modules
:return:
"""
if self.freqtrade.config['telegram'].get('enabled', False):
# Enable telegram
if freqtrade.config['telegram'].get('enabled', False):
logger.info('Enabling rpc.telegram ...')
self.registered_modules.append('telegram')
self.telegram = Telegram(self.freqtrade)
self.registered_modules.append(Telegram(freqtrade))
def cleanup(self) -> None:
"""
Stops all enabled rpc modules
:return: None
"""
if 'telegram' in self.registered_modules:
logger.info('Cleaning up rpc.telegram ...')
self.registered_modules.remove('telegram')
self.telegram.cleanup()
""" Stops all enabled rpc modules """
for mod in self.registered_modules:
logger.info('Cleaning up rpc.%s ...', mod.name)
mod.cleanup()
self.registered_modules = []
def send_msg(self, msg: str) -> None:
"""
@ -52,6 +37,7 @@ class RPCManager(object):
:param msg: message
:return: None
"""
logger.info(msg)
if 'telegram' in self.registered_modules:
self.telegram.send_msg(msg)
logger.info('Sending rpc message: %s', msg)
for mod in self.registered_modules:
logger.debug('Forwarding message to rpc.%s', mod.name)
mod.send_msg(msg)

View File

@ -14,7 +14,6 @@ from telegram.ext import CommandHandler, Updater
from freqtrade.__init__ import __version__
from freqtrade.rpc.rpc import RPC
logger = logging.getLogger(__name__)
@ -57,6 +56,11 @@ class Telegram(RPC):
"""
Telegram, this class send messages to Telegram
"""
@property
def name(self) -> str:
return "telegram"
def __init__(self, freqtrade) -> None:
"""
Init the Telegram call, and init the super class RPC
@ -120,6 +124,10 @@ class Telegram(RPC):
self._updater.stop()
def send_msg(self, msg: str) -> None:
""" Send a message to telegram channel """
self._send_msg(msg)
def is_enabled(self) -> bool:
"""
Returns True if the telegram module is activated, False otherwise
@ -146,10 +154,10 @@ class Telegram(RPC):
# Fetch open trade
(error, trades) = self.rpc_trade_status()
if error:
self.send_msg(trades, bot=bot)
self._send_msg(trades, bot=bot)
else:
for trademsg in trades:
self.send_msg(trademsg, bot=bot)
self._send_msg(trademsg, bot=bot)
@authorized_only
def _status_table(self, bot: Bot, update: Update) -> None:
@ -163,12 +171,12 @@ class Telegram(RPC):
# Fetch open trade
(err, df_statuses) = self.rpc_status_table()
if err:
self.send_msg(df_statuses, bot=bot)
self._send_msg(df_statuses, bot=bot)
else:
message = tabulate(df_statuses, headers='keys', tablefmt='simple')
message = "<pre>{}</pre>".format(message)
self.send_msg(message, parse_mode=ParseMode.HTML)
self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
def _daily(self, bot: Bot, update: Update) -> None:
@ -189,7 +197,7 @@ class Telegram(RPC):
self._config['fiat_display_currency']
)
if error:
self.send_msg(stats, bot=bot)
self._send_msg(stats, bot=bot)
else:
stats = tabulate(stats,
headers=[
@ -203,7 +211,7 @@ class Telegram(RPC):
timescale,
stats
)
self.send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
@authorized_only
def _profit(self, bot: Bot, update: Update) -> None:
@ -219,7 +227,7 @@ class Telegram(RPC):
self._config['fiat_display_currency']
)
if error:
self.send_msg(stats, bot=bot)
self._send_msg(stats, bot=bot)
return
# Message to display
@ -250,7 +258,7 @@ class Telegram(RPC):
best_pair=stats['best_pair'],
best_rate=stats['best_rate']
)
self.send_msg(markdown_msg, bot=bot)
self._send_msg(markdown_msg, bot=bot)
@authorized_only
def _balance(self, bot: Bot, update: Update) -> None:
@ -259,7 +267,7 @@ class Telegram(RPC):
"""
(error, result) = self.rpc_balance(self._config['fiat_display_currency'])
if error:
self.send_msg('`All balances are zero.`')
self._send_msg('`All balances are zero.`')
return
(currencys, total, symbol, value) = result
@ -274,7 +282,7 @@ class Telegram(RPC):
output += "\n*Estimated Value*:\n" \
"\t`BTC: {0: .8f}`\n" \
"\t`{1}: {2: .2f}`\n".format(total, symbol, value)
self.send_msg(output)
self._send_msg(output)
@authorized_only
def _start(self, bot: Bot, update: Update) -> None:
@ -287,7 +295,7 @@ class Telegram(RPC):
"""
(error, msg) = self.rpc_start()
if error:
self.send_msg(msg, bot=bot)
self._send_msg(msg, bot=bot)
@authorized_only
def _stop(self, bot: Bot, update: Update) -> None:
@ -299,7 +307,7 @@ class Telegram(RPC):
:return: None
"""
(error, msg) = self.rpc_stop()
self.send_msg(msg, bot=bot)
self._send_msg(msg, bot=bot)
@authorized_only
def _reload_conf(self, bot: Bot, update: Update) -> None:
@ -326,7 +334,7 @@ class Telegram(RPC):
trade_id = update.message.text.replace('/forcesell', '').strip()
(error, message) = self.rpc_forcesell(trade_id)
if error:
self.send_msg(message, bot=bot)
self._send_msg(message, bot=bot)
return
@authorized_only
@ -340,7 +348,7 @@ class Telegram(RPC):
"""
(error, trades) = self.rpc_performance()
if error:
self.send_msg(trades, bot=bot)
self._send_msg(trades, bot=bot)
return
stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format(
@ -350,7 +358,7 @@ class Telegram(RPC):
count=trade['count']
) for i, trade in enumerate(trades))
message = '<b>Performance:</b>\n{}'.format(stats)
self.send_msg(message, parse_mode=ParseMode.HTML)
self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
def _count(self, bot: Bot, update: Update) -> None:
@ -363,7 +371,7 @@ class Telegram(RPC):
"""
(error, trades) = self.rpc_count()
if error:
self.send_msg(trades, bot=bot)
self._send_msg(trades, bot=bot)
return
message = tabulate({
@ -373,7 +381,7 @@ class Telegram(RPC):
}, headers=['current', 'max', 'total stake'], tablefmt='simple')
message = "<pre>{}</pre>".format(message)
logger.debug(message)
self.send_msg(message, parse_mode=ParseMode.HTML)
self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
def _help(self, bot: Bot, update: Update) -> None:
@ -399,7 +407,7 @@ class Telegram(RPC):
"*/help:* `This help message`\n" \
"*/version:* `Show version`"
self.send_msg(message, bot=bot)
self._send_msg(message, bot=bot)
@authorized_only
def _version(self, bot: Bot, update: Update) -> None:
@ -410,10 +418,10 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
self.send_msg('*Version:* `{}`'.format(__version__), bot=bot)
self._send_msg('*Version:* `{}`'.format(__version__), bot=bot)
def send_msg(self, msg: str, bot: Bot = None,
parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
def _send_msg(self, msg: str, bot: Bot = None,
parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
"""
Send given markdown message
:param msg: message

View File

@ -7,49 +7,35 @@ from copy import deepcopy
from unittest.mock import MagicMock
from freqtrade.rpc.rpc_manager import RPCManager
from freqtrade.rpc.telegram import Telegram
from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
def test_rpc_manager_object() -> None:
"""
Test the Arguments object has the mandatory methods
:return: None
"""
assert hasattr(RPCManager, '_init')
""" Test the Arguments object has the mandatory methods """
assert hasattr(RPCManager, 'send_msg')
assert hasattr(RPCManager, 'cleanup')
def test__init__(mocker, default_conf) -> None:
"""
Test __init__() method
"""
init_mock = mocker.patch('freqtrade.rpc.rpc_manager.RPCManager._init', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
""" Test __init__() method """
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
rpc_manager = RPCManager(freqtradebot)
assert rpc_manager.freqtrade == freqtradebot
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
assert rpc_manager.registered_modules == []
assert rpc_manager.telegram is None
assert init_mock.call_count == 1
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
"""
Test _init() method with Telegram disabled
"""
""" Test _init() method with Telegram disabled """
caplog.set_level(logging.DEBUG)
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
freqtradebot = get_patched_freqtradebot(mocker, conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples)
assert rpc_manager.registered_modules == []
assert rpc_manager.telegram is None
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
@ -59,14 +45,12 @@ def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
assert log_has('Enabling rpc.telegram ...', caplog.record_tuples)
len_modules = len(rpc_manager.registered_modules)
assert len_modules == 1
assert 'telegram' in rpc_manager.registered_modules
assert isinstance(rpc_manager.telegram, Telegram)
assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules]
def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
@ -99,11 +83,11 @@ def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
rpc_manager = RPCManager(freqtradebot)
# Check we have Telegram as a registered modules
assert 'telegram' in rpc_manager.registered_modules
assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules]
rpc_manager.cleanup()
assert log_has('Cleaning up rpc.telegram ...', caplog.record_tuples)
assert 'telegram' not in rpc_manager.registered_modules
assert 'telegram' not in [mod.name for mod in rpc_manager.registered_modules]
assert telegram_mock.call_count == 1

View File

@ -258,7 +258,7 @@ def test_status(default_conf, update, mocker, fee, ticker) -> None:
_init=MagicMock(),
rpc_trade_status=MagicMock(return_value=(False, [1, 2, 3])),
_status_table=status_table,
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -296,7 +296,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_status_table=status_table,
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -341,7 +341,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -397,7 +397,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -465,7 +465,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -506,7 +506,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -604,7 +604,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
@ -634,7 +634,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
@ -656,7 +656,7 @@ def test_start_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -680,7 +680,7 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -705,7 +705,7 @@ def test_stop_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -730,7 +730,7 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -898,7 +898,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
@ -940,7 +940,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
@ -981,7 +981,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
@ -1004,7 +1004,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
@ -1047,7 +1047,7 @@ def test_help_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot)
@ -1067,7 +1067,7 @@ def test_version_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot)
@ -1090,12 +1090,12 @@ def test_send_msg(default_conf, mocker) -> None:
telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = False
telegram.send_msg('test', bot)
telegram._send_msg('test', bot)
assert not bot.method_calls
bot.reset_mock()
telegram._config['telegram']['enabled'] = True
telegram.send_msg('test', bot)
telegram._send_msg('test', bot)
assert len(bot.method_calls) == 1
@ -1113,7 +1113,7 @@ def test_send_msg_network_error(default_conf, mocker, caplog) -> None:
telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = True
telegram.send_msg('test', bot)
telegram._send_msg('test', bot)
# Bot should've tried to send it twice
assert len(bot.method_calls) == 2

View File

@ -57,7 +57,7 @@ def patch_RPCManager(mocker) -> MagicMock:
:param mocker: mocker to patch RPCManager class
:return: RPCManager.send_msg MagicMock to track if this method is called
"""
mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock())
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
rpc_mock = mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
return rpc_mock