Skip to content

Commit

Permalink
Use Yahoo! currency exchange rates
Browse files Browse the repository at this point in the history
  • Loading branch information
deanishe committed Dec 26, 2014
1 parent d270efb commit 1004faf
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 116 deletions.
49 changes: 12 additions & 37 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,26 @@

from __future__ import print_function, unicode_literals

import json
import os

CURRENCY_CACHE_AGE = 3600 * 12 # 12 hours
CURRENCY_CACHE_NAME = 'exchange_rates'

with open(os.path.join(os.path.dirname(__file__),
'currencies.json'), 'rb') as fp:
CURRENCIES = json.load(fp)


ICON_UPDATE = 'icons/update-available.png'
ICON_CURRENCY = 'icons/money.png'

REFERENCE_CURRENCY = 'EUR'

YAHOO_BASE_URL = 'http://download.finance.yahoo.com/d/quotes.csv?f=sl1&s={}'

SYMBOLS_PER_REQUEST = 50

UPDATE_SETTINGS = {'github_slug': 'deanishe/alfred-convert'}

DEFAULT_SETTINGS = {
'active_currencies': [
'AUD',
'BGN',
'BRL',
'CAD',
'CHF',
'CNY',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HRK',
'HUF',
'IDR',
'ILS',
'INR',
'JPY',
'KRW',
'LTL',
'MXN',
'MYR',
'NOK',
'NZD',
'PHP',
'PLN',
'RON',
'RUB',
'SEK',
'SGD',
'THB',
'TRY',
'USD',
'ZAR'
]
}
DEFAULT_SETTINGS = {}
177 changes: 98 additions & 79 deletions src/currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,27 @@

from __future__ import print_function, unicode_literals

import csv
from datetime import timedelta
from itertools import izip_longest
import re

try:
from xml.etree import cElementTree as ET
except ImportError:
from xml.etree import ElementTree as ET

from workflow import Workflow, web, ICON_WARNING, ICON_INFO
from config import CURRENCY_CACHE_NAME, ICON_CURRENCY, REFERENCE_CURRENCY

log = None

# ECB XML feed settings
XML_URL = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
NS_ECB = '{http://www.ecb.int/vocabulary/2002-08-01/eurofxref}'

CURRENCIES = {
'AUD': 'Australian dollar',
'BGN': 'Bulgarian lev',
'BRL': 'Brasilian real',
'CAD': 'Canadian dollar',
'CHF': 'Swiss franc',
'CNY': 'Chinese yuan renminbi',
'CZK': 'Czech koruna',
'DKK': 'Danish krone',
'EUR': 'Euro',
'GBP': 'Pound sterling',
'HKD': 'Hong Kong dollar',
'HRK': 'Croatian kuna',
'HUF': 'Hungarian forint',
'IDR': 'Indonesian rupiah',
'ILS': 'Israeli shekel',
'INR': 'Indian rupee',
'JPY': 'Japanese yen',
'KRW': 'South Korean won',
'LTL': 'Lithuanian litas',
'MXN': 'Mexican peso',
'MYR': 'Malaysian ringgit',
'NOK': 'Norwegian krone',
'NZD': 'New Zealand dollar',
'PHP': 'Philippine peso',
'PLN': 'Polish zloty',
'RON': 'New Romanian leu',
'RUB': 'Russian rouble',
'SEK': 'Swedish krona',
'SGD': 'Singapore dollar',
'THB': 'Thai baht',
'TRY': 'Turkish lira',
'USD': 'US dollar',
'ZAR': 'South African rand'
}
from workflow import (Workflow, web,
ICON_WARNING, ICON_INFO,
MATCH_ALL, MATCH_ALLCHARS)

from config import (CURRENCY_CACHE_NAME,
ICON_CURRENCY,
REFERENCE_CURRENCY,
CURRENCIES,
YAHOO_BASE_URL,
SYMBOLS_PER_REQUEST)


wf = Workflow()
log = wf.logger

parse_yahoo_response = re.compile(r'{}(.+)=X'.format(REFERENCE_CURRENCY)).match


def human_timedelta(td):
Expand All @@ -84,49 +54,90 @@ def human_timedelta(td):
return ' '.join(output)


def fetch_currency_rates():
"""Retrieve today's currency rates from the ECB's homepage
def grouper(n, iterable, fillvalue=None):
"""Return iterable that groups ``iterable`` into groups of length ``n``
:returns: `dict` {abbr : ``float``} of currency value in EUR
:param n: Size of group
:type n: ``int``
:param iterable: Iterable to split into groups
:param fillvalue: Value to pad groups with if there aren't enough values
in ``iterable``
:returns: Iterator
"""

exchange_rates = {}
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)


def load_yahoo_rates(symbols):
"""Return dict of exchange rates from Yahoo!
r = web.get(XML_URL)
:param symbols: List of symbols, e.g. ``['GBP', 'USD', ...]``
:returns: Dictionary of rates: ``{'GBP': 1.12, 'USD': 3.2}``
"""

rates = {}
count = len(symbols)

# Build URL
parts = []
for symbol in symbols:
if symbol == REFERENCE_CURRENCY:
count -= 1
continue
parts.append('{}{}=X'.format(REFERENCE_CURRENCY, symbol))
query = ','.join(parts)
url = YAHOO_BASE_URL.format(query)

# Fetch data
# log.debug('Fetching {} ...'.format(url))
r = web.get(url)
r.raise_for_status()
root = ET.fromstring(r.content)

for elem in root.findall('{0}Cube/{0}Cube/{0}Cube'.format(NS_ECB)):
currency = elem.attrib.get('currency')
rate = float(elem.attrib.get('rate'))
exchange_rates[currency] = rate
# Parse response
lines = r.content.split('\n')
ycount = 0
for row in csv.reader(lines):
if not row:
continue
name, rate = row
m = parse_yahoo_response(name)
if not m:
log.error('Invalid currency : {}'.format(name))
ycount += 1
continue
symbol = m.group(1)
rate = float(rate)
if rate == 0:
log.error('No exchange rate for : {}'.format(name))
ycount += 1
continue
rates[symbol] = rate
ycount += 1

assert ycount == count, 'Yahoo! returned {} results, not {}'.format(
ycount, count)

return rates

return exchange_rates

def fetch_currency_rates():
"""Retrieve today's currency rates from the ECB's homepage
def filter_currencies(query):
"""Return list of currency tuples `(name, abbr)` for supported
currencies matching `query`
:returns: `dict` {abbr : ``float``} of currency value in EUR
"""

if not query:
return [(v, k) for k, v in CURRENCIES.items()]
rates = {}

currencies = []
query = query.lower()
# Currencies that start with query
for k, v in CURRENCIES.items():
if k.lower().startswith(query) or v.lower().startswith(query):
currencies.append((v, k))
# Currencies that contain query
for k, v in CURRENCIES.items():
if query in k.lower() or query in v.lower():
if (v, k) not in currencies:
currencies.append((v, k))
for symbols in grouper(SYMBOLS_PER_REQUEST, CURRENCIES.keys()):
symbols = [s for s in symbols if s]
d = load_yahoo_rates(symbols)
rates.update(d)

return currencies
return rates


def main(wf):
Expand All @@ -135,7 +146,16 @@ def main(wf):
query = ''
if len(wf.args):
query = wf.args[0]
currencies = filter_currencies(query)

currencies = CURRENCIES.items()

if query:
currencies = wf.filter(query, currencies,
key=lambda t: ' '.join(t),
match_on=MATCH_ALL ^ MATCH_ALLCHARS,
min_score=30)

# currencies = filter_currencies(query)
if not currencies:
wf.add_item('No matching currencies found',
valid=False, icon=ICON_WARNING)
Expand All @@ -156,5 +176,4 @@ def main(wf):


if __name__ == '__main__':
wf = Workflow()
wf.run(main)
3 changes: 3 additions & 0 deletions src/update_exchange_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from currency import fetch_currency_rates
from workflow import Workflow

log = None


def main(wf):
# Insert delay to check info message is posted in Alfred
Expand All @@ -34,4 +36,5 @@ def main(wf):

if __name__ == '__main__':
wf = Workflow()
log = wf.logger
wf.run(main)

0 comments on commit 1004faf

Please sign in to comment.