Semplifica le tue impostazioni Django con suggerimenti di tipo: un tutorial pidantico
Pubblicato: 2022-07-22I progetti Django mi frustravano perché mi mancava un modo robusto e scalabile per aggiungere nuovi ambienti. Riunendo suggerimenti di tipo pydantic e Python, ho costruito le potenti basi di cui avevo bisogno.
Come descritto in PEP 484, gli hint di tipo supportano l'analisi statica, ma queste stesse annotazioni sono disponibili anche in fase di esecuzione. Pacchetti di terze parti come pydantic offrono il controllo del tipo di runtime che utilizza questi metadati aggiuntivi. Pydantic usa suggerimenti di tipo Python per aiutare a gestire i metadati delle impostazioni ed eseguire la convalida dei dati di runtime.
Questo tutorial pydantic mostrerà gli effetti positivi e di vasta portata dell'utilizzo della gestione delle impostazioni pydantic con Django.
La nostra configurazione aderisce alle migliori pratiche descritte sul sito Web dell'app Twelve-Factor:
- Definire configurazioni non costanti e segrete come variabili di ambiente.
- Negli ambienti di sviluppo, definisci le variabili di ambiente in un file
.env
e aggiungi.env
a.gitignore
. - Utilizza i meccanismi del provider di servizi cloud per definire le variabili di ambiente (segrete) per gli ambienti di controllo qualità, staging e produzione.
- Utilizzare un unico file
settings.py
che si autoconfigura dalle variabili di ambiente. - Usa pydantic per leggere, controllare, convalidare e typecast variabili di ambiente su variabili Python che definiscono le configurazioni di Django.
In alternativa, alcuni sviluppatori creano più file di impostazioni, come settings_dev.py
e settings_prod.py
. Sfortunatamente, questo approccio non si adatta bene. Porta a duplicazione del codice, confusione, bug difficili da trovare e maggiori sforzi di manutenzione.
Utilizzando le suddette best practice, aggiungere un numero qualsiasi di ambienti è facile, ben definito ea prova di errore. Sebbene potremmo esplorare una configurazione dell'ambiente più complicata, ci concentreremo su due per chiarezza: sviluppo e produzione.
Che aspetto ha in pratica?
Gestione delle Impostazioni Pydantic e Variabili d'Ambiente
Ora ci concentriamo su un esempio sia nello sviluppo che nella produzione. Mostriamo come ogni ambiente configura le proprie impostazioni in modo diverso e come pydantic li supporta ciascuno.
La nostra applicazione di esempio richiede un database supportato da Django, quindi è necessario archiviare la stringa di connessione al database. Trasferiamo le informazioni sulla configurazione della connessione al database in una variabile di ambiente, DATABASE_URL
, utilizzando il pacchetto Python dj-database-url. Si noti che questa variabile è di tipo str
ed è formattata come segue:
postgres://{user}:{password}@{hostname}:{port}/{database-name} mysql://{user}:{password}@{hostname}:{port}/{database-name} oracle://{user}:{password}@{hostname}:{port}/{database-name} sqlite:///PATH
Nel nostro ambiente di sviluppo, possiamo utilizzare un'istanza PostgreSQL contenuta in Docker per facilità d'uso, mentre nel nostro ambiente di produzione punteremo a un servizio di database con provisioning.
Un'altra variabile che vogliamo definire è una booleana, DEBUG
. Il flag DEBUG in Django non deve mai essere attivato in una distribuzione di produzione. Ha lo scopo di ottenere un feedback aggiuntivo durante lo sviluppo. Ad esempio, in modalità debug, Django visualizzerà pagine di errore dettagliate quando si verifica un'eccezione.
Diversi valori per lo sviluppo e la produzione possono essere definiti come segue:
Nome variabile | Sviluppo | Produzione |
---|---|---|
DATABASE_URL | postgres://postgres:mypw@localhost:5432/mydb | postgres://foo1:foo2@foo3:5432/foo4 |
DEBUG | True | False |
Utilizziamo il modulo di gestione delle impostazioni pydantic per gestire questi diversi set di valori delle variabili di ambiente a seconda dell'ambiente.
Passi preparatori
Per mettere in pratica ciò, iniziamo a configurare il nostro ambiente di sviluppo creando il nostro unico file .env
con questo contenuto:
DATABASE_URL=postgres://postgres:mypw@localhost:5432/mydb DEBUG=True
Successivamente, aggiungiamo il file .env
al file .gitignore
del progetto. Il file .gitignore
evita il salvataggio di informazioni potenzialmente riservate nel controllo del codice sorgente.
Mentre questo approccio funziona bene nel nostro ambiente di sviluppo, la nostra specifica dell'ambiente di produzione utilizza un meccanismo diverso. Le nostre best practice impongono che le variabili dell'ambiente di produzione utilizzino i segreti dell'ambiente. Ad esempio, su Heroku, questi segreti sono chiamati Config Vars e vengono configurati tramite la dashboard di Heroku. Sono resi disponibili all'applicazione distribuita come variabili di ambiente:
Successivamente, è necessario regolare la configurazione dell'applicazione per leggere automaticamente questi valori da entrambi gli ambienti.
Configurazione di settings.py
di Django
Iniziamo con un nuovo progetto Django per fornire la struttura essenziale per il nostro esempio. Impalcatura di un nuovo progetto Django con il seguente comando da terminale:
$ django-admin startproject mysite
Ora abbiamo una struttura di progetto di base per il progetto mysite
. La struttura del file del progetto è la seguente:
mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py
Il file settings.py
contiene il codice boilerplate che ci consente di gestire la configurazione dell'applicazione. Ha molte impostazioni predefinite predefinite che dobbiamo adattare all'ambiente pertinente.
Per gestire queste impostazioni dell'applicazione utilizzando variabili di ambiente e pydantic, aggiungi questo codice all'inizio del file 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
Questo codice esegue le seguenti operazioni:
- Definisce una classe
SettingsFromEnvironment
, ereditando dalla classe BaseSettings diBaseSettings
. - Definisce
DATABASE_URL
eDEBUG
, impostandone il tipo e l'impostazione predefinita facoltativa utilizzando gli hint di tipo Python. - Definisce una classe
Config
che dice a pydantic di cercare le variabili in un file.env
se non sono presenti nelle variabili di ambiente del sistema. - Crea un'istanza della classe
Config
nell'oggettoconfig
; le variabili desiderate diventano disponibili comeconfig.DATABASE_URL
econfig.DEBUG
. - Definisce le normali variabili Django
DATABASES
eDEBUG
da questi membri diconfig
.
Lo stesso codice viene eseguito in tutti gli ambienti e pydantic si occupa di quanto segue:
- Cerca le variabili di ambiente
DATABASE_URL
eDEBUG
.- Se definite come variabili di ambiente, come in produzione, le utilizzerà.
- In caso contrario, estrae quei valori dal file
.env
. - Se non trova un valore, eseguirà le seguenti operazioni:
- Per
DATABASE_URL
, genera un errore. - Per
DEBUG
, assegna un valore predefinito diFalse
.
- Per
- Se trova una variabile di ambiente, controllerà i tipi di campo e darà un errore se uno di essi è sbagliato:
- Per
DATABASE_URL
, verifica che il tipo di campo sia un URL in stilePostgresDsn
. - Per
DEBUG
, verifica che il tipo di campo sia un booleano pydantic valido e non rigoroso.
- Per
Si prega di notare l'impostazione esplicita della variabile di ambiente del sistema operativo dal valore di configurazione per DATABASE_URL
. Può sembrare ridondante impostare os.environ["DATABASE_URL"] = config.DATABASE_URL
perché DATABASE_URL
è già definito come una variabile di ambiente esterna. Tuttavia, ciò consente a pydantic di analizzare, controllare e convalidare questa variabile. Se la variabile di ambiente DATABASE_URL
è mancante o formattata in modo errato, pydantic visualizzerà un chiaro messaggio di errore. Questi controlli degli errori sono preziosi quando l'applicazione passa dallo sviluppo agli ambienti successivi.
Se una variabile non è definita, verrà assegnato un valore predefinito o verrà visualizzato un errore per richiederne la definizione. Eventuali prompt generati descrivono anche il tipo di variabile desiderato. Un vantaggio collaterale di questi controlli è che i nuovi membri del team e gli ingegneri DevOps scoprono più facilmente quali variabili devono essere definite. Ciò evita i problemi difficili da trovare che risultano quando l'applicazione viene eseguita senza tutte le variabili definite.
Questo è tutto. L'applicazione ora ha un'implementazione gestibile di gestione delle impostazioni che utilizza un'unica versione di settings.py
. La bellezza di questo approccio è che ci consente di specificare le variabili di ambiente corrette in un file .env
o in qualsiasi altro mezzo desiderato disponibile tramite il nostro ambiente di hosting.
Il percorso scalabile
Ho utilizzato la gestione delle impostazioni di Django con la digitazione di runtime pydantic in tutti i miei progetti Django. Ho scoperto che porta a basi di codice più piccole e più gestibili. Fornisce inoltre un approccio ben strutturato, autodocumentante e scalabile per l'aggiunta di nuovi ambienti.
Il prossimo articolo di questa serie è un tutorial passo dopo passo sulla creazione di un'applicazione Django da zero, con la gestione delle impostazioni pydantic e sulla sua distribuzione in Heroku.
Il Toptal Engineering Blog estende la sua gratitudine a Stephen Davidson per aver esaminato gli esempi di codice presentati in questo articolo.
Una versione precedente di questo articolo enfatizzava una versione specifica di Python. Poiché Python supporta i suggerimenti sui tipi da diversi anni, abbiamo generalizzato il testo introduttivo rimuovendo questo riferimento alla versione.