This commit is contained in:
2026-02-26 19:18:58 +05:00
commit 2fedc1ac18
18 changed files with 445 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

84
crud.py Normal file
View File

@@ -0,0 +1,84 @@
from sqlalchemy.orm import Session
import models
import schemas
# Tasks
def get_task(db: Session, task_id: int, telegram_user_id: str = None):
query = db.query(models.Task).filter(models.Task.id == task_id)
if telegram_user_id:
query = query.filter(models.Task.telegram_user_id == telegram_user_id)
return query.first()
def get_tasks(db: Session, skip: int = 0, limit: int = 100, telegram_user_id: str = None, status: models.TaskStatus = None):
query = db.query(models.Task)
if telegram_user_id:
query = query.filter(models.Task.telegram_user_id == telegram_user_id)
if status:
query = query.filter(models.Task.status == status)
return query.offset(skip).limit(limit).all()
def create_task(db: Session, task: schemas.TaskCreate):
db_task = models.Task(**task.model_dump())
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
def update_task(db: Session, task_id: int, task_update: schemas.TaskUpdate, telegram_user_id: str = None):
db_task = get_task(db, task_id, telegram_user_id)
if not db_task:
return None
update_data = task_update.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(db_task, key, value)
db.commit()
db.refresh(db_task)
return db_task
def delete_task(db: Session, task_id: int, telegram_user_id: str = None):
db_task = get_task(db, task_id, telegram_user_id)
if db_task:
db.delete(db_task)
db.commit()
return True
return False
# Reminders
def get_reminder(db: Session, reminder_id: int):
return db.query(models.Reminder).filter(models.Reminder.id == reminder_id).first()
def get_reminders(db: Session, task_id: int = None, skip: int = 0, limit: int = 100):
query = db.query(models.Reminder)
if task_id:
query = query.filter(models.Reminder.task_id == task_id)
return query.offset(skip).limit(limit).all()
def create_reminder(db: Session, reminder: schemas.ReminderCreate):
db_reminder = models.Reminder(**reminder.model_dump())
db.add(db_reminder)
db.commit()
db.refresh(db_reminder)
return db_reminder
def update_reminder(db: Session, reminder_id: int, reminder_update: schemas.ReminderUpdate):
db_reminder = get_reminder(db, reminder_id)
if not db_reminder:
return None
update_data = reminder_update.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(db_reminder, key, value)
db.commit()
db.refresh(db_reminder)
return db_reminder
def delete_reminder(db: Session, reminder_id: int):
db_reminder = get_reminder(db, reminder_id)
if db_reminder:
db.delete(db_reminder)
db.commit()
return True
return False

19
database.py Normal file
View File

@@ -0,0 +1,19 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
SQLALCHEMY_DATABASE_URL = "sqlite:///./tasks.db"
# connect_args={"check_same_thread": False} is required for SQLite in FastAPI
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

35
int.txt Normal file
View File

@@ -0,0 +1,35 @@
Task and Reminder Manager Backend - Walkthrough
What Was Accomplished
I successfully developed the backend infrastructure for the Task and Reminder Manager application, exposing a REST API built with FastAPI that meets the user requirements. I set up the database, models, and endpoints necessary to manage tasks and reminders.
Key Changes Made
Application Structure: Created a modular FastAPI backend (
main.py
,
database.py
,
models.py
,
schemas.py
,
crud.py
).
Database System: Implemented SQLite functionality via SQLAlchemy, with specific models adapted to track user execution and messaging contexts.
Designed
Task
model containing the telegram_user_id and the status (pending, completed).
Designed
Reminder
model tightly coupled to
Task
, capable of deep tracking: trigger_time, recurrence_type (daily, custom, etc.), and notification toggles (notify_push, notify_telegram).
Data Validation: Built Pydantic schemas representing creation payloads, updates, and serialized API responses to ensure safe data boundaries.
API Endpoints: Built isolated routers to handle operations across tasks (
routers/tasks.py
) and reminders (
routers/reminders.py
). Examples include retrieving tasks specific to a telegram_user_id, handling task completion logic (via PUT endpoint updates), and constructing sophisticated reminders.
Validation Results
Verified that all necessary dependencies (including updated Python 3.13-compatible versions) install inside a valid virtual environment.
Verified successful Uvicorn server boot without errors.
Fired test API requests to generate entries into SQLite, returning 200 success responses. The system's CRUD functionality handles database commits successfully and retrieves nested relationships gracefully.

34
main.py Normal file
View File

@@ -0,0 +1,34 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import models
from database import engine
from routers import tasks, reminders
# Create database tables
models.Base.metadata.create_all(bind=engine)
app = FastAPI(
title="Task Manager API",
description="Backend API for managing tasks and reminders for Web and Telegram.",
version="1.0.0"
)
# CORS configuration
origins = [
"*" # Adjust this in production
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include routers
app.include_router(tasks.router)
app.include_router(reminders.router)
@app.get("/")
def read_root():
return {"message": "Welcome to the Task Manager API"}

46
models.py Normal file
View File

@@ -0,0 +1,46 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Enum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
import enum
from database import Base
class TaskStatus(str, enum.Enum):
pending = "pending"
completed = "completed"
class RecurrenceType(str, enum.Enum):
none = "none"
daily = "daily"
weekly = "weekly"
monthly = "monthly"
custom = "custom"
class Task(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, nullable=False)
description = Column(String, nullable=True)
telegram_user_id = Column(String, index=True, nullable=False)
status = Column(Enum(TaskStatus), default=TaskStatus.pending, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
reminders = relationship("Reminder", back_populates="task", cascade="all, delete-orphan")
class Reminder(Base):
__tablename__ = "reminders"
id = Column(Integer, primary_key=True, index=True)
task_id = Column(Integer, ForeignKey("tasks.id"), nullable=False)
trigger_time = Column(DateTime(timezone=True), nullable=False)
recurrence_type = Column(Enum(RecurrenceType), default=RecurrenceType.none, nullable=False)
recurrence_interval = Column(Integer, nullable=True) # E.g. every X minutes/hours if custom
remind_count_limit = Column(Integer, default=0, nullable=False) # 0 for infinite
remind_count_current = Column(Integer, default=0, nullable=False)
notify_push = Column(Boolean, default=False, nullable=False)
notify_telegram = Column(Boolean, default=False, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
task = relationship("Task", back_populates="reminders")

60
pip_install.log Normal file
View File

@@ -0,0 +1,60 @@
Collecting fastapi==0.110.0 (from -r requirements.txt (line 1))
Using cached fastapi-0.110.0-py3-none-any.whl.metadata (25 kB)
Collecting uvicorn==0.27.1 (from -r requirements.txt (line 2))
Using cached uvicorn-0.27.1-py3-none-any.whl.metadata (6.3 kB)
Collecting sqlalchemy==2.0.28 (from -r requirements.txt (line 3))
Using cached SQLAlchemy-2.0.28-py3-none-any.whl.metadata (9.6 kB)
Collecting pydantic==2.6.4 (from -r requirements.txt (line 4))
Using cached pydantic-2.6.4-py3-none-any.whl.metadata (85 kB)
Collecting pydantic-settings==2.2.1 (from -r requirements.txt (line 5))
Using cached pydantic_settings-2.2.1-py3-none-any.whl.metadata (3.1 kB)
Collecting starlette<0.37.0,>=0.36.3 (from fastapi==0.110.0->-r requirements.txt (line 1))
Using cached starlette-0.36.3-py3-none-any.whl.metadata (5.9 kB)
Collecting typing-extensions>=4.8.0 (from fastapi==0.110.0->-r requirements.txt (line 1))
Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Collecting annotated-types>=0.4.0 (from pydantic==2.6.4->-r requirements.txt (line 4))
Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
Collecting pydantic-core==2.16.3 (from pydantic==2.6.4->-r requirements.txt (line 4))
Using cached pydantic_core-2.16.3.tar.gz (368 kB)
Installing build dependencies: started
Installing build dependencies: finished with status 'done'
Getting requirements to build wheel: started
Getting requirements to build wheel: finished with status 'done'
Installing backend dependencies: started
Installing backend dependencies: finished with status 'done'
Preparing metadata (pyproject.toml): started
Preparing metadata (pyproject.toml): finished with status 'error'
error: subprocess-exited-with-error
Preparing metadata (pyproject.toml) did not run successfully.
exit code: 1
[14 lines of output]
Python reports SOABI: cp313-win_amd64
Computed rustc target triple: x86_64-pc-windows-msvc
Installation directory: C:\Users\patro\AppData\Local\puccinialin\puccinialin\Cache
Rustup already downloaded
Installing rust to C:\Users\patro\AppData\Local\puccinialin\puccinialin\Cache\rustup
warn: installing msvc toolchain without its prerequisites
error: could not read metadata for file: 'C:\Users\patro\AppData\Local\puccinialin\puccinialin\Cache\rustup-init\rustup-init.exe': ╨б╨╕╤Б╤В╨╡╨╝╨╡ ╨╜╨╡ ╤Г╨┤╨░╨╡╤В╤Б╤П ╨╜╨░╨╣╤В╨╕ ╤Г╨║╨░╨╖╨░╨╜╨╜╤Л╨╣ ╨┐╤Г╤В╤М. (os error 3)
Cargo, the Rust package manager, is not installed or is not on PATH.
This package requires Rust and Cargo to compile extensions. Install it through
the system's package manager or via https://rustup.rs/
Checking for Rust toolchain....
Rust not found, installing into a temporary directory
[end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.
[notice] A new release of pip is available: 25.3 -> 26.0.1
[notice] To update, run: G:\CodeAssistent\task\api\task-api\.venv\Scripts\python.exe -m pip install --upgrade pip
error: metadata-generation-failed
Encountered error while generating package metadata.
pydantic-core
note: This is an issue with the package mentioned above, not pip.
hint: See above for details.

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
fastapi
uvicorn
sqlalchemy
pydantic
pydantic-settings

Binary file not shown.

Binary file not shown.

53
routers/reminders.py Normal file
View File

@@ -0,0 +1,53 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
import crud
import models
import schemas
from database import get_db
router = APIRouter(
prefix="/reminders",
tags=["reminders"]
)
@router.post("/", response_model=schemas.Reminder)
def create_reminder(reminder: schemas.ReminderCreate, db: Session = Depends(get_db)):
# Verify task exists
db_task = crud.get_task(db, task_id=reminder.task_id)
if not db_task:
raise HTTPException(status_code=404, detail="Task not found")
return crud.create_reminder(db=db, reminder=reminder)
@router.get("/", response_model=List[schemas.Reminder])
def read_reminders(
skip: int = 0,
limit: int = 100,
task_id: int = Query(None, description="Filter by Task ID"),
db: Session = Depends(get_db)
):
reminders = crud.get_reminders(db, skip=skip, limit=limit, task_id=task_id)
return reminders
@router.get("/{reminder_id}", response_model=schemas.Reminder)
def read_reminder(reminder_id: int, db: Session = Depends(get_db)):
db_reminder = crud.get_reminder(db, reminder_id=reminder_id)
if db_reminder is None:
raise HTTPException(status_code=404, detail="Reminder not found")
return db_reminder
@router.put("/{reminder_id}", response_model=schemas.Reminder)
def update_reminder_endpoint(reminder_id: int, reminder: schemas.ReminderUpdate, db: Session = Depends(get_db)):
db_reminder = crud.update_reminder(db, reminder_id=reminder_id, reminder_update=reminder)
if db_reminder is None:
raise HTTPException(status_code=404, detail="Reminder not found")
return db_reminder
@router.delete("/{reminder_id}")
def delete_reminder_endpoint(reminder_id: int, db: Session = Depends(get_db)):
success = crud.delete_reminder(db, reminder_id=reminder_id)
if not success:
raise HTTPException(status_code=404, detail="Reminder not found")
return {"message": "Reminder successfully deleted"}

49
routers/tasks.py Normal file
View File

@@ -0,0 +1,49 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
import crud
import models
import schemas
from database import get_db
router = APIRouter(
prefix="/tasks",
tags=["tasks"]
)
@router.post("/", response_model=schemas.Task)
def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)):
return crud.create_task(db=db, task=task)
@router.get("/", response_model=List[schemas.Task])
def read_tasks(
skip: int = 0,
limit: int = 100,
telegram_user_id: str = Query(None, description="Filter by Telegram User ID"),
status: models.TaskStatus = Query(None, description="Filter by task status"),
db: Session = Depends(get_db)
):
tasks = crud.get_tasks(db, skip=skip, limit=limit, telegram_user_id=telegram_user_id, status=status)
return tasks
@router.get("/{task_id}", response_model=schemas.Task)
def read_task(task_id: int, telegram_user_id: str = Query(None, description="Telegram User ID for authorization"), db: Session = Depends(get_db)):
db_task = crud.get_task(db, task_id=task_id, telegram_user_id=telegram_user_id)
if db_task is None:
raise HTTPException(status_code=404, detail="Task not found")
return db_task
@router.put("/{task_id}", response_model=schemas.Task)
def update_task_endpoint(task_id: int, task: schemas.TaskUpdate, telegram_user_id: str = Query(None, description="Telegram User ID for authorization"), db: Session = Depends(get_db)):
db_task = crud.update_task(db, task_id=task_id, task_update=task, telegram_user_id=telegram_user_id)
if db_task is None:
raise HTTPException(status_code=404, detail="Task not found or you don't have access")
return db_task
@router.delete("/{task_id}")
def delete_task_endpoint(task_id: int, telegram_user_id: str = Query(None, description="Telegram User ID for authorization"), db: Session = Depends(get_db)):
success = crud.delete_task(db, task_id=task_id, telegram_user_id=telegram_user_id)
if not success:
raise HTTPException(status_code=404, detail="Task not found or you don't have access")
return {"message": "Task successfully deleted"}

60
schemas.py Normal file
View File

@@ -0,0 +1,60 @@
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional, List
from models import TaskStatus, RecurrenceType
# Reminder Schemas
class ReminderBase(BaseModel):
trigger_time: datetime
recurrence_type: RecurrenceType = RecurrenceType.none
recurrence_interval: Optional[int] = None
remind_count_limit: int = 0
notify_push: bool = False
notify_telegram: bool = False
class ReminderCreate(ReminderBase):
task_id: int
class ReminderUpdate(BaseModel):
trigger_time: Optional[datetime] = None
recurrence_type: Optional[RecurrenceType] = None
recurrence_interval: Optional[int] = None
remind_count_limit: Optional[int] = None
remind_count_current: Optional[int] = None
notify_push: Optional[bool] = None
notify_telegram: Optional[bool] = None
class Reminder(ReminderBase):
id: int
task_id: int
remind_count_current: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
# Task Schemas
class TaskBase(BaseModel):
title: str
description: Optional[str] = None
telegram_user_id: str
class TaskCreate(TaskBase):
pass
class TaskUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
status: Optional[TaskStatus] = None
class Task(TaskBase):
id: int
status: TaskStatus
created_at: datetime
updated_at: datetime
reminders: List[Reminder] = []
class Config:
from_attributes = True

BIN
tasks.db Normal file

Binary file not shown.