graded = 8/10

Homework 6: Web Applications

For this homework, you're going to write a web API for the lake data in the MONDIAL database. (Make sure you've imported the data as originally outlined in our week 1 tutorial.)

The API should perform the following tasks:

  • A request to /lakes should return a JSON list of dictionaries, with the information from the name, elevation, area and type fields from the lake table in MONDIAL.
  • The API should recognize the query string parameter sort. When left blank or set to name, the results should be sorted by the name of the lake (in alphabetical order). When set to area or elevation, the results should be sorted by the requested field, in descending order.
  • The API should recognize the query string parameter type. When specified, the results should only include rows that have the specified value in the type field.
  • You should be able to use both the sort and type parameters in any request.

This notebook contains only test requests to your API. Write the API as a standalone Python program, start the program and then run the code in the cells below to ensure that your API produces the expected output. When you're done, paste the source code in the final cell (so we can check your work, if needed).

Hints when writing your API code:

  • You'll need to construct the SQL query as a string, piece by piece. This will likely involve a somewhat messy tangle of if statements. Lean into the messy tangle.
  • Make sure to use parameter placeholders (%s) in the query.
  • If you're getting SQL errors, print out your SQL statement in the request handler function so you can debug it. (When you use print() in Flask, the results will display in your terminal window.)
  • When in doubt, return to the test code. Examine it carefully and make sure you know exactly what it's trying to do.

Problem set #1: A list of lakes

Your API should return a JSON list of dictionaries (objects). Use the code below to determine what the keys of the dictionaries should be. (For brevity, this example only prints out the first ten records, but of course your API should return all of them.)

Expected output:

143 lakes
Ammersee - elevation: 533 m / area: 46 km^2 / type: None
Arresoe - elevation: None m / area: 40 km^2 / type: None
Atlin Lake - elevation: 668 m / area: 798 km^2 / type: None
Balaton - elevation: 104 m / area: 594 km^2 / type: None
Barrage de Mbakaou - elevation: None m / area: None km^2 / type: dam
Bodensee - elevation: 395 m / area: 538 km^2 / type: None
Brienzersee - elevation: 564 m / area: 29 km^2 / type: None
Caspian Sea - elevation: -28 m / area: 386400 km^2 / type: salt
Chad Lake - elevation: 250 m / area: 23000 km^2 / type: salt
Chew Bahir - elevation: 520 m / area: 800 km^2 / type: salt

In [1]:
import requests
data = requests.get('http://localhost:5000/lakes').json()
print(len(data), "lakes")
for item in data[:10]:
    print(item['name'], "- elevation:", item['elevation'], "m / area:", item['area'], "km^2 / type:", item['type'])


143 lakes
Ammersee - elevation: 533.0 m / area: 46.6 km^2 / type: None
Arresoe - elevation: None m / area: 40.2 km^2 / type: None
Atlin Lake - elevation: 668.0 m / area: 798.0 km^2 / type: None
Balaton - elevation: 104.0 m / area: 594.0 km^2 / type: None
Barrage de Mbakaou - elevation: None m / area: None km^2 / type: dam
Bodensee - elevation: 395.0 m / area: 538.5 km^2 / type: None
Brienzersee - elevation: 564.0 m / area: 29.8 km^2 / type: None
Caspian Sea - elevation: -28.0 m / area: 386400.0 km^2 / type: salt
Chad Lake - elevation: 250.0 m / area: 23000.0 km^2 / type: salt
Chew Bahir - elevation: 520.0 m / area: 800.0 km^2 / type: salt

Problem set #2: Lakes of a certain type

The following code fetches all lakes of type salt and finds their average area and elevation.

Expected output:

average area: 18880
average elevation: 970

In [2]:
import requests
data = requests.get('http://localhost:5000/lakes?type=salt').json()
avg_area = sum([x['area'] for x in data if x['area'] is not None]) / len(data)
avg_elev = sum([x['elevation'] for x in data if x['elevation'] is not None]) / len(data)
print("average area:", int(avg_area))
print("average elevation:", int(avg_elev))


average area: 18880
average elevation: 970

Problem set #3: Lakes in order

The following code fetches lakes in reverse order by their elevation and prints out the name of the first fifteen, excluding lakes with an empty elevation field.

Expected output:

* Licancabur Crater Lake
* Nam Co
* Lago Junin
* Lake Titicaca
* Poopo
* Salar de Uyuni
* Koli Sarez
* Lake Irazu
* Qinghai Lake
* Segara Anak
* Lake Tahoe
* Crater Lake
* Lake Tana
* Lake Van
* Issyk-Kul

In [3]:
import requests
data = requests.get('http://localhost:5000/lakes?sort=elevation').json()
for item in [x['name'] for x in data if x['elevation'] is not None][:15]:
    print("*", item)


* Licancabur Crater Lake
* Nam Co
* Lago Junin
* Lake Titicaca
* Poopo
* Salar de Uyuni
* Koli Sarez
* Lake Irazu
* Qinghai Lake
* Segara Anak
* Lake Tahoe
* Crater Lake
* Lake Tana
* Lake Van
* Issyk-Kul

Problem set #4: Order and type

The following code prints the names of the largest caldera lakes, ordered in reverse order by area.

Expected output:

* Lake Nyos
* Lake Toba
* Lago Trasimeno
* Lago di Bolsena
* Lago di Bracciano
* Crater Lake
* Segara Anak
* Laacher Maar

In [4]:
import requests
data = requests.get('http://localhost:5000/lakes?sort=area&type=caldera').json()
for item in data:
    print("*", item['name'])


* Lake Nyos
* Lake Toba
* Lago Trasimeno
* Lago di Bolsena
* Lago di Bracciano
* Crater Lake
* Segara Anak
* Laacher Maar

Problem set #5: Error handling

Your API should work fine even when faced with potential error-causing inputs. For example, the expected output for this statement is an empty list ([]), not every row in the table.


In [5]:
import requests
data = requests.get('http://localhost:5000/lakes', params={'type': "' OR true; --"}).json()
data


Out[5]:
[{'area': 1040.0, 'elevation': 119.0, 'name': 'Inari', 'type': 'None'},
 {'area': 928.0, 'elevation': 123.0, 'name': 'Oulujaervi', 'type': 'None'},
 {'area': 472.0, 'elevation': None, 'name': 'Kallavesi', 'type': 'None'},
 {'area': 4370.0, 'elevation': 76.0, 'name': 'Saimaa', 'type': 'None'},
 {'area': 1118.0, 'elevation': 78.0, 'name': 'Paeijaenne', 'type': 'None'},
 {'area': 368.0, 'elevation': 123.0, 'name': 'Mjoesa-See', 'type': 'None'},
 {'area': 173.0, 'elevation': 419.0, 'name': 'Storuman', 'type': 'None'},
 {'area': 290.0, 'elevation': 161.0, 'name': 'Siljan', 'type': 'impact'},
 {'area': 1140.0, 'elevation': 0.7, 'name': 'Maelaren', 'type': 'None'},
 {'area': 5648.0, 'elevation': 44.0, 'name': 'Vaenern', 'type': 'None'},
 {'area': 1900.0, 'elevation': 88.0, 'name': 'Vaettern', 'type': 'None'},
 {'area': 40.2, 'elevation': None, 'name': 'Arresoe', 'type': 'None'},
 {'area': 56.0, 'elevation': 16.0, 'name': 'Loch Ness', 'type': 'None'},
 {'area': 71.0, 'elevation': 7.9, 'name': 'Loch Lomond', 'type': 'None'},
 {'area': 538.5, 'elevation': 395.0, 'name': 'Bodensee', 'type': 'None'},
 {'area': 80.0, 'elevation': 518.0, 'name': 'Chiemsee', 'type': 'None'},
 {'area': 56.36,
  'elevation': 584.0,
  'name': 'Starnberger See',
  'type': 'None'},
 {'area': 46.6, 'elevation': 533.0, 'name': 'Ammersee', 'type': 'None'},
 {'area': 3.3, 'elevation': 275.0, 'name': 'Laacher Maar', 'type': 'caldera'},
 {'area': 581.0, 'elevation': 372.0, 'name': 'Lac Leman', 'type': 'None'},
 {'area': 88.0, 'elevation': 406.0, 'name': 'Zurichsee', 'type': 'None'},
 {'area': 48.3, 'elevation': 558.0, 'name': 'Thunersee', 'type': 'None'},
 {'area': 29.8, 'elevation': 564.0, 'name': 'Brienzersee', 'type': 'None'},
 {'area': 113.7,
  'elevation': 434.0,
  'name': 'Vierwaldstattersee',
  'type': 'None'},
 {'area': 216.0, 'elevation': 193.0, 'name': 'Lago Maggiore', 'type': 'None'},
 {'area': 146.0, 'elevation': 197.0, 'name': 'Lago di Como', 'type': 'None'},
 {'area': 370.0, 'elevation': 65.0, 'name': 'Lago di Garda', 'type': 'None'},
 {'area': 128.0,
  'elevation': 259.0,
  'name': 'Lago Trasimeno',
  'type': 'caldera'},
 {'area': 114.0,
  'elevation': 335.0,
  'name': 'Lago di Bolsena',
  'type': 'caldera'},
 {'area': 57.0,
  'elevation': 164.0,
  'name': 'Lago di Bracciano',
  'type': 'caldera'},
 {'area': 14.4,
  'elevation': 995.0,
  'name': 'Laguna de Gallocanta',
  'type': 'None'},
 {'area': 320.0, 'elevation': 115.5, 'name': 'Neusiedlersee', 'type': 'None'},
 {'area': 594.0, 'elevation': 104.0, 'name': 'Balaton', 'type': 'None'},
 {'area': 368.0, 'elevation': 7.0, 'name': 'Lake Skutari', 'type': 'None'},
 {'area': 273.0, 'elevation': 849.0, 'name': 'Lake Prespa', 'type': 'None'},
 {'area': 367.0, 'elevation': 695.0, 'name': 'Lake Ohrid', 'type': 'None'},
 {'area': 922.0, 'elevation': None, 'name': 'Kiev Reservoir', 'type': 'dam'},
 {'area': 2155.0,
  'elevation': 44.0,
  'name': 'Kakhovka Reservoir',
  'type': 'dam'},
 {'area': 2252.0,
  'elevation': 44.0,
  'name': 'Kremenchuk Reservoir',
  'type': 'dam'},
 {'area': 6450.0,
  'elevation': 53.0,
  'name': 'Kuybyshev Reservoir',
  'type': 'dam'},
 {'area': 18400.0, 'elevation': 4.0, 'name': 'Ozero Ladoga', 'type': 'None'},
 {'area': 9616.0, 'elevation': 33.0, 'name': 'Ozero Onega', 'type': 'None'},
 {'area': 3555.0,
  'elevation': 30.0,
  'name': 'Ozero Pskovskoje',
  'type': 'None'},
 {'area': 31492.0, 'elevation': 455.0, 'name': 'Ozero Baikal', 'type': 'None'},
 {'area': 4560.0, 'elevation': 5.0, 'name': 'Ozero Taimyr', 'type': 'None'},
 {'area': 4400.0, 'elevation': None, 'name': 'Ozero Chanka', 'type': 'None'},
 {'area': 2500.0, 'elevation': 10.0, 'name': 'Ozero Tschany', 'type': 'None'},
 {'area': 41650.0, 'elevation': -422.0, 'name': 'Dead Sea', 'type': 'salt'},
 {'area': 165.0,
  'elevation': -212.0,
  'name': 'Lake Genezareth',
  'type': 'None'},
 {'area': 3740.0, 'elevation': 1719.0, 'name': 'Lake Van', 'type': 'salt'},
 {'area': 675.0, 'elevation': None, 'name': 'Lake Keban', 'type': 'None'},
 {'area': 5470.0, 'elevation': 1280.0, 'name': 'Lake Urmia', 'type': 'salt'},
 {'area': 1800.0,
  'elevation': 790.0,
  'name': 'Daryacheh ye Namak',
  'type': 'salt'},
 {'area': 1600.0,
  'elevation': 500.0,
  'name': 'Hamun e Jaz Murian',
  'type': 'salt'},
 {'area': 386400.0, 'elevation': -28.0, 'name': 'Caspian Sea', 'type': 'salt'},
 {'area': 17160.0, 'elevation': 31.0, 'name': 'Ozero Aral', 'type': 'salt'},
 {'area': 18428.0,
  'elevation': 342.0,
  'name': 'Ozero Balchash',
  'type': 'salt'},
 {'area': 6236.0, 'elevation': 1609.0, 'name': 'Issyk-Kul', 'type': 'salt'},
 {'area': 80.0,
  'elevation': 3250.0,
  'name': 'Koli Sarez',
  'type': 'naturaldam'},
 {'area': 5000.0, 'elevation': 780.0, 'name': 'Lop Nor', 'type': 'salt'},
 {'area': 911.0, 'elevation': None, 'name': 'Laguna de Bay', 'type': 'None'},
 {'area': 1103.0, 'elevation': 905.0, 'name': 'Lake Toba', 'type': 'caldera'},
 {'area': 11.3, 'elevation': 2008.0, 'name': 'Segara Anak', 'type': 'caldera'},
 {'area': 4583.0, 'elevation': 3195.0, 'name': 'Qinghai Lake', 'type': 'salt'},
 {'area': 1855.0, 'elevation': 4718.0, 'name': 'Nam Co', 'type': 'salt'},
 {'area': 5250.0, 'elevation': 183.0, 'name': 'Lake Nasser', 'type': 'dam'},
 {'area': 8502.0, 'elevation': None, 'name': 'Lake Volta', 'type': 'dam'},
 {'area': 49.0, 'elevation': 107.0, 'name': 'Lake Bosumtwi', 'type': 'impact'},
 {'area': 1243.0, 'elevation': None, 'name': 'Lake Kainji', 'type': 'dam'},
 {'area': 23000.0, 'elevation': 250.0, 'name': 'Chad Lake', 'type': 'salt'},
 {'area': None,
  'elevation': None,
  'name': 'Barrage de Mbakaou',
  'type': 'dam'},
 {'area': None, 'elevation': None, 'name': 'Lake Nyos', 'type': 'caldera'},
 {'area': 54.0, 'elevation': -155.0, 'name': 'Lac Assal', 'type': 'salt'},
 {'area': 320.0, 'elevation': 243.0, 'name': 'Lake Abbe', 'type': 'salt'},
 {'area': 1285.0, 'elevation': 619.0, 'name': 'Lake Abaya', 'type': 'salt'},
 {'area': 800.0, 'elevation': 520.0, 'name': 'Chew Bahir', 'type': 'salt'},
 {'area': 6405.0, 'elevation': 375.0, 'name': 'Lake Turkana', 'type': 'salt'},
 {'area': 3000.0, 'elevation': 1830.0, 'name': 'Lake Tana', 'type': 'None'},
 {'area': 5347.0,
  'elevation': 619.0,
  'name': 'Lake Sese Seko/Albertsee',
  'type': 'None'},
 {'area': 2325.0,
  'elevation': 920.0,
  'name': 'Rutanzige/Eduardsee',
  'type': 'None'},
 {'area': 1720.0, 'elevation': 914.0, 'name': 'Lake Kioga', 'type': 'None'},
 {'area': 68870.0,
  'elevation': 1134.0,
  'name': 'Lake Victoria',
  'type': 'None'},
 {'area': 2650.0, 'elevation': 1462.0, 'name': 'Lake Kivu', 'type': 'None'},
 {'area': 32893.0,
  'elevation': 782.0,
  'name': 'Lake Tanganjika',
  'type': 'None'},
 {'area': 5120.0, 'elevation': 931.0, 'name': 'Lake Mweru', 'type': 'None'},
 {'area': 10000.0,
  'elevation': 1140.0,
  'name': 'Lake Bangweulu',
  'type': 'None'},
 {'area': 5760.0, 'elevation': 800.0, 'name': 'Lake Rukwa', 'type': 'salt'},
 {'area': 1050.0, 'elevation': 1030.0, 'name': 'Lake Eyasi', 'type': 'salt'},
 {'area': 1040.0, 'elevation': 600.0, 'name': 'Lake Natron', 'type': 'salt'},
 {'area': 29600.0, 'elevation': 474.0, 'name': 'Lake Malawi', 'type': 'None'},
 {'area': 687.0, 'elevation': 474.0, 'name': 'Lake Chilwa', 'type': 'salt'},
 {'area': 8200.0,
  'elevation': 300.0,
  'name': 'Lake Mai Ndombe',
  'type': 'None'},
 {'area': 500.0, 'elevation': 272.0, 'name': 'Malebo Pool', 'type': 'None'},
 {'area': 2800.0,
  'elevation': 326.0,
  'name': 'Lake Cabora-Bassa',
  'type': 'dam'},
 {'area': 5580.0, 'elevation': None, 'name': 'Lake Kariba', 'type': 'dam'},
 {'area': 770.0, 'elevation': None, 'name': 'Lake Ngami', 'type': 'salt'},
 {'area': 6133.0,
  'elevation': 1000.0,
  'name': 'Etoscha Salt Pan',
  'type': 'salt'},
 {'area': 12000.0,
  'elevation': None,
  'name': 'Makarikari Salt Pan',
  'type': 'salt'},
 {'area': 31792.0,
  'elevation': 156.0,
  'name': 'Great Bear Lake',
  'type': 'None'},
 {'area': 28568.0,
  'elevation': 156.0,
  'name': 'Great Slave Lake',
  'type': 'None'},
 {'area': 798.0, 'elevation': 668.0, 'name': 'Atlin Lake', 'type': 'None'},
 {'area': 24420.0,
  'elevation': 218.0,
  'name': 'Lake Winnipeg',
  'type': 'None'},
 {'area': 4848.0, 'elevation': 260.0, 'name': 'Lake Nipigon', 'type': 'None'},
 {'area': 1942.0,
  'elevation': 350.0,
  'name': 'Lake Manicouagan',
  'type': 'impact'},
 {'area': 18960.0, 'elevation': 75.0, 'name': 'Lake Ontario', 'type': 'None'},
 {'area': 25745.0, 'elevation': 174.0, 'name': 'Lake Erie', 'type': 'None'},
 {'area': 59600.0, 'elevation': 176.0, 'name': 'Lake Huron', 'type': 'None'},
 {'area': 104.0, 'elevation': None, 'name': 'Lake Manitou', 'type': 'None'},
 {'area': 57800.0,
  'elevation': 176.0,
  'name': 'Lake Michigan',
  'type': 'None'},
 {'area': 82103.0,
  'elevation': 184.0,
  'name': 'Lake Superior',
  'type': 'None'},
 {'area': 1130.0, 'elevation': 30.0, 'name': 'Lake Champlain', 'type': 'None'},
 {'area': 186.0,
  'elevation': 153.0,
  'name': 'Lake Winnipesaukee',
  'type': 'None'},
 {'area': 1890.0,
  'elevation': None,
  'name': 'Lake Okeechobee',
  'type': 'None'},
 {'area': 648.0, 'elevation': None, 'name': 'Kentucky Lake', 'type': 'None'},
 {'area': 147.0,
  'elevation': None,
  'name': 'Chickamauga Lake',
  'type': 'None'},
 {'area': 337.0,
  'elevation': None,
  'name': 'Franklin. D. Roosevelt Lake',
  'type': 'dam'},
 {'area': 53.2, 'elevation': 1883.0, 'name': 'Crater Lake', 'type': 'caldera'},
 {'area': 4400.0,
  'elevation': 1279.0,
  'name': 'Great Salt Lake',
  'type': 'salt'},
 {'area': 497.0, 'elevation': 1900.0, 'name': 'Lake Tahoe', 'type': 'None'},
 {'area': 487.0, 'elevation': 1155.0, 'name': 'Pyramid Lake', 'type': 'salt'},
 {'area': 645.0, 'elevation': 1127.0, 'name': 'Lake Powell', 'type': 'dam'},
 {'area': 640.0, 'elevation': 372.0, 'name': 'Lake Mead', 'type': 'dam'},
 {'area': 981.0, 'elevation': None, 'name': 'Fort Peck Lake', 'type': 'dam'},
 {'area': 1578.0, 'elevation': None, 'name': 'Lake Sakakawea', 'type': 'dam'},
 {'area': 1453.0, 'elevation': None, 'name': 'Lake Oahe', 'type': 'dam'},
 {'area': 1685.0,
  'elevation': 1520.0,
  'name': 'Lago de Chapala',
  'type': 'None'},
 {'area': 8157.0, 'elevation': 31.0, 'name': 'Lake Nicaragua', 'type': 'None'},
 {'area': 1035.0, 'elevation': 39.0, 'name': 'Lake Managua', 'type': 'None'},
 {'area': 1.0, 'elevation': 3200.0, 'name': 'Lake Irazu', 'type': 'acid'},
 {'area': 0.4,
  'elevation': None,
  'name': 'La Brea Pitch Lake',
  'type': 'asphalt'},
 {'area': 13000.0, 'elevation': 0.0, 'name': 'Lake Maracaibo', 'type': 'None'},
 {'area': 4214.0,
  'elevation': None,
  'name': 'Lago de Sobradinho',
  'type': 'dam'},
 {'area': 175.0, 'elevation': 4082.0, 'name': 'Lago Junin', 'type': 'None'},
 {'area': 8372.0,
  'elevation': 3812.0,
  'name': 'Lake Titicaca',
  'type': 'None'},
 {'area': 1340.0, 'elevation': 3686.0, 'name': 'Poopo', 'type': 'salt'},
 {'area': 10582.0,
  'elevation': 3650.0,
  'name': 'Salar de Uyuni',
  'type': 'salt'},
 {'area': 0.005,
  'elevation': 5600.0,
  'name': 'Licancabur Crater Lake',
  'type': 'crater'},
 {'area': 5770.0,
  'elevation': 71.0,
  'name': 'Laguna Mar Chiquita',
  'type': 'salt'},
 {'area': 9500.0, 'elevation': -17.0, 'name': 'Lake Eyre', 'type': 'salt'},
 {'area': 6.64,
  'elevation': 556.0,
  'name': 'Lake Burley Griffin',
  'type': 'dam'},
 {'area': 145.0, 'elevation': 1200.0, 'name': 'Lake Eucumbene', 'type': 'dam'},
 {'area': 30.0, 'elevation': 915.0, 'name': 'Lake Jindabyne', 'type': 'dam'},
 {'area': 202.0, 'elevation': None, 'name': 'Lake Hume', 'type': 'None'}]

Specifying a field other than name, area or elevation for the sort parameter should fail silently, defaulting to sorting alphabetically. Expected output: ['Ammersee', 'Arresoe', 'Atlin Lake', 'Balaton', 'Barrage de Mbakaou']


In [6]:
import requests
data = requests.get('http://localhost:5000/lakes', params={'sort': "florb"}).json()
[x['name'] for x in data[:5]]


Out[6]:
['Ammersee', 'Arresoe', 'Atlin Lake', 'Balaton', 'Barrage de Mbakaou']

Paste your code

Please paste the code for your entire Flask application in the cell below, in case we want to take a look when grading or debugging your assignment.


In [ ]:
## THIS CODE RETURNS CORRECTLY ALL OF THE ABOVE EXCEPT THE FIRST PROBLEM OF PROBLEM SET 5, there are two lines commented out below that I used 
# to try solve that  but it didn't work out unfortunately.

from flask import Flask, request, jsonify
import pg8000

app = Flask (__name__)
conn = pg8000.connect(database="mondial", user="gcg")

@app.route("/lakes")
def get_lakes():
    cursor = conn.cursor()
    sort_default = "name"
    sorting_option = request.args.get('sort', sort_default)
    if 'area' in sorting_option:
        sorting_option = sorting_option + " DESC"
    if 'elevation' in sorting_option:
        sorting_option = sorting_option + " DESC"
    if 'florb' in sorting_option:
        sorting_option = sort_default

    cursor.execute("SELECT name, elevation, area, type FROM lake ORDER BY {}".format(sorting_option))


    type_option = request.args.get('type', None)
    # if "' " in type_option:
    #     return ([])
    if type_option:
        cursor.execute("SELECT name, elevation, area, type FROM lake WHERE type = '{}' ORDER BY {}".format(type_option, sorting_option))
    else:
        cursor.execute("SELECT name, elevation, area, type FROM lake ORDER BY {}".format(sorting_option))


    output=[]
    for item in cursor.fetchall():
        get_name = str(item[0])
        get_type = str(item[3])
        if item[1] is not None:
            get_elevation = float(item[1])
        else:
            get_elevation = None
        if item[2] is not None:
            get_area = float(item[2])
        else:
            get_area = None
        output.append({'name': get_name,
                       'elevation': get_elevation,
                       'area': get_area,
                       'type': get_type})

    return jsonify(output)

app.run()