Claude Code Plugins

Community-maintained marketplace

Feedback

fastapi-async-patterns

@ils15/copilot-global-config
0
0

Create async FastAPI endpoints with proper error handling, dependency injection, service/repository patterns, and type safety. Includes async database queries, pagination, filtering, and Gemini integration.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name fastapi-async-patterns
description Create async FastAPI endpoints with proper error handling, dependency injection, service/repository patterns, and type safety. Includes async database queries, pagination, filtering, and Gemini integration.

FastAPI Async Patterns Skill

When to Use

Use this skill when:

  • Creating new FastAPI endpoints with async support
  • Building service layers with dependency injection
  • Implementing repository patterns for data access
  • Adding pagination, filtering, or search
  • Integrating with async databases (SQLAlchemy async)
  • Handling Gemini AI integration with fallbacks
  • Creating proper error handling and validation

Key Patterns

1. Service Layer Architecture

# services/product_service.py
from typing import Optional, List
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import Product
from app.repositories import ProductRepository
from app.schemas import ProductCreate, ProductResponse

class ProductService:
    def __init__(self, db: AsyncSession):
        self.repository = ProductRepository(db)
    
    async def create_product(self, product_data: ProductCreate) -> ProductResponse:
        """Create product with validation and error handling"""
        existing = await self.repository.get_by_slug(product_data.slug)
        if existing:
            raise ValueError(f"Product slug '{product_data.slug}' already exists")
        
        product = await self.repository.create(product_data.dict())
        return ProductResponse.from_orm(product)
    
    async def list_products(
        self,
        skip: int = 0,
        limit: int = 10,
        category: Optional[str] = None
    ) -> List[ProductResponse]:
        """List products with pagination and filtering"""
        filters = {}
        if category:
            filters['category'] = category
        
        products = await self.repository.list(skip=skip, limit=limit, filters=filters)
        return [ProductResponse.from_orm(p) for p in products]
    
    async def search_products(self, query: str) -> List[ProductResponse]:
        """Full-text search with Gemini enrichment"""
        products = await self.repository.search(query)
        # Optional: Enhance with Gemini insights
        return [ProductResponse.from_orm(p) for p in products]

2. Repository Pattern (Data Access)

# repositories/product_repository.py
from typing import Optional, List, Dict, Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import Product

class ProductRepository:
    def __init__(self, db: AsyncSession):
        self.db = db
    
    async def create(self, data: Dict[str, Any]) -> Product:
        """Create new product"""
        product = Product(**data)
        self.db.add(product)
        await self.db.commit()
        await self.db.refresh(product)
        return product
    
    async def get_by_id(self, product_id: int) -> Optional[Product]:
        """Get product by ID"""
        result = await self.db.execute(
            select(Product).where(Product.id == product_id)
        )
        return result.scalar_one_or_none()
    
    async def get_by_slug(self, slug: str) -> Optional[Product]:
        """Get product by slug"""
        result = await self.db.execute(
            select(Product).where(Product.slug == slug)
        )
        return result.scalar_one_or_none()
    
    async def list(
        self,
        skip: int = 0,
        limit: int = 10,
        filters: Optional[Dict[str, Any]] = None
    ) -> List[Product]:
        """List products with filtering and pagination"""
        query = select(Product)
        
        if filters:
            for key, value in filters.items():
                if hasattr(Product, key):
                    query = query.where(getattr(Product, key) == value)
        
        query = query.offset(skip).limit(limit)
        result = await self.db.execute(query)
        return result.scalars().all()
    
    async def search(self, query: str) -> List[Product]:
        """Full-text search"""
        result = await self.db.execute(
            select(Product).where(
                Product.title.ilike(f"%{query}%") |
                Product.description.ilike(f"%{query}%")
            )
        )
        return result.scalars().all()
    
    async def update(self, product_id: int, data: Dict[str, Any]) -> Optional[Product]:
        """Update product"""
        product = await self.get_by_id(product_id)
        if not product:
            return None
        
        for key, value in data.items():
            if hasattr(product, key):
                setattr(product, key, value)
        
        await self.db.commit()
        await self.db.refresh(product)
        return product
    
    async def delete(self, product_id: int) -> bool:
        """Delete product"""
        product = await self.get_by_id(product_id)
        if not product:
            return False
        
        await self.db.delete(product)
        await self.db.commit()
        return True

3. Router with Dependency Injection

# routers/products.py
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.services.product_service import ProductService
from app.schemas import ProductCreate, ProductResponse

router = APIRouter(prefix="/api/products", tags=["products"])

async def get_product_service(db: AsyncSession = Depends(get_db)) -> ProductService:
    return ProductService(db)

@router.get("", response_model=list[ProductResponse])
async def list_products(
    skip: int = Query(0, ge=0),
    limit: int = Query(10, ge=1, le=100),
    category: str = Query(None),
    service: ProductService = Depends(get_product_service)
):
    """List all products with pagination"""
    return await service.list_products(skip=skip, limit=limit, category=category)

@router.get("/search", response_model=list[ProductResponse])
async def search_products(
    q: str = Query(..., min_length=1),
    service: ProductService = Depends(get_product_service)
):
    """Search products by query"""
    try:
        return await service.search_products(q)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.get("/{product_id}", response_model=ProductResponse)
async def get_product(
    product_id: int,
    service: ProductService = Depends(get_product_service)
):
    """Get product by ID"""
    product = await service.repository.get_by_id(product_id)
    if not product:
        raise HTTPException(status_code=404, detail="Product not found")
    return ProductResponse.from_orm(product)

@router.post("", response_model=ProductResponse, status_code=201)
async def create_product(
    product: ProductCreate,
    service: ProductService = Depends(get_product_service)
):
    """Create new product"""
    try:
        return await service.create_product(product)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.put("/{product_id}", response_model=ProductResponse)
async def update_product(
    product_id: int,
    product: ProductCreate,
    service: ProductService = Depends(get_product_service)
):
    """Update product"""
    updated = await service.repository.update(product_id, product.dict(exclude_unset=True))
    if not updated:
        raise HTTPException(status_code=404, detail="Product not found")
    return ProductResponse.from_orm(updated)

@router.delete("/{product_id}", status_code=204)
async def delete_product(
    product_id: int,
    service: ProductService = Depends(get_product_service)
):
    """Delete product"""
    success = await service.repository.delete(product_id)
    if not success:
        raise HTTPException(status_code=404, detail="Product not found")

4. Error Handling with Custom Exceptions

# exceptions.py
from fastapi import HTTPException, status

class ProductNotFoundError(HTTPException):
    def __init__(self, product_id: int):
        super().__init__(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Product with ID {product_id} not found"
        )

class DuplicateSlugError(HTTPException):
    def __init__(self, slug: str):
        super().__init__(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Product with slug '{slug}' already exists"
        )

class ValidationError(HTTPException):
    def __init__(self, message: str):
        super().__init__(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=message
        )

5. Gemini Integration with Fallback

# services/gemini_service.py
import os
from typing import Optional
import google.generativeai as genai

class GeminiService:
    def __init__(self, api_key: Optional[str] = None):
        self.api_key = api_key or os.getenv("GEMINI_API_KEY")
        if self.api_key:
            genai.configure(api_key=self.api_key)
        self.model = genai.GenerativeModel("gemini-pro") if self.api_key else None
    
    async def generate_product_description(
        self,
        title: str,
        category: str,
        max_length: int = 500
    ) -> Optional[str]:
        """Generate product description using Gemini with fallback"""
        if not self.model:
            return None  # Fallback to default description
        
        try:
            prompt = f"""
            Create a compelling product description for an e-commerce site.
            Title: {title}
            Category: {category}
            Max length: {max_length} characters
            
            Return only the description, no additional text.
            """
            
            response = await self.model.generate_content_async(prompt)
            return response.text[:max_length]
        except Exception as e:
            print(f"Gemini error: {e}")
            return None  # Fallback gracefully
    
    async def generate_seo_keywords(
        self,
        title: str,
        description: str
    ) -> Optional[list[str]]:
        """Generate SEO keywords"""
        if not self.model:
            return None
        
        try:
            prompt = f"""
            Generate 5-10 SEO keywords for this product:
            Title: {title}
            Description: {description}
            
            Return only keywords separated by commas, no additional text.
            """
            
            response = await self.model.generate_content_async(prompt)
            return [kw.strip() for kw in response.text.split(",")]
        except Exception:
            return None

Best Practices

Always use async/await for database and external API calls
Dependency Injection - Use FastAPI Depends() for services
Type hints - Use Pydantic schemas for request/response
Error handling - Use HTTPException with proper status codes
Pagination - Implement skip/limit for large datasets
Filtering - Build dynamic filters for list endpoints
Repository pattern - Keep data access logic separate
Service layer - Business logic goes in services, not routers

Related Files in Skill

References