본문 바로가기
Python/Python Web Framework

Django DRF에서 serializer와 pydantic

by mintropy 2022. 2. 26.

들어가기에 앞서

1. DRF는 Django REST Framework를 의미합니다.

2. pydantic은 Python validation을 위한 라이브러리입니다.

 

이 글의 목적

- DRF를 사용한다면 serializer를 사용하는 것이 더 좋다고 생각합니다.

- 하지만, pydantic을 활용하여 어느 정도 대체 가능한지 확인해보기 위함입니다. pydantic 공식문서의 벤치마크에 따르면 data validation에서는 serializer가 12배 이상 느리다는 평가가 있어 궁금증을 가지게 되었습니다.

 

코드 비교 및 설명

- 코드 : https://github.com/mintropy/Python-web-framework

 

GitHub - mintropy/Python-web-framework

Contribute to mintropy/Python-web-framework development by creating an account on GitHub.

github.com

- DRF에서의 serializer 사용은 django-DRF에 있습니다.

- DRF에서의 pydantic 사용은 django-pydantic에 있습니다.

 

우선 공통적인 모델은 다음과 같습니다.

class Article(models.Model):
    title = models.CharField(max_length=20)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now=True)
    updated_at = models.DateTimeField(auto_now_add=True)


class Reply(models.Model):
    article = models.ForeignKey(
        Article,
        related_name='replies',
        on_delete=models.CASCADE,
    )
    content = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now=True)
    updated_at = models.DateTimeField(auto_now_add=True)

- Article에서는 게시글을 작성합니다.

- Reply를 통하여 댓글을 작성하고, 댓글은 각 게시글에 작성됩니다

 

각각의 view는 다음과 같이 작성되어 있습니다.

# serializer 사용
class ArticleViewSet(ViewSet):
    model = Article
    queryset = Article.objects.all()

    def list(self, request):
        articles = Article.objects.all().annotate(replies_count=Count('replies'))
        serializer = ArticleListSerialzier(articles, many=True)
        return Response(serializer.data)

    def create(self, request):
        data = {
            'title': request.data.get('title', None),
            'content': request.data.get('content', None),
        }
        serializer = ArticleDetailSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors)

    def retrieve(self, requset, article_id):
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleDetailSerializer(article)
        return Response(serializer.data)
 
 # pydantic 사용
 class ArticleViewSet(ViewSet):
    model = Article
    queryset = Article.objects.all()

    def list(self, request):
        articles = Article.objects.all().annotate(replies_count=Count('replies'))\
            .values('id', 'title', 'created_at', 'replies_count')
        response = [
            ArticleListSchema(**article) for article in articles
        ]
        return Response(response)

    def create(self, request):
        time_now = datetime.now()
        prev_id = Article.objects.all().order_by('-id').values('id')[0]['id']
        data = {
            'id': prev_id + 1,
            'title': request.data.get('title', None),
            'content': request.data.get('content', None),
            'created_at': time_now,
            'updated_at': time_now,
        }
        article_schema = ArticleDetailSchema(**data)
        try:
            Article.objects.create(**article_schema.dict())
            return Response(
                article_schema.dict(),
                status=status.HTTP_201_CREATED
            )
        except Exception:
            return Response(
                status=status.HTTP_400_BAD_REQUEST
            )

    def retrieve(self, request, article_id):
        article = get_object_or_404(Article, id=article_id)
        article_schema = ArticleDetailSchema(**article.__dict__)
        return Response(article_schema.dict())

설명에 들어가기 앞서

- 코드는 DRF ViewSet기준으로 작성되어 있습니다.

- 다른 API를 위한 코드가 추가될 수 있지만, 지금은 전체 게시글 목록을 위한 list, 생성을 위한 create, 개별 목록을 위한 retireve만 작성되어 있습니다.

- serializer와 pydantic의 basemodel과 관련해서는 밑에서 설명을 이어가겠습니다.

 

1. 코드에서 볼 수 있듯이 list와 retrieve는 거의 유사하게 작성되어 있습니다.

다만, serializer의 강력한 편의 기능을 확인할 수 있습니다.

pydantic을 활용하기 위해서는 특정 필드를 확인하거나 .__dict__을 활용하여 딕셔너리 형태로 바꿔주는 등의 작업이 필요할 수 있습니다.

2. create는 serializer의 편의 기능을 더욱 잘 확인할 수 있습니다.

serializer는 정의된 필드를 기준으로 하여 거의 자동적으로 생성해줍니다. 특히 primary key와 생성, 수정 시간 등을 자동으로 생성해줍니다. 또한 매우 강력한 기능으로 .is_valid(), .data, .errors를 활용할 수 있습니다. 실제 상용제품에서는 .errors의 사용이 좋지 않을 수 있다는 생각도 들지만, 개발과정을 생각한다면 매우 강력한 기능이라고 생각합니다.

반면, pydantic의 경우 각 필드를 정확하게 입력해줘야 합니다. 또한, 기존 Flask, FastAPI 등을 통하여 pydantic에 익숙하다면 큰 문제가 되지 않을 수 있지만, 그렇지 않다면 각 필드를 정의하고 validation 설정에서 조금 문제를 겪을 수도 있겠다고 생각했습니다. 마지막으로, validation을 실패했을 경우 잘못한다면 원인을 찾기 어려운 경우가 있을 수 있어, try-except문을 활용했습니다.

 

검증을 위한 serializer, basemodel은 다음과 같습니다.

# serializer
class ArticleListSerialzier(serializers.ModelSerializer):
    replies_count = serializers.IntegerField(read_only=True)
    class Meta:
        model = Article
        fields = ('id', 'title', 'created_at', 'replies_count',)

class ArticleDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'

# BaseModel
class ArticleListSchema(BaseModel):
    id: int
    title: str
    created_at: datetime.datetime
    replies_count: int

class ArticleDetailSchema(BaseModel):
    id: int
    title: str
    content: str
    created_at: datetime.datetime
    updated_at: datetime.datetime

    @validator('title')
    def title_length(cls, v):
        if len(v) > 20:
            return None
        return v

먼저 pydantic의 경우 Django에서 편리하게 사용할 수 있게 하는 djantic 라이브러리가 존재하지만, 아직 많이 부족한 부분이 있어 도입하지는 않았습니다. 특히 replies_count처럼 커스텀 필드를 사용할 때, 잘 적용되지 않아서 이번 경우 제외했습니다. 만약 추가적인 진전이 있다면 차후 작성해보겠습니다.

그리고 pydantic의 경우  validator 데코레이터를 통하여 따로 작성해야 합니다. 공식문서 예시에 따르면 raise를 통하여 Validation error를 발생하는 방향으로 설명하지만, 코드를 구현하며 잘 이루어지지 않아 None을 반환 후 validation여부에 따라 위의 view에서 try-except를 통하여 처리했습니다.

serialzier의 경우 많은 ModelSerializer를 통하여 매우 편하게 작성할 수 있습니다.

 

결론

1. DRF serializer는 매우 강력하고 편리한 도구입니다.

거의 대부분의 경우 serializer를 통하여 편리하고 빠르게 구현할 수 있습니다.

2. pydantic을 활용하여 serializer를 대체할 수는 있습니다.

하지만, 즉시 많은 불편함을 마주할 것이고, 특정 경우 매우 편리하게 가능했던 것을 구현하기 위해 더 많은 시간과 노력이 필요할 수 있습니다.

3. 위에서 설명했듯 pydantic을 Django에서 편리하게 사용할 수 있게 하는 djantic이 있습니다.

이를 활용하면 매우 편리한 구현을 할 수 있지만, 아직 불안정한 부분이 있는 것 같아 이번 소개에서는 제외했습니다. 하지만, 한 번 탐구해보셔도 좋다고 생각합니다.

댓글