Usprawnij swoje ustawienia Django dzięki wskazówkom typu: samouczek pydantyczny

Opublikowany: 2022-07-22

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

  1. Zdefiniuj konfiguracje niestałe i tajne jako zmienne środowiskowe.
  2. W środowiskach programistycznych zdefiniuj zmienne środowiskowe w pliku .env i dodaj .env do .gitignore .
  3. Użyj mechanizmów dostawcy chmury, aby zdefiniować (tajne) zmienne środowiskowe dla środowisk QA, pomostowych i produkcyjnych.
  4. Użyj pojedynczego pliku settings.py , który konfiguruje się na podstawie zmiennych środowiskowych.
  5. 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:

Zrzut ekranu interfejsu internetowego Config Vars. Lewy pasek boczny zawiera opis: „Zmienne konfiguracyjne zmieniają sposób działania Twojej aplikacji. Oprócz tworzenia własnych, niektóre dodatki mają własne”. Główna sekcja ma dwa wiersze, każdy z dwoma polami tekstowymi, ikoną ołówka i ikoną X. Pola tekstowe zawierają te same dane, co kolumny „Nazwa zmiennej” i „Produkcja” w poprzedniej tabeli. W prawym górnym rogu znajduje się przycisk „Hide Config Vars”.

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 klasie BaseSettings .
  • Definiuje DATABASE_URL i DEBUG , 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 obiekcie config ; żądane zmienne stają się dostępne jako config.DATABASE_URL i config.DEBUG .
  • Definiuje zwykłe zmienne Django DATABASES i DEBUG z tych elementów config .

Ten sam kod działa we wszystkich środowiskach, a pydantic dba o:

  • Szuka zmiennych środowiskowych DATABASE_URL i DEBUG .
    • 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 .
  • 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 stylu PostgresDsn .
    • W przypadku DEBUG sprawdza, czy typ pola jest prawidłową, nieścisłą wartością logiczną pydantic.

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.