He estado usando el contexto de solicitud / aplicación durante algún tiempo sin comprender completamente cómo funciona o por qué fue diseñado de la manera en que fue. ¿Cuál es el propósito de la "pila" cuando se trata del contexto de solicitud o aplicación? ¿Son estas dos pilas separadas, o son parte de una pila? ¿Se inserta el contexto de solicitud en una pila, o es una pila en sí misma? ¿Puedo empujar / hacer estallar múltiples contextos uno encima del otro? Si es así, ¿por qué querría hacer eso?
Perdón por todas las preguntas, pero sigo confundido después de leer la documentación para Contexto de solicitud y Contexto de aplicación.
Respuestas:
Múltiples aplicaciones
El contexto de la aplicación (y su propósito) es realmente confuso hasta que te das cuenta de que Flask puede tener múltiples aplicaciones. Imagine la situación en la que desea que un solo intérprete WSGI Python ejecute múltiples aplicaciones de Flask. No estamos hablando de planos aquí, estamos hablando de aplicaciones de matraces completamente diferentes.
Puede configurar esto de manera similar a la sección de documentación de Flask en el ejemplo de "Despacho de aplicaciones" :
Observe que hay dos aplicaciones Flask completamente diferentes que se crean "frontend" y "backend". En otras palabras, el
Flask(...)
constructor de la aplicación ha sido llamado dos veces, creando dos instancias de una aplicación Flask.Contextos
Cuando trabajas con Flask, a menudo terminas usando variables globales para acceder a varias funciones. Por ejemplo, probablemente tenga un código que lea ...
Luego, durante una vista, puede usar
request
para acceder a la información de la solicitud actual. Obviamente,request
no es una variable global normal; en realidad, es un contexto de valor local . En otras palabras, hay algo de magia detrás de escena que dice "cuando llamerequest.path
, obtenga elpath
atributo delrequest
objeto de la solicitud ACTUAL". Dos solicitudes diferentes tendrán resultados diferentes pararequest.path
.De hecho, incluso si ejecuta Flask con varios subprocesos, Flask es lo suficientemente inteligente como para mantener aislados los objetos de solicitud. Al hacerlo, es posible que dos subprocesos, cada uno que maneja una solicitud diferente, llame simultáneamente
request.path
y obtenga la información correcta para sus respectivas solicitudes.Poniendo todo junto
Así que ya hemos visto que Flask puede manejar múltiples aplicaciones en el mismo intérprete, y también debido a la forma en que Flask le permite usar globales "contextuales" debe haber algún mecanismo para determinar cuál es la solicitud "actual" ( para hacer cosas como
request.path
).Al unir estas ideas, también debería tener sentido que Flask deba tener alguna forma de determinar cuál es la aplicación "actual".
Probablemente también tenga un código similar al siguiente:
Como nuestro
request
ejemplo, laurl_for
función tiene una lógica que depende del entorno actual. En este caso, sin embargo, es claro ver que la lógica depende en gran medida de qué aplicación se considera la aplicación "actual". En el ejemplo frontend / backend que se muestra arriba, las aplicaciones "frontend" y "backend" podrían tener una ruta "/ login", por lo queurl_for('/login')
deberían devolver algo diferente dependiendo de si la vista está manejando la solicitud de la aplicación frontend o backend.Para responder tu pregunta...
De los documentos del contexto de solicitud:
En otras palabras, aunque normalmente tendrá 0 o 1 elementos en esta pila de solicitudes "actuales" o aplicaciones "actuales", es posible que pueda tener más.
El ejemplo dado es donde desea que su solicitud devuelva los resultados de una "redirección interna". Supongamos que un usuario solicita A, pero desea volver al usuario B. En la mayoría de los casos, emite una redirección al usuario y lo señala al recurso B, lo que significa que el usuario ejecutará una segunda solicitud para buscar B. A Una forma ligeramente diferente de manejar esto sería hacer una redirección interna, lo que significa que mientras procesa A, Flask se hará una nueva solicitud para el recurso B y usará los resultados de esta segunda solicitud como los resultados de la solicitud original del usuario.
Son dos pilas separadas . Sin embargo, este es un detalle de implementación. Lo más importante no es tanto que haya una pila, sino el hecho de que en cualquier momento puede obtener la aplicación o solicitud "actual" (parte superior de la pila).
Un "contexto de solicitud" es un elemento de la "pila de contexto de solicitud". De manera similar con el "contexto de la aplicación" y la "pila de contexto de la aplicación".
En una aplicación Flask, normalmente no haría esto. Un ejemplo de dónde puede desear es una redirección interna (descrita anteriormente). Sin embargo, incluso en ese caso, probablemente terminaría haciendo que Flask maneje una nueva solicitud, por lo que Flask haría todo el empuje / estallido por usted.
Sin embargo, hay algunos casos en los que desea manipular la pila usted mismo.
Ejecutar código fuera de una solicitud
Un problema típico que tienen las personas es que usan la extensión Flask-SQLAlchemy para configurar una base de datos SQL y la definición del modelo usando un código similar a lo que se muestra a continuación ...
Luego usan los valores
app
ydb
en un script que debe ejecutarse desde el shell. Por ejemplo, un script "setup_tables.py" ...En este caso, la extensión Flask-SQLAlchemy conoce la
app
aplicación, pero durante lacreate_all()
misma arrojará un error quejándose de que no hay un contexto de aplicación. Este error está justificado; nunca le dijo a Flask con qué aplicación debería estar tratando cuando ejecuta elcreate_all
método.Tal vez se pregunte por qué no necesita esta
with app.app_context()
llamada cuando ejecuta funciones similares en sus vistas. La razón es que Flask ya maneja la administración del contexto de la aplicación cuando maneja solicitudes web reales. El problema realmente solo surge fuera de estas funciones de vista (u otras devoluciones de llamada), como cuando se usan los modelos en un script único.La resolución es impulsar el contexto de la aplicación usted mismo, lo que se puede hacer haciendo ...
Esto empujará un nuevo contexto de aplicación (usando la aplicación de
app
, recuerde que podría haber más de una aplicación).Pruebas
Otro caso en el que desea manipular la pila es para probar. Puede crear una prueba unitaria que maneje una solicitud y verifique los resultados:
fuente
request = Local()
diseño más simple para global.py? Probablemente hay casos de uso en los que no estoy pensando.Las respuestas anteriores ya ofrecen una buena descripción de lo que sucede en el fondo de Flask durante una solicitud. Si aún no lo ha leído, recomiendo la respuesta de @ MarkHildreth antes de leer esto. En resumen, se crea un nuevo contexto (hilo) para cada solicitud http, razón por la cual es necesario tener una función de hilo
Local
que permita objetos comorequest
yg
para ser accesible globalmente a través de subprocesos, manteniendo su contexto específico de solicitud. Además, al procesar una solicitud http, Flask puede emular solicitudes adicionales desde dentro, de ahí la necesidad de almacenar su contexto respectivo en una pila. Además, Flask permite que múltiples aplicaciones wsgi se ejecuten juntas dentro de un solo proceso, y más de una puede ser llamada a la acción durante una solicitud (cada solicitud crea un nuevo contexto de aplicación), de ahí la necesidad de una pila de contexto para las aplicaciones. Ese es un resumen de lo que se cubrió en las respuestas anteriores.Mi objetivo ahora es complementar nuestra comprensión actual explicando cómo Flask y Werkzeug hacen lo que hacen con estos locales de contexto. Simplifiqué el código para mejorar la comprensión de su lógica, pero si obtienes esto, deberías poder comprender fácilmente la mayoría de lo que hay en la fuente real (
werkzeug.local
yflask.globals
).Primero comprendamos cómo Werkzeug implementa hilos locales.
Local
Cuando entra una solicitud http, se procesa dentro del contexto de un solo hilo. Como medio alternativo para generar un nuevo contexto durante una solicitud http, Werkzeug también permite el uso de greenlets (una especie de "microhilos" más ligeros) en lugar de hilos normales. Si no tiene instalados los greenlets, volverá a usar hilos en su lugar. Cada uno de estos hilos (o greenlets) son identificables por una identificación única, que puede recuperar con la
get_ident()
función del módulo . Esa función es el punto de partida a la magia detrás de tenerrequest
,current_app
,url_for
,g
, y otros objetos globales el contexto.Ahora que tenemos nuestra función de identidad, podemos saber en qué subproceso estamos en un momento dado y podemos crear lo que se llama un subproceso
Local
, un objeto contextual al que se puede acceder globalmente, pero cuando accede a sus atributos, resuelven su valor para ese hilo específico. p.ejAmbos valores están presentes en el
Local
objeto accesible globalmente al mismo tiempo, pero accederlocal.first_name
dentro del contexto del hilo 1 le dará'John'
, mientras que volverá'Debbie'
en el hilo 2.¿Cómo es eso posible? Veamos un código (simplificado):
Del código anterior podemos ver que la magia se reduce a lo
get_ident()
que identifica el greenlet o hilo actual. ElLocal
almacenamiento solo lo usa como clave para almacenar cualquier dato contextual al hilo actual.Se pueden tener varios
Local
objetos por proceso yrequest
,g
,current_app
y otros simplemente pudo haber sido creado por el estilo. Pero no es así como se hace en Flask en el que estos no son técnicamenteLocal
objetos, sino más exactamenteLocalProxy
objetos. ¿Qué es unLocalProxy
?LocalProxy
Un LocalProxy es un objeto que consulta a
Local
para encontrar otro objeto de interés (es decir, el objeto al que se aproxima). Echemos un vistazo para entender:Ahora, para crear proxies accesibles globalmente, harías
y ahora un poco antes en el transcurso de una solicitud, almacenaría algunos objetos dentro del local a los que pueden acceder los servidores proxy creados anteriormente, sin importar en qué hilo estemos
La ventaja de usar
LocalProxy
objetos globalmente accesibles en lugar de hacerlos ellosLocals
mismos es que simplifica su administración. Solo necesita un soloLocal
objeto para crear muchos servidores proxy accesibles globalmente. Al final de la solicitud, durante la limpieza, simplemente libera el unoLocal
(es decir, saca el context_id de su almacenamiento) y no se molesta con los proxies, todavía son accesibles globalmente y aún difieren delLocal
que busca su objeto. de interés para solicitudes http posteriores.Para simplificar la creación de un
LocalProxy
cuando ya tenemos unLocal
, Werkzeug implementa elLocal.__call__()
método mágico de la siguiente manera:Sin embargo, si nos fijamos en la fuente Frasco (flask.globals) que todavía no es como
request
,g
,current_app
ysession
son creados. Como hemos establecido, Flask puede generar múltiples solicitudes "falsas" (desde una única solicitud http verdadera) y en el proceso también puede impulsar múltiples contextos de aplicación. Este no es un caso de uso común, pero es una capacidad del marco. Dado que estas solicitudes y aplicaciones "concurrentes" todavía se limitan a ejecutarse con solo una que tiene el "foco" en cualquier momento, tiene sentido usar una pila para su contexto respectivo. Cada vez que se genera una nueva solicitud o se llama a una de las aplicaciones, empujan su contexto en la parte superior de su pila respectiva. Flask usaLocalStack
objetos para este propósito. Cuando concluyen su negocio, sacan el contexto de la pila.LocalStack
Así es
LocalStack
como se ve (de nuevo, el código se simplifica para facilitar la comprensión de su lógica).Tenga en cuenta de lo anterior que a
LocalStack
es una pila almacenada en un local, no un montón de locales almacenados en una pila. Esto implica que aunque la pila es accesible globalmente, es una pila diferente en cada hilo.Frasco no tiene su
request
,current_app
,g
, ysession
objetos resolver directamente a unaLocalStack
, más bien utilizaLocalProxy
objetos que se colocan una función de búsqueda (en lugar de unLocal
objeto) que se encuentra el objeto subyacente de laLocalStack
:Todos estos se declaran al inicio de la aplicación, pero en realidad no resuelven nada hasta que un contexto de solicitud o contexto de aplicación se empuje a su respectiva pila.
Si tiene curiosidad por ver cómo se inserta realmente un contexto en la pila (y luego emerge), observe
flask.app.Flask.wsgi_app()
cuál es el punto de entrada de la aplicación wsgi (es decir, a qué llama el servidor web y pasa el entorno http cuando petición llega), y sigue la creación delRequestContext
objeto a lo largo de su posteriorpush()
dentro_request_ctx_stack
. Una vez presionado en la parte superior de la pila, se puede acceder a través de_request_ctx_stack.top
. Aquí hay un código abreviado para demostrar el flujo:Entonces inicia una aplicación y la pone a disposición del servidor WSGI ...
Más tarde, llega una solicitud http y el servidor WSGI llama a la aplicación con los parámetros habituales ...
Esto es más o menos lo que sucede en la aplicación ...
y esto es más o menos lo que sucede con RequestContext ...
Digamos que una solicitud ha terminado de inicializarse, por lo tanto, la búsqueda
request.path
de una de sus funciones de vista sería la siguiente:LocalProxy
objeto accesible globalmenterequest
._find_request()
(la función que registró como suself.local
).LocalStack
objeto_request_ctx_stack
para el contexto superior en la pila.LocalStack
objeto primero consulta suLocal
atributo interno (self.local
) para lastack
propiedad que estaba previamente almacenada allí.stack
obtiene el contexto superiortop.request
lo tanto se resuelve como el objeto de interés subyacente.path
atributoAsí hemos visto cómo
Local
,LocalProxy
yLocalStack
el trabajo, ahora piensa por un momento de las implicaciones y matices en la recuperación de lapath
de:request
objeto que sería un simple objeto accesible globalmente.request
objeto que sería un local.request
objeto almacenado como un atributo de un local.request
objeto que es un proxy para un objeto almacenado en un local.request
objeto almacenado en una pila, que a su vez se almacena en un local.request
objeto que es un proxy para un objeto en una pila almacenada en un local. <- esto es lo que hace Flask.fuente
Local
,LocalStack
yLocalProxy
le sugiero que revise estos artículos del documento: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev y flask.pocoo .org / docs / 0.11 / reqcontext . Su nueva comprensión puede permitirle verlos con una nueva luz y puede proporcionarle más información.Pequeña adición a la respuesta de @Mark Hildreth .
Se parece a la pila de contexto
{thread.get_ident(): []}
, donde se[]
llama "pila" porque solo se usan las operacionesappend
(push
)pop
y[-1]
(__getitem__(-1)
). Por lo tanto, la pila de contexto mantendrá datos reales para el hilo o el hilo greenlet.current_app
,g
,request
,session
Y etc esLocalProxy
objeto que acaba de overrided métodos especiales__getattr__
,__getitem__
,__call__
,__eq__
y etc. y valor de retorno de la parte superior pila de contexto ([-1]
) por nombre de argumento (current_app
,request
por ejemplo).LocalProxy
necesitaba importar estos objetos una vez y no perderán la actualidad. Así que mejor solo importarequest
donde estés en el código en lugar de jugar con el envío de argumentos de solicitud a tus funciones y métodos. Puede escribir fácilmente sus propias extensiones con él, pero no olvide que el uso frívolo puede dificultar la comprensión del código.Dedique tiempo a comprender https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .
Entonces, ¿cómo poblaron ambas pilas? A petición
Flask
:request_context
por entorno (initmap_adapter
, ruta de coincidencia)request_context
app_context
si se perdió y empujó a la pila de contexto de la aplicaciónfuente
Tomemos un ejemplo, supongamos que desea establecer un contexto de usuario (usando la construcción de matraz de Local y LocalProxy).
Defina una clase de usuario:
definir una función para recuperar el objeto del usuario dentro del hilo actual o greenlet
Ahora defina un LocalProxy
Ahora para obtener el ID de usuario del usuario en el hilo actual usercontext.userid
explicación:
1.Local tiene un dict de identidad y objet, la identidad es threadid o id de greenlet, en este ejemplo _local.user = User () es equivalente a _local .___ storage __ [id del thread actual] ["user"] = User ()
fuente