Django и Pydantic
В этой статье мы рассмотрим, как интегрировать Pydantic с приложением Django, используя Пакеты Djantic и Django Ninja.
pydantic
Pydantic - это пакет на Python для проверки данных и управления настройками, основанный на подсказках типа Python. Он реализует подсказки по вводу данных во время выполнения, предоставляет удобные для пользователя ошибки, допускает пользовательские типы данных и хорошо работает со многими популярными IDE. Кроме того, он чрезвычайно быстр и прост в использовании!
Давайте рассмотрим пример:
from pydantic import BaseModel
class Song(BaseModel):
id: int
name: str
Здесь мы определили модель Song
с двумя атрибутами, оба из которых обязательны:
id
является целым числомname
является строкой
Затем при инициализации происходит проверка:
>>> song = Song(id=1, name='I can almost see you')
>>> song.name
'I can almost see you'
>> Song(id='1')
pydantic.error_wrappers.ValidationError: 1 validation error for Song
name
field required (type=value_error.missing)
>>> Song(id='foo', name='I can almost see you')
pydantic.error_wrappers.ValidationError: 1 validation error for Song
id
value is not a valid integer (type=type_error.integer)
Чтобы узнать больше о pydantic, обязательно прочитайте страницу Обзор из официальной документации.
pydantic и Джанго
В сочетании с Django мы можем использовать pydantic для обеспечения того, чтобы в нашем приложении использовались только те данные, которые соответствуют определенным схемам. Итак, мы определим схемы для проверки запросов и ответов, и при возникновении ошибки проверки мы просто вернем приятное для пользователя сообщение об ошибке.
Хотя вы можете интегрировать pydantic с Django без каких-либо сторонних пакетов, мы упростим этот процесс, используя следующие пакеты:
- Djantic - добавлена поддержка Pydantic для проверки данных модели
- Django Ninja - наряду с Pydantic, этот пакет предоставляет вам ряд дополнительных преимуществ, таких как автоматически генерируемая документация по API (через OpenAPI и Схема JSON), сериализация и управление версиями API
Django Ninja в значительной степени вдохновлен FastAPI. Проверьте это, если вам нравится быстрый API, но при этом вы хотите использовать многое из того, что может предложить Django.
Djantic
Теперь, когда у вас есть общее представление о том, что такое pydantic, давайте рассмотрим практический пример. Мы создадим простой RESTful API, с помощью Django и Django, который позволяет извлекать, список и создавать статьи.
Основные настройки
Начните с создания нового проекта Django:
$ mkdir django-with-pydantic && cd django-with-pydantic
$ python3.11 -m venv env
$ source env/bin/activate
(env)$ pip install django==4.2
(env)$ django-admin startproject core .
После этого создайте новое приложение с именем blog
:
(env)$ python manage.py startapp blog
Зарегистрируйте приложение в core/settings.py в разделе INSTALLED_APPS
:
# core/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog.apps.BlogConfig', # new
]
Создание моделей баз данных
Далее давайте создадим модель Article
.
Добавьте к следующееblog/models.py:
# blog/models.py
from django.contrib.auth.models import User
from django.db import models
class Article(models.Model):
author = models.ForeignKey(to=User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=512, unique=True)
content = models.TextField()
def __str__(self):
return f'{self.author.username}: {self.title}'
Создайте, а затем примените изменения:
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
Зарегистрируйте модель в blog/admin.py , чтобы она была доступна из панели администратора Django:
# blog/admin.py
from django.contrib import admin
from blog.models import Article
admin.site.register(Article)
Установите Dj antic и создайте схемы
Установите pydantic и Djantic:
(env)$ pip install pydantic==1.10.7 djantic==0.7.0
Теперь мы можем определить схему, которая будет использоваться для-
- Проверьте поля из полезной нагрузки запроса, а затем используйте эти данные для создания новых объектов модели
- Извлекать и проверять объекты модели для объектов-ответов
Создайте новый файл с именем blog/schemas.py:
# blog/schemas.py
from djantic import ModelSchema
from blog.models import Article
class ArticleSchema(ModelSchema):
class Config:
model = Article
Это самая простая из возможных схем, которая вытекает из нашей модели.
Модели Django должны быть загружены перед схемами, поэтому схемы должны храниться в отдельном файле, чтобы избежать ошибок при загрузке модели.
С помощью схем вы также можете определить, какие поля следует и не следует включать из конкретной модели, передав exclude
или include
в Config
. Например, чтобы исключить author
:
class ArticleSchema(ModelSchema):
class Config:
model = Article
exclude = ['author']
# or
class ArticleSchema(ModelSchema):
class Config:
model = Article
include = ['created', 'title', 'content']
Вы также можете использовать схемы для переопределения свойств модели Django, изменив поля внутри схемы. Например:
class ArticleSchema(ModelSchema):
title: Optional[str]
class Config:
model = Article
Просмотры и URL-адреса
Далее давайте настроим следующие конечные точки:
/blog/articles/create/
создает новую статью/blog/articles/<ARTICLE_ID>/
извлекает одну статью/blog/articles/
содержит список всех статей
Добавьте следующие виды в blog/views.py:
# blog/views.py
import json
from django.contrib.auth.models import User
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from blog.models import Article
from blog.schemas import ArticleSchema
@csrf_exempt # testing purposes; you should always pass your CSRF token with your POST requests (+ authentication)
@require_http_methods('POST')
def create_article(request):
try:
json_data = json.loads(request.body)
# fetch the user and pass it to schema
author = User.objects.get(id=json_data['author'])
article = Article(
author=author,
title=json_data['title'],
content=json_data['content']
)
article.save()
schema = ArticleSchema.from_django(article)
return JsonResponse({
'article': schema.dict()
})
except User.DoesNotExist:
return JsonResponse({'detail': 'Cannot find a user with this id.'}, status=404)
def get_article(request, article_id):
try:
article = Article.objects.get(id=article_id)
schema = ArticleSchema.from_django(article)
return JsonResponse({
'article': schema.dict()
})
except Article.DoesNotExist:
return JsonResponse({'detail': 'Cannot find an article with this id.'}, status=404)
def get_all_articles(request):
articles = Article.objects.all()
data = []
for article in articles:
schema = ArticleSchema.from_django(article)
data.append(schema.dict())
return JsonResponse({
'articles': data
})
Обратите внимание на области, в которых мы используем схему, ArticleSchema
:
ArticleSchema.create()
создает новыйArticle
объектschema.dict()
возвращает словарь полей и значений, которые мы передали вJsonResponse
ArticleSchema.from_django()
генерирует схему изArticle
объекта
Помните: и
create()
, иfrom_django()
также будут проверять соответствие данных схеме.
Добавьте urls.py файл в "блог" и укажите следующие URL-адреса:
# blog/urls.py
from django.urls import path
from blog import views
urlpatterns = [
path('articles/create/', views.create_article),
path('articles/<str:article_id>/', views.get_article),
path('articles/', views.get_all_articles),
]
Теперь давайте зарегистрируем URL-адреса наших приложений в базовом проекте:
# core/urls.py
from django.contrib import admin
from django.shortcuts import render
from django.urls import path, include # new import
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')), # new
]
Проверка на вменяемость
Для тестирования сначала создайте суперпользователя:
(env)$ python manage.py createsuperuser
Затем запустите сервер разработки:
(env)$ python manage.py runserver
В новом окне терминала добавьте новую статью с помощью cURL:
$ curl --header "Content-Type: application/json" --request POST \
--data '{"author":"1","title":"Something Interesting", "content":"Really interesting."}' \
http://localhost:8000/blog/articles/create/
Вы должны увидеть что-то вроде:
{
"article": {
"id": 1,
"author": 1,
"created": "2021-02-01T20:01:35.904Z",
"title": "Something Interesting",
"content": "Really interesting."
}
}
После этого вы сможете просмотреть статью по ссылкам http://127.0.0.1:8000/blog/articles/1/ и http://127.0.0.1:8000/blog/articles/.
Схема ответа
Хотите удалить поле created
из ответов по всем статьям?
Добавьте новую схему в blog/schemas.py:
class ArticleResponseSchema(ModelSchema):
class Config:
model = Article
exclude = ['created']
Затем обновите представление:
def get_all_articles(request):
articles = Article.objects.all()
data = []
for article in articles:
schema = ArticleResponseSchema.from_django(article)
data.append(schema.dict())
return JsonResponse({
'articles': data
})
Не забудьте импортировать ArticleResponseSchema
:
from blog.schemas import ArticleSchema, ArticleResponseSchema
Проверьте это на http://127.0.0.1:8000/blog/articles/.
Джанго Ниндзя
Django Ninja - это инструмент для создания API-интерфейсов с подсказками типов на основе Django и Python. Как уже упоминалось, он поставляется с рядом мощных функций. Он "быстр в освоении, быстром программировании, быстром запуске".
Основные настройки
Создаем новый проект на Django:
$ mkdir django-with-ninja && cd django-with-ninja
$ python3.11 -m venv env
$ source env/bin/activate
(env)$ pip install django==4.2
(env)$ django-admin startproject core .
Создайте новое приложение под названием blog
:
(env)$ python manage.py startapp blog
Зарегистрируйте приложение в core/settings.py в разделе INSTALLED_APPS
:
# core/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog.apps.BlogConfig', # new
]
Создание моделей баз данных
Затем добавьте Article
модель в blog/models.py:
# blog/models.py
from django.contrib.auth.models import User
from django.db import models
class Article(models.Model):
author = models.ForeignKey(to=User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=512, unique=True)
content = models.TextField()
def __str__(self):
return f'{self.author.username}: {self.title}'
Создайте и примените миграции:
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
Зарегистрируйте модель в blog/admin.py:
# blog/admin.py
from django.contrib import admin
from blog.models import Article
admin.site.register(Article)
Установите Django Ninja и создайте схемы
Установка:
(env)$ pip install django-ninja==0.21.0
Как и в случае с Dj antic, вам необходимо создавать схемы для проверки ваших запросов и ответов. Django Ninja обеспечивает гибкость при создании схем, используя как автоматическую генерацию схем, так и ручную генерацию схем. Автоматическая генерация схемы позволяет вам автоматически создавать схему на основе ваших моделей Django, в то время как ручная генерация схемы дает вам больше контроля над схемой.
Чтобы создать схему для модели User
с помощью автоматической генерации схемы, добавьте следующее в blog/schemas.py:
from django.contrib.auth.models import User
from ninja import ModelSchema
class UserSchema(ModelSchema):
class Config:
model = User
fields = ['id', 'username']
Для получения дополнительной информации о поддержке автоматической генерации схем ознакомьтесь с Схемами из Django models.
Чтобы создать остальные схемы вручную, добавьте следующее в blog/schemas.py:
from datetime import datetime # new line
from django.contrib.auth.models import User
from ninja import ModelSchema, Schema # updated line
# Auto-schema generation for UserSchema
class UserSchema(ModelSchema):
class Config:
model = User
model_fields = ['id', 'username']
# Manual schema generation for ArticleIn and ArticleOut
class ArticleIn(Schema): # new schema
author: int
title: str
content: str
class ArticleOut(Schema): # new schema
id: int
author: UserSchema
created: datetime
title: str
content: str
Теперь у нас есть три разные схемы:
UserSchema
проверяет и преобразует данные в пользовательскую модель Django и из нееArticleIn
проверяет и десериализует данные, передаваемые в API для создания статейArticleOut
проверяет и сериализует данные из моделиArticle
В Django Ninja есть концепция маршрутизатора, который используется для разделения API на несколько модулей.
Создать blog/api.py файл:
# blog/api.py
from typing import List
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from ninja import Router
from blog.models import Article
from blog.schemas import ArticleOut, ArticleIn
router = Router()
@router.post('/articles/create')
def create_article(request, payload: ArticleIn):
data = payload.dict()
try:
author = User.objects.get(id=data['author'])
del data['author']
article = Article.objects.create(author=author, **data)
return {
'detail': 'Article has been successfully created.',
'id': article.id,
}
except User.DoesNotExist:
return {'detail': 'The specific user cannot be found.'}
@router.get('/articles/{article_id}', response=ArticleOut)
def get_article(request, article_id: int):
article = get_object_or_404(Article, id=article_id)
return article
@router.get('/articles', response=List[ArticleOut])
def get_articles(request):
articles = Article.objects.all()
return articles
Здесь мы создали три функции, которые служат нашими представлениями. Django Ninja использует декораторы HTTP-операций, в которых вы определяете структуру URL-адреса, параметры пути и необязательный запрос и ответ схемы.
Примечания:
get_article
используетArticleOut
для своей схемы ответа.ArticleOut
, таким образом, будет использоваться для автоматической проверки и сериализации данных из модели.- В
get_articles
набор запросов Django, например,articles = Article.objects.all()
, будет правильно проверен с помощьюList[ArticleOut]
.
Регистрация конечных точек API
Последнее, что нам нужно сделать, это создать новый экземпляр NinjaAPI
и зарегистрировать наш API-маршрутизатор в core/urls.py:
# core/urls.py
from django.contrib import admin
from django.urls import path
from ninja import NinjaAPI
from blog.api import router as blog_router
api = NinjaAPI()
api.add_router('/blog/', blog_router)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', api.urls),
]
После этого Django Ninja автоматически создаст следующие конечные точки:
/blog/articles/create/
создает новую статью/blog/articles/<ARTICLE_ID>/
извлекает одну статью/blog/articles/
содержит список всех статей
Проверка на вменяемость
Создайте суперпользователя, а затем запустите сервер разработки:
(env)$ python manage.py createsuperuser
(env)$ python manage.py runserver
Перейдите к http://localhost:8000/api/docs , чтобы просмотреть автоматически созданную интерактивную документацию по API:
Здесь вы можете просматривать зарегистрированные конечные точки и взаимодействовать с ними.
Попробуйте добавить новую статью:
Попробуйте остальные конечные точки самостоятельно.
Заключение
В этой статье мы сначала рассмотрели, что такое pydantic, а затем рассмотрели, как интегрировать его с приложением Django с помощью Djantic и Django Ninja.
Полный код вы можете найти на GitHub:
Вернуться на верх