Python

Python API Development: Frameworks and Best Practices

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.

Why Is Python Ideal for API Development?

Python has consolidated its position as the preferred language for Python API development for several reasons:

  • Readability and simplicity: Cleaner code means faster development and fewer bugs
  • Mature ecosystem: Robust, well-documented frameworks for every type of API
  • Async support: Modern capabilities for handling high concurrency
  • Active community: An abundance of resources, libraries, and solutions to common problems
  • Versatility: Seamless integration with a wide range of technologies and services

These advantages have become even more evident as Python API frameworks evolve to meet growing demands for performance and scalability.

Main Frameworks for Python RESTful API Development

The Python ecosystem offers several frameworks for Python API development, each with its own strengths. Let’s explore the three most popular ones.

FastAPI: The New Standard for High-Performance Python APIs

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:

  • Exceptional performance: One of the fastest Python frameworks, comparable to Node.js and Go
  • Automatic validation: Based on Python type hints and Pydantic
  • Automatic documentation: Swagger UI and ReDoc generated automatically
  • Native async support: Takes advantage of modern Python features for high concurrency
  • Gentle learning curve: Easy to get started with, yet powerful enough for complex applications

Flask: Flexibility and Simplicity for Python API Development

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:

  • Minimalist and flexible: You choose the components you need
  • Mature ecosystem: Extensions for practically any feature
  • Easy to understand: Ideal for beginners and small to medium projects
  • Fine-grained control: Freedom to structure the API however you like
  • Active community: An abundance of resources and examples available

Django REST Framework: A Complete Solution for Complex Python APIs

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:

  • Complete solution: Includes authentication, permissions, pagination, and much more
  • Powerful ORM: A robust abstraction for interacting with the database
  • Scalability: Designed for large enterprise applications
  • Security: Built-in protection against common vulnerabilities
  • Mature ecosystem: Integration with the vast Django ecosystem

Performance Comparison: Which Framework Is Fastest for Python API Development?

Performance is a crucial consideration when choosing a framework for Python API development. Benchmarks show significant differences:

FrameworkRequests/secondAverage latencyMemory usage
FastAPI~10,000~2msMedium
Flask~2,500~8msLow
Django RF~1,200~15msHigh

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.

Best Practices for Python RESTful API Development

Regardless of the framework you choose, some practices have become essential for high-quality RESTful APIs:

1. Consistent Endpoint Design in Python API Development

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

2. API Versioning in Python API Development

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)

3. Robust Data Validation in Python API Development

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

4. Secure Authentication and Authorization in Python API Development

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

5. Automatic Documentation in Python API Development

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

Conclusion on Python API Development

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.

Vinicius Sodré

Formado em Ciência da Computação pela Unicarioca, desenvolvedor de software com 15 anos de experiência em grandes empresas nacionais e multinacionais. Vinicius está à frente deste blog, feito de desenvolvedor para desenvolvedores de iniciantes a experientes.

Compartilhar
Publicado por:
Vinicius Sodré

Posts Recentes

Python Course: Learn the Basics Quickly – Lesson 1

Python is at the top of the most widely used programming languages, and that is…

2 years atrás

Cyber Security – Replacing BinaryFormatter in .NET

Cyber Security - BinaryFormatter: A Security Risk The .NET source code is constantly evolving, and…

2 years atrás

How to Program in Python – The Basics You Need to Know

A Fresh Start for a Programmer Experienced in Object-Oriented Languages After more than 10 years…

2 years atrás

Este blog utiliza cookies. Se você continuar assumiremos que você está satisfeito com ele.

Leia Mais...