455 lines
13 KiB
Markdown
455 lines
13 KiB
Markdown
---
|
|
name: flask-smorest-api
|
|
description: Set up Flask REST API with flask-smorest, OpenAPI/Swagger docs, and blueprint architecture
|
|
---
|
|
|
|
# Flask REST API with flask-smorest Pattern
|
|
|
|
This skill helps you set up a Flask REST API following a standardized pattern with flask-smorest for OpenAPI documentation, blueprint architecture, and best practices for production-ready APIs.
|
|
|
|
## When to Use This Skill
|
|
|
|
Use this skill when:
|
|
- Starting a new Flask REST API project
|
|
- You want automatic OpenAPI/Swagger documentation
|
|
- You need a clean, modular blueprint architecture
|
|
- You want type-safe request/response handling with Marshmallow schemas
|
|
- You're building a production-ready API server
|
|
|
|
## What This Skill Creates
|
|
|
|
1. **Main application file** - Flask app initialization with flask-smorest
|
|
2. **Blueprint structure** - Modular endpoint organization
|
|
3. **Schema files** - Marshmallow schemas for validation and docs
|
|
4. **Singleton manager pattern** - Centralized service/database initialization
|
|
5. **CORS support** - Cross-origin request handling
|
|
6. **Requirements file** - All necessary dependencies
|
|
|
|
## Step 1: Gather Project Information
|
|
|
|
**IMPORTANT**: Before creating files, ask the user these questions:
|
|
|
|
1. **"What is your project name?"** (e.g., "materia-server", "trading-api", "myapp")
|
|
- Use this to derive:
|
|
- Main module: `{project_name}.py` (e.g., `materia_server.py`)
|
|
- Port number (suggest based on project, default: 5000)
|
|
|
|
2. **"What features/endpoints do you need?"** (e.g., "users", "tokens", "orders")
|
|
- Each feature will become a blueprint
|
|
|
|
3. **"Do you need database integration?"** (yes/no)
|
|
- If yes, reference postgres-setup skill for database layer
|
|
|
|
4. **"What port should the server run on?"** (default: 5000)
|
|
|
|
## Step 2: Create Directory Structure
|
|
|
|
Create these directories if they don't exist:
|
|
```
|
|
{project_root}/
|
|
├── blueprints/ # Blueprint modules (one per feature)
|
|
│ ├── __init__.py
|
|
│ ├── {feature}.py
|
|
│ └── {feature}_schemas.py
|
|
├── src/ # Optional: for package code (database drivers, models, etc.)
|
|
│ └── {project_name}/
|
|
└── {project_name}.py # Main application file
|
|
```
|
|
|
|
## Step 3: Create Main Application File
|
|
|
|
Create `{project_name}.py` using this template:
|
|
|
|
```python
|
|
import os
|
|
import logging
|
|
from flask import Flask
|
|
from flask_cors import CORS
|
|
from flask_smorest import Api
|
|
from dotenv import load_dotenv
|
|
|
|
# Load environment variables from .env file
|
|
load_dotenv()
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def create_app():
|
|
app = Flask(__name__)
|
|
app.config['API_TITLE'] = '{Project Name} API'
|
|
app.config['API_VERSION'] = 'v1'
|
|
app.config['OPENAPI_VERSION'] = '3.0.2'
|
|
app.config['OPENAPI_URL_PREFIX'] = '/'
|
|
app.config['OPENAPI_SWAGGER_UI_PATH'] = '/swagger'
|
|
app.config['OPENAPI_SWAGGER_UI_URL'] = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist/'
|
|
|
|
CORS(app)
|
|
api = Api(app)
|
|
|
|
from blueprints.{feature} import blp as {feature}_blp
|
|
api.register_blueprint({feature}_blp)
|
|
|
|
logger.info("Flask app initialized")
|
|
return app
|
|
|
|
if __name__ == '__main__':
|
|
port = int(os.environ.get('PORT', {port_number}))
|
|
app = create_app()
|
|
logger.info(f"Swagger UI: http://localhost:{port}/swagger")
|
|
app.run(host='0.0.0.0', port=port)
|
|
```
|
|
|
|
**CRITICAL**: Replace:
|
|
- `{Project Name}` → Human-readable project name (e.g., "Materia Server")
|
|
- `{project_name}` → Snake case project name (e.g., "materia_server")
|
|
- `{port_number}` → Actual port number (e.g., 5151)
|
|
- `{feature}` → Feature name from user's response
|
|
|
|
## Step 4: Create Blueprint and Schema Files
|
|
|
|
For each feature/endpoint, create two files:
|
|
|
|
### File: `blueprints/{feature}.py`
|
|
|
|
```python
|
|
from flask.views import MethodView
|
|
from flask_smorest import Blueprint, abort
|
|
from .{feature}_schemas import {Feature}QuerySchema, {Feature}ResponseSchema, ErrorResponseSchema
|
|
|
|
blp = Blueprint('{feature}', __name__, url_prefix='/api', description='{Feature} API')
|
|
|
|
@blp.route('/{feature}')
|
|
class {Feature}Resource(MethodView):
|
|
@blp.arguments({Feature}QuerySchema, location='query')
|
|
@blp.response(200, {Feature}ResponseSchema)
|
|
@blp.alt_response(400, schema=ErrorResponseSchema)
|
|
@blp.alt_response(500, schema=ErrorResponseSchema)
|
|
def get(self, query_args):
|
|
try:
|
|
# TODO: Implement logic
|
|
return {"message": "Success", "data": []}
|
|
except ValueError as e:
|
|
abort(400, message=str(e))
|
|
except Exception as e:
|
|
abort(500, message=str(e))
|
|
```
|
|
|
|
### File: `blueprints/{feature}_schemas.py`
|
|
|
|
```python
|
|
from marshmallow import Schema, fields
|
|
|
|
class {Feature}QuerySchema(Schema):
|
|
limit = fields.Integer(load_default=100, metadata={'description': 'Max results'})
|
|
offset = fields.Integer(load_default=0, metadata={'description': 'Skip count'})
|
|
|
|
class {Feature}ResponseSchema(Schema):
|
|
message = fields.String(required=True)
|
|
data = fields.List(fields.Dict(), required=True)
|
|
|
|
class ErrorResponseSchema(Schema):
|
|
code = fields.Integer(required=True)
|
|
status = fields.String(required=True)
|
|
message = fields.String()
|
|
```
|
|
|
|
**CRITICAL**: Replace:
|
|
- `{Feature}` → PascalCase feature name (e.g., "TradableTokens")
|
|
- `{feature}` → Snake case feature name (e.g., "tradable_tokens")
|
|
|
|
## Step 5: Create Common Singleton Manager (If Needed)
|
|
|
|
If the project needs shared services (database, API clients, etc.), create a singleton manager:
|
|
|
|
### File: `common.py`
|
|
|
|
```python
|
|
"""
|
|
Singleton manager for shared service instances.
|
|
|
|
Provides centralized initialization of database connections, API clients,
|
|
and other shared resources.
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ServiceManager:
|
|
"""
|
|
Singleton manager for shared service instances.
|
|
|
|
Ensures only one instance of each service is created and reused
|
|
across all blueprints.
|
|
"""
|
|
|
|
_instance = None
|
|
|
|
def __new__(cls):
|
|
if cls._instance is None:
|
|
cls._instance = super(ServiceManager, cls).__new__(cls)
|
|
cls._instance._initialized = False
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
if self._initialized:
|
|
return
|
|
|
|
# Initialize services
|
|
self._db = None
|
|
self._initialized = True
|
|
logger.info("ServiceManager initialized")
|
|
|
|
def get_database(self):
|
|
"""
|
|
Get database connection instance.
|
|
|
|
Returns:
|
|
Database connection instance (lazy initialization)
|
|
"""
|
|
if self._db is None:
|
|
# Import database driver
|
|
from src.{project_name}.database import Database
|
|
|
|
# Get connection parameters from environment
|
|
db_host = os.environ.get('{PROJECT_NAME}_DB_HOST', 'localhost')
|
|
db_name = os.environ.get('{PROJECT_NAME}_DB_NAME', '{project_name}')
|
|
db_user = os.environ.get('{PROJECT_NAME}_DB_USER', '{project_name}')
|
|
db_passwd = os.environ.get('{PROJECT_NAME}_DB_PASSWORD')
|
|
|
|
if not db_passwd:
|
|
raise ValueError("{PROJECT_NAME}_DB_PASSWORD environment variable required")
|
|
|
|
self._db = Database(db_host, db_name, db_user, db_passwd)
|
|
logger.info("Database connection initialized")
|
|
|
|
return self._db
|
|
|
|
|
|
# Global singleton instance
|
|
service_manager = ServiceManager()
|
|
```
|
|
|
|
**CRITICAL**: Replace:
|
|
- `{PROJECT_NAME}` → Uppercase project name (e.g., "MATERIA_SERVER")
|
|
- `{project_name}` → Snake case project name (e.g., "materia_server")
|
|
|
|
## Step 6: Create Environment Configuration
|
|
|
|
### File: `example.env`
|
|
|
|
Create or update `example.env` with required environment variables:
|
|
|
|
```bash
|
|
# Server Configuration
|
|
PORT={port_number}
|
|
DEBUG=False
|
|
|
|
# Database Configuration (if applicable)
|
|
{PROJECT_NAME}_DB_HOST=localhost
|
|
{PROJECT_NAME}_DB_NAME={project_name}
|
|
{PROJECT_NAME}_DB_USER={project_name}
|
|
{PROJECT_NAME}_DB_PASSWORD=your_password_here
|
|
|
|
# Optional: CORS Configuration
|
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
|
|
```
|
|
|
|
**CRITICAL**: Replace:
|
|
- `{port_number}` → Actual port number (e.g., 5151)
|
|
- `{PROJECT_NAME}` → Uppercase project name (e.g., "MATERIA_SERVER")
|
|
- `{project_name}` → Snake case project name (e.g., "materia_server")
|
|
|
|
### File: `.env` (gitignored)
|
|
|
|
Instruct the user to copy `example.env` to `.env` and fill in actual values:
|
|
|
|
```bash
|
|
# Copy example.env to .env and update with actual values
|
|
cp example.env .env
|
|
```
|
|
|
|
### Update .gitignore
|
|
|
|
Add `.env` to `.gitignore` if not already present:
|
|
|
|
```
|
|
# Environment variables
|
|
.env
|
|
```
|
|
|
|
## Step 7: Create Requirements File
|
|
|
|
Create `requirements.txt` with flask-smorest dependencies (no version pinning):
|
|
|
|
```txt
|
|
# Flask and API framework
|
|
Flask
|
|
flask-smorest
|
|
flask-cors
|
|
|
|
# Schema validation and serialization
|
|
marshmallow
|
|
|
|
# Environment variable management
|
|
python-dotenv
|
|
|
|
# Production server (optional but recommended)
|
|
gunicorn
|
|
|
|
# Database (if needed)
|
|
psycopg2-binary
|
|
```
|
|
|
|
## Step 8: Create Blueprints __init__.py
|
|
|
|
Create `blueprints/__init__.py`:
|
|
|
|
```python
|
|
"""
|
|
Blueprint modules for {Project Name} API.
|
|
|
|
Each blueprint represents a distinct feature or resource endpoint.
|
|
"""
|
|
```
|
|
|
|
## Step 9: Document Usage
|
|
|
|
Create or update README.md with:
|
|
|
|
### Setup
|
|
|
|
```bash
|
|
# Copy example environment file
|
|
cp example.env .env
|
|
|
|
# Edit .env and fill in actual values
|
|
# Then install dependencies
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
### Running the Server
|
|
|
|
```bash
|
|
# Development mode
|
|
python {project_name}.py
|
|
|
|
# Production mode with Gunicorn
|
|
gunicorn -w 4 -b 0.0.0.0:{port_number} '{project_name}:create_app()'
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
Copy `example.env` to `.env` and configure:
|
|
|
|
**Server Configuration:**
|
|
- `PORT` - Server port (default: {port_number})
|
|
- `DEBUG` - Enable debug mode (default: False)
|
|
|
|
**Database (if applicable):**
|
|
- `{PROJECT_NAME}_DB_HOST` - Database host (default: localhost)
|
|
- `{PROJECT_NAME}_DB_NAME` - Database name (default: {project_name})
|
|
- `{PROJECT_NAME}_DB_USER` - Database user (default: {project_name})
|
|
- `{PROJECT_NAME}_DB_PASSWORD` - Database password (REQUIRED)
|
|
|
|
### API Documentation
|
|
|
|
Once running, access Swagger UI at:
|
|
```
|
|
http://localhost:{port_number}/swagger
|
|
```
|
|
|
|
## Design Principles
|
|
|
|
This pattern follows these principles:
|
|
|
|
### Architecture:
|
|
1. **Blueprint Organization** - Modular endpoint organization, one blueprint per feature
|
|
2. **MethodView Classes** - Class-based views for HTTP methods (get, post, put, delete)
|
|
3. **Separation of Concerns** - Routes, schemas, and business logic separated
|
|
4. **Singleton Manager** - Centralized service initialization prevents duplicate connections
|
|
5. **Application Factory** - `create_app()` pattern for testing and flexibility
|
|
|
|
### API Design:
|
|
1. **OpenAPI/Swagger** - Automatic documentation via flask-smorest
|
|
2. **Schema-Driven** - Marshmallow schemas for validation and serialization
|
|
3. **Type Safety** - `@blp.arguments()` and `@blp.response()` decorators
|
|
4. **Error Handling** - Consistent error responses with proper HTTP status codes
|
|
5. **CORS Support** - Cross-origin requests for frontend consumption
|
|
|
|
### Best Practices:
|
|
1. **Environment-Based Config** - All secrets via environment variables
|
|
2. **Logging** - Structured logging throughout
|
|
3. **Idempotent Operations** - Safe to call multiple times
|
|
4. **Production Ready** - Gunicorn support out of the box
|
|
5. **Testing Friendly** - Application factory enables easy testing
|
|
|
|
## Integration with Other Skills
|
|
|
|
### Database Integration
|
|
If database is needed, use **postgres-setup** skill first:
|
|
```
|
|
User: "Set up postgres database for my project"
|
|
```
|
|
|
|
Then reference the database in your blueprints via the singleton manager:
|
|
```python
|
|
from common import service_manager
|
|
|
|
db = service_manager.get_database()
|
|
```
|
|
|
|
### Package Structure
|
|
If publishing as a package, use **python-pypi-setup** skill:
|
|
```
|
|
User: "Set up Python package for PyPI"
|
|
```
|
|
|
|
## Example Usage in Claude Code
|
|
|
|
**User:** "Set up Flask API server for my project"
|
|
|
|
**Claude:** "What is your project name?"
|
|
|
|
**User:** "crypto-tracker"
|
|
|
|
**Claude:** "What features/endpoints do you need?"
|
|
|
|
**User:** "prices, tokens, portfolio"
|
|
|
|
**Claude:** "Do you need database integration?"
|
|
|
|
**User:** "yes"
|
|
|
|
**Claude:** "What port should the server run on?"
|
|
|
|
**User:** "8080"
|
|
|
|
**Claude:**
|
|
1. Creates `crypto_tracker.py` with Flask app
|
|
2. Creates `blueprints/` directory with:
|
|
- `prices.py` and `prices_schemas.py`
|
|
- `tokens.py` and `tokens_schemas.py`
|
|
- `portfolio.py` and `portfolio_schemas.py`
|
|
3. Creates `common.py` with ServiceManager singleton
|
|
4. Creates `requirements.txt` with dependencies
|
|
5. Documents environment variables needed
|
|
6. Provides startup instructions
|
|
|
|
## Optional: Docker Support
|
|
|
|
If user requests Docker, create `Dockerfile`:
|
|
|
|
```dockerfile
|
|
FROM python:3.11-slim
|
|
WORKDIR /app
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
COPY . .
|
|
EXPOSE {port_number}
|
|
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:{port_number}", "{project_name}:create_app()"]
|
|
```
|