REST API#

  • REST stands for Representational State Transfer.

  • Architectural style for designing web services.

  • REST services are stateless, which means that they do not maintain any state between requests. This makes them scalable and reliable.

  • For us they are mainly interfaces for information retrieval.

Accessing REST#

  • REST services are based on the HTTP protocol, and they use a set of well-defined verbs to manipulate resources.

  • Some are open and free, some need an API key (free or subscription)

  • The four main verbs in REST are:

    • GET: Retrieve a resource.

    • POST: Create a resource.

    • PUT: Update a resource.

    • DELETE: Delete a resource.

Retrieving data (GET)#

import requests
# Power prices in the NO5 zone on a particular date
url = "https://www.hvakosterstrommen.no/api/v1/prices/2024/09-18_NO5.json"
response = requests.get(url)
print(response.json())
[{'NOK_per_kWh': 0.13359, 'EUR_per_kWh': 0.01134, 'EXR': 11.78, 'time_start': '2024-09-18T00:00:00+02:00', 'time_end': '2024-09-18T01:00:00+02:00'}, {'NOK_per_kWh': 0.11827, 'EUR_per_kWh': 0.01004, 'EXR': 11.78, 'time_start': '2024-09-18T01:00:00+02:00', 'time_end': '2024-09-18T02:00:00+02:00'}, {'NOK_per_kWh': 0.09448, 'EUR_per_kWh': 0.00802, 'EXR': 11.78, 'time_start': '2024-09-18T02:00:00+02:00', 'time_end': '2024-09-18T03:00:00+02:00'}, {'NOK_per_kWh': 0.08281, 'EUR_per_kWh': 0.00703, 'EXR': 11.78, 'time_start': '2024-09-18T03:00:00+02:00', 'time_end': '2024-09-18T04:00:00+02:00'}, {'NOK_per_kWh': 0.08293, 'EUR_per_kWh': 0.00704, 'EXR': 11.78, 'time_start': '2024-09-18T04:00:00+02:00', 'time_end': '2024-09-18T05:00:00+02:00'}, {'NOK_per_kWh': 0.12404, 'EUR_per_kWh': 0.01053, 'EXR': 11.78, 'time_start': '2024-09-18T05:00:00+02:00', 'time_end': '2024-09-18T06:00:00+02:00'}, {'NOK_per_kWh': 0.21098, 'EUR_per_kWh': 0.01791, 'EXR': 11.78, 'time_start': '2024-09-18T06:00:00+02:00', 'time_end': '2024-09-18T07:00:00+02:00'}, {'NOK_per_kWh': 0.23548, 'EUR_per_kWh': 0.01999, 'EXR': 11.78, 'time_start': '2024-09-18T07:00:00+02:00', 'time_end': '2024-09-18T08:00:00+02:00'}, {'NOK_per_kWh': 0.23372, 'EUR_per_kWh': 0.01984, 'EXR': 11.78, 'time_start': '2024-09-18T08:00:00+02:00', 'time_end': '2024-09-18T09:00:00+02:00'}, {'NOK_per_kWh': 0.23124, 'EUR_per_kWh': 0.01963, 'EXR': 11.78, 'time_start': '2024-09-18T09:00:00+02:00', 'time_end': '2024-09-18T10:00:00+02:00'}, {'NOK_per_kWh': 0.23266, 'EUR_per_kWh': 0.01975, 'EXR': 11.78, 'time_start': '2024-09-18T10:00:00+02:00', 'time_end': '2024-09-18T11:00:00+02:00'}, {'NOK_per_kWh': 0.23548, 'EUR_per_kWh': 0.01999, 'EXR': 11.78, 'time_start': '2024-09-18T11:00:00+02:00', 'time_end': '2024-09-18T12:00:00+02:00'}, {'NOK_per_kWh': 0.15302, 'EUR_per_kWh': 0.01299, 'EXR': 11.78, 'time_start': '2024-09-18T12:00:00+02:00', 'time_end': '2024-09-18T13:00:00+02:00'}, {'NOK_per_kWh': 0.1416, 'EUR_per_kWh': 0.01202, 'EXR': 11.78, 'time_start': '2024-09-18T13:00:00+02:00', 'time_end': '2024-09-18T14:00:00+02:00'}, {'NOK_per_kWh': 0.14937, 'EUR_per_kWh': 0.01268, 'EXR': 11.78, 'time_start': '2024-09-18T14:00:00+02:00', 'time_end': '2024-09-18T15:00:00+02:00'}, {'NOK_per_kWh': 0.17517, 'EUR_per_kWh': 0.01487, 'EXR': 11.78, 'time_start': '2024-09-18T15:00:00+02:00', 'time_end': '2024-09-18T16:00:00+02:00'}, {'NOK_per_kWh': 0.23053, 'EUR_per_kWh': 0.01957, 'EXR': 11.78, 'time_start': '2024-09-18T16:00:00+02:00', 'time_end': '2024-09-18T17:00:00+02:00'}, {'NOK_per_kWh': 0.2356, 'EUR_per_kWh': 0.02, 'EXR': 11.78, 'time_start': '2024-09-18T17:00:00+02:00', 'time_end': '2024-09-18T18:00:00+02:00'}, {'NOK_per_kWh': 0.24102, 'EUR_per_kWh': 0.02046, 'EXR': 11.78, 'time_start': '2024-09-18T18:00:00+02:00', 'time_end': '2024-09-18T19:00:00+02:00'}, {'NOK_per_kWh': 0.23548, 'EUR_per_kWh': 0.01999, 'EXR': 11.78, 'time_start': '2024-09-18T19:00:00+02:00', 'time_end': '2024-09-18T20:00:00+02:00'}, {'NOK_per_kWh': 0.23159, 'EUR_per_kWh': 0.01966, 'EXR': 11.78, 'time_start': '2024-09-18T20:00:00+02:00', 'time_end': '2024-09-18T21:00:00+02:00'}, {'NOK_per_kWh': 0.22712, 'EUR_per_kWh': 0.01928, 'EXR': 11.78, 'time_start': '2024-09-18T21:00:00+02:00', 'time_end': '2024-09-18T22:00:00+02:00'}, {'NOK_per_kWh': 0.21817, 'EUR_per_kWh': 0.01852, 'EXR': 11.78, 'time_start': '2024-09-18T22:00:00+02:00', 'time_end': '2024-09-18T23:00:00+02:00'}, {'NOK_per_kWh': 0.17694, 'EUR_per_kWh': 0.01502, 'EXR': 11.78, 'time_start': '2024-09-18T23:00:00+02:00', 'time_end': '2024-09-19T00:00:00+02:00'}]
# Write JSON to file for viewing
import json
with open('downloads/power_price.json', 'w') as f:
    json.dump(response.json(), f, indent=4)

Creating data (POST)#

  • For this example we assume there is an API server running locally.

  • We rely on the Flask framework (see flask_API.py) in the current folder.

    • Simple POST, GET, “UPDATE”, DELETE

  • Running a Flask instance from the terminal can be done similar to this:

conda activate tf_M1
python /Users/kristian/Documents/GitHub/IND320/D2Dbook/3_Data_sources/3_APIs/flask_API.py
# POST data (stored locally in a dictionary)
id = 'test'
data = {'key': 'value'}
response = requests.post('http://localhost:8000/api/post/{}'.format(id), json=data)
print(response.json())
{'key': 'value'}
# GET data
id = 'test'
response = requests.get('http://localhost:8000/api/get/{}'.format(id))
print(response.json())
# You can also test this in a browser since this is an HTTP based (and we use no passwords here).
{'key': 'value'}

Change data (UPDATE)#

  • This command can mostly be exchanged with POST.

  • The Flask framework does not have a separate UPDATE (see implementation in flask_API.py).

# UPDATE data
id = 'test'
data = {'key': 'new value'}
response = requests.post('http://localhost:8000/api/update/{}'.format(id), json=data)
print(response.json())
{'key': 'new value'}

Remove data (DELETE)#

  • Let’s remove some data and try to read it again.

# DELETE data
id = 'test'
response = requests.delete('http://localhost:8000/api/delete/{}'.format(id))
print(response.json())
{'key': 'new value'}
# GET data again
id = 'test'
response = requests.get('http://localhost:8000/api/get/{}'.format(id))
print(response.json())
---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
File ~/miniforge3/envs/IND320_2024/lib/python3.12/site-packages/requests/models.py:974, in Response.json(self, **kwargs)
    973 try:
--> 974     return complexjson.loads(self.text, **kwargs)
    975 except JSONDecodeError as e:
    976     # Catch JSON-related errors and raise as requests.JSONDecodeError
    977     # This aliases json.JSONDecodeError and simplejson.JSONDecodeError

File ~/miniforge3/envs/IND320_2024/lib/python3.12/json/__init__.py:346, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    343 if (cls is None and object_hook is None and
    344         parse_int is None and parse_float is None and
    345         parse_constant is None and object_pairs_hook is None and not kw):
--> 346     return _default_decoder.decode(s)
    347 if cls is None:

File ~/miniforge3/envs/IND320_2024/lib/python3.12/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
    333 """Return the Python representation of ``s`` (a ``str`` instance
    334 containing a JSON document).
    335 
    336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338 end = _w(s, end).end()

File ~/miniforge3/envs/IND320_2024/lib/python3.12/json/decoder.py:355, in JSONDecoder.raw_decode(self, s, idx)
    354 except StopIteration as err:
--> 355     raise JSONDecodeError("Expecting value", s, err.value) from None
    356 return obj, end

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

JSONDecodeError                           Traceback (most recent call last)
Cell In[7], line 4
      2 id = 'test'
      3 response = requests.get('http://localhost:8000/api/get/{}'.format(id))
----> 4 print(response.json())

File ~/miniforge3/envs/IND320_2024/lib/python3.12/site-packages/requests/models.py:978, in Response.json(self, **kwargs)
    974     return complexjson.loads(self.text, **kwargs)
    975 except JSONDecodeError as e:
    976     # Catch JSON-related errors and raise as requests.JSONDecodeError
    977     # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
--> 978     raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Exercise#

  • Look at flask_API.py.

  • Add “try-except” to GET to return an empty JSON when an ‘id’ does not exist.

  • Are there other potential sources of error here?

See also

Resources