محیط توسعه بهینه: آموزش Pydantic، قسمت 2


توسعه دهندگان می توانند بدترین دشمنان خود باشند. من نمونه‌های بی‌شماری از مهندسین را دیده‌ام که روی سیستمی توسعه می‌دهند که با محیط تولیدشان مطابقت ندارد. این ناهماهنگی منجر به کار اضافی و عدم شناسایی خطاهای سیستم تا بعداً در فرآیند توسعه می شود. تراز کردن این تنظیمات در نهایت استقرار مداوم را تسهیل می کند. با در نظر گرفتن این موضوع، ما یک نمونه برنامه کاربردی در محیط توسعه جنگو خود ایجاد خواهیم کرد که از طریق Docker، pydantic و conda ساده شده است.

یک محیط توسعه معمولی از موارد زیر استفاده می کند:

  • یک مخزن محلی؛
  • پایگاه داده PostgreSQL مبتنی بر Docker. و
  • یک محیط conda (برای مدیریت وابستگی های پایتون).

Pydantic و Django برای پروژه های ساده و پیچیده مناسب هستند. مراحل زیر راه حل ساده ای را نشان می دهد که نحوه انعکاس محیط هایمان را برجسته می کند.

پیکربندی مخزن Git

قبل از شروع نوشتن کد یا نصب سیستم های توسعه، بیایید یک مخزن Git محلی ایجاد کنیم:

mkdir hello-visitor
cd hello-visitor

git init

ما با یک شروع می کنیم پایتون پایه .gitignore فایل در ریشه مخزن در طول این آموزش، قبل از افزودن فایل‌هایی که نمی‌خواهیم Git آنها را ردیابی کند، به این فایل اضافه می‌کنیم.

پیکربندی Django PostgreSQL با استفاده از Docker

جنگو به یک پایگاه داده رابطه ای نیاز دارد و به طور پیش فرض از SQLite استفاده می کند. ما معمولاً از SQLite برای ذخیره سازی داده های حیاتی اجتناب می کنیم زیرا دسترسی همزمان کاربر را به خوبی مدیریت نمی کند. اکثر توسعه دهندگان یک پایگاه داده تولید معمولی مانند PostgreSQL را انتخاب می کنند. صرف نظر از این، ما باید از همان پایگاه داده برای توسعه و تولید استفاده کنیم. این دستور معماری بخشی از برنامه دوازده عاملی.

خوشبختانه، یک نمونه محلی PostgreSQL با داکر و Docker Compose یک نسیم است

برای جلوگیری از آلوده شدن دایرکتوری ریشه ما، فایل های مربوط به Docker را در زیر شاخه های جداگانه قرار می دهیم. ما با ایجاد یک فایل Docker Compose برای استقرار PostgreSQL شروع می کنیم:

# 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:

بعد، یک را ایجاد می کنیم docker-compose فایل محیطی برای پیکربندی ظرف PostgreSQL:

# 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 در دستور قبلی ضروری است. مورد نیاز خواهد بود مگر اینکه مراحل خاص در محیط توسعه ما دنبال می شوند.

ایجاد پایگاه داده

بیایید با استفاده از یک مجموعه ابزار استاندارد به PostgreSQL متصل و پیکربندی کنیم. pgAdmin4. ما از همان اعتبار ورود به سیستم استفاده خواهیم کرد که قبلاً در متغیرهای محیطی پیکربندی شده بود.

حالا بیایید یک پایگاه داده جدید به نام ایجاد کنیم hello_visitor:

یک صفحه pgAdmin4 در یک مرورگر که برگه عمومی را در گفتگوی ایجاد پایگاه داده نشان می دهد.  فیلد متنی پایگاه داده حاوی مقدار hello_visitor است، فیلد مالک کاربر postgres را نمایش می دهد و فیلد نظر خالی است.

با در اختیار داشتن پایگاه داده، ما آماده نصب محیط برنامه نویسی خود هستیم.

مدیریت محیط زیست پایتون از طریق Miniconda

اکنون باید یک محیط Python ایزوله و وابستگی های مورد نیاز را راه اندازی کنیم. برای سادگی راه اندازی و نگهداری، ما را انتخاب کردیم مینیکوندا.

بیایید محیط conda خود را ایجاد و فعال کنیم:

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

اکنون یک فایل ایجاد می کنیم، 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

در مرحله بعد، از پایتون می خواهیم این وابستگی ها را نصب کند:

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

بعد، ما باید جنگو را برای بارگذاری پروژه خود پیکربندی کنیم. را settings.py فایل نیاز به تنظیم دارد INSTALLED_APPS آرایه ای برای ثبت نام جدید ایجاد شده ما homepage برنامه:

# src/hello_visitor/settings.py

# ...

INSTALLED_APPS = [
    "homepage.apps.HomepageConfig",
    "django.contrib.admin",
    # ...
]

# ...

پیکربندی تنظیمات برنامه

با استفاده از رویکرد تنظیمات pydantic و جنگو که در قسمت اول نشان داده شد، باید یک فایل متغیرهای محیطی برای سیستم توسعه خود ایجاد کنیم. تنظیمات فعلی خود را به صورت زیر به این فایل منتقل می کنیم:

  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، با این قطعه کد پیکربندی می‌کنیم:

# 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 فایل با نسخه در مخزن کد منبع ما.

ایجاد مدل

برنامه ما تعداد بازدیدکنندگان صفحه اصلی را ردیابی و نمایش می دهد. ما نیاز داریم a مدل برای نگه داشتن آن شمارش و سپس استفاده نقشه‌بردار شی رابطه‌ای جنگو (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 جدول. بیایید یک فایل مهاجرت جداگانه برای انجام این کار با استفاده از داربست جنگو ایجاد کنیم:

# 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:
    # 
    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

بیایید با مشاهده محتویات جدول بررسی کنیم که انتقال به درستی انجام شده است:

یک صفحه pgAdmin4 در یک مرورگر که یک پرس و جو را نشان می دهد

ما می بینیم که ما homepage_visitcounter جدول وجود دارد و با تعداد بازدید اولیه 0 پر شده است. با مجذور شدن پایگاه داده، ما بر ایجاد رابط کاربری خود تمرکز خواهیم کرد.

دیدگاه های ما را ایجاد و پیکربندی کنید

ما باید دو بخش اصلی UI خود را پیاده سازی کنیم: یک view و یک الگو.

ما ایجاد می کنیم 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)

برنامه جنگو ما نیاز به گوش دادن به درخواست های هدف دارد 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=" 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=" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>

  </body>
</html>

ما قالب پایه را برای خود گسترش می دهیم homepage برنامه در یک فایل جدید، src/templates/homepage/index.html:

{% 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 %}

آخرین مرحله در ایجاد رابط کاربری ما این است که به جنگو بگوییم این الگوها را کجا پیدا کند. بیایید یک را اضافه کنیم TEMPLATES['DIRS'] مورد فرهنگ لغت به ما settings.py فایل:

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

رابط کاربری ما اکنون پیاده سازی شده است و تقریباً آماده آزمایش عملکرد برنامه خود هستیم. قبل از اینکه آزمایش خود را انجام دهیم، باید آخرین قطعه محیط خود را در جای خود قرار دهیم: ذخیره محتوای ثابت.

پیکربندی محتوای ثابت ما

برای جلوگیری از استفاده از میانبرهای معماری در سیستم توسعه خود، ذخیره محتوای ثابت را برای انعکاس محیط تولید خود پیکربندی می کنیم.

ما تمام فایل های استاتیک پروژه خود را در یک دایرکتوری نگه می داریم، src/staticو به جنگو دستور دهید تا آن فایل ها را قبل از استقرار جمع آوری کند.

ما از آرم Toptal برای برنامه خود استفاده خواهیم کرد favicon و آن را به عنوان ذخیره کنید src/static/favicon.ico:

# from `src` folder
mkdir static
cd static
wget 
mv 83b2f6e0d02cdb3d951a75bd07ee4058.png favicon.ico

در مرحله بعد، جنگو را برای جمع آوری فایل های استاتیک پیکربندی می کنیم:

# src/hello_visitor/settings.py
# Static files (CSS, JavaScript, images)
# a la 
#
# 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 را به عنوان میان افزار با افزودن قطعه زیر به ما ثبت می کنیم src/hello_visitor/settings.py فایل. سفارش ثبت به شدت تعریف شده است، و WhiteNoiseMiddleware باید بلافاصله پس از آن ظاهر شود SecurityMiddleware:

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

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

کش فایل استاتیک اکنون باید در محیط توسعه ما پیکربندی شود و ما را قادر می سازد تا برنامه خود را اجرا کنیم.

اجرای سرور توسعه ما

ما یک برنامه کاملاً کدگذاری شده داریم و اکنون می توانیم وب سرور توسعه جاسازی شده جنگو خود را با این دستور راه اندازی کنیم:

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

وقتی به سمت http://localhost:8000، هر بار که صفحه را بازخوانی می کنیم تعداد آن افزایش می یابد:

یک پنجره مرورگر که صفحه اصلی برنامه جنگو pydantic ما را نشان می دهد، که می گوید:

ما اکنون یک برنامه کاربردی داریم که با بازخوانی صفحه، تعداد بازدیدهایش را افزایش می دهد.

آماده استقرار

این آموزش تمام مراحل مورد نیاز برای ایجاد یک برنامه کاربردی را در یک محیط زیبای توسعه جنگو که با تولید مطابقت دارد را پوشش داده است. در قسمت 3، ما استقرار برنامه خود را در محیط تولید آن پوشش خواهیم داد. همچنین ارزش کاوش در تمرینات اضافی ما را دارد که مزایای جنگو و پیدانتیک را برجسته می کند: آنها در مخزن کد کامل برای این آموزش pydantic.


وبلاگ مهندسی Toptal از استفان دیویدسون برای بررسی و آزمایش بتا نمونه کد ارائه شده در این مقاله تشکر می کند.



منبع

Matthew Newman

Matthew Newman Matthew has over 15 years of experience in database management and software development, with a strong focus on full-stack web applications. He specializes in Django and Vue.js with expertise deploying to both server and serverless environments on AWS. He also works with relational databases and large datasets
[ Back To Top ]