Skip to content

Commit 7f65d99

Browse files
committed
Resolve merge conflicts and format code with ruff
2 parents 1ece50e + bb5a993 commit 7f65d99

File tree

9 files changed

+245
-48
lines changed

9 files changed

+245
-48
lines changed

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ git clone [email protected]:gardiys/fastapi-backend-course.git
1515
- [ ] [Урок 3: Настройка окружения](./setting_environment/)
1616
- [ ] [Урок 4: Использование хранилищ](./using_storage/)
1717
- [ ] [Урок 5: Архитектура проекта](./project_architecture/)
18-
- [ ] [Урок 6: Общение сервисов](./services_communication/)
19-
- [ ] [Урок 7: Тестирование](./testing/)
20-
- [ ] [Урок 8: Деплой сервисов](./services_deploy/)
21-
- [ ] [Урок 9: Отказоустойчивость](./fault_tolerance/)
22-
- [ ] [Урок 10: Мониторинг](./monitoring/)
18+
- [ ] [Урок 6: Фоновые задачи](./async_tasks/)
19+
- [ ] [Урок 7: Общение сервисов](./services_communication/)
20+
- [ ] [Урок 8: Тестирование](./testing/)
21+
- [ ] [Урок 9: Деплой сервисов](./services_deploy/)
22+
- [ ] [Урок 10: Отказоустойчивость](./fault_tolerance/)
23+
- [ ] [Урок 11: Мониторинг](./monitoring/)

async_tasks/README.md

Whitespace-only changes.

git/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
3. Создать новую ветку с названием задачи "pull-request-task" и переключиться на неё
66
4. В проекте создать два новых файла main.py и config.py
77
5. Закоммитить изменения и запушить ветку в репозиторий
8-
6. Затем создать пулл-реквест
8+
6. Затем создать пулл-реквест с ветки pull-request-task на ветку main
99

1010
## Задание 2: разрешение конфликтов
1111
В пулл-реквесте по [ссылке](https://github.com/gardiys/fastapi-backend-course/pull/1) есть конфликт. Нужно:
1212
1. Форкнуть к себе репозиторий
1313
2. Разрешить конфликт в этой ветке, чтобы ветку можно было вмержить в мастер без потери логики
14+
3. Проверить папку git/src с помощью линтера ruff на ошибки и несоответствия стандартам разработки на Python
1415

1516
## Задание 3: простой CI
1617
Нужно добавить конфигурацию CI с помощью GitHub Actions в свой репозиторий.

git/src/main.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
import json
22
import os
33

4-
def load_books(filename='library.json'):
4+
5+
def load_books(filename="library.json"):
56
"""
67
Загрузка списка книг из JSON-файла.
78
Возвращает список книг (каждая книга - это словарь).
89
"""
910
if not os.path.isfile(filename):
1011
return []
11-
with open(filename, 'r', encoding='utf-8') as file:
12+
with open(filename, "r", encoding="utf-8") as file:
1213
try:
1314
return json.load(file)
1415
except json.JSONDecodeError:
1516
return []
1617

17-
def saving_books(books, filename='library.json'):
18+
19+
def save_books(books, filename="library.json"):
1820
"""
1921
Сохранение списка книг в JSON-файл.
2022
"""
21-
with open(filename, 'w', encoding='utf-8') as file:
23+
with open(filename, "w", encoding="utf-8") as file:
2224
json.dump(books, file, ensure_ascii=False, indent=4)
2325

26+
2427
def list_books(books):
2528
"""
2629
Возвращает строку со списком всех книг.
@@ -29,29 +32,30 @@ def list_books(books):
2932
return "Библиотека пуста."
3033
result_lines = []
3134
for idx, book in enumerate(books, start=1):
32-
result_lines.append(f"{idx}. {book['title']} | {book['author']} | {book['year']}")
35+
result_lines.append(
36+
f"{idx}. {book['title']} | {book['author']} | {book['year']}"
37+
)
3338
return "\n".join(result_lines)
3439

40+
3541
def add_book(books, title, author, year):
3642
"""
3743
Принимает текущий список книг и данные о новой книге.
3844
Возвращает новый список, в котором добавлена новая книга.
3945
"""
40-
new_book = {
41-
'title': title,
42-
'author': author,
43-
'year': year
44-
}
46+
new_book = {"title": title, "author": author, "year": year}
4547
# Создаём НОВЫЙ список, добавляя new_book
4648
return books + [new_book]
4749

50+
4851
def remove_book(books, title):
4952
"""
5053
Принимает текущий список книг и название книги для удаления.
5154
Возвращает новый список без книги, у которой совпадает название.
5255
"""
5356
# Фильтруем список: оставляем только те книги, у которых название не совпадает с переданным
54-
return [book for book in books if book['title'].lower() != title.lower()]
57+
return [book for book in books if book["title"].lower() != title.lower()]
58+
5559

5660
def search_books(books, keyword):
5761
"""
@@ -60,13 +64,16 @@ def search_books(books, keyword):
6064
"""
6165
keyword_lower = keyword.lower()
6266
return [
63-
book for book in books
64-
if keyword_lower in book['title'].lower() or keyword_lower in book['author'].lower()
67+
book
68+
for book in books
69+
if keyword_lower in book["title"].lower()
70+
or keyword_lower in book["author"].lower()
6571
]
6672

73+
6774
def main():
6875
"""
69-
Точка входа в программу: здесь мы загружаем книги,
76+
Точка входа в программу: здесь мы загружаем книги,
7077
показываем меню и обрабатываем ввод пользователя.
7178
"""
7279
books = load_books() # Загрузили список книг из JSON
@@ -81,11 +88,11 @@ def main():
8188

8289
choice = input("Выберите действие (1-5): ").strip()
8390

84-
if choice == '1':
91+
if choice == "1":
8592
print("\nСписок книг:")
8693
print(list_books(books))
8794

88-
elif choice == '2':
95+
elif choice == "2":
8996
print("\nДобавление новой книги:")
9097
title = input("Введите название: ").strip()
9198
author = input("Введите автора: ").strip()
@@ -94,42 +101,42 @@ def main():
94101
# Получаем новый список с добавленной книгой
95102
new_books = add_book(books, title, author, year)
96103
books = new_books # Обновляем переменную, чтобы сохранить изменения
97-
saving_books(books) # Сразу сохраняем в файл
104+
save_books(books) # Сразу сохраняем в файл
98105
print("Книга добавлена!")
99106

100-
elif choice == '3':
107+
elif choice == "3":
101108
print("\nУдаление книги:")
102-
title_to_remove = input("Введите название книги, которую хотите удалить: ").strip()
109+
title_to_remove = input(
110+
"Введите название книги, которую хотите удалить: "
111+
).strip()
103112

104113
new_books = remove_book(books, title_to_remove)
105-
if len(new_books) > len(books):
114+
if len(new_books) < len(books):
106115
books = new_books
107-
saving_books(books)
116+
save_books(books)
108117
print("Книга удалена!")
109118
else:
110119
print("Книга с таким названием не найдена.")
111120

112-
elif choice == '4':
121+
elif choice == "4":
113122
print("\nПоиск книг:")
114-
keyword = input("Введите ключевое слово для поиска (в названии или авторе): ").strip()
123+
keyword = input(
124+
"Введите ключевое слово для поиска (в названии или авторе): "
125+
).strip()
115126
found_books = search_books(books, keyword)
116127
if found_books:
117128
print("\nНайденные книги:")
118129
print(list_books(found_books))
119130
else:
120131
print("Ничего не найдено.")
121132

122-
elif choice == '6':
133+
elif choice == "5":
123134
print("Выход из программы.")
124135
break
125136

126137
else:
127138
print("Некорректный ввод. Попробуйте ещё раз.")
128139

129140

130-
131-
132-
133-
134141
if __name__ == "__main__":
135-
main()
142+
main()

setting_environment/README.md

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1-
Задание 1
2-
настройка Poetry
3-
Задание 2
4-
Создание docker образа
5-
Задание 3
6-
создание docker compose файла
7-
Задание 4
8-
линтеры и pre-commit хуки
9-
задание 5
10-
конфигурация проектов
1+
# Задачи
2+
3+
## Задание 1
4+
Переведите проект, полученный в разделе [Простой backend](/simple_backend/README.md) на Poetry.
5+
6+
## Задание 2
7+
Установите в dev группу poetry линтер ruff. Настройте pre-commit хуки для ruff check и ruff format.
8+
При каждом коммите у вас должны запускаться проверки с помощью линтеров.
9+
10+
## Задание 3
11+
Заверните проект в Docker-образ. Критерии:
12+
- Должен быть slim
13+
- Внутри должны устанавливаться poetry зависимости
14+
- Образ должен собираться
15+
- С помощью образа можно запустить приложение на FastAPI
16+
17+
## Задание 4
18+
Создайте docker-compose.yaml файл. Критерии:
19+
- Должен быть сервис приложения
20+
- Внутри должна быть команда запуска приложения
21+
22+
## Задание 5
23+
Создайте makefile с основными командами для работы с вашим проектом. Критерии:
24+
- Должны быть команды для запуска, перезагрузки, остановки проекта с помощью docker compose
25+
- Должны быть команды для запуска pre-commit check
26+
27+
## Задание 6
28+
Создайте файлы конфигурации `.env` и `.env.example`. `.env` должен быть в gitignore, а `.env.example` должен содержать структуру `.env` файла.
29+
Подключите `.env` в docker compose с помощью `env_file:`.
30+
Вынесите все свои токены из проекта в `.env`

simple_backend/README.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,46 @@
1-
Задание 1
2-
- создайте приложение main.py на fastapi
3-
- проверьте его на тестах
1+
# Задачи
2+
**Во всех заданиях обязательно создание pull-request по аналогии из задачи блока git!**
3+
4+
**Ваш код обязательно должен запускаться и проверяться на работоспособность**
5+
## Задание 1
6+
Нужно локально запустить backend приложение. Пройдитесь по шагам:
7+
1. Если еще не склонирован этот репозиторий, то склонируйте
8+
2. Создайте в папке `simple_backend/src/task_tracker` виртуальное окружение
9+
3. Активируйте `venv`
10+
4. Установите записимости из `requirements.txt`
11+
5. Запустите сервер с помощью команды `uvicorn main:app`
12+
6. Перейдите по ссылке http://127.0.0.1:8000/docs и проверьте, что бэк работает
13+
## Задание 2
14+
Вам нужно оживить бекенд из первой задачи и создать простой API для управления списком задач. Данные нужно хранить в оперативной памяти (например в списке Python).
15+
В каждой из функций нужно прописать логику:
16+
- get_tasks должен возвращать список всех задач
17+
- create_task должен создавать новую задачу
18+
- update_task должен обновлять информацию о задаче
19+
- delete_task должен удалять задачу
20+
21+
У задачи должны быть параметры: id, название, статус задачи.
22+
23+
### Подзадачи
24+
- Прочитайте, что такое "Хранение состояния", создайте в task_tracker readme.md файл и напишите в чём минусы подхода с хранением задач в оперативной памяти (списке python)
25+
- Исправьте ситуацию и переделайте хранение информации о задачах в файле проекта. Информацию можно хранить например в формате json.
26+
- Напишите в readme.md:
27+
- что улучшилось после того, как список из оперативной памяти изменился на файл проекта?
28+
- избавились ли мы таким способом от хранения состояния или нет?
29+
- где еще можно хранить задачи и какие есть преимущества и недостатки этих подходов?
30+
- Напишите класс для работы с файлом хранения задач в task_tracker и измените код проекта так, чтобы он работал с объектом этого класса.
31+
- Сделайте свой backend - stateless с помощью интеграции с облачным сервисом (jsonbin.io, mockapi.io, github gist). Организуйте хранение и обновление json файла во внешнем сервисе.
32+
- Прочитайте что такое "состояние гонки" и напишите в readme файле о том, какие проблемы остались в бекенде на данном этапе проекта. Есть ли у вас какое-то решение этой проблемы?
33+
34+
35+
## Задание 3
36+
Давайте прокачаем наш таск-треккер. Хочется, чтобы текст задачи заливался в LLM модель и она выдавала способы решения задачи и добавляла к её тексту.
37+
Для того, чтобы это сделать:
38+
- Настройте интеграцию с сервисом [Cloudflare](https://developers.cloudflare.com/workers-ai/get-started/rest-api/) через REST API. Для этого создайте новый класс для работы с этой API.
39+
- При создании новой задачи отправляйте запрос с её текстом в LLM и просьбой объяснить как решать задачу
40+
- Добавляйте полученный ответ в текст задачи
41+
42+
## Задание 4
43+
Заметили, что в коде для работы с файлами и в коде для работы с LLM API есть похожие участки? Давайте избавимся от дублирования через наследование.
44+
- Сделайте базовый класс BaseHTTPClient и вынесите в него общие функции и методы из двух классов
45+
- Сделайте наследование от базового класса в клиентах
46+
- С помощью абстрактных классов реализуйте абстрактные методы, которые должны быть в классах наследниках

simple_backend/orders.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
2+
class Order:
3+
TAX_RATE = 0.08 # 8% налог
4+
SERVICE_CHARGE = 0.05 # 5% сервисный сбор
5+
6+
def __init__(self, customer):
7+
self.customer = customer
8+
self.dishes = []
9+
10+
def add_dish(self, dish):
11+
if isinstance(dish, Dish):
12+
self.dishes.append(dish)
13+
else:
14+
raise ValueError("Можно добавлять только объекты класса Dish.")
15+
16+
def remove_dish(self, dish):
17+
if dish in self.dishes:
18+
self.dishes.remove(dish)
19+
else:
20+
raise ValueError("Такого блюда нет в заказе.")
21+
22+
def calculate_total(self):
23+
return sum(dish.price for dish in self.dishes)
24+
25+
26+
def final_total(self):
27+
total_after_discount = self.apply_discount()
28+
total_with_tax = total_after_discount * (1 + Order.TAX_RATE)
29+
final_total = total_with_tax * (1 + Order.SERVICE_CHARGE)
30+
return final_total
31+
32+
def apply_discount(self):
33+
discount_rate = self.customer.get_discount() / 100
34+
return self.calculate_total() * (1 - discount_rate)
35+
36+
def __str__(self):
37+
dish_list = "\n".join([str(dish) for dish in self.dishes])
38+
return f"Order for {self.customer.name}:\n{dish_list}\nTotal: ${self.final_total():.2f}"
39+
40+
41+
class GroupOrder(Order):
42+
def __init__(self, customers):
43+
super().__init__(customer=None) # Групповой заказ не привязан к одному клиенту
44+
self.customers = customers
45+
46+
def split_bill(self):
47+
if not self.customers:
48+
raise ValueError("Нет клиентов для разделения счета.")
49+
total = self.final_total()
50+
return total / len(self.customers)
51+
52+
def __str__(self):
53+
customer_list = ", ".join([customer.name for customer in self.customers])
54+
dish_list = "\n".join([str(dish) for dish in self.dishes])
55+
return f"Group Order for {customer_list}:\n{dish_list}\nTotal: ${self.final_total():.2f}"
56+
57+
class Dish:
58+
def __init__(self, name, price, category):
59+
self.name = name
60+
self.price = price
61+
self.category = category
62+
63+
def __str__(self):
64+
return f"Dish: {self.name}, Category: {self.category}, Price: ${self.price:.2f}"
65+
66+
class Customer:
67+
def __init__(self, name, membership="Regular"):
68+
self.name = name
69+
self.membership = membership
70+
71+
def get_discount(self):
72+
if self.membership == "VIP":
73+
return 10 # VIP клиенты получают 10% скидки
74+
return 0 # Обычные клиенты не получают скидки
75+
76+
def __str__(self):
77+
return f"Customer: {self.name}, Membership: {self.membership}"
78+
# Пример использования
79+
80+
# Создаем блюда
81+
pizza = Dish("Pizza", 12, "Main Course")
82+
ice_cream = Dish("Ice Cream", 5, "Dessert")
83+
coffee = Dish("Coffee", 3, "Drink")
84+
85+
# Создаем клиентов
86+
regular_customer = Customer("Alice", "Regular")
87+
vip_customer = Customer("Bob", "VIP")
88+
89+
# Индивидуальный заказ
90+
order1 = Order(regular_customer)
91+
order1.add_dish(pizza)
92+
order1.add_dish(ice_cream)
93+
94+
print(order1) # Вывод информации о заказе
95+
print(f"Final Total: ${order1.final_total():.2f}") # Итоговая стоимость
96+
97+
# Групповой заказ
98+
group_order = GroupOrder([regular_customer, vip_customer])
99+
group_order.add_dish(pizza)
100+
group_order.add_dish(ice_cream)
101+
group_order.add_dish(coffee)
102+
103+
print(group_order) # Вывод информации о групповом заказе
104+
print(f"Split Bill: ${group_order.split_bill():.2f} per person") # Стоимость на каждого

0 commit comments

Comments
 (0)