優化開發和生產環境: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。 無論如何,我們應該使用相同的數據庫進行開發和生產。 此架構任務是十二要素應用程序的一部分。

幸運的是,使用 Docker 和 Docker Compose 操作本地 PostgreSQL 實例輕而易舉。

為了避免污染我們的根目錄,我們將把與 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 的使用。 除非在我們的開發環境中遵循特定步驟,否則它將是必需的。

數據庫創建

讓我們使用標準工具套件 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

現在,我們將創建一個文件hello-visitor/requirements.txt ,枚舉我們的 Python 依賴項:

 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文件需要調整INSTALLED_APPS數組來註冊我們新創建的homepage應用程序:

 # 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" ]'

我們將配置 Django 以使用 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文件與我們源代碼存儲庫中的版本進行比較。

模型創建

我們的應用程序跟踪並顯示主頁訪問者數量。 我們需要一個模型來保存該計數,然後使用 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

讓我們通過查看表的內容來驗證遷移是否正確執行:

瀏覽器中的 pgAdmin4 屏幕顯示查詢“SELECT * FROM public.homepage_visitcounter ORDER BY id ASC”。數據輸出選項卡顯示該表中有一行。代理鍵 id 字段值為 1,count 字段值為 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 在部署之前收集這些文件。

我們將使用 Toptal 的徽標作為我們應用程序的favicon並將其存儲為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 註冊為中間件。 註冊順序是嚴格定義的, WhiteNoiseMiddleware必須緊跟在SecurityMiddleware之後:

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

現在應該在我們的開發環境中配置靜態文件緩存,使我們能夠運行我們的應用程序。

運行我們的開發服務器

我們有一個完全編碼的應用程序,現在可以使用以下命令啟動 Django 的嵌入式開發 Web 服務器:

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

當我們導航到http://localhost:8000時,每次刷新頁面時計數都會增加:

一個瀏覽器窗口,顯示我們的 pydantic Django 應用程序的主屏幕,上面寫著“你好,訪客!”在一行和下一行“1”。

我們現在有一個工作應用程序,它會在我們刷新頁面時增加其訪問次數。

準備部署

本教程涵蓋了在與生產相匹配的漂亮 Django 開發環境中創建工作應用程序所需的所有步驟。 在第 3 部分中,我們將介紹將應用程序部署到其生產環境。 還值得探索我們的其他練習,突出 Django 和 pydantic 的優點:它們包含在本 pydantic 教程的代碼完整存儲庫中。


Toptal 工程博客對 Stephen Davidson 對本文中提供的代碼示例進行審查和 beta 測試表示感謝。