Python API development has become a fundamental skill for modern programmers. With its clear syntax and powerful libraries, Python has established itself as one of the best languages for building robust, scalable RESTful APIs. This article explores Python API development, comparing the main frameworks and presenting best practices.
Python has consolidated its position as the preferred language for Python API development for several reasons:
These advantages have become even more evident as Python API frameworks evolve to meet growing demands for performance and scalability.
The Python ecosystem offers several frameworks for Python API development, each with its own strengths. Let’s explore the three most popular ones.
FastAPI has revolutionized Python API development with its focus on performance and ease of use:
This code example shows how to build a complete RESTful API for managing products with FastAPI. It includes the definition of a data model (Product), a simulated in-memory database, and routes for product CRUD operations (Create, Read, Update, Delete). A simple middleware is also added to log requests.
# FastAPI API example
from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel
from typing import List, Optional
import uvicorn
# Initialize the application
app = FastAPI(
title="Products API",
description="RESTful API for managing products",
version="1.0.0"
)
# Data model with validation
class Product(BaseModel):
id: Optional[int] = None
name: str
description: str
price: float
available: bool = True
class Config:
schema_extra = {
"example": {
"name": "Smartphone XYZ",
"description": "Latest-generation smartphone",
"price": 1999.90,
"available": True
}
}
# Simulated database
db_products = {}
id_counter = 1
# CRUD routes
@app.post("/products/", response_model=Product, status_code=status.HTTP_201_CREATED)
async def create_product(product: Product):
global id_counter
product.id = id_counter
db_products[id_counter] = product
id_counter += 1
return product
@app.get("/products/", response_model=List[Product])
async def list_products(skip: int = 0, limit: int = 100):
return list(db_products.values())[skip : skip + limit]
@app.get("/products/{product_id}", response_model=Product)
async def get_product(product_id: int):
if product_id not in db_products:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Product with ID {product_id} not found"
)
return db_products[product_id]
@app.put("/products/{product_id}", response_model=Product)
async def update_product(product_id: int, product: Product):
if product_id not in db_products:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Product with ID {product_id} not found"
)
product.id = product_id
db_products[product_id] = product
return product
@app.delete("/products/{product_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_product(product_id: int):
if product_id not in db_products:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Product with ID {product_id} not found"
)
del db_products[product_id]
return None
# Logging middleware
@app.middleware("http")
async def log_requests(request, call_next):
print(f"Request to: {request.url.path}")
response = await call_next(request)
return response
# Run the server
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
FastAPI advantages:
Flask remains a popular choice for APIs thanks to its simplicity and flexibility:
This code example shows how to build a RESTful API for managing products with Flask and Flask-RESTful. It uses Marshmallow for data validation and simulates an in-memory database. The routes for listing, creating, retrieving, updating, and deleting products are defined as RESTful resources.
# RESTful API example with Flask
from flask import Flask, request, jsonify
from flask_restful import Api, Resource
from marshmallow import Schema, fields, validate, ValidationError
import uuid
app = Flask(__name__)
api = Api(app)
# Validation schema with Marshmallow
class ProductSchema(Schema):
id = fields.Str(dump_only=True) # Output only, not accepted as input
name = fields.Str(required=True, validate=validate.Length(min=1))
description = fields.Str(required=True)
price = fields.Float(required=True, validate=validate.Range(min=0.01))
available = fields.Bool(missing=True) # Default value if not provided
product_schema = ProductSchema()
products_schema = ProductSchema(many=True)
# Simulated database
products = {}
# Resource for the product collection
class ProductListResource(Resource):
def get(self):
return jsonify(products_schema.dump(list(products.values())))
def post(self):
try:
# Validate the input data
product_data = product_schema.load(request.json)
# Generate a unique ID
product_id = str(uuid.uuid4())
product_data["id"] = product_id
# Save to the "database"
products[product_id] = product_data
return product_schema.dump(product_data), 201
except ValidationError as err:
return {"errors": err.messages}, 400
# Resource for a single product
class ProductResource(Resource):
def get(self, product_id):
product = products.get(product_id)
if not product:
return {"error": "Product not found"}, 404
return product_schema.dump(product)
def put(self, product_id):
if product_id not in products:
return {"error": "Product not found"}, 404
try:
# Validate the input data
product_data = product_schema.load(request.json)
product_data["id"] = product_id
# Update the "database"
products[product_id] = product_data
return product_schema.dump(product_data)
except ValidationError as err:
return {"errors": err.messages}, 400
def delete(self, product_id):
if product_id not in products:
return {"error": "Product not found"}, 404
del products[product_id]
return "", 204
# Register resources
api.add_resource(ProductListResource,
'/products')
api.add_resource(ProductResource,
'/products/<string:product_id>')
# Run the server
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
Flask advantages:
For larger, more complex projects, Django REST Framework (DRF) offers a robust solution for Python API development:
This set of code examples shows how to build a complete RESTful API with Django REST Framework. It includes the definition of a Django model (Product), a serializer that converts model data to JSON and back (ProductSerializer), a ViewSet that handles CRUD operations and product filtering, and the URL configuration that routes requests to the ViewSet.
# API example with Django REST Framework
# file: models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
# file: serializers.py
from rest_framework import serializers
from .models import Product
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'name', 'description', 'price', 'available',
'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at']
# file: views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.pagination import PageNumberPagination
from .models import Product
from .serializers import ProductSerializer
class ProductPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all().order_by('-updated_at')
serializer_class = ProductSerializer
pagination_class = ProductPagination
permission_classes = [IsAuthenticatedOrReadOnly]
def get_queryset(self):
queryset = Product.objects.all()
# Filter by availability
available = self.request.query_params.get('available')
if available is not None:
queryset = queryset.filter(available=available.lower() == 'true')
# Filter by minimum price
min_price = self.request.query_params.get('min_price')
if min_price is not None:
queryset = queryset.filter(price__gte=float(min_price))
# Filter by maximum price
max_price = self.request.query_params.get('max_price')
if max_price is not None:
queryset = queryset.filter(price__lte=float(max_price))
return queryset
# file: urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet
router = DefaultRouter()
router.register(r'products', ProductViewSet)
urlpatterns = [
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls')),
]
Django REST Framework advantages:
Performance is a crucial consideration when choosing a framework for Python API development. Benchmarks show significant differences:
| Framework | Requests/second | Average latency | Memory usage |
|---|---|---|---|
| FastAPI | ~10,000 | ~2ms | Medium |
| Flask | ~2,500 | ~8ms | Low |
| Django RF | ~1,200 | ~15ms | High |
These numbers are approximate and vary depending on the API’s complexity, hardware, and configuration. FastAPI keeps its performance edge, but the other frameworks have also evolved significantly.
Regardless of the framework you choose, some practices have become essential for high-quality RESTful APIs:
Keep a consistent pattern for your endpoints:
# Recommended pattern for RESTful endpoints
GET /resources # List resources
POST /resources # Create a new resource
GET /resources/{id} # Get a specific resource
PUT /resources/{id} # Update a resource (full)
PATCH /resources/{id} # Update a resource (partial)
DELETE /resources/{id} # Delete a resource
# For relationships
GET /resources/{id}/subresources # List subresources
POST /resources/{id}/subresources # Create a subresource
Implement versioning to avoid breaking existing clients:
This code example shows how to implement API versioning with FastAPI. It defines two versions of the API (v1 and v2) with separate routes, allowing different API versions to coexist and be accessed through different URL prefixes.
# FastAPI example
from fastapi import FastAPI, APIRouter
app = FastAPI()
# Sample functions for the different API versions
def get_resources_v1():
return {"message": "API v1 resources"}
def get_resources_v2():
return {"message": "API v2 resources with additional data"}
# API v1
v1_router = APIRouter(prefix="/api/v1")
v1_router.add_api_route("/resources", get_resources_v1, methods=["GET"])
app.include_router(v1_router)
# API v2
v2_router = APIRouter(prefix="/api/v2")
v2_router.add_api_route("/resources", get_resources_v2, methods=["GET"])
app.include_router(v2_router)
Always validate input data to avoid security and integrity problems:
This code example shows how to perform robust input validation with Pydantic, which is commonly used with FastAPI. It defines a UserCreate model with required fields and custom validations for email format and password strength, ensuring the incoming data complies with the business rules.
# Pydantic example (FastAPI)
from pydantic import BaseModel, Field, validator
from typing import Optional
import re
class UserCreate(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
email: str = Field(..., max_length=100)
password: str = Field(..., min_length=8)
phone: Optional[str] = Field(None)
@validator('email')
def valid_email(cls, v):
if not re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", v):
raise ValueError('Invalid email')
return v
@validator('password')
def strong_password(cls, v):
if not re.search(r"[A-Z]", v):
raise ValueError('Password must contain at least one uppercase letter')
if not re.search(r"[a-z]", v):
raise ValueError('Password must contain at least one lowercase letter')
if not re.search(r"[0-9]", v):
raise ValueError('Password must contain at least one number')
return v
Implement robust authentication to protect your endpoints:
This code example shows a JWT (JSON Web Token)-based authentication system for FastAPI. It includes functions to create access tokens and to get the current user from a token, protecting the API endpoints and ensuring that only authenticated users can access them.
# FastAPI and JWT example
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional
# Configuration
SECRET_KEY = "your_very_secure_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(username) # Function that looks up the user in the database
if user is None:
raise credentials_exception
return user
# Usage in a protected endpoint
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_user)):
return current_user
Keep your API well documented to make adoption easier:
This simple code example shows how to set up automatic documentation in FastAPI. When you initialize the FastAPI application with a title, description, and version, the framework automatically generates interactive documentation interfaces (Swagger UI and ReDoc) that can be accessed directly in the browser, making the API easier to use and test.
# FastAPI example (automatic documentation)
from fastapi import FastAPI
app = FastAPI(
title="My API",
description="API for managing resources",
version="1.0.0",
openapi_tags=[
{
"name": "users",
"description": "User-related operations"
},
{
"name": "products",
"description": "Product-related operations"
}
]
)
# ... (your routes and API logic here)
# The interactive documentation (Swagger UI) will be available at /docs
# The alternative documentation (ReDoc) will be available at /redoc
Python remains an excellent choice for Python API development, offering a range of frameworks that meet different needs and project scales. By following best practices and choosing the right framework, you can build robust, efficient, and maintainable APIs. Whether it is FastAPI’s speed, Flask’s flexibility, or Django REST Framework’s robustness, Python provides the tools you need to succeed in Python API development.
Python is at the top of the most widely used programming languages, and that is…
Cyber Security - BinaryFormatter: A Security Risk The .NET source code is constantly evolving, and…
A Fresh Start for a Programmer Experienced in Object-Oriented Languages After more than 10 years…
Este blog utiliza cookies. Se você continuar assumiremos que você está satisfeito com ele.
Leia Mais...