From a22a1a61eef8193354c0a4a914a2b6d5c5921ce9 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 16:35:57 +0300 Subject: [PATCH 01/10] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=202,=20=D0=B4=D0=BE=20=D0=BF=D0=BE=D0=B4=D0=B7=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D1=87.=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=B2=D1=81=D0=B5=204=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8,=20=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BE=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D0=B9=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=BC=D1=8F=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 29 ++++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 3db98d0d..a07789f1 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,19 +1,36 @@ from fastapi import FastAPI +from pydantic import BaseModel +from enum import Enum +import uuid app = FastAPI() +class Status(Enum): + OPEN = "OPEN" + IN_PROCESS = "IN_PROCESS" + ON_REVIEW = "ON_REVIEW" + CLOSED = "CLOSED" + +class Task(BaseModel): + status: Status + descr:str +tasks = {} @app.get("/tasks") def get_tasks(): - pass + return tasks @app.post("/tasks") -def create_task(task): - pass +def create_task(task:Task): + task_id = uuid.uuid4() + tasks[task_id] = task + return f"Task created with id {task_id}" @app.put("/tasks/{task_id}") -def update_task(task_id: int): - pass +def update_task(task_id: int, new_status: Status): + tasks[task_id].status = new_status + return "Task status changed succesfully" @app.delete("/tasks/{task_id}") def delete_task(task_id: int): - pass + tasks.pop(task_id) + return From fd0f9cd832a29bd18e0dbfd4395c7d109f679690 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 16:43:16 +0300 Subject: [PATCH 02/10] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B0=201.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20readme.md=20=D1=81=20=D0=BC=D0=B8=D0=BD=D1=83?= =?UTF-8?q?=D1=81=D0=B0=D0=BC=D0=B8=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=BF=D0=B0=D0=BC=D1=8F=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 simple_backend/src/task_tracker/readme.md diff --git a/simple_backend/src/task_tracker/readme.md b/simple_backend/src/task_tracker/readme.md new file mode 100644 index 00000000..c0e55178 --- /dev/null +++ b/simple_backend/src/task_tracker/readme.md @@ -0,0 +1,9 @@ +Хранение данных в оперативной памяти имеет ряд очевидных минусов. +1) Потеря данных при перезапуске. +Невозможно использовать в долгосрок, всё полностью уничтожается если сервер упадет. + +2) Ограниченность памяти +Оперативная память, как правило, значительно меньше по объёму чем долгосрочная. Хранить большую базу данных не удастся. + +3) Проблемы масштабирования +Нельзя легко переносить данные между серверами и разными инстансами запущенной программы. From 4b7640b2fb7e77a409c7f7a5a2dc7dff85ef3af9 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 17:33:42 +0300 Subject: [PATCH 03/10] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B0=202.=20=D0=A5=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=82=D0=B0=D1=81=D0=BA=D0=BE=D0=B2=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B8=D1=81?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=B8=D1=82=20=D0=B2=20json-=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/database.json | 6 +++ simple_backend/src/task_tracker/main.py | 48 ++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 simple_backend/src/task_tracker/database.json diff --git a/simple_backend/src/task_tracker/database.json b/simple_backend/src/task_tracker/database.json new file mode 100644 index 00000000..9ff1c066 --- /dev/null +++ b/simple_backend/src/task_tracker/database.json @@ -0,0 +1,6 @@ +{ + "8263106097683147186": { + "status": "ON_REVIEW", + "descr": "\u0421\u043f\u043b\u044f\u0441\u0430\u0442\u044c \u0434\u0436\u0438\u0433\u0443" + } +} \ 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 a07789f1..bd880947 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,7 +1,8 @@ from fastapi import FastAPI from pydantic import BaseModel from enum import Enum -import uuid +import json +import random app = FastAPI() class Status(Enum): @@ -14,23 +15,56 @@ class Task(BaseModel): status: Status descr:str -tasks = {} +def get_tasks_from_database(): + with open("database.json", "r") as db: + try: + tasks = json.load(db) + except json.JSONDecodeError: + tasks = {} + return tasks + +def dump_tasks_to_database(tasks): + with open("database.json","w") as db: + json.dump(tasks,db,indent=4) + @app.get("/tasks") def get_tasks(): - return tasks + with open("database.json","r") as db: + return get_tasks_from_database() @app.post("/tasks") def create_task(task:Task): - task_id = uuid.uuid4() + + task = task.model_dump(mode = "json") + task_id = abs(hash(random.randbytes(32))) + + tasks = get_tasks_from_database() + tasks[task_id] = task + + dump_tasks_to_database(tasks) + return f"Task created with id {task_id}" @app.put("/tasks/{task_id}") def update_task(task_id: int, new_status: Status): - tasks[task_id].status = new_status + + tasks = get_tasks_from_database() + try: + tasks[str(task_id)]['status'] = new_status.value + except KeyError: + return "No task with this ID" + + dump_tasks_to_database(tasks) + return "Task status changed succesfully" @app.delete("/tasks/{task_id}") def delete_task(task_id: int): - tasks.pop(task_id) - return + tasks = get_tasks_from_database() + try: + tasks.pop(str(task_id)) + except KeyError: + return "No task with this ID" + dump_tasks_to_database(tasks) + return "Task deleted succesfully" From 01704ae996333edd32344f2f016e3696ee25c4e2 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 18:38:08 +0300 Subject: [PATCH 04/10] =?UTF-8?q?=D0=9F=D0=BE=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=203.=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D1=91=D0=BD?= =?UTF-8?q?=20readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/readme.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/simple_backend/src/task_tracker/readme.md b/simple_backend/src/task_tracker/readme.md index c0e55178..495f4834 100644 --- a/simple_backend/src/task_tracker/readme.md +++ b/simple_backend/src/task_tracker/readme.md @@ -1,9 +1,11 @@ -Хранение данных в оперативной памяти имеет ряд очевидных минусов. -1) Потеря данных при перезапуске. -Невозможно использовать в долгосрок, всё полностью уничтожается если сервер упадет. +Теперь мы храним данные в json-файле. +Хранение базы данных теперь не занимает оперативную память. +Теперь мы можем взять базу данных и перенести её на другое устройство, запустить там таск-трекер и всё заработает. -2) Ограниченность памяти -Оперативная память, как правило, значительно меньше по объёму чем долгосрочная. Хранить большую базу данных не удастся. +Мы всё ещё храним состояния, но не в оперативной памяти, а на диске. -3) Проблемы масштабирования -Нельзя легко переносить данные между серверами и разными инстансами запущенной программы. + +Альтернативные хранилища: +СУБД: легко масштабируются, надежны, безопасны, но избыточны для тасктрекера +Облачные хранилища: доступны, легко интегрируются по АПИ, но данные находятся на чужом сервере, есть риск утечки +Блокчейн: полная неизменяемость, децентрализация, отказоустойчивость, однако очень высокие затраты на транзакции и низкая производительность From fdaca50bfc5a8142477ca3fc94616d4b1f0a12f6 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 18:40:49 +0300 Subject: [PATCH 05/10] =?UTF-8?q?=D0=9F=D0=BE=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=204.=20=D0=9C=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8=20=D0=B8=20=D1=87=D1=82?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=B7=20=D0=91=D0=94=20=D0=B8?= =?UTF-8?q?=D0=BD=D0=BA=D0=B0=D0=BF=D1=81=D1=83=D0=BB=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20Storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 48 +++++++++++++++---------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index bd880947..045fb04f 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -4,7 +4,6 @@ import json import random -app = FastAPI() class Status(Enum): OPEN = "OPEN" IN_PROCESS = "IN_PROCESS" @@ -15,22 +14,33 @@ class Task(BaseModel): status: Status descr:str -def get_tasks_from_database(): - with open("database.json", "r") as db: - try: - tasks = json.load(db) - except json.JSONDecodeError: - tasks = {} - return tasks +class Storage(): + __storage: str|None + def __init___(self, storage:str|None): + self.storage = storage + if not os.path.exists(self.storage): + with open(self.filename, "w") as f: + json.dump({}, f) + + def get_tasks_from_database(self): + with open(storage, "r") as db: + try: + tasks = json.load(db) + except json.JSONDecodeError: + tasks = {} + return tasks + + def dump_tasks_to_database(self,tasks): + with open(storage,"w") as db: + json.dump(tasks,db,indent=4) -def dump_tasks_to_database(tasks): - with open("database.json","w") as db: - json.dump(tasks,db,indent=4) +app = FastAPI() +storage = Storage("database.json") @app.get("/tasks") def get_tasks(): - with open("database.json","r") as db: - return get_tasks_from_database() + return storage.get_tasks_from_database() + @app.post("/tasks") def create_task(task:Task): @@ -38,33 +48,33 @@ def create_task(task:Task): task = task.model_dump(mode = "json") task_id = abs(hash(random.randbytes(32))) - tasks = get_tasks_from_database() + tasks = storage.get_tasks_from_database() tasks[task_id] = task - dump_tasks_to_database(tasks) + storage.dump_tasks_to_database(tasks) return f"Task created with id {task_id}" @app.put("/tasks/{task_id}") def update_task(task_id: int, new_status: Status): - tasks = get_tasks_from_database() + tasks = storage.get_tasks_from_database() try: tasks[str(task_id)]['status'] = new_status.value except KeyError: return "No task with this ID" - dump_tasks_to_database(tasks) + storage.dump_tasks_to_database(tasks) return "Task status changed succesfully" @app.delete("/tasks/{task_id}") def delete_task(task_id: int): - tasks = get_tasks_from_database() + tasks = storage.get_tasks_from_database() try: tasks.pop(str(task_id)) except KeyError: return "No task with this ID" - dump_tasks_to_database(tasks) + storage.dump_tasks_to_database(tasks) return "Task deleted succesfully" From afe861cb759f4105de7bead4ae0af135d7fdf5d0 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 18:51:37 +0300 Subject: [PATCH 06/10] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 simple_backend/src/task_tracker/.gitignore diff --git a/simple_backend/src/task_tracker/.gitignore b/simple_backend/src/task_tracker/.gitignore new file mode 100644 index 00000000..4c49bd78 --- /dev/null +++ b/simple_backend/src/task_tracker/.gitignore @@ -0,0 +1 @@ +.env From 458b70d130743132a38f4808701207ce231a94dd Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 20:12:06 +0300 Subject: [PATCH 07/10] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B0=205.=20=D0=A2=D0=B5=D0=BF=D0=B5=D1=80=D1=8C?= =?UTF-8?q?=20json=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D0=B8=D0=B9=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B5=D1=80=20github=20gist.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 58 ++++++++++++++++++------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 045fb04f..f61e9c07 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,8 +1,11 @@ from fastapi import FastAPI from pydantic import BaseModel from enum import Enum +from dotenv import load_dotenv import json import random +import requests +import os class Status(Enum): OPEN = "OPEN" @@ -14,28 +17,51 @@ class Task(BaseModel): status: Status descr:str -class Storage(): - __storage: str|None - def __init___(self, storage:str|None): - self.storage = storage - if not os.path.exists(self.storage): - with open(self.filename, "w") as f: - json.dump({}, f) +class Storage: + __TOKEN: str + __GIST_ID: str + __GIST_FILENAME: str + + def __init__(self, TOKEN: str, GIST_ID: str, GIST_FILENAME: str): + self.__TOKEN = TOKEN + self.__GIST_ID = GIST_ID + self.__GIST_FILENAME = GIST_FILENAME def get_tasks_from_database(self): - with open(storage, "r") as db: - try: - tasks = json.load(db) - except json.JSONDecodeError: - tasks = {} - return tasks + + url = f"https://api.github.com/gists/{self.__GIST_ID}" + headers = { + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28" + } + + response = requests.get(url, headers=headers) + return json.loads(response.json()['files'][self.__GIST_FILENAME]['content']) def dump_tasks_to_database(self,tasks): - with open(storage,"w") as db: - json.dump(tasks,db,indent=4) + url = f"https://api.github.com/gists/{self.__GIST_ID}" + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {self.__TOKEN}", + "X-GitHub-Api-Version": "2022-11-28" + } + data = { + "files": { + f"{self.__GIST_FILENAME}": { + "content": json.dumps(tasks,ensure_ascii=False) + } + } + } + response = requests.patch(url, headers=headers, json=data) + print(response) + + app = FastAPI() -storage = Storage("database.json") +load_dotenv() +storage = Storage(os.getenv('TOKEN'), os.getenv('GIST_ID'), os.getenv('GIST_FILENAME')) + + @app.get("/tasks") def get_tasks(): From e58a5d4f55866d76e9b3857c5e5a94aec3e93221 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 20:26:45 +0300 Subject: [PATCH 08/10] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B0=206.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/readme.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/simple_backend/src/task_tracker/readme.md b/simple_backend/src/task_tracker/readme.md index 495f4834..ff283d90 100644 --- a/simple_backend/src/task_tracker/readme.md +++ b/simple_backend/src/task_tracker/readme.md @@ -1,11 +1,8 @@ -Теперь мы храним данные в json-файле. -Хранение базы данных теперь не занимает оперативную память. -Теперь мы можем взять базу данных и перенести её на другое устройство, запустить там таск-трекер и всё заработает. +Состояние гонки -- оно же race condition -- может возникать в данном случае, если несколько клиентов одновременно или почти одновременно отправляют заявки в gist, либо если один клиент посылает запросы с очень высокой частотой. +Это может привести к откату изменений, внесённых одними клиентами, запросом, сделанным другим клиентом. +Это возникает из за того, что работа с json-файлом выглядит как загрузка и перезапись всего файла. +Это можно решить, отказавшись от json в пользу СУБД +Если использование json необходимо, следует сделать промежуточный исполняемый файл, который будет лежать на одном сервере с json, хранить очередь заявок на внесение изменений и перезаписывать файл согласно этой очереди. +Можно также попробовать внедрить блокировку по ETag -Мы всё ещё храним состояния, но не в оперативной памяти, а на диске. - - -Альтернативные хранилища: -СУБД: легко масштабируются, надежны, безопасны, но избыточны для тасктрекера -Облачные хранилища: доступны, легко интегрируются по АПИ, но данные находятся на чужом сервере, есть риск утечки -Блокчейн: полная неизменяемость, децентрализация, отказоустойчивость, однако очень высокие затраты на транзакции и низкая производительность +Помимо того, потенциально, могут возникать коллизии хэшей, однако их шансы незначительны для объёма тасок измеряемых миллионами. Риски существенны только при миллиардах тасок. From 5e5294204782503ab9281f17c39946076c389a91 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 22:30:57 +0300 Subject: [PATCH 09/10] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=203.=20=D0=9A=D0=BB=D0=B0=D1=83=D0=B4=D1=84=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0,=20=D1=82=D0=B0=D0=BA=20=D1=87=D1=82=D0=BE=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BB=20?= =?UTF-8?q?LLM=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20OpenRouter=20API,=20?= =?UTF-8?q?=D0=BD=D0=BE,=20=D0=BF=D0=BE=D0=BB=D0=B0=D0=B3=D0=B0=D1=8E,=20?= =?UTF-8?q?=D1=81=D1=83=D1=82=D1=8C=20=D0=BE=D0=B4=D0=B8=D0=BD=D0=B0=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 28 +++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index f61e9c07..f866ebe6 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -2,6 +2,7 @@ from pydantic import BaseModel from enum import Enum from dotenv import load_dotenv +from openai import OpenAI import json import random import requests @@ -15,7 +16,8 @@ class Status(Enum): class Task(BaseModel): status: Status - descr:str + descr: str + ai_solve_idea: str = "" class Storage: __TOKEN: str @@ -61,8 +63,10 @@ def dump_tasks_to_database(self,tasks): load_dotenv() storage = Storage(os.getenv('TOKEN'), os.getenv('GIST_ID'), os.getenv('GIST_FILENAME')) - - +client = OpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv('OPENROUTER_API_KEY'), +) @app.get("/tasks") def get_tasks(): return storage.get_tasks_from_database() @@ -75,7 +79,23 @@ def create_task(task:Task): task_id = abs(hash(random.randbytes(32))) tasks = storage.get_tasks_from_database() - + + + completion = client.chat.completions.create( + model="deepseek/deepseek-r1:free", + messages=[ + { + "role": "system", + "content": "You are an assistaint. User will send you task, and you must give user an advice about how to solve this task. Answer in russian." + }, + { + "role": "user", + "content": task['descr'] + } + ] + ) + print(completion.choices[0].message.content) + task['ai_solve_idea'] = completion.choices[0].message.content tasks[task_id] = task storage.dump_tasks_to_database(tasks) From 836d8471e3e03e015d50ba99800cc07b99760d02 Mon Sep 17 00:00:00 2001 From: SailorLekalo Date: Fri, 1 Aug 2025 23:12:25 +0300 Subject: [PATCH 10/10] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=204.=20=D0=9F=D1=80=D0=B8=D1=88=D0=BB=D0=BE=D1=81=D1=8C?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=BA=20LLM=20=D1=81?= =?UTF-8?q?=20openai=20=D0=BD=D0=B0=20requests.=20=D0=9A=D0=B0=D0=BA=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=BC=D0=BD=D0=B5=20--=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=20=D1=85=D1=83=D0=B6=D0=B5,=20=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20=D0=B2=20?= =?UTF-8?q?=D1=8D=D1=82=D0=BE=D0=BC=20=D0=B1=D1=8B=D0=BB=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=BA=D0=BE=D0=B9-=D1=82=D0=BE=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=81=D0=BC=D1=8B=D1=81=D0=BB.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 116 ++++++++++++++++-------- 1 file changed, 79 insertions(+), 37 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index f866ebe6..f10524f9 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -3,6 +3,7 @@ from enum import Enum from dotenv import load_dotenv from openai import OpenAI +from abc import ABC, abstractmethod import json import random import requests @@ -19,54 +20,96 @@ class Task(BaseModel): descr: str ai_solve_idea: str = "" -class Storage: - __TOKEN: str - __GIST_ID: str - __GIST_FILENAME: str - - def __init__(self, TOKEN: str, GIST_ID: str, GIST_FILENAME: str): - self.__TOKEN = TOKEN - self.__GIST_ID = GIST_ID - self.__GIST_FILENAME = GIST_FILENAME - - def get_tasks_from_database(self): - - url = f"https://api.github.com/gists/{self.__GIST_ID}" + +class BaseHTTPClient(ABC): + def __init__(self, base_url: str, headers: dict): + self.base_url = base_url + self.headers = headers + + @abstractmethod + def _send_request(self, method: str, endpoint: str, **kwargs): + pass + + def get(self, endpoint: str, params: dict = None): + return self._send_request("GET", endpoint, params=params) + + def post(self, endpoint: str, json: dict = None): + return self._send_request("POST", endpoint, json=json) + + def patch(self, endpoint: str, json: dict = None): + return self._send_request("PATCH", endpoint, json=json) + +class GistClient(BaseHTTPClient): + def __init__(self, token: str): + base_url = "https://api.github.com" headers = { "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", "X-GitHub-Api-Version": "2022-11-28" } + super().__init__(base_url, headers) - response = requests.get(url, headers=headers) - return json.loads(response.json()['files'][self.__GIST_FILENAME]['content']) + def _send_request(self, method: str, endpoint: str, **kwargs): + response = requests.request( + method, + f"{self.base_url}{endpoint}", + headers=self.headers, + **kwargs + ) + response.raise_for_status() + return response.json() - def dump_tasks_to_database(self,tasks): - url = f"https://api.github.com/gists/{self.__GIST_ID}" +class OpenRouterClient(BaseHTTPClient): + def __init__(self, api_key: str, base_url: str): headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {self.__TOKEN}", - "X-GitHub-Api-Version": "2022-11-28" + "Authorization": f"Bearer {api_key}" } - data = { - "files": { - f"{self.__GIST_FILENAME}": { - "content": json.dumps(tasks,ensure_ascii=False) - } - } - } - response = requests.patch(url, headers=headers, json=data) - print(response) - + super().__init__(base_url, headers) + + def _send_request(self, method: str, endpoint: str, **kwargs): + response = requests.request( + method, + f"{self.base_url}{endpoint}", + headers=self.headers, + **kwargs + ) + response.raise_for_status() + return response.json() + + def create_chat_completion(self, model: str, messages: list): + return self.post( + "/chat/completions", + json={"model": model, "messages": messages} + ) + +class Storage: + def __init__(self, client: GistClient, gist_id: str, filename: str): + self.client = client + self.gist_id = gist_id + self.filename = filename + + def get_tasks_from_database(self): + data = self.client.get(f"/gists/{self.gist_id}") + return json.loads(data['files'][self.filename]['content']) + + def dump_tasks_to_database(self, tasks): + self.client.patch( + f"/gists/{self.gist_id}", + json={"files": {self.filename: {"content": json.dumps(tasks)}}} + ) app = FastAPI() load_dotenv() -storage = Storage(os.getenv('TOKEN'), os.getenv('GIST_ID'), os.getenv('GIST_FILENAME')) -client = OpenAI( - base_url="https://openrouter.ai/api/v1", - api_key=os.getenv('OPENROUTER_API_KEY'), +gist_client = GistClient(os.getenv('TOKEN')) +openrouter_client = OpenRouterClient( + api_key = os.getenv('OPENROUTER_API_KEY'), + base_url="https://openrouter.ai/api/v1" ) +storage = Storage(gist_client, os.getenv('GIST_ID'), os.getenv('GIST_FILENAME')) + + @app.get("/tasks") def get_tasks(): return storage.get_tasks_from_database() @@ -81,7 +124,7 @@ def create_task(task:Task): tasks = storage.get_tasks_from_database() - completion = client.chat.completions.create( + completion = openrouter_client.create_chat_completion( model="deepseek/deepseek-r1:free", messages=[ { @@ -94,8 +137,7 @@ def create_task(task:Task): } ] ) - print(completion.choices[0].message.content) - task['ai_solve_idea'] = completion.choices[0].message.content + task['ai_solve_idea'] = completion['choices'][0]['message']['content'] tasks[task_id] = task storage.dump_tasks_to_database(tasks)