[TIL 13일자] 데브코스 데이터엔지니어링

·2023년 4월 26일
0

데브코스

목록 보기
12/55
post-thumbnail

📚 오늘 공부한 내용

1. Serialize란?

  • model 인스턴스QuerySet과 같은 데이터를 JSON 형식의 파일로 변환하는 작업
  • Deserialize는 반대로 JSON 형식의 파일model 인스턴스QuerySet으로 변환하는 작업이다.

1) API 생성

  • cmd 창에 python manage.py startapp api_name 명령어를 통해 API를 생성해 준다.
  • 생성하면 다음과 같이 polls_api (내가 설정해 준 api_name)가 생성된 것을 확인할 수 있다.

2) serializer 만들기

  • 1)의 과정을 통해 만들어진 api 폴더에 새로운 파일 serializers.py를 추가해 준다. (serializers를 사용하기 위해)
  • serialize를 해 주기 위해서는 먼저 modelcolumn들을 모두 serializers 작업을 해 주어야 한다.
from rest_framework import serializers
from polls.models import Question 

class QuestionSerializer(serializers.Serializers):
    id  = serializers.IntegerField(read_only= True)
    question_text = serializers.CharField(max_length=200)
    pub_date = serializers.DateTimeField(read_only = True)

2. Serializer 동작

  • Django Shell을 통해 구현한 Serializer가 어떻게 동작하는지 확인할 수 있다.
  • serializer을 할 때는 처음 들어가는 데이터면 create, 이미 들어갔던 데이터라면 update 함수를 타게 된다.
  • 또한 serializer를 한 값을 그냥 .save()로 저장하거나 validated_data를 확인하려 하면 오류가 발생한다. -> .is_valid()를 통해 유효한 데이터인지 확인해 주어야 한다.
from polls.models import Question #Question 모델을 serializer 해 줄 것이기 때문에 import
from polls_api.serializers import QuestionSerializer #QuestionSerializer을 사용해 줄 것이기 때문에 import

q = Question.objects.first() #첫 번째 Question 데이터를 추출
q
>> <Question:  제목: 가장 좋아하는 계절은?, 날짜: 2023-04-24 17:16:45+00:00>

serializer = QuestionSerializer(q) 
serializer.data   #serializer 된 데이터를 보여 줄 때는 .data를 사용한다.
>> {'id': 1, 'question_text': '가장 좋아하는 계절은?', 'pub_date': '2023-04-24T17:16:45Z'}

from rest_framework.renderers import JSONRenderer #JSON으로 render 해 주기 위해 import
json_str = JSONRenderer().render(serializer.data)
json_str
>> b'{"id":1,"question_text":"\xea\xb0\x80\xec\x9e\xa5 \xec\xa2\x8b\xec\x95\x84\xed\x95\x98\xeb\x8a\x94 \xea\xb3\x84\xec\xa0\x88\xec\x9d\x80?","pub_date":"2023-04-24T17:16:45Z"}'
#아마 한글이라 JSON으로 바뀐 값이 저렇게 표현된 것으로 보임.

import json
data = json.loads(json_str)
data
>> {'id': 1, 'question_text': '가장 좋아하는 계절은?', 'pub_date': '2023-04-24T17:16:45Z'}
#json을 다시 loads 해 주면 처음 표현된 방식으로 돌아감. Deserialize

serializer = QuestionSerializer(data=data)  #serializer에 QuestionSerializer된 data를 넣어 줌. 이때 data는 처음 들어가는 것이기 때문에 create
serializer.is_valid()
> True
new_question = serializer.save()  #이때 create가 되어 처음 질문이 한 번 더 추가되었음을 알 수 있다.

data = {'question_text': '바다vs산'}
serializer = QuestionSerializer(new_question, data=data) #기존의 데이터에 serializer를 할 경우 update

serializer.is_valid()
>True
serializer.validated_data
> OrderedDict([('question_text', '바다vs산')])
  • 그런데 만약 question_text의 기본 조건인 max_length = 200이 넘는 값을 넣으려고 하면 어떤 문제가 발생할까?
long_text = "abcd"*399
data={'question_text':long_text}
serializer = QuestionSerializer(data=data)
serializer.is_valid()
>> False
serializer.errors #유효성 검사 실패 시 오류를 확인하는 코드
>> {'question_text': [ErrorDetail(string='Ensure this field has no more than 200 characters.', code='max_length')]}
  • .errors를 통해 오류가 발생 시 해당 오류를 확인할 수 있는데 다음과 같이 code=max_length가 200 characters를 넘어 False라는 것을 알려 준다.

3. ModelSerializer

  • ModelSerializercreateupdate 기능을 기본적으로 제공하기 때문에 코드로 구현할 필요가 없다.
  • 앞서 한 serializer보다 코드가 더 간단하다
from rest_framework import serializers
from polls.models import Question 

class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Question
        fields = ['id', 'question_text', 'pub_date']

4. API의 VIEW 구현

4-1. METHOD로 구현

1) GET

  • GET은 데이터를 조회해 오는 것을 말하며 READ에 해당한다.
  • 구현할 api 쪽의 views.py를 먼저 수정해 준다.
  • Question 목록을 보여 주기 위해 Question 모델에 있는 모든 데이터를 가지고 와 serializer 해 주었다.
  • 이때 QuestionSerializer(serializer할 변수, many=True)와 같이 manyTrue로 설정해 주면 serializer 해야 하는 값이 여러 개임을 알 수 있게 해 준다.
from django.shortcuts import render
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view

@api_view() #괄호 안에 안 넣더라도 question_list가 get 명령을 처리한다
def question_list(request):
    questions = Question.objects.all()
    serializer = QuestionSerializer(questions, many = True)
    #serializer를 여러 개 줄 때는 many 옵션을 줘 serializer 해야 하는 게 여러 개임을 인식하게 해 준다.
  • 구현할 api 쪽의 urls.py를 구현해 준다.
  • url이 없으면 이동이 불가하기 때문이다.
from django.urls import path
from .views import *

urlpatterns = [
    path('question/', question_list, name='question-list')
]
  • 그러므로 처음 페이지를 켰을 때 메인이 되는 mysiteurls.py에도 api와 이어질 수 있는 url을 추가해 준다.
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('rest/', include('polls_api.urls')),  #api url 추가
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]

2) POST

  • POST는 데이터를 처음 생성하는 것을 말하며 CREATE에 해당한다.
  • views.py에서 @api_view()POST를 추가해 준다. (아무것도 입력하지 않으면 GET)
  • 이때 POSTGET은 다른 로직을 타야 하므로 request.method을 통해 입력된 것이 GET인지 POST인지 확인한다.
  • 새로 serialize된 데이터를 저장할 시에는 is_valid를 통해 유효성 검사를 해 주어야 하며 True인 경우 값을 저장하고, False인 경우 errors를 통해 무슨 오류인지를 표출해 준다.
  • Response를 통해 status 값을 정해 줄 수 있다. 이를 해 주는 이유는 만약 잘못된 데이터라 유효성 검사에서 실패한 경우에도 .errors로 오류 메시지만 표시해 주기 때문에 status를 입력하지 않으면 정상적으로 처리됐다는 status = 200 상태가 된다.
@api_view(['GET', 'POST']) 
def question_list(request):
    if request.method == 'GET':
        questions = Question.objects.all()
        serializer = QuestionSerializer(questions, many = True)
        #serializer를 여러 개 줄 때는 many 옵션을 줘 serializer 해야 하는 게 여러 개임을 인식하게 해 준다.
        return Response(serializer.data)
    
    if request.method == 'POST':
        serializer = QuestionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

3) PUT, DELETE

  • PUT은 데이터를 수정하는 것을 말하며 UPDATE에 해당한다.
  • DELETE는 데이터를 삭제하는 것이다.
  • views.py에서 @api_view()PUTDELETE를 추가해 준다.
  • 이때 업데이트(UPDATE)삭제(DELETE)를 위해서는 하나의 질문을 선택해 주어야 하므로 PKID를 통해 수정 혹은 삭제할 질문을 가지고 올 수 있도록 한다.
  • PUT의 경우 업데이트 로직이기 때문에 POST와 동일하게 유효성 검사를 해 준 후 유효한 데이터이면 .save() 아니라면 .errors를 통해 에러 메시지를 보여 준다.
  • DELETE는 단순하게 삭제 기능이기 때문에 .delete()를 통해 삭제 처리만 해 주면 된다. 다만 삭제가 완료되었으므로 더 이상 반환할 데이터가 없다는 뜻에서 204_NO_CONTENT 코드를 사용한다.
@api_view(['GET', 'PUT', 'DELETE'])
def question_detail(request, id):
    question = get_object_or_404(Question, pk=id)
    
    if request.method == 'GET':
        serializer = QuestionSerializer(question)
        return Response(serializer.data)

    if request.method == 'PUT':
        serializer = QuestionSerializer(question, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    if request.method == 'DELETE':
        question.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

4-2. CLASS로 구현

  • 4-1의 방식과 거의 모든 내용이 동일하다.
  • views.py를 수정해 줘야 하며 class로 구현할 경우 APIView를 사용하기 때문에 rest_framework.viewsAPIView를 import 해 준다.
  • if-else 조건문이 아니라 각각의 함수로 구현할 수 있어 코드가 잘 정리되고 반복되는 코드가 없다는 장점이 있다.
from django.shortcuts import render, get_object_or_404
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status 
from rest_framework.views import APIView

class QuestionList(APIView):
    def get(self, request):
        questions = Question.objects.all()
        serializer = QuestionSerializer(questions, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = QuestionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class QuestionDetail(APIView):
    def get(self, request, id):
        question = get_object_or_404(Question, pk=id)
        serializer = QuestionSerializer(question)
        return Response(serializer.data)

    def put(self, request, id):
        question = get_object_or_404(Question, pk=id)
        serializer = QuestionSerializer(question, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    def delete(self, request, id):
        question = get_object_or_404(Question, pk=id)
        question.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
  • 다만 호출 url 부분이 수정되어야 하므로 urls.py를 수정해 준다.
  • class_name.as_view()를 통해 호출해 주어야 한다.
from django.urls import path
from .views import *

urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/<int:id>/', QuestionDetail.as_view(), name='question-detail'),
]

4-3. mixins을 통해 구현

  • 4-2 방식처럼 class 형식을 사용해 구현한다.
  • 다른 방식들에 비해 훨씬 더 간결한 것을 알 수 있다.
  • 자체 기능이 존재하기 때문에 여러 값을 가지고 올 때는 .list, 하나의 값을 가지고 올 때는 .retrieve, 새로 만들 때는 .create, 수정할 때는 .update, 마지막으로 삭제할 때는 .destroy를 쓰면 된다.
from django.shortcuts import render, get_object_or_404
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status, mixins, generics
from rest_framework.views import APIView

class QuestionList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class QuestionDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
  • 다만 QuestionDetail을 호출할 때 pkid가 필요한데 이는 url을 통해서 넘겨 줘야 하므로 urls.py를 수정한다.
from django.urls import path
from .views import *

urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/<int:pk>/', QuestionDetail.as_view(), name='question-detail'),
]

4-4. generics를 통해 구현

  • 가장 간단한 코드로 구현된다.
  • get, put, delete를 하지 않아도 RetrieveUpdateDestroyAPIViewListCreateAPIView 내부에 모두 구현되어 있기 때문이다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import generics


class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

5. HTTP Method

📑 HTTP 요청 메소드 공식 문서

  • GET : 특정 리소스 표시 요청
  • POST : 특정 리소스 엔티티를 제출할 때 (새로운 데이터를 만들 때, 즉, create에 해당)
  • PUT : 목적 리소스 모든 현재 표시를 요청 PAYLOAD로 바꾼다 (있는 데이터를 수정할 때, 즉, update에 해당)
  • DELETE : 특정 리소스를 삭제할 때

✔ [특강] Git/Github 익히기

1. HOW TO PLAN?

  • 소프트웨어 개발 시 요구 조건은 계속해서 변한다.
  • Water-Fall 모델은 소프트웨어 개발에 부적합하다.
    * Water-Fall 모델: 요구 조건 -> 디자인 -> 개발 -> 테스트 -> 릴리스 -> 유지 보수
    • 문제점: 테스트 하다 발견되는 결함들이 생기면 다시 디자인 단계로 돌아가야 함.
    • 빠른 변화로 한 사이클을 돌면서 소프트웨어 개발을 하는 것이 어려워짐.

  • 애자일 개발(Agile Development)
    • 사이클을 스프린트(Sprint)라고 부르는데 이 사이클을 단기간(보통 2주)에 반복적으로 도는 방식이다.
    • 매 사이클마다 다음과 같은 작업을 반복한다.
      - 작업별로 우선 순위 결정 (Backlog Prioritization)
      - 보통 PM이 수행
      - 이를 Grooming이라고 부름
      - 각 작업별로 중요도복잡도를 결정
      - 이번 사이클에 일할 작업을 결정 (Planning)
      - 매일 Standup 미팅 (5 분에서 10 분)
      - 마지막 날 Retrospective(회고: 좋았던 점, 아쉬웠던 점) & Demo(데모: 좀 더 시각화된 형식으로 결과를 보여 줌) 미팅

  • 스프린트 카드(Task)에는 어떤 내용이 들어갈까?
    • 타이틀
    • 세부 설명
    • 포인트 (숫자) - 회사에 따라, 팀에 따라 다른 고유의 값
    • 성공의 정의 - 이 작업을 성공적으로 끝내려면 어떤 작업을 해야 하는가. (Definition of Done)
    • 체크 리스트 - 이 작업이 성공적으로 끝나는데 필요한 세부 작업은 무엇인가.

  • 플랜닝 포커(Planning Poker)
    • 작업별로 포인트 산정.
    • 포인트의 정의는 회사마다 다르다.
    • 이 과정에서 시간이 많이 들면 애자일 개발의 원칙에 어긋나게 된다.

  • 일일 스탠드업(StandUp)
    • 매일 모든 팀원들이 10-15분씩 모여서 각자 상황 공유 (각자 2-3분 정도 사용)
    • 이 과정에서 시간이 많이 들면 이것 역시 애자일 개발의 원칙에 어긋나게 된다. 가볍게 진행되는 사항만 보고하고 문제가 발생할 경우 혹은 의논할 게 있는 경우 따로 미팅을 잡는다.
  • 이런 개발에서 흔히 사용하는 툴은 무엇인가?
    - JIRA
    - Trello: JIRA보다 훨씬 단순하다.

2. HOW TO MANAGE SOURCE CODE?

  • 어떤 버전 컨트롤 소프트웨어들이 있나?
    • CVS(Concurrent Version System)
    • SVN(SubVersionN)
    • Git/Github : 가장 인기 있는 버전 컨트롤 소프트웨어. 대부분의 오픈소스 소프트웨어들은 Github에 존재.
  • 코드 리뷰 통해 진행한다.

3.HOW TO TEST?

  • Test Driven Development
    • 개발 시 테스트를 어떻게 할 것인지부터 생각한다.
    • 코드 구성 자체가 테스트하기 쉬운 방식으로 구현된다.

4. HOW TO BUILD?

  • 소프트웨어를 최종적으로 출시하기 위한 형태로 만드는 것으로 테스트가 빌드의 중요한 일부이다.
  • 개발이 끝나기 전부터 빌드를 하면 소프트웨어의 안정성이 증대된다. (STAGING SERVER와 같은 서버에 배포)

  • Continuous Intergration이란?
    • Software Engineering Pratice의 하나
    • 코드 repo 하나만 유지 (Master)
    • 코드 변경을 최대한 자주 반영
    • 테스트를 최대한 추가
    • 빌드를 계속적으로 수행 (자동화)
    • 성공한 빌드의 프로덕션 배포 (자동화)

  • Jenkins란?
    • 오픈 소스 CI 빌드 소프트웨어
    • 빌드된 소프트웨어의 배포를 위해서도 사용이 가능하다.
    • cronjob 관리 소프트웨어에 가깝다.
  • 하지만 Github는 Github와 연동되는 CI 서비스가 존재한다. (Github Actions, Travis CI, Circle CI 등)

🔎 어려웠던 내용 & 새로 알게 된 내용

1. OrderedDict

  • 일반 Dictionary는 순서를 보장하지 않기 때문에 키-값의 쌍이 순서대로 유지되지 않는다.
  • OrderedDict은 그 순서를 보장하는 Dictionary이다.
  • 만약 OrderedDict.get('key', default처리-키가 없을 경우)랄 하면 value 값을 return 받을 수 있다.
  • 하지만 Python 3.7부터는 Dictionary 역시 키-값으로 이루어지고 순서를 보장한다고 선언되었다.

2. serializer 실행 시 rest_framework 모듈이 없다는 오류 발생
- Django Shell에서 다음과 같은 명령어 from polls_api.serializers import QuestionSerializer로 만든 serializer을 실행하려는 과정에서 ModuleNotFoundError: No module named 'rest_framework' 오류가 발생하였다.
- 발생한 까닭은 rest_framework가 설치되어 있지 않기 때문이었다.
- 이 문제를 해결하기 위해서는 python -m pip install djangorestframework 다음과 같은 명령어로 rest_framework를 설치해 주어야 했다.

3. AssertionError: You must call '.is_valid()' before accessing '.validated_data' 오류

  • 해당 오류의 발생 원인은 serializer된 값을 유효성 검사를 해 주지 않았기 때문에 유효한 값인지를 알기 전에 .save().validated_data를 처리할 수 없어 발생하는 오류이다.
  • 유효성 검사.is_valid()를 통해 가능하며 True일 경우 처리할 수 있는 유효한 데이터, False인 경우 유효하지 않은 데이터라는 뜻이다.

4. Django . TemplateDoesNotExist at /rest/question/ 오류

5. HTTP Status Code

  • 200 번대: 정상적인 결과
    • 200 OK
    • 201 CREATED
  • 400 번대: 사용자의 잘못된 요청
    • 400 BAD REQUEST
    • 404 NOT FOUND
  • 500 번대: 서버 내부 오류

✍ 회고

- 처음으로 serializer의 개념을 알게 되었다. 왜 serializer가 필요한지, 어떨 때 사용하는지 조금 더 명확하게 공부할 필요가 있을 것 같다. 또한 같은 코드지만 총 네 개의 경우로 구현을 해 두었는데 이때 import 된 내용을 기억해 둘 필요가 있어 보인다. 개발하는 과정에서 비슷한 경우를 겪을 시 더 간결한 코드를 만들 수 있는 서비스가 존재하기 때문이다.

- 프로젝트를 하면서 겪었던 하나의 플로우를 강의로 듣게 되면서 대부분의 회사들이 소프트웨어를 개발하는 과정은 결국 애자일 방식으로 간다는 것을 느꼈다. 또한 github의 중요성을 느끼게 되었는데 내일은 조금 더 git을 사용해 볼 것이다.

profile
송의 개발 LOG

0개의 댓글