Qual é o objetivo das pilhas de contexto do Flask?

158

Estou usando o contexto de solicitação / aplicativo há algum tempo, sem entender completamente como ele funciona ou por que ele foi projetado da maneira que era. Qual é o objetivo da "pilha" quando se trata do contexto de solicitação ou aplicativo? Essas duas pilhas são separadas ou fazem parte de uma pilha? O contexto da solicitação é empurrado para uma pilha ou é uma pilha propriamente dita? Posso empurrar / exibir vários contextos em cima uns dos outros? Se sim, por que eu iria querer fazer isso?

Desculpe por todas as perguntas, mas ainda estou confuso depois de ler a documentação de Solicitar contexto e Contexto de aplicativo.

Ben Davis
fonte
5
kronosapiens.github.io/blog/2014/08/14/… IMO, esta postagem no blog me dá a descrição mais compreensível do contexto do balão.
mission.liao

Respostas:

243

Vários aplicativos

O contexto do aplicativo (e seu objetivo) é realmente confuso até você perceber que o Flask pode ter vários aplicativos. Imagine a situação em que você deseja que um único intérprete WSGI Python execute vários aplicativos Flask. Não estamos falando do Blueprints aqui, estamos falando de aplicativos Flask totalmente diferentes.

Você pode configurar isso de forma semelhante à seção de documentação do Flask no exemplo "Despacho de aplicativos" :

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 existem dois aplicativos Flask completamente diferentes sendo criados "frontend" e "backend". Em outras palavras, o Flask(...)construtor do aplicativo foi chamado duas vezes, criando duas instâncias de um aplicativo Flask.

Contextos

Quando você trabalha com o Flask, geralmente acaba usando variáveis ​​globais para acessar várias funcionalidades. Por exemplo, você provavelmente tem um código que lê ...

from flask import request

Em uma visualização, você pode usar requestpara acessar as informações da solicitação atual. Obviamente, requestnão é uma variável global normal; na realidade, é um valor local do contexto . Em outras palavras, há alguma mágica nos bastidores que diz "quando eu ligar request.path, obtenha o pathatributo do requestobjeto da solicitação ATUAL". Dois pedidos diferentes terão resultados diferentes para request.path.

De fato, mesmo se você executar o Flask com vários threads, o Flask é inteligente o suficiente para manter os objetos de solicitação isolados. Ao fazer isso, torna-se possível para dois threads, cada um manipulando uma solicitação diferente, chamar request.pathe obter simultaneamente as informações corretas para suas respectivas solicitações.

Juntar as peças

Portanto, já vimos que o Flask pode lidar com vários aplicativos no mesmo intérprete e também devido à maneira como o Flask permite que você use globais globais "context local", deve haver algum mecanismo para determinar qual é a solicitação "atual" ( para fazer coisas como request.path).

Juntando essas idéias, também deve fazer sentido que o Flask deva ter alguma maneira de determinar qual é a aplicação "atual"!

Você provavelmente também possui um código semelhante ao seguinte:

from flask import url_for

Como nosso requestexemplo, a url_forfunção possui lógica dependente do ambiente atual. Nesse caso, no entanto, é claro que a lógica depende fortemente de qual aplicativo é considerado o aplicativo "atual". No exemplo de front-end / back-end mostrado acima, os aplicativos "front-end" e "back-end" podem ter uma rota "/ login" e, portanto, url_for('/login')devem retornar algo diferente, dependendo se a exibição estiver lidando com a solicitação do aplicativo de front-end ou back-end.

Para responder suas perguntas ...

Qual é o objetivo da "pilha" quando se trata do contexto de solicitação ou aplicativo?

Nos documentos de contexto de solicitação:

Como o contexto de solicitação é mantido internamente como uma pilha, você pode enviar e enviar várias vezes. Isso é muito útil para implementar coisas como redirecionamentos internos.

Em outras palavras, mesmo que você normalmente tenha 0 ou 1 itens nessa pilha de solicitações "atuais" ou aplicativos "atuais", é possível que você tenha mais.

O exemplo dado é onde você solicitaria que retornasse os resultados de um "redirecionamento interno". Digamos que um usuário solicite A, mas você deseja retornar ao usuário B. Na maioria dos casos, você emite um redirecionamento para o usuário e aponta o usuário para o recurso B, o que significa que o usuário executará uma segunda solicitação para buscar B. A Uma maneira ligeiramente diferente de lidar com isso seria fazer um redirecionamento interno, o que significa que, durante o processamento de A, o Flask fará uma nova solicitação para o recurso B e usará os resultados dessa segunda solicitação como resultados da solicitação original do usuário.

Essas duas pilhas são separadas ou fazem parte de uma pilha?

São duas pilhas separadas . No entanto, este é um detalhe de implementação. O que é mais importante não é tanto o fato de haver uma pilha, mas o fato de que a qualquer momento você pode obter o aplicativo ou solicitação "atual" (parte superior da pilha).

O contexto da solicitação é empurrado para uma pilha ou é uma pilha propriamente dita?

Um "contexto de solicitação" é um item da "pilha de contexto de solicitação". Da mesma forma com o "contexto do aplicativo" e a "pilha de contexto do aplicativo".

Posso empurrar / exibir vários contextos em cima uns dos outros? Se sim, por que eu iria querer fazer isso?

Em um aplicativo Flask, você normalmente não faria isso. Um exemplo de onde você pode querer é um redirecionamento interno (descrito acima). Mesmo nesse caso, no entanto, você provavelmente acabaria fazendo com que o Flask lidasse com uma nova solicitação e, portanto, o Flask faria todo o esforço para você.

No entanto, existem alguns casos em que você deseja manipular a pilha sozinho.

Executando código fora de uma solicitação

Um problema típico que as pessoas têm é que usam a extensão Flask-SQLAlchemy para configurar um banco de dados SQL e uma definição de modelo usando código semelhante ao que é mostrado abaixo ...

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

Em seguida, eles usam os valores appe dbem um script que deve ser executado a partir do shell. Por exemplo, um script "setup_tables.py" ...

from myapp import app, db

# Set up models
db.create_all()

Nesse caso, a extensão Flask-SQLAlchemy conhece o appaplicativo, mas durante create_all()o processo gera um erro reclamando que não existe um contexto de aplicativo. Este erro é justificado; você nunca disse ao Flask com qual aplicativo ele deveria lidar ao executar o create_allmétodo.

Você pode estar se perguntando por que não precisa dessa with app.app_context()chamada quando executa funções semelhantes em suas visualizações. O motivo é que o Flask já lida com o gerenciamento do contexto do aplicativo quando ele está lidando com solicitações reais da Web. O problema realmente surge apenas fora dessas funções de visualização (ou outras chamadas de retorno), como ao usar seus modelos em um script único.

A resolução é empurrar você mesmo o contexto do aplicativo, o que pode ser feito fazendo ...

from myapp import app, db

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

Isso empurrará um novo contexto de aplicativo (usando o aplicativo de app , lembre-se de que pode haver mais de um aplicativo).

Teste

Outro caso em que você deseja manipular a pilha é para testar. Você pode criar um teste de unidade que lida com uma solicitação e verificar os 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
fonte
3
Isso ainda é confuso para mim! Por que não ter um único contexto de solicitação e substituí-lo se você deseja fazer um redirecionamento interno. Parece um design claro para mim.
Maarten
@Maarten Se, ao manipular a solicitação A, você faz a solicitação B e a solicitação B substitui a solicitação A na pilha, a manipulação da solicitação A não pode terminar. No entanto, mesmo que você tenha feito a estratégia de substituição como sugerido e não tenha uma pilha (o que significa que os redirecionamentos internos seriam mais difíceis), isso não muda o fato de que os contextos de aplicativo e solicitação são necessários para isolar o tratamento de solicitações.
Mark Hildreth
Boa explicação! Mas ainda estou um pouco confuso sobre: ​​"O contexto do aplicativo é criado e destruído conforme necessário. Ele nunca se move entre threads e não será compartilhado entre solicitações". No documento do frasco. Por que um "contexto de aplicativo" não persiste junto com o aplicativo?
Jayven
1
Um exemplo de redirecionamento interno no Flask seria útil, pesquisando no Google não aparece muito. Se não fosse por isso, um request = Local()design mais simples não seria suficiente para global.py? Provavelmente existem casos de uso em que não estou pensando.
QuadrupleA
É bom colocar o contexto do aplicativo dentro do método de fábrica ao importar as visualizações? Como as visualizações contêm rotas referentes ao current_app, preciso do contexto.
variável
48

As respostas anteriores já oferecem uma boa visão geral do que acontece no plano de fundo do Flask durante uma solicitação. Se você ainda não o leu, recomendo a resposta de @ MarkHildreth antes de ler isso. Em resumo, um novo contexto (thread) é criado para cada solicitação http, e é por isso que é necessário ter um threadLocal recurso de que permita objetos como requestegseja acessível globalmente através de threads, mantendo o contexto específico da solicitação. Além disso, ao processar uma solicitação http, o Flask pode emular solicitações adicionais de dentro, daí a necessidade de armazenar seu respectivo contexto em uma pilha. Além disso, o Flask permite que vários aplicativos wsgi sejam executados entre si em um único processo, e mais de um pode ser chamado à ação durante uma solicitação (cada solicitação cria um novo contexto de aplicativo), daí a necessidade de uma pilha de contexto para aplicativos. Este é um resumo do que foi abordado nas respostas anteriores.

Meu objetivo agora é complementar nosso entendimento atual, explicando como Flask e Werkzeug fazem o que fazem com esses locais de contexto. Simplifiquei o código para aprimorar o entendimento de sua lógica, mas se você entender isso, poderá entender facilmente a maior parte do que está na fonte ( werkzeug.locale flask.globals) real .

Vamos primeiro entender como o Werkzeug implementa Thread Locals.

Local

Quando uma solicitação http é recebida, ela é processada no contexto de um único encadeamento. Como um meio alternativo para gerar um novo contexto durante uma solicitação de http, o Werkzeug também permite o uso de greenlets (uma espécie de "micro-threads" mais leves) em vez de threads normais. Se você não tiver greenlets instalados, ele voltará ao uso de threads. Cada um desses encadeamentos (ou greenlets) é identificável por um ID exclusivo, que você pode recuperar com a get_ident()função do módulo . Essa função é o ponto de partida para a mágica por trás tendo request, current_app, url_for, g, e outros tais objetos globais ligados ao contexto.

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

Agora que temos nossa função de identidade, podemos saber em qual thread estamos em um determinado momento e podemos criar o que é chamado de thread Local, um objeto contextual que pode ser acessado globalmente, mas quando você acessa seus atributos, eles resolvem seu valor para esse segmento específico. por exemplo

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

Ambos os valores estão presentes no Localobjeto acessível globalmente ao mesmo tempo, mas o acesso local.first_nameno contexto do encadeamento 1 fornecerá a você 'John', enquanto retornará 'Debbie'no encadeamento 2.

Como isso é possível? Vamos dar uma olhada em algum 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()

A partir do código acima, podemos ver que a mágica se resume a get_ident()qual identifica o greenlet ou segmento atual. O Localarmazenamento usa apenas isso como uma chave para armazenar quaisquer dados contextuais no encadeamento atual.

Você pode ter vários Localobjetos por processo e request, g, current_appe outros poderiam simplesmente ter sido criado assim. Mas não é assim que é feito no Flask, no qual estes não são tecnicamente Local objetos, mas LocalProxyobjetos com mais precisão . O que é um LocalProxy?

LocalProxy

Um LocalProxy é um objeto que consulta a Localpara encontrar outro objeto de interesse (ou seja, o objeto ao qual ele procura proxy). Vamos dar uma olhada 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 ...

Agora, para criar proxies acessíveis globalmente, você faria

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

e agora, com algum tempo de antecedência, no decorrer de uma solicitação, você armazenaria alguns objetos dentro do local que os proxies criados anteriormente podem acessar, independentemente do segmento em que estamos

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

A vantagem de usar LocalProxycomo objetos acessíveis globalmente, em vez de torná-los eles Localsmesmos, é que simplifica seu gerenciamento. Você só precisa de um único Localobjeto para criar muitos proxies acessíveis globalmente. No final da solicitação, durante a limpeza, você simplesmente libera uma Local(ou seja, extrai o context_id do armazenamento) e não se preocupa com os proxies, eles ainda são acessíveis globalmente e ainda diferem Localpara encontrar o objeto de interesse para solicitações http subsequentes.

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

Para simplificar a criação de a LocalProxyquando já temos a Local, Werkzeug implementa o Local.__call__()método mágico da seguinte maneira:

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

No entanto, se você olhar na fonte Flask (flask.globals) que ainda não é como request, g, current_appe sessionsão criados. Como estabelecemos, o Flask pode gerar várias solicitações "falsas" (a partir de uma única solicitação HTTP verdadeira) e, no processo, também envia vários contextos de aplicativos. Este não é um caso de uso comum, mas é um recurso da estrutura. Como essas solicitações e aplicativos "simultâneos" ainda estão limitados para serem executados, com apenas um tendo o "foco" a qualquer momento, faz sentido usar uma pilha para o respectivo contexto. Sempre que uma nova solicitação é gerada ou um dos aplicativos é chamado, eles colocam seu contexto no topo da respectiva pilha. O Flask usa LocalStackobjetos para essa finalidade. Quando concluem seus negócios, expõem o contexto da pilha.

LocalStack

É assim que LocalStackparece (novamente o código é simplificado para facilitar a compreensão de sua 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

Observe acima que um LocalStack é uma pilha armazenada em um local, não um monte de locais armazenados em uma pilha. Isso implica que, embora a pilha seja globalmente acessível, é uma pilha diferente em cada thread.

O frasco não tem o seu request, current_app, g, e sessionobjetos resolver diretamente a um LocalStack, que, em vez usa LocalProxyobjetos que envolvem uma função de pesquisa (em vez de um Localobjeto) que irá encontrar o objeto subjacente do 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 esses itens são declarados na inicialização do aplicativo, mas na verdade não resolvem nada até que um contexto de solicitação ou contexto de aplicativo seja enviado para a respectiva pilha.

Se você estiver curioso para ver como um contexto é realmente inserido na pilha (e posteriormente exibido), flask.app.Flask.wsgi_app()verifique qual é o ponto de entrada do aplicativo wsgi (ou seja, o que o servidor da web chama e passe o ambiente http para quando um pedido vem em), e siga a criação do RequestContextobjeto durante toda a sua posterior push()em _request_ctx_stack. Uma vez pressionado no topo da pilha, é acessível via_request_ctx_stack.top . Aqui está um código abreviado para demonstrar o fluxo:

Então, você inicia um aplicativo e o disponibiliza para o servidor WSGI ...

app = Flask(*config, **kwconfig)

# ...

Mais tarde, uma solicitação http é recebida e o servidor WSGI chama o aplicativo com os parâmetros comuns ...

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

Isso é mais ou menos o que acontece no aplicativo ...

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

    # ...

e isso é aproximadamente o que acontece com o 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 uma solicitação tenha terminado de inicializar, a pesquisa para request.pathuma de suas funções de visualização seria a seguinte:

  • comece a partir do LocalProxyobjeto acessível globalmente request.
  • para encontrar seu objeto de interesse subjacente (o objeto para o qual está fazendo proxy), ele chama sua função de pesquisa _find_request()(a função que ele registrou como seuself.local ).
  • essa função consulta o LocalStackobjeto_request_ctx_stack para o contexto superior na pilha.
  • Para encontrar o contexto principal, o LocalStackobjeto primeiro consulta seu Localatributo interno ( self.local) para ostack propriedade que foi armazenada anteriormente lá.
  • de stack que obtém o contexto superior
  • e top.request portanto, é resolvido como o objeto de interesse subjacente.
  • desse objeto, obtemos o pathatributo

Então, vimos como Local, LocalProxye LocalStacktrabalhamos, agora pense por um momento nas implicações e nuances na recuperação pathde:

  • um requestobjeto que seria um simples objeto acessível globalmente.
  • um requestobjeto que seria um local.
  • um requestobjeto armazenado como um atributo de um local.
  • uma request objeto que é um proxy para um objeto armazenado em um local.
  • uma request objeto armazenado em uma pilha, que por sua vez é armazenado em um local.
  • um requestobjeto que é um proxy para um objeto em uma pilha armazenada em um local. <- é isso que o Flask faz.
Michael Ekoka
fonte
4
Excelente resumo, estudei o código em flask / globals.py e werkzeug / local.py e isso ajuda a esclarecer minha compreensão. Meu senso de aranha me diz que esse é um design muito complicado, mas admito que não entendo todos os casos de uso a que ele se destina. "Redirecionamentos internos" são a única justificativa que eu vi nas descrições acima, e o "redirecionamento interno do frasco" no Google não aparece muito, por isso ainda estou um pouco perdido. Uma das coisas que eu mais gosto no balão é que geralmente não é um tipo de sopa de objetos java cheio de AbstractProviderContextBaseFactories e tal.
QuadrupleA
1
@QuadrupleA Depois de entender como estes Local, LocalStacke LocalProxytrabalho, sugiro rever estes artigos do doc: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev e flask.pocoo .org / docs / 0.11 / reqcontext . Sua nova compreensão pode permitir que você os veja com uma nova luz e pode fornecer mais informações.
Michael Ekoka
Leia esses links - eles geralmente fazem sentido, mas o design ainda me parece muito complicado e talvez muito inteligente para o seu próprio bem. Mas eu não sou um grande fã de OOP em geral, e coisas implícitas de controle de fluxo (substituindo __call __ (), __getattr __ (), despacho dinâmico de eventos versus chamadas de função simples, agrupando itens em acessadores de propriedades em vez de apenas usar um atributo comum, etc. .) então talvez seja apenas uma diferença na filosofia. Também não é um praticante de TDD, que muitas dessas máquinas extras parecem ter como objetivo apoiar.
QuadrupleA
1
Obrigado por compartilhar isso, apreciado. O encadeamento é a fraqueza de linguagens como python - você acaba com padrões como o acima, que vazam nas estruturas de aplicativos e que não são realmente dimensionados. Java é outro exemplo em uma situação semelhante. threadlocals, semáforos, etc. Notoriamente difícil de acertar ou manter. É aqui que idiomas como Erlang / Elixir (usando BEAM) ou abordagens de loop de eventos (por exemplo, nginx vs apache etc.) geralmente oferecem uma abordagem mais poderosa, escalável e menos complexa.
Arcseldon 20/03/19
13

Pequena adição @Mark Hildreth resposta de .

Aparece a pilha de contexto {thread.get_ident(): []}, onde é []chamada "pilha" porque usa apenas operações append( push) pope [-1]( __getitem__(-1)). Portanto, a pilha de contexto manterá os dados reais do thread ou do greenlet.

current_app, g, request, sessionE etc, é LocalProxyobjecto que apenas overrided métodos especiais __getattr__, __getitem__, __call__, __eq__e etc, e valor de retorno da parte superior da pilha de contexto ( [-1]) pelo nome do argumento ( current_app, requestpor exemplo). LocalProxynecessário importar esses objetos uma vez e eles não perderão a realidade. Então é melhor importarrequest onde quer que você esteja no código, em vez disso, brinque com o envio do argumento de solicitação para suas funções e métodos. Você pode escrever facilmente suas próprias extensões, mas não se esqueça que o uso frívolo pode dificultar o entendimento do código.

Reserve um tempo para entender https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .

Então, como as duas pilhas foram preenchidas? A pedido Flask:

  1. criar request_contextpor ambiente (init map_adapter, caminho de correspondência)
  2. insira ou envie esta solicitação:
    1. limpar anterior request_context
    2. criar app_contextse falhou e empurrou para a pilha de contexto do aplicativo
    3. esta solicitação foi enviada para solicitar a pilha de contexto
    4. sessão init se falhou
  3. pedido de expedição
  4. pedido claro e pop-lo da pilha
tbicr
fonte
2

Vamos dar um exemplo, suponha que você queira definir um contexto de usuário (usando a construção de balão de Local e LocalProxy).

Defina uma classe de usuário:

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

definir uma função para recuperar o objeto do usuário dentro do thread ou greenlet atual

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

Agora defina um LocalProxy

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

Agora, para obter userid of user no segmento atual usercontext.userid

explicação:

1.Local tem um ditado de identidade e objeto, a identidade é id de thread ou greenlet, neste exemplo _local.user = User () é equivalente a _local .___ storage __ [id do thread atual] ["user"] = User ()

  1. LocalProxy delega a operação para empacotar o objeto Local ou você pode fornecer uma função que retorna o objeto de destino. No exemplo acima, a função get_user fornece o objeto de usuário atual ao LocalProxy e, quando você solicita o userid do usuário atual por usercontext.userid, a função __getattr__ do LocalProxy chama primeiro get_user para obter o objeto User (usuário) e depois chama getattr (user, "userid"). para definir userid no User (no thread ou greenlet atual), basta: usercontext.userid = "user_123"
Ratn Deo - Dev
fonte