Semplifica le tue impostazioni Django con suggerimenti di tipo: un tutorial pidantico

Pubblicato: 2022-07-22

I 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:

  1. Definire configurazioni non costanti e segrete come variabili di ambiente.
  2. Negli ambienti di sviluppo, definisci le variabili di ambiente in un file .env e aggiungi .env a .gitignore .
  3. Utilizza i meccanismi del provider di servizi cloud per definire le variabili di ambiente (segrete) per gli ambienti di controllo qualità, staging e produzione.
  4. Utilizzare un unico file settings.py che si autoconfigura dalle variabili di ambiente.
  5. 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:

Uno screenshot dell'interfaccia web di Config Vars. La barra laterale sinistra ha una descrizione: "Config vars cambia il modo in cui si comporta la tua app. Oltre a crearne di tue, alcuni componenti aggiuntivi sono dotati di propri". La sezione principale ha due righe, ciascuna con due campi di testo, un'icona a forma di matita e un'icona X. I campi di testo hanno gli stessi dati delle colonne "Nome variabile" e "Produzione" della tabella precedente. L'angolo in alto a destra ha un pulsante che dice "Nascondi variabili di configurazione".

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 di BaseSettings .
  • Definisce DATABASE_URL e DEBUG , 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'oggetto config ; le variabili desiderate diventano disponibili come config.DATABASE_URL e config.DEBUG .
  • Definisce le normali variabili Django DATABASES e DEBUG da questi membri di config .

Lo stesso codice viene eseguito in tutti gli ambienti e pydantic si occupa di quanto segue:

  • Cerca le variabili di ambiente DATABASE_URL e DEBUG .
    • 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 di False .
  • 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 stile PostgresDsn .
    • Per DEBUG , verifica che il tipo di campo sia un booleano pydantic valido e non rigoroso.

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.