Skip to content
This repository has been archived by the owner on Aug 30, 2019. It is now read-only.

Commit

Permalink
Add offline support and show list of currencies
Browse files Browse the repository at this point in the history
Add supporting functions to show list of currencies available and add
offline support by storing the exchange rates in a json file.

Remove the json files for testing, and use the original files instead.

Not commiting exhcange rates file, since we have a function to download
the rates if not found.

This closes #24, fixes #16.
  • Loading branch information
anshulxyz committed Nov 30, 2017
1 parent 0c4e855 commit ca55e5e
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 46 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
.vscode
venv
env
__pycache__
*.egg-info
.cache
.coverage
.tox
*.pyc
fixer_rates.json
38 changes: 30 additions & 8 deletions exch/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,50 @@
:copyright: (c) 2017 by Anshul Chauhan
"""

import json
import click
import pkg_resources
from exch.helpers import fixer
from exch.file_handling import get_default_base, get_default_target, set_default_base, set_default_target
from exch.helpers import fixer, fixer_sync
from exch.file_handling import get_default_base, get_default_target,\
set_default_base, set_default_target

CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])

RATES_FIXER_JSON_FILE = pkg_resources.resource_filename('exch', 'data/fixer_rates.json')
DEFAULT_JSON_FILE = pkg_resources.resource_filename('exch', 'data/defaults.json')

@click.command(context_settings=CONTEXT_SETTINGS)
@click.option('--base', '-b', default=get_default_base(DEFAULT_JSON_FILE), type=str, show_default=True,
@click.option('--base', '-b', default=get_default_base(DEFAULT_JSON_FILE),
type=str, show_default=True,
help='Currency you are converting from.')
@click.option('--target', '-t', default=get_default_target(DEFAULT_JSON_FILE), type=str, show_default=True,
help='Currency you\'re converting to.')
@click.option('--target', '-t', default=get_default_target(DEFAULT_JSON_FILE),
type=str, show_default=True, help='Currency you\'re converting to.')
@click.option('--amount', '-a', default=1.0, type=float, show_default=True,
help='Amount to convert.')
@click.option('--set_base', '-sb', is_flag=True, default=False,
help='Set new default base.')
@click.option('--set_target', '-st', is_flag=True, default=False,
help='Set new default target.')
def cli(base, target, amount, set_base, set_target):
@click.option('--sync', is_flag=True, default=False,
help='Download the latest rates from web.')
@click.option('--currency', '-c', is_flag=True, default=False,
help='List the currencies available.')
def cli(base, target, amount, set_base, set_target, sync, currency):
"""
Get the latetst currency exchange rates from:
\b
- fixer.io
"""

output = fixer(base, target, amount)
if sync:
if fixer_sync(RATES_FIXER_JSON_FILE) in range(200, 300):
click.echo("New rates have been saved.")

output = fixer(base, target, amount, RATES_FIXER_JSON_FILE)
if isinstance(output, float):
output = "{} {} = {} {}".format(amount, base, output, target)
# 2:.2f for two decimal values, manually specified
output = "{0} {1} = {2:.2f} {3}".format(amount, base, output, target)

if set_base:
set_default_base(base, DEFAULT_JSON_FILE)
Expand All @@ -48,3 +61,12 @@ def cli(base, target, amount, set_base, set_target):
set_default_target(target, DEFAULT_JSON_FILE)

click.echo(output)

if currency:
with open(RATES_FIXER_JSON_FILE) as rates_json_file:
json_rates = json.load(rates_json_file)
currencies = []
currencies.append(json_rates['base'])
for key in json_rates['rates']:
currencies.append(key)
click.echo(',\n'.join(currencies))
2 changes: 1 addition & 1 deletion exch/data/defaults.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"target": "NZD", "base": "CAD"}
{"target": "CAD", "base": "PHP"}
24 changes: 12 additions & 12 deletions exch/file_handling.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
""" Functions related to file(JSON) handling. """
""" Functions related to default rates file. """

import json

def set_default_base(new_base, filepath):
def set_default_base(new_base, default_rates_filepath):
""" set new default base currency """
with open(filepath) as json_file:
with open(default_rates_filepath) as json_file:
json_data = json.load(json_file)

json_data['base'] = new_base

with open(filepath, 'w') as json_file:
json.dump(json_data, json_file, indent=4)
with open(default_rates_filepath, 'w') as json_file:
json.dump(json_data, json_file)

def set_default_target(new_target, filepath):
def set_default_target(new_target, default_rates_filepath):
""" set new default arget currency """
with open(filepath) as json_file:
with open(default_rates_filepath) as json_file:
json_data = json.load(json_file)

json_data['target'] = new_target

with open(filepath, 'w') as json_file:
with open(default_rates_filepath, 'w') as json_file:
json.dump(json_data, json_file)

def get_default_base(filepath):
def get_default_base(default_rates_filepath):
""" get the currenct default base currency """
with open(filepath) as json_file:
with open(default_rates_filepath) as json_file:
json_data = json.load(json_file)
return json_data['base']

def get_default_target(filepath):
def get_default_target(default_rates_filepath):
""" get the current default target currency """
with open(filepath) as json_file:
with open(default_rates_filepath) as json_file:
json_data = json.load(json_file)
return json_data['target']
56 changes: 47 additions & 9 deletions exch/helpers.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,61 @@
""" helper functions to gather the currency rates """

import json
import requests

def fixer(base, target, value, date='latest'):
"""get currency exchange rate from fixer.io in JSON"""
def fixer(base, target, value, file_path):
"""
get currency exchange rate from fixer.io in JSON,
take note that all the rates are stored as EUR as base
"""

main_api = 'http://api.fixer.io/{}?'.format(date)
url = requests.get(main_api, params={'base':base})
try:
"""
try to find the json file which contains currency exchange rates
"""
with open(file_path) as json_file:
json_rates = json.load(json_file)
except FileNotFoundError:
"""
if file not found, 'fixer_sync' will download the latests rates,
the range of 200-300 is for succesful HTTP code
"""
if fixer_sync(file_path) in range(200, 300):
with open(file_path) as json_file:
json_rates = json.load(json_file)

try:
json_data = requests.get(url.url).json()
result = round(json_data['rates'][target] * value, 2)
except requests.exceptions.ConnectionError:
result = "Connection Error"
eur_to_target = json_rates['rates'][target]
eur_to_base = json_rates['rates'][base]
result = eur_to_target/eur_to_base * value
except KeyError:
result = 1.00 if base == target else "KeyError: Invalid curreny"
if base == 'EUR':
result = json_rates['rates'][target] * value
elif target == 'EUR':
result = (1.0/json_rates['rates'][base]) * value
else:
result = "KeyError: Invalid curreny"

return result

def fixer_sync(file_path):
"""
downloads the rates JSON to the local location,
'file_path' is the location where the fixer.io rates will the placed,
returns status_code, if it is 200 then file successfully created
"""
url = 'http://api.fixer.io/latest'
response = requests.get(url, stream=True)
if response.status_code == 200:
with open(file_path, 'wb') as json_file:
json_file.write(response.content)

return response.status_code

# function to list currencies present
# use the above sync method to down the file if not present
# show the date of the file present

#def google_finance_converter(base, target, value):
# """ parse the Google Finance Converter html to extract the value"""
# pass
1 change: 0 additions & 1 deletion tests/data_for_test_file_handling/defaults.json

This file was deleted.

16 changes: 12 additions & 4 deletions tests/test_advanced.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
""" test the click and terminal behaviour """

import os
import json
import pytest
import pkg_resources
Expand All @@ -8,7 +9,6 @@

FILEPATH = pkg_resources.resource_filename('exch', 'data/defaults.json')


@pytest.fixture
def runner():
""" prefix alias function """
Expand All @@ -18,14 +18,14 @@ def test_click_same_currency_code():
""" test terminal behaviour for same currency code """
result = runner().invoke(cli, ['-t', 'USD', '-b', 'USD'])
assert result.exit_code == 0
assert result.output == '1.0 USD = 1.0 USD\n'
assert result.output == '1.0 USD = 1.00 USD\n'

def test_invalid_base_currency_code():
def test_invalid_base_currency():
result = runner().invoke(cli, ['-t', 'USD', '-b', 'AAA'])
assert result.exit_code == 0
assert result.output == "KeyError: Invalid curreny\n"

def test_invalid_target_currency_code():
def test_invalid_taget_currency():
result = runner().invoke(cli, ['-t', 'OOO'])
assert result.exit_code == 0
assert result.output == "KeyError: Invalid curreny\n"
Expand All @@ -47,4 +47,12 @@ def test_setting_default_target():
assert result.exit_code == 0
with open(FILEPATH) as json_file:
json_data = json.load(json_file)
print(json_data)
assert json_data['target'] == 'NZD'

def test_show_fixer_currencies():
result = runner().invoke(cli, ['-b', 'NZD', '-t', 'NZD', '-c'])
assert result.exit_code == 0
assert result.output == "1.0 NZD = 1.00 NZD\nEUR,\nAUD,\nBGN,\nBRL,\nCAD,\nCHF,\nCNY,\nCZK,\n\
DKK,\nGBP,\nHKD,\nHRK,\nHUF,\nIDR,\nILS,\nINR,\nJPY,\nKRW,\nMXN,\nMYR,\nNOK,\
\nNZD,\nPHP,\nPLN,\nRON,\nRUB,\nSEK,\nSGD,\nTHB,\nTRY,\nUSD,\nZAR\n"
29 changes: 20 additions & 9 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
""" unit testing the helper functions """

import pkg_resources
from exch.helpers import fixer

FIXER_RATES_JSON_TEST = pkg_resources.resource_filename('exch', 'data/fixer_rates.json')


def test_fixer_same_currency_code():
""" when the same currency is base as well as target """
assert fixer('USD', 'USD', 1) == 1.00
assert fixer('INR', 'INR', 1) == 1.00
assert fixer('USD', 'USD', 1, FIXER_RATES_JSON_TEST) == 1.00
assert fixer('INR', 'INR', 1, FIXER_RATES_JSON_TEST) == 1.00

def test_fixer_usd_to_inr():
""" fixer_io base: USD, target: INR, on date 2017-05-12 """
assert fixer('USD', 'INR', 1, date='2017-05-12') == round(64.307, 2)
""" fixer_io base: USD, target: INR """
assert fixer('USD', 'INR', 1, FIXER_RATES_JSON_TEST) > 60

def test_fixer_usd_to_jpy():
""" fixer_io base: USD, target: JPY, on date 2017-05-12 """
assert fixer('USD', 'JPY', 1, date='2017-05-12') == round(113.85, 2)
""" fixer_io base: USD, target: JPY """
assert fixer('USD', 'JPY', 1, FIXER_RATES_JSON_TEST) > 90

def test_fixer_gbp_to_php_value_99():
""" fixer_io base: GBP, target: PHP, on date 2017-05-12, value: 99 """
assert fixer('GBP', 'PHP', 99, date='2017-05-12') == round(63.942 * 99, 2)
""" fixer_io base: GBP, target: PHP, value: 99 """
assert fixer('GBP', 'PHP', 99, FIXER_RATES_JSON_TEST) > 50

def test_fixer_eur_to_aud_value_99():
assert fixer('EUR', 'AUD', 99, FIXER_RATES_JSON_TEST) > 1.2 * 99

def test_fixer_nzd_to_eur_value():
""" checking since stored values are in terms of EUR as base """
assert fixer('NZD', 'EUR', 1, FIXER_RATES_JSON_TEST) > 0.5

def test_fixer_invalid_currency():
""" when invalid currency is passed to fixer.io """
assert fixer('USD', 'TTT', 1) == "KeyError: Invalid curreny"
assert fixer('USD', 'TTT', 1, FIXER_RATES_JSON_TEST) == "KeyError: Invalid curreny"
3 changes: 2 additions & 1 deletion tests/test_file_handling.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
""" test the JSON file handling """

import pkg_resources
import json
from exch import file_handling

Expand All @@ -12,7 +13,7 @@ class TestFileHandling:
"""
new_base = 'PHP'
new_target = 'CAD'
filepath = 'tests/data_for_test_file_handling/defaults.json'
filepath = pkg_resources.resource_filename('exch', 'data/defaults.json')

def test_set_default_base(self):
file_handling.set_default_base(self.new_base, self.filepath)
Expand Down

0 comments on commit ca55e5e

Please sign in to comment.