Optimice la configuración de Django con sugerencias de tipo: un tutorial de Pydantic

Publicado: 2022-07-22

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

  1. Defina configuraciones no constantes y secretas como variables de entorno.
  2. En entornos de desarrollo, defina las variables de entorno en un archivo .env y agregue el .env a .gitignore .
  3. 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.
  4. Utilice un único archivo settings.py que se configura a sí mismo a partir de las variables de entorno.
  5. 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:

Una captura de pantalla de la interfaz web de Config Vars. La barra lateral izquierda tiene una descripción: "Las variables de configuración cambian la forma en que se comporta su aplicación. Además de crear las suyas propias, algunos complementos vienen con los suyos". La sección principal tiene dos filas, cada una con dos campos de texto, un ícono de lápiz y un ícono X. Los campos de texto tienen los mismos datos que las columnas "Nombre de la variable" y "Producción" de la tabla anterior. La esquina superior derecha tiene un botón que dice "Ocultar vars. de configuración".

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 de BaseSettings .
  • Define DATABASE_URL y DEBUG , 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 la config del objeto; las variables deseadas estarán disponibles como config.DATABASE_URL y config.DEBUG .
  • Define las variables regulares de Django DATABASES y DEBUG de estos miembros de config .

El mismo código se ejecuta en todos los entornos y pydantic se ocupa de lo siguiente:

  • Busca las variables de entorno DATABASE_URL y DEBUG .
    • 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 de False .
  • 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 estilo PostgresDsn .
    • Para DEBUG , verifica que su tipo de campo sea un booleano pydantic válido y no estricto.

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.