Solution 1

class Currency(object):
    USD_CONVERSIONS = {
        'CHF': 0.95,
        'CAD': 1.28,
        'GBP': 0.72,
        'JPY': 106.80,
        'EUR': 0.81,
        'USD': 1.0,
        'MXN': 18.62,
        'ARG': 20.24,
        'AUD': 1.27
    }
    BASE_UNIT = 'USD'

    def __init__(self, value, unit="USD"):
        if unit not in self.USD_CONVERSIONS:
            raise ValueError('Invalid Unit {}'.format(unit))
        self.value = value
        self.unit = unit

    def convert(self, target_unit):
        if target_unit not in self.USD_CONVERSIONS:
            raise ValueError('Invalid Unit {}'.format(target_unit))
        self_usd = self.value / self.USD_CONVERSIONS[self.unit]
        target_usd = self_usd * self.USD_CONVERSIONS[target_unit]
        return Currency(round(target_usd, 4), target_unit)

    def __str__(self):
        return '{}${}'.format(self.unit, self.value)

    def __repr__(self):
        return "Currency({}, '{}')".format(self.value, self.unit)

    def __eq__(self, other):
        self_usd = self.convert(self.BASE_UNIT)
        other_usd = other.convert(self.BASE_UNIT)
        return self_usd.value == other_usd.value

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        self_usd = self.convert(self.BASE_UNIT)
        other_usd = other.convert(self.BASE_UNIT)
        return self_usd.value < other_usd.value

    def __le__(self, other):
        return self < other or self == other

    def __gt__(self, other):
        return other < self

    def __ge__(self, other):
        return self > other or self == other

    def __add__(self, other):
        self_usd = self.convert(self.BASE_UNIT)
        other_usd = other.convert(self.BASE_UNIT)
        return Currency(self_usd.value + other_usd.value, self.BASE_UNIT)

    def __iadd__(self, other):
        total_usd = self + other
        total_self_unit = total_usd.convert(self.unit)
        return Currency(total_self_unit.value, self.unit)

    def __radd__(self, other):
        return self + Currency(other, 'USD')

    def __sub__(self, other):
        pass

    def __isub__(self, other):
        pass

    def __rsub__(self, other):
        pass

Currency My Way Out

Complete the methods for the class Currency so that it has functionality to compare and perform basic math operations on Currencies.

You can see in the editor that we have a dictionary containing the exchange rates of many currencies (MXN, EUR, ARG, etc). But they're all expressed in US Dollars (USD). So, how do you convert from (for example) EUR > MXN? Well, you have to go through USD first. The convert method takes any pair of currencies (as in our previous example EUR > MXN) and converts them (EUR to MXN). As we mentioned, you have to convert it to USD first, so the pseudocode looks something like:

# Objective: EUR > MXN
# Step 1: Convert EUR > USD (store value in EUR_usd)
# Step 2: Convert EUR_usd to > MXN

Probably makes more sense in this example:

# 4 EUR to MXN
# 4 / 0.81 (EUR conversion to USD) * 18.62 (MXN conversion from USD) == 91.95

This has similar magic methods to the Distance assignment, but now there additional ones to teach your class how to be added and (optionally) subtracted.

Use this guide for magic method guidance, and read the tests if you get stuck.

IMPORTANT: To avoid rounding issues, round your conversion up to 4 decimals (round(value, 4)). For example, to convert from AUD > USD you have to do (1 / 1.27) which yields a result with too many decimals: 0.7874015748031495. Round it to 4 decimals to get the expected result: round(0.7874015748031495, 4) == 0.7874.

Test Cases

test convert target to target - Run Test

def test_convert_target_to_target():
    c1 = Currency(1, 'AUD')
    c2 = Currency(1, 'EUR')

    aud_to_eur = c1.convert('EUR')
    aud_to_mxn = c1.convert('MXN')
    eur_to_jpy = c2.convert('JPY')
    eur_to_arg = c2.convert('ARG')

    assert aud_to_eur.value == 0.6378
    assert aud_to_mxn.value == 14.6614
    assert eur_to_jpy.value == 131.8519
    assert eur_to_arg.value == 24.9877

test general comparison - Run Test

def test_general_comparison():
    c1 = Currency(1.27, 'AUD')
    c2 = Currency(1, 'USD')
    c3 = Currency(100, 'CAD')

    assert c3 > c1
    assert c1 < c3

    # Same for c2
    assert c3 > c2
    assert c2 < c3

    assert c2 >= c1
    assert c2 <= c1
    assert c1 >= c2
    assert c1 <= c2

    assert c3 >= c1
    assert c1 <= c3
    assert c3 >= c2
    assert c2 <= c3

test convert invalid units - Run Test

import pytest

def test_convert_invalid_units():
    c1 = Currency(1, 'USD')

    with pytest.raises(ValueError):
        c1.convert('NOT-FOUND')

test convert usd to target - Run Test

def test_convert_usd_to_target():
    c1 = Currency(1, 'USD')

    in_aud = c1.convert('AUD')
    in_mxn = c1.convert('MXN')
    in_eur = c1.convert('EUR')
    in_usd = c1.convert('USD')

    assert in_aud.value == 1.27
    assert in_mxn.value == 18.62
    assert in_eur.value == 0.81
    assert in_usd.value == 1  # Same

test str repr - Run Test

def test_str_repr():
    c1 = Currency(1.1, 'CAD')
    c2 = Currency(2.5, 'USD')
    c3 = Currency(150, 'MXN')

    assert str(c1) == 'CAD$1.1'
    assert str(c2) == 'USD$2.5'
    assert str(c3) == 'MXN$150'

    assert repr(c1) == "Currency(1.1, 'CAD')"
    assert repr(c2) == "Currency(2.5, 'USD')"
    assert repr(c3) == "Currency(150, 'MXN')"

test add methods - Run Test

def test_add_methods():
    c1 = Currency(1, 'AUD')
    c2 = Currency(5, 'USD')
    c3 = Currency(100, 'CAD')
    c4 = Currency(2, 'USD')

    assert c1 + c2 == Currency(5.7874, 'USD')
    assert c1 + c3 == Currency(78.9124, 'USD')
    assert c2 + c3 == Currency(83.125, 'USD')
    assert c2 + c4 == Currency(7, 'USD')

    # __radd__
    assert 4 + c2 == Currency(9, 'USD')
    assert 1 + c3 == Currency(79.125, 'USD')

    # __iadd__
    c1 += Currency(3, 'AUD')
    assert c1.value == 4
    assert c1.unit == 'AUD'

    c2 += Currency(100, 'MXN')
    assert c2.value == 10.3706
    assert c2.unit == 'USD'

test convert target to usd - Run Test

def test_convert_target_to_usd():
    c1 = Currency(1, 'AUD')
    c2 = Currency(1, 'MXN')
    c3 = Currency(1, 'EUR')
    c4 = Currency(1, 'JPY')
    c5 = Currency(1, 'USD')

    c1_to_usd = c1.convert('USD')
    c2_to_usd = c2.convert('USD')
    c3_to_usd = c3.convert('USD')
    c4_to_usd = c4.convert('USD')
    c5_to_usd = c5.convert('USD')

    assert c1_to_usd.value == 0.7874
    assert c2_to_usd.value == 0.0537
    assert c3_to_usd.value == 1.2346
    assert c4_to_usd.value == 0.0094
    assert c5_to_usd.value == 1

test valid units - Run Test

import pytest

def test_valid_units():
    # We can't create a Currency with a unit not present in the
    # USD_conversions dict
    with pytest.raises(ValueError):
        Currency(1, 'NOT-FOUND')

test compare equality - Run Test

def test_compare_equality():
    c1 = Currency(1.27, 'AUD')
    c2 = Currency(1, 'USD')
    c3 = Currency(2, 'CAD')

    assert c1 == c2
    assert c1 != c3
    assert c2 != c3
class Currency(object): USD_CONVERSIONS = { 'CHF': 0.95, 'CAD': 1.28, 'GBP': 0.72, 'JPY': 106.80, 'EUR': 0.81, 'USD': 1.0, 'MXN': 18.62, 'ARG': 20.24, 'AUD': 1.27 } def __init__(self, value, unit="USD"): pass def convert(self, target_unit): pass def __str__(self): pass def __repr__(self): pass def __eq__(self, other): pass def __ne__(self, other): pass def __lt__(self, other): pass def __le__(self, other): pass def __gt__(self, other): pass def __ge__(self, other): pass def __add__(self, other): pass def __iadd__(self, other): pass def __radd__(self, other): pass def __sub__(self, other): # Optional. Not tested pass def __isub__(self, other): # Optional. Not tested pass def __rsub__(self, other): # Optional. Not tested pass