Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Tests

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.13"]

steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --group dev
- name: Run tests
run: uv run pytest tests/ -v
31 changes: 23 additions & 8 deletions crud.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from typing import Optional
import models, schemas
import models, schemas


def create_user(db: Session, user: schemas.UserCreate):
existing = db.query(models.User).filter(models.User.email == user.email).first()
if existing:
return None
db_user = models.User(name=user.name, email=user.email)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
try:
db.commit()
db.refresh(db_user)
return db_user
except IntegrityError:
db.rollback()
return None


def get_user(db: Session, user_id: int):
Expand All @@ -20,12 +28,19 @@ def get_users(db: Session, skip: int = 0, limit: int = 10):



def create_task(db:Session, task: schemas.TaskCreate):
def create_task(db: Session, task: schemas.TaskCreate):
owner = db.query(models.User).filter(models.User.id == task.owner_id).first()
if not owner:
return None
db_task = models.Task(**task.model_dump())
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
try:
db.commit()
db.refresh(db_task)
return db_task
except IntegrityError:
db.rollback()
return None

def get_task(db:Session, task_id: int):
return db.query(models.Task).filter(models.Task.id == task_id).first()
Expand Down
10 changes: 4 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@
version="1.0.0"
)

origins = [
"http://localhost:3000", # React dev server
"http://localhost:5173", # Vite dev server
]

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:3000", # React dev server
"http://localhost:5173", # Vite dev server
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Expand Down
4 changes: 3 additions & 1 deletion models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from datetime import date
from sqlalchemy import Column, Integer, String, Boolean, Date, ForeignKey
from sqlalchemy.orm import relationship
from database import Base

Expand All @@ -20,6 +21,7 @@ class Task(Base):
description = Column(String, nullable=True)
completed = Column(Boolean, default=False)
priority = Column(String, default="medium")
due_date = Column(Date, nullable=True)
owner_id = Column(Integer, ForeignKey("users.id"))

owner = relationship("User", back_populates="tasks")
17 changes: 16 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
[project]
name = "task-manager"
version = "0.1.0"
description = "Add your description here"
description = "A simple task manager built with FastAPI and SQLite"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.137.2",
"sqlalchemy>=2.0.51",
"uvicorn>=0.49.0",
]

[project.optional-dependencies]
dev = [
"pytest>=8",
"httpx>=0.27",
]

[tool.pytest.ini_options]
testpaths = ["tests"]

[dependency-groups]
dev = [
"httpx>=0.28.1",
"pytest>=9.1.1",
]
11 changes: 7 additions & 4 deletions routers/tasks.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List, Optional
import crud, schemas
from database import get_db

router = APIRouter(prefix="/tasks", tags=["Tasks"])

@router.post("/", response_model=schemas.TaskResponse)
@router.post("/", response_model=schemas.TaskResponse, status_code=201)
def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)):
return crud.create_task(db, task)
result = crud.create_task(db, task)
if not result:
raise HTTPException(status_code=400, detail="Invalid owner_id or conflicting data")
return result


@router.get("/", response_model=List[schemas.TaskResponse])
def get_tasks(skip: int = 0, limit: int = 10, completed: Optional[bool] = None, db: Session = Depends(get_db)):
def get_tasks(skip: int = Query(0, ge=0), limit: int = Query(10, ge=1, le=1000), completed: Optional[bool] = None, db: Session = Depends(get_db)):
return crud.get_tasks(db, skip=skip, limit=limit, completed=completed)


Expand Down
18 changes: 11 additions & 7 deletions routers/users.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
import crud, schemas
import crud, schemas
from database import get_db

router = APIRouter(prefix="/users", tags=["Users"])

@router.post("/", response_model=schemas.UserResponse)
@router.post("/", response_model=schemas.UserResponse, status_code=201)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
return crud.create_user(db, user)
result = crud.create_user(db, user)
if not result:
raise HTTPException(status_code=409, detail="Email already registered")
return result


@router.get("/", response_model=List[schemas.UserResponse])
def get_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
def get_users(skip: int = Query(0, ge=0), limit: int = Query(10, ge=1, le=1000), db: Session = Depends(get_db)):
return crud.get_users(db, skip=skip, limit=limit)

@router.get("/{user_id}", response_model=schemas.UserResponse)
Expand All @@ -22,12 +25,13 @@ def get_user(user_id: int, db: Session = Depends(get_db)):
raise HTTPException(status_code=404, detail="User not found")
return user

@router.put("/{user_id}", response_model= schemas.UserResponse)
@router.put("/{user_id}", response_model=schemas.UserResponse)
def update_user(user_id: int, data: schemas.UserUpdate, db: Session = Depends(get_db)):
user = crud.update_user(db, user_id, data)
if not user:
raise HTTPException(status_code=404, detail="User not found")

return user

@router.delete("/{user_id}")
def delete_user(user_id: int, db: Session = Depends(get_db)):
user = crud.delete_user(db, user_id)
Expand Down
4 changes: 4 additions & 0 deletions schemas.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import date
from pydantic import BaseModel
from typing import Optional, List

Expand All @@ -8,17 +9,20 @@ class TaskBase(BaseModel):
class TaskCreate(TaskBase):
owner_id: int
priority: str = "medium"
due_date: Optional[date] = None

class TaskUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
completed: Optional[bool] = None
priority: Optional[str] = None
due_date: Optional[date] = None

class TaskResponse(TaskBase):
id: int
completed: bool
priority: str
due_date: Optional[date] = None
owner_id: int

class Config:
Expand Down
Loading