Optimice la configuración de Django con sugerencias de tipo: un tutorial de Pydantic
Publicado: 2022-07-22Los proyectos de Django solían frustrarme porque carecía de una forma robusta y escalable de agregar nuevos entornos. Al reunir sugerencias de tipo pydantic y Python, construí la poderosa base que necesitaba.
Como se describe en PEP 484, las sugerencias de tipo admiten el análisis estático, pero estas mismas anotaciones también están disponibles en tiempo de ejecución. Paquetes de terceros como pydantic ofrecen verificación de tipo de tiempo de ejecución que utiliza estos metadatos adicionales. Pydantic usa sugerencias de tipo Python para ayudar a administrar los metadatos de configuración y realizar la validación de datos en tiempo de ejecución.
Este tutorial de pydantic mostrará los efectos positivos y de gran alcance del uso de la gestión de configuración de pydantic con Django.
Nuestra configuración se adhiere a las mejores prácticas descritas en el sitio web de la aplicación Twelve-Factor:
- Defina configuraciones no constantes y secretas como variables de entorno.
- En entornos de desarrollo, defina las variables de entorno en un archivo
.env
y agregue el.env
a.gitignore
. - Utilice los mecanismos del proveedor de la nube para definir variables de entorno (secretas) para los entornos de control de calidad, ensayo y producción.
- Utilice un único archivo
settings.py
que se configura a sí mismo a partir de las variables de entorno. - Use pydantic para leer, verificar, validar y encasillar variables de entorno en variables de Python que definen las configuraciones de Django.
Alternativamente, algunos desarrolladores crean varios archivos de configuración, como settings_dev.py
y settings_prod.py
. Desafortunadamente, este enfoque no escala bien. Conduce a la duplicación de código, confusión, errores difíciles de encontrar y mayores esfuerzos de mantenimiento.
Usando las mejores prácticas antes mencionadas, agregar cualquier número de entornos es fácil, bien definido y a prueba de errores. Aunque podríamos explorar una configuración de entorno más complicada, nos centraremos en dos para mayor claridad: desarrollo y producción.
¿Cómo se ve esto en la práctica?
Gestión de configuración de Pydantic y variables de entorno
Ahora nos centramos en un ejemplo tanto en desarrollo como en producción. Mostramos cómo cada entorno configura sus ajustes de manera diferente y cómo pydantic admite cada uno.
Nuestra aplicación de ejemplo requiere una base de datos compatible con Django, por lo que debemos almacenar la cadena de conexión de la base de datos. Movemos la información de configuración de la conexión de la base de datos a una variable de entorno, DATABASE_URL
, usando el paquete Python dj-database-url. Tenga en cuenta que esta variable es de tipo str
y tiene el siguiente formato:
postgres://{user}:{password}@{hostname}:{port}/{database-name} mysql://{user}:{password}@{hostname}:{port}/{database-name} oracle://{user}:{password}@{hostname}:{port}/{database-name} sqlite:///PATH
En nuestro entorno de desarrollo, podemos usar una instancia de PostgreSQL contenida en Docker para facilitar el uso, mientras que en nuestro entorno de producción apuntaremos a un servicio de base de datos aprovisionado.
Otra variable que queremos definir es un valor booleano, DEBUG
. El indicador DEBUG en Django nunca debe activarse en una implementación de producción. Su objetivo es obtener comentarios adicionales durante el desarrollo. Por ejemplo, en el modo de depuración, Django mostrará páginas de error detalladas cuando ocurra una excepción.
Los diferentes valores para el desarrollo y la producción podrían definirse de la siguiente manera:
Nombre de la variable | Desarrollo | Producción |
---|---|---|
DATABASE_URL | postgres://postgres:mypw@localhost:5432/mydb | postgres://foo1:foo2@foo3:5432/foo4 |
DEBUG | True | False |
Usamos el módulo de administración de configuración de pydantic para administrar estos diferentes conjuntos de valores de variables de entorno según el entorno.
Pasos preparatorios
Para poner esto en práctica, comenzamos a configurar nuestro entorno de desarrollo creando nuestro único archivo .env
con este contenido:
DATABASE_URL=postgres://postgres:mypw@localhost:5432/mydb DEBUG=True
A continuación, agregamos el archivo .env
al archivo .gitignore
del proyecto. El archivo .gitignore
evita guardar información potencialmente confidencial en el control de código fuente.
Mientras que este enfoque funciona bien en nuestro entorno de desarrollo, la especificación de nuestro entorno de producción utiliza un mecanismo diferente. Nuestras prácticas recomendadas dictan que las variables de entorno de producción utilizan secretos de entorno. Por ejemplo, en Heroku, estos secretos se denominan Config Vars y se configuran a través del panel de Heroku. Están disponibles para la aplicación implementada como variables de entorno:
Después de eso, debemos ajustar la configuración de la aplicación para leer estos valores de cualquier entorno automáticamente.

Configurando los settings.py
de Django.py
Comencemos con un nuevo proyecto de Django para proporcionar la estructura esencial para nuestro ejemplo. Construimos un nuevo proyecto de Django con el siguiente comando de terminal:
$ django-admin startproject mysite
Ahora tenemos una estructura de proyecto básica para el proyecto mysite
. La estructura de archivos del proyecto es la siguiente:
mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py
El archivo settings.py
contiene un código repetitivo que nos permite administrar la configuración de la aplicación. Tiene muchas configuraciones predeterminadas predefinidas que debemos ajustar al entorno relevante.
Para administrar esta configuración de la aplicación mediante variables de entorno y pydantic, agregue este código en la parte superior del archivo 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
Este código hace lo siguiente:
- Define una clase
SettingsFromEnvironment
, heredada de la clase BaseSettings deBaseSettings
. - Define
DATABASE_URL
yDEBUG
, configurando su tipo y el valor predeterminado opcional mediante sugerencias de tipo de Python. - Define una
Config
de clase que le dice a pydantic que busque las variables en un archivo.env
si no están presentes en las variables de entorno del sistema. - Instancia la clase
Config
en laconfig
del objeto; las variables deseadas estarán disponibles comoconfig.DATABASE_URL
yconfig.DEBUG
. - Define las variables regulares de Django
DATABASES
yDEBUG
de estos miembros deconfig
.
El mismo código se ejecuta en todos los entornos y pydantic se ocupa de lo siguiente:
- Busca las variables de entorno
DATABASE_URL
yDEBUG
.- Si se definen como variables de entorno, como en producción, las utilizará.
- De lo contrario, extrae esos valores del archivo
.env
. - Si no encuentra un valor, hará lo siguiente:
- Para
DATABASE_URL
, arroja un error. - Para
DEBUG
, asigna un valor predeterminado deFalse
.
- Para
- Si encuentra una variable de entorno, verificará los tipos de campo y dará un error si alguno de ellos es incorrecto:
- Para
DATABASE_URL
, verifica que su tipo de campo sea una URL de estiloPostgresDsn
. - Para
DEBUG
, verifica que su tipo de campo sea un booleano pydantic válido y no estricto.
- Para
Tenga en cuenta la configuración explícita de la variable de entorno del sistema operativo del valor de configuración para DATABASE_URL
. Puede parecer redundante establecer os.environ["DATABASE_URL"] = config.DATABASE_URL
porque DATABASE_URL
ya está definida como una variable de entorno externa. Sin embargo, esto permite que pydantic analice, verifique y valide esta variable. Si falta la variable de entorno DATABASE_URL
o tiene un formato incorrecto, pydantic mostrará un claro mensaje de error. Estas verificaciones de errores son invaluables a medida que la aplicación pasa del desarrollo a entornos posteriores.
Si no se define una variable, se asignará un valor predeterminado o se generará un error para que se defina. Las indicaciones generadas también detallan el tipo de variable deseada. Un beneficio adicional de estas comprobaciones es que los nuevos miembros del equipo y los ingenieros de DevOps descubren más fácilmente qué variables deben definirse. Esto evita los problemas difíciles de encontrar que resultan cuando la aplicación se ejecuta sin todas las variables definidas.
Eso es todo. La aplicación ahora tiene una implementación de administración de configuraciones que se puede mantener usando una única versión de settings.py
. La belleza de este enfoque es que nos permite especificar las variables de entorno correctas en un archivo .env
o cualquier otro medio deseado disponible a través de nuestro entorno de alojamiento.
El camino escalable
He estado usando la administración de configuraciones de Django con tiempo de ejecución pydantic escribiendo en todos mis proyectos de Django. Descubrí que conduce a bases de código más pequeñas y fáciles de mantener. También proporciona un enfoque bien estructurado, autodocumentado y escalable para agregar nuevos entornos.
El siguiente artículo de esta serie es un tutorial paso a paso sobre la creación de una aplicación Django desde cero, con administración de configuraciones dinámicas e implementación en Heroku.
El blog de ingeniería de Toptal agradece a Stephen Davidson por revisar los ejemplos de código presentados en este artículo.
Una versión anterior de este artículo enfatizó una versión específica de Python. Dado que Python ha admitido sugerencias de tipo durante varios años, hemos generalizado el texto introductorio eliminando esta referencia de versión.