Vistas de carga lenta

Flask se utiliza generalmente con los decoradores. Los decoradores son simples y tienes la URL justo al lado de la función que se llama para esa URL específica. Sin embargo, hay una desventaja en este enfoque: significa que todo su código que utiliza decoradores tiene que ser importado por adelantado o Flask nunca encontrará su función.

Esto puede ser un problema si su aplicación tiene que importar rápidamente. Puede que tenga que hacerlo en sistemas como el App Engine de Google u otros sistemas. Así que si de repente notas que tu aplicación se queda pequeña con este enfoque puedes volver a un mapeo de URLs centralizado.

El sistema que permite tener un mapa de URLs central es la función add_url_rule(). En lugar de usar decoradores, tienes un archivo que configura la aplicación con todas las URLs.

Conversión a mapa de URLs centralizado

Imagina que la aplicación actual tiene un aspecto similar al siguiente:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    pass

@app.route('/user/<username>')
def user(username):
    pass

Entonces, con el enfoque centralizado tendrías un archivo con las vistas (views.py) pero sin ningún decorador:: algo así:

def index():
    pass

def user(username):
    pass

Y luego un archivo que configura una aplicación que mapea las funciones a URLs:

from flask import Flask
from yourapplication import views
app = Flask(__name__)
app.add_url_rule('/', view_func=views.index)
app.add_url_rule('/user/<username>', view_func=views.user)

Cargando con retraso

Hasta ahora sólo hemos dividido las vistas y el enrutamiento, pero el módulo se sigue cargando por adelantado. El truco está en cargar realmente la función de la vista cuando se necesita. Esto se puede lograr con una clase helper que se comporta como una función pero que importa internamente la función real en el primer uso:

from werkzeug.utils import import_string, cached_property

class LazyView(object):

    def __init__(self, import_name):
        self.__module__, self.__name__ = import_name.rsplit('.', 1)
        self.import_name = import_name

    @cached_property
    def view(self):
        return import_string(self.import_name)

    def __call__(self, *args, **kwargs):
        return self.view(*args, **kwargs)

Lo importante aquí es que __module__ y __name__ estén correctamente configurados. Esto es utilizado por Flask internamente para averiguar cómo nombrar las reglas URL en caso de que no proporciones un nombre para la regla tú mismo.

Entonces puedes definir tu lugar central para combinar las vistas así:

from flask import Flask
from yourapplication.helpers import LazyView
app = Flask(__name__)
app.add_url_rule('/',
                 view_func=LazyView('yourapplication.views.index'))
app.add_url_rule('/user/<username>',
                 view_func=LazyView('yourapplication.views.user'))

Puedes optimizar aún más esto en términos de cantidad de pulsaciones de teclas necesarias para escribir esto teniendo una función que llame a add_url_rule() prefijando una cadena con el nombre del proyecto y un punto, y envolviendo view_func en un LazyView según sea necesario.

def url(import_name, url_rules=[], **options):
    view = LazyView(f"yourapplication.{import_name}")
    for url_rule in url_rules:
        app.add_url_rule(url_rule, view_func=view, **options)

# add a single route to the index view
url('views.index', ['/'])

# add two routes to a single function endpoint
url_rules = ['/user/','/user/<username>']
url('views.user', url_rules)

Una cosa a tener en cuenta es que los manejadores de antes y después de la solicitud tienen que estar en un archivo que se importa por adelantado para que funcione correctamente en la primera solicitud. Lo mismo ocurre con cualquier tipo de decorador restante.