Привет, Хаброжители!
FastAPI — относительно новый, но надежный фреймворк с чистым дизайном, использующий преимущества актуальных возможностей Python. Как следует из названия, FastAPI отличается высоким быстродействием и способен конкурировать в этом с аналогичными фреймворками на таких языках, как Golang. Эта практическая книга расскажет разработчикам, знакомым с Python, как FastAPI позволяет достичь большего за меньшее время и с меньшим количеством кода.
Билл Любанович рассказывает о тонкостях разработки с применением FastAPI и предлагает множество рекомендаций по таким темам, как формы, доступ к базам данных, графика, карты и многое другое, что поможет освоить основы и даже пойти дальше. Кроме того, вы познакомитесь с RESTful API, приемами валидации данных, авторизации и повышения производительности. Благодаря сходству с такими фреймворками, как Flask и Django, вы легко начнете работу с FastAPI.
FastAPI во многом опирается на пакет Python с названием Pydantic. Для определения структур данных используются модели (объектные классы Python). Они широко применяются в приложениях FastAPI и становятся реальным преимуществом при написании больших приложений.
Пришло время узнать немного больше о подсказках типов в Python.
В главе 2 упоминалось, что во многих компьютерных языках переменная указывает непосредственно на значение в памяти. Это требует от программиста объявления типа значения, чтобы можно было определить его размер и разрядность. В Python переменные — это просто имена, связанные с объектами, и именно у объектов есть типы.
В стандартном программировании переменная обычно связана с одним и тем же объектом. Если мы свяжем с этой переменной подсказку типа, то сможем избежать некоторых ошибок в программировании. Поэтому Python добавил подсказки типов к языку, в стандартный модуль типизации. Интерпретатор Python игнорирует синтаксис подсказки типа и выполняет программу так, как будто ее нет. Тогда в чем смысл?
В одной строке вы можете рассматривать переменную как строку, а потом забыть и присвоить ей объект другого типа. Компиляторы других языков будут жаловаться, а Python этого не сделает. Стандартный интерпретатор Python отлавливает обычные синтаксические ошибки и исключения времени выполнения, но не смешивает типы переменных. Инструменты-помощники, такие как mypy, обращают внимание на подсказки типов и предупреждают о любых несоответствиях.
Кроме того, подсказки доступны разработчикам Python, которые могут написать инструменты, выполняющие не только проверку ошибок типов. В следующих разделах описывается, как пакет Pydantic был разработан для удовлетворения неочевидных потребностей. Позже вы увидите, как его интеграция с FastAPI значительно упрощает решение многих вопросов веб-разработки.
Кстати, как выглядят подсказки? Существует один синтаксис для переменных и другой — для возвращаемых значений функций.
Подсказки типа переменной могут включать только тип:
или также инициализировать переменную значением:
Тип может быть одним из стандартных простых типов Python, таких как
При использовании Python до версии 3.9 необходимо импортировать прописные версии стандартных имен типов из модуля типизации:
Вот несколько примеров с инициализацией:
Можно также включать подтипы коллекций:
Модуль типизации содержит полезные дополнения для подтипов. Наиболее распространенные из них следующие:
В Python, начиная с версии 3.10, можно написать type1 | type2, а не Union[type1,type2].
Примеры определений Pydantic для словарей (
Или, если быть более точными:
либо (в Python 3.10 и более поздних версиях):
Обратите внимание на то, что в Python строка переменной с подсказкой типа является верной, а простая строка переменной — нет:
Кроме того, некорректное использование типов не отлавливается обычным интерпретатором Python:
Но такие ошибки будут обнаружены mypy. Если у вас еще не установлен этот статический анализатор, наберите команду
В подсказке типа возврата функции вместо двоеточия применяется стрелка:
Вот пример возврата функции при использовании Pydantic:
Можно задействовать любой тип, включая определенные классы или их комбинации. Вы увидите это через несколько страниц.
Зачастую нам нужно сохранить связанную группу переменных, а не передавать множество отдельных переменных. Как объединить несколько переменных в группу и сохранить подсказки типа?
Давайте оставим в прошлом пример с простым приветствием из предыдущих глав и начнем использовать более богатые данные. Как и в остальных частях этой книги, я буду приводить примеры криптидов (воображаемых существ) и исследователей (тоже воображаемых), которые их ищут. Начальные определения криптидов будут включать в себя только строковые переменные для следующих параметров:
Пример 5.3 показывает, что вы можете получить немного больше объяснений, определив имена для целочисленных смещений.
Пример 5.3. Использование кортежей и именованных смещений
В примере 5.4 словари выглядят немного лучше, предоставляя доступ по описательным ключам.
Пример 5.4. Использование словаря
Множества содержат только уникальные значения, поэтому они не очень полезны для кластеризации различных переменных.
В примере 5.5 именованный кортеж — это кортеж, предоставляющий вам доступ по целочисленному смещению или имени.
Пример 5.5. Использование именованного кортежа
Нельзя написать namedtuple_thing[«name»]. Это будет
В примере 5.6 определяется новый класс Python под названием
набрать много текста.
Пример 5.6. Использование стандартного класса
Вы можете подумать: что в этом плохого? В обычном классе можно добавить больше данных (атрибутов), но особенно много поведения (методов). В один безумный день вы можете решить добавить метод для поиска любимых песен исследователя. (Это нельзя применить к существам1.) Но в данном случае речь идет о том, чтобы просто без помех перемещать сборки данных между уровнями и проверять их на входе и выходе. Кроме того, методы — это квадратные детали, которые с трудом помещаются в круглые отверстия базы данных.
Есть ли в Python что-то похожее на то, что в других компьютерных языках называется записью (record) или структурой (struct) (группа имен и значений)? Недавно в Python появился класс для хранения данных (dataclass). В примере 5.7 показано, как все эти self-выражения исчезают при использовании классов данных.
Пример 5.7. Применение класса данных
Это очень хорошо для части описания, связанной с сохранением переменных вместе. Но нам требуется больше, так что давайте попросим у Дедушки Мороза вот что:
Очень заманчиво использовать встроенные структуры данных Python, особенно словари. Но вы неизбежно обнаружите, что словари слишком свободны. А за свободу приходится платить. Вам нужно будет проверить абсолютно все.
По крайней мере три решения отвечают хотя бы некоторым из этих требований:
Удобное сравнение этих трех вариантов можно посмотреть на YouTube (https://oreil.ly/pkQD3). Одним из выводов является то, что Pydantic выделяется при проверке, а его интеграция с FastAPI позволяет выявить множество потенциальных ошибок в данных. Другое дело, что Pydantic полагается на наследование (от класса
В другом сравнении (https://oreil.ly/gU28a) Pydantic превзошел более старые пакеты проверки, такие как marshmallow (https://marshmallow.readthedocs.io) и библиотека с интригующим названием Voluptuous1 (https://github.com/alecthomas/voluptuous). Еще один большой плюс Pydantic в том, что он использует стандартный синтаксис подсказок типов Python — более старые библиотеки не применяли подсказки типов и создавали собственные.
В книге я остановился на Pydantic, но вы можете найти применение любой из альтернатив, если не используете FastAPI.
Pydantic предоставляет возможность задать любую комбинацию следующих проверок:
Вы уже видели, как передать простую строку в конечную точку веб-приложения через URL, параметр запроса или тело HTTP-запроса. Проблема в том, что обычно вы запрашиваете и получаете группы данных разных типов. Именно здесь в FastAPI впервые появляются модели Pydantic. В начальном примере будут использоваться три файла:
Для простоты в этой главе сохраним все файлы в одном каталоге. В последующих главах, посвященных более крупным веб-сайтам, мы разделим их на соответствующие уровни. Сначала определим модель существа в примере 5.8.
Пример 5.8. Определение модели существа: model.py
Класс
сказку типа — каждое из значений относится к строковому типу данных Python.
В этом примере все поля обязательны для заполнения. В Pydantic, если слово Optional отсутствует в описании типа, поле должно содержать значение.
В примере 5.9 аргументы передаются в любом порядке, если вы указываете их
имена.
Пример 5.9. Создание существа
Пока что в примере 5.10 определен небольшой источник данных. В последующих главах этим будут заниматься базы данных. Подсказка типа list[Creature] говорит Python, что это список только объектов Creature.
Пример 5.10. Определение фиктивных данных в файле data.py
(Мы использовали символ "*" для аргумента area объекта
Этот код импортирует написанный нами ранее файл model.py. Он немного скрывает данные, вызывая свой список объектов
В примере 5.11 приведен файл
Пример 5.11. Определение конечной точки веб-приложения FastAPI:
Теперь запустите этот сервер с одной конечной точкой в примере 5.12.
Пример 5.12. Запуск Uvicorn
В другом окне примера 5.13 осуществляется доступ к веб-приложению с помощью веб-клиента HTTPie (попробуйте использовать свой браузер или модуль Requests по желанию).
Пример 5.13. Проверка с помощью HTTPie
FastAPI и Starlette автоматически преобразуют исходный список объектов модели Creature в строку JSON. Это формат вывода по умолчанию в FastAPI, поэтому нам не нужно его указывать.
Кроме того, в окне, в котором вы первоначально запустили веб-сервер Uvicorn, должна быть выведена строка журнала:
В предыдущем разделе было показано, как сделать следующее:
А теперь действительно применим этот план для проверки данных. Попробуйте присвоить значение неправильного типа одному или нескольким полям объекта Creature. Для этого воспользуйтесь автономным тестом (Pydantic не применяется ни к какому веб-коду, он относится к данным).
В примере 5.14 показано содержимое файла
Пример 5.14. Проверка модели Creature
Теперь попробуйте выполнить тест из примера 5.15. Он показывает, что мы присвоили полю
Пример 5.15. Продолжение теста
Даже если тип значения соответствует его спецификации в классе
могут потребоваться дополнительные проверки. Некоторые ограничения могут быть наложены на само значение.
Они указываются в типовых частях модели.
Пример 5.16 позволяет убедиться, что поле name всегда будет содержать не менее двух символов. В противном случае "" (пустая строка) будет считаться допустимой.
Пример 5.16. Просмотр ошибки проверки
Ключевое слово constr означает ограниченную строку (constrained string).
В примере 5.17 используется альтернативный вариант — спецификация
Пример 5.17. Еще один сбой проверки, применена функция
Аргумент… функции
Это минимальное введение в Pydantic. Главное, что можно сделать, — автоматизировать проверку данных. Вы увидите, насколько это полезно, при получении данных с веб-уровня или уровня данных.
Модели предоставляют лучший способ определить данные, передаваемые в вашем веб-приложении. Библиотека Pydantic использует подсказки типов Python для определения моделей, передаваемых в приложении данных. Далее — определение зависимостей для выделения конкретных деталей из общего кода.
Более подробно с книгой можно ознакомиться на сайте издательства:
» Оглавление
» Отрывок
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Для Хаброжителей скидка 25% по купону — FastAPI
FastAPI — относительно новый, но надежный фреймворк с чистым дизайном, использующий преимущества актуальных возможностей Python. Как следует из названия, FastAPI отличается высоким быстродействием и способен конкурировать в этом с аналогичными фреймворками на таких языках, как Golang. Эта практическая книга расскажет разработчикам, знакомым с Python, как FastAPI позволяет достичь большего за меньшее время и с меньшим количеством кода.
Билл Любанович рассказывает о тонкостях разработки с применением FastAPI и предлагает множество рекомендаций по таким темам, как формы, доступ к базам данных, графика, карты и многое другое, что поможет освоить основы и даже пойти дальше. Кроме того, вы познакомитесь с RESTful API, приемами валидации данных, авторизации и повышения производительности. Благодаря сходству с такими фреймворками, как Flask и Django, вы легко начнете работу с FastAPI.
Для кого эта книга
Эта книга предназначена для опытных программистов, которые только начинают знакомиться с FastAPI. Книга дает исчерпывающее описание фреймворка FastAPI и окружающей его экосистемы, позволяя читателям быстро и полно ознакомиться с разработкой современных веб-приложений.
Структура книги
В двух главах части I книги обсуждаются новые темы в веб-разработке и языке Python — сервисы и API, конкурентность, многоуровневые архитектуры и большие-большие данные.
Часть II — это обзор FastAPI, свежего веб-фреймворка на Python. В этой части содержатся ответы на заданные в части I вопросы.
В части III мы углубляемся в инструментарий FastAPI, включая советы, полученные в процессе разработки.
Наконец, в части IV представлена галерея веб-примеров FastAPI. Для них использовался общий источник данных — список воображаемых существ, что может быть немного интереснее и целостнее, чем обычные случайные представления данных. Это должно дать вам отправную точку для конкретного применения этого веб-фреймворка.
Часть II — это обзор FastAPI, свежего веб-фреймворка на Python. В этой части содержатся ответы на заданные в части I вопросы.
В части III мы углубляемся в инструментарий FastAPI, включая советы, полученные в процессе разработки.
Наконец, в части IV представлена галерея веб-примеров FastAPI. Для них использовался общий источник данных — список воображаемых существ, что может быть немного интереснее и целостнее, чем обычные случайные представления данных. Это должно дать вам отправную точку для конкретного применения этого веб-фреймворка.
Pydantic, подсказки типов и обзор моделей
Обзор
FastAPI во многом опирается на пакет Python с названием Pydantic. Для определения структур данных используются модели (объектные классы Python). Они широко применяются в приложениях FastAPI и становятся реальным преимуществом при написании больших приложений.
Подсказки типов данных
Пришло время узнать немного больше о подсказках типов в Python.
В главе 2 упоминалось, что во многих компьютерных языках переменная указывает непосредственно на значение в памяти. Это требует от программиста объявления типа значения, чтобы можно было определить его размер и разрядность. В Python переменные — это просто имена, связанные с объектами, и именно у объектов есть типы.
В стандартном программировании переменная обычно связана с одним и тем же объектом. Если мы свяжем с этой переменной подсказку типа, то сможем избежать некоторых ошибок в программировании. Поэтому Python добавил подсказки типов к языку, в стандартный модуль типизации. Интерпретатор Python игнорирует синтаксис подсказки типа и выполняет программу так, как будто ее нет. Тогда в чем смысл?
В одной строке вы можете рассматривать переменную как строку, а потом забыть и присвоить ей объект другого типа. Компиляторы других языков будут жаловаться, а Python этого не сделает. Стандартный интерпретатор Python отлавливает обычные синтаксические ошибки и исключения времени выполнения, но не смешивает типы переменных. Инструменты-помощники, такие как mypy, обращают внимание на подсказки типов и предупреждают о любых несоответствиях.
Кроме того, подсказки доступны разработчикам Python, которые могут написать инструменты, выполняющие не только проверку ошибок типов. В следующих разделах описывается, как пакет Pydantic был разработан для удовлетворения неочевидных потребностей. Позже вы увидите, как его интеграция с FastAPI значительно упрощает решение многих вопросов веб-разработки.
Кстати, как выглядят подсказки? Существует один синтаксис для переменных и другой — для возвращаемых значений функций.
Подсказки типа переменной могут включать только тип:
name: type
или также инициализировать переменную значением:
name: type = value
Тип может быть одним из стандартных простых типов Python, таких как
int
илиstr
, или коллекцией, такой как tuple
, list
или dict
:thing: str = "yeti"
При использовании Python до версии 3.9 необходимо импортировать прописные версии стандартных имен типов из модуля типизации:
from typing import Str
thing: Str = "yeti"
Вот несколько примеров с инициализацией:
physics_magic_number: float = 1.0/137.03599913
hp_lovecraft_noun: str = "ichor"
exploding_sheep: tuple = "sis", "boom", bah!"
responses: dict = {"Marco": "Polo", "answer": 42}
Можно также включать подтипы коллекций:
name: dict[keytype, valtype] = {key1: val1, key2: val2}
Модуль типизации содержит полезные дополнения для подтипов. Наиболее распространенные из них следующие:
- Any — любой тип;
- Union — любой из указанных типов, например Union[
str
,int
].
В Python, начиная с версии 3.10, можно написать type1 | type2, а не Union[type1,type2].
Примеры определений Pydantic для словарей (
dict
) в Python включают следующее:from typing import Any
responses: dict[str, Any] = {"Marco": "Polo", "answer": 42}
Или, если быть более точными:
from typing import Union
responses: dict[str, Union[str, int]] = {"Marco": "Polo", "answer": 42}
либо (в Python 3.10 и более поздних версиях):
responses: dict[str, str | int] = {"Marco": "Polo", "answer": 42}
Обратите внимание на то, что в Python строка переменной с подсказкой типа является верной, а простая строка переменной — нет:
$ python
...
>>> thing0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name thing0 is not defined
>>> thing0: str
Кроме того, некорректное использование типов не отлавливается обычным интерпретатором Python:
$ python
...
>>> thing1: str = "yeti"
>>> thing1 = 47
Но такие ошибки будут обнаружены mypy. Если у вас еще не установлен этот статический анализатор, наберите команду
pip
install
mypy
. Сохраните две предыдущие строки в файле stuff.py
, а затем попробуйте выполнить следующие команды:$ mypy stuff.py
stuff.py:2: error: Incompatible types in assignment
(expression has type "int", variable has type "str")
Found 1 error in 1 file (checked 1 source file)
В подсказке типа возврата функции вместо двоеточия применяется стрелка:
function(args) -> type:
Вот пример возврата функции при использовании Pydantic:
def get_thing() -> str:
return "yeti"
Можно задействовать любой тип, включая определенные классы или их комбинации. Вы увидите это через несколько страниц.
Группировка данных
Зачастую нам нужно сохранить связанную группу переменных, а не передавать множество отдельных переменных. Как объединить несколько переменных в группу и сохранить подсказки типа?
Давайте оставим в прошлом пример с простым приветствием из предыдущих глав и начнем использовать более богатые данные. Как и в остальных частях этой книги, я буду приводить примеры криптидов (воображаемых существ) и исследователей (тоже воображаемых), которые их ищут. Начальные определения криптидов будут включать в себя только строковые переменные для следующих параметров:
-
name
— ключ;
country
— двухсимвольный код страны согласно стандарту ISO (3166-1 alpha 2) или *, что означает «все»;
area
(необязательный) — штат США или другое территориальное образование страны;
description
— в свободной форме;
aka
— обозначает «также известен как…» (also known as…).
Пример 5.3 показывает, что вы можете получить немного больше объяснений, определив имена для целочисленных смещений.
Пример 5.3. Использование кортежей и именованных смещений
>>> NAME = 0
>>> COUNTRY = 1
>>> AREA = 2
>>> DESCRIPTION = 3
>>> AKA = 4
>>> tuple_thing = ("yeti", "CN", "Himalayas",
"Hirsute Himalayan", "Abominable Snowman")
>>> print("Name is", tuple_thing[NAME])
Name is yeti
В примере 5.4 словари выглядят немного лучше, предоставляя доступ по описательным ключам.
Пример 5.4. Использование словаря
>>> dict_thing = {"name": "yeti",
... "country": "CN",
... "area": "Himalayas",
... "description": "Hirsute Himalayan",
... "aka": "Abominable Snowman"}
>>> print("Name is", dict_thing["name"])
Name is yeti
Множества содержат только уникальные значения, поэтому они не очень полезны для кластеризации различных переменных.
В примере 5.5 именованный кортеж — это кортеж, предоставляющий вам доступ по целочисленному смещению или имени.
Пример 5.5. Использование именованного кортежа
>>> from collections import namedtuple
>>> CreatureNamedTuple = namedtuple("CreatureNamedTuple",
... "name, country, area, description, aka")
>>> namedtuple_thing = CreatureNamedTuple("yeti",
... "CN",
... "Himalaya",
... "Hirsute HImalayan",
... "Abominable Snowman")
>>> print("Name is", namedtuple_thing[0])
Name is yeti
>>> print("Name is", namedtuple_thing.name)
Name is yeti
Нельзя написать namedtuple_thing[«name»]. Это будет
tuple
, а не dict
, поэтому индекс должен быть целым числом.В примере 5.6 определяется новый класс Python под названием
class
и добавляются все атрибуты с помощью self
. Но для их определения вам придетсянабрать много текста.
Пример 5.6. Использование стандартного класса
>>> class CreatureClass():
... def __init__(self,
... name: str,
... country: str,
... area: str,
... description: str,
... aka: str):
... self.name = name
... self.country = country
... self.area = area
... self.description = description
... self.aka = aka
...
>>> class_thing = CreatureClass(
... "yeti",
... "CN",
... "Himalayas"
... "Hirsute Himalayan",
... "Abominable Snowman")
>>> print("Name is", class_thing.name)
Name is yeti
Вы можете подумать: что в этом плохого? В обычном классе можно добавить больше данных (атрибутов), но особенно много поведения (методов). В один безумный день вы можете решить добавить метод для поиска любимых песен исследователя. (Это нельзя применить к существам1.) Но в данном случае речь идет о том, чтобы просто без помех перемещать сборки данных между уровнями и проверять их на входе и выходе. Кроме того, методы — это квадратные детали, которые с трудом помещаются в круглые отверстия базы данных.
Есть ли в Python что-то похожее на то, что в других компьютерных языках называется записью (record) или структурой (struct) (группа имен и значений)? Недавно в Python появился класс для хранения данных (dataclass). В примере 5.7 показано, как все эти self-выражения исчезают при использовании классов данных.
Пример 5.7. Применение класса данных
dataclass
>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class CreatureDataClass():
... name: str
... country: str
... area: str
... description: str
... aka: str
...
>>> dataclass_thing = CreatureDataClass(
... "yeti",
... "CN",
... "Himalayas"
... "Hirsute Himalayan",
... "Abominable Snowman")
>>> print("Name is", dataclass_thing.name)
Name is yeti
Это очень хорошо для части описания, связанной с сохранением переменных вместе. Но нам требуется больше, так что давайте попросим у Дедушки Мороза вот что:
- объединение возможных альтернативных типов;
- отсутствующие/дополнительные значения;
- значения по умолчанию;
- проверку достоверности данных;
- сериализацию в форматы, такие как JSON, и из них.
Альтернативы
Очень заманчиво использовать встроенные структуры данных Python, особенно словари. Но вы неизбежно обнаружите, что словари слишком свободны. А за свободу приходится платить. Вам нужно будет проверить абсолютно все.
- Ключ необязателен?
- Если ключ отсутствует, есть ли значение по умолчанию?
- Существует ли ключ?
- Если да, то относится ли значение ключа к правильному типу?
- Если да, то находится ли значение в нужном диапазоне или соответствует ли
оно шаблону?
По крайней мере три решения отвечают хотя бы некоторым из этих требований:
- Dataclasses (https://oreil.ly/mxANA) — часть стандартного языка Python;
- attrs (https://www.attrs.org) — сторонний пакет, но содержит супернабор классов
данных; - Pydantic (https://docs.pydantic.dev) — тоже сторонний продукт, но интегрированный в FastAPI, поэтому его легко выбрать, если вы уже используете FastAPI.
И если вы читаете эту книгу, то вполне вероятно, что это именно так.
Удобное сравнение этих трех вариантов можно посмотреть на YouTube (https://oreil.ly/pkQD3). Одним из выводов является то, что Pydantic выделяется при проверке, а его интеграция с FastAPI позволяет выявить множество потенциальных ошибок в данных. Другое дело, что Pydantic полагается на наследование (от класса
BaseModel
), а два других используют декораторы Python для определения своих объектов. Это скорее вопрос стиля.В другом сравнении (https://oreil.ly/gU28a) Pydantic превзошел более старые пакеты проверки, такие как marshmallow (https://marshmallow.readthedocs.io) и библиотека с интригующим названием Voluptuous1 (https://github.com/alecthomas/voluptuous). Еще один большой плюс Pydantic в том, что он использует стандартный синтаксис подсказок типов Python — более старые библиотеки не применяли подсказки типов и создавали собственные.
В книге я остановился на Pydantic, но вы можете найти применение любой из альтернатив, если не используете FastAPI.
Pydantic предоставляет возможность задать любую комбинацию следующих проверок:
- обязательные и необязательные;
- значение по умолчанию, если не указано, но требуется;
- ожидаемый тип или типы данных;
- ограничения диапазона значений;
- другие проверки на основе функций, если необходимо;
- сериализацию и десериализацию.
Простой пример
Вы уже видели, как передать простую строку в конечную точку веб-приложения через URL, параметр запроса или тело HTTP-запроса. Проблема в том, что обычно вы запрашиваете и получаете группы данных разных типов. Именно здесь в FastAPI впервые появляются модели Pydantic. В начальном примере будут использоваться три файла:
-
model.py
— определяет модель Pydantic; -
data.py
— источник фиктивных данных, определяющих экземпляр модели; -
web.py
— определяет конечную точку веб-приложения FastAPI, возвращающую фиктивные данные.
Для простоты в этой главе сохраним все файлы в одном каталоге. В последующих главах, посвященных более крупным веб-сайтам, мы разделим их на соответствующие уровни. Сначала определим модель существа в примере 5.8.
Пример 5.8. Определение модели существа: model.py
from pydantic import BaseModel
class Creature(BaseModel):
name: str
country: str
area: str
description: str
aka: str
thing = Creature(
name="yeti",
country="CN",
area="Himalayas",
description="Hirsute Himalayan",
aka="Abominable Snowman")
)
print("Name is", thing.name)
Класс
Creature
наследуется от класса BaseModel из Pydantic. Часть выражения: str
после слов name
, country
,area
, description
и aka
представляет собой под-сказку типа — каждое из значений относится к строковому типу данных Python.
В этом примере все поля обязательны для заполнения. В Pydantic, если слово Optional отсутствует в описании типа, поле должно содержать значение.
В примере 5.9 аргументы передаются в любом порядке, если вы указываете их
имена.
Пример 5.9. Создание существа
>>> thing = Creature(
... name="yeti",
... country="CN",
... area="Himalayas"
... description="Hirsute Himalayan",
... aka="Abominable Snowman")
>>> print("Name is", thing.name)
Name is yeti
Пока что в примере 5.10 определен небольшой источник данных. В последующих главах этим будут заниматься базы данных. Подсказка типа list[Creature] говорит Python, что это список только объектов Creature.
Пример 5.10. Определение фиктивных данных в файле data.py
from model import Creature
_creatures: list[Creature] = [
Creature(name="yeti",
country="CN",
area="Himalayas",
description="Hirsute Himalayan",
aka="Abominable Snowman"
),
Creature(name="sasquatch",
country="US",
area="*",
description="Yeti's Cousin Eddie",
aka="Bigfoot")
]
def get_creatures() -> list[Creature]:
return _creatures
(Мы использовали символ "*" для аргумента area объекта
Bigfoot
, потому что он может жить почти везде.)Этот код импортирует написанный нами ранее файл model.py. Он немного скрывает данные, вызывая свой список объектов
Creature_creatures
и предоставляя функцию get_creatures
() для их возврата.В примере 5.11 приведен файл
web.py
, определяющий конечную точку веб-приложения FastAPI.Пример 5.11. Определение конечной точки веб-приложения FastAPI:
web.py
from model import Creature
from fastapi import FastAPI
app = FastAPI()
@app.get("/creature")
def get_all() -> list[Creature]:
from data import get_creatures
return get_creatures()
Теперь запустите этот сервер с одной конечной точкой в примере 5.12.
Пример 5.12. Запуск Uvicorn
$ uvicorn creature:app
INFO: Started server process [24782]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
В другом окне примера 5.13 осуществляется доступ к веб-приложению с помощью веб-клиента HTTPie (попробуйте использовать свой браузер или модуль Requests по желанию).
Пример 5.13. Проверка с помощью HTTPie
$ http http://localhost:8000/creature
HTTP/1.1 200 OK
content-length: 183
content-type: application/json
date: Mon, 12 Sep 2022 02:21:15 GMT
server: uvicorn
[
{
"aka": "Abominable Snowman",
"area": "Himalayas",
"country": "CN",
"name": "yeti",
"description": "Hirsute Himalayan"
},
{
"aka": "Bigfoot",
"country": "US",
"area": "*",
"name": "sasquatch",
"description": "Yeti's Cousin Eddie"
}
FastAPI и Starlette автоматически преобразуют исходный список объектов модели Creature в строку JSON. Это формат вывода по умолчанию в FastAPI, поэтому нам не нужно его указывать.
Кроме того, в окне, в котором вы первоначально запустили веб-сервер Uvicorn, должна быть выведена строка журнала:
INFO: 127.0.0.1:52375 - "GET /creature HTTP/1.1" 200 OK
Проверка типов
В предыдущем разделе было показано, как сделать следующее:
- применить подсказки типов к переменным и функциям;
- определить и использовать модель Pydantic;
- возвратить список моделей из источника данных;
- возвратить список моделей веб-клиенту, автоматически преобразовав его
в JSON.
А теперь действительно применим этот план для проверки данных. Попробуйте присвоить значение неправильного типа одному или нескольким полям объекта Creature. Для этого воспользуйтесь автономным тестом (Pydantic не применяется ни к какому веб-коду, он относится к данным).
В примере 5.14 показано содержимое файла
test1.py.
Пример 5.14. Проверка модели Creature
from model import Creature
dragon = Creature(
name="dragon",
description=["incorrect", "string", "list"],
country="*" ,
area="*",
aka="firedrake")
Теперь попробуйте выполнить тест из примера 5.15. Он показывает, что мы присвоили полю
description
список строк, а ему нужна обычная строка.Пример 5.15. Продолжение теста
$ python test1.py
Traceback (most recent call last):
File ".../test1.py", line 3, in <module>
dragon = Creature(
File "pydantic/main.py", line 342, in
pydantic.main.BaseModel.init
pydantic.error_wrappers.ValidationError:
1 validation error for Creature description
str type expected (type=type_error.str)
Проверка значений
Даже если тип значения соответствует его спецификации в классе
Creature
,могут потребоваться дополнительные проверки. Некоторые ограничения могут быть наложены на само значение.
- Целочисленное значение (
conint
) или число с плавающей точкой:
gt
— больше чем;
lt
— меньше чем;
ge
— больше или равно;
le
— меньше или равно;
multiple_of
— целое число, кратное значению. - Строковое (
constr
) значение:
min_length
— минимальная длина в символах (не в байтах);
max_length
— максимальная длина в символах;
to_upper
— преобразование в прописные буквы;
to_lower
— преобразование в строчные буквы;
regex
— сопоставление с регулярным выражением Python. - Кортеж, список или множество:
min_items
— минимальное количество элементов;
max_items
— максимальное количество элементов.
Они указываются в типовых частях модели.
Пример 5.16 позволяет убедиться, что поле name всегда будет содержать не менее двух символов. В противном случае "" (пустая строка) будет считаться допустимой.
Пример 5.16. Просмотр ошибки проверки
>>> from pydantic import BaseModel, constr
>>>
>>> class Creature(BaseModel):
... name: constr(min_length=2)
... country: str
... area: str
... description: str
... aka: str
...
>>> bad_creature = Creature(name="!",
... description="it's a raccoon",
... area="your attic")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pydantic/main.py", line 342,
in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError:
1 validation error for Creature name
ensure this value has at least 2 characters
(type=value_error.any_str.min_length; limit_value=2)
Ключевое слово constr означает ограниченную строку (constrained string).
В примере 5.17 используется альтернативный вариант — спецификация
Field
из библиотеки Pydantic.Пример 5.17. Еще один сбой проверки, применена функция
Field
>>> from pydantic import BaseModel, Field
>>>
>>> class Creature(BaseModel):
... name: str = Field(..., min_length=2)
... country: str
... area: str
... description: str
... aka: str
...
>>> bad_creature = Creature(name="!",
... area="your attic",
... description="it's a raccoon")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pydantic/main.py", line 342,
in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError:
1 validation error for Creature name
ensure this value has at least 2 characters
(type=value_error.any_str.min_length; limit_value=2)
Аргумент… функции
Field
() означает, что значение обязательное и значения по умолчанию не предусмотрено.Это минимальное введение в Pydantic. Главное, что можно сделать, — автоматизировать проверку данных. Вы увидите, насколько это полезно, при получении данных с веб-уровня или уровня данных.
Заключение
Модели предоставляют лучший способ определить данные, передаваемые в вашем веб-приложении. Библиотека Pydantic использует подсказки типов Python для определения моделей, передаваемых в приложении данных. Далее — определение зависимостей для выделения конкретных деталей из общего кода.
Об авторе
Билл Любанович занимается разработкой ПО уже более 40 лет, специализируясь на Linux, Web и Python. Билл выступил соавтором книги “Системное администрирование в Linux” и написал “Простой Python”.
Более подробно с книгой можно ознакомиться на сайте издательства:
» Оглавление
» Отрывок
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Для Хаброжителей скидка 25% по купону — FastAPI
denilenko
Не знаю как в русской версии, но в английской у меня сложилось впечатление что автор, у которого безусловно большой опыт программирования, писал примеры в голове и тут же добавлял их в примеры кода. В результате очень много не то чтобы даже ошибок, а просто несовпадений (например, переменная в примере одна, а на гитхабе - другая, и т.д.). Поначалу даже интересно, т.к. больше вникаешь и разбираешься в теме, но со временем этот ком ошибок накапливается, что пропадает желание дочитывать книгу и вместо нее залезть в официальную документацию, которая хоть и как всякая официальная документация достаточно "сухая", но хотя бы как написано, так и работает.