Problem:
In most web frameworks we can define a request handler with
app.get('/items/{id}')
and item
will get passed in to the function
as a parameter. We see it and use it every day. But, have you ever
thought how its done? Lets find out!
Example in FastAPI
# test.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{id}")
def get_item(id):
return {"id": id}
Run it with this command:
# install dependencies if you have't already
pip install fastapi uvicorn
# Run it
uvicorn test:app --reload
and call it:
curl localhost:8000/items/123
# Result
{"id": "123"}
Solution 1: Using Regular Expressions
The idea is to take a string like '/items/{id}'
and make it into a
regular expression where we can use named groups to name things out
and extract the parts.
# parse_with_regex.py
import re
def extract_params(path_definition, incoming_path):
# Convert it to 'items/(?P<id>.+)'
pattern = path_definition.replace('{', '(?P<').replace('}', '>[^/]+)')
matches = re.match(pattern, incoming_path)
return matches.groupdict()
if __name__ == '__main__':
print(extract_params(
path_definition='/items/{id}',
incoming_path='/items/123'
))
# output:
# {'id': '123'}
Code readability suffers here. This works at first. But as we add more test cases it starts falling apart very quick. Like when our path definition wants to be a bit more greedy, {id+} for example in AWS API Gateway. Also, what happens when our test contains the extra '/' at the end. The regular expression will get too complicated very fast. It is already not that readable with all string replacements. Let's see if we can do better!
Solution 2: Using Arrays
Let's explore the fact that HTTP paths are separated by '/'
. So, we
can split by it and compare segements
def extract_params(path_definition, incoming_path):
definition_parts = path_definition.split('/')
incoming_path_parts = incoming_path.split('/')
result = {}
for index, part in enumerate(definition_parts):
if '{' in part:
result[part[1:-1]] = incoming_path_parts[index]
return result
if __name__ == '__main__':
print(extract_params(
path_definition='/items/{id}',
incoming_path='/items/123'
))
# output:
# {'id': '123'}
With this approach, the code is easily extendable. Just add more conditions as we inspect the segments.