Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The rates are updated daily 3PM CET.

BitCoin Price Source:
---------------------
Bitcoin prices calculated every minute. For more infomation visit [CoinDesk API](http://www.coindesk.com/api/).
Bitcoin prices calculated every minute. For more information visit [CoinDesk API](http://www.coindesk.com/api/).

Installation
--------------
Expand Down Expand Up @@ -124,4 +124,4 @@ We welcome your feedback and support, raise `github ticket`_ if you want to repo
.. _contact us here: https://micropyramid.com/contact-us/
.. _Forex Python: https://micropyramid.com/oss/
.. _github ticket: https://github.com/MicroPyramid/forex-python/issues
.. _Documentation Here: http://forex-python.readthedocs.org/en/latest/?badge=latest
.. _Documentation Here: http://forex-python.readthedocs.org/en/latest/?badge=latest
20 changes: 17 additions & 3 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ Currency Rates
>>> c.convert('USD', 'INR', 10, date_obj)
585.09

7. Force use of Decimal::
>>> from forex_python.converter import CurrencyRates
>>> c = CurrencyRates(force_decimal=True)
>>> c.convert('USD', 'INR', Decimal('10.45'))
705.09
>>> c.convert('USD', 'INR', 10)
DecimalFloatMismatchError: convert requires amount parameter is of type Decimal when use_decimal=True

8. Detect use of Decimal::
>>> from forex_python.converter import CurrencyRates
>>> c = CurrencyRates()
>>> c.convert('USD', 'INR', Decimal('10.45'))
705.09
>>> c.convert('USD', 'INR', 10)
674.73


Bitcoin Prices:
---------------
1. Get latest price of one Bitcoin::
Expand Down Expand Up @@ -98,6 +115,3 @@ Currency Symbols & Codes
u'European Euro'
>>> c.get_currency_name('INR')
u'Indian rupee'



41 changes: 33 additions & 8 deletions forex_python/converter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import json
import simplejson as json
import requests
from decimal import Decimal


class RatesNotAvailableError(Exception):
Expand All @@ -10,10 +11,17 @@ class RatesNotAvailableError(Exception):
pass


class DecimalFloatMismatchError(Exception):
"""
A float has been supplied when force_decimal was set to True
"""
pass


class Common:

def __init__(self):
pass
def __init__(self, force_decimal=False):
self._force_decimal = force_decimal

def _source_url(self):
return "http://api.fixer.io/"
Expand All @@ -24,6 +32,16 @@ def _get_date_string(self, date_obj):
date_str = date_obj.strftime('%Y-%m-%d')
return date_str

def _decode_rates(self, response, use_decimal=False):
if self._force_decimal or use_decimal:
decoded_data = json.loads(response.text, use_decimal=True).get('rates', {})
else:
decoded_data = response.json().get('rates', {})
return decoded_data

def _get_decoded_rate(self, response, dest_cur, use_decimal=False):
return self._decode_rates(response, use_decimal=use_decimal).get(dest_cur, None)


class CurrencyRates(Common):

Expand All @@ -33,7 +51,7 @@ def get_rates(self, base_cur, date_obj=None):
source_url = self._source_url() + date_str
response = requests.get(source_url, params=payload)
if response.status_code == 200:
rates = response.json().get('rates', {})
rates = self._decode_rates(response)
return rates
raise RatesNotAvailableError("Currency Rates Source Not Ready")

Expand All @@ -43,25 +61,32 @@ def get_rate(self, base_cur, dest_cur, date_obj=None):
source_url = self._source_url() + date_str
response = requests.get(source_url, params=payload)
if response.status_code == 200:
rate = response.json().get('rates', {}).get(dest_cur)
rate = self._get_decoded_rate(response, dest_cur)
if not rate:
raise RatesNotAvailableError("Currency Rate {0} => {1} not available for Date {2}".format(
base_cur, dest_cur, date_str))
return rate
raise RatesNotAvailableError("Currency Rates Source Not Ready")

def convert(self, base_cur, dest_cur, amount, date_obj=None):
if isinstance(amount, Decimal):
use_decimal = True
else:
use_decimal = self._force_decimal
date_str = self._get_date_string(date_obj)
payload = {'base': base_cur, 'symbols': dest_cur}
source_url = self._source_url() + date_str
response = requests.get(source_url, params=payload)
if response.status_code == 200:
rate = response.json().get('rates', {}).get(dest_cur, None)
rate = self._get_decoded_rate(response, dest_cur, use_decimal=use_decimal)
if not rate:
raise RatesNotAvailableError("Currency {0} => {1} rate not available for Date {2}.".format(
source_url, dest_cur, date_str))
converted_amount = rate * amount
return converted_amount
try:
converted_amount = rate * amount
return converted_amount
except TypeError:
raise DecimalFloatMismatchError("convert requires amount parameter is of type Decimal when force_decimal=True")
raise RatesNotAvailableError("Currency Rates Source Not Ready")


Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from setuptools import setup, find_packages

VERSION = '0.3.1'
VERSION = '0.3.2'

setup(
name='forex-python',
Expand All @@ -16,6 +16,7 @@
include_package_data=True,
install_requires=[
'requests',
'simplejson',
],
classifiers=[
'Intended Audience :: Developers',
Expand Down
65 changes: 64 additions & 1 deletion tests/test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import datetime
from unittest import TestCase
from forex_python.converter import get_rates, get_rate, convert, get_symbol, get_currency_name, RatesNotAvailableError
from forex_python.converter import (get_rates, get_rate, convert, get_symbol,
get_currency_name, RatesNotAvailableError,
CurrencyRates, DecimalFloatMismatchError)
from decimal import Decimal


class TestGetRates(TestCase):
Expand Down Expand Up @@ -83,6 +86,66 @@ def test_amount_convert_invalid_currency(self):
self.assertRaises(RatesNotAvailableError, convert, 'ABC', 'XYZ', 10)


class TestForceDecimalAmountConvert(TestCase):
"""
Test the force_decimal=True type enforcing
"""

def setUp(self):
self.c = CurrencyRates(force_decimal=True)

def test_amount_decimal_convert(self):
amount = self.c.convert('USD', 'INR', Decimal('10.45'))

self.assertTrue(isinstance(amount, Decimal))

def test_amount_decimal_convert_date(self):
date_obj = datetime.datetime.strptime('2010-05-10', "%Y-%m-%d").date()
amount = self.c.convert('USD', 'INR', Decimal('10.45'), date_obj)

self.assertTrue(isinstance(amount, Decimal))

def test_amount_decimal_invalid_type(self):
self.assertRaises(DecimalFloatMismatchError, self.c.convert, 'USD', 'INR', 10.45)

def test_decimal_get_rates_valid_code(self):
all_rates = self.c.get_rates('USD')
# Check if return value of get_rates dictionary
self.assertTrue(isinstance(all_rates, dict))
# Test at least one rate value returned
self.assertTrue(len(all_rates.keys()))
# Test one rate in returned dict is now a Decimal
self.assertTrue(isinstance(all_rates.get('INR'), Decimal))

def test_decimal_get_rates_with_date(self):
date_obj = datetime.datetime.strptime('2010-05-10', "%Y-%m-%d").date()
all_rates = self.c.get_rates('USD', date_obj)
# Check if return value of get_rates dictionary
self.assertTrue(isinstance(all_rates, dict))
# Test at least one rate value returned
self.assertTrue(len(all_rates.keys()))
# Test one rate in returned dict is now a Decimal
self.assertTrue(isinstance(all_rates.get('INR'), Decimal))

def test_decimal_get_rates_invalid_code(self):
self.assertRaises(RatesNotAvailableError, self.c.get_rates, 'XYZ')

def test_decimal_get_rate_with_valid_codes(self):
rate = self.c.get_rate('USD', 'INR')
# check if return value is Decimal
self.assertTrue(isinstance(rate, Decimal))

def test_decimal_get_rate_with_date(self):
date_obj = datetime.datetime.strptime('2010-05-10', "%Y-%m-%d").date()
rate = self.c.get_rate('USD', 'INR', date_obj)
# check if return value is Decimal
self.assertTrue(isinstance(rate, Decimal))

def test_decimal_get_rate_with_invalid_codes(self):
# raise exception for invalid currency codes
self.assertRaises(RatesNotAvailableError, self.c.get_rate, 'ABCD', 'XYZ')


class TestCurrencySymbol(TestCase):
"""
test currency symbols from currency codes
Expand Down