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
3 changes: 3 additions & 0 deletions simple_backend/src/task_tracker/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
BIN_ID=687f8b92f7e7a370d1ec0422
SECRET_KEY=$2a$10$U1C8bVbyrpEJ5N7HhdIqiurq3VkrUW8mVA0wnF9s7NpEQ8.TJwdri
CLOUDFLARE_API_KEY=w050KFAkAy1DXJGdwP2lw4TZohm-uf9pjMHp8U3f
34 changes: 34 additions & 0 deletions simple_backend/src/task_tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
### Минусы подхода с хранением задач в оперативной памяти (списке python)

- Все данные теряются при перезапуске сервера.
- Хранит состояние.
- Несколько пользователей не могут работать с одними и теми же задачами.
- Нельзя использовать для прода.

Итог:
Рационально использовать данный подход только, если нужно быстро набросать
функционал или показать реализацию чего-либо, когда нет времени или возможности поднять бд.


### Про JSON

Что улучшилось:
- Данные не сносятся при перезапуске сервера.
- Можно копировать, бэкапить и синхронизировать файл(данные).

Избавились ли от хранения состояния?
Нет, состояние всё ещё хранится локально — но уже более устойчиво. Это по-прежнему stateful-подход.

Где ещё можно хранить задачи?
1. Базы данных (SQLite, PostgreSQL, MongoDB)
2. Облачные API (jsonbin.io, mockapi.io)


### Состояние гонки

Осталась проблема параллельной записи.

Возможные решения:
- Использовать БД
- Использовать блокировки (threading.Lock, Redis-блокировки)
- Делать optimistic update через версионирование (etag, timestamp)
26 changes: 26 additions & 0 deletions simple_backend/src/task_tracker/base_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from abc import ABC, abstractmethod
import requests

class BaseHTTPClient(ABC):
def __init__(self, base_url: str, headers: dict):
self.url = base_url
self.headers = headers

def get(self):
response = requests.get(self.url, headers=self.headers)
response.raise_for_status()
return response.json()

def put(self, data):
response = requests.put(self.url, json=data, headers=self.headers)
response.raise_for_status()
return response.json()

def post(self, data):
response = requests.post(self.url, json=data, headers=self.headers)
response.raise_for_status()
return response.json()

@abstractmethod
def handle(self, *args, **kwargs):
pass
54 changes: 54 additions & 0 deletions simple_backend/src/task_tracker/cloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

from schemas import Task
from base_client import BaseHTTPClient


class CloudStorage(BaseHTTPClient):
def __init__(self, bin_id: str, secret_key: str):
url = f"https://api.jsonbin.io/v3/b/{bin_id}"
headers = {
"X-Master-Key": secret_key,
"Content-Type": "application/json"
}
super().__init__(url, headers)

def _read(self) -> list[Task]:
data = self.get()
return [Task(**task) for task in data["record"]]

def _write(self, tasks: list[Task]):
json_data = [task.model_dump() for task in tasks]
self.put(json_data)

def get_all(self) -> list[Task]:
return self._read()

def add(self, task: Task):
tasks = self._read()
tasks.append(task)
self._write(tasks)

def update(self, task_id: int, updated_task: Task):
tasks = self._read()
for i, task in enumerate(tasks):
if task.id == task_id:
tasks[i] = Task(
id=task_id,
title=updated_task.title,
status=updated_task.status,
)
self._write(tasks)
return
raise ValueError("Task not found")

def delete(self, task_id: int):
tasks = self._read()
for i, t in enumerate(tasks):
if t.id == task_id:
tasks.pop(i)
self._write(tasks)
return
raise ValueError("Task not found")

def handle(self, *args, **kwargs):
return self.get_all()
6 changes: 6 additions & 0 deletions simple_backend/src/task_tracker/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import enum

class TaskStatus(enum.Enum):
PLANNED = 0 # Запланировано
IN_PROGRESS = 1 # В работе
COMPLETED = 2 # Выполнено
56 changes: 56 additions & 0 deletions simple_backend/src/task_tracker/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import json
from typing import List
from pathlib import Path
from schemas import Task


class TaskJson:
def __init__(self, file_name: str):
self.file = Path(file_name)
if not self.file.exists():
self._write([])


def _read(self) -> List[Task]:
with self.file.open("r", encoding="utf-8") as f:
data = json.load(f)
return [Task(**item) for item in data]


def _write(self, tasks: list[Task]):
with self.file.open("w", encoding="utf-8") as f:
json.dump([task.dict() for task in tasks], f, indent=2)


def get_all(self) -> List[Task]:
return self._read()


def add(self, task: Task):
tasks = self._read()
tasks.append(task)
self._write(tasks)


def update(self, task_id: int, updated_task: Task):
tasks = self._read()
for i, task in enumerate(tasks):
if task.id == task_id:
tasks[i] = Task(
id=task_id,
title=updated_task.title,
status=updated_task.status,
)
self._write(tasks)
return
raise ValueError("Task not found")


def delete(self, task_id: int):
tasks = self._read()
for i, t in enumerate(tasks):
if t.id == task_id:
tasks.pop(i)
self._write(tasks)
return
raise ValueError("Task not found")
25 changes: 25 additions & 0 deletions simple_backend/src/task_tracker/llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

from base_client import BaseHTTPClient

class LLMAssistant(BaseHTTPClient):
def __init__(self, api_token: str):
url = "https://api.cloudflare.com/client/v4/llm/infer"
headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
super().__init__(url, headers)

def task_llm(self, task_text: str):
body = {
"prompt": f"Задача: {task_text}. Объясни пошагово, как её выполнить.",
"max_tokens": 150
}
try:
result = self.post(body)
return result["result"]["response"]
except Exception:
return "LLM не нашел решение для вашей задачи"

def handle(self, task_text):
return self.task_llm(task_text)
41 changes: 34 additions & 7 deletions simple_backend/src/task_tracker/main.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import os

from dotenv import load_dotenv
from fastapi import FastAPI
from http.client import HTTPException

from cloud import CloudStorage
from schemas import Task
from llm import LLMAssistant

load_dotenv()
app = FastAPI()

data = CloudStorage(
bin_id=os.getenv("BIN_ID"),
secret_key=os.getenv("SECRET_KEY")
)

llm = LLMAssistant(api_token=os.getenv("CLOUDFLARE_API_KEY"))

@app.get("/tasks")
def get_tasks():
pass
return data.get_all()

@app.post("/tasks")
def create_task(task):
pass
def create_task(task: Task):
solution = llm.task_llm(task.title)
task.title += f"\n\n💡 Решение от LLM:\n{solution}"
data.add(task)
return task

@app.put("/tasks/{task_id}")
def update_task(task_id: int):
pass
@app.put("/tasks/{task_id}", response_model=Task)
def update_task(task_id: int, update_task: Task):
try:
data.update(task_id, update_task)
return update_task
except ValueError:
raise HTTPException(status_code=404, detail="Task not found")

@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
pass
try:
data.delete(task_id)
return {"message": "Task deleted"}
except ValueError:
raise HTTPException(status_code=404, detail="Task not found")
4 changes: 3 additions & 1 deletion simple_backend/src/task_tracker/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
fastapi
uvicorn[standard]
uvicorn[standard]
requests
ruff
9 changes: 9 additions & 0 deletions simple_backend/src/task_tracker/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pydantic import BaseModel, ConfigDict
from const import TaskStatus

class Task(BaseModel):
id: int
title: str
status: TaskStatus = TaskStatus.PLANNED

model_config = ConfigDict(use_enum_values=True)