
بروزرسانی: 01 تیر 1404
محیط توسعه بهینه: آموزش Pydantic، قسمت 2
توسعه دهندگان می توانند بدترین دشمنان خود باشند. من نمونه های بی شماری از مهندسین را دیده ام که روی سیستمی توسعه می دهند که با محیط تولیدشان مطابقت ندارد. این ناهماهنگی منجر به کار اضافی و عدم شناسایی خطاهای سیستم تا بعداً در فرآیند توسعه می شود. تراز کردن این تنظیمات در نهایت استقرار مداوم را تسهیل می کند. با در نظر گرفتن این موضوع، ما یک نمونه برنامه کاربردی در محیط توسعه جنگو خود ایجاد خواهیم کرد که از طریق Docker، pydantic و conda ساده شده است.
یک محیط توسعه معمولی از موارد زیر استفاده می کند:
- یک مخزن محلی؛
- پایگاه داده PostgreSQL مبتنی بر Docker. و
- یک محیط conda (برای مدیریت وابستگی های پایتون).
Pydantic و Django برای پروژه های ساده و پیچیده مناسب هستند. مراحل زیر راه حل ساده ای را نشان می دهد که نحوه انعکاس محیط هایمان را برجسته می کند.
پیکربندی مخزن Git
قبل از شروع نوشتن کد یا نصب سیستم های توسعه، بیایید یک مخزن Git محلی ایجاد کنیم:
mkdir hello-visitorcd hello-visitorgit init
ما با یک شروع می کنیم پایتون پایه .gitignore
فایل در ریشه مخزن در طول این آموزش، قبل از افزودن فایل هایی که نمی خواهیم Git آنها را ردیابی کند، به این فایل اضافه می کنیم.
پیکربندی Django PostgreSQL با استفاده از Docker
جنگو به یک پایگاه داده رابطه ای نیاز دارد و به طور پیش فرض از SQLite استفاده می کند. ما معمولاً از SQLite برای ذخیره سازی داده های حیاتی اجتناب می کنیم زیرا دسترسی همزمان کاربر را به خوبی مدیریت نمی کند. اکثر توسعه دهندگان یک پایگاه داده تولید معمولی مانند PostgreSQL را انتخاب می کنند. صرف نظر از این، ما باید از همان پایگاه داده برای توسعه و تولید استفاده کنیم. این دستور معماری بخشی از برنامه دوازده عاملی.
خوشبختانه، یک نمونه محلی PostgreSQL با داکر و Docker Compose یک نسیم است
برای جلوگیری از آلوده شدن دایرکتوری ریشه ما، فایل های مربوط به Docker را در زیر شاخه های جداگانه قرار می دهیم. ما با ایجاد یک فایل Docker Compose برای استقرار PostgreSQL شروع می کنیم:
# docker-services/docker-compose.ymlversion: "3.9"services: db: image: "postgres:13.4" env_file: .env volumes: - hello-visitor-postgres:/var/lib/postgresql/data ports: - ${POSTGRES_PORT}:5432volumes: hello-visitor-postgres:
بعد، یک را ایجاد می کنیم docker-compose
فایل محیطی برای پیکربندی ظرف PostgreSQL:
# docker-services/.envPOSTGRES_USER=postgresPOSTGRES_PASSWORD=MyDBPassword123# The \'maintenance\' databasePOSTGRES_DB=postgres# The port exposed to localhostPOSTGRES_PORT=5432
سرور پایگاه داده اکنون تعریف و پیکربندی شده است. بیایید ظرف خود را در پس زمینه شروع کنیم:
sudo docker compose --project-directory docker-services/ up -d
توجه به استفاده از sudo در دستور قبلی ضروری است. مورد نیاز خواهد بود مگر اینکه مراحل خاص در محیط توسعه ما دنبال می شوند.
ایجاد پایگاه داده
بیایید با استفاده از یک مجموعه ابزار استاندارد به PostgreSQL متصل و پیکربندی کنیم. pgAdmin4. ما از همان اعتبار ورود به سیستم استفاده خواهیم کرد که قبلاً در متغیرهای محیطی پیکربندی شده بود.
حالا بیایید یک پایگاه داده جدید به نام ایجاد کنیم hello_visitor
:
با در اختیار داشتن پایگاه داده، ما آماده نصب محیط برنامه نویسی خود هستیم.
مدیریت محیط زیست پایتون از طریق Miniconda
اکنون باید یک محیط Python ایزوله و وابستگی های مورد نیاز را راه اندازی کنیم. برای سادگی راه اندازی و نگهداری، ما را انتخاب کردیم مینیکوندا.
بیایید محیط conda خود را ایجاد و فعال کنیم:
conda create --name hello-visitor python=3.9conda activate hello-visitor
اکنون یک فایل ایجاد می کنیم، hello-visitor/requirements.txt
با برشمردن وابستگی های پایتون ما:
django# PostgreSQL database adapter:psycopg2# Pushes .env key-value pairs into environment variables:python-dotenvpydantic# Utility library to read database connection information:dj-database-url# Static file caching:whitenoise# Python WSGI HTTP Server:gunicorn
در مرحله بعد، از پایتون می خواهیم این وابستگی ها را نصب کند:
cd hello-visitorpip install -r requirements.txt
وابستگی های ما اکنون باید برای آماده سازی کار توسعه برنامه نصب شوند.
داربست جنگو
با اجرای اول پروژه و برنامه خود را داربست خواهیم داشت django-admin
، سپس فایلی را که تولید می کند اجرا کنید، manage.py
:
# From the `hello-visitor` directorymkdir srccd 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 و جنگو که در قسمت اول نشان داده شد، باید یک فایل متغیرهای محیطی برای سیستم توسعه خود ایجاد کنیم. تنظیمات فعلی خود را به صورت زیر به این فایل منتقل می کنیم:
- فایل را ایجاد کنید
src/.env
برای نگه داشتن تنظیمات محیط توسعه ما. - تنظیمات را از
src/hello_visitor/settings.py
و آنها را به آن اضافه کنیدsrc/.env
. - آن خطوط کپی شده را از قسمت حذف کنید
settings.py
فایل. - اطمینان حاصل کنید که رشته اتصال پایگاه داده از همان اعتبارنامه هایی استفاده می کند که قبلاً پیکربندی کردیم.
فایل محیطی ما، src/.env
، باید به شکل زیر باشد:
DATABASE_URL=postgres://postgres:MyDBPassword123@localhost:5432/hello_visitorDATABASE_SSL=FalseSECRET_KEY="django-insecure-sackl&7(1hc3+%#*4e=)^q3qiw!hnnui*-^($o8t@2^^qqs=%i"DEBUG=TrueDEBUG_TEMPLATES=TrueUSE_SSL=FalseALLOWED_HOSTS=\'[ "localhost", "127.0.0.1", "0.0.0.0"]\'
ما جنگو را برای خواندن تنظیمات از متغیرهای محیطی با استفاده از pydantic، با این قطعه کد پیکربندی می کنیم:
# src/hello_visitor/settings.pyimport osfrom pathlib import Pathfrom 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.parentclass 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 = Trueconfig = SettingsFromEnvironment()os.environ["DATABASE_URL"] = config.DATABASE_URLDATABASES = { "default": dj_database_url.config(conn_max_age=600, ssl_require=config.DATABASE_SSL)}SECRET_KEY = config.SECRET_KEYDEBUG = config.DEBUGDEBUG_TEMPLATES = config.DEBUG_TEMPLATESUSE_SSL = config.USE_SSLALLOWED_HOSTS = config.ALLOWED_HOSTS# ...
اگر پس از تکمیل ویرایش های قبلی با مشکلی مواجه شدید، ساخته شده ما را مقایسه کنید settings.py
فایل با نسخه در مخزن کد منبع ما.
ایجاد مدل
برنامه ما تعداد بازدیدکنندگان صفحه اصلی را ردیابی و نمایش می دهد. ما نیاز داریم a مدل برای نگه داشتن آن شمارش و سپس استفاده نقشه بردار شی رابطه ای جنگو (ORM) برای مقداردهی اولیه یک ردیف پایگاه داده از طریق انتقال داده.
اول، ما خود را ایجاد می کنیم VisitCounter
مدل:
# hello-visitor/src/homepage/models.py"""Defines the models"""from django.db import modelsclass 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` folderpython manage.py makemigrationspython manage.py migrate
برای تأیید اینکه homepage_visitcounter
جدول وجود دارد، ما می توانیم پایگاه داده را در pgAdmin4 مشاهده کنیم.
در مرحله بعد، باید یک مقدار اولیه در ما قرار دهیم homepage_visitcounter
جدول. بیایید یک فایل مهاجرت جداگانه برای انجام این کار با استفاده از داربست جنگو ایجاد کنیم:
# from the \'src\' directorypython 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 migrationsfrom ..models import VisitCounterdef 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\' directorypython manage.py migrate homepage
بیایید با مشاهده محتویات جدول بررسی کنیم که انتقال به درستی انجام شده است:
ما می بینیم که ما homepage_visitcounter
جدول وجود دارد و با تعداد بازدید اولیه 0 پر شده است. با مجذور شدن پایگاه داده، ما بر ایجاد رابط کاربری خود تمرکز خواهیم کرد.
دیدگاه های ما را ایجاد و پیکربندی کنید
ما باید دو بخش اصلی UI خود را پیاده سازی کنیم: یک view و یک الگو.
ما ایجاد می کنیم homepage
مشاهده کنید تا تعداد بازدیدکنندگان را افزایش دهید، آن را در پایگاه داده ذخیره کنید و آن تعداد را برای نمایش به قالب ارسال کنید:
# src/homepage/views.pyfrom django.shortcuts import get_object_or_404, renderfrom .models import VisitCounterdef 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 pathfrom . import views# The namespace of the apps\' URLconfapp_name = "homepage" # pylint: disable=invalid-nameurlpatterns = [ path("", views.index, name="index"),]
برای ما homepage
درخواست خدمت، ما باید آن را در یک متفاوت ثبت نام کنید urls.py
فایل:
# src/hello_visitor/urls.pyfrom django.contrib import adminfrom django.urls import include, pathurlpatterns = [ 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.pyTEMPLATES = [ { ... \'DIRS\': [BASE_DIR / \'templates\'], ... },]
رابط کاربری ما اکنون پیاده سازی شده است و تقریباً آماده آزمایش عملکرد برنامه خود هستیم. قبل از اینکه آزمایش خود را انجام دهیم، باید آخرین قطعه محیط خود را در جای خود قرار دهیم: ذخیره محتوای ثابت.
پیکربندی محتوای ثابت ما
برای جلوگیری از استفاده از میانبرهای معماری در سیستم توسعه خود، ذخیره محتوای ثابت را برای انعکاس محیط تولید خود پیکربندی می کنیم.
ما تمام فایل های استاتیک پروژه خود را در یک دایرکتوری نگه می داریم، src/static
و به جنگو دستور دهید تا آن فایل ها را قبل از استقرار جمع آوری کند.
ما از آرم Toptal برای برنامه خود استفاده خواهیم کرد favicon
و آن را به عنوان ذخیره کنید src/static/favicon.ico
:
# from `src` foldermkdir staticcd staticwget 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 filesSTATICFILES_DIRS = [BASE_DIR / "static"]# Build output location where Django collects all static filesSTATIC_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` folderpython manage.py runserver
وقتی به سمت http://localhost:8000
، هر بار که صفحه را بازخوانی می کنیم تعداد آن افزایش می یابد:
ما اکنون یک برنامه کاربردی داریم که با بازخوانی صفحه، تعداد بازدیدهایش را افزایش می دهد.
آماده استقرار
این آموزش تمام مراحل مورد نیاز برای ایجاد یک برنامه کاربردی را در یک محیط زیبای توسعه جنگو که با تولید مطابقت دارد را پوشش داده است. در قسمت 3، ما استقرار برنامه خود را در محیط تولید آن پوشش خواهیم داد. همچنین ارزش کاوش در تمرینات اضافی ما را دارد که مزایای جنگو و پیدانتیک را برجسته می کند: آنها در مخزن کد کامل برای این آموزش pydantic.
وبلاگ مهندسی Toptal از استفان دیویدسون برای بررسی و آزمایش بتا نمونه کد ارائه شده در این مقاله تشکر می کند.
منبع