angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

El misterioso Flask Application Context, mis preguntas y respuestas

El contexto de la aplicación se inicializa, se empuja y se hace estallar, por lo que para el usuario por primera vez las cosas pueden resultar confusas. Es hora de empezar a leer.

30 enero 2020
En Flask
post main image
https://unsplash.com/@donovan_valdivia

Cuando empiezas con Flask lees un poco sobre el Application Context, TL;DR. No sé nada de ti, pero ciertamente no lo he entendido del todo. Qué es app, qué es current_app, cuál es la diferencia, simplemente empiezas a programar tu aplicación. Todo el tiempo en el fondo hay este extraño zumbido: ¿qué es exactamente el Application Context ... Entonces, en cierto momento, al usar una clase que instanció en su fábrica create_app(), se produjo este error:

RuntimeError: working outside of application context

¡WTF! Entonces empiezas a leer, pero no demasiado, por supuesto, TL;DR de nuevo. Ok, la solución es poner un "con" antes de eso y estás bien:

with app_context(app):
    ...

Se vuelve molesto

Pero lentamente se vuelve más y más molesto. Por ejemplo, instancio un objeto durante la creación de una aplicación en create_app(). Este objeto tiene métodos con declaraciones de registro como:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

Pero si llamo a este método desde un plano, entonces obtengo un mensaje de error. Ahora debo usar la declaración del logger con current_app:

     current_app.logger.debug(fname  +  ': message, var = {}'.format(var))

Es confuso

Pero esto también puede deberse a que no soy un programador muy experimentado en Python . En create_app() adjunto el logger al objeto de la aplicación:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

pero en el mismo create_app() también adjuntamos eventos como @before_request. En @before_request podemos usar ambos:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

y

     current_app.logger.debug(fname  +  ': message, var = {}'.format(var))

¿Por qué? Puedo entender que ambos funcionen pero ¿ambos producirán el mismo resultado? En @before_request el current_app (probablemente) es el Application Context empujado. ¿Pero es esto realmente cierto? También estaba buscando una manera de almacenar datos en el Application Context. La documentación dice:

"Almacenamiento de datos": El Application Context es un buen lugar para almacenar datos comunes durante una solicitud o comando CLI. Flask proporciona el objeto g para este propósito. Es un simple objeto de espacio de nombres que tiene la misma vida útil que un Application Context.

Fíjese que dice: DURANTE una petición. También dice que hay que usar el objeto g. De todos modos, es hora de empezar a leer y hacer algunas pruebas. Traté de responder a varias preguntas que tenía y pensé en escribirlas para no olvidarlas nunca. Espero que las respuestas sean correctas... :-( Por cierto, en mi aplicación estoy usando el patrón de fábrica:

def  create_app(config_name):

    ...

    @app.before_request
    def  before_request():
        ...

    return app

Pregunta: ¿se destruye la solicitud Application Context empujada después de una solicitud?

Respuesta corta: Sí.

Cuanto más lees sobre ello, más confuso se vuelve. Por ejemplo, de la documentación Flask :

El Application Context se crea y se destruye según sea necesario. Cuando una aplicación Flask comienza a manejar una solicitud, empuja un Application Context y un contexto de solicitud. Cuando la solicitud termina, se abre el contexto de la solicitud y luego el Application Context. Típicamente, un Application Context tendrá la misma vida útil que una solicitud.'

Veamos esta frase: "El Application Context se crea y se destruye según sea necesario". ¿Qué significa exactamente este "necesario"?

La documentación establece que el Application Context se destruye después de una solicitud, pero esto no es cierto, ¿o sí? Para comprobar esto, añadí una variable al Application Context empujado en alguna función de la vista, por ejemplo:

def home():
    fname = 'home'
    ...
    # test to check if config can be accessed and is maintained between requests
    if 'SOME_PARAM_KEY' not in  current_app.config:
         current_app.logger.debug(fname  +  ': SOME_PARAM_KEY not in  current_app.config')
        # add it now
         current_app.config['SOME_PARAM_KEY'] = 'some_param_value'
    else:		
         current_app.logger.debug(fname  +  ': SOME_PARAM_KEY in  current_app.config,  current_app.config[SOME_PARAM] = {}'.format(current_app.config['SOME_PARAM_KEY']))

En la primera petición SOME_PARAM_KEY no está en la configuración, imprimimos esto y luego lo configuramos. Las siguientes peticiones muestran que SOME_PARAM_KEY está en la configuración y la configuración mantiene el valor correcto para SOME_PARAM_KEY. Esto significa que el Application Context no se destruye, sino que parece que sólo se cambia una referencia al Application Context .

Esto es lo que está mal: lo anterior se hizo con Flask en modo DEBUG. Si haces exactamente la misma prueba en el modo de PRODUCCIÓN, NUNCA encontrarás SOME_PARAM_KEY en current_app.config en la siguiente solicitud. Por lo tanto, se le advierte en qué estado está usando Flask. En modo DEBUG está encendido parece que Flask guarda el Application Context, obviamente para depuración.

Pregunta: ¿puede adjuntar un objeto de la base de datos en el momento de la creación de la aplicación y acceder a él más tarde en sus planos?

Respuesta corta: no.

Con acceso posterior quiero decir: en las solicitudes posteriores. Debes ser muy cuidadoso con lo que adjuntas al Application Context en el momento de la creación de la aplicación. Si se adjunta un objeto de base de datos SQLAlchemy en el momento de la creación de la aplicación, por ejemplo.

    app.languages = db.session.query(Language).all()

Entonces, durante la primera solicitud, podrá acceder a los idiomas en un plano como:

    for language in  current_app.languages:
        ...

Pero entonces recuerde que la sesión se destruye (se quita) en el momento del desmontaje:

    @app.teardown_appcontext
    def teardown_db(resp_or_exc):
         current_app.db.session.remove()

En la siguiente solicitud, si intentas acceder a un objeto de este tipo, recibirás un mensaje de error:

(DetachedInstanceError('Instance <Setting at 0x7f4466739d90> is not bound to a Session; attribute refresh operation cannot proceed'),)

Era de esperar, pero lo golpeé.

Pregunta: ¿significa que el Application Context todavía está disponible?

Respuesta corta: no.

Del mismo texto de arriba: "Cuando la solicitud termina, aparece el contexto de la solicitud y luego el Application Context". Así que aquí estamos hablando de papá. Esto puede significar dos cosas:

  1. El Application Context está en el cielo azul...
  2. Cuando hablan del Application Context, en realidad se refieren a un puntero al Application Context

¿Por qué es tan importante? Porque en el primer caso significa que el Application Context es de sólo lectura, y en el segundo caso el Application Context entre las peticiones no se destruye. Véase también la respuesta en una pregunta anterior.

Pregunta: ¿debería mantener un objeto global fuera del objeto de la aplicación?

Respuesta corta: depende.

Objeto de la bitácora

Primero veamos el registro. Aquí adjuntamos el registrador al objeto de la aplicación.

def  create_app(config_name):

    ...
    # set app log handler level
    app_log_handler.setLevel(app_log_level)
    # set app logger
    app.logger.setLevel(app_log_level)
    app.logger.addHandler(app_log_handler)
    ...


    return app

Y luego en los planos podemos usar el registrador de la siguiente manera:

from flask import  current_app

def user_list(page_number):
    fname = 'user_list'
     current_app.logger.debug(fname  +  '()')
    ...

El registrador se inicializa durante la creación de la aplicación y se adjunta al objeto de la aplicación y funciona bien cuando se utiliza en las funciones de la vista del plano.

Objeto de la base de datos

Para Flask-SQLAlchemy y otras extensiones se sugiere otro método. Aquí mantenemos el objeto SQLAlchemy fuera del objeto app. Durante la creación de la aplicación llamamos a init_app(app) para configurar y preparar el objeto SQLAlchemy .

from flask_sqlalchemy import  SQLAlchemy
db =  SQLAlchemy()


def  create_app(config_name):

    app =  Flask()
    ...
    # set up
    db.init_app(app)
    ...

    return app

Entonces podemos usar esto en una función de vista blueprint , algo así como:

from my_app import db

def list_users:
    users = db.session.query(User).all()

Ciertamente hay algo de magia en el objeto SQLAlchemy . Pero aquí, a diferencia del registrador, el objeto de la base de datos está fuera del objeto de la aplicación.

Pregunta: ¿el Application Context es de sólo lectura?

Respuesta corta: depende.

Durante una solicitud que puedas adjuntar a ella, léela. Pero la próxima vez que todo esto desaparezca.

Pregunta: ¿el Application Context es único para cada hilo?

Respuesta corta: sí.

De la documentación:

"Cuando una aplicación Flask comienza a manejar una solicitud, empuja un contexto de solicitud, que también empuja un The Application Context.

¿Significa esto que meterse con el Application Context, usando current_app, puede afectar a otros hilos? Esto todavía no está claro para mí.

¿Qué hacer?

Hice una lista de reglas para mí sobre el Application Context:

  1. Nunca almacene datos / objetos en el Application Context durante el tiempo de creación de la aplicación
  2. Nunca adjunte datos / objetos al Application Context durante una solicitud
  3. Mantener los objetos globales fuera del Application Context
  4. Utilice g en lugar de current_app para adjuntar datos/objetos que se utilizarán durante una solicitud
  5. Sólo usa la aplicación en create_app(), pero en los manejadores de eventos adjuntos en create_app() usa siempre current_app
  6. En todos los demás lugares use current_app

Esto significa, por ejemplo, que si tiene datos que son globales para todas las peticiones y no están en la configuración Flask , debe tener estos datos, por ejemplo, en algún lugar del sistema de archivos o en una caché externa, y luego cada vez, al inicio de una petición, debe cargar estos datos y adjuntarlos al objeto g. El primer momento en el que puedes hacer esto es en el manejador @before_request . Así que @before_request es el lugar para adjuntar sus datos y otras cosas como una sesión de base de datos, que debe estar disponible durante todas las solicitudes. Esto significa que no tienes acceso a la base de datos en el manejador de eventos @app.url_value_preprocessor pero esto no es gran cosa.

Resumen

Entre otras cosas, buscaba una manera de almacenar datos, por ejemplo, la configuración de la aplicación, en el Application Context y luego en algún lugar modificar esto, haciendo que los cambios estén disponibles para todos los hilos. Pensé que esto podría hacerse pero no podemos acceder al Application Context inicial una vez que la aplicación se está ejecutando. Fui engañado por el modo DEBUG. Este texto ha sido escrito por una persona, yo, escribiendo su primera aplicación Flask por lo que algunas afirmaciones pueden no ser correctas. Un día espero entender mejor el Application Context . Hasta ese día usaré el Application Context de una manera muy conservadora.

Enlaces / créditos

Demystifying Flask’s Application Context
https://hackingandslacking.com/demystifying-flasks-application-context-c7bd31a53817

Documention for "The Application Context" is confusing #1151
https://github.com/pallets/flask/issues/1151

Making an object persist between requests and available to the whole app
https://www.reddit.com/r/flask/comments/9kenzp/making_an_object_persist_between_requests_and/

number of threads in flask
https://stackoverflow.com/questions/59320537/number-of-threads-in-flask

The Application Context
https://flask.palletsprojects.com/en/1.1.x/appcontext/

Understand Flask (---)
http://songroger.win/understand-flask-s1/

What is the purpose of Flask's context stacks?
https://stackoverflow.com/questions/20036520/what-is-the-purpose-of-flasks-context-stacks

Leer más

Flask

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.