Simplificați setările Django cu indicații de tip: un tutorial Pydantic

Publicat: 2022-07-22

Proiectele Django mă frustrau pentru că îmi lipsea o modalitate robustă și scalabilă de a adăuga noi medii. Reunind indicii de tip pydantic și Python, am construit fundația puternică de care aveam nevoie.

După cum este descris în PEP 484, sugestiile de tip acceptă analiza statică, dar aceleași adnotări sunt disponibile și în timpul execuției. Pachetele terțe precum pydantic oferă verificarea tipului de rulare care utilizează aceste metadate suplimentare. Pydantic folosește indicii de tip Python pentru a ajuta la gestionarea metadatelor setărilor și pentru a efectua validarea datelor de rulare.

Acest tutorial pydantic va arăta efectele pozitive de anvergură ale utilizării gestionării setărilor pydantic cu Django.

Configurația noastră respectă cele mai bune practici descrise pe site-ul web al aplicației Twelve-Factor:

  1. Definiți configurațiile nonconstante și secrete ca variabile de mediu.
  2. În mediile de dezvoltare, definiți variabilele de mediu într-un fișier .env și adăugați .env la .gitignore .
  3. Utilizați mecanismele furnizorului de cloud pentru a defini variabilele de mediu (secrete) pentru mediile QA, staging și producție.
  4. Utilizați un singur fișier settings.py care se configurează singur din variabilele de mediu.
  5. Utilizați pydantic pentru a citi, verifica, valida și tipifica variabilele de mediu pe variabilele Python care definesc configurațiile Django.

Ca alternativă, unii dezvoltatori creează mai multe fișiere de setări, cum ar fi settings_dev.py și settings_prod.py . Din păcate, această abordare nu se extinde bine. Aceasta duce la duplicarea codului, confuzie, erori greu de găsit și eforturi mai mari de întreținere.

Folosind cele mai bune practici menționate mai sus, adăugarea oricărui număr de medii este ușoară, bine definită și fără erori. Deși am putea explora o configurație de mediu mai complicată, ne vom concentra pe două pentru claritate: dezvoltare și producție.

Cum arată asta în practică?

Gestionarea setărilor Pydantic și variabilele de mediu

Acum ne concentrăm pe un exemplu atât în ​​dezvoltare, cât și în producție. Arătăm cum fiecare mediu își configurează setările în mod diferit și cum pydantic le acceptă pe fiecare.

Exemplul nostru de aplicație necesită o bază de date acceptată de Django, așa că trebuie să stocăm șirul de conexiune la baza de date. Mutăm informațiile de configurare a conexiunii la baza de date într-o variabilă de mediu, DATABASE_URL , folosind pachetul Python dj-database-url. Vă rugăm să rețineți că această variabilă este de tip str și este formatată după cum urmează:

 postgres://{user}:{password}@{hostname}:{port}/{database-name} mysql://{user}:{password}@{hostname}:{port}/{database-name} oracle://{user}:{password}@{hostname}:{port}/{database-name} sqlite:///PATH

În mediul nostru de dezvoltare, putem folosi o instanță PostgreSQL conținută în Docker pentru ușurință în utilizare, în timp ce în mediul nostru de producție, vom indica un serviciu de bază de date furnizat.

O altă variabilă pe care vrem să o definim este o booleană, DEBUG . Indicatorul DEBUG din Django nu trebuie să fie niciodată activat într-o implementare de producție. Este destinat să obțină feedback suplimentar în timpul dezvoltării. De exemplu, în modul de depanare, Django va afișa pagini detaliate de eroare atunci când apare o excepție.

Diferite valori pentru dezvoltare și producție ar putea fi definite după cum urmează:

Nume variabilă Dezvoltare Productie
DATABASE_URL postgres://postgres:mypw@localhost:5432/mydb postgres://foo1:foo2@foo3:5432/foo4
DEBUG True False

Folosim modulul de gestionare a setărilor pydantic pentru a gestiona aceste seturi diferite de valori ale variabilelor de mediu în funcție de mediu.

Etape pregătitoare

Pentru a pune acest lucru în practică, începem să configuram mediul nostru de dezvoltare prin crearea unui singur fișier .env cu acest conținut:

 DATABASE_URL=postgres://postgres:mypw@localhost:5432/mydb DEBUG=True

Apoi, adăugăm fișierul .env în fișierul .gitignore al proiectului. Fișierul .gitignore evită salvarea informațiilor potențial sensibile în controlul sursei.

În timp ce această abordare funcționează bine în mediul nostru de dezvoltare, specificațiile pentru mediul nostru de producție utilizează un mecanism diferit. Cele mai bune practici dictează că variabilele de mediu de producție utilizează secrete de mediu. De exemplu, pe Heroku, aceste secrete se numesc Config Vars și sunt configurate prin intermediul Heroku Dashboard. Acestea sunt puse la dispoziția aplicației implementate ca variabile de mediu:

O captură de ecran a interfeței web Config Vars. Bara laterală din stânga are o descriere: „Variile de configurare schimbă modul în care se comportă aplicația dvs. Pe lângă crearea propriei suplimente, unele suplimente vin cu propriile lor.” Secțiunea principală are două rânduri, fiecare cu două câmpuri de text, o pictogramă creion și o pictogramă X. Câmpurile de text au aceleași date ca și coloanele „Nume variabilă” și „Producție” din tabelul anterior. Colțul din dreapta sus are un buton pe care scrie „Hide Config Vars”.

După aceea, trebuie să ajustăm configurația aplicației pentru a citi automat aceste valori din oricare mediu.

Configurarea settings.py Django.py

Să începem cu un nou proiect Django pentru a oferi structura esențială pentru exemplul nostru. Construim un nou proiect Django cu următoarea comandă de terminal:

 $ django-admin startproject mysite

Acum avem o structură de bază a proiectului pentru proiectul mysite . Structura fișierului proiectului este următoarea:

 mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py

Fișierul settings.py conține codul standard care ne permite să gestionăm configurația aplicației. Are multe setări implicite predefinite pe care trebuie să le adaptăm la mediul relevant.

Pentru a gestiona aceste setări ale aplicației folosind variabile de mediu și pydantic, adăugați acest cod în partea de sus a fișierului 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""" DATABASE_URL: PostgresDsn DEBUG: bool = False 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=True) } DEBUG = config.DEBUG

Acest cod face următoarele:

  • Definește o clasă SettingsFromEnvironment , moștenind din clasa BaseSettings a lui BaseSettings .
  • Definește DATABASE_URL și DEBUG , setându-le tipul și implicit opțional folosind indicii de tip Python.
  • Definește o clasă Config care îi spune lui pydantic să caute variabilele într-un fișier .env dacă nu sunt prezente în variabilele de mediu ale sistemului.
  • Instanțiază clasa Config în obiectul config ; variabilele dorite devin disponibile ca config.DATABASE_URL și config.DEBUG .
  • Definește variabilele obișnuite Django DATABASES și DEBUG din acești membri de config .

Același cod rulează în toate mediile, iar pydantic are grijă de următoarele:

  • Acesta caută variabilele de mediu DATABASE_URL și DEBUG .
    • Dacă sunt definite ca variabile de mediu, ca în producție, le va folosi.
    • În caz contrar, extrage acele valori din fișierul .env .
    • Dacă nu găsește o valoare, va face următoarele:
      • Pentru DATABASE_URL , se afișează o eroare.
      • Pentru DEBUG , atribuie o valoare implicită False .
  • Dacă găsește o variabilă de mediu, va verifica tipurile de câmpuri și va da o eroare dacă oricare dintre ele este greșit:
    • Pentru DATABASE_URL , acesta verifică dacă tipul său de câmp este o adresă URL în stil PostgresDsn .
    • Pentru DEBUG , se verifică dacă tipul său de câmp este un boolean pidantic valid, nestrict.

Vă rugăm să rețineți setarea explicită a variabilei de mediu a sistemului de operare din valoarea de configurare pentru DATABASE_URL . Poate părea redundant să setați os.environ["DATABASE_URL"] = config.DATABASE_URL deoarece DATABASE_URL este deja definită ca o variabilă de mediu externă. Cu toate acestea, acest lucru îi permite lui pydantic să analizeze, să verifice și să valideze această variabilă. Dacă variabila de mediu DATABASE_URL lipsește sau este formatată incorect, pydantic va da un mesaj de eroare clar. Aceste verificări ale erorilor sunt neprețuite, deoarece aplicația trece de la dezvoltare la mediile ulterioare.

Dacă o variabilă nu este definită, fie va fi atribuită o variabilă implicită, fie o eroare va solicita să fie definită. Orice prompturi generate detaliază și tipul de variabilă dorit. Un beneficiu secundar al acestor verificări este că noii membri ai echipei și inginerii DevOps descoperă mai ușor care variabile trebuie definite. Acest lucru evită problemele greu de găsit care apar atunci când aplicația rulează fără toate variabilele definite.

Asta e. Aplicația are acum o implementare care poate fi întreținută, de gestionare a setărilor, folosind o singură versiune a settings.py . Frumusețea acestei abordări este că ne permite să specificăm variabilele de mediu corecte într-un fișier .env sau orice alt mijloc dorit disponibil prin mediul nostru de găzduire.

Calea scalabilă

Am folosit managementul setărilor Django cu tastarea pydantic runtime în toate proiectele mele Django. Am descoperit că duce la baze de cod mai mici și mai ușor de întreținut. De asemenea, oferă o abordare bine structurată, auto-documentată și scalabilă pentru adăugarea de noi medii.

Următorul articol din această serie este un tutorial pas cu pas despre construirea unei aplicații Django de la zero, cu gestionarea pidantică a setărilor și implementarea acesteia în Heroku.


Blogul Toptal Engineering își exprimă recunoștința lui Stephen Davidson pentru revizuirea exemplelor de cod prezentate în acest articol.

O versiune anterioară a acestui articol a subliniat o ediție specifică Python. Deoarece Python a acceptat indicii de tip de câțiva ani, am generalizat textul introductiv eliminând această referință de versiune.