¿Cuál es el propósito de las pilas de contexto de Flask?

158

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.

Ben Davis
fuente
55
kronosapiens.github.io/blog/2014/08/14/… OMI, esta publicación de blog me da la descripción más comprensible del contexto del matraz.
mission.liao

Respuestas:

243

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" :

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

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 ...

from flask import request

Luego, durante una vista, puede usar requestpara acceder a la información de la solicitud actual. Obviamente, requestno 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 llame request.path, obtenga el pathatributo del requestobjeto de la solicitud ACTUAL". Dos solicitudes diferentes tendrán resultados diferentes para request.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.pathy 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:

from flask import url_for

Como nuestro requestejemplo, la url_forfunció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 que url_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...

¿Cuál es el propósito de la "pila" cuando se trata del contexto de solicitud o aplicación?

De los documentos del contexto de solicitud:

Debido a que el contexto de solicitud se mantiene internamente como una pila, puede presionar y hacer estallar varias veces. Esto es muy útil para implementar cosas como redireccionamientos internos.

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 estas dos pilas separadas, o son parte de una pila?

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).

¿Se inserta el contexto de solicitud en una pila, o es una pila en sí misma?

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".

¿Puedo empujar / hacer estallar múltiples contextos uno encima del otro? Si es así, ¿por qué querría hacer eso?

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 ...

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

Luego usan los valores appy dben un script que debe ejecutarse desde el shell. Por ejemplo, un script "setup_tables.py" ...

from myapp import app, db

# Set up models
db.create_all()

En este caso, la extensión Flask-SQLAlchemy conoce la appaplicación, pero durante la create_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 el create_allmé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 ...

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

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:

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty
Mark Hildreth
fuente
3
¡Esto sigue siendo confuso para mí! ¿Por qué no tener un solo contexto de solicitud y reemplazarlo si desea hacer una redirección interna? Me parece un diseño claro.
Maarten
@Maarten Si mientras maneja la solicitud A realiza la solicitud B y la solicitud B reemplaza la solicitud A en la pila, el manejo de la solicitud A no puede finalizar. Sin embargo, incluso si realizó la estrategia de reemplazo como sugirió y no tenía una pila (lo que significa que las redirecciones internas serían más difíciles), esto realmente no cambia el hecho de que se requieren contextos de aplicación y solicitud para aislar el manejo de las solicitudes.
Mark Hildreth
¡Buena explicación! Pero todavía estoy un poco confuso acerca de: "El contexto de la aplicación se crea y destruye según sea necesario. Nunca se mueve entre hilos y no se compartirá entre solicitudes". En el documento del matraz. ¿Por qué un "contexto de aplicación" no persiste junto con la aplicación?
Jayven
1
Sería útil un ejemplo de una redirección interna en Flask, buscar en Google no aparece mucho. Si no fuera por eso, ¿no sería suficiente un request = Local()diseño más simple para global.py? Probablemente hay casos de uso en los que no estoy pensando.
CuádrupleA
¿Está bien empujar el contexto de la aplicación dentro del método de fábrica al importar las vistas? Como las vistas contienen rutas que hacen referencia a current_app, necesito el contexto.
variable
48

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 Localque permita objetos como requestygpara 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.localy flask.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 tener request, current_app, url_for, g, y otros objetos globales el contexto.

try:
    from greenlet import get_ident
except ImportError:
    from thread import get_ident

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.ej

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

Ambos valores están presentes en el Localobjeto accesible globalmente al mismo tiempo, pero acceder local.first_namedentro 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):

class Local(object)
    def __init__(self):
        self.storage = {}

    def __getattr__(self, name):
        context_id = get_ident() # we get the current thread's or greenlet's id
        contextual_storage = self.storage.setdefault(context_id, {})
        try:
            return contextual_storage[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        context_id = get_ident()
        contextual_storage = self.storage.setdefault(context_id, {})
        contextual_storage[name] = value

    def __release_local__(self):
        context_id = get_ident()
        self.storage.pop(context_id, None)

local = Local()

Del código anterior podemos ver que la magia se reduce a lo get_ident()que identifica el greenlet o hilo actual. El Localalmacenamiento solo lo usa como clave para almacenar cualquier dato contextual al hilo actual.

Se pueden tener varios Localobjetos por proceso y request, g, current_appy 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écnicamente Local objetos, sino más exactamente LocalProxyobjetos. ¿Qué es un LocalProxy?

LocalProxy

Un LocalProxy es un objeto que consulta a Localpara encontrar otro objeto de interés (es decir, el objeto al que se aproxima). Echemos un vistazo para entender:

class LocalProxy(object):
    def __init__(self, local, name):
        # `local` here is either an actual `Local` object, that can be used
        # to find the object of interest, here identified by `name`, or it's
        # a callable that can resolve to that proxied object
        self.local = local
        # `name` is an identifier that will be passed to the local to find the
        # object of interest.
        self.name = name

    def _get_current_object(self):
        # if `self.local` is truly a `Local` it means that it implements
        # the `__release_local__()` method which, as its name implies, is
        # normally used to release the local. We simply look for it here
        # to identify which is actually a Local and which is rather just
        # a callable:
        if hasattr(self.local, '__release_local__'):
            try:
                return getattr(self.local, self.name)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.name)

        # if self.local is not actually a Local it must be a callable that 
        # would resolve to the object of interest.
        return self.local(self.name)

    # Now for the LocalProxy to perform its intended duties i.e. proxying 
    # to an underlying object located somewhere in a Local, we turn all magic
    # methods into proxies for the same methods in the object of interest.
    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    def __repr__(self):
        try:
            return repr(self._get_current_object())
        except RuntimeError:
            return '<%s unbound>' % self.__class__.__name__

    def __bool__(self):
        try:
            return bool(self._get_current_object())
        except RuntimeError:
            return False

    # ... etc etc ... 

    def __getattr__(self, name):
        if name == '__members__':
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]

    # ... and so on ...

    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
    __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
    __str__ = lambda x: str(x._get_current_object())
    __lt__ = lambda x, o: x._get_current_object() < o
    __le__ = lambda x, o: x._get_current_object() <= o
    __eq__ = lambda x, o: x._get_current_object() == o

    # ... and so forth ...

Ahora, para crear proxies accesibles globalmente, harías

# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')

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

# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()

La ventaja de usar LocalProxyobjetos globalmente accesibles en lugar de hacerlos ellos Localsmismos es que simplifica su administración. Solo necesita un solo Localobjeto para crear muchos servidores proxy accesibles globalmente. Al final de la solicitud, durante la limpieza, simplemente libera el uno Local(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 del Localque busca su objeto. de interés para solicitudes http posteriores.

# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()

Para simplificar la creación de un LocalProxycuando ya tenemos un Local, Werkzeug implementa el Local.__call__()método mágico de la siguiente manera:

class Local(object):
    # ... 
    # ... all same stuff as before go here ...
    # ... 

    def __call__(self, name):
        return LocalProxy(self, name)

# now you can do
local = Local()
request = local('request')
g = local('g')

Sin embargo, si nos fijamos en la fuente Frasco (flask.globals) que todavía no es como request, g, current_appy sessionson 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 usa LocalStackobjetos para este propósito. Cuando concluyen su negocio, sacan el contexto de la pila.

LocalStack

Así es LocalStackcomo se ve (de nuevo, el código se simplifica para facilitar la comprensión de su lógica).

class LocalStack(object):

    def __init__(self):
        self.local = Local()

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self.local, 'stack', None)
        if rv is None:
            self.local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self.local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self.local) # this simply releases the local
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self.local.stack[-1]
        except (AttributeError, IndexError):
            return None

Tenga en cuenta de lo anterior que a LocalStackes 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, y sessionobjetos resolver directamente a una LocalStack, más bien utiliza LocalProxyobjetos que se colocan una función de búsqueda (en lugar de un Localobjeto) que se encuentra el objeto subyacente de la LocalStack:

_request_ctx_stack = LocalStack()
def _find_request():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.request
request = LocalProxy(_find_request)

def _find_session():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.session
session = LocalProxy(_find_session)

_app_ctx_stack = LocalStack()
def _find_g():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.g
g = LocalProxy(_find_g)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app
current_app = LocalProxy(_find_app)

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 del RequestContextobjeto a lo largo de su posterior push()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 ...

app = Flask(*config, **kwconfig)

# ...

Más tarde, llega una solicitud http y el servidor WSGI llama a la aplicación con los parámetros habituales ...

app(environ, start_response) # aka app.__call__(environ, start_response)

Esto es más o menos lo que sucede en la aplicación ...

def Flask(object):

    # ...

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        ctx = RequestContext(self, environ)
        ctx.push()
        try:
            # process the request here
            # raise error if any
            # return Response
        finally:
            ctx.pop()

    # ...

y esto es más o menos lo que sucede con RequestContext ...

class RequestContext(object):

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
        self.flashes = None

    def push(self):
        _request_ctx_stack.push(self)

    def pop(self):
        _request_ctx_stack.pop()

Digamos que una solicitud ha terminado de inicializarse, por lo tanto, la búsqueda request.pathde una de sus funciones de vista sería la siguiente:

  • comenzar desde el LocalProxyobjeto accesible globalmente request.
  • para encontrar su objeto de interés subyacente (el objeto al que está representando) llama a su función de búsqueda _find_request()(la función que registró como su self.local).
  • esa función consulta el LocalStackobjeto _request_ctx_stackpara el contexto superior en la pila.
  • Para encontrar el contexto superior, el LocalStackobjeto primero consulta su Localatributo interno ( self.local) para la stackpropiedad que estaba previamente almacenada allí.
  • del stackobtiene el contexto superior
  • y por top.requestlo tanto se resuelve como el objeto de interés subyacente.
  • de ese objeto obtenemos el pathatributo

Así hemos visto cómo Local, LocalProxyy LocalStackel trabajo, ahora piensa por un momento de las implicaciones y matices en la recuperación de la pathde:

  • un requestobjeto que sería un simple objeto accesible globalmente.
  • un requestobjeto que sería un local.
  • un requestobjeto almacenado como un atributo de un local.
  • un requestobjeto que es un proxy para un objeto almacenado en un local.
  • un requestobjeto almacenado en una pila, que a su vez se almacena en un local.
  • un requestobjeto que es un proxy para un objeto en una pila almacenada en un local. <- esto es lo que hace Flask.
Michael Ekoka
fuente
44
Excelente resumen, he estado estudiando el código en flask / globals.py y werkzeug / local.py y esto ayuda a aclarar mi comprensión. Mi sentido de la araña me dice que este es un diseño demasiado complicado, pero admito que no entiendo todos los casos de uso para los que está destinado. Las "redirecciones internas" son la única justificación que he visto en las descripciones anteriores, y la "redirección interna" de Google no aparece mucho, así que todavía estoy un poco perdido. Una de las cosas que me gustan del matraz es que, por lo general, no es un tipo de sopa de objetos java llena de AbstractProviderContextBaseFactories y demás.
CuádrupleA
1
@QuadrupleA Una vez que comprenda cómo funcionan estos Local, LocalStacky LocalProxyle 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.
Michael Ekoka
Lea estos enlaces, en su mayoría tienen sentido, pero el diseño todavía me parece demasiado complicado y tal vez demasiado inteligente para su propio bien. Pero no soy un gran admirador de OOP en general, y cosas de control de flujo implícito (anulando __call __ (), __getattr __ (), despacho de eventos dinámicos versus llamadas de funciones simples, envolviendo cosas en accesores de propiedad en lugar de simplemente usar un atributo regular, etc. .) así que tal vez sea solo una diferencia en filosofía. Tampoco soy un practicante de TDD, lo que parece que gran parte de esta maquinaria adicional está destinada a soportar.
CuádrupleA
1
Gracias por compartir esto, apreciado. El enhebrado es la debilidad con lenguajes como Python: terminas con patrones como el anterior que se filtran en los marcos de aplicaciones y que tampoco escalan realmente. Java es otro ejemplo en una situación similar re. hilos de rosca, semáforos, etc. Notoriamente difícil de corregir o mantener. Aquí es donde los lenguajes como Erlang / Elixir (usando BEAM) o los enfoques de bucle de eventos (por ejemplo, nginx vs apache, etc.) suelen ofrecer un enfoque más potente, escalable y menos complejo.
arcseldon
13

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 operaciones append( push) popy [-1]( __getitem__(-1)). Por lo tanto, la pila de contexto mantendrá datos reales para el hilo o el hilo greenlet.

current_app, g, request, sessionY etc es LocalProxyobjeto 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, requestpor ejemplo). LocalProxynecesitaba importar estos objetos una vez y no perderán la actualidad. Así que mejor solo importa requestdonde 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:

  1. crear request_contextpor entorno (init map_adapter, ruta de coincidencia)
  2. ingrese o envíe esta solicitud:
    1. claro anterior request_context
    2. crear app_contextsi se perdió y empujó a la pila de contexto de la aplicación
    3. esta solicitud se empujó para solicitar la pila de contexto
    4. sesión de inicio si se perdió
  3. solicitud de despacho
  4. borrar la solicitud y sacarla de la pila
tbicr
fuente
2

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:

class User(object):
    def __init__(self):
        self.userid = None

definir una función para recuperar el objeto del usuario dentro del hilo actual o greenlet

def get_user(_local):
    try:
        # get user object in current thread or greenlet
        return _local.user
    except AttributeError:
        # if user object is not set in current thread ,set empty user object 
       _local.user = User()
    return _local.user

Ahora defina un LocalProxy

usercontext = LocalProxy(partial(get_user, Local()))

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 ()

  1. LocalProxy delega la operación a un objeto local envuelto o puede proporcionar una función que devuelva el objeto de destino. En el ejemplo anterior, la función get_user proporciona el objeto de usuario actual a LocalProxy, y cuando solicita el userid del usuario actual por usercontext.userid, la función __getattr__ de LocalProxy primero llama a get_user para obtener el objeto User (user) y luego llama a getattr (user, "userid"). para configurar userid en User (en el hilo actual o greenlet) simplemente haga: usercontext.userid = "user_123"
Ratn Deo - Dev
fuente