Start here
What you will learn
- Why calls to other systems can fail, and what to do.
- Two ways to read pages: offset and cursor.
- How to follow a cursor without trusting the API.
- How to retry, and how to obey a rate limit.
- How to read and write a database safely.
- How to make a re-run safe.
An API is a way for one program to ask another program for data. You send a request. The other program sends back a response.
Most APIs answer in JSON. JSON is text in a fixed shape, with curly braces and square brackets. Python reads it with json.loads and gives you dicts and lists.
This page cannot call a real API. So we use a tiny fake one. It returns the shop orders from the earlier chapters, two orders per page, as JSON text.
| order_id | customer_id | status | amount |
|---|---|---|---|
| 101 | 1 | paid | 40 |
| 102 | 1 | paid | 25 |
| 103 | 2 | refunded | 60 |
| 104 | 3 | paid | 15.5 |
| 105 | 3 | pending | NULL |
| 106 | 4 | paid | 80 |
import json
ORDERS = [
{"order_id": 101, "customer_id": 1, "status": "paid", "amount": 40.0},
{"order_id": 102, "customer_id": 1, "status": "paid", "amount": 25.0},
{"order_id": 103, "customer_id": 2, "status": "refunded", "amount": 60.0},
{"order_id": 104, "customer_id": 3, "status": "paid", "amount": 15.5},
{"order_id": 105, "customer_id": 3, "status": "pending", "amount": None},
{"order_id": 106, "customer_id": 4, "status": "paid", "amount": 80.0},
]
def fake_api(cursor=None, limit=2):
last = 0 if cursor is None else int(cursor)
page = [o for o in ORDERS if o["order_id"] > last][:limit]
has_more = bool(page) and page[-1] != ORDERS[-1]
next_cursor = str(page[-1]["order_id"]) if has_more else None
return json.dumps({"orders": page, "next_cursor": next_cursor})page[-1] is the last order on this page. If it is not the last order of all, there are more pages, so has_more is True. An empty page has no last order, so we check bool(page) first.
Call the function with limit=1 and look at the raw response. A real API sends text like this over the network:
text = fake_api(limit=1)
print(text)
print(type(text).__name__)| output |
|---|
| {"orders": [{"order_id": 101, "customer_id": 1, "status": "paid", "amount": 40.0}], "next_cursor": "101"} |
| str |
Now parse it. json.loads turns the text into a dict. A JSON null would become None, the same as order 105 in our table.
data = json.loads(fake_api())
print(type(data).__name__)
print(data["next_cursor"])
for order in data["orders"]:
print(order["order_id"], order["amount"])| output |
|---|
| dict |
| 102 |
| 101 40.0 |
| 102 25.0 |
