개발 및 생산을 위한 환경 최적화: Pydantic 자습서, 2부

게시 됨: 2022-07-22

개발자는 자신의 최악의 적이 될 수 있습니다. 저는 엔지니어가 자신의 프로덕션 환경과 일치하지 않는 시스템에서 개발하는 수많은 예를 보았습니다. 이러한 부조화로 인해 추가 작업이 발생하고 개발 프로세스의 후반부까지 시스템 오류를 포착하지 못합니다. 이러한 설정을 조정하면 궁극적으로 지속적인 배포가 쉬워집니다. 이를 염두에 두고 Django 개발 환경에서 Docker, pydantic 및 conda를 통해 단순화된 샘플 애플리케이션을 만들 것입니다.

일반적인 개발 환경은 다음을 사용합니다.

  • 로컬 리포지토리
  • Docker 기반 PostgreSQL 데이터베이스 그리고
  • Conda 환경(Python 종속성을 관리하기 위해).

Pydantic과 Django는 단순하고 복잡한 프로젝트에 모두 적합합니다. 다음 단계에서는 환경을 미러링하는 방법을 강조하는 간단한 솔루션을 보여줍니다.

Git 저장소 구성

코드 작성이나 개발 시스템 설치를 시작하기 전에 로컬 Git 리포지토리를 생성해 보겠습니다.

 mkdir hello-visitor cd hello-visitor git init

저장소 루트에 있는 기본 Python .gitignore 파일로 시작하겠습니다. 이 튜토리얼 전체에서 Git이 추적하지 않기를 바라는 파일을 추가하기 전에 이 파일에 추가할 것입니다.

Docker를 사용한 Django PostgreSQL 구성

Django는 관계형 데이터베이스가 필요하며 기본적으로 SQLite를 사용합니다. 우리는 일반적으로 동시 사용자 액세스를 잘 처리하지 못하기 때문에 미션 크리티컬 데이터 저장을 위해 SQLite를 피합니다. 대부분의 개발자는 PostgreSQL과 같은 보다 일반적인 프로덕션 데이터베이스를 선택합니다. 그럼에도 불구하고 개발 및 생산에 동일한 데이터베이스를 사용해야 합니다. 이 아키텍처 명령은 Twelve-Factor App의 일부입니다.

운 좋게도 Docker 및 Docker Compose로 로컬 PostgreSQL 인스턴스를 운영하는 것은 아주 쉽습니다.

루트 디렉터리를 오염시키는 것을 방지하기 위해 Docker 관련 파일을 별도의 하위 디렉터리에 넣습니다. PostgreSQL을 배포하기 위한 Docker Compose 파일을 만드는 것부터 시작하겠습니다.

 # docker-services/docker-compose.yml version: "3.9" services: db: image: "postgres:13.4" env_file: .env volumes: - hello-visitor-postgres:/var/lib/postgresql/data ports: - ${POSTGRES_PORT}:5432 volumes: hello-visitor-postgres:

다음으로 PostgreSQL 컨테이너를 구성 docker-compose 환경 파일을 생성합니다.

 # docker-services/.env POSTGRES_USER=postgres POSTGRES_PASSWORD=MyDBPassword123 # The 'maintenance' database POSTGRES_DB=postgres # The port exposed to localhost POSTGRES_PORT=5432

이제 데이터베이스 서버가 정의 및 구성되었습니다. 백그라운드에서 컨테이너를 시작하겠습니다.

 sudo docker compose --project-directory docker-services/ up -d

이전 명령에서 sudo를 사용했다는 점에 유의하는 것이 중요합니다. 개발 환경에서 특정 단계를 따르지 않는 한 필요합니다.

데이터베이스 생성

표준 도구 모음인 pgAdmin4를 사용하여 PostgreSQL에 연결하고 구성해 보겠습니다. 환경 변수에서 이전에 구성한 것과 동일한 로그인 자격 증명을 사용합니다.

이제 hello_visitor 라는 새 데이터베이스를 생성해 보겠습니다.

데이터베이스 생성 대화 상자의 일반 탭을 표시하는 브라우저 내의 pgAdmin4 화면. 데이터베이스 텍스트 필드에는 값 hello_visitor가 포함되고 소유자 필드에는 postgres 사용자가 표시되며 설명 필드는 비어 있습니다.

데이터베이스가 준비되면 프로그래밍 환경을 설치할 준비가 되었습니다.

Miniconda를 통한 Python 환경 관리

이제 격리된 Python 환경과 필수 종속성을 설정해야 합니다. 설치 및 유지 관리의 단순성을 위해 Miniconda를 선택했습니다.

conda 환경을 만들고 활성화해 보겠습니다.

 conda create --name hello-visitor python=3.9 conda activate hello-visitor

이제 Python 종속성을 열거하는 hello-visitor/requirements.txt 파일을 생성합니다.

 django # PostgreSQL database adapter: psycopg2 # Pushes .env key-value pairs into environment variables: python-dotenv pydantic # Utility library to read database connection information: dj-database-url # Static file caching: whitenoise # Python WSGI HTTP Server: gunicorn

다음으로 Python에 이러한 종속성을 설치하도록 요청할 것입니다.

 cd hello-visitor pip install -r requirements.txt

이제 애플리케이션 개발 작업을 준비하기 위해 종속성을 설치해야 합니다.

장고 스캐폴딩

먼저 django-admin 을 실행한 다음 생성된 파일 manage.py 를 실행하여 프로젝트와 앱을 스캐폴딩합니다.

 # From the `hello-visitor` directory mkdir src cd src # Generate starter code for our Django project. django-admin startproject hello_visitor . # Generate starter code for our Django app. python manage.py startapp homepage

다음으로 프로젝트를 로드하도록 Django를 구성해야 합니다. settings.py 파일은 새로 생성된 homepage 앱을 등록하기 위해 INSTALLED_APPS 배열을 조정해야 합니다.

 # src/hello_visitor/settings.py # ... INSTALLED_APPS = [ "homepage.apps.HomepageConfig", "django.contrib.admin", # ... ] # ...

애플리케이션 설정 구성

첫 번째 기사에 나와 있는 pydantic 및 Django 설정 접근 방식을 사용하여 개발 시스템에 대한 환경 변수 파일을 만들어야 합니다. 현재 설정을 다음과 같이 이 파일로 이동합니다.

  1. 개발 환경 설정을 저장할 src/.env 파일을 만듭니다.
  2. src/hello_visitor/settings.py 에서 설정을 복사하고 src/.env 에 추가하십시오.
  3. settings.py 파일에서 복사된 줄을 제거합니다.
  4. 데이터베이스 연결 문자열이 이전에 구성한 것과 동일한 자격 증명을 사용하는지 확인합니다.

환경 파일 src/.env 는 다음과 같아야 합니다.

 DATABASE_URL=postgres://postgres:MyDBPassword123@localhost:5432/hello_visitor DATABASE_SSL=False SECRET_KEY="django-insecure-sackl&7(1hc3+%#*4e=)^q3qiw!hnnui*-^($o8t@2^^qqs=%i" DEBUG=True DEBUG_TEMPLATES=True USE_SSL=False ALLOWED_HOSTS='[ "localhost", "127.0.0.1", "0.0.0.0" ]'

다음 코드 조각과 함께 pydantic을 사용하여 환경 변수에서 설정을 읽도록 Django를 구성합니다.

 # src/hello_visitor/settings.py import os from pathlib import Path from pydantic import ( BaseSettings, PostgresDsn, EmailStr, HttpUrl, ) import dj_database_url # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent class SettingsFromEnvironment(BaseSettings): """Defines environment variables with their types and optional defaults""" # PostgreSQL DATABASE_URL: PostgresDsn DATABASE_SSL: bool = True # Django SECRET_KEY: str DEBUG: bool = False DEBUG_TEMPLATES: bool = False USE_SSL: bool = False ALLOWED_HOSTS: list class Config: """Defines configuration for pydantic environment loading""" env_file = str(BASE_DIR / ".env") case_sensitive = True config = SettingsFromEnvironment() os.environ["DATABASE_URL"] = config.DATABASE_URL DATABASES = { "default": dj_database_url.config(conn_max_age=600, ssl_require=config.DATABASE_SSL) } SECRET_KEY = config.SECRET_KEY DEBUG = config.DEBUG DEBUG_TEMPLATES = config.DEBUG_TEMPLATES USE_SSL = config.USE_SSL ALLOWED_HOSTS = config.ALLOWED_HOSTS # ...

이전 편집을 완료한 후 문제가 발생하면 제작된 settings.py 파일을 소스 코드 저장소의 버전과 비교하십시오.

모델 생성

우리의 응용 프로그램은 홈페이지 방문자 수를 추적하고 표시합니다. 그 카운트를 유지하고 Django의 객체 관계형 매퍼(ORM)를 사용하여 데이터 마이그레이션을 통해 단일 데이터베이스 행을 초기화하는 모델이 필요합니다.

먼저 VisitCounter 모델을 생성합니다.

 # hello-visitor/src/homepage/models.py """Defines the models""" from django.db import models class VisitCounter(models.Model): """ORM for VisitCounter""" count = models.IntegerField() @staticmethod def insert_visit_counter(): """Populates database with one visit counter. Call from a data migration.""" visit_counter = VisitCounter(count=0) visit_counter.save() def __str__(self): return f"VisitCounter - number of visits: {self.count}"

다음으로 마이그레이션을 트리거하여 데이터베이스 테이블을 생성합니다.

 # in the `src` folder python manage.py makemigrations python manage.py migrate

homepage_visitcounter 테이블이 존재하는지 확인하기 위해 pgAdmin4에서 데이터베이스를 볼 수 있습니다.

다음으로, 우리의 homepage_visitcounter 테이블에 초기 값을 넣어야 합니다. Django 스캐폴딩을 사용하여 이를 수행하기 위해 별도의 마이그레이션 파일을 생성해 보겠습니다.

 # from the 'src' directory python manage.py makemigrations --empty homepage

이 섹션의 시작 부분에서 정의한 VisitCounter.insert_visit_counter 메서드를 사용하도록 생성된 마이그레이션 파일을 조정할 것입니다.

 # src/homepage/migrations/0002_auto_-------_----.py # Note: The dashes are dependent on execution time. from django.db import migrations from ..models import VisitCounter def insert_default_items(apps, _schema_editor): """Populates database with one visit counter.""" # To learn about apps, see: # https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations VisitCounter.insert_visit_counter() class Migration(migrations.Migration): """Runs a data migration.""" dependencies = [ ("homepage", "0001_initial"), ] operations = [ migrations.RunPython(insert_default_items), ]

이제 homepage 앱에 대해 이 수정된 마이그레이션을 실행할 준비가 되었습니다.

 # from the 'src' directory python manage.py migrate homepage

테이블의 내용을 보고 마이그레이션이 올바르게 실행되었는지 확인하겠습니다.

"SELECT * FROM public.homepage_visitcounter ORDER BY id ASC" 쿼리를 표시하는 브라우저 내의 pgAdmin4 화면. 데이터 출력 탭은 해당 테이블 내에 하나의 행이 있음을 보여줍니다. 대리 키 ID 필드 값은 1이고 개수 필드 값은 0입니다.

우리는 우리의 homepage_visitcounter 테이블이 존재하고 초기 방문 수가 0으로 채워진 것을 볼 수 있습니다. 데이터베이스를 정사각형으로 만들고 UI를 만드는 데 집중할 것입니다.

보기 생성 및 구성

UI의 두 가지 주요 부분인 뷰와 템플릿을 구현해야 합니다.

방문자 수를 늘리기 위해 homepage 보기를 만들고 데이터베이스에 저장한 다음 해당 수를 템플릿에 전달하여 표시합니다.

 # src/homepage/views.py from django.shortcuts import get_object_or_404, render from .models import VisitCounter def index(request): """View for the main page of the app.""" visit_counter = get_object_or_404(VisitCounter, pk=1) visit_counter.count += 1 visit_counter.save() context = {"visit_counter": visit_counter} return render(request, "homepage/index.html", context)

Django 애플리케이션은 homepage 를 대상으로 하는 요청을 수신해야 합니다. 이 설정을 구성하기 위해 다음 파일을 추가합니다.

 # src/homepage/urls.py """Defines urls""" from django.urls import path from . import views # The namespace of the apps' URLconf app_name = "homepage" # pylint: disable=invalid-name urlpatterns = [ path("", views.index, name="index"), ]

homepage 애플리케이션을 제공하려면 다른 urls.py 파일에 등록해야 합니다.

 # src/hello_visitor/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path("", include("homepage.urls")), path("admin/", admin.site.urls), ]

프로젝트의 기본 HTML 템플릿은 새 파일 src/templates/layouts/base.html 에 있습니다.

 <!DOCTYPE html> {% load static %} <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous"> <title>Hello, visitor!</title> <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/> </head> <body> {% block main %}{% endblock %} <!-- Option 1: Bootstrap Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> </body> </html>

새 파일 src/templates/homepage/index.html 에서 homepage 앱의 기본 템플릿을 확장합니다.

 {% extends "layouts/base.html" %} {% block main %} <main> <div class="container py-4"> <div class="p-5 mb-4 bg-dark text-white text-center rounded-3"> <div class="container-fluid py-5"> <h1 class="display-5 fw-bold">Hello, visitor {{ visit_counter.count }}!</h1> </div> </div> </div> </main> {% endblock %}

UI 생성의 마지막 단계는 Django에게 이러한 템플릿을 찾을 수 있는 위치를 알려주는 것입니다. settings.py 파일에 TEMPLATES['DIRS'] 사전 항목을 추가해 보겠습니다.

 # src/hello_visitor/settings.py TEMPLATES = [ { ... 'DIRS': [BASE_DIR / 'templates'], ... }, ]

이제 사용자 인터페이스가 구현되었으며 애플리케이션의 기능을 테스트할 준비가 거의 되었습니다. 테스트를 수행하기 전에 환경의 마지막 부분인 정적 콘텐츠 캐싱을 배치해야 합니다.

정적 콘텐츠 구성

개발 시스템에서 아키텍처 바로 가기를 사용하지 않도록 프로덕션 환경을 미러링하도록 정적 콘텐츠 캐싱을 구성합니다.

프로젝트의 모든 정적 파일을 단일 디렉토리인 src/static 에 보관하고 Django가 배포 전에 해당 파일을 수집하도록 지시합니다.

애플리케이션의 favicon 에 Toptal의 로고를 사용하고 src/static/favicon.ico 로 저장합니다.

 # from `src` folder mkdir static cd static wget https://frontier-assets.toptal.com/83b2f6e0d02cdb3d951a75bd07ee4058.png mv 83b2f6e0d02cdb3d951a75bd07ee4058.png favicon.ico

다음으로 정적 파일을 수집하도록 Django를 구성합니다.

 # src/hello_visitor/settings.py # Static files (CSS, JavaScript, images) # a la https://docs.djangoproject.com/en/3.2/howto/static-files/ # # Source location where we'll store our static files STATICFILES_DIRS = [BASE_DIR / "static"] # Build output location where Django collects all static files STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_ROOT.mkdir(exist_ok=True) # URL to use when referring to static files located in STATIC_ROOT. STATIC_URL = "/static/" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

우리는 원본 정적 파일만 소스 코드 저장소에 저장하기를 원합니다. 우리는 프로덕션에 최적화된 버전을 저장하고 싶지 않습니다. 이 간단한 줄을 사용하여 후자를 .gitignore 에 추가해 보겠습니다.

 staticfiles

소스 코드 리포지토리가 필요한 파일을 올바르게 저장하므로 이제 이러한 정적 파일과 함께 작동하도록 캐싱 시스템을 구성해야 합니다.

정적 파일 캐싱

프로덕션 환경에서 그리고 개발 환경에서도 WhiteNoise를 사용하여 Django 애플리케이션의 정적 파일을 보다 효율적으로 제공할 것입니다.

src/hello_visitor/settings.py 파일에 다음 스니펫을 추가하여 WhiteNoise를 미들웨어로 등록합니다. 등록 순서는 엄격하게 정의되며 WhiteNoiseMiddlewareSecurityMiddleware 바로 다음에 나타나야 합니다.

 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # ... ] STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

이제 개발 환경에서 정적 파일 캐싱을 구성해야 애플리케이션을 실행할 수 있습니다.

개발 서버 실행

우리는 완전히 코딩된 애플리케이션을 가지고 있으며 이제 다음 명령으로 Django의 임베디드 개발 웹 서버를 시작할 수 있습니다.

 # in the `src` folder python manage.py runserver

http://localhost:8000 으로 이동하면 페이지를 새로 고칠 때마다 수가 증가합니다.

"안녕하세요, 방문자!"라고 말하는 pydantic Django 애플리케이션의 메인 화면을 보여주는 브라우저 창 한 줄에 "1"이 다음 줄에 표시됩니다.

이제 페이지를 새로 고칠 때 방문 횟수를 증가시키는 작동하는 애플리케이션이 있습니다.

배포 준비 완료

이 튜토리얼에서는 프로덕션과 일치하는 아름다운 Django 개발 환경에서 작동하는 앱을 만드는 데 필요한 모든 단계를 다뤘습니다. 3부에서는 애플리케이션을 프로덕션 환경에 배포하는 방법을 다룹니다. Django 및 pydantic의 이점을 강조하는 추가 연습을 살펴보는 것도 가치가 있습니다. 이 pydantic 자습서의 코드 완성 저장소에 포함되어 있습니다.


Toptal 엔지니어링 블로그는 이 기사에 제공된 코드 샘플을 검토하고 베타 테스트한 Stephen Davidson에게 감사를 표합니다.