Decisiones de diseño en Flask

Si tienes curiosidad por saber por qué Flask hace ciertas cosas de la manera que lo hace y no de otra manera, esta sección es para ti. Esto debería darte una idea sobre algunas de las decisiones de diseño que pueden parecer arbitrarias y sorprendentes al principio, especialmente en comparación directa con otros frameworks.

El objeto de aplicación explícito

Una aplicación web de Python basada en WSGI tiene que tener un objeto central llamable que implemente la aplicación real. En Flask esto es una instancia de la clase Flask. Cada aplicación Flask tiene que crear una instancia de esta clase por sí misma y pasarle el nombre del módulo, pero ¿por qué no puede hacerlo Flask por sí mismo?

Sin ese objeto de aplicación explícito el siguiente código:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'

Se vería así en su lugar:

from hypothetical_flask import route

@route('/')
def index():
    return 'Hello World!'

Hay tres razones principales para ello. La más importante es que los objetos de aplicación implícitos requieren que sólo pueda haber una instancia a la vez. Hay formas de fingir múltiples aplicaciones con un solo objeto de aplicación, como mantener una pila de aplicaciones, pero esto causa algunos problemas que no voy a describir aquí en detalle. Ahora la pregunta es: ¿cuándo necesita un microframework más de una aplicación al mismo tiempo? Un buen ejemplo es el de las pruebas unitarias.

Otra cosa que se hace posible cuando tienes un objeto explícito en tu código es que puedes subclasificar la clase base (Flask) para alterar un comportamiento específico. Esto no sería posible sin hacks si el objeto se creara de antemano para usted basado en una clase que no está expuesta a usted.

Pero hay otra razón muy importante por la que Flask depende de una instanciación explícita de esa clase: el nombre del paquete. Cada vez que creas una instancia de Flask sueles pasarle __name__ como nombre del paquete. Flask depende de esa información para cargar correctamente los recursos relativos a su módulo. Con el excelente soporte de Python para la reflexión, puede acceder al paquete para averiguar dónde se almacenan las plantillas y los archivos estáticos (ver open_resource()). Ahora, obviamente, hay frameworks que no necesitan ninguna configuración y todavía será capaz de cargar las plantillas en relación con su módulo de aplicación.

La tercera razón es que «lo explícito es mejor que lo implícito». Ese objeto es tu aplicación WSGI, no tienes que recordar nada más. Si quieres aplicar un middleware WSGI, simplemente envuélvelo y ya está (aunque hay mejores formas de hacerlo para no perder la referencia al objeto de la aplicación wsgi_app()).

Además, este diseño permite utilizar una función de fábrica para crear la aplicación, lo que es muy útil para las pruebas unitarias y cosas similares (Fábricas de aplicaciones).

El sistema de enrutamiento

Flask utiliza el sistema de enrutamiento Werkzeug que fue diseñado para ordenar automáticamente las rutas por complejidad. Esto significa que puedes declarar rutas en un orden arbitrario y seguirán funcionando como se espera. Esto es un requisito si quieres implementar adecuadamente el enrutamiento basado en decoradores, ya que los decoradores podrían dispararse en un orden indefinido cuando la aplicación se divide en múltiples módulos.

Otra decisión de diseño con el sistema de enrutamiento de Werkzeug es que las rutas en Werkzeug intentan asegurar que las URLs son únicas. Werkzeug irá bastante lejos con eso en el sentido de que redirigirá automáticamente a una URL canónica si una ruta es ambigua.

Un motor de plantillas

Flask se decide por un motor de plantillas: Jinja2. ¿Por qué Flask no tiene una interfaz de motor de plantillas enchufable? Obviamente puedes usar un motor de plantillas diferente, pero Flask seguirá configurando Jinja2 por ti. Mientras que la limitación de que Jinja2 esté siempre configurado probablemente desaparecerá, la decisión de agrupar un motor de plantillas y utilizarlo no lo hará.

Los motores de plantillas son como los lenguajes de programación y cada uno de esos motores tiene un cierto entendimiento sobre cómo funcionan las cosas. En apariencia, todos funcionan igual: Le dices al motor que evalúe una plantilla con un conjunto de variables y que tome el valor de retorno como una cadena.

Pero ahí acaban las similitudes. Jinja2, por ejemplo, tiene un extenso sistema de filtros, una cierta forma de hacer la herencia de plantillas, soporte para bloques reutilizables (macros) que pueden ser usados desde dentro de las plantillas y también desde el código Python, soporta la renderización iterativa de plantillas, sintaxis configurable y más. Por otro lado un motor como Genshi se basa en la evaluación de flujos XML, herencia de plantillas teniendo en cuenta la disponibilidad de XPath y más. Por otro lado, Mako trata las plantillas de forma similar a los módulos de Python.

Cuando se trata de conectar un motor de plantillas con una aplicación o un marco de trabajo, hay algo más que renderizar plantillas. Por ejemplo, Flask utiliza el amplio soporte de autoescapado de Jinja2. También proporciona formas de acceder a las macros desde las plantillas de Jinja2.

Una capa de abstracción de plantillas que no elimine las características únicas de los motores de plantillas es una ciencia en sí misma y una empresa demasiado grande para un microframework como Flask.

Además, las extensiones pueden depender fácilmente de la presencia de un lenguaje de plantillas. Puedes utilizar fácilmente tu propio lenguaje de plantillas, pero una extensión podría seguir dependiendo del propio Jinja.

Micro con dependencias

¿Por qué Flask se autodenomina microframework y sin embargo depende de dos librerías (concretamente Werkzeug y Jinja2). ¿Por qué no debería hacerlo? Si miramos hacia el lado de Ruby del desarrollo web tenemos un protocolo muy similar a WSGI. Sólo que allí se llama Rack, pero aparte de eso se parece mucho a una versión de WSGI para Ruby. Pero casi todas las aplicaciones en la tierra de Ruby no trabajan con Rack directamente, sino sobre una biblioteca con el mismo nombre. Esta biblioteca Rack tiene dos equivalentes en Python: WebOb (antes Paste) y Werkzeug.

Flask es un framework que aprovecha el trabajo ya realizado por Werkzeug para interconectar adecuadamente WSGI (lo que puede ser una tarea compleja en ocasiones). Gracias a los recientes desarrollos en la infraestructura de paquetes de Python, los paquetes con dependencias ya no son un problema y hay muy pocas razones para no tener bibliotecas que dependan de otras.

Hilos Locales

Flask utiliza objetos locales de hilo (objetos locales de contexto, de hecho, soportan contextos de Greenlet también) para la solicitud, la sesión y un objeto extra en el que puedes poner tus propias cosas (g). ¿Por qué es eso y no es una mala idea?

Sí, normalmente no es una idea tan brillante usar hilos locales. Causan problemas en los servidores que no se basan en el concepto de hilos y hacen que las aplicaciones grandes sean más difíciles de mantener. Sin embargo, Flask no está diseñado para aplicaciones grandes o servidores asíncronos. Flask quiere que sea rápido y fácil escribir una aplicación web tradicional.

Soporte de Async/await y ASGI

Flask soporta las coroutinas async para las funciones de la vista ejecutando la coroutina en un hilo separado en lugar de usar un bucle de eventos en el hilo principal como haría un framework async-first (ASGI). Esto es necesario para que Flask siga siendo compatible con las extensiones y el código construido antes de que async fuera introducido en Python. Este compromiso introduce un coste de rendimiento en comparación con los frameworks ASGI, debido a la sobrecarga de los hilos.

Debido a lo ligado que está el código de Flask a WSGI, no está claro si es posible hacer que la clase Flask soporte ASGI y WSGI al mismo tiempo. Actualmente se está trabajando en Werkzeug para trabajar con ASGI, lo que eventualmente podría permitir el soporte en Flask también.

Ver Uso de async y await para más información.

Qué es Flask, qué no es Flask

Flask nunca tendrá una capa de base de datos. No tendrá una biblioteca de formularios ni nada en ese sentido. Flask en sí mismo sólo se une a Werkzeug para implementar una aplicación WSGI adecuada y a Jinja2 para manejar las plantillas. También se vincula a algunos paquetes de bibliotecas estándar comunes, como el registro. Todo lo demás es para las extensiones.

¿Por qué es así? Porque la gente tiene diferentes preferencias y requisitos y Flask no podría satisfacerlos si forzara algo de esto en el núcleo. La mayoría de las aplicaciones web necesitarán un motor de plantillas de algún tipo. Sin embargo, no todas las aplicaciones necesitan una base de datos SQL.

La idea de Flask es construir una buena base para todas las aplicaciones. Todo lo demás depende de ti o de las extensiones.