Hafif is a lightweight, Pythonic, and opinionated library designed to simplify common web development tasks, particularly around request parsing, validation, and serialization. It's crucial to understand that Hafif is NOT a web framework itself, but rather a utility library that COMPLEMENTS existing frameworks like Flask by adding a robust layer for handling incoming request data and structuring API responses.
Why use Hafif with Flask?
Flask is known for its minimalism; it provides the core tools for building web applications but intentionally leaves many decisions (like data validation, ORM, etc.) to the developer. This is where Hafif shines when paired with Flask, especially for building RESTful APIs:
1. Request Data Validation: Flask's `request` object gives you raw access to form data, JSON bodies, and query parameters. However, it doesn't inherently validate this data against expected types, formats, or business rules. Hafif allows you to define declarative schemas for your request inputs, ensuring that only valid data proceeds into your application logic.
2. Serialization and Deserialization: While Flask can return JSON directly using `jsonify`, managing complex object serialization or deserialization from incoming JSON bodies can become tedious. Hafif simplifies this by mapping incoming raw data to Python objects (deserialization) and preparing Python objects for JSON responses (serialization, though Hafif primarily focuses on the input side).
3. Reduced Boilerplate: For API endpoints that involve receiving and processing structured data, Hafif significantly reduces the amount of repetitive code needed for manual parsing, type conversion, and validation checks.
4. Consistent Error Handling: When validation fails, Hafif provides structured error messages, making it easier to return consistent '400 Bad Request' responses to clients.
Core Concepts of Hafif:
- `RequestSchema`: This is the central component where you define the structure and validation rules for your expected request data (e.g., JSON body, query parameters, form data).
- `Field` Types: Hafif provides various field types like `StringField`, `IntegerField`, `BooleanField`, `ListField`, `NestedField`, etc., allowing you to specify the expected data type for each piece of input.
- Validation Rules: Within each `Field`, you can specify validation rules such as `required=True`, `min_length`, `max_value`, `min_value`, `max_value`, `enum`, `pattern`, and custom validators.
How it integrates with Flask:
1. You define one or more `RequestSchema` classes for your API endpoints.
2. Inside your Flask view functions, you instantiate the relevant schema.
3. You call the schema's `parse()` method, passing Flask's `request` object (or specifically `request.json`, `request.args`, `request.form`).
4. If validation passes, the `parse()` method returns a dictionary of the validated and cleaned data.
5. If validation fails, it raises a `ValidationError`, which you can catch and use to return a '400 Bad Request' response with detailed error messages to the client.
This approach helps enforce data integrity, improve API reliability, and streamline development by centralizing data handling logic.
Example Code
from flask import Flask, request, jsonify
from hafif import RequestSchema, StringField, IntegerField, ValidationError, BooleanField
app = Flask(__name__)
--- Hafif Schemas ---
class UserCreationSchema(RequestSchema):
username = StringField(required=True, min_length=3, max_length=50)
email = StringField(required=True, pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") Basic email regex
age = IntegerField(required=False, min_value=18, max_value=120)
is_active = BooleanField(required=False, default=True)
class UserUpdateSchema(RequestSchema):
username = StringField(min_length=3, max_length=50)
email = StringField(pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
age = IntegerField(min_value=18, max_value=120)
is_active = BooleanField()
class UserFilterSchema(RequestSchema):
min_age = IntegerField(required=False, min_value=0)
max_age = IntegerField(required=False, min_value=0)
is_active = BooleanField(required=False)
limit = IntegerField(required=False, default=10, min_value=1, max_value=100)
--- Flask Routes ---
A simple in-memory 'database'
users_db = {}
user_id_counter = 1
@app.route('/')
def home():
return "Welcome to the Hafif + Flask example! Try POST /users or GET /users."
@app.route('/users', methods=['POST'])
def create_user():
global user_id_counter
try:
Parse and validate the request JSON body using UserCreationSchema
data = UserCreationSchema().parse(request.json)
except ValidationError as e:
If validation fails, return a 400 Bad Request with error details
return jsonify({"message": "Validation Error", "errors": e.messages}), 400
If validation passes, 'data' contains the cleaned and validated input
user_id = user_id_counter
user_id_counter += 1
users_db[user_id] = {"id": user_id, data}
return jsonify({"message": "User created successfully", "user": users_db[user_id]}), 201
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = users_db.get(user_id)
if user:
return jsonify(user), 200
return jsonify({"message": "User not found"}), 404
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
user = users_db.get(user_id)
if not user:
return jsonify({"message": "User not found"}), 404
try:
Parse and validate the request JSON body for update
Use partial=True to allow only some fields to be present for update
update_data = UserUpdateSchema(partial=True).parse(request.json)
except ValidationError as e:
return jsonify({"message": "Validation Error", "errors": e.messages}), 400
Update user data with validated fields
user.update(update_data)
return jsonify({"message": "User updated successfully", "user": user}), 200
@app.route('/users', methods=['GET'])
def list_users():
try:
Parse and validate query parameters using UserFilterSchema
filters = UserFilterSchema().parse(request.args)
except ValidationError as e:
return jsonify({"message": "Validation Error", "errors": e.messages}), 400
filtered_users = list(users_db.values())
Apply filters from 'data'
if 'min_age' in filters:
filtered_users = [u for u in filtered_users if u.get('age', 0) >= filters['min_age']]
if 'max_age' in filters:
filtered_users = [u for u in filtered_users if u.get('age', 999) <= filters['max_age']]
if 'is_active' in filters:
filtered_users = [u for u in filtered_users if u.get('is_active') == filters['is_active']]
Apply limit
limit = filters.get('limit')
if limit is not None:
filtered_users = filtered_users[:limit]
return jsonify({"users": filtered_users, "count": len(filtered_users)}), 200
if __name__ == '__main__':
To run this Flask app:
1. Ensure you have Flask and Hafif installed: pip install Flask hafif
2. Save this code as e.g., 'app.py'
3. Run from your terminal: python app.py
Example cURL commands:
Create User (success):
curl -X POST -H "Content-Type: application/json" -d '{"username": "john_doe", "email": "john@example.com", "age": 30, "is_active": true}' http://127.0.0.1:5000/users
Create User (validation error - missing username, invalid email):
curl -X POST -H "Content-Type: application/json" -d '{"email": "invalid-email", "age": 15}' http://127.0.0.1:5000/users
List Users (no filters):
curl http://127.0.0.1:5000/users
List Users (with filters - by age and active status):
curl "http://127.0.0.1:5000/users?min_age=25&is_active=true&limit=5"
List Users (with invalid filter parameter):
curl "http://127.0.0.1:5000/users?min_age=-5"
Update User (success):
curl -X PUT -H "Content-Type: application/json" -d '{"age": 31}' http://127.0.0.1:5000/users/1
app.run(debug=True)








Hafif Web Framework + Flask