From d4c7fceb050423794915eb43a7b1d15de891135d Mon Sep 17 00:00:00 2001 From: Anton Shcherbak Date: Tue, 19 Nov 2024 22:14:00 +0300 Subject: [PATCH 1/8] 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 2/8] 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 eee4e5608f5f44c79de5465aeebd97ed5505ba27 Mon Sep 17 00:00:00 2001 From: Krabler228 Date: Mon, 25 Aug 2025 15:13:27 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B3=D0=BD=D0=B0=D0=BB?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BB=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- git/src/main.py | 57 +++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/git/src/main.py b/git/src/main.py index 16f19231..5c920b33 100644 --- a/git/src/main.py +++ b/git/src/main.py @@ -1,28 +1,29 @@ import json import os -def load_books(filename='library.json'): + +def load_books(filename="library.json"): """ Загрузка списка книг из JSON-файла. Возвращает список книг (каждая книга - это словарь). """ if not os.path.isfile(filename): return [] - with open(filename, 'r', encoding='utf-8') as file: + with open(filename, "r", encoding="utf-8") as file: try: return json.load(file) except json.JSONDecodeError: return [] -def save_books(books, filename='library.json'): - +def save_books(books, filename="library.json"): """ Сохранение списка книг в JSON-файл. """ - with open(filename, 'w', encoding='utf-8') as file: + with open(filename, "w", encoding="utf-8") as file: json.dump(books, file, ensure_ascii=False, indent=4) + def list_books(books): """ Возвращает строку со списком всех книг. @@ -31,29 +32,30 @@ def list_books(books): return "Библиотека пуста." result_lines = [] for idx, book in enumerate(books, start=1): - result_lines.append(f"{idx}. {book['title']} | {book['author']} | {book['year']}") + 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 = {"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()] + return [book for book in books if book["title"].lower() != title.lower()] + def search_books(books, keyword): """ @@ -62,13 +64,16 @@ 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() + 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 @@ -83,11 +88,11 @@ def main(): choice = input("Выберите действие (1-5): ").strip() - if choice == '1': + if choice == "1": print("\nСписок книг:") print(list_books(books)) - elif choice == '2': + elif choice == "2": print("\nДобавление новой книги:") title = input("Введите название: ").strip() author = input("Введите автора: ").strip() @@ -97,17 +102,17 @@ def main(): new_books = add_book(books, title, author, year) books = new_books # Обновляем переменную, чтобы сохранить изменения - save_books(books) # Сразу сохраняем в файл print("Книга добавлена!") - elif choice == '3': + elif choice == "3": print("\nУдаление книги:") - title_to_remove = input("Введите название книги, которую хотите удалить: ").strip() + title_to_remove = input( + "Введите название книги, которую хотите удалить: " + ).strip() new_books = remove_book(books, title_to_remove) - if len(new_books) < len(books): books = new_books save_books(books) @@ -116,9 +121,11 @@ def main(): else: print("Книга с таким названием не найдена.") - elif choice == '4': + elif choice == "4": print("\nПоиск книг:") - keyword = input("Введите ключевое слово для поиска (в названии или авторе): ").strip() + keyword = input( + "Введите ключевое слово для поиска (в названии или авторе): " + ).strip() found_books = search_books(books, keyword) if found_books: print("\nНайденные книги:") @@ -126,8 +133,7 @@ def main(): else: print("Ничего не найдено.") - - elif choice == '5': + elif choice == "5": print("Выход из программы.") break @@ -137,4 +143,3 @@ def main(): if __name__ == "__main__": main() - From 01ee253def7a03d456af2319dfe2dace74ed58a1 Mon Sep 17 00:00:00 2001 From: Krabler228 Date: Mon, 25 Aug 2025 15:46:25 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20ci=20=D1=81=20=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=BE?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 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..3d6f3e51 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + pull_request: + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Ruff + run: pip install ruff + + - name: Ruff lint + run: ruff check . + + - name: Ruff format check + run: ruff format . --check \ No newline at end of file From b9273db8757fa0b6c3e2a0d5abcb6827049432de Mon Sep 17 00:00:00 2001 From: Krabler228 Date: Thu, 28 Aug 2025 17:45:29 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20Task=20=D0=B8=20=D1=81=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BB=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/main.py | 55 +++++++++++++++++++++---- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 3db98d0d..5b89632c 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,19 +1,60 @@ -from fastapi import FastAPI +import json +from fastapi import FastAPI, HTTPException app = FastAPI() +ID = 0 +STATUS_LIST = ["Задача создана", "Задача в процессе выполнения", "Задача выполнена"] +DEFAULT_STATUS = STATUS_LIST[0] + +def get_new_id(): + global ID + ID += 1 + return ID + +class Task: + def __init__(self, name: str): + self.task_id = get_new_id() + self.name = name + self.status = DEFAULT_STATUS + def to_dict(self): + return { + "task_id": self.task_id, + "name": self.name, + "status": self.status, + } + + +tasks: list[Task] = [] + @app.get("/tasks") -def get_tasks(): - pass +def get_tasks() -> list[dict]: + return [t.to_dict() for t in tasks] @app.post("/tasks") -def create_task(task): - pass +def create_task(name: str): + new_task = Task(name) + tasks.append(new_task) + return new_task.to_dict() + @app.put("/tasks/{task_id}") def update_task(task_id: int): - pass + for t in tasks: + if t.id == task_id: + if t.status == DEFAULT_STATUS: + t.status = STATUS_LIST[1] + elif t.status == STATUS_LIST[1]: + t.status = STATUS_LIST[2] + else: + t.status = STATUS_LIST[2] + else: + return HTTPException(status_code=404, detail="ID не найден") @app.delete("/tasks/{task_id}") def delete_task(task_id: int): - pass + for task in tasks: + if task_id == task_id: + tasks.remove(task) + else: + return HTTPException(status_code=404, detail="ID не найден") \ No newline at end of file From 18f73e29698931fd1640f57d8b64785b8683bef1 Mon Sep 17 00:00:00 2001 From: Krabler228 Date: Thu, 28 Aug 2025 17:45:40 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20Task=20=D0=B8=20=D1=81=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BB=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/classes.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 simple_backend/src/task_tracker/classes.py diff --git a/simple_backend/src/task_tracker/classes.py b/simple_backend/src/task_tracker/classes.py new file mode 100644 index 00000000..1e88aff8 --- /dev/null +++ b/simple_backend/src/task_tracker/classes.py @@ -0,0 +1 @@ +class \ No newline at end of file From b0560cb70efb43e8816d597e245f8dd99d6becf3 Mon Sep 17 00:00:00 2001 From: Krabler228 Date: Fri, 29 Aug 2025 11:16:13 +0300 Subject: [PATCH 7/8] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=B8=D0=BB=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B4=D0=BE=20=D0=BA=D0=BE=D0=BD=D1=86=D0=B0=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=BE=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8E.=20=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=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=B1?= =?UTF-8?q?=D0=BB=D0=B0=D0=BA=D0=B5=20=D0=B8=20=D0=BD=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D0=BB=20=D1=80=D0=B0=D1=81=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=B2=D1=81=D0=B5=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D1=8B=20=D0=B2=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B5=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/classes.py | 40 ++++++++++++- .../src/task_tracker/data/task.json | 0 simple_backend/src/task_tracker/main.py | 60 +++++++++---------- simple_backend/src/task_tracker/readme.md | 19 ++++++ simple_backend/src/task_tracker/settings.py | 6 ++ 5 files changed, 92 insertions(+), 33 deletions(-) create mode 100644 simple_backend/src/task_tracker/data/task.json create mode 100644 simple_backend/src/task_tracker/readme.md create mode 100644 simple_backend/src/task_tracker/settings.py diff --git a/simple_backend/src/task_tracker/classes.py b/simple_backend/src/task_tracker/classes.py index 1e88aff8..4a18c701 100644 --- a/simple_backend/src/task_tracker/classes.py +++ b/simple_backend/src/task_tracker/classes.py @@ -1 +1,39 @@ -class \ No newline at end of file +import json +from pathlib import Path +import requests +from settings import JSONBIN_API_KEY, JSONBIN_BIN_ID +from abc import ABC, abstractmethod + +BASE = "https://api.jsonbin.io/v3/b" + +class BaseStorage(ABC): + @abstractmethod + def load(self) -> list[dict]: + pass + @abstractmethod + def save(self, data: list[dict]) -> None: + pass + +class TaskStorage(BaseStorage): + def __init__(self, path = "tasks.json"): + self.path = Path(path) + if not self.path.exists(): + self.save([]) + def load(self): + with self.path.open("r", encoding="utf-8") as f: + return json.load(f) + def save(self, data): + with self.path.open("w", encoding="utf-8") as f: + json.dump(data, f,ensure_ascii=False, indent=2) + +class RemoteTaskStorage(BaseStorage): + def __init__(self): + self.bin_id = JSONBIN_BIN_ID + self.headers = {"X-Master-Key": JSONBIN_API_KEY, "Content-Type": "application/json"} + def load(self) -> list[dict]: + r = requests.get(f"{BASE}/{self.bin_id}/latest", headers=self.headers, timeout=10) + r.raise_for_status() + return r.json()["record"] + def save(self, data: list[dict])->None: + r = requests.put(f"{BASE}/{JSONBIN_BIN_ID}", headers=self.headers, json=data, timeout=10) + r.raise_for_status() diff --git a/simple_backend/src/task_tracker/data/task.json b/simple_backend/src/task_tracker/data/task.json new file mode 100644 index 00000000..e69de29b diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 5b89632c..0044e607 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,60 +1,56 @@ -import json from fastapi import FastAPI, HTTPException +from classes import RemoteTaskStorage app = FastAPI() -ID = 0 STATUS_LIST = ["Задача создана", "Задача в процессе выполнения", "Задача выполнена"] DEFAULT_STATUS = STATUS_LIST[0] -def get_new_id(): - global ID - ID += 1 - return ID +storage = RemoteTaskStorage() + +def next_id() -> int: + tasks = storage.load() + return max((t["task_id"] for t in tasks), default=0) + 1 class Task: def __init__(self, name: str): - self.task_id = get_new_id() + self.task_id = next_id() self.name = name self.status = DEFAULT_STATUS def to_dict(self): - return { - "task_id": self.task_id, - "name": self.name, - "status": self.status, - } - - -tasks: list[Task] = [] + return {"task_id": self.task_id, "name": self.name, "status": self.status} @app.get("/tasks") def get_tasks() -> list[dict]: - return [t.to_dict() for t in tasks] + return storage.load() @app.post("/tasks") def create_task(name: str): + tasks = storage.load() new_task = Task(name) - tasks.append(new_task) + tasks.append(new_task.to_dict()) + storage.save(tasks) return new_task.to_dict() - @app.put("/tasks/{task_id}") def update_task(task_id: int): + tasks = storage.load() for t in tasks: - if t.id == task_id: - if t.status == DEFAULT_STATUS: - t.status = STATUS_LIST[1] - elif t.status == STATUS_LIST[1]: - t.status = STATUS_LIST[2] - else: - t.status = STATUS_LIST[2] - else: - return HTTPException(status_code=404, detail="ID не найден") + if t["task_id"] == task_id: + if t["status"] == DEFAULT_STATUS: + t["status"] = STATUS_LIST[1] + elif t["status"] == STATUS_LIST[1]: + t["status"] = STATUS_LIST[2] + storage.save(tasks) + return t + raise HTTPException(404, "ID не найден") @app.delete("/tasks/{task_id}") def delete_task(task_id: int): - for task in tasks: - if task_id == task_id: - tasks.remove(task) - else: - return HTTPException(status_code=404, detail="ID не найден") \ No newline at end of file + tasks = storage.load() + for i, t in enumerate(tasks): + if t["task_id"] == task_id: + tasks.pop(i) + storage.save(tasks) + return {"detail": "Удалено"} + raise HTTPException(404, "ID не найден") \ 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..3723cb2a --- /dev/null +++ b/simple_backend/src/task_tracker/readme.md @@ -0,0 +1,19 @@ +Хранение во внутреней памяти ++ легкая реализация +- отваливаются данные при перезапуске +Хранение в json ++ задачи сохраняются на диск и не пропадают +- все еще stateful плюс проблемы с одновременным сохранением и тд +Убрал ли я хранение состояния +Нет, я только перенес его из РАМ на диск +Варианты хранения +-Текстовые файлы (тхт, json, cvs) проблемы остаются стандартными для хранения на внутреней памяти +-Облачные сервисы, проблема гонки не исчезает но уже работает stateles подход +-Базы данных, вобще все круто все работает проблем никаких нет доллар по 30 и вкусное мороженное там же(я ничего не знаю про БД) +Хранени +Состояние гонки +Это ситуация, когда два клиента одновременно читают старое состояние и перезаписывают друг друга. - это термин из интернета +Как я понял: состояние гонки это когда один файл берут два человека и пытаются с ним работать(не получится - файл то один). +Если я правильно понимаю БД в таких случая обладаюст инструментами типа копирования и последующей синхронизации со склеиванием двух версий в одну. +Что то типа контроля версий гитхаба что ли. +Как подсказвает интернет состояние гонки можно решить оптимистичной блокировкой и генерацией уникальных айди через UUID(ксати я сначала так и делал) diff --git a/simple_backend/src/task_tracker/settings.py b/simple_backend/src/task_tracker/settings.py new file mode 100644 index 00000000..1f3dc6b3 --- /dev/null +++ b/simple_backend/src/task_tracker/settings.py @@ -0,0 +1,6 @@ +import os +from dotenv import load_dotenv +load_dotenv() + +JSONBIN_API_KEY = os.getenv("JSONBIN_API_KEY") +JSONBIN_BIN_ID = os.getenv("JSONBIN_BIN_ID") From 23cf4e1ff771e93d705184a23d9ea9d0f52b7481 Mon Sep 17 00:00:00 2001 From: Krabler228 Date: Fri, 29 Aug 2025 17:51:48 +0300 Subject: [PATCH 8/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D1=8E?= =?UTF-8?q?=20=D1=81=20cloudflare?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple_backend/src/task_tracker/classes.py | 33 ++++++++++++++------- simple_backend/src/task_tracker/main.py | 10 +++++-- simple_backend/src/task_tracker/settings.py | 3 ++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/simple_backend/src/task_tracker/classes.py b/simple_backend/src/task_tracker/classes.py index 4a18c701..3f86a973 100644 --- a/simple_backend/src/task_tracker/classes.py +++ b/simple_backend/src/task_tracker/classes.py @@ -1,39 +1,50 @@ import json from pathlib import Path import requests -from settings import JSONBIN_API_KEY, JSONBIN_BIN_ID from abc import ABC, abstractmethod +from settings import JSONBIN_API_KEY, JSONBIN_BIN_ID, CF_API_TOKEN, CF_ACCOUNT_ID, CF_MODEL -BASE = "https://api.jsonbin.io/v3/b" +JSONBIN_BASE = "https://api.jsonbin.io/v3/b" +CF_BASE = "https://api.cloudflare.com/client/v4" class BaseStorage(ABC): @abstractmethod - def load(self) -> list[dict]: + def load(self): pass @abstractmethod - def save(self, data: list[dict]) -> None: + def save(self): pass class TaskStorage(BaseStorage): - def __init__(self, path = "tasks.json"): + def __init__(self, path: str = "tasks.json"): self.path = Path(path) if not self.path.exists(): self.save([]) - def load(self): + def load(self) -> list[dict]: with self.path.open("r", encoding="utf-8") as f: return json.load(f) - def save(self, data): + def save(self, data: list[dict]) -> None: with self.path.open("w", encoding="utf-8") as f: - json.dump(data, f,ensure_ascii=False, indent=2) + json.dump(data, f, ensure_ascii=False, indent=2) class RemoteTaskStorage(BaseStorage): def __init__(self): self.bin_id = JSONBIN_BIN_ID self.headers = {"X-Master-Key": JSONBIN_API_KEY, "Content-Type": "application/json"} def load(self) -> list[dict]: - r = requests.get(f"{BASE}/{self.bin_id}/latest", headers=self.headers, timeout=10) + r = requests.get(f"{JSONBIN_BASE}/{self.bin_id}/latest", headers=self.headers, timeout=10) r.raise_for_status() return r.json()["record"] - def save(self, data: list[dict])->None: - r = requests.put(f"{BASE}/{JSONBIN_BIN_ID}", headers=self.headers, json=data, timeout=10) + def save(self, data: list[dict]) -> None: + r = requests.put(f"{JSONBIN_BASE}/{self.bin_id}", headers=self.headers, json=data, timeout=10) r.raise_for_status() + +class CloudflareAIClient: + def __init__(self): + self.url = f"{CF_BASE}/accounts/{CF_ACCOUNT_ID}/ai/run/{CF_MODEL}" + self.headers = {"Authorization": f"Bearer {CF_API_TOKEN}"} + def explain(self, text: str) -> str: + r = requests.post(self.url, headers=self.headers, json={"prompt": text}, timeout=15) + r.raise_for_status() + return r.json()["result"]["response"] + diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py index 0044e607..7b46beb7 100644 --- a/simple_backend/src/task_tracker/main.py +++ b/simple_backend/src/task_tracker/main.py @@ -1,5 +1,5 @@ from fastapi import FastAPI, HTTPException -from classes import RemoteTaskStorage +from classes import RemoteTaskStorage, CloudflareAIClient app = FastAPI() @@ -7,6 +7,7 @@ DEFAULT_STATUS = STATUS_LIST[0] storage = RemoteTaskStorage() +ai = CloudflareAIClient() def next_id() -> int: tasks = storage.load() @@ -27,7 +28,12 @@ def get_tasks() -> list[dict]: @app.post("/tasks") def create_task(name: str): tasks = storage.load() - new_task = Task(name) + try: + tip = ai.explain(f"Объясни пошагово, как решать вот эту задачу -> {name} ") + full_name = f"{name} \n\nПодсказка LLM:\n{tip}" + except Exception: + full_name = name + new_task = Task(full_name) tasks.append(new_task.to_dict()) storage.save(tasks) return new_task.to_dict() diff --git a/simple_backend/src/task_tracker/settings.py b/simple_backend/src/task_tracker/settings.py index 1f3dc6b3..be6ffc1e 100644 --- a/simple_backend/src/task_tracker/settings.py +++ b/simple_backend/src/task_tracker/settings.py @@ -4,3 +4,6 @@ JSONBIN_API_KEY = os.getenv("JSONBIN_API_KEY") JSONBIN_BIN_ID = os.getenv("JSONBIN_BIN_ID") +CF_API_TOKEN = os.getenv("CF_API_TOKEN") +CF_ACCOUNT_ID = os.getenv("CF_ACCOUNT_ID") +CF_MODEL = os.getenv("CF_MODEL", "@cf/meta/llama-3.1-8b-instruct")