Целью данной лабораторной работы является создание веб-приложения для работы с абстрактными элементами (items). Для достижения этой цели мы будем использовать язык программирования Python, веб-фреймворк FastAPI и контейнеризацию с помощью Docker. Работа включает в себя установку необходимых инструментов, написание кода приложения, и контейнеризацию для обеспечения удобства развертывания.
Теоретическая база
Python и FastAPI
Python выбран в качестве основного языка программирования из-за его простоты и мощности. FastAPI был выбран как веб-фреймворк благодаря высокой производительности и интуитивно понятному синтаксису.
Docker
Docker используется для контейнеризации приложения, что позволяет упаковать все зависимости и окружение в один контейнер. Это обеспечивает переносимость и упрощает развертывание приложения.
Цели и задачи
Установка Python и Docker.
Создание виртуального окружения и установка зависимостей.
Написание кода веб-приложения с использованием FastAPI.
Тестирование приложения.
Создание Docker-образа и запуск контейнера.
Проверка работоспособности приложения в контейнере.
Технические требования
Python
Docker
pip-tools для управления зависимостями
FastAPI для разработки веб-приложения
Ожидаемые результаты
Виртуальное окружение с установленными зависимостями.
Работающее веб-приложение с возможностью добавления, изменения, удаления и получения элементов.
Docker-образ, готовый для развертывания приложения.
Проверка работоспособности приложения в контейнере.
Выполнение лабораторной работы
Настройка виртуального окружения и зависимостей:
Склонируйте данный репозиторий
Перейдите в него
-
Создайте виртуальное окружение
python3 -m venv venv
-
Активируйте его
source venv/bin/activate
-
Установите pip-tools
pip install pip-tools
Создайте файл requirements.in и откройте его
-
Запишите в данный файл следующие строки
fastapi
uvicorn
-
Далее в консоли с активированным виртуальным окружением пишем
pip-compile
-
У нас автоматически создаться requirements.txt, в котором будут библиотеки и фреймворки совместимых версий. Далее данной командой все библиотеки установятся в наше виртуальное окружение.
pip-sync
Написание веб-приложения:
Мы разработаем простое веб-приложение, которое будет работать с абстрактными элементами (назовем их items). Наше приложение сможет хранить items, также по запросу пользователя приложение сможет добавлять новые items, изменять их и удалять. В item будут лежать данные о его названии, цене и наличии.
1. Создать файл main.py и запустить его
2. Далее импортируем классы FastAPI, HTTPException, status для дальнейшей работы с ними
from fastapi import FastAPI, HTTPException, status
3. Создаем экземпляр класса FastAPI для создания нашего приложения. Также
нам потребуется создать список items для хранения данных и переменную
items_id для идентификации items
app = FastAPI()
items = []
items_id = 1
4. Создадим первый endpoint, который будет возвращать сообщение "Hello World"
@app.get('/')
def root():
return {'message': 'Hello World'}
5. Далее создадим endpoint '/items', который будет обрабатывать GET-запросы и возвращать требуемый item, если он существует
@app.get('/items')
def get_item(item_id: int):
try:
return items[item_id - 1]
except IndexError:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='item does not exists')
Проверка существования item происходит за счёт блока try except. Если элемента с переданным id не существует, будет вызвано исключение IndexError, так как мы выйдем за границы нашего списка. Если элемента не существует, мы оповещаем об этом пользователя статус-кодом 400 и сообщением "item does not exists"
6. Теперь создадим endpoint '/items', который будет обрабатывать POST-запросы, создавать item и добавлять его в список items
@app.post('/items')
def create_item(name: str, coast: float, in_stock: bool):
global items_id
item = {
'id': items_id,
'name': name,
'coast': coast,
'in_stock': in_stock
}
items.append(item)
items_id += 1
return item
Мы работаем с глобальной переменной items_id, так как у каждого item должен быть уникальный id, поэтому перед return увеличиваем её на 1
7. По этому же endpoint будем обрабатывать PATCH-запросы для частичного изменения требуемого item
@app.patch('/items')
def patch_item(item_id: int, new_name: str = None, new_coast: float = None, new_in_stock: bool = None):
try:
if new_name:
items[item_id - 1]['name'] = new_name
return items[item_id - 1]
elif new_coast:
items[item_id - 1]['coast'] = new_coast
return items[item_id - 1]
elif new_in_stock:
items[item_id - 1]['in_stock'] = new_in_stock
return items[item_id - 1]
except IndexError:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='item does not exists')
Так же, как и при обработке GET-запросов будем проверять, существует ли требуемый item
8. Теперь будем обрабатывать PUT-запросы для полного изменения требуемого item
@app.put('/items')
def patch_item(item_id: int, new_name: str, new_coast: float, new_in_stock: bool):
try:
items[item_id - 1]['name'] = new_name
items[item_id - 1]['coast'] = new_coast
items[item_id - 1]['in_stock'] = new_in_stock
return items[item_id - 1]
except IndexError:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='item does not exists')
Все так же, как и в прошлом пункте, только требуемый item изменяется полностью
9. Создадим обработчик DELETE-запросов для удаления требуемого item
@app.delete('/items')
def delete_item(item_id: int):
global items_id
try:
if item_id != len(items):
for index in range(item_id, len(items)):
items[index]['id'] -= 1
item = items.pop(item_id - 1)
items_id -= 1
return item
except IndexError:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='item does not exists')
Наше приложение готово. Теперь можно запустить его и проверить работоспособность.
Запуск веб-приложения:
Теперь мы можем запустить наше веб-приложение, для этого введем команду
uvicorn main:app
Теперь мы можем перейти по ссылке localhost:8000/docs и делать запросы к нашему приложению
Контейнеризация приложения:
1. Создать Dockerfile и запустить его
2. Сначала импортируем образ python
FROM python:yourversion
Вместо yourversion напишите вашу версию python
3. Теперь сделаем рабочую директорию app
WORKDIR /app
4. Далее скопируем файлы, необходимые для работы нашего приложения
COPY ./main.py /app
COPY ./requirements.txt /app
5. Установим зависимости внутри образа
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
6. Наконец, напишем последнюю команду, которая будет исполняться при запуске контейнера
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
Флаг --host 0.0.0.0 отвечает за то, чтобы наше веб-приложение работало на всех сетевых интерфейсах контейнера на порту 80.
7. Теперь создадим образ. Впишем в консоли
docker build -t fastapiapp .
8. Запустим контейнер, используя образ fastapiapp
docker run -d --name fastapiapp -p 8000:80 fastapiapp
Флаг -d отвечает за запуск контейнера в фоновом режиме. Флагом --name задаем имя нашему контейнеру. Флагом -p мы пробрасываем 80 порт контейнера на 8000 порт хоста
Проверка работоспособности:
Теперь можем перейти по ссылке localhost:8000/docs и проверить работоспособность нашего веб-приложения
Чтобы остановить работу контейнера, надо прописать данную команду в консоль
docker stop fastapiapp
Выводы
Лабораторная работа предоставила ценный опыт в области разработки backend-приложений, особенно в использовании веб-фреймворка FastAPI. Этот опыт оказывается крайне полезным для студентов и профессионалов, стремящихся погрузиться в мир веб-разработки и создания надежных серверных приложений.
Кроме того, работа с Docker в контексте данного проекта предоставила понимание принципов контейнеризации и умения упаковывать приложения со всеми их зависимостями в единый контейнер. Этот подход обеспечивает переносимость и удобство развертывания приложений в различных средах.
Особое внимание уделялось работе с портами в контейнере, что является важным навыком при создании масштабируемых и безопасных веб-приложений. Практика работы с портами в контейнере предоставляет необходимые навыки для эффективного управления входящими и исходящими сетевыми соединениями.
Таким образом, лабораторная работа не только дала практический опыт в создании веб-приложений с использованием FastAPI, но также подготовила к важным аспектам контейнеризации и управлению сетевыми портами в контейнеризированных приложениях.
Список использованных источников:
Примеры кода и руководства по созданию веб-приложений с FastAPI.
Официальные ресурсы Python и pip-tools
Комментарии (12)
icya
15.11.2023 11:54Странно, в документации FastAPI есть и туториал, и пример использования (и оно даже на русский язык переведено), а у вас всё равно получилось что-то иное.
Предлагаю потратить время и доработать приложение в соответствиями с тем, что пишут в документации чтобы не ограничиваться только целью лабораторной работы. И, пожалуйста, пишите юнит тесты. Они нужны этому приложению.
keystore
15.11.2023 11:54+3Вроде как лучше надо так: сначала
COPY ./requirements.txt /app
RUN pip install
А потом уже копировать исходники, чтобы не скачивать зависимости при каждом изменении исходников
vasya3
15.11.2023 11:54+1После:
Написание веб-приложения:...
1. Создать файл main.py и запустить еготупик. touch main.py nano main.py ?
Aloxiy Автор
15.11.2023 11:54Чтобы создать файл, нужно написать
touch main.py
Дальше, с помощью любого текстового редактора запустить файл, текстовый редактор gedit имеет интерфейс, поэтому можно написать
gedit main.py
Но если хочешь, можно использовать и nano
sh-vasily
15.11.2023 11:54Почему в post методе передается не объект, а поля по отдельности в качестве аргументов?
unstopppable
15.11.2023 11:54Вместо global обьявить можно так:
app.get_ids = []
И все методы спокойно могут обращаться без глобалок
sh-vasily
15.11.2023 11:54except IndexError: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='item does not exists')
Здесь всего в двух строках содержится уйма проблем:
1. BAD_REQUEST здесь не подходит. Холиварный вопрос, конечно, но BAD_REQUEST обычно означает, что клиент неправильно составил запрос(к примеру, передал строку вместо числа), здесь же клиент передал число(иначе fast api сам автоматически обработает некорректные данные). И вот допустим, кто-то захочет использовать это API и задумается, почему, когда он делает GET /items/1 все нормально, а когда GET /items/2 - возвращается BAD_REQUEST. Понятно, что догадаться можно, на практике приходилось сталкиваться и с куда более ужасными API, но это все таки обучающая статья, и она должна придерживаться принятых стандартов.
2. Действительно, fast api умеет преобразовывать исключения в response c соответствующим кодом, но это сделано больше для защиты от необработанных исключений, так что это не повод возвращать любой код, отличный от 2XX. Лучше возвращать Response. Исключения на то и исключения, чтобы выражать через них только экстраординарные ситуации, которых по идее возникнуть не должно, а здесь элемент не найден - вполне стандартная ситуация. Вот пример: https://github.com/sh-vasily/nosql-2023/blob/74073a6095fe1675e12be12d0e2f2f85936dc4ee/router/students_router.py#L26
3. Тоже в тему исключений: зачем вы отлавливаете IndexError, если можно сразу вначале проверить, есть ли значение с таким индексом? IndexError это ошибка, при этом то, что если клиент запросил по индексу, которого нет, это вполне ожидаемо. Исключения нельзя использовать для выражения ожидаемого поведения, они нарушают структуру кода, создавая множество скрытых точек выхода, что затрудняет чтение и изучение кода. Может быть в реальности на очень маленьком проекте, где весь код умещается в одной функции можно забить, но в больших приложениях это чревато проблемами. И опять же я бы понял, если бы это сделал стартап, где нужно срочно успеть сделать новую фичу для инвесторов и нет времени наводить красоту кода, но здесь учебная статья, зачем учить людей писать плохой код?Next
15.11.2023 11:54По п.3 конкретно в python это же считается нормальным, типа
try: os.makedirs(new_dir) except FileExistsError: pass
sh-vasily
15.11.2023 11:54Хм, не буду спорить, потому что на питоне в крупных проектах не участвовал. Но конкретно при работе с массивом я бы предпочел все таки if'ом индекс проверить)
chemdev
Кто-то пользуется докер отдельно от docker-compose? Ведь как минимум всегда нужно прописать порты, зачем это каждый раз делать в терминале.