-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlambda.py
302 lines (231 loc) · 10.4 KB
/
lambda.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from json import loads
from urllib.request import urlopen
from time import time, strftime, localtime
import logging
from currency_config import CURR_ABBRS
'''Currency Exchange Rate program written as a AWS lambda routine.
Makes one request when invoked and returns HTML to calling browser.
'''
logger = logging.getLogger()
logger.setLevel(logging.INFO)
class CurrencyLayer:
def __init__(self, base, mode, key, basket):
'''Build URL we will use to get latest exchange rates
Arguments:
base {str}- base portion of URL
mode {str}- 'live' or 'list'
key {str} - Access Key provided when siging up for CurrencyLayer Account
basket {str} - Comma separated currency abbreviations
'''
if mode == 'list':
self.cl_url = base + 'list?' + 'access_key=' + key
elif mode == 'live':
self.cl_url = base + 'live?' + 'access_key=' \
+ key + '¤cies=' + basket
def cl_validate(self, url):
"""Attempt to open supplied URL. If initial open is successful, read
contents to determine if API call was successful. If so, return
dictionary object with rates, else return error string
"""
try:
webUrl = urlopen (url)
except:
err_msg = 'In cl_validate(): url() open failed'
return err_msg
else:
rate_data = webUrl.read()
rate_dict = loads(rate_data.decode('utf-8'))
if rate_dict['success'] is False:
err_msg = 'CL Error: {}'.format(rate_dict['error']['info'])
return err_msg
else:
return rate_dict
def get_rates(self, spread):
'''Loop through exchange rate raw data and returned formatted HTML'''
rates = self.cl_validate(self.cl_url)
spread = float(spread)
if isinstance(rates, str): # cl_validate returned Error
rate_html = "<p>" + rates + "</p>"
elif isinstance(rates, dict): # cl_validate returned data
ts = t_stamp(rates['timestamp'])
rate_html = "<h2>As of " + ts + "</h2>"
# Create Form to enable manipulation of Spread within a range
# This approach also provides input validation
rate_html += "<div id='inputs' class='myForm' text-align: center>"
rate_html += "<form id='spread_form' action='#' "
rate_html += "onsubmit=\"changeSpread('text');return false\">"
rate_html += "<label for='spread_label'>Spread: </label>"
rate_html += "<input id='spread_input' type='number' min='.10' \
max='2.0' step='.05' size='4' maxlength='4' \
value='{:3.2f}'>".format(spread)
rate_html += "<input type='submit' class='button'>"
rate_html += "</form></div>"
spread = spread / 100 # convert to percentage
rate_html += "<br>"
for exch, cur_rate in rates['quotes'].items():
in_usd = exch[-3:] + '/USD'
in_for = 'USD/' + exch[3:]
usd_spread = (1/cur_rate)*(1+spread)
for_spread = cur_rate*(1/(1+spread))
_usd = "{}: {:>9.4f} ({:>9.4f}) {}: {:>7.4f} ({:>6.4f})".format(
in_usd, 1/cur_rate, usd_spread,
in_for, cur_rate, for_spread)
_for = "{}: {:>9.4f} ({:>9.4f}) {}: {:>7.4f} ({:>6.4f})".format(
in_for, cur_rate, for_spread,
in_usd, 1/cur_rate, usd_spread)
if exch[3:] in ['EUR', 'GBP', 'AUD', 'BTC']:
rate_html += "<pre>" + _usd + "</pre>"
else:
rate_html += "<pre>" + _for + "</pre>"
else:
rate_html = "<p>Expected string or dict in get_rates()<p>"
return rate_html
def get_list(basket):
'''Loop through basket of currency abbreviations and return with definitions
Implemented as a function vs. class as not dependent on web service.
'''
rate_html = "<h2>Abbreviations</h2>"
basket_list = basket.split(',')
unique = [] # Used to eliminate redundant currencies
for abbr in basket_list:
if abbr not in unique:
unique.append(abbr)
if abbr in CURR_ABBRS:
rate_html += "<p>{} = {}</p>".format(abbr, CURR_ABBRS[abbr])
else:
rate_html += "<p>{} = {}</p>".format(
abbr.upper(), "Sorry, have no idea!")
return rate_html
def build_select(basket):
'''Loop through basket of currency abbreviations and return with a list of
selections to be added to basket.
'''
basket_list = basket.split(',')
select_html = "<div id='cur_select' class='myForm'>"
select_html += "<form id='currency_form' action='#' "
select_html += "onsubmit=\"addCurrency('text');return false\">"
select_html += "<label for='select_label'></label>"
select_html += "<select id='currency_abbr' type='text' name='abbrSelect'>"
select_html += "<option disabled selected value> Add Currency </option>"
for abbr in CURR_ABBRS:
if abbr not in basket_list:
select_html += "<option value='{}'>{}</option>".format(
abbr, CURR_ABBRS[abbr])
select_html += "</select>"
select_html += "<input type='submit' class='button' onclick = '...'>"
select_html += "</form></div>"
select_html += "<br>"
return select_html
def t_stamp(t):
"""Utility function to format date and time from passed UNIX time"""
return(strftime('%y-%m-%d %H:%M %Z', localtime(t)))
def build_resp(event):
'''Format the Head section of the DOM including any CSS formatting to
apply to the remainder of the document. Break into multiple lines for
improved readability
'''
# Define key variables defaults associated with CurrencyLayer web service
from currency_config import CL_KEY, BASE, MODE, basket, api_spread
from currency_config import MAIN_CSS_HREF
# If options passed as URL parameters replace default values accordingly
try:
options = event['params']['querystring']
except:
options = False
else:
for key, val in options.items():
if key.lower() == "currencies":
if val:
basket = val
if key.lower() == "spread":
if val:
api_spread = val
logger.info('Basket: {}'.format(basket))
logger.info('Spread: {}'.format(api_spread))
# Instantiate currency_layer() object and initialize valiables
try:
c = CurrencyLayer(BASE, MODE, CL_KEY, basket)
except:
rates = "<p>Error: unable to instantiate currency_layer()"
else:
rates = c.get_rates(api_spread)
logger.info('Rates: {}'.format(rates))
# Variables used by Javascript routines to refresh page content
api_params = '\u003F{}{}'.format('currencies=', basket)
html_head = "<!DOCTYPE html>"
html_head += "<head>"
html_head += "<title>Display Currency Exchange Rates</title>"
html_head += "<meta charset='utf-8'>"
html_head += "<meta name='viewport' content='width=device-width'>"
# Stop annoying favicon.ico download attempts / failure
html_head += "<link rel='icon' href='data:,'>"
# Import CSS style config from publically readable S3 bucket
html_head += "<link rel='stylesheet' type='text/css' media='screen'"
html_head += "href={}>".format(MAIN_CSS_HREF)
html_head += "</head>"
html_body = "<body>"
html_body += "<h1>Currency Exchange Rates</h1>"
# Output list of currency exchange rates
html_body += "<div class='center'>"
html_body += "<div>"
html_body += rates
html_body += "</div>"
# Add a new currency to basket
html_body += "<div>"
html_body += build_select(basket)
html_body += "</div>"
# Output list of currency definitions
html_body += "<div>"
html_body += get_list(basket)
html_body += "</div>"
# Provide button to reset currency basket and spread to default
html_body += "<div>"
html_body += "<button class='button' onclick='resetDefaults()'>"
html_body += "Reset Currencies and Spread"
html_body += "</button>"
html_body += "</div>"
html_body += "</div>"
html_body += "<br><br>"
html_body += "</body>"
# Note the following section should ideally be moved to a separate file on
# S3 similar to what was done with the CSS stylesheeet. Given the small
# amount of Javascript code and the need to enforce strict JS loading with
# approximately the same amount of JS, decision is to leave inline for now
html_js = "<script type='text/javascript'>"
html_js += "'use strict';"
#html_js += "var _spread = {:3.1f};".format(float(api_spread))
html_js += "var _base = getURIbase() + '{}';".format(api_params)
html_js += "function getURIbase() {"
html_js += "var getUrl = window.location;"
html_js += "var baseUrl = getUrl.origin + getUrl.pathname;"
html_js += "return baseUrl"
html_js += "}"
html_js += "function resetDefaults() {"
html_js += "location.replace(getURIbase());"
html_js += "return false;"
html_js += "}"
html_js += "function changeSpread(action) {"
html_js += "var _spr = document.getElementById('spread_input').value;"
html_js += "var _url = _base + '\u0026spread=' + _spr;"
html_js += "location.replace(`${_url}`);"
html_js += "}"
html_js += "function addCurrency(action) {"
html_js += "var _spr = document.getElementById('spread_input').value;"
html_js += "var _abbr = document.getElementById('currency_abbr').value;"
html_js += "if (_abbr) {"
html_js += "var _url = _base + ',' + _abbr + '\u0026spread=' + _spr;"
html_js += "location.replace(`${_url}`);"
html_js += "} else {"
html_js += "alert('Please select a currency');"
html_js += "}"
html_js += "}"
html_js += "</script>"
html_tail = '</html>'
resp = html_head + html_body + html_js + html_tail
return resp
def lambda_handler(event, context):
print("In lambda handler")
logger.info('Event: {}'.format(event))
return build_resp(event)