Entorno de desarrollo optimizado: tutorial de Pydantic, parte 2

[ad_1]

Los desarrolladores pueden ser sus propios peores enemigos. He visto innumerables ejemplos de ingenieros que desarrollan en un sistema que no se ajusta a su entorno de producción. Esta disonancia genera trabajo adicional y errores del sistema que solo se detectan más adelante en el proceso de desarrollo. La alineación de estas configuraciones facilitará, en última instancia, las implementaciones continuas. Con eso en mente, creemos una aplicación de muestra sobre nuestro entorno de desarrollo Django, simplificado por Docker, Pydantic y Conda.

Un entorno de desarrollo típico utiliza:

  • Un repositorio local;
  • Una base de datos PostgreSQL basada en Docker; y
  • Un entorno conda (para administrar las dependencias de Python).

Pydantic y Django son adecuados tanto para proyectos simples como complejos. Los siguientes pasos muestran una solución simple que muestra cómo se reflejan nuestros entornos.

Índice
  1. Configuración del repositorio Git
  2. Configuración de Django PostgreSQL con Docker
  3. creación de base de datos
  4. Gestión del entorno de Python a través de Miniconda
  5. Marco Django
  6. construcción del modelo
  7. Crear y configurar nuestras vistas
  8. Nuestra configuración de contenido estático
  9. Funcionamiento de nuestro servidor de desarrollo
  10. Listo para usar

Configuración del repositorio Git

Antes de comenzar a escribir código o instalar sistemas de desarrollo, creemos un repositorio Git local:

mkdir hello-visitor
cd hello-visitor

git init

Empezaremos con un Python simple .gitignore Archivo en la raíz del repositorio. En este tutorial, agregaremos este archivo antes de agregar cualquier archivo que no queremos que Git rastree.

Configuración de Django PostgreSQL con Docker

Django requiere una base de datos relacional y usa SQLite por defecto. Por lo general, evitamos SQLite para almacenar datos críticos para el negocio, ya que no maneja bien el acceso de usuarios simultáneos. La mayoría de los desarrolladores optan por una base de datos de producción más típica como PostgreSQL. Independientemente, debemos usar la misma base de datos para el desarrollo y la producción. Este resumen arquitectónico es parte de la aplicación Twelve Factors.

Afortunadamente, ejecutar una instancia local de PostgreSQL con Docker y Docker Compose es muy sencillo.

Para evitar contaminar nuestro directorio raíz, colocamos los archivos relacionados con Docker en subdirectorios separados. Comenzaremos creando un archivo Docker Compose para implementar PostgreSQL:

# docker-services/docker-compose.yml
version: "3.9"

services:
  db:
    image: "postgres:13.4"
    env_file: .env
    volumes:
      - hello-visitor-postgres:/var/lib/postgresql/data
    ports:
      - ${POSTGRES_PORT}:5432

volumes:
  hello-visitor-postgres:

A continuación creamos uno docker-compose Archivo de entorno para configurar nuestro contenedor PostgreSQL:

# docker-services/.env

POSTGRES_USER=postgres
POSTGRES_PASSWORD=MyDBPassword123

# The 'maintenance' database
POSTGRES_DB=postgres

# The port exposed to localhost
POSTGRES_PORT=5432

El servidor de la base de datos ya está definido y configurado. Comencemos nuestro contenedor en segundo plano:

sudo docker compose --project-directory docker-services/ up -d

Es importante notar el uso de sudo en el comando anterior. Es obligatorio a menos que se sigan pasos específicos en nuestro entorno de desarrollo.

creación de base de datos

Conectémonos a PostgreSQL y configurémoslo usando un conjunto de herramientas estándar, pgAdmin4. Usaremos las mismas credenciales previamente configuradas en las variables de entorno.

Ahora vamos a crear una nueva base de datos llamada hello_visitor:

Una pantalla pgAdmin4 en un navegador que muestra la pestaña General en un cuadro de diálogo Crear base de datos.  El campo de texto de la base de datos contiene el valor hello_visitor, el campo de propietario muestra el usuario de postgres y el campo de comentarios está vacío.

Ahora que nuestra base de datos está configurada, podemos instalar nuestro entorno de programación.

Gestión del entorno de Python a través de Miniconda

Ahora necesitamos configurar un entorno de Python aislado y las dependencias necesarias. Para facilitar la configuración y el mantenimiento, elegimos Miniconda.

Vamos a crear y activar nuestro entorno conda:

conda create --name hello-visitor python=3.9
conda activate hello-visitor

Ahora vamos a crear un archivo, hello-visitor/requirements.txtque enumera nuestras dependencias de Python:

django
# PostgreSQL database adapter:
psycopg2
# Pushes .env key-value pairs into environment variables:
python-dotenv
pydantic
# Utility library to read database connection information:
dj-database-url
# Static file caching:
whitenoise
# Python WSGI HTTP Server:
gunicorn

A continuación, le pedimos a Python que instale estas dependencias:

cd hello-visitor

pip install -r requirements.txt

Nuestras dependencias ahora deberían estar instaladas en preparación para el trabajo de desarrollo de la aplicación.

Marco Django

Vamos a preparar nuestro proyecto y aplicación ejecutándolos por primera vez django-adminluego ejecutar un archivo que genera, manage.py:

# From the `hello-visitor` directory
mkdir src
cd src

# Generate starter code for our Django project.
django-admin startproject hello_visitor .

# Generate starter code for our Django app.
python manage.py startapp homepage

A continuación, debemos configurar Django para cargar nuestro proyecto. Que settings.py Archivo requiere adaptación a la INSTALLED_APPS matriz para registrar nuestra recién creada homepage Solicitud:

# src/hello_visitor/settings.py

# ...

INSTALLED_APPS = [
    "homepage.apps.HomepageConfig",
    "django.contrib.admin",
    # ...
]

# ...

Configuración de los ajustes de la aplicación

Usando el enfoque de configuración de pydantic y django que se muestra en la primera parte, necesitamos crear un archivo de variables de entorno para nuestro sistema de desarrollo. Movemos nuestra configuración actual a este archivo de la siguiente manera:

  1. Crear el archivo src/.env para guardar la configuración de nuestro entorno de desarrollo.
  2. Copie la configuración de src/hello_visitor/settings.py y agregarlos src/.env.
  3. Eliminar estas líneas copiadas de la settings.py Expediente.
  4. Asegúrese de que la cadena de conexión de la base de datos use las mismas credenciales que configuramos anteriormente.

nuestro archivo de entorno, src/.envdebería verse así:

DATABASE_URL=postgres://postgres:MyDBPassword123@localhost:5432/hello_visitor
DATABASE_SSL=False

SECRET_KEY="django-insecure-sackl&7(1hc3+%#*4e=)^q3qiw!hnnui*-^($o8t@2^^qqs=%i"
DEBUG=True
DEBUG_TEMPLATES=True
USE_SSL=False
ALLOWED_HOSTS='[
    "localhost",
    "127.0.0.1",
    "0.0.0.0"
]'

Configuraremos Django para leer la configuración de nuestras variables de entorno usando pydantic con este fragmento de código:

# src/hello_visitor/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"""

    # PostgreSQL
    DATABASE_URL: PostgresDsn
    DATABASE_SSL: bool = True

    # Django
    SECRET_KEY: str
    DEBUG: bool = False
    DEBUG_TEMPLATES: bool = False
    USE_SSL: bool = False
    ALLOWED_HOSTS: list

    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=config.DATABASE_SSL)
}

SECRET_KEY = config.SECRET_KEY
DEBUG = config.DEBUG
DEBUG_TEMPLATES = config.DEBUG_TEMPLATES
USE_SSL = config.USE_SSL
ALLOWED_HOSTS = config.ALLOWED_HOSTS

# ...

Si encuentra algún problema después de completar los cambios anteriores, compare nuestro Crafted settings.py Archivo con la versión en nuestro repositorio de código fuente.

construcción del modelo

Nuestra aplicación rastrea y muestra el número de visitantes a la página de inicio. Necesitamos un modelo que mantenga esa cuenta y luego usar el mapeador relacional de objetos (ORM) de Django para inicializar una única fila de la base de datos a través de una migración de datos.

Primero creamos el nuestro VisitCounter Modelo:

# hello-visitor/src/homepage/models.py
"""Defines the models"""
from django.db import models


class VisitCounter(models.Model):
    """ORM for VisitCounter"""

    count = models.IntegerField()

    @staticmethod
    def insert_visit_counter():
        """Populates database with one visit counter. Call from a data migration."""
        visit_counter = VisitCounter(count=0)
        visit_counter.save()

    def __str__(self):
        return f"VisitCounter - number of visits: {self.count}"

A continuación, activamos una migración para crear nuestras tablas de base de datos:

# in the `src` folder
python manage.py makemigrations
python manage.py migrate

Para comprobar si el homepage_visitcounter existe una tabla, podemos ver la base de datos en pgAdmin4.

A continuación, debemos poner un valor inicial en nuestro homepage_visitcounter Mesa. Vamos a crear un archivo de migración separado para lograr esto con el andamiaje de Django:

# from the 'src' directory
python manage.py makemigrations --empty homepage

Personalizaremos el archivo de migración creado para usar el VisitCounter.insert_visit_counter Método que definimos al comienzo de esta sección:

# src/homepage/migrations/0002_auto_-------_----.py 
# Note: The dashes are dependent on execution time.
from django.db import migrations
from ..models import VisitCounter

def insert_default_items(apps, _schema_editor):
    """Populates database with one visit counter."""
    # To learn about apps, see:
    # https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations
    VisitCounter.insert_visit_counter()


class Migration(migrations.Migration):
    """Runs a data migration."""

    dependencies = [
        ("homepage", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(insert_default_items),
    ]

Ahora podemos ejecutar esta migración modificada para el homepage Solicitud:

# from the 'src' directory
python manage.py migrate homepage

Verifiquemos que la migración se ejecutó correctamente mirando el contenido de nuestra tabla:

Una pantalla de pgAdmin4 en un navegador que muestra una consulta

eso lo vemos en nosotros mismos homepage_visitcounter -La tabla existe y se ha rellenado con un recuento de visitas inicial de 0. Ahora que nuestra base de datos es cuadrada, concentrémonos en crear nuestra interfaz de usuario.

Crear y configurar nuestras vistas

Necesitamos implementar dos partes principales de nuestra interfaz de usuario: una vista y una plantilla.

Creamos el homepage view para incrementar el número de visitantes, guárdelo en la base de datos y pase este número a la plantilla para su visualización:

# src/homepage/views.py
from django.shortcuts import get_object_or_404, render
from .models import VisitCounter

def index(request):
    """View for the main page of the app."""
    visit_counter = get_object_or_404(VisitCounter, pk=1)

    visit_counter.count += 1
    visit_counter.save()

    context = {"visit_counter": visit_counter}
    return render(request, "homepage/index.html", context)

Nuestra aplicación Django necesita escuchar las solicitudes realizadas. homepage. Para configurar esta configuración, agreguemos este archivo:

# src/homepage/urls.py
"""Defines urls"""
from django.urls import path

from . import views

# The namespace of the apps' URLconf
app_name = "homepage"  # pylint: disable=invalid-name

urlpatterns = [
    path("", views.index, name="index"),
]

Para nuestro homepage Para entregar la solicitud, tenemos que registrarla en otro urls.py Expediente:

# src/hello_visitor/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("", include("homepage.urls")),
    path("admin/", admin.site.urls),
]

La plantilla HTML base de nuestro proyecto se guardará en un nuevo archivo. src/templates/layouts/base.html:

<!DOCTYPE html>
{% load static %}

<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">

    <title>Hello, visitor!</title>
    <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
  </head>
  <body>
  
    {% block main %}{% endblock %}

    <!-- Option 1: Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>

  </body>
</html>

Ampliaremos la plantilla base para la nuestra. homepage aplicación en un nuevo archivo, src/templates/homepage/index.html:

{% extends "layouts/base.html" %}

{% block main %}
  <main>
    <div class="container py-4">
      <div class="p-5 mb-4 bg-dark text-white text-center rounded-3">
        <div class="container-fluid py-5">
          <h1 class="display-5 fw-bold">Hello, visitor {{ visit_counter.count }}!</h1>
        </div>
      </div>
    </div>
  </main>
{% endblock %}

El último paso para crear nuestra interfaz de usuario es decirle a Django dónde encontrar estas plantillas. agreguemos uno TEMPLATES['DIRS'] Entrada de diccionario a nuestro settings.py Expediente:

# src/hello_visitor/settings.py
TEMPLATES = [
    {
        ...
        'DIRS': [BASE_DIR / 'templates'],
        ...
    },
]

Nuestra interfaz de usuario ya está implementada y estamos casi listos para probar la funcionalidad de nuestra aplicación. Antes de ejecutar nuestras pruebas, debemos configurar la última parte de nuestro entorno: el almacenamiento en caché de contenido estático.

Nuestra configuración de contenido estático

Para evitar atajos arquitectónicos en nuestro sistema de desarrollo, configuramos el almacenamiento en caché de contenido estático para reflejar nuestro entorno de producción.

Mantenemos todos los archivos estáticos de nuestro proyecto en un solo directorio, src/staticy dile a Django que recopile estos archivos antes de servir.

Usamos el logo de Toptal para nuestras aplicaciones favicon y guardarlo como src/static/favicon.ico:

# from `src` folder
mkdir static
cd static
wget https://frontier-assets.toptal.com/83b2f6e0d02cdb3d951a75bd07ee4058.png
mv 83b2f6e0d02cdb3d951a75bd07ee4058.png favicon.ico

A continuación, configuremos Django para recopilar los archivos estáticos:

# src/hello_visitor/settings.py
# Static files (CSS, JavaScript, images)
# a la https://docs.djangoproject.com/en/3.2/howto/static-files/
#
# Source location where we'll store our static files
STATICFILES_DIRS = [BASE_DIR / "static"]
# Build output location where Django collects all static files
STATIC_ROOT = BASE_DIR / "staticfiles"
STATIC_ROOT.mkdir(exist_ok=True)

# URL to use when referring to static files located in STATIC_ROOT.
STATIC_URL = "/static/"

STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

Solo queremos almacenar nuestros archivos estáticos originales en el repositorio del código fuente; No queremos guardar las versiones optimizadas de producción. Agreguemos este último al nuestro. .gitignore con esta sencilla línea:

staticfiles

Dado que nuestro repositorio de código fuente almacena correctamente los archivos necesarios, ahora debemos configurar nuestro sistema de almacenamiento en caché para que funcione con estos archivos estáticos.

Almacenamiento en caché de archivos estáticos

En producción, y por lo tanto también en nuestro entorno de desarrollo, usaremos WhiteNoise para servir los archivos estáticos de nuestra aplicación Django de manera más eficiente.

Registramos WhiteNoise como middleware agregando el siguiente fragmento al nuestro src/hello_visitor/settings.py Expediente. El orden de registro está estrictamente definido, y WhiteNoiseMiddleware debe aparecer inmediatamente después SecurityMiddleware:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    # ...
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

El almacenamiento en caché de archivos estáticos ahora debe configurarse en nuestro entorno de desarrollo para que podamos ejecutar nuestra aplicación.

Funcionamiento de nuestro servidor de desarrollo

Tenemos una aplicación completamente codificada y ahora podemos iniciar nuestro servidor web de desarrollo integrado de Django con este comando:

# in the `src` folder
python manage.py runserver

cuando navegamos http://localhost:8000el conteo aumenta cada vez que actualizamos la página:

Una ventana del navegador que muestra la pantalla principal de nuestra aplicación pydantic Django que indica:

Ahora tenemos una aplicación que funciona y que aumenta el número de visitas cada vez que actualizamos la página.

Listo para usar

Este tutorial ha cubierto todos los pasos necesarios para crear una aplicación que funcione en un buen entorno de desarrollo Django similar a la producción. En la Parte 3, cubriremos la implementación de nuestra aplicación en su entorno de producción. También vale la pena explorar nuestros ejercicios adicionales que resaltan los beneficios de Django y Pydantic: están incluidos en el repositorio de código completo para este tutorial de Pydantic.


El blog de ingeniería de Toptal quisiera agradecer a Stephen Davidson por revisar y realizar pruebas beta de los ejemplos de código presentados en este artículo.

[ad_2]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir