Optimizați-vă mediul pentru dezvoltare și producție: un tutorial Pydantic, partea 2

Publicat: 2022-07-22

Dezvoltatorii pot fi cei mai mari dușmani ai lor. Am văzut nenumărate exemple de ingineri care se dezvoltă pe un sistem care nu se potrivește cu mediul lor de producție. Această disonanță duce la muncă suplimentară și nu prinde erorile de sistem decât mai târziu în procesul de dezvoltare. Alinierea acestor configurații va ușura în cele din urmă implementările continue. Având în vedere acest lucru, vom crea un exemplu de aplicație în mediul nostru de dezvoltare Django, simplificat prin Docker, pydantic și conda.

Un mediu de dezvoltare tipic folosește:

  • Un depozit local;
  • O bază de date PostgreSQL bazată pe Docker; și
  • Un mediu conda (pentru a gestiona dependențele Python).

Pydantic și Django sunt potrivite pentru proiecte atât simple, cât și complexe. Următorii pași prezintă o soluție simplă care evidențiază cum să ne oglindim mediile.

Configurarea depozitului Git

Înainte de a începe să scriem cod sau să instalăm sisteme de dezvoltare, să creăm un depozit local Git:

 mkdir hello-visitor cd hello-visitor git init

Vom începe cu un fișier de bază Python .gitignore în rădăcina depozitului. Pe parcursul acestui tutorial, vom adăuga la acest fișier înainte de a adăuga fișiere pe care nu dorim să le urmărească Git.

Configurare Django PostgreSQL folosind Docker

Django necesită o bază de date relațională și, implicit, folosește SQLite. De obicei, evităm SQLite pentru stocarea datelor esențiale, deoarece nu gestionează bine accesul utilizatorilor simultan. Majoritatea dezvoltatorilor optează pentru o bază de date de producție mai tipică, cum ar fi PostgreSQL. Indiferent, ar trebui să folosim aceeași bază de date pentru dezvoltare și producție. Acest mandat arhitectural face parte din aplicația The Twelve-Factor.

Din fericire, operarea unei instanțe locale PostgreSQL cu Docker și Docker Compose este ușoară.

Pentru a evita poluarea directorului nostru rădăcină, vom pune fișierele legate de Docker în subdirectoare separate. Vom începe prin a crea un fișier Docker Compose pentru a implementa 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:

În continuare, vom crea un fișier de mediu docker-compose pentru a configura containerul nostru PostgreSQL:

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

Serverul bazei de date este acum definit și configurat. Să începem containerul nostru în fundal:

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

Este important de remarcat utilizarea sudo în comanda anterioară. Va fi necesar, cu excepția cazului în care sunt urmați pași specifici în mediul nostru de dezvoltare.

Crearea bazei de date

Să ne conectăm și să configuram PostgreSQL folosind o suită de instrumente standard, pgAdmin4. Vom folosi aceleași date de conectare ca și cele configurate anterior în variabilele de mediu.

Acum să creăm o nouă bază de date numită hello_visitor :

Un ecran pgAdmin4 într-un browser care arată fila General într-o casetă de dialog Creare bază de date. Câmpul de text al bazei de date conține valoarea hello_visitor, câmpul proprietar afișează utilizatorul postgres, iar câmpul de comentariu este gol.

Cu baza noastră de date instalată, suntem gata să instalăm mediul nostru de programare.

Managementul mediului Python prin Miniconda

Acum trebuie să setăm un mediu Python izolat și dependențele necesare. Pentru simplitatea instalării și întreținerii, am ales Miniconda.

Să creăm și să activăm mediul nostru conda:

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

Acum, vom crea un fișier, hello-visitor/requirements.txt , care enumerează dependențele noastre 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

În continuare, îi vom cere lui Python să instaleze aceste dependențe:

 cd hello-visitor pip install -r requirements.txt

Dependențele noastre ar trebui să fie acum instalate în pregătirea pentru munca de dezvoltare a aplicației.

Schela Django

Vom construi proiectul și aplicația noastră executând mai întâi django-admin , apoi rulând un fișier pe care îl generează, 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

Apoi, trebuie să configuram Django pentru a ne încărca proiectul. Fișierul settings.py necesită o ajustare a matricei INSTALLED_APPS pentru a înregistra aplicația nouă creată pentru homepage de pornire:

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

Configurarea setărilor aplicației

Folosind abordarea setărilor pydantic și Django prezentată în prima tranșă, trebuie să creăm un fișier cu variabile de mediu pentru sistemul nostru de dezvoltare. Vom muta setările noastre curente în acest fișier după cum urmează:

  1. Creați fișierul src/.env pentru a păstra setările mediului de dezvoltare.
  2. Copiați setările de pe src/hello_visitor/settings.py și adăugați-le la src/.env .
  3. Eliminați acele linii copiate din fișierul settings.py .
  4. Asigurați-vă că șirul de conexiune la baza de date utilizează aceleași acreditări pe care le-am configurat anterior.

Fișierul nostru de mediu, src/.env , ar trebui să arate astfel:

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

Vom configura Django să citească setările din variabilele noastre de mediu folosind pydantic, cu acest fragment de cod:

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

Dacă întâmpinați probleme după finalizarea editărilor anterioare, comparați fișierul nostru settings.py creat cu versiunea din depozitul nostru de cod sursă.

Crearea modelului

Aplicația noastră urmărește și afișează numărul de vizitatori ai paginii de pornire. Avem nevoie de un model care să mențină acest număr și apoi să folosim mapatorul relațional obiect (ORM) al Django pentru a inițializa un singur rând de bază de date printr-o migrare de date.

În primul rând, vom crea modelul nostru 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}"

În continuare, vom declanșa o migrare pentru a crea tabelele bazei de date:

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

Pentru a verifica dacă tabelul homepage_visitcounter există, putem vizualiza baza de date în pgAdmin4.

Apoi, trebuie să punem o valoare inițială în tabelul nostru homepage_visitcounter . Să creăm un fișier de migrare separat pentru a realiza acest lucru folosind schelele Django:

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

Vom ajusta fișierul de migrare creat pentru a utiliza metoda VisitCounter.insert_visit_counter pe care am definit-o la începutul acestei secțiuni:

 # 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), ]

Acum suntem gata să executăm această migrare modificată pentru aplicația de pe homepage de pornire:

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

Să verificăm dacă migrarea a fost executată corect, uitându-ne la conținutul tabelului nostru:

Un ecran pgAdmin4 într-un browser care arată o interogare „SELECT * FROM public.homepage_visitcounter ORDER BY id ASC”. Fila Ieșire de date arată că există un rând în acel tabel. Valoarea câmpului ID-ul cheii surogat este 1, iar valoarea câmpului de numărare este 0.

Vedem că tabelul nostru homepage_visitcounter există și a fost completat cu un număr inițial de vizite de 0. Cu baza noastră de date redusă, ne vom concentra pe crearea interfeței noastre de utilizare.

Creați și configurați vizualizările noastre

Trebuie să implementăm două părți principale ale interfeței noastre de utilizare: o vizualizare și un șablon.

Creăm vizualizarea paginii de homepage pentru a crește numărul de vizitatori, îl salvăm în baza de date și trecem acel număr șablonului pentru afișare:

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

Aplicația noastră Django trebuie să asculte solicitările care vizează pagina de homepage . Pentru a configura această setare, vom adăuga acest fișier:

 # 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"), ]

Pentru ca aplicația noastră de pe pagina de homepage să fie difuzată, trebuie să o înregistrăm într-un alt fișier 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), ]

Șablonul HTML de bază al proiectului nostru va locui într-un fișier nou, 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>

Vom extinde șablonul de bază pentru aplicația noastră pentru homepage de pornire într-un fișier nou, 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 %}

Ultimul pas în crearea interfeței noastre de utilizare este să îi spunem lui Django unde să găsească aceste șabloane. Să adăugăm un element de dicționar TEMPLATES['DIRS'] în fișierul nostru settings.py :

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

Interfața noastră cu utilizatorul este acum implementată și suntem aproape gata să testăm funcționalitatea aplicației noastre. Înainte de a face testarea, trebuie să punem la punct ultima parte a mediului nostru: stocarea în cache a conținutului static.

Configurația noastră de conținut static

Pentru a evita folosirea de comenzi rapide arhitecturale pe sistemul nostru de dezvoltare, vom configura stocarea în cache a conținutului static pentru a oglindi mediul nostru de producție.

Vom păstra toate fișierele statice ale proiectului nostru într-un singur director, src/static și vom instrui Django să colecteze acele fișiere înainte de implementare.

Vom folosi sigla Toptal pentru favicon -ul aplicației noastre și o vom stoca ca src/static/favicon.ico :

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

În continuare, vom configura Django să colecteze fișierele statice:

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

Vrem doar să stocăm fișierele noastre statice originale în depozitul de cod sursă; nu dorim să stocăm versiunile optimizate pentru producție. Să-l adăugăm pe acesta din urmă la .gitignore -ul nostru cu această linie simplă:

 staticfiles

Cu depozitul nostru de cod sursă care stochează corect fișierele necesare, acum trebuie să ne configuram sistemul de stocare în cache pentru a funcționa cu aceste fișiere statice.

Memorarea în cache a fișierelor statice

În producție – și, prin urmare, și în mediul nostru de dezvoltare – vom folosi WhiteNoise pentru a servi mai eficient fișierele statice ale aplicației noastre Django.

Înregistrăm WhiteNoise ca middleware adăugând următorul fragment în fișierul nostru src/hello_visitor/settings.py . Ordinea de înregistrare este strict definită, iar WhiteNoiseMiddleware trebuie să apară imediat după SecurityMiddleware :

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

Memorarea în cache a fișierelor static ar trebui acum configurată în mediul nostru de dezvoltare, permițându-ne să rulăm aplicația noastră.

Rularea serverului nostru de dezvoltare

Avem o aplicație complet codificată și acum putem lansa serverul nostru web de dezvoltare încorporat Django cu această comandă:

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

Când navigăm la http://localhost:8000 , numărul va crește de fiecare dată când reîmprospătăm pagina:

O fereastră de browser care arată ecranul principal al aplicației noastre pidantice Django, care spune: „Bună, vizitator!” pe o linie și „1” pe următoarea.

Acum avem o aplicație funcțională care își va crește numărul de vizite pe măsură ce reîmprospăm pagina.

Gata de implementare

Acest tutorial a acoperit toți pașii necesari pentru a crea o aplicație funcțională într-un mediu de dezvoltare Django frumos care se potrivește cu producția. În partea 3, vom aborda implementarea aplicației noastre în mediul său de producție. De asemenea, merită să explorați exercițiile noastre suplimentare care evidențiază beneficiile Django și pydantic: sunt incluse în depozitul complet de cod pentru acest tutorial pydantic.


Blogul Toptal Engineering îi mulțumește lui Stephen Davidson pentru revizuirea și testarea beta a mostrelor de cod prezentate în acest articol.