From d4c7fceb050423794915eb43a7b1d15de891135d Mon Sep 17 00:00:00 2001 From: Anton Shcherbak Date: Tue, 19 Nov 2024 22:14:00 +0300 Subject: [PATCH 01/27] feat: create conflicts --- .../src/main.py" | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git "a/\320\243\321\200\320\276\320\272 1: Git/src/main.py" "b/\320\243\321\200\320\276\320\272 1: Git/src/main.py" index 13580259..377a73b2 100644 --- "a/\320\243\321\200\320\276\320\272 1: Git/src/main.py" +++ "b/\320\243\321\200\320\276\320\272 1: Git/src/main.py" @@ -22,6 +22,22 @@ def __str__(self): return f"Customer: {self.name}, Membership: {self.membership}" +class GroupOrder("Order"): + def __init__(self, customers): + super().__init__(customer=None) # Групповой заказ не привязан к одному клиенту + self.customers = customers + + def split_bill(self): + if not self.customers: + raise ValueError("Нет клиентов для разделения счета.") + total = self.final_total() + return total / len(self.customers) + + def __str__(self): + customer_list = ", ".join([customer.name for customer in self.customers]) + dish_list = "\n".join([str(dish) for dish in self.dishes]) + return f"Group Order for {customer_list}:\n{dish_list}\nTotal: ${self.final_total():.2f}" + class Order: TAX_RATE = 0.08 # 8% налог SERVICE_CHARGE = 0.05 # 5% сервисный сбор @@ -60,23 +76,6 @@ def __str__(self): return f"Order for {self.customer.name}:\n{dish_list}\nTotal: ${self.final_total():.2f}" -class GroupOrder(Order): - def __init__(self, customers): - super().__init__(customer=None) # Групповой заказ не привязан к одному клиенту - self.customers = customers - - def split_bill(self): - if not self.customers: - raise ValueError("Нет клиентов для разделения счета.") - total = self.final_total() - return total / len(self.customers) - - def __str__(self): - customer_list = ", ".join([customer.name for customer in self.customers]) - dish_list = "\n".join([str(dish) for dish in self.dishes]) - return f"Group Order for {customer_list}:\n{dish_list}\nTotal: ${self.final_total():.2f}" - - # Пример использования # Создаем блюда From 1ece50e5412787288409cd8cc18d38b2fbdb1ba0 Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 27 Feb 2025 23:32:06 +0300 Subject: [PATCH 02/27] feat: changes --- git/src/main.py | 250 +++++++++++++++++++++++++----------------------- 1 file changed, 132 insertions(+), 118 deletions(-) diff --git a/git/src/main.py b/git/src/main.py index 8a11b44b..ca0839c3 100644 --- a/git/src/main.py +++ b/git/src/main.py @@ -1,121 +1,135 @@ +import json +import os + +def load_books(filename='library.json'): + """ + Загрузка списка книг из JSON-файла. + Возвращает список книг (каждая книга - это словарь). + """ + if not os.path.isfile(filename): + return [] + with open(filename, 'r', encoding='utf-8') as file: + try: + return json.load(file) + except json.JSONDecodeError: + return [] + +def saving_books(books, filename='library.json'): + """ + Сохранение списка книг в JSON-файл. + """ + with open(filename, 'w', encoding='utf-8') as file: + json.dump(books, file, ensure_ascii=False, indent=4) + +def list_books(books): + """ + Возвращает строку со списком всех книг. + """ + if not books: + return "Библиотека пуста." + result_lines = [] + for idx, book in enumerate(books, start=1): + result_lines.append(f"{idx}. {book['title']} | {book['author']} | {book['year']}") + return "\n".join(result_lines) + +def add_book(books, title, author, year): + """ + Принимает текущий список книг и данные о новой книге. + Возвращает новый список, в котором добавлена новая книга. + """ + new_book = { + 'title': title, + 'author': author, + 'year': year + } + # Создаём НОВЫЙ список, добавляя new_book + return books + [new_book] + +def remove_book(books, title): + """ + Принимает текущий список книг и название книги для удаления. + Возвращает новый список без книги, у которой совпадает название. + """ + # Фильтруем список: оставляем только те книги, у которых название не совпадает с переданным + return [book for book in books if book['title'].lower() != title.lower()] + +def search_books(books, keyword): + """ + Поиск книг по ключевому слову (ищется в названии и авторе). + Возвращает отфильтрованный список. + """ + keyword_lower = keyword.lower() + return [ + book for book in books + if keyword_lower in book['title'].lower() or keyword_lower in book['author'].lower() + ] + +def main(): + """ + Точка входа в программу: здесь мы загружаем книги, + показываем меню и обрабатываем ввод пользователя. + """ + books = load_books() # Загрузили список книг из JSON + + while True: + print("\n=== Управление онлайн-библиотекой ===") + print("1. Показать все книги") + print("2. Добавить книгу") + print("3. Удалить книгу") + print("4. Поиск книг") + print("5. Выйти") + + choice = input("Выберите действие (1-5): ").strip() + + if choice == '1': + print("\nСписок книг:") + print(list_books(books)) + + elif choice == '2': + print("\nДобавление новой книги:") + title = input("Введите название: ").strip() + author = input("Введите автора: ").strip() + year = input("Введите год издания: ").strip() + + # Получаем новый список с добавленной книгой + new_books = add_book(books, title, author, year) + books = new_books # Обновляем переменную, чтобы сохранить изменения + saving_books(books) # Сразу сохраняем в файл + print("Книга добавлена!") + + elif choice == '3': + print("\nУдаление книги:") + title_to_remove = input("Введите название книги, которую хотите удалить: ").strip() + + new_books = remove_book(books, title_to_remove) + if len(new_books) > len(books): + books = new_books + saving_books(books) + print("Книга удалена!") + else: + print("Книга с таким названием не найдена.") + + elif choice == '4': + print("\nПоиск книг:") + keyword = input("Введите ключевое слово для поиска (в названии или авторе): ").strip() + found_books = search_books(books, keyword) + if found_books: + print("\nНайденные книги:") + print(list_books(found_books)) + else: + print("Ничего не найдено.") + + elif choice == '6': + print("Выход из программы.") + break -class GroupOrder("Order"): - def __init__(self, customers): - super().__init__(customer=None) # Групповой заказ не привязан к одному клиенту - self.customers = customers - - def split_bill(self): - if not self.customers: - raise ValueError("Нет клиентов для разделения счета.") - total = self.final_total() - return total / len(self.customers) - - def __str__(self): - customer_list = ", ".join([customer.name for customer in self.customers]) - dish_list = "\n".join([str(dish) for dish in self.dishes]) - return f"Group Order for {customer_list}:\n{dish_list}\nTotal: ${self.final_total():.2f}" - -class Order: - TAX_RATE = 0.08 # 8% налог - SERVICE_CHARGE = 0.05 # 5% сервисный сбор - - def __init__(self, customer): - self.customer = customer - self.dishes = [] - - def add_dish(self, dish): - if isinstance(dish, Dish): - self.dishes.append(dish) else: - raise ValueError("Можно добавлять только объекты класса Dish.") + print("Некорректный ввод. Попробуйте ещё раз.") - def remove_dish(self, dish): - if dish in self.dishes: - self.dishes.remove(dish) - else: - raise ValueError("Такого блюда нет в заказе.") - - def calculate_total(self): - return sum(dish.price for dish in self.dishes) - - def apply_discount(self): - discount_rate = self.customer.get_discount() / 100 - return self.calculate_total() * (1 - discount_rate) - - def final_total(self): - total_after_discount = self.apply_discount() - total_with_tax = total_after_discount * (1 + Order.TAX_RATE) - final_total = total_with_tax * (1 + Order.SERVICE_CHARGE) - return final_total - - def __str__(self): - dish_list = "\n".join([str(dish) for dish in self.dishes]) - return f"Order for {self.customer.name}:\n{dish_list}\nTotal: ${self.final_total():.2f}" - - -class GroupOrder(Order): - def __init__(self, customers): - super().__init__(customer=None) # Групповой заказ не привязан к одному клиенту - self.customers = customers - - def split_bill(self): - if not self.customers: - raise ValueError("Нет клиентов для разделения счета.") - total = self.final_total() - return total / len(self.customers) - - def __str__(self): - customer_list = ", ".join([customer.name for customer in self.customers]) - dish_list = "\n".join([str(dish) for dish in self.dishes]) - return f"Group Order for {customer_list}:\n{dish_list}\nTotal: ${self.final_total():.2f}" - -class Dish: - def __init__(self, name, price, category): - self.name = name - self.price = price - self.category = category - - def __str__(self): - return f"Dish: {self.name}, Category: {self.category}, Price: ${self.price:.2f}" - - -class Customer: - def __init__(self, name, membership="Regular"): - self.name = name - self.membership = membership - - def get_discount(self): - if self.membership == "VIP": - return 10 # VIP клиенты получают 10% скидки - return 0 # Обычные клиенты не получают скидки - - def __str__(self): - return f"Customer: {self.name}, Membership: {self.membership}" - -# Пример использования - -# Создаем блюда -pizza = Dish("Pizza", 12, "Main Course") -ice_cream = Dish("Ice Cream", 5, "Dessert") -coffee = Dish("Coffee", 3, "Drink") - -# Создаем клиентов -regular_customer = Customer("Alice", "Regular") -vip_customer = Customer("Bob", "VIP") - -# Индивидуальный заказ -order1 = Order(regular_customer) -order1.add_dish(pizza) -order1.add_dish(ice_cream) - -print(order1) # Вывод информации о заказе -print(f"Final Total: ${order1.final_total():.2f}") # Итоговая стоимость - -# Групповой заказ -group_order = GroupOrder([regular_customer, vip_customer]) -group_order.add_dish(pizza) -group_order.add_dish(ice_cream) -group_order.add_dish(coffee) - -print(group_order) # Вывод информации о групповом заказе -print(f"Split Bill: ${group_order.split_bill():.2f} per person") # Стоимость на каждого \ No newline at end of file + + + + + +if __name__ == "__main__": + main() \ No newline at end of file From 0eba75a0472781bf8c70cc82df7dc14a54aad688 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 15:34:39 +0300 Subject: [PATCH 03/27] Task1 --- simple_backend/src/task_tracker/main.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 simple_backend/src/task_tracker/main.py diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py new file mode 100644 index 00000000..0301c7c5 --- /dev/null +++ b/simple_backend/src/task_tracker/main.py @@ -0,0 +1,20 @@ +'''backend''' +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/tasks") +def get_tasks(): + pass + +@app.post("/tasks") +def create_task(task): + pass + +@app.put("/tasks/{task_id}") +def update_task(task_id: int): + pass + +@app.delete("/tasks/{task_id}") +def delete_task(task_id: int): + pass From 169e70f9fd82a61593b8b40357bb329f6378e223 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 16:23:37 +0300 Subject: [PATCH 04/27] task2 backend with state storage --- simple_backend/src/task_tracker/main.py | 37 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 0301c7c5..178f03da 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,20 +1,35 @@ '''backend''' -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException app = FastAPI() - +tasks = [] +task_id_counter = 1 +# return tasks list @app.get("/tasks") def get_tasks(): - pass - + return tasks +#create a new task with id, name, and status @app.post("/tasks") -def create_task(task): - pass - +def create_task(name: str, condition: str = 'new'): + global task_id_counter + task = {'id': task_id_counter, 'name': name, 'condition': condition} + tasks.append(task) + task_id_counter += 1 + return task +#update task @app.put("/tasks/{task_id}") -def update_task(task_id: int): - pass - +def update_task(task_id: int, name: str, condition: str): + for task in tasks: + if task['id'] == task_id: + task['name'] = name + task['condition'] = condition + return task + raise HTTPException(status_code = 404, detail = 'task not found') +#delete task @app.delete("/tasks/{task_id}") def delete_task(task_id: int): - pass + for task in tasks: + if task['id'] == task_id: + tasks.remove(task) + return 'task deleted' + \ No newline at end of file From 269a8a71a862dc48a44ccc73835555a80229a55a Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 16:39:03 +0300 Subject: [PATCH 05/27] storage class with json --- simple_backend/src/task_tracker/main.py | 19 ++++++++++++------- simple_backend/src/task_tracker/storage.py | 13 +++++++++++++ simple_backend/src/task_tracker/tasks.json | 1 + 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 simple_backend/src/task_tracker/storage.py 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 178f03da..403953a5 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,35 +1,40 @@ '''backend''' from fastapi import FastAPI, HTTPException - +from storage import TaskStorage app = FastAPI() -tasks = [] -task_id_counter = 1 +storage = TaskStorage() # return tasks list @app.get("/tasks") def get_tasks(): - return tasks + return storage.load() #create a new task with id, name, and status @app.post("/tasks") def create_task(name: str, condition: str = 'new'): - global task_id_counter - task = {'id': task_id_counter, 'name': name, 'condition': condition} + tasks = storage.load() + new_id = len(tasks) + 1 + task = {'id': new_id, 'name': name, 'condition': condition} tasks.append(task) - task_id_counter += 1 + storage.save(tasks) return task #update task @app.put("/tasks/{task_id}") def update_task(task_id: int, name: str, condition: str): + tasks = storage.load() for task in tasks: if task['id'] == task_id: task['name'] = name task['condition'] = condition + storage.save(tasks) return task raise HTTPException(status_code = 404, detail = 'task not found') #delete task @app.delete("/tasks/{task_id}") def delete_task(task_id: int): + tasks = storage.load() for task in tasks: if task['id'] == task_id: tasks.remove(task) + storage.save(tasks) return 'task deleted' + raise HTTPException(status_code = 404, detail = 'task not found') \ 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..ce237692 --- /dev/null +++ b/simple_backend/src/task_tracker/storage.py @@ -0,0 +1,13 @@ +import json +import os +class TaskStorage: + def __init__(self, filename: str = 'tasks.json'): + self.file = filename + if not os.path.exists(self.file): # if file does not exist, create file with empty list + self.save([]) + def load(self): + with open(self.file, 'r', encoding = 'utf-8') as f: + return json.load(f) + def save(self, tasks): + with open(self.file, 'w', encoding = 'utf-8') as f: + json.dump(tasks, f) \ 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..80c933c8 --- /dev/null +++ b/simple_backend/src/task_tracker/tasks.json @@ -0,0 +1 @@ +[{"id": 1, "name": "finaltask", "condition": "updated"}, {"id": 2, "name": "totaltask", "condition": "updated"}, {"id": 3, "name": "Task3", "condition": "new"}] \ No newline at end of file From dc061538269a54b358cf77000e9bae0affba3240 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 18:34:59 +0300 Subject: [PATCH 06/27] Add files via upload --- simple_backend/src/task_tracker/.env | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 simple_backend/src/task_tracker/.env diff --git a/simple_backend/src/task_tracker/.env b/simple_backend/src/task_tracker/.env new file mode 100644 index 00000000..7ca83ca8 --- /dev/null +++ b/simple_backend/src/task_tracker/.env @@ -0,0 +1,2 @@ +GITHUB_TOKEN=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 +GIST_ID=69fc2b19cd06bf212c1107481b1bad6f From efd0bb46723c80cf86d3b6bef56771a02f51f982 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 18:36:41 +0300 Subject: [PATCH 07/27] storage gist add --- simple_backend/src/task_tracker/main.py | 7 ++- .../src/task_tracker/storage_gist.py | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 simple_backend/src/task_tracker/storage_gist.py diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 403953a5..6209f4dd 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,8 +1,8 @@ '''backend''' from fastapi import FastAPI, HTTPException -from storage import TaskStorage +from storage_gist import GistStorage app = FastAPI() -storage = TaskStorage() +storage = GistStorage() # return tasks list @app.get("/tasks") def get_tasks(): @@ -36,5 +36,4 @@ def delete_task(task_id: int): tasks.remove(task) storage.save(tasks) return 'task deleted' - raise HTTPException(status_code = 404, detail = 'task not found') - \ No newline at end of file + raise HTTPException(status_code = 404, detail = 'task not found') \ No newline at end of file diff --git a/simple_backend/src/task_tracker/storage_gist.py b/simple_backend/src/task_tracker/storage_gist.py new file mode 100644 index 00000000..6f548389 --- /dev/null +++ b/simple_backend/src/task_tracker/storage_gist.py @@ -0,0 +1,45 @@ +import os +import requests +import json +from dotenv import load_dotenv +load_dotenv() +GITHUB_API = 'https://api.github.com' +class GistStorage: + def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): + self.token: str = token or os.getenv('GITHUB_TOKEN') + self.gist_id: str = gist_id or os.getenv('GIST_ID') + self.filename: str = filename + if not self.token or not self.gist_id: + raise RuntimeError('GITHUB_TOKEN and GIST_ID must be given') + self.headers: dict = { + 'Authorization': f'token {self.token}', + 'Accept': 'application/vnd.github+json' + } + + def _gist_url(self) -> str: + return f'{GITHUB_API}/gists/{self.gist_id}' + # loading task list from gist + def load(self) -> list[dict]: + r = requests.get(self._gist_url(), headers = self.headers, timeout = 10) + r.raise_for_status() + gist_data: dict = r.json() + files: dict = gist_data.get('files', {}) + if self.filename not in files: + return [] + content: str = files[self.filename]['content'] + try: + data: dict = json.loads(content) + return data.get('tasks', []) + except json.JSONDecodeError: + return [] + # saving task list on gist + def save(self, tasks: list[dict]) -> None: + body: dict = { + 'files': { + self.filename: { + 'content': json.dumps({'tasks': tasks}) + } + } + } + r = requests.patch(self._gist_url(), headers = self.headers, json = body, timeout = 10) + r.raise_for_status() From 073cc99665c6b236f7734575b8a766b76274a43e Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 19:12:53 +0300 Subject: [PATCH 08/27] Delete simple_backend/src/task_tracker/.env --- simple_backend/src/task_tracker/.env | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 simple_backend/src/task_tracker/.env diff --git a/simple_backend/src/task_tracker/.env b/simple_backend/src/task_tracker/.env deleted file mode 100644 index 7ca83ca8..00000000 --- a/simple_backend/src/task_tracker/.env +++ /dev/null @@ -1,2 +0,0 @@ -GITHUB_TOKEN=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 -GIST_ID=69fc2b19cd06bf212c1107481b1bad6f From cafa301bea0a62f28813535a5f7f2830bc254a17 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 19:55:59 +0300 Subject: [PATCH 09/27] Delete simple_backend/src/task_tracker/tasks.json --- simple_backend/src/task_tracker/tasks.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 simple_backend/src/task_tracker/tasks.json diff --git a/simple_backend/src/task_tracker/tasks.json b/simple_backend/src/task_tracker/tasks.json deleted file mode 100644 index 80c933c8..00000000 --- a/simple_backend/src/task_tracker/tasks.json +++ /dev/null @@ -1 +0,0 @@ -[{"id": 1, "name": "finaltask", "condition": "updated"}, {"id": 2, "name": "totaltask", "condition": "updated"}, {"id": 3, "name": "Task3", "condition": "new"}] \ No newline at end of file From 128e409943ea157f9f70f04c3423bc31fb8a20f0 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 19:58:39 +0300 Subject: [PATCH 10/27] Delete simple_backend/src/task_tracker/main.py --- simple_backend/src/task_tracker/main.py | 39 ------------------------- 1 file changed, 39 deletions(-) delete mode 100644 simple_backend/src/task_tracker/main.py diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py deleted file mode 100644 index 6209f4dd..00000000 --- a/simple_backend/src/task_tracker/main.py +++ /dev/null @@ -1,39 +0,0 @@ -'''backend''' -from fastapi import FastAPI, HTTPException -from storage_gist import GistStorage -app = FastAPI() -storage = GistStorage() -# return tasks list -@app.get("/tasks") -def get_tasks(): - return storage.load() -#create a new task with id, name, and status -@app.post("/tasks") -def create_task(name: str, condition: str = 'new'): - tasks = storage.load() - new_id = len(tasks) + 1 - task = {'id': new_id, 'name': name, 'condition': condition} - tasks.append(task) - storage.save(tasks) - return task -#update task -@app.put("/tasks/{task_id}") -def update_task(task_id: int, name: str, condition: str): - tasks = storage.load() - for task in tasks: - if task['id'] == task_id: - task['name'] = name - task['condition'] = condition - storage.save(tasks) - return task - raise HTTPException(status_code = 404, detail = 'task not found') -#delete task -@app.delete("/tasks/{task_id}") -def delete_task(task_id: int): - tasks = storage.load() - for task in tasks: - if task['id'] == task_id: - tasks.remove(task) - storage.save(tasks) - return 'task deleted' - raise HTTPException(status_code = 404, detail = 'task not found') \ No newline at end of file From d77a82efe97cf73d46d94049753db66de47cbab4 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 19:58:47 +0300 Subject: [PATCH 11/27] Delete simple_backend/src/task_tracker/storage.py --- simple_backend/src/task_tracker/storage.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 simple_backend/src/task_tracker/storage.py diff --git a/simple_backend/src/task_tracker/storage.py b/simple_backend/src/task_tracker/storage.py deleted file mode 100644 index ce237692..00000000 --- a/simple_backend/src/task_tracker/storage.py +++ /dev/null @@ -1,13 +0,0 @@ -import json -import os -class TaskStorage: - def __init__(self, filename: str = 'tasks.json'): - self.file = filename - if not os.path.exists(self.file): # if file does not exist, create file with empty list - self.save([]) - def load(self): - with open(self.file, 'r', encoding = 'utf-8') as f: - return json.load(f) - def save(self, tasks): - with open(self.file, 'w', encoding = 'utf-8') as f: - json.dump(tasks, f) \ No newline at end of file From 712ad577dea4eb62ebe39f6421d0c32b67e4469e Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 19:58:54 +0300 Subject: [PATCH 12/27] Delete simple_backend/src/task_tracker/storage_gist.py --- .../src/task_tracker/storage_gist.py | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 simple_backend/src/task_tracker/storage_gist.py diff --git a/simple_backend/src/task_tracker/storage_gist.py b/simple_backend/src/task_tracker/storage_gist.py deleted file mode 100644 index 6f548389..00000000 --- a/simple_backend/src/task_tracker/storage_gist.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import requests -import json -from dotenv import load_dotenv -load_dotenv() -GITHUB_API = 'https://api.github.com' -class GistStorage: - def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): - self.token: str = token or os.getenv('GITHUB_TOKEN') - self.gist_id: str = gist_id or os.getenv('GIST_ID') - self.filename: str = filename - if not self.token or not self.gist_id: - raise RuntimeError('GITHUB_TOKEN and GIST_ID must be given') - self.headers: dict = { - 'Authorization': f'token {self.token}', - 'Accept': 'application/vnd.github+json' - } - - def _gist_url(self) -> str: - return f'{GITHUB_API}/gists/{self.gist_id}' - # loading task list from gist - def load(self) -> list[dict]: - r = requests.get(self._gist_url(), headers = self.headers, timeout = 10) - r.raise_for_status() - gist_data: dict = r.json() - files: dict = gist_data.get('files', {}) - if self.filename not in files: - return [] - content: str = files[self.filename]['content'] - try: - data: dict = json.loads(content) - return data.get('tasks', []) - except json.JSONDecodeError: - return [] - # saving task list on gist - def save(self, tasks: list[dict]) -> None: - body: dict = { - 'files': { - self.filename: { - 'content': json.dumps({'tasks': tasks}) - } - } - } - r = requests.patch(self._gist_url(), headers = self.headers, json = body, timeout = 10) - r.raise_for_status() From 2b800b352d48e62c6af41f3581a664b1f89abafb Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 20:11:52 +0300 Subject: [PATCH 13/27] del --- simple_backend/src/task_tracker/main.py | 39 ---------------- simple_backend/src/task_tracker/storage.py | 13 ------ .../src/task_tracker/storage_gist.py | 45 ------------------- simple_backend/src/task_tracker/tasks.json | 1 - 4 files changed, 98 deletions(-) delete mode 100644 simple_backend/src/task_tracker/main.py delete mode 100644 simple_backend/src/task_tracker/storage.py delete mode 100644 simple_backend/src/task_tracker/storage_gist.py delete 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 deleted file mode 100644 index 6209f4dd..00000000 --- a/simple_backend/src/task_tracker/main.py +++ /dev/null @@ -1,39 +0,0 @@ -'''backend''' -from fastapi import FastAPI, HTTPException -from storage_gist import GistStorage -app = FastAPI() -storage = GistStorage() -# return tasks list -@app.get("/tasks") -def get_tasks(): - return storage.load() -#create a new task with id, name, and status -@app.post("/tasks") -def create_task(name: str, condition: str = 'new'): - tasks = storage.load() - new_id = len(tasks) + 1 - task = {'id': new_id, 'name': name, 'condition': condition} - tasks.append(task) - storage.save(tasks) - return task -#update task -@app.put("/tasks/{task_id}") -def update_task(task_id: int, name: str, condition: str): - tasks = storage.load() - for task in tasks: - if task['id'] == task_id: - task['name'] = name - task['condition'] = condition - storage.save(tasks) - return task - raise HTTPException(status_code = 404, detail = 'task not found') -#delete task -@app.delete("/tasks/{task_id}") -def delete_task(task_id: int): - tasks = storage.load() - for task in tasks: - if task['id'] == task_id: - tasks.remove(task) - storage.save(tasks) - return 'task deleted' - raise HTTPException(status_code = 404, detail = 'task not found') \ No newline at end of file diff --git a/simple_backend/src/task_tracker/storage.py b/simple_backend/src/task_tracker/storage.py deleted file mode 100644 index ce237692..00000000 --- a/simple_backend/src/task_tracker/storage.py +++ /dev/null @@ -1,13 +0,0 @@ -import json -import os -class TaskStorage: - def __init__(self, filename: str = 'tasks.json'): - self.file = filename - if not os.path.exists(self.file): # if file does not exist, create file with empty list - self.save([]) - def load(self): - with open(self.file, 'r', encoding = 'utf-8') as f: - return json.load(f) - def save(self, tasks): - with open(self.file, 'w', encoding = 'utf-8') as f: - json.dump(tasks, f) \ No newline at end of file diff --git a/simple_backend/src/task_tracker/storage_gist.py b/simple_backend/src/task_tracker/storage_gist.py deleted file mode 100644 index 6f548389..00000000 --- a/simple_backend/src/task_tracker/storage_gist.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import requests -import json -from dotenv import load_dotenv -load_dotenv() -GITHUB_API = 'https://api.github.com' -class GistStorage: - def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): - self.token: str = token or os.getenv('GITHUB_TOKEN') - self.gist_id: str = gist_id or os.getenv('GIST_ID') - self.filename: str = filename - if not self.token or not self.gist_id: - raise RuntimeError('GITHUB_TOKEN and GIST_ID must be given') - self.headers: dict = { - 'Authorization': f'token {self.token}', - 'Accept': 'application/vnd.github+json' - } - - def _gist_url(self) -> str: - return f'{GITHUB_API}/gists/{self.gist_id}' - # loading task list from gist - def load(self) -> list[dict]: - r = requests.get(self._gist_url(), headers = self.headers, timeout = 10) - r.raise_for_status() - gist_data: dict = r.json() - files: dict = gist_data.get('files', {}) - if self.filename not in files: - return [] - content: str = files[self.filename]['content'] - try: - data: dict = json.loads(content) - return data.get('tasks', []) - except json.JSONDecodeError: - return [] - # saving task list on gist - def save(self, tasks: list[dict]) -> None: - body: dict = { - 'files': { - self.filename: { - 'content': json.dumps({'tasks': tasks}) - } - } - } - r = requests.patch(self._gist_url(), headers = self.headers, json = body, timeout = 10) - r.raise_for_status() diff --git a/simple_backend/src/task_tracker/tasks.json b/simple_backend/src/task_tracker/tasks.json deleted file mode 100644 index 80c933c8..00000000 --- a/simple_backend/src/task_tracker/tasks.json +++ /dev/null @@ -1 +0,0 @@ -[{"id": 1, "name": "finaltask", "condition": "updated"}, {"id": 2, "name": "totaltask", "condition": "updated"}, {"id": 3, "name": "Task3", "condition": "new"}] \ No newline at end of file From 35e423dc900815c0746d53acdb0f29794077e79a Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 20:17:11 +0300 Subject: [PATCH 14/27] storage github gist add --- simple_backend/src/task_tracker/main.py | 39 ++++++++++++++++ simple_backend/src/task_tracker/storage.py | 13 ++++++ .../src/task_tracker/storage_gist.py | 45 +++++++++++++++++++ simple_backend/src/task_tracker/tasks.json | 1 + 4 files changed, 98 insertions(+) create mode 100644 simple_backend/src/task_tracker/main.py create mode 100644 simple_backend/src/task_tracker/storage.py create mode 100644 simple_backend/src/task_tracker/storage_gist.py 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 new file mode 100644 index 00000000..6209f4dd --- /dev/null +++ b/simple_backend/src/task_tracker/main.py @@ -0,0 +1,39 @@ +'''backend''' +from fastapi import FastAPI, HTTPException +from storage_gist import GistStorage +app = FastAPI() +storage = GistStorage() +# return tasks list +@app.get("/tasks") +def get_tasks(): + return storage.load() +#create a new task with id, name, and status +@app.post("/tasks") +def create_task(name: str, condition: str = 'new'): + tasks = storage.load() + new_id = len(tasks) + 1 + task = {'id': new_id, 'name': name, 'condition': condition} + tasks.append(task) + storage.save(tasks) + return task +#update task +@app.put("/tasks/{task_id}") +def update_task(task_id: int, name: str, condition: str): + tasks = storage.load() + for task in tasks: + if task['id'] == task_id: + task['name'] = name + task['condition'] = condition + storage.save(tasks) + return task + raise HTTPException(status_code = 404, detail = 'task not found') +#delete task +@app.delete("/tasks/{task_id}") +def delete_task(task_id: int): + tasks = storage.load() + for task in tasks: + if task['id'] == task_id: + tasks.remove(task) + storage.save(tasks) + return 'task deleted' + raise HTTPException(status_code = 404, detail = 'task not found') \ 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..ce237692 --- /dev/null +++ b/simple_backend/src/task_tracker/storage.py @@ -0,0 +1,13 @@ +import json +import os +class TaskStorage: + def __init__(self, filename: str = 'tasks.json'): + self.file = filename + if not os.path.exists(self.file): # if file does not exist, create file with empty list + self.save([]) + def load(self): + with open(self.file, 'r', encoding = 'utf-8') as f: + return json.load(f) + def save(self, tasks): + with open(self.file, 'w', encoding = 'utf-8') as f: + json.dump(tasks, f) \ No newline at end of file diff --git a/simple_backend/src/task_tracker/storage_gist.py b/simple_backend/src/task_tracker/storage_gist.py new file mode 100644 index 00000000..6f548389 --- /dev/null +++ b/simple_backend/src/task_tracker/storage_gist.py @@ -0,0 +1,45 @@ +import os +import requests +import json +from dotenv import load_dotenv +load_dotenv() +GITHUB_API = 'https://api.github.com' +class GistStorage: + def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): + self.token: str = token or os.getenv('GITHUB_TOKEN') + self.gist_id: str = gist_id or os.getenv('GIST_ID') + self.filename: str = filename + if not self.token or not self.gist_id: + raise RuntimeError('GITHUB_TOKEN and GIST_ID must be given') + self.headers: dict = { + 'Authorization': f'token {self.token}', + 'Accept': 'application/vnd.github+json' + } + + def _gist_url(self) -> str: + return f'{GITHUB_API}/gists/{self.gist_id}' + # loading task list from gist + def load(self) -> list[dict]: + r = requests.get(self._gist_url(), headers = self.headers, timeout = 10) + r.raise_for_status() + gist_data: dict = r.json() + files: dict = gist_data.get('files', {}) + if self.filename not in files: + return [] + content: str = files[self.filename]['content'] + try: + data: dict = json.loads(content) + return data.get('tasks', []) + except json.JSONDecodeError: + return [] + # saving task list on gist + def save(self, tasks: list[dict]) -> None: + body: dict = { + 'files': { + self.filename: { + 'content': json.dumps({'tasks': tasks}) + } + } + } + r = requests.patch(self._gist_url(), headers = self.headers, json = body, timeout = 10) + r.raise_for_status() diff --git a/simple_backend/src/task_tracker/tasks.json b/simple_backend/src/task_tracker/tasks.json new file mode 100644 index 00000000..80c933c8 --- /dev/null +++ b/simple_backend/src/task_tracker/tasks.json @@ -0,0 +1 @@ +[{"id": 1, "name": "finaltask", "condition": "updated"}, {"id": 2, "name": "totaltask", "condition": "updated"}, {"id": 3, "name": "Task3", "condition": "new"}] \ No newline at end of file From f6f5ead90bed830f0d6198d7df8a50263c108520 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 20:21:41 +0300 Subject: [PATCH 15/27] Create README.md --- simple_backend/src/task_tracker/README.md | 24 +++++++++++++++++++++++ 1 file changed, 24 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..c786bbe7 --- /dev/null +++ b/simple_backend/src/task_tracker/README.md @@ -0,0 +1,24 @@ +Хранение состояния + ++ 1.Минусы метода хранения в оперативной памяти в том, что доступ к данным есть только во время одной сессии , + после перезагрузки приложения, доступа к данным уже не будет. ++ 2.Отсутствие масштабируемости. ++ 3.Нет истории изменений +----------------------------------------------------------------------------------------------------------------------------- +Переход на json + ++ 1.После того, как список оперативной памяти изменился на файл проекта, мы получаем доступ к данным хранилища даже после перезапуска приложения, данные не исчезают, а остаются в списке ++ 2.Можно открыть файл и редактировать его вручную ++ 3.Так как json файл является локальным, то при создании копий приложения, у них будут разные файлы tasks.json +----------------------------------------------------------------------------------------------------------------------------- +Хранилища задач + +Задачи можно хранить в облачных хранилищах: + ++ Плюсы: Доступ откуда угодно ++ Минусы: Обдачные хранилища зависимы от сторонних ресурсов +Задачи можно хранить в баазах данныхЖ + ++ Плюсы: Поддержка транзакций, масштабирование и надежность ++ Минусы: Сложная настройка +----------------------------------------------------------------------------------------------------------------------------- From 7eb183acc83bd4afa8957d6492ae0fbc08eda4e9 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 20:43:57 +0300 Subject: [PATCH 16/27] Update README.md --- simple_backend/src/task_tracker/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simple_backend/src/task_tracker/README.md b/simple_backend/src/task_tracker/README.md index c786bbe7..765f273c 100644 --- a/simple_backend/src/task_tracker/README.md +++ b/simple_backend/src/task_tracker/README.md @@ -22,3 +22,5 @@ + Плюсы: Поддержка транзакций, масштабирование и надежность + Минусы: Сложная настройка ----------------------------------------------------------------------------------------------------------------------------- +Состояние гонки, это когда два разработчика делают одно и то же в коде и возникает конфликт(данные одного разработчика перезаписывают данные другого. +Если перенести хранение на базу данных , то решится проблема с гонками, плюс данные лучше защищены и легче масштабировать From 443cd7721caeb9864e16710f17422c620a69b75c Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sat, 4 Oct 2025 23:55:23 +0300 Subject: [PATCH 17/27] cludflare add --- simple_backend/src/task_tracker/Cloudflare.py | 30 +++++++++++++++++++ simple_backend/src/task_tracker/main.py | 6 +++- 2 files changed, 35 insertions(+), 1 deletion(-) 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..3c2c208d --- /dev/null +++ b/simple_backend/src/task_tracker/Cloudflare.py @@ -0,0 +1,30 @@ +import os +import requests +from dotenv import load_dotenv +load_dotenv() +class Cloudflare: + def __init__(self, api_key: str = None, acc_id: str = None): + self.api_key = api_key or os.getenv("CLOUDFLARE_API_KEY") + self.acc_id = acc_id or os.getenv("CLOUDFLARE_ACCOUNT_ID") + self.url = f"https://api.cloudflare.com/client/v4/accounts/{self.acc_id}/ai/run/@cf/meta/llama-3-8b-instruct" + + def get_llm_response(self, text:str) -> str: + headers = { + "Authorization": f'Bearer {self.api_key}', + "Content-type": "application/json" + } + pyload = { + 'messages': [ + {'role': 'system', 'content': 'I will help you with somethimg'}, + {'role': 'user', 'content': text} + ] + } + + response = requests.post(self.url, headers=headers, json=pyload, timeout=15) + print("CLOUDFLARE RAW RESPONSE:") + print(response.text) + try: + data = response.json() + return data['result']['response'] + except (KeyError, IndexError): + return 'Error: wrong API answer' \ 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 6209f4dd..fd29a564 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,8 +1,11 @@ '''backend''' from fastapi import FastAPI, HTTPException +from pydantic import BaseModel from storage_gist import GistStorage +from Cloudflare import Cloudflare app = FastAPI() storage = GistStorage() +llm = Cloudflare() # return tasks list @app.get("/tasks") def get_tasks(): @@ -12,7 +15,8 @@ def get_tasks(): def create_task(name: str, condition: str = 'new'): tasks = storage.load() new_id = len(tasks) + 1 - task = {'id': new_id, 'name': name, 'condition': condition} + llm_response = llm.get_llm_response(name) + task = {'id': new_id, 'name': name, 'condition': condition, 'description': f'LLM Suggestion: {llm_response}'} tasks.append(task) storage.save(tasks) return task From 4943856850ad474e45760e054cd397edbd7b20f9 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Sun, 5 Oct 2025 03:09:57 +0300 Subject: [PATCH 18/27] BaseHTTPClient add --- .../src/task_tracker/BaseHTTPClient.py | 34 +++++++++ simple_backend/src/task_tracker/Cloudflare.py | 36 +++++----- .../src/task_tracker/storage_gist.py | 69 +++++++++++++++---- 3 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 simple_backend/src/task_tracker/BaseHTTPClient.py diff --git a/simple_backend/src/task_tracker/BaseHTTPClient.py b/simple_backend/src/task_tracker/BaseHTTPClient.py new file mode 100644 index 00000000..3c766281 --- /dev/null +++ b/simple_backend/src/task_tracker/BaseHTTPClient.py @@ -0,0 +1,34 @@ +import requests +from abc import ABC, abstractmethod + +class BaseHTTPClient(ABC): + + def __init__(self, base_url: str, headers: dict = None): + self.base_url = base_url.rstrip("/") # убираем лишний слэш + self.headers = headers or {} + + def _get(self, endpoint: str = "", **kwargs): + url = f"{self.base_url}{endpoint}" + print(f"[GET] {url}") + resp = requests.get(url, headers=self.headers, timeout=15, **kwargs) + resp.raise_for_status() + return resp.json() + + def _post(self, endpoint: str = "", json_data=None, **kwargs): + url = f"{self.base_url}{endpoint}" + print(f"[POST] {url} | DATA: {json_data}") + resp = requests.post(url, headers=self.headers, json=json_data, timeout=15, **kwargs) + resp.raise_for_status() + return resp.json() + + def _patch(self, endpoint: str = "", json_data=None, **kwargs): + url = f"{self.base_url}{endpoint}" + print(f"[PATCH] {url} | DATA: {json_data}") + resp = requests.patch(url, headers=self.headers, json=json_data, timeout=15, **kwargs) + resp.raise_for_status() + return resp.json() + + @abstractmethod + def _make_request(self, *args, **kwargs): + pass + \ 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 3c2c208d..5bea74e0 100644 --- a/simple_backend/src/task_tracker/Cloudflare.py +++ b/simple_backend/src/task_tracker/Cloudflare.py @@ -1,30 +1,30 @@ import os import requests from dotenv import load_dotenv +from BaseHTTPClient import BaseHTTPClient load_dotenv() -class Cloudflare: +class Cloudflare(BaseHTTPClient): def __init__(self, api_key: str = None, acc_id: str = None): self.api_key = api_key or os.getenv("CLOUDFLARE_API_KEY") self.acc_id = acc_id or os.getenv("CLOUDFLARE_ACCOUNT_ID") - self.url = f"https://api.cloudflare.com/client/v4/accounts/{self.acc_id}/ai/run/@cf/meta/llama-3-8b-instruct" - - def get_llm_response(self, text:str) -> str: + base_url = f"https://api.cloudflare.com/client/v4/accounts/{self.acc_id}/ai/run/@cf/meta/llama-3-8b-instruct" headers = { - "Authorization": f'Bearer {self.api_key}', - "Content-type": "application/json" + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" } - pyload = { - 'messages': [ - {'role': 'system', 'content': 'I will help you with somethimg'}, - {'role': 'user', 'content': text} + super().__init__(base_url=base_url, headers=headers) + def _make_request(self, text: str): + payload = { + "messages": [ + {"role": "system", "content": "You are a helpful assistant that explains tasks clearly."}, + {"role": "user", "content": text} ] } - - response = requests.post(self.url, headers=headers, json=pyload, timeout=15) - print("CLOUDFLARE RAW RESPONSE:") - print(response.text) + return self._post(json_data=payload) + def get_llm_response(self, text:str) -> str: try: - data = response.json() - return data['result']['response'] - except (KeyError, IndexError): - return 'Error: wrong API answer' \ No newline at end of file + data = self._make_request(text) + return data.get('result', {}).get('response', "No response") + except Exception as e: + print("Error getting LLM response:", e) + return "Error: wrong API answer" \ No newline at end of file diff --git a/simple_backend/src/task_tracker/storage_gist.py b/simple_backend/src/task_tracker/storage_gist.py index 6f548389..10a08f3a 100644 --- a/simple_backend/src/task_tracker/storage_gist.py +++ b/simple_backend/src/task_tracker/storage_gist.py @@ -2,34 +2,76 @@ import requests import json from dotenv import load_dotenv +from BaseHTTPClient import BaseHTTPClient load_dotenv() GITHUB_API = 'https://api.github.com' -class GistStorage: +class GistStorage(BaseHTTPClient): def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): self.token: str = token or os.getenv('GITHUB_TOKEN') self.gist_id: str = gist_id or os.getenv('GIST_ID') self.filename: str = filename + base_url = f"{GITHUB_API}/gists/{self.gist_id}" if not self.token or not self.gist_id: raise RuntimeError('GITHUB_TOKEN and GIST_ID must be given') - self.headers: dict = { + headers: dict = { 'Authorization': f'token {self.token}', 'Accept': 'application/vnd.github+json' } - - def _gist_url(self) -> str: - return f'{GITHUB_API}/gists/{self.gist_id}' + super().__init__(base_url=base_url, headers=headers) + import os +import requests +import json +from dotenv import load_dotenv +from BaseHTTPClient import BaseHTTPClient +load_dotenv() +GITHUB_API = 'https://api.github.com' +class GistStorage(BaseHTTPClient): + def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): + self.token: str = token or os.getenv('GITHUB_TOKEN') + self.gist_id: str = gist_id or os.getenv('GIST_ID') + self.filename: str = filename + base_url = f"{GITHUB_API}/gists/{self.gist_id}" + if not self.token or not self.gist_id: + raise RuntimeError('GITHUB_TOKEN and GIST_ID must be given') + headers: dict = { + 'Authorization': f'token {self.token}', + 'Accept': 'application/vnd.github+json' + } + super().__init__(base_url=base_url, headers=headers) + def _make_request(self, *args, **kwargs): + pass + # loading task list from gist + def load(self) -> list[dict]: + gist_data = self._get() + files = gist_data.get("files", {}) + if self.filename not in files: + return [] + try: + content = files[self.filename]["content"] + data = json.loads(content) + return data.get("tasks", []) + except json.JSONDecodeError: + return [] + # saving task list on gist + def save(self, tasks: list[dict]) -> None: + body: dict = { + 'files': { + self.filename: { + 'content': json.dumps({'tasks': tasks}) + } + } + } + self._patch(json_data=body) # loading task list from gist def load(self) -> list[dict]: - r = requests.get(self._gist_url(), headers = self.headers, timeout = 10) - r.raise_for_status() - gist_data: dict = r.json() - files: dict = gist_data.get('files', {}) + gist_data = self._get() + files = gist_data.get("files", {}) if self.filename not in files: return [] - content: str = files[self.filename]['content'] try: - data: dict = json.loads(content) - return data.get('tasks', []) + content = files[self.filename]["content"] + data = json.loads(content) + return data.get("tasks", []) except json.JSONDecodeError: return [] # saving task list on gist @@ -41,5 +83,4 @@ def save(self, tasks: list[dict]) -> None: } } } - r = requests.patch(self._gist_url(), headers = self.headers, json = body, timeout = 10) - r.raise_for_status() + self._patch(json_data=body) \ No newline at end of file From 5dce8a4a60e298da3a0d0cdc25975094379ab9b3 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 19:35:51 +0300 Subject: [PATCH 19/27] add workflows --- .github/workflows/ci.yml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6b2cde7d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12.3" + + - name: Cache pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-ruff-v1 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff + + - name: Auto-fix code with Ruff + run: | + ruff format . --fix --line-length 88 --exclude migrations,venv || true + + - name: Run Ruff linter + run: | + ruff check . --select ALL --ignore F401,W391 --line-length 88 From 4584deb12787da77b3b0d305efa9706a4e8d0a8e Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 23:31:30 +0300 Subject: [PATCH 20/27] correct smth --- simple_backend/src/task_tracker/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index fd29a564..cd272e3f 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,6 +1,5 @@ '''backend''' from fastapi import FastAPI, HTTPException -from pydantic import BaseModel from storage_gist import GistStorage from Cloudflare import Cloudflare app = FastAPI() From ec3de8dde413d5ef839ed428d3e9bbb7170eeeb0 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 23:32:49 +0300 Subject: [PATCH 21/27] Update ci.yml --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b2cde7d..8ee302b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ main ] + branches: [ main, second-branch ] pull_request: - branches: [ main ] + branches: [ main, second-branch ] jobs: lint: @@ -23,7 +23,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-ruff-v1 + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- - name: Install dependencies run: | From e2910243a98b6035fc6dfecaab51923ed5e7bf67 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 23:37:11 +0300 Subject: [PATCH 22/27] Update ci.yml --- .github/workflows/ci.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ee302b8..b72adceb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,7 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + key: ${{ runner.os }}-pip-ruff-v1 - name: Install dependencies run: | @@ -34,8 +32,8 @@ jobs: - name: Auto-fix code with Ruff run: | - ruff format . --fix --line-length 88 --exclude migrations,venv || true + ruff check . --fix --line-length 88 || true - name: Run Ruff linter run: | - ruff check . --select ALL --ignore F401,W391 --line-length 88 + ruff check . --line-length 88 From f79221fae03c094d19cad6077bb9ca9969b8e720 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 23:39:18 +0300 Subject: [PATCH 23/27] Create .ruff.toml --- .ruff.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 00000000..a38af153 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,11 @@ +[tool.ruff] +line-length = 88 +select = ["E", "F"] +ignore = ["F401", "W391", "E501"] +exclude = [ + "venv", + "migrations", + ".git", + "__pycache__", + "node_modules" +] From 46ef92e39cff6c22550dd1bb932578d6fc202441 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 23:41:01 +0300 Subject: [PATCH 24/27] Update .ruff.toml --- .ruff.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.ruff.toml b/.ruff.toml index a38af153..aa31313b 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,4 +1,3 @@ -[tool.ruff] line-length = 88 select = ["E", "F"] ignore = ["F401", "W391", "E501"] From c152ca941d63f6f0c9ac42990395461f376090a1 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 23:54:53 +0300 Subject: [PATCH 25/27] fix giststorage, put now works --- .../src/task_tracker/storage_gist.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/simple_backend/src/task_tracker/storage_gist.py b/simple_backend/src/task_tracker/storage_gist.py index 10a08f3a..237852e3 100644 --- a/simple_backend/src/task_tracker/storage_gist.py +++ b/simple_backend/src/task_tracker/storage_gist.py @@ -5,26 +5,6 @@ from BaseHTTPClient import BaseHTTPClient load_dotenv() GITHUB_API = 'https://api.github.com' -class GistStorage(BaseHTTPClient): - def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): - self.token: str = token or os.getenv('GITHUB_TOKEN') - self.gist_id: str = gist_id or os.getenv('GIST_ID') - self.filename: str = filename - base_url = f"{GITHUB_API}/gists/{self.gist_id}" - if not self.token or not self.gist_id: - raise RuntimeError('GITHUB_TOKEN and GIST_ID must be given') - headers: dict = { - 'Authorization': f'token {self.token}', - 'Accept': 'application/vnd.github+json' - } - super().__init__(base_url=base_url, headers=headers) - import os -import requests -import json -from dotenv import load_dotenv -from BaseHTTPClient import BaseHTTPClient -load_dotenv() -GITHUB_API = 'https://api.github.com' class GistStorage(BaseHTTPClient): def __init__(self, token: str = None, gist_id: str = None, filename: str = 'tasks.json'): self.token: str = token or os.getenv('GITHUB_TOKEN') From 6f9945fc3830c05821b3091a0a32b6de53120426 Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Thu, 9 Oct 2025 23:57:58 +0300 Subject: [PATCH 26/27] fix gist storage --- .../src/task_tracker/storage_gist.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/simple_backend/src/task_tracker/storage_gist.py b/simple_backend/src/task_tracker/storage_gist.py index 237852e3..34c9972a 100644 --- a/simple_backend/src/task_tracker/storage_gist.py +++ b/simple_backend/src/task_tracker/storage_gist.py @@ -33,28 +33,6 @@ def load(self) -> list[dict]: except json.JSONDecodeError: return [] # saving task list on gist - def save(self, tasks: list[dict]) -> None: - body: dict = { - 'files': { - self.filename: { - 'content': json.dumps({'tasks': tasks}) - } - } - } - self._patch(json_data=body) - # loading task list from gist - def load(self) -> list[dict]: - gist_data = self._get() - files = gist_data.get("files", {}) - if self.filename not in files: - return [] - try: - content = files[self.filename]["content"] - data = json.loads(content) - return data.get("tasks", []) - except json.JSONDecodeError: - return [] - # saving task list on gist def save(self, tasks: list[dict]) -> None: body: dict = { 'files': { From 9011e890b1eb4f89d6ac1e23efa985ccdcace7ff Mon Sep 17 00:00:00 2001 From: Roman Andreev Date: Fri, 10 Oct 2025 00:38:18 +0300 Subject: [PATCH 27/27] del comment httpclient --- simple_backend/src/task_tracker/BaseHTTPClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple_backend/src/task_tracker/BaseHTTPClient.py b/simple_backend/src/task_tracker/BaseHTTPClient.py index 3c766281..d5f328e2 100644 --- a/simple_backend/src/task_tracker/BaseHTTPClient.py +++ b/simple_backend/src/task_tracker/BaseHTTPClient.py @@ -4,7 +4,7 @@ class BaseHTTPClient(ABC): def __init__(self, base_url: str, headers: dict = None): - self.base_url = base_url.rstrip("/") # убираем лишний слэш + self.base_url = base_url.rstrip("/") self.headers = headers or {} def _get(self, endpoint: str = "", **kwargs):