Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3272ede
fix: main
Nov 21, 2024
6d52a1d
fix: first-branch
Nov 21, 2024
f32a7fa
fix: second task
Nov 21, 2024
5352f5e
fix: tasks
Nov 21, 2024
de13048
fix: tasks
Nov 21, 2024
8b2b714
feat: update with new lesson
Feb 17, 2025
87afae5
feat: update tasks
Feb 17, 2025
adc836d
fix: tasks
Feb 17, 2025
2c9dd50
Update README.md
gardiys Feb 25, 2025
20987c8
Update README.md
gardiys Feb 27, 2025
0f4f66e
feat: change task
Feb 27, 2025
cd298cc
Merge branch 'main' of github.com:gardiys/fastapi-backend-course
Feb 27, 2025
4126798
Update README.md
gardiys Mar 7, 2025
bb5a993
Update README.md
gardiys Mar 7, 2025
c9d3378
fixed conflict
maloyparser Sep 22, 2025
0eba75a
Task1
maloyparser Oct 4, 2025
dc27349
Merge branch 'second-branch' of https://github.com/maloyparser/fastap…
maloyparser Oct 4, 2025
169e70f
task2 backend with state storage
maloyparser Oct 4, 2025
269a8a7
storage class with json
maloyparser Oct 4, 2025
dc06153
Add files via upload
maloyparser Oct 4, 2025
efd0bb4
storage gist add
maloyparser Oct 4, 2025
1f1627c
Merge branch 'second-branch' of https://github.com/maloyparser/fastap…
maloyparser Oct 4, 2025
073cc99
Delete simple_backend/src/task_tracker/.env
maloyparser Oct 4, 2025
cafa301
Delete simple_backend/src/task_tracker/tasks.json
maloyparser Oct 4, 2025
128e409
Delete simple_backend/src/task_tracker/main.py
maloyparser Oct 4, 2025
d77a82e
Delete simple_backend/src/task_tracker/storage.py
maloyparser Oct 4, 2025
712ad57
Delete simple_backend/src/task_tracker/storage_gist.py
maloyparser Oct 4, 2025
2b800b3
del
maloyparser Oct 4, 2025
cd764d6
Merge branch 'second-branch' of https://github.com/maloyparser/fastap…
maloyparser Oct 4, 2025
35e423d
storage github gist add
maloyparser Oct 4, 2025
f6f5ead
Create README.md
maloyparser Oct 4, 2025
7eb183a
Update README.md
maloyparser Oct 4, 2025
443cd77
cludflare add
maloyparser Oct 4, 2025
26f94bc
Merge branch 'second-branch' of https://github.com/maloyparser/fastap…
maloyparser Oct 4, 2025
4943856
BaseHTTPClient add
maloyparser Oct 5, 2025
5dce8a4
add workflows
maloyparser Oct 9, 2025
4584deb
correct smth
maloyparser Oct 9, 2025
ec3de8d
Update ci.yml
maloyparser Oct 9, 2025
e291024
Update ci.yml
maloyparser Oct 9, 2025
f79221f
Create .ruff.toml
maloyparser Oct 9, 2025
46ef92e
Update .ruff.toml
maloyparser Oct 9, 2025
c152ca9
fix giststorage, put now works
maloyparser Oct 9, 2025
a6f9490
Merge branch 'second-branch' of https://github.com/maloyparser/fastap…
maloyparser Oct 9, 2025
6f9945f
fix gist storage
maloyparser Oct 9, 2025
9011e89
del comment httpclient
maloyparser Oct 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI

on:
push:
branches: [ main, second-branch ]
pull_request:
branches: [ main, second-branch ]

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 check . --fix --line-length 88 || true
- name: Run Ruff linter
run: |
ruff check . --line-length 88
10 changes: 10 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
line-length = 88
select = ["E", "F"]
ignore = ["F401", "W391", "E501"]
exclude = [
"venv",
"migrations",
".git",
"__pycache__",
"node_modules"
]
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ git clone [email protected]:gardiys/fastapi-backend-course.git
- [ ] [Урок 3: Настройка окружения](./setting_environment/)
- [ ] [Урок 4: Использование хранилищ](./using_storage/)
- [ ] [Урок 5: Архитектура проекта](./project_architecture/)
- [ ] [Урок 6: Общение сервисов](./services_communication/)
- [ ] [Урок 7: Тестирование](./testing/)
- [ ] [Урок 8: Деплой сервисов](./services_deploy/)
- [ ] [Урок 9: Отказоустойчивость](./fault_tolerance/)
- [ ] [Урок 10: Мониторинг](./monitoring/)
- [ ] [Урок 6: Фоновые задачи](./async_tasks/)
- [ ] [Урок 7: Общение сервисов](./services_communication/)
- [ ] [Урок 8: Тестирование](./testing/)
- [ ] [Урок 9: Деплой сервисов](./services_deploy/)
- [ ] [Урок 10: Отказоустойчивость](./fault_tolerance/)
- [ ] [Урок 11: Мониторинг](./monitoring/)
Empty file added async_tasks/README.md
Empty file.
3 changes: 2 additions & 1 deletion git/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
3. Создать новую ветку с названием задачи "pull-request-task" и переключиться на неё
4. В проекте создать два новых файла main.py и config.py
5. Закоммитить изменения и запушить ветку в репозиторий
6. Затем создать пулл-реквест
6. Затем создать пулл-реквест с ветки pull-request-task на ветку main

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

## Задание 3: простой CI
Нужно добавить конфигурацию CI с помощью GitHub Actions в свой репозиторий.
Expand Down
18 changes: 7 additions & 11 deletions git/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def load_books(filename='library.json'):
except json.JSONDecodeError:
return []

def saving_books(books, filename='library.json'):
def save_books(books, filename='library.json'):
"""
Сохранение списка книг в JSON-файл.
"""
Expand Down Expand Up @@ -94,17 +94,18 @@ def main():
# Получаем новый список с добавленной книгой
new_books = add_book(books, title, author, year)
books = new_books # Обновляем переменную, чтобы сохранить изменения
saving_books(books) # Сразу сохраняем в файл
save_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):

if len(new_books) < len(books):
books = new_books
saving_books(books)
save_books(books)
print("Книга удалена!")
else:
print("Книга с таким названием не найдена.")
Expand All @@ -119,17 +120,12 @@ def main():
else:
print("Ничего не найдено.")

elif choice == '6':
elif choice == '5':
print("Выход из программы.")
break

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






if __name__ == "__main__":
main()
main()
40 changes: 30 additions & 10 deletions setting_environment/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
Задание 1
настройка Poetry
Задание 2
Создание docker образа
Задание 3
создание docker compose файла
Задание 4
линтеры и pre-commit хуки
задание 5
конфигурация проектов
# Задачи

## Задание 1
Переведите проект, полученный в разделе [Простой backend](/simple_backend/README.md) на Poetry.

## Задание 2
Установите в dev группу poetry линтер ruff. Настройте pre-commit хуки для ruff check и ruff format.
При каждом коммите у вас должны запускаться проверки с помощью линтеров.

## Задание 3
Заверните проект в Docker-образ. Критерии:
- Должен быть slim
- Внутри должны устанавливаться poetry зависимости
- Образ должен собираться
- С помощью образа можно запустить приложение на FastAPI

## Задание 4
Создайте docker-compose.yaml файл. Критерии:
- Должен быть сервис приложения
- Внутри должна быть команда запуска приложения

## Задание 5
Создайте makefile с основными командами для работы с вашим проектом. Критерии:
- Должны быть команды для запуска, перезагрузки, остановки проекта с помощью docker compose
- Должны быть команды для запуска pre-commit check

## Задание 6
Создайте файлы конфигурации `.env` и `.env.example`. `.env` должен быть в gitignore, а `.env.example` должен содержать структуру `.env` файла.
Подключите `.env` в docker compose с помощью `env_file:`.
Вынесите все свои токены из проекта в `.env`
49 changes: 46 additions & 3 deletions simple_backend/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
Задание 1
- создайте приложение main.py на fastapi
- проверьте его на тестах
# Задачи
**Во всех заданиях обязательно создание pull-request по аналогии из задачи блока git!**

**Ваш код обязательно должен запускаться и проверяться на работоспособность**
## Задание 1
Нужно локально запустить backend приложение. Пройдитесь по шагам:
1. Если еще не склонирован этот репозиторий, то склонируйте
2. Создайте в папке `simple_backend/src/task_tracker` виртуальное окружение
3. Активируйте `venv`
4. Установите записимости из `requirements.txt`
5. Запустите сервер с помощью команды `uvicorn main:app`
6. Перейдите по ссылке http://127.0.0.1:8000/docs и проверьте, что бэк работает
## Задание 2
Вам нужно оживить бекенд из первой задачи и создать простой API для управления списком задач. Данные нужно хранить в оперативной памяти (например в списке Python).
В каждой из функций нужно прописать логику:
- get_tasks должен возвращать список всех задач
- create_task должен создавать новую задачу
- update_task должен обновлять информацию о задаче
- delete_task должен удалять задачу

У задачи должны быть параметры: id, название, статус задачи.

### Подзадачи
- Прочитайте, что такое "Хранение состояния", создайте в task_tracker readme.md файл и напишите в чём минусы подхода с хранением задач в оперативной памяти (списке python)
- Исправьте ситуацию и переделайте хранение информации о задачах в файле проекта. Информацию можно хранить например в формате json.
- Напишите в readme.md:
- что улучшилось после того, как список из оперативной памяти изменился на файл проекта?
- избавились ли мы таким способом от хранения состояния или нет?
- где еще можно хранить задачи и какие есть преимущества и недостатки этих подходов?
- Напишите класс для работы с файлом хранения задач в task_tracker и измените код проекта так, чтобы он работал с объектом этого класса.
- Сделайте свой backend - stateless с помощью интеграции с облачным сервисом (jsonbin.io, mockapi.io, github gist). Организуйте хранение и обновление json файла во внешнем сервисе.
- Прочитайте что такое "состояние гонки" и напишите в readme файле о том, какие проблемы остались в бекенде на данном этапе проекта. Есть ли у вас какое-то решение этой проблемы?


## Задание 3
Давайте прокачаем наш таск-треккер. Хочется, чтобы текст задачи заливался в LLM модель и она выдавала способы решения задачи и добавляла к её тексту.
Для того, чтобы это сделать:
- Настройте интеграцию с сервисом [Cloudflare](https://developers.cloudflare.com/workers-ai/get-started/rest-api/) через REST API. Для этого создайте новый класс для работы с этой API.
- При создании новой задачи отправляйте запрос с её текстом в LLM и просьбой объяснить как решать задачу
- Добавляйте полученный ответ в текст задачи

## Задание 4
Заметили, что в коде для работы с файлами и в коде для работы с LLM API есть похожие участки? Давайте избавимся от дублирования через наследование.
- Сделайте базовый класс BaseHTTPClient и вынесите в него общие функции и методы из двух классов
- Сделайте наследование от базового класса в клиентах
- С помощью абстрактных классов реализуйте абстрактные методы, которые должны быть в классах наследниках
104 changes: 104 additions & 0 deletions simple_backend/orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@

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.")

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 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 apply_discount(self):
discount_rate = self.customer.get_discount() / 100
return self.calculate_total() * (1 - discount_rate)

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") # Стоимость на каждого
34 changes: 34 additions & 0 deletions simple_backend/src/task_tracker/BaseHTTPClient.py
Original file line number Diff line number Diff line change
@@ -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

30 changes: 30 additions & 0 deletions simple_backend/src/task_tracker/Cloudflare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
import requests
from dotenv import load_dotenv
from BaseHTTPClient import BaseHTTPClient
load_dotenv()
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")
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"
}
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}
]
}
return self._post(json_data=payload)
def get_llm_response(self, text:str) -> str:
try:
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"
Loading