REST API Part II
Kay Ashaolu - Instructor
Aishwarya Sriram - TA
Aside: Errors in Python
Understanding and Handling Errors
The Problem with Print Statements
- Using print for error messages mixes math logic with program context
- Example: In a grading program, printing "divisor cannot be zero" confuses users
- Solution: Use errors to signal exceptional conditions
Raising and Handling Errors
def divide(dividend, divisor):
if divisor == 0:
raise ZeroDivisionError("divisor cannot be zero")
return dividend / divisor
try:
result = divide(sum(grades), len(grades))
except ZeroDivisionError as e:
print("There are no grades yet in your list.")
- Raise: Signal error conditions
- try/except: Catch and handle exceptions
Chapter 5: Flask-Smorest for Efficient API Development
- Transition from simple API to a well-structured REST API
- Enhancing data models, error handling, and documentation
- Introducing Flask-Smorest, Blueprints, and Marshmallow
Moving from Names to Unique IDs
- Old Approach: Use store names as identifiers
- New Approach: Use unique IDs (UUIDs or auto-increment numbers)
-
Benefits:
- Direct access using dictionaries
- Simplified and efficient data retrieval
Data Model Transformation
Before:
stores = [store1, store2, ...]
After:
stores = {
store_id1: { ... },
store_id2: { ... }
}
- No more iterating through lists; access data by unique keys
Creating the Database Module
File: db.py
stores = {}
items = {}
- Centralizes data storage
- Easily imported into API endpoints
Updating API Endpoints for Unique IDs
@app.get("/store/<string:store_id>")
def get_store(store_id):
try:
return stores[store_id]
except KeyError:
abort(404, message="Store not found.")
- Replace name-based endpoints with ID-based ones
- Simplifies code and error handling
Improved Error Handling with Flask-Smorest
- Use the
abort
function from Flask-Smorest - Automatically includes error info in the API documentation
from flask_smorest import abort
abort(404, message="Store not found.")
Creating Robust Endpoints: CRUD Operations
- CRUD: Create, Read, Update, Delete
- Organize endpoints logically for items and stores
- Example for deleting an item:
@app.delete("/item/<string:item_id>")
def delete_item(item_id):
try:
del items[item_id]
return {"message": "Item deleted."}
except KeyError:
abort(404, message="Item not found.")
Organizing Your API Project
- Group endpoints into folders (e.g.,
/items
,/stores
) - Keeps the codebase maintainable
- Align endpoint organization with API clients (e.g., Insomnia)
Running the API in Docker
-
Why Docker?
- Mimics production environment
- Avoids “works on my machine” issues
- Update Dockerfile to install dependencies from
requirements.txt
Enabling Hot Reloading with Docker Volumes
docker run -dp 5005:5000 -v "$PWD":/app flask-smorest-api
- Maps local directory to container’s
/app
- Automatically updates container when code changes
Introducing Blueprints and MethodViews
- Blueprints: Modularize API endpoints
- MethodViews: Map HTTP methods to class methods
from flask.views import MethodView
from flask_smorest import Blueprint
blp = Blueprint("stores", __name__, description="Operations on stores")
@blp.route("/store/<string:store_id>")
class Store(MethodView):
def get(self, store_id):
# Retrieve store logic
pass
def delete(self, store_id):
# Delete store logic
pass
Registering Blueprints in Your Flask App
- Import Blueprints and register them with Flask-Smorest’s API
from flask_smorest import Api
from resources.item import blp as ItemBlueprint
from resources.store import blp as StoreBlueprint
api = Api(app)
api.register_blueprint(ItemBlueprint)
api.register_blueprint(StoreBlueprint)
- Integrates all endpoints and documentation
Introduction to Marshmallow Schemas
- Purpose: Validate incoming data and serialize outgoing responses
- Example Item Schema:
from marshmallow import Schema, fields
class ItemSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str(required=True)
price = fields.Float(required=True)
store_id = fields.Str(required=True)
Validating API Requests with Marshmallow
- Use
@blp.arguments
to validate incoming JSON data
@blp.arguments(ItemSchema)
def post(self, item_data):
# Process validated data
pass
- Eliminates manual if-checks for required fields
Decorating Responses with Marshmallow
- Use
@blp.response
to format outgoing responses
@blp.response(200, ItemSchema)
def get(self, item_id):
# Return item serialized by ItemSchema
pass
- Enhances documentation and consistency of responses
Handling Multiple Items with Marshmallow
- When returning lists, specify
many=True
@blp.response(200, ItemSchema(many=True))
def get(self):
return list(items.values())
- Automatically serializes a list of items into JSON
Enhanced API Documentation
- Flask-Smorest integrates with Swagger UI
- Automatically documents endpoints, schemas, and responses
- Example: Access API docs at
/swagger-ui
Benefits of Using Flask-Smorest & Marshmallow
- Structured Code: Modular, maintainable endpoints
- Validation: Clear schema-based validation
- Documentation: Up-to-date API docs via Swagger UI
- Error Handling: Consistent error messages and status codes
Best Practices Recap
- Transition to unique identifiers for data models
- Organize endpoints with Blueprints and MethodViews
- Validate and serialize data with Marshmallow
- Use Docker for consistent development environments
Recap: Chapter 5 Highlights
- Data Models: Efficient lookup using dictionaries
- Error Handling: Centralized and documented via Flask-Smorest
- Blueprints & MethodViews: Clean, modular API structure
- Marshmallow: Robust data validation and serialization
Full Example
Bringing together serialization and deserialization
- Use Marshmallow Schemas to validate incoming data (deserialization)
- Use schemas to format outgoing responses (serialization)
- Organize endpoints with Flask-Smorest Blueprints and MethodViews
Item Resource Blueprint with Marshmallow
# resources/item.py
from flask.views import MethodView
from flask_smorest import Blueprint, abort
import uuid
from schemas import ItemSchema, ItemUpdateSchema
# In-memory storage for demonstration
items = {}
blp = Blueprint("items", __name__, description="Operations on items")
@blp.route("/item/<string:item_id>")
class Item(MethodView):
@blp.response(200, ItemSchema)
def get(self, item_id):
try:
return items[item_id]
except KeyError:
abort(404, message="Item not found.")
@blp.arguments(ItemUpdateSchema)
@blp.response(200, ItemSchema)
def put(self, item_data, item_id):
try:
item = items[item_id]
except KeyError:
abort(404, message="Item not found.")
item.update(item_data)
return item
def delete(self, item_id):
try:
del items[item_id]
return {"message": "Item deleted."}
except KeyError:
abort(404, message="Item not found.")
@blp.route("/item")
class ItemList(MethodView):
@blp.response(200, ItemSchema(many=True))
def get(self):
return list(items.values())
@blp.arguments(ItemSchema)
@blp.response(201, ItemSchema)
def post(self, item_data):
item_id = uuid.uuid4().hex
item = {**item_data, "id": item_id}
items[item_id] = item
return item
- @blp.arguments: Validates and deserializes incoming JSON using a schema
- @blp.response: Serializes outgoing data using a schema
Example Summary
-
Deserialization:
- Incoming JSON is validated by
ItemSchema
orItemUpdateSchema
- Ensures required fields and types before processing
- Incoming JSON is validated by
-
Serialization:
- Outgoing Python objects are formatted as JSON using the same schemas
- Guarantees consistent API responses and up-to-date documentation
-
Blueprints & MethodViews:
- Modular structure for API endpoints
- Clean separation of HTTP methods (GET, POST, PUT, DELETE) within a class
Questions?
Rest API part II - Backend Webarch
By kayashaolu
Rest API part II - Backend Webarch
Course Website: https://groups.ischool.berkeley.edu/i253/sp25
- 158