From aee3186771ddf812c117966e41c7ad5a95747873 Mon Sep 17 00:00:00 2001 From: ou Date: Tue, 28 Oct 2025 00:20:05 +0300 Subject: [PATCH 1/8] =?UTF-8?q?1=20=D0=B8=202=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D1=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 45 +++++++++++++++++++------ 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 3db98d0d..94d5d7c6 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,19 +1,42 @@ -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel app = FastAPI() +tasks = [{'id': 1, 'task': 'Закончить простые бэки', 'status': 'No'}, {'id': 2, 'task': 'Хз', 'status': 'Yes'}] -@app.get("/tasks") +class CreateTask(BaseModel): + task: str + status: str = 'No' +class UpdateTask(BaseModel): + task: str + status: str + +@app.get("/tasks", tags=['Вывод всех задач']) def get_tasks(): - pass + return tasks -@app.post("/tasks") -def create_task(task): - pass +@app.post("/tasks", tags=['Добавление задачи']) +def create_task(new_task: CreateTask): + max_id = max(task['id'] for task in tasks) + tasks.append({'id': max_id + 1, 'task': new_task.task, 'status': new_task.status}) + return {"success": True} -@app.put("/tasks/{task_id}") -def update_task(task_id: int): - pass +@app.put("/tasks/{task_id}", tags=['Обновление задачи']) +def update_task(task_id: int, update_task: UpdateTask): + for i in tasks: + if i['id'] == task_id: + i['task'] = update_task.task + i['status'] = update_task.status + return {"success": True} + + raise HTTPException(status_code=404, detail = 'Задача не найдена') -@app.delete("/tasks/{task_id}") +@app.delete("/tasks/{task_id}", tags=['Удаление задачи']) def delete_task(task_id: int): - pass + global tasks + for i, task in enumerate(tasks): + if task['id'] == task_id: + tasks.pop(i) + return {"success": True} + + raise HTTPException(status_code=404, detail = 'Задача не найдена') \ No newline at end of file From 1c35a62ef20f10073f5db43d6afa5e092aca8f75 Mon Sep 17 00:00:00 2001 From: ou Date: Tue, 28 Oct 2025 17:20:40 +0300 Subject: [PATCH 2/8] =?UTF-8?q?2=D0=BE=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=B7=D0=B0=D0=B4=D0=B0=D1=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 34 +++++++++++++--- simple_backend/src/task_tracker/readme.md | 45 ++++++++++++++++++++++ simple_backend/src/task_tracker/tasks.json | 14 +++++++ 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 simple_backend/src/task_tracker/readme.md create mode 100644 simple_backend/src/task_tracker/tasks.json diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 94d5d7c6..5a29197d 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,42 +1,66 @@ from fastapi import FastAPI, HTTPException from pydantic import BaseModel +import os +import json app = FastAPI() -tasks = [{'id': 1, 'task': 'Закончить простые бэки', 'status': 'No'}, {'id': 2, 'task': 'Хз', 'status': 'Yes'}] + +json_name = 'tasks.json' + +def load_tasks(): + if not os.path.exists(json_name): + return [] + + try: + with open(json_name, 'r', encoding='utf-8') as file: + return json.load(file) + except (FileNotFoundError, json.JSONDecodeError): + return [] + + +def save_tasks(task_list): + with open(json_name, 'w', encoding='utf-8') as file: + json.dump(task_list, file) class CreateTask(BaseModel): task: str status: str = 'No' + class UpdateTask(BaseModel): task: str status: str @app.get("/tasks", tags=['Вывод всех задач']) def get_tasks(): - return tasks + return load_tasks() @app.post("/tasks", tags=['Добавление задачи']) def create_task(new_task: CreateTask): - max_id = max(task['id'] for task in tasks) - tasks.append({'id': max_id + 1, 'task': new_task.task, 'status': new_task.status}) + tasks = load_tasks() + max_id = max(task['id'] for task in tasks) + 1 + tasks.append({'id': max_id, 'task': new_task.task, 'status': new_task.status}) + save_tasks(tasks) return {"success": True} @app.put("/tasks/{task_id}", tags=['Обновление задачи']) def update_task(task_id: int, update_task: UpdateTask): + tasks = load_tasks() for i in tasks: if i['id'] == task_id: i['task'] = update_task.task i['status'] = update_task.status + save_tasks(i) return {"success": True} raise HTTPException(status_code=404, detail = 'Задача не найдена') @app.delete("/tasks/{task_id}", tags=['Удаление задачи']) def delete_task(task_id: int): - global tasks + tasks = load_tasks() for i, task in enumerate(tasks): if task['id'] == task_id: tasks.pop(i) + save_tasks(tasks) return {"success": True} raise HTTPException(status_code=404, detail = 'Задача не найдена') \ No newline at end of file diff --git a/simple_backend/src/task_tracker/readme.md b/simple_backend/src/task_tracker/readme.md new file mode 100644 index 00000000..481a264c --- /dev/null +++ b/simple_backend/src/task_tracker/readme.md @@ -0,0 +1,45 @@ +**В чём минусы подхода с хранением задач в оперативной памяти (списке python)?** + +Минусы хранения задач в оперативной памяти (списке Python): +1 - Потеря данных при перезапуске +2 - Ограниченный объем памяти +3 - Данные существуют только во время работы программы + +## после перехода на json файл +**Что улучшилось после того, как список из оперативной памяти изменился на файл проекта?** + +Улучшения: +1. Сохраняемость данных - задачи не теряются при перезапуске сервера +2. Устойчивость к сбоям - данные сохраняются на диск, а не только в RAM +3. Прозрачность данных - можно открыть файл и посмотреть задачи вручную + +Остались проблемы: +1. Производительность - чтение/запись файла медленнее чем работа с памятью +2. Состояние гонки - при одновременных запросах возможна потеря данных + +**Избавились ли мы от хранения состояния?** +Нет, Backend остался STATEful - он по-прежнему хранит данные между запросами, просто теперь они сохраняются на диск. + +**Где еще можно хранить задачи и какие есть преимущества и недостатки этих подходов?** +### 1. Реляционные БД (PostgreSQL, MySQL) +Плюсы: +- ACID-транзакции +- Сложные запросы (JOIN, GROUP BY) +- Целостность данных +- Одновременный доступ + +Минусы: +- Сложность настройки +- Оверкилл для простых проектов +- Требует отдельного сервера БД + +### 2. Облачные хранилища +Плюсы: +- Доступность из любого места +- Автоматическое резервное копирование +- Не нужно настраивать сервер + +Минусы: +- Зависимость от внешнего сервиса +- Лимиты запросов +- Проблемы с доступностью \ No newline at end of file diff --git a/simple_backend/src/task_tracker/tasks.json b/simple_backend/src/task_tracker/tasks.json new file mode 100644 index 00000000..0e26473e --- /dev/null +++ b/simple_backend/src/task_tracker/tasks.json @@ -0,0 +1,14 @@ +[ + { + "id": 1, + "task": "Закончить простые бэки", + "status": "No" + }, + + { + "id": 2, + "task": "Хз", + "status": "Yes" + } + +] \ No newline at end of file From 031f8dc7ab802af1ee2e7d8857d405b95ef72eec Mon Sep 17 00:00:00 2001 From: ou Date: Tue, 28 Oct 2025 18:07:31 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D0=B4=D0=BD=D0=BE=D0=B9=20=D1=81=D1=82=D1=80=D0=BE=D1=87=D0=BA?= =?UTF-8?q?=D0=B5,=20=D0=BF=D0=BE=D0=BA=D0=B0=20=D0=BD=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 5a29197d..2f6faef2 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -49,7 +49,7 @@ def update_task(task_id: int, update_task: UpdateTask): if i['id'] == task_id: i['task'] = update_task.task i['status'] = update_task.status - save_tasks(i) + save_tasks(tasks) return {"success": True} raise HTTPException(status_code=404, detail = 'Задача не найдена') From 57ce7f2b65ffb50bdd82dd8ce29bc1ea7793cf9e Mon Sep 17 00:00:00 2001 From: ou Date: Tue, 28 Oct 2025 18:13:49 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=9D=D0=B0=D0=BF=D0=B8=D1=88=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D0=BE=D0=BC=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=20=D0=B2=20task?= =?UTF-8?q?=5Ftracker=20=D0=B8=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=20=D0=BA=D0=BE=D0=B4=20=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B0=20=D1=82=D0=B0=D0=BA,=20=D1=87=D1=82=D0=BE?= =?UTF-8?q?=D0=B1=D1=8B=20=D0=BE=D0=BD=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=81=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=20=D1=8D=D1=82=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 35 +++++++--------------- simple_backend/src/task_tracker/storage.py | 20 +++++++++++++ 2 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 simple_backend/src/task_tracker/storage.py diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 2f6faef2..c8eed7bb 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,26 +1,11 @@ from fastapi import FastAPI, HTTPException from pydantic import BaseModel -import os -import json +from storage import FileStorage app = FastAPI() -json_name = 'tasks.json' +storage = FileStorage() -def load_tasks(): - if not os.path.exists(json_name): - return [] - - try: - with open(json_name, 'r', encoding='utf-8') as file: - return json.load(file) - except (FileNotFoundError, json.JSONDecodeError): - return [] - - -def save_tasks(task_list): - with open(json_name, 'w', encoding='utf-8') as file: - json.dump(task_list, file) class CreateTask(BaseModel): task: str @@ -32,35 +17,35 @@ class UpdateTask(BaseModel): @app.get("/tasks", tags=['Вывод всех задач']) def get_tasks(): - return load_tasks() + return storage.load_tasks() @app.post("/tasks", tags=['Добавление задачи']) def create_task(new_task: CreateTask): - tasks = load_tasks() + tasks = storage.load_tasks() max_id = max(task['id'] for task in tasks) + 1 tasks.append({'id': max_id, 'task': new_task.task, 'status': new_task.status}) - save_tasks(tasks) + storage.save_tasks(tasks) return {"success": True} @app.put("/tasks/{task_id}", tags=['Обновление задачи']) def update_task(task_id: int, update_task: UpdateTask): - tasks = load_tasks() + tasks = storage.load_tasks() for i in tasks: if i['id'] == task_id: i['task'] = update_task.task i['status'] = update_task.status - save_tasks(tasks) + storage.save_tasks(tasks) return {"success": True} raise HTTPException(status_code=404, detail = 'Задача не найдена') @app.delete("/tasks/{task_id}", tags=['Удаление задачи']) def delete_task(task_id: int): - tasks = load_tasks() + tasks = storage.load_tasks() for i, task in enumerate(tasks): if task['id'] == task_id: tasks.pop(i) - save_tasks(tasks) - return {"success": True} + storage.save_tasks(tasks) + return {"success": True} raise HTTPException(status_code=404, detail = 'Задача не найдена') \ No newline at end of file diff --git a/simple_backend/src/task_tracker/storage.py b/simple_backend/src/task_tracker/storage.py new file mode 100644 index 00000000..afd14959 --- /dev/null +++ b/simple_backend/src/task_tracker/storage.py @@ -0,0 +1,20 @@ +import json +import os + +class FileStorage: + def __init__(self, filename='tasks.json'): + self.json_name = filename + + def load_tasks(self): + if not os.path.exists(self.json_name): + return [] + + try: + with open(self.json_name, 'r', encoding='utf-8') as file: + return json.load(file) + except (FileNotFoundError, json.JSONDecodeError): + return [] + + def save_tasks(self, task_list): + with open(self.json_name, 'w', encoding='utf-8') as file: + json.dump(task_list, file) \ No newline at end of file From 8b3a0a753ecbd9bd57826c5d78b85a05d39d0707 Mon Sep 17 00:00:00 2001 From: ou Date: Tue, 28 Oct 2025 23:36:54 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20back?= =?UTF-8?q?end=20-=20stateless?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/task_tracker/gist_storage.py | 52 +++++++++++++++++++ simple_backend/src/task_tracker/main.py | 7 ++- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 simple_backend/src/task_tracker/gist_storage.py diff --git a/simple_backend/src/task_tracker/gist_storage.py b/simple_backend/src/task_tracker/gist_storage.py new file mode 100644 index 00000000..3cbd496a --- /dev/null +++ b/simple_backend/src/task_tracker/gist_storage.py @@ -0,0 +1,52 @@ +import json +import requests +import os + +class GistStorage: + def __init__(self, filename='tasks.json'): + self.json_name = filename + self.gist_id = os.getenv('GIST_ID') + self.github_token = os.getenv('GITHUB_TOKEN') + + def load_tasks(self): + if not self.gist_id or not self.github_token: + return [] + + try: + headers = {"Authorization": f"token {self.github_token}"} + response = requests.get(f"https://api.github.com/gists/{self.gist_id}", headers=headers) + + if response.status_code == 200: + gist_data = response.json() + tasks_content = gist_data['files'][self.json_name]['content'] + return json.loads(tasks_content) + return [] + except: + return [] + + def save_tasks(self, task_list): + if not self.gist_id or not self.github_token: + return False + + try: + gist_data = { + "files": { + self.json_name: { + "content": json.dumps(task_list, indent=2, ensure_ascii=False) + } + } + } + + headers = { + "Authorization": f"token {self.github_token}", + "Content-Type": "application/json" + } + + response = requests.patch( + f"https://api.github.com/gists/{self.gist_id}", + headers=headers, + data=json.dumps(gist_data) + ) + return response.status_code == 200 + except: + return False \ No newline at end of file diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index c8eed7bb..fdc335a5 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,10 +1,13 @@ from fastapi import FastAPI, HTTPException from pydantic import BaseModel -from storage import FileStorage +from gist_storage import GistStorage app = FastAPI() -storage = FileStorage() +from dotenv import load_dotenv +load_dotenv() + +storage = GistStorage() class CreateTask(BaseModel): From 83a1083c1a7008ac27e6b3158b7b3599c17793ed Mon Sep 17 00:00:00 2001 From: ou Date: Tue, 28 Oct 2025 23:45:08 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D0=A1=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B3=D0=BE=D0=BD=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/readme.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/simple_backend/src/task_tracker/readme.md b/simple_backend/src/task_tracker/readme.md index 481a264c..b2b8a7a3 100644 --- a/simple_backend/src/task_tracker/readme.md +++ b/simple_backend/src/task_tracker/readme.md @@ -42,4 +42,22 @@ Минусы: - Зависимость от внешнего сервиса - Лимиты запросов -- Проблемы с доступностью \ No newline at end of file +- Проблемы с доступностью + +**Прочитайте что такое "состояние гонки" и напишите в readme файле о том, какие проблемы остались в бекенде на данном этапе проекта. Есть ли у вас какое-то решение этой проблемы?** + +При одновременных запросах возможна потеря данных: +- Два пользователя добавляют задачи → одна задача теряется +- Редактирование и удаление одновременно → повреждение данных +- Изменения статусов конфликтуют + +## Решение проблемы + +mb Реализовать оптимистичную блокировку с повторными попытками: + +def update_with_retry(task_id, update_data, max_attempts=3): + for attempt in range(max_attempts): + tasks = load_tasks() + if save_tasks(tasks): + return True + return False # \ No newline at end of file From db54af9798407abdb770aa36a2d13dfcc9c7c65f Mon Sep 17 00:00:00 2001 From: ou Date: Wed, 29 Oct 2025 22:08:29 +0300 Subject: [PATCH 7/8] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D1=83=20clo?= =?UTF-8?q?udflare?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/cloudflare.py | 46 +++++++++++++++++++ simple_backend/src/task_tracker/main.py | 26 +++++++---- 2 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 simple_backend/src/task_tracker/cloudflare.py diff --git a/simple_backend/src/task_tracker/cloudflare.py b/simple_backend/src/task_tracker/cloudflare.py new file mode 100644 index 00000000..f0b2b3e6 --- /dev/null +++ b/simple_backend/src/task_tracker/cloudflare.py @@ -0,0 +1,46 @@ +import requests +import os + +class CloudflareAI: + def __init__(self): + self.account_id = os.getenv('CLOUDFLARE_ACCOUNT_ID') + self.api_token = os.getenv('CLOUDFLARE_API_TOKEN') + self.model = '@cf/meta/llama-3-8b-instruct' + + def get_advice(self, task_text: str) -> str: + if not self.account_id or not self.api_token: + return "AI не настроен" + + try: + api_url = f"https://api.cloudflare.com/client/v4/accounts/{self.account_id}/ai/run/{self.model}" + + headers = { + "Authorization": f"Bearer {self.api_token}", + "Content-Type": "application/json" + } + + payload = { + "messages": [ + { + "role": "system", + "content": "Ты - полезный ассистент. Отвечай ТОЛЬКО на русском языке. Давай четкие, структурированные ответы с нумерованными шагами. Используй Markdown-разметку для форматирования." + }, + { + "role": "user", + "content": f"Дай подробные шаги для решения этой задачи на русском языке: {task_text}" + } + ] + } + + response = requests.post(api_url, headers=headers, json=payload, timeout=30) + + print(f"Cloudflare Status: {response.status_code}") + + if response.status_code == 200: + result = response.json() + return result.get('result', {}).get('response', 'Ответ получен') + else: + return f"Ошибка API: {response.status_code} - {response.text}" + + except Exception as e: + return f"Ошибка: {str(e)}" \ No newline at end of file diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index fdc335a5..8870d47a 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,14 +1,15 @@ from fastapi import FastAPI, HTTPException from pydantic import BaseModel from gist_storage import GistStorage - -app = FastAPI() - +from cloudflare import CloudflareAI from dotenv import load_dotenv + load_dotenv() -storage = GistStorage() +app = FastAPI() +storage = GistStorage() +ai_client = CloudflareAI() class CreateTask(BaseModel): task: str @@ -25,8 +26,17 @@ def get_tasks(): @app.post("/tasks", tags=['Добавление задачи']) def create_task(new_task: CreateTask): tasks = storage.load_tasks() - max_id = max(task['id'] for task in tasks) + 1 - tasks.append({'id': max_id, 'task': new_task.task, 'status': new_task.status}) + max_id = max(task['id'] for task in tasks) + 1 if tasks else 1 + + ai_advice = ai_client.get_advice(new_task.task) + enhanced_task = f"{new_task.task}\n\nСоветы по решению:\n{ai_advice}" + + tasks.append({ + 'id': max_id, + 'task': enhanced_task, + 'status': new_task.status + }) + storage.save_tasks(tasks) return {"success": True} @@ -40,7 +50,7 @@ def update_task(task_id: int, update_task: UpdateTask): storage.save_tasks(tasks) return {"success": True} - raise HTTPException(status_code=404, detail = 'Задача не найдена') + raise HTTPException(status_code=404, detail='Задача не найдена') @app.delete("/tasks/{task_id}", tags=['Удаление задачи']) def delete_task(task_id: int): @@ -51,4 +61,4 @@ def delete_task(task_id: int): storage.save_tasks(tasks) return {"success": True} - raise HTTPException(status_code=404, detail = 'Задача не найдена') \ No newline at end of file + raise HTTPException(status_code=404, detail='Задача не найдена') \ No newline at end of file From 3b0239e134d9b247177ea965907981e0c5971bb2 Mon Sep 17 00:00:00 2001 From: ou Date: Wed, 29 Oct 2025 22:18:51 +0300 Subject: [PATCH 8/8] =?UTF-8?q?4=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20BaseHTTPClient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/task_tracker/base_http_client.py | 35 +++++++++++++++++++ simple_backend/src/task_tracker/cloudflare.py | 26 ++++++++------ .../src/task_tracker/gist_storage.py | 30 +++++++++------- 3 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 simple_backend/src/task_tracker/base_http_client.py diff --git a/simple_backend/src/task_tracker/base_http_client.py b/simple_backend/src/task_tracker/base_http_client.py new file mode 100644 index 00000000..e17d2423 --- /dev/null +++ b/simple_backend/src/task_tracker/base_http_client.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod +import requests +import json + +class BaseHTTPClient(ABC): + def __init__(self): + self.base_url = self.get_base_url() + self.headers = self.get_headers() + + @abstractmethod + def get_base_url(self) -> str: + + pass + + @abstractmethod + def get_headers(self) -> dict: + + pass + + def make_request(self, method: str, endpoint: str = "", json_data: dict = None, data: str = None, timeout: int = 30): + + url = f"{self.base_url}{endpoint}" + + try: + response = requests.request( + method=method, + url=url, + headers=self.headers, + json=json_data, + data=data, + timeout=timeout + ) + return response + except Exception as e: + raise Exception(f"Request error: {str(e)}") \ No newline at end of file diff --git a/simple_backend/src/task_tracker/cloudflare.py b/simple_backend/src/task_tracker/cloudflare.py index f0b2b3e6..b089d5cd 100644 --- a/simple_backend/src/task_tracker/cloudflare.py +++ b/simple_backend/src/task_tracker/cloudflare.py @@ -1,29 +1,33 @@ -import requests import os +from base_http_client import BaseHTTPClient -class CloudflareAI: +class CloudflareAI(BaseHTTPClient): def __init__(self): self.account_id = os.getenv('CLOUDFLARE_ACCOUNT_ID') self.api_token = os.getenv('CLOUDFLARE_API_TOKEN') self.model = '@cf/meta/llama-3-8b-instruct' + super().__init__() + + def get_base_url(self) -> str: + return f"https://api.cloudflare.com/client/v4/accounts/{self.account_id}/ai/run/{self.model}" + + def get_headers(self) -> dict: + return { + "Authorization": f"Bearer {self.api_token}", + "Content-Type": "application/json" + } def get_advice(self, task_text: str) -> str: + """Получает советы по решению задачи от LLM""" if not self.account_id or not self.api_token: return "AI не настроен" try: - api_url = f"https://api.cloudflare.com/client/v4/accounts/{self.account_id}/ai/run/{self.model}" - - headers = { - "Authorization": f"Bearer {self.api_token}", - "Content-Type": "application/json" - } - payload = { "messages": [ { "role": "system", - "content": "Ты - полезный ассистент. Отвечай ТОЛЬКО на русском языке. Давай четкие, структурированные ответы с нумерованными шагами. Используй Markdown-разметку для форматирования." + "content": "Ты - полезный ассистент. Отвечай ТОЛЬКО на русском языке. Давай четкие, структурированные ответы с нумерованными шагами." }, { "role": "user", @@ -32,7 +36,7 @@ def get_advice(self, task_text: str) -> str: ] } - response = requests.post(api_url, headers=headers, json=payload, timeout=30) + response = self.make_request("POST", json_data=payload, timeout=30) print(f"Cloudflare Status: {response.status_code}") diff --git a/simple_backend/src/task_tracker/gist_storage.py b/simple_backend/src/task_tracker/gist_storage.py index 3cbd496a..79396e21 100644 --- a/simple_backend/src/task_tracker/gist_storage.py +++ b/simple_backend/src/task_tracker/gist_storage.py @@ -1,20 +1,29 @@ import json -import requests import os +from base_http_client import BaseHTTPClient -class GistStorage: +class GistStorage(BaseHTTPClient): def __init__(self, filename='tasks.json'): self.json_name = filename self.gist_id = os.getenv('GIST_ID') - self.github_token = os.getenv('GITHUB_TOKEN') + self.github_token = os.getenv('GITHUB_TOKEN') + super().__init__() + + def get_base_url(self) -> str: + return "https://api.github.com" + + def get_headers(self) -> dict: + return { + "Authorization": f"token {self.github_token}", + "Accept": "application/vnd.github.v3+json" + } def load_tasks(self): if not self.gist_id or not self.github_token: return [] try: - headers = {"Authorization": f"token {self.github_token}"} - response = requests.get(f"https://api.github.com/gists/{self.gist_id}", headers=headers) + response = self.make_request("GET", f"/gists/{self.gist_id}") if response.status_code == 200: gist_data = response.json() @@ -37,14 +46,9 @@ def save_tasks(self, task_list): } } - headers = { - "Authorization": f"token {self.github_token}", - "Content-Type": "application/json" - } - - response = requests.patch( - f"https://api.github.com/gists/{self.gist_id}", - headers=headers, + response = self.make_request( + "PATCH", + f"/gists/{self.gist_id}", data=json.dumps(gist_data) ) return response.status_code == 200