This is a fork of the tool made by kuszaj (https://github.com/kuszaj/nbpy), I added a http(s) proxy connectivity.
A utility package for calling NBP (Polish National Bank) Web API and converting various currencies to Polish zloty using its exchange rates.
NBPy requires Python 3.3 or newer
From the forked source code:
$ pip install git+https://github.com/oskar-j/nbpy.git
NBPy provides a NBPClient
class for generating API callers, given available currency code:
>>> import nbpy
>>> #: Available currencies
>>> nbpy.currencies
{'EUR': NBPCurrency(Euro, code=EUR, tables={'A', 'C'}), 'USD': NBPCurrency(United States dollar, code=USD, tables={'A', 'C'}), ...}
>>> nbp = nbpy.NBPClient('eur')
>>> nbp
NBPClient(USD, as_float=False, suppress_errors=False, cache_size=128)
>>> nbp.currency_code = 'EUR'
>>> nbp
NBPClient(EUR, as_float=False, suppress_errors=False, cache_size=128)
currency_code
has to be one of the available codes from nbpy.currencies
otherwise NBPClient
raises UnknownCurrencyCode
.
>>> from nbpy.errors import UnknownCurrencyCode
>>> 'XYZ' in nbpy.currencies
False
>>> try:
... nbp.currency_code = 'XYZ'
... except UnknownCurrencyCode:
... print('XYZ is unknown')
...
XYZ is unknown
All API calls defined in NBPClient
returns either a NBPExchangeRate
object or a list its instances.
.current()
returns current exchange rate for currency. Note that it doesn't necessarily mean current day: for weekends, holidays and before official announcements by Polish National Bank method returns last available value.
>>> nbp.current()
NBPExchangeRate(EUR->PLN, 2017-10-31, mid=4.2498)
>>> #: Calling NBPClient object is synonymous with current()
>>> nbp()
NBPExchangeRate(EUR->PLN, 2017-10-31, mid=4.2498)
.today()
returns exchange rate for current day, if available.
Otherwise, raises APIError
.
>>> nbp.today()
NBPExchangeRate(EUR->PLN, 2017-10-31, mid=4.2498)
...
>>> #: A day later, during national holiday
>>> from nbpy.errors import APIError
>>> try:
... nbp.today()
... except APIError:
... print("No data available")
...
No data available
.date(date)
returns exchange rate for given day, if available. Otherwise, raises APIError
. Argument date
has to be either datetime.datetime
or a properly formatted date string (YYYY-MM-DD
), otherwise method raises DateFormattingError
.
>>> from nbpy.errors import APIError, DateFormattingError
>>> nbp.date('2017-10-02')
NBPExchangeRate(EUR->PLN, 2017-10-02, mid=4.3137)
>>> try:
... nbp.date('2017-10-01')
... except APIError:
... print("No data available for date")
...
No data available for date
>>> try:
... nbp.date('01/10/17')
... except DateFormattingError:
... print("Improperly formatted date string")
...
Improperly formatted date string
.last(n)
returns last n
available exchange rates, ordered by date in ascending order.
>>> nbp.last(3)
[NBPExchangeRate(EUR->PLN, 2017-10-27, mid=4.2520),
NBPExchangeRate(EUR->PLN, 2017-10-30, mid=4.2403),
NBPExchangeRate(EUR->PLN, 2017-10-31, mid=4.2498)]
.date_range(start_date, end_date)
returns exchange rates for given date range [start_date, end_date]
, ordered by date in ascending order. Both arguments are restricted in the same way as date
for date()
method.
If range covers more than 93 days, method raises APIError
.
>>> from nbp.errors import APIError
>>> nbp.date_range('2017-10-01', '2017-10-14')
[NBPExchangeRate(EUR->PLN, 2017-10-02, mid=4.3137),
NBPExchangeRate(EUR->PLN, 2017-10-03, mid=4.3105),
NBPExchangeRate(EUR->PLN, 2017-10-04, mid=4.3025), ...]
>>> try:
... nbp.date_range('2017-01-01', '2017-06-01')
... except APIError:
... print('Invalid date range')
...
Invalid date range
By default all API call methods return average exchange rate (mid
). However, by passing bid_ask=True
you can additionally get bid/ask values. Not that not every currency has them available: for such case bid_ask
is ignored.
>>> nbp()
NBPExchangeRate(EUR->PLN, 2017-10-31, mid=4.2498)
>>> nbp(bid_ask=True)
NBPExchangeRate(EUR->PLN, 2017-11-02, bid=4.2036, ask=4.2886)
>>> #: No bid/ask values for CUP
>>> nbp.currency_code = 'CUP'
>>> nbp()
NBPExchangeRate(CUP->PLN, 2017-10-31, mid=3.6529)
>>> from nbpy.errors import BidAskUnavailable
>>> try:
... nbp(bid_ask=True)
... except BidAskUnavailable:
... print('Bid/ask unavailable')
...
Bid/ask unavailable
If you want API calls to always return something, despite possible issues with API, you can pass suppress_errors=True
to NBPClient
. With this flag turned on API calls instead of raising BidAskUnavailable
and APIError
exceptions will return None
.
>>> from nbp.errors import APIError
>>> try:
... nbp.date_range('2017-01-01', '2017-06-01')
... except APIError:
... print('Invalid date range')
...
Invalid date range
>>> nbp.suppress_errors = True
>>> print(nbp.date_range('2017-01-01', '2017-06-01'))
None
For efficiency, NBPClient
utilizes LRU cache for by saving last 128 calls. You can change this value by passing cache_size
to NBPClient
. This value can be set only during object initialization.
>>> nbp = NBPClient('eur', cache_size=64)
>>> nbp
NBPClient(EUR, as_float=False, suppress_errors=False, cache_size=64)
>>> try:
... nbp.cache_size = 128
... except AttributeError:
... print("Can't overwrite cache_size")
...
Can't overwrite cache_size
NBPClient
allows to set a http proxy
>>> nbp = NBPClient('eur', proxy_url='http://ip:port')
By default all exchange rates are parsed as decimal.Decimal
objects. You can change this behaviour by passing as_float=True
, which will force all exchange rates to be parsed as float
.
>>> nbp = NBPClient('eur')
>>> type(nbp().mid)
<class 'decimal.Decimal'>
>>> nbp = NBPClient('eur', as_float=True)
>>> type(nbp().mid)
<class 'float'>
NBPClient
calls returns an NBPExchangeRate
object (their list), which can be used as a converter for calculating given amount in foreign currency to Polish zlotys.
>>> exchange_rate = nbp()
>>> exchange_rate
NBPExchangeRate(EUR->PLN, 2017-10-31, mid=4.2498)
>>> amount = 1000
>>> exchange_rate(amount)
{'mid': Decimal('4249.8000')}
>>> exchange_rate * amount
{'mid': Decimal('4249.8000')}
>>> amount * exchange_rate
{'mid': Decimal('4249.8000')}
>>>
>>> exchange_rate = nbp(all_values=True)
>>> exchange_rate
NBPExchangeRate(EUR->PLN, 2017-11-02, bid=4.2036, ask=4.2886)
>>> exchange_rate(amount)
{'bid': Decimal('4204.3000'), 'ask': Decimal('4289.3000')}
Below script prints and summarises a list of invoices in foreign currencies.
from datetime import datetime, timedelta
from decimal import Decimal
from nbpy import NBPClient
from nbpy.errors import APIError
class Invoice(object):
"""Invoice class with builtin currency converter."""
def __init__(self, currency_code, date, amount):
self.currency_code = currency_code
self.date = date
self.amount = Decimal("{:.2f}".format(amount))
self._nbp = NBPClient(currency_code)
@property
def amount_in_pln(self):
exchange_rate = None
date = datetime.strptime(self.date, '%Y-%m-%d')
while exchange_rate is None:
# Get exchange rates until valid is found
try:
exchange_rate = self._nbp.date(date.strftime('%Y-%m-%d'))
break
except APIError:
date -= timedelta(days=1)
amount = (exchange_rate * self.amount)['mid']
return round(amount, 2)
# List of invoices in foreign currencies
invoices = [
Invoice('EUR', '2017-10-03', 650.0),
Invoice('EUR', '2017-10-06', 890.0),
Invoice('USD', '2017-10-11', 1230.0),
]
# Print all amounts in their currencies and PLN
template = "{currency} {amount:7.2f} {amount_in_pln:7.2f}"
for invoice in invoices:
print(template.format(
currency=invoice.currency_code,
amount=invoice.amount,
amount_in_pln=invoice.amount_in_pln,
))
# Sum all values in PLN
# Since amount_in_pln were already called, script will use cached values
# instead of calling NBP Web API
sum_amount_in_pln = sum([invoice.amount_in_pln for invoice in invoices])
print("-" * 23)
print(" total: {sum:8.2f}".format(sum=sum_amount_in_pln))
# EUR 650.00 2801.82
# EUR 890.00 3830.74
# USD 1230.00 4454.94
# -----------------------
# total: 11087.50