Add complete Task Manager API with FastAPI and SQLite

- Created FastAPI application with CRUD operations for tasks
- Implemented SQLAlchemy models with Task entity
- Added Pydantic schemas for request/response validation
- Set up Alembic for database migrations
- Configured SQLite database with proper file structure
- Added health check and root endpoints
- Included comprehensive API documentation
- Applied CORS middleware for development
- Updated README with detailed setup and usage instructions
- Applied code formatting with ruff linter
This commit is contained in:
Automated Action 2025-06-20 23:27:38 +00:00
parent ca478b53c0
commit 3625685e4f
17 changed files with 447 additions and 2 deletions

117
README.md
View File

@ -1,3 +1,116 @@
# FastAPI Application # Task Manager API
This is a FastAPI application bootstrapped by BackendIM, the AI-powered backend generation platform. A simple and efficient Task Manager API built with FastAPI and SQLite. This API allows you to create, read, update, and delete tasks with full CRUD operations.
## Features
- Create new tasks with title and description
- List all tasks with pagination support
- Get individual task details
- Update existing tasks (title, description, completion status)
- Delete tasks
- Mark tasks as completed/incomplete
- Health check endpoint
- Interactive API documentation
## Technology Stack
- **FastAPI** - Modern, fast web framework for building APIs
- **SQLAlchemy** - SQL toolkit and Object-Relational Mapping (ORM)
- **SQLite** - Lightweight, file-based database
- **Alembic** - Database migration tool
- **Pydantic** - Data validation using Python type annotations
- **Uvicorn** - ASGI web server
## Installation
1. Install dependencies:
```bash
pip install -r requirements.txt
```
2. Run database migrations:
```bash
alembic upgrade head
```
3. Start the development server:
```bash
uvicorn main:app --reload
```
The API will be available at `http://localhost:8000`
## API Documentation
- **Interactive docs**: http://localhost:8000/docs
- **ReDoc**: http://localhost:8000/redoc
- **OpenAPI JSON**: http://localhost:8000/openapi.json
## API Endpoints
### Root
- `GET /` - API information and links
### Health Check
- `GET /health` - Application health status
### Tasks
- `POST /api/tasks/` - Create a new task
- `GET /api/tasks/` - Get all tasks (supports pagination with `skip` and `limit` parameters)
- `GET /api/tasks/{task_id}` - Get a specific task by ID
- `PUT /api/tasks/{task_id}` - Update a specific task
- `DELETE /api/tasks/{task_id}` - Delete a specific task
## Task Schema
```json
{
"id": 1,
"title": "Sample Task",
"description": "This is a sample task description",
"completed": false,
"created_at": "2024-01-01T12:00:00.000Z",
"updated_at": "2024-01-01T12:00:00.000Z"
}
```
## Project Structure
```
├── main.py # FastAPI application entry point
├── requirements.txt # Python dependencies
├── alembic.ini # Alembic configuration
├── alembic/ # Database migrations
│ ├── versions/ # Migration files
│ ├── env.py # Alembic environment configuration
│ └── script.py.mako # Migration template
├── app/
│ ├── __init__.py
│ ├── api/ # API route handlers
│ │ ├── __init__.py
│ │ └── tasks.py # Task-related endpoints
│ ├── db/ # Database configuration
│ │ ├── __init__.py
│ │ ├── base.py # SQLAlchemy base
│ │ └── session.py # Database session management
│ ├── models/ # SQLAlchemy models
│ │ ├── __init__.py
│ │ └── task.py # Task model
│ └── schemas/ # Pydantic schemas
│ ├── __init__.py
│ └── task.py # Task schemas for validation
└── storage/
└── db/ # SQLite database storage
└── db.sqlite # Database file (created automatically)
```
## Development
The application uses automatic CORS configuration to allow requests from any origin during development.
Database migrations are handled through Alembic. The initial migration creates the tasks table with all necessary fields and indexes.
## Environment Variables
No environment variables are required for basic operation. The application uses SQLite with a local file-based database stored in `/app/storage/db/db.sqlite`.

42
alembic.ini Normal file
View File

@ -0,0 +1,42 @@
[alembic]
script_location = alembic
prepend_sys_path = .
version_path_separator = os
sqlalchemy.url = sqlite:////app/storage/db/db.sqlite
[post_write_hooks]
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

50
alembic/env.py Normal file
View File

@ -0,0 +1,50 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from app.db.base import Base
config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
target_metadata = Base.metadata
def run_migrations_offline() -> None:
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
alembic/script.py.mako Normal file
View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,37 @@
"""create tasks table
Revision ID: 001
Revises:
Create Date: 2024-01-01 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "001"
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"tasks",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("completed", sa.Boolean(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_tasks_id"), "tasks", ["id"], unique=False)
op.create_index(op.f("ix_tasks_title"), "tasks", ["title"], unique=False)
def downgrade() -> None:
op.drop_index(op.f("ix_tasks_title"), table_name="tasks")
op.drop_index(op.f("ix_tasks_id"), table_name="tasks")
op.drop_table("tasks")

0
app/__init__.py Normal file
View File

3
app/api/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .tasks import router as tasks_router
__all__ = ["tasks_router"]

58
app/api/tasks.py Normal file
View File

@ -0,0 +1,58 @@
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.models.task import Task
from app.schemas.task import TaskCreate, TaskUpdate, TaskResponse
router = APIRouter()
@router.post("/", response_model=TaskResponse, status_code=status.HTTP_201_CREATED)
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
db_task = Task(**task.dict())
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
@router.get("/", response_model=List[TaskResponse])
def read_tasks(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
tasks = db.query(Task).offset(skip).limit(limit).all()
return tasks
@router.get("/{task_id}", response_model=TaskResponse)
def read_task(task_id: int, db: Session = Depends(get_db)):
task = db.query(Task).filter(Task.id == task_id).first()
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
return task
@router.put("/{task_id}", response_model=TaskResponse)
def update_task(task_id: int, task_update: TaskUpdate, db: Session = Depends(get_db)):
task = db.query(Task).filter(Task.id == task_id).first()
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
update_data = task_update.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(task, field, value)
db.commit()
db.refresh(task)
return task
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_task(task_id: int, db: Session = Depends(get_db)):
task = db.query(Task).filter(Task.id == task_id).first()
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
db.delete(task)
db.commit()
return None

0
app/db/__init__.py Normal file
View File

3
app/db/base.py Normal file
View File

@ -0,0 +1,3 @@
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

23
app/db/session.py Normal file
View File

@ -0,0 +1,23 @@
from pathlib import Path
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
DB_DIR = Path("/app") / "storage" / "db"
DB_DIR.mkdir(parents=True, exist_ok=True)
SQLALCHEMY_DATABASE_URL = f"sqlite:///{DB_DIR}/db.sqlite"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

3
app/models/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .task import Task
__all__ = ["Task"]

16
app/models/task.py Normal file
View File

@ -0,0 +1,16 @@
from datetime import datetime
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime
from app.db.base import Base
class Task(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, nullable=False, index=True)
description = Column(Text, nullable=True)
completed = Column(Boolean, default=False, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)

3
app/schemas/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .task import TaskBase, TaskCreate, TaskUpdate, TaskResponse
__all__ = ["TaskBase", "TaskCreate", "TaskUpdate", "TaskResponse"]

28
app/schemas/task.py Normal file
View File

@ -0,0 +1,28 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
class TaskBase(BaseModel):
title: str
description: Optional[str] = None
completed: bool = False
class TaskCreate(TaskBase):
pass
class TaskUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
completed: Optional[bool] = None
class TaskResponse(TaskBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True

35
main.py Normal file
View File

@ -0,0 +1,35 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.tasks import router as tasks_router
from app.db.session import engine
from app.db.base import Base
app = FastAPI(
title="Task Manager API",
description="A simple Task Manager API built with FastAPI",
version="1.0.0",
openapi_url="/openapi.json",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Base.metadata.create_all(bind=engine)
app.include_router(tasks_router, prefix="/api/tasks", tags=["tasks"])
@app.get("/")
def root():
return {"title": "Task Manager API", "documentation": "/docs", "health": "/health"}
@app.get("/health")
def health_check():
return {"status": "healthy", "service": "Task Manager API"}

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
alembic==1.12.1
pydantic==2.5.0
python-multipart==0.0.6
ruff==0.1.6