03 - Timeout
BRANCH: You can start from the branch
first-request
.
git checkout first-request
The client of our API can specify a timeout. The endpoint should always end in that timeout. If the timeout is not specified we will wait for the first successful response.
Implementing the timeout functionality
Get the timeout parameter from the request.
-
Import required packages.
from flask import Flask, jsonify, redirect, url_for, request from http import HTTPStatus
-
Get the timeout from request args.
@app.get("/api/smart") def smart_api_requester(): timeout = request.args["timeout"] ...
QUESTION: Do you spot any problems?
Click on me for the answer!
- We are missing validation. Let’s add it.
def milliseconds_to_seconds(value: int) -> float: return value / 1000 def get_and_validate_timeout() -> None | float: timeout = request.args.get("timeout") if timeout is None: return timeout try: converted_timeout = milliseconds_to_seconds(int(timeout)) except ValueError: raise BadRequest("Timeout has to be integer value.") if converted_timeout <= 0: raise BadRequest("Timeout has to be positive non zero value.") return converted_timeout @app.get("/api/smart") def smart_api_requester(): timeout_seconds = get_and_validate_timeout()
- Test timeout arguments.
- http://127.0.0.1:8081/api/smart?timeout=0
- http://127.0.0.1:8081/api/smart?timeout=nonumber
- http://127.0.0.1:8081/api/smart?timeout=10
The first two requests should fail with the bad request status code. The last one should pass.
End after the timeout is reached.
QUESTION: How could we implement this? We need to wait for the response and also check if the timeout is not reached.
Click on me for the answer!
We can use asynchronous features of python. This allows you to write concurrent code.
QUESTION: What is the difference between parallelism and concurrency? Is it better to use asynchronous framework instead of multiprocessing or threading?
Click on me for the answer!
Parallelism consists of performing multiple operations at the same time.
Concurrency is a slightly broader term than parallelism. It suggests that multiple tasks have the ability to run in an overlapping manner. (There’s a saying that concurrency does not imply parallelism.)
Threads consume more memory because they need to have their own stack. We also need to secure the thread safety. The same thing goes with multiprocessing. Multiprocessing works well if we have multiple CPUs and our processes are independent - we don’t need to provide thread safety.
On the other hand, async is ideal if we have many I/O operations, e.g. waiting for server to respond, multiple writes into the file and so on.
Source: Async IO python
So, let’s create an async program.
- First we need to install flask with async, so our server can be run asynchronously.
pip install flask[async]==2.2.2
- After that, we need to import required packages.
import asyncio from aiohttp import ClientSession
- Create the async request.
Success: TypeAlias = bool async def fetch() -> tuple[Success, dict]: async with ClientSession() as session: try: async with session.get(BLOOMREACH_SERVER) as response: if response.status == HTTPStatus.OK: try: result_data = await response.json() except ContentTypeError: return False, {} return True, result_data else: return False, {} except Exception: # Catch any session error (e.g. timeout) return False, {}
- Call async request from our route.
@app.get("/api/smart") async def smart_api_requester(): timeout_seconds = get_and_validate_timeout() try: success, result = await asyncio.wait_for(fetch(), timeout=timeout_seconds) except asyncio.TimeoutError: raise RequestTimeout() return jsonify(success=success, response=result)