commit 4b3a285871161916708f31b0d43c25c13a1b5d7d
parent a2047e2c68da78181c9f99e32942e11682062018
Author: SomberNight <somber.night@protonmail.com>
Date: Wed, 27 Feb 2019 21:48:33 +0100
exchange_rate: some clean-up and fixes
- generation of currencies.json was broken
- removed some dead exchanges
Diffstat:
3 files changed, 53 insertions(+), 76 deletions(-)
diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py
@@ -8,10 +8,11 @@ import time
import csv
import decimal
from decimal import Decimal
-import concurrent.futures
import traceback
from typing import Sequence
+from aiorpcx.curio import timeout_after, TaskTimeout, TaskGroup
+
from .bitcoin import COIN
from .i18n import _
from .util import (PrintError, ThreadJob, make_dir, log_exceptions,
@@ -40,14 +41,18 @@ class ExchangeBase(PrintError):
async def get_raw(self, site, get_string):
# APIs must have https
url = ''.join(['https://', site, get_string])
- async with make_aiohttp_session(Network.get_instance().proxy) as session:
+ network = Network.get_instance()
+ proxy = network.proxy if network else None
+ async with make_aiohttp_session(proxy) as session:
async with session.get(url) as response:
return await response.text()
async def get_json(self, site, get_string):
# APIs must have https
url = ''.join(['https://', site, get_string])
- async with make_aiohttp_session(Network.get_instance().proxy) as session:
+ network = Network.get_instance()
+ proxy = network.proxy if network else None
+ async with make_aiohttp_session(proxy) as session:
async with session.get(url) as response:
# set content_type to None to disable checking MIME type
return await response.json(content_type=None)
@@ -60,7 +65,6 @@ class ExchangeBase(PrintError):
def name(self):
return self.__class__.__name__
- @log_exceptions
async def update_safe(self, ccy):
try:
self.print_error("getting fx quotes for", ccy)
@@ -71,9 +75,6 @@ class ExchangeBase(PrintError):
self.quotes = {}
self.on_quotes()
- def update(self, ccy):
- asyncio.get_event_loop().create_task(self.update_safe(ccy))
-
def read_historical_rates(self, ccy, cache_dir):
filename = os.path.join(cache_dir, self.name() + '_'+ ccy)
if os.path.exists(filename):
@@ -123,8 +124,8 @@ class ExchangeBase(PrintError):
def historical_rate(self, ccy, d_t):
return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d'), 'NaN')
- def get_currencies(self):
- rates = self.get_rates('')
+ async def get_currencies(self):
+ rates = await self.get_rates('')
return sorted([str(a) for (a, b) in rates.items() if b is not None and len(a)==3])
class BitcoinAverage(ExchangeBase):
@@ -229,20 +230,6 @@ class BlockchainInfo(ExchangeBase):
return dict([(r, Decimal(json[r]['15m'])) for r in json])
-class BTCChina(ExchangeBase):
-
- async def get_rates(self, ccy):
- json = await self.get_json('data.btcchina.com', '/data/ticker')
- return {'CNY': Decimal(json['ticker']['last'])}
-
-
-class BTCParalelo(ExchangeBase):
-
- async def get_rates(self, ccy):
- json = await self.get_json('btcparalelo.com', '/api/price')
- return {'VEF': Decimal(json['price'])}
-
-
class Coinbase(ExchangeBase):
async def get_rates(self, ccy):
@@ -280,20 +267,6 @@ class CoinDesk(ExchangeBase):
return json['bpi']
-class Coinsecure(ExchangeBase):
-
- async def get_rates(self, ccy):
- json = await self.get_json('api.coinsecure.in', '/v0/noauth/newticker')
- return {'INR': Decimal(json['lastprice'] / 100.0 )}
-
-
-class Foxbit(ExchangeBase):
-
- async def get_rates(self,ccy):
- json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
- return {'BRL': Decimal(json['ticker_1h']['exchanges']['FOX']['last'])}
-
-
class itBit(ExchangeBase):
async def get_rates(self, ccy):
@@ -344,23 +317,6 @@ class TheRockTrading(ExchangeBase):
'/v1/funds/BTCEUR/ticker')
return {'EUR': Decimal(json['last'])}
-class Unocoin(ExchangeBase):
-
- async def get_rates(self, ccy):
- json = await self.get_json('www.unocoin.com', 'trade?buy')
- return {'INR': Decimal(json)}
-
-
-class WEX(ExchangeBase):
-
- async def get_rates(self, ccy):
- json_eur = await self.get_json('wex.nz', '/api/3/ticker/btc_eur')
- json_rub = await self.get_json('wex.nz', '/api/3/ticker/btc_rur')
- json_usd = await self.get_json('wex.nz', '/api/3/ticker/btc_usd')
- return {'EUR': Decimal(json_eur['btc_eur']['last']),
- 'RUB': Decimal(json_rub['btc_rur']['last']),
- 'USD': Decimal(json_usd['btc_usd']['last'])}
-
class Winkdex(ExchangeBase):
@@ -394,25 +350,39 @@ def dictinvert(d):
return inv
def get_exchanges_and_currencies():
+ # load currencies.json from disk
path = resource_path('currencies.json')
try:
with open(path, 'r', encoding='utf-8') as f:
return json.loads(f.read())
except:
pass
+ # or if not present, generate it now.
+ print("cannot find currencies.json. will regenerate it now.")
d = {}
is_exchange = lambda obj: (inspect.isclass(obj)
and issubclass(obj, ExchangeBase)
and obj != ExchangeBase)
exchanges = dict(inspect.getmembers(sys.modules[__name__], is_exchange))
- for name, klass in exchanges.items():
- exchange = klass(None, None)
+
+ async def get_currencies_safe(name, exchange):
try:
- d[name] = exchange.get_currencies()
+ d[name] = await exchange.get_currencies()
print(name, "ok")
except:
print(name, "error")
- continue
+
+ async def query_all_exchanges_for_their_ccys_over_network():
+ async with timeout_after(10):
+ async with TaskGroup() as group:
+ for name, klass in exchanges.items():
+ exchange = klass(None, None)
+ await group.spawn(get_currencies_safe(name, exchange))
+ loop = asyncio.get_event_loop()
+ try:
+ loop.run_until_complete(query_all_exchanges_for_their_ccys_over_network())
+ except Exception as e:
+ pass
with open(path, 'w', encoding='utf-8') as f:
f.write(json.dumps(d, indent=4, sort_keys=True))
return d
@@ -478,17 +448,18 @@ class FxThread(ThreadJob):
async def run(self):
while True:
+ # approx. every 2.5 minutes, refresh spot price
try:
- await asyncio.wait_for(self._trigger.wait(), 150)
- except concurrent.futures.TimeoutError:
+ async with timeout_after(150):
+ await self._trigger.wait()
+ self._trigger.clear()
+ # we were manually triggered, so get historical rates
+ if self.is_enabled() and self.show_history():
+ self.exchange.get_historical_rates(self.ccy, self.cache_dir)
+ except TaskTimeout:
pass
- else:
- self._trigger.clear()
- if self.is_enabled():
- if self.show_history():
- self.exchange.get_historical_rates(self.ccy, self.cache_dir)
if self.is_enabled():
- self.exchange.update(self.ccy)
+ await self.exchange.update_safe(self.ccy)
def is_enabled(self):
return bool(self.config.get('use_exchange_rate'))
diff --git a/electrum/network.py b/electrum/network.py
@@ -287,7 +287,7 @@ class Network(PrintError):
return fut.result()
@staticmethod
- def get_instance():
+ def get_instance() -> Optional["Network"]:
return INSTANCE
def with_recent_servers_lock(func):
@@ -1147,8 +1147,8 @@ class Network(PrintError):
raise
await asyncio.sleep(0.1)
-
- async def _send_http_on_proxy(self, method: str, url: str, params: str = None, body: bytes = None, json: dict = None, headers=None, on_finish=None):
+ @classmethod
+ async def _send_http_on_proxy(cls, method: str, url: str, params: str = None, body: bytes = None, json: dict = None, headers=None, on_finish=None):
async def default_on_finish(resp: ClientResponse):
resp.raise_for_status()
return await resp.text()
@@ -1156,7 +1156,9 @@ class Network(PrintError):
headers = {}
if on_finish is None:
on_finish = default_on_finish
- async with make_aiohttp_session(self.proxy) as session:
+ network = cls.get_instance()
+ proxy = network.proxy if network else None
+ async with make_aiohttp_session(proxy) as session:
if method == 'get':
async with session.get(url, params=params, headers=headers) as resp:
return await on_finish(resp)
@@ -1171,11 +1173,15 @@ class Network(PrintError):
else:
assert False
- @staticmethod
- def send_http_on_proxy(method, url, **kwargs):
- network = Network.get_instance()
- assert network._loop_thread is not threading.currentThread()
- coro = asyncio.run_coroutine_threadsafe(network._send_http_on_proxy(method, url, **kwargs), network.asyncio_loop)
+ @classmethod
+ def send_http_on_proxy(cls, method, url, **kwargs):
+ network = cls.get_instance()
+ if network:
+ assert network._loop_thread is not threading.currentThread()
+ loop = network.asyncio_loop
+ else:
+ loop = asyncio.get_event_loop()
+ coro = asyncio.run_coroutine_threadsafe(cls._send_http_on_proxy(method, url, **kwargs), loop)
return coro.result(5)
diff --git a/electrum/util.py b/electrum/util.py
@@ -948,7 +948,7 @@ class TxMinedInfo(NamedTuple):
header_hash: Optional[str] = None # hash of block that mined tx
-def make_aiohttp_session(proxy: dict, headers=None, timeout=None):
+def make_aiohttp_session(proxy: Optional[dict], headers=None, timeout=None):
if headers is None:
headers = {'User-Agent': 'Electrum'}
if timeout is None: