How to Build a Hypermedia Collection Resource in Python
Creates a paginated hypermedia collection resource with HATEOAS links and embedded items.
Python code
61 linesimport json
import math
class HypermediaCollection:
"""A mock hypermedia collection resource."""
def __init__(self, items, base_url="/api/items"):
self.items = items
self.base_url = base_url
def to_dict(self, page=1, per_page=3):
total = len(self.items)
pages = math.ceil(total / per_page) if per_page > 0 else 1
page = max(1, min(page, pages))
start = (page - 1) * per_page
end = start + per_page
page_items = self.items[start:end]
return {
"_links": {
"self": {"href": f"{self.base_url}?page={page}&per_page={per_page}"},
"first": {"href": f"{self.base_url}?page=1&per_page={per_page}"},
"last": {"href": f"{self.base_url}?page={pages}&per_page={per_page}"},
"next": (
{"href": f"{self.base_url}?page={page + 1}&per_page={per_page}"}
if page < pages else None
),
"prev": (
{"href": f"{self.base_url}?page={page - 1}&per_page={per_page}"}
if page > 1 else None
),
},
"page": page,
"per_page": per_page,
"total": total,
"total_pages": pages,
"_embedded": {
"items": [
{
"id": item["id"],
"name": item["name"],
"_links": {
"self": {"href": f"{self.base_url}/{item['id']}"}
},
}
for item in page_items
]
},
}
if __name__ == "__main__":
items = [
{"id": 1, "name": "Alpha"},
{"id": 2, "name": "Beta"},
{"id": 3, "name": "Gamma"},
{"id": 4, "name": "Delta"},
]
collection = HypermediaCollection(items)
print(json.dumps(collection.to_dict(page=2), indent=2))
Output
{
"_links": {
"self": {
"href": "/api/items?page=2&per_page=3"
},
"first": {
"href": "/api/items?page=1&per_page=3"
},
"last": {
"href": "/api/items?page=2&per_page=3"
},
"next": null,
"prev": {
"href": "/api/items?page=1&per_page=3"
}
},
"page": 2,
"per_page": 3,
"total": 4,
"total_pages": 2,
"_embedded": {
"items": [
{
"id": 4,
"name": "Delta",
"_links": {
"self": {
"href": "/api/items/4"
}
}
}
]
}
}
How it works
The HypermediaCollection class wraps a list of items and exposes a to_dict method that builds a HAL-style payload. It computes total pages with math.ceil and clamps the requested page into a valid range, so page 5 of 2 pages safely returns page 2. The _links block follows HATEOAS principles by generating navigation URLs for self, first, last, next, and prev. Embedded items include their own self links, making the resource self-describing for API clients. Serializing with json.dumps(indent=2) produces readable HAL JSON for inspection or API responses.
Common mistakes
- Slicing items without clamping the page, which produces empty embedded arrays for out-of-range pages
- Hardcoding the base URL instead of passing it as a parameter, breaking portability across environments
- Forgetting to guard `per_page > 0`, which causes a ZeroDivisionError when calculating total pages
Variations
- Return a Python dict and let a web framework (FastAPI or Flask) serialize it with its own JSON renderer
- Add query params filtering or sorting before pagination to make the collection more production-ready
Real-world use cases
- Building a paginated REST API endpoint that returns HATEOAS links for navigation between pages.
- Mocking a hypermedia-driven backend during frontend development to iterate on client behavior.
- Generating documentation examples or API contract fixtures that demonstrate pagination and linking.
Sponsored
More from API design & gRPC
- Build a Bulk Array POST Mock Server in Python medium
- Build a Mock REST API with PUT and GET in Python medium
- Convert Protobuf to JSON and Dict in Python easy
- Create a Data Helper in Python for gRPC-style APIs easy
- Format data in Python using dataclasses like gRPC messages easy
- Generate an OpenAPI Spec from Mock Routes in Python easy
Keep learning
Related tutorials and quizzes for this topic.