Usprawnij swoje ustawienia Django dzięki wskazówkom typu: samouczek pydantyczny
Opublikowany: 2022-07-22Projekty Django frustrowały mnie, ponieważ brakowało mi solidnego i skalowalnego sposobu na dodawanie nowych środowisk. Łącząc podpowiedzi typu pydantic i Python, zbudowałem potężny fundament, którego potrzebowałem.
Jak opisano w PEP 484, wskazówki dotyczące typów obsługują analizę statyczną, ale te same adnotacje są również dostępne w czasie wykonywania. Pakiety innych firm, takie jak pydantic, oferują sprawdzanie typu środowiska uruchomieniowego, które wykorzystuje te dodatkowe metadane. Pydantic korzysta z podpowiedzi w języku Python, aby pomóc w zarządzaniu metadanymi ustawień i przeprowadzaniu walidacji danych w czasie wykonywania.
Ten samouczek pydantyczny pokaże daleko idące, pozytywne efekty korzystania z zarządzania ustawieniami pydantycznymi w Django.
Nasza konfiguracja jest zgodna z najlepszymi praktykami opisanymi na stronie aplikacji Twelve-Factor:
- Zdefiniuj konfiguracje niestałe i tajne jako zmienne środowiskowe.
- W środowiskach programistycznych zdefiniuj zmienne środowiskowe w pliku
.env
i dodaj.env
do.gitignore
. - Użyj mechanizmów dostawcy chmury, aby zdefiniować (tajne) zmienne środowiskowe dla środowisk QA, pomostowych i produkcyjnych.
- Użyj pojedynczego pliku
settings.py
, który konfiguruje się na podstawie zmiennych środowiskowych. - Użyj pydantic do odczytywania, sprawdzania, walidacji i typowania zmiennych środowiskowych na zmienne Pythona, które definiują konfiguracje Django.
Alternatywnie, niektórzy programiści tworzą wiele plików ustawień, takich jak settings_dev.py
i settings_prod.py
. Niestety to podejście nie jest dobrze skalowalne. Prowadzi to do powielania kodu, zamieszania, trudnych do znalezienia błędów i większych nakładów na konserwację.
Korzystając z wyżej wymienionych najlepszych praktyk, dodawanie dowolnej liczby środowisk jest łatwe, dobrze zdefiniowane i odporne na błędy. Chociaż moglibyśmy zbadać bardziej skomplikowaną konfigurację środowiska, dla jasności skupimy się na dwóch: rozwoju i produkcji.
Jak to wygląda w praktyce?
Zarządzanie ustawieniami pidantycznymi i zmiennymi środowiskowymi
Skupiamy się teraz na przykładzie zarówno w rozwoju, jak i produkcji. Pokazujemy, jak każde środowisko inaczej konfiguruje swoje ustawienia i jak pydantic je obsługuje.
Nasza przykładowa aplikacja wymaga bazy danych obsługiwanej przez Django, więc musimy przechowywać parametry połączenia z bazą danych. Przenosimy informacje o konfiguracji połączenia z bazą danych do zmiennej środowiskowej DATABASE_URL
, używając pakietu Pythona dj-database-url. Zwróć uwagę, że ta zmienna jest typu str
i jest sformatowana w następujący sposób:
postgres://{user}:{password}@{hostname}:{port}/{database-name} mysql://{user}:{password}@{hostname}:{port}/{database-name} oracle://{user}:{password}@{hostname}:{port}/{database-name} sqlite:///PATH
W naszym środowisku programistycznym możemy użyć instancji PostgreSQL zawierającej Docker, aby ułatwić obsługę, podczas gdy w naszym środowisku produkcyjnym wskażemy na udostępnioną usługę bazy danych.
Kolejną zmienną, którą chcemy zdefiniować, jest wartość logiczna DEBUG
. Flaga DEBUG w Django nie może być nigdy włączona we wdrożeniu produkcyjnym. Ma na celu uzyskanie dodatkowych informacji zwrotnych podczas opracowywania. Na przykład w trybie debugowania Django wyświetli szczegółowe strony błędów, gdy wystąpi wyjątek.
Różne wartości rozwoju i produkcji można zdefiniować w następujący sposób:
Nazwa zmiennej | Rozwój | Produkcja |
---|---|---|
DATABASE_URL | postgres://postgres:mypw@localhost:5432/mydb | postgres://foo1:foo2@foo3:5432/foo4 |
DEBUG | True | False |
Używamy modułu zarządzania ustawieniami pydantic do zarządzania tymi różnymi zestawami wartości zmiennych środowiskowych w zależności od środowiska.
Kroki przygotowawcze
Aby zastosować to w praktyce, zaczynamy konfigurować nasze środowisko programistyczne, tworząc nasz pojedynczy plik .env
z następującą zawartością:
DATABASE_URL=postgres://postgres:mypw@localhost:5432/mydb DEBUG=True
Następnie dodajemy plik .env
do pliku .gitignore
projektu. Plik .gitignore
pozwala uniknąć zapisywania potencjalnie poufnych informacji w kontroli źródła.
Podczas gdy takie podejście sprawdza się dobrze w naszym środowisku programistycznym, specyfikacja środowiska produkcyjnego wykorzystuje inny mechanizm. Zgodnie z naszymi najlepszymi praktykami zmienne środowiska produkcyjnego wykorzystują tajemnice środowiska. Na przykład w Heroku te sekrety nazywają się Config Vars i są konfigurowane za pomocą pulpitu nawigacyjnego Heroku. Są one udostępniane wdrożonej aplikacji jako zmienne środowiskowe:
Następnie musimy dostosować konfigurację aplikacji, aby automatycznie odczytywać te wartości z dowolnego środowiska.
Konfigurowanie settings.py
Django.py
Zacznijmy od nowego projektu Django, aby zapewnić podstawową strukturę naszego przykładu. Tworzymy szkielet nowego projektu Django za pomocą następującego polecenia terminala:

$ django-admin startproject mysite
Mamy teraz podstawową strukturę projektu mysite
. Struktura plików projektu jest następująca:
mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py
Plik settings.py
zawiera standardowy kod, który pozwala nam zarządzać konfiguracją aplikacji. Posiada wiele predefiniowanych ustawień domyślnych, które musimy dostosować do danego środowiska.
Aby zarządzać tymi ustawieniami aplikacji za pomocą zmiennych środowiskowych i pydantic, dodaj ten kod na początku pliku 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
Ten kod wykonuje następujące czynności:
- Definiuje klasę
SettingsFromEnvironment
, dziedziczącą po klasieBaseSettings
. - Definiuje
DATABASE_URL
iDEBUG
, ustawiając ich typ i opcjonalną wartość domyślną za pomocą wskazówek typu Python. - Definiuje klasę
Config
mówiącą pydantic, aby szukała zmiennych w pliku.env
, jeśli nie ma ich w zmiennych środowiskowych systemu. - Tworzy wystąpienie klasy
Config
w obiekcieconfig
; żądane zmienne stają się dostępne jakoconfig.DATABASE_URL
iconfig.DEBUG
. - Definiuje zwykłe zmienne Django
DATABASES
iDEBUG
z tych elementówconfig
.
Ten sam kod działa we wszystkich środowiskach, a pydantic dba o:
- Szuka zmiennych środowiskowych
DATABASE_URL
iDEBUG
.- Jeśli zostaną zdefiniowane jako zmienne środowiskowe, tak jak w produkcji, użyje ich.
- W przeciwnym razie pobiera te wartości z pliku
.env
. - Jeśli nie znajdzie wartości, wykona następujące czynności:
- W przypadku
DATABASE_URL
zgłasza błąd. - W przypadku
DEBUG
przypisuje domyślną wartośćFalse
.
- W przypadku
- Jeśli znajdzie zmienną środowiskową, sprawdzi typy pól i wyświetli błąd, jeśli któryś z nich jest nieprawidłowy:
- W przypadku
DATABASE_URL
sprawdza, czy typ pola to adres URL w styluPostgresDsn
. - W przypadku
DEBUG
sprawdza, czy typ pola jest prawidłową, nieścisłą wartością logiczną pydantic.
- W przypadku
Zwróć uwagę na jawne ustawienie zmiennej środowiskowej systemu operacyjnego z wartości konfiguracyjnej dla DATABASE_URL
. Ustawienie os.environ["DATABASE_URL"] = config.DATABASE_URL
może wydawać się zbędne, ponieważ DATABASE_URL
jest już zdefiniowana jako zewnętrzna zmienna środowiskowa. Pozwala to jednak pydantic analizować, sprawdzać i walidować tę zmienną. Jeśli brakuje zmiennej środowiskowej DATABASE_URL
lub jest ona niepoprawnie sformatowana, pydantic wyświetli jasny komunikat o błędzie. Te kontrole błędów są nieocenione, gdy aplikacja przechodzi z rozwoju do kolejnych środowisk.
Jeśli zmienna nie jest zdefiniowana, zostanie przypisana wartość domyślna lub pojawi się komunikat o błędzie, aby ją zdefiniować. Wszelkie wygenerowane monity zawierają również szczegółowe informacje o żądanym typie zmiennej. Dodatkową korzyścią tych kontroli jest to, że nowi członkowie zespołu i inżynierowie DevOps łatwiej odkrywają, które zmienne należy zdefiniować. Pozwala to uniknąć trudnych do znalezienia problemów, które powstają, gdy aplikacja działa bez zdefiniowanych wszystkich zmiennych.
Otóż to. Aplikacja ma teraz możliwą do utrzymania implementację do zarządzania ustawieniami przy użyciu jednej wersji settings.py
. Piękno tego podejścia polega na tym, że pozwala nam ono określić prawidłowe zmienne środowiskowe w pliku .env
lub w inny pożądany sposób dostępny za pośrednictwem naszego środowiska hostingowego.
Skalowalna ścieżka
Używałem zarządzania ustawieniami Django z pydantycznym pisaniem runtime we wszystkich moich projektach Django. Odkryłem, że prowadzi to do mniejszych, łatwiejszych w utrzymaniu baz kodu. Zapewnia również dobrze ustrukturyzowane, samodokumentujące się i skalowalne podejście do dodawania nowych środowisk.
Następny artykuł z tej serii to samouczek krok po kroku na temat budowania aplikacji Django od podstaw, z pydantycznym zarządzaniem ustawieniami i wdrażania jej w Heroku.
Blog Toptal Engineering wyraża wdzięczność Stephenowi Davidsonowi za przejrzenie przykładów kodu przedstawionych w tym artykule.
Wcześniejsza wersja tego artykułu kładła nacisk na konkretne wydanie Pythona. Ponieważ Python od kilku lat obsługuje wskazówki dotyczące typów, uogólniliśmy tekst wprowadzający, usuwając to odniesienie do wersji.