FastAPI Python

Введение

FastAPI — веб-фреймворк для создания API, написанный на Python. Один из самых быстрых и популярных (после Django и Flask) веб-фреймворков, написанных на Python (на 2023 год).

FastAPI активно использует декораторы, аннотации типов и интроспекцию кода, что позволяет уменьшить количество шаблонного кода в веб-приложении. FastAPI автоматически генерирует и отображает документацию согласно спецификации OpenAPI.

В основе FastAPI лежат две библиотеки — Starlette (ASGI-фреймворк) и Pydantic (для описания схем данных); FastAPI склеивает их и реализует некоторые дополнительные возможности — регистрацию представлений через внедрение зависимостей, работу с аутентификацией и авторизацией, автоматическую генерацию документации и другое.

Возможно использование как асинхронных, так и синхронных представлений.

Самый любимый (most loved) python-веб-фреймворк по опросу среди разработчиков на портале Stack Overflow (2021, 2022).

Официальный сайт - https://fastapi.tiangolo.com/

Некоторые изображения будет лучше видно если открывать их в отдельной вкладке.

Установка

python -m venv venv
source venv/Scripts/activate
touch requirements.txt
vi requirements.txt

fastapi==0.110.1 uvicorn==0.29.0

python -m pip install --upgrade pip
python -m pip install -r requirements.txt

Collecting fastapi Downloading fastapi-0.110.0-py3-none-any.whl (92 kB) |████████████████████████████████| 92 kB 5.7 MB/s Collecting uvicorn[standard] Downloading uvicorn-0.27.1-py3-none-any.whl (60 kB) |████████████████████████████████| 60 kB 4.1 MB/s Collecting typing-extensions>= 4.8.0 Downloading typing_extensions-4.10.0-py3-none-any.whl (33 kB) Collecting starlette<0.37.0,>= 0.36.3 Downloading starlette-0.36.3-py3-none-any.whl (71 kB) |████████████████████████████████| 71 kB 4.5 MB/s Collecting pydantic&33;= 1.8,&33;= 1.8.1,&33;= 2.0.0,&33;= 2.0.1,&33;= 2.1.0,<3.0.0,>= 1.7.4 Downloading pydantic-2.6.2-py3-none-any.whl (394 kB) |████████████████████████████████| 394 kB 6.4 MB/s Collecting pydantic-core= = 2.16.3 Downloading pydantic_core-2.16.3-cp38-none-win_amd64.whl (1.9 MB) |████████████████████████████████| 1.9 MB ... Collecting annotated-types>= 0.4.0 Downloading annotated_types-0.6.0-py3-none-any.whl (12 kB) Collecting anyio<5,>= 3.4.0 Downloading anyio-4.3.0-py3-none-any.whl (85 kB) |████████████████████████████████| 85 kB ... Collecting idna>= 2.8 Downloading idna-3.6-py3-none-any.whl (61 kB) |████████████████████████████████| 61 kB 4.5 MB/s Collecting exceptiongroup>= 1.0.2 Downloading exceptiongroup-1.2.0-py3-none-any.whl (16 kB) Collecting sniffio>= 1.1 Downloading sniffio-1.3.1-py3-none-any.whl (10 kB) Collecting h11>= 0.8 Downloading h11-0.14.0-py3-none-any.whl (58 kB) |████████████████████████████████| 58 kB 3.8 MB/s Collecting click>= 7.0 Using cached click-8.1.7-py3-none-any.whl (97 kB) Collecting websockets>= 10.4 Downloading websockets-12.0-cp38-cp38-win_amd64.whl (124 kB) |████████████████████████████████| 124 kB 6.8 MB/s Collecting httptools>= 0.5.0 Downloading httptools-0.6.1-cp38-cp38-win_amd64.whl (60 kB) |████████████████████████████████| 60 kB ... Collecting colorama>= 0.4 Downloading colorama-0.4.6-py2.py3-none-any.whl (25 kB) Collecting python-dotenv>= 0.13 Downloading python_dotenv-1.0.1-py3-none-any.whl (19 kB) Collecting pyyaml>= 5.1 Downloading PyYAML-6.0.1-cp38-cp38-win_amd64.whl (157 kB) |████████████████████████████████| 157 kB ... Collecting watchfiles>= 0.13 Downloading watchfiles-0.21.0-cp38-none-win_amd64.whl (279 kB) |████████████████████████████████| 279 kB 6.8 MB/s Installing collected packages: typing-extensions, sniffio, idna, exceptiongroup, colorama, pydantic-core, h11, click, anyio, annotated-types, websockets, watchfiles, uvicorn, starlette, pyyaml, python-dotenv, pydantic, httptools, fastapi Successfully installed annotated-types-0.6.0 anyio-4.3.0 click-8.1.7 colorama-0.4.6 exceptiongroup-1.2.0 fastapi-0.110.0 h11-0.14.0 httptools-0.6.1 idna-3.6 pydantic-2.6.2 pydantic-core-2.16.3 python-dotenv-1.0.1 pyyaml-6.0.1 sn sniffio-1.3.1 starlette-0.36.3 typing-extensions-4.10.0 uvicorn-0.27.1 watchfiles-0.21.0 websockets-12.0

Запуск сервиса

Если файл с кодом называется main.py а приложение вызывается как app, сервис будет запускаться командой uvicorn main:app

# main.py … app = FastAPI() …

uvicorn main:app

FastAPI запущенный таким образом остановится если вы закроете терминал. Если вы подлкючаетесь к облачному серверу uvicorn остановится после оключения.

Чтобы FastAPI постоянно работал на моём облачном сервере Beget я устанавливаю gunicorn и затем выполняю команду

gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80

Простейший пример

touch main.py
vi main.py

from fastapi import FastAPI app = FastAPI() @app.get("/") async def root(): return {"message": "Hello World"}

uvicorn main:app

INFO: Started server process [33972] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

main - так как наш скрипт main.py

app - так как app = FastAPI()

uvicorn main:app --port 8003 --reload

INFO: Will watch for changes in these directories: ['C:\Users\Andrei\sandbox\python\fastapi'] INFO: Uvicorn running on http://127.0.0.1:8003 (Press CTRL+C to quit) INFO: Started reloader process [32404] using WatchFiles INFO: Started server process [19652] INFO: Waiting for application startup. INFO: Application startup complete.

FastAPI Hello World изображение с сайта www.devhops.ru
FastAPI
Hello World

Благодаря --reload внесённые изменения подгрузятся автоматически

from fastapi import FastAPI app = FastAPI() @app.get("/", description="This is our first route.") async def root(): return {"message": "Hello World"} @app.post("/") async def post(): return {"message": "hello from the post route"} @app.put("/") async def put(): return {"message": "hello from the put route"}

Если в браузере открыть

localhost:8003/docs

Появится автоматически созданная документация Swagger

FastAPI Swagger изображение с сайта www.devhops.ru
FastAPI
Swagger

Если обратится по несуществующему адресу FastAPI вернёт сообщение

detail "Not Found"

FastAPI detail not found изображение с сайта www.devhops.ru
FastAPI
detail Not Found
FastAPI item_id изображение с сайта www.devhops.ru
FastAPI
item id

Проверка типа

В предыдущем примере мы не использовали type hints при объявлении get_item(item_id)

… @app.get("/items/{item_id}") async def get_item(item_id): return {"item_id": item_id}

FastAPI без type hints изображение с сайта www.devhops.ru
FastAPI
Любой тип

Если теперь с помощью type hint указать тип как int, обратиться к строке уже не получится

… @app.get("/items/{item_id}") async def get_item(item_id: int): return {"item_id": item_id}

FastAPI int изображение с сайта www.devhops.ru
FastAPI
Только int

int по-прежнему работает

FastAPI без type hints изображение с сайта www.devhops.ru
FastAPI
Только int

Swagger docs

Как уже было показано выше обратившись к /docs можно изучить Swagger документацию к нашему API

Протестируем её на примере item_id

Прямо из документации можно отправить curl запрос с желаемым id. 0 как целое число приведёт к успеху

FastAPI без type hints изображение с сайта www.devhops.ru
FastAPI
Только int

Обратите внимание на Response body

{ "item_id": 0 }

Здесь 0 это число. Если бы type hint был не int а str, 0 всё-равно можно бы было передать, просто вернулся бы он как строка

{ "item_id": "0" }

"test" как строка приведёт к ошибке.

FastAPI без type hints изображение с сайта www.devhops.ru
FastAPI
Только int

FastAPI перебирает адреса сверху вниз. Если вам нужно, чтобы у какого-то адрес был приоритет, ставьте его выше.

Например, если я хочу выделить специальный item с именем default нельзя объявить метод снизу.

… @app.get("/items/{item_id}") async def get_item(item_id: str): return {"item_id": item_id} @app.get("/items/default") async def get_default_item(): return {"message": "This is default item"}

В этом случае все мой обращения к

http://localhost:8003/items/default

Будут возвращать

item_id "default"

Так как до последнего адреса FastAPI просто не дойдет - предыдущий полностью подходит под запрос.

Если теперь поменять функции местами, default будет выполняться раньше чем всё остальное, но на все остальные запросы это никак не повлияет.

… @app.get("/items/default") async def get_default_item(): return {"message": "This is default item"} @app.get("/items/{item_id}") async def get_item(item_id: str): return {"item_id": item_id}

http://localhost:8003/items/default

message "This is default item"

http://localhost:8003/items/regulat

item_id "regular"

INFO: 127.0.0.1:65380 - "GET /favicon.ico HTTP/1.1" 404 Not Found

Автор статьи: Андрей Олегович