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)#
A simple example without an API key (Strømpris API):
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
Flask local HTTP server in Python.
Strømpris API (only in Norwegian)
YouTube: What is a REST API? (9m:11s)
YouTube: REST API Crash Course - Introduction + Full Python API Tutorial (52m:49s)