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.
Respostas:
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" :
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ê ...
Em uma visualização, você pode usar
request
para acessar as informações da solicitação atual. Obviamente,request
nã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 ligarrequest.path
, obtenha opath
atributo dorequest
objeto da solicitação ATUAL". Dois pedidos diferentes terão resultados diferentes pararequest.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.path
e 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:
Como nosso
request
exemplo, aurl_for
funçã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 ...
Nos documentos de contexto de solicitação:
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.
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).
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".
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 ...
Em seguida, eles usam os valores
app
edb
em um script que deve ser executado a partir do shell. Por exemplo, um script "setup_tables.py" ...Nesse caso, a extensão Flask-SQLAlchemy conhece o
app
aplicativo, mas durantecreate_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 ocreate_all
mé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 ...
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:
fonte
request = Local()
design mais simples não seria suficiente para global.py? Provavelmente existem casos de uso em que não estou pensando.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 thread
Local
recurso de que permita objetos comorequest
eg
seja 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.local
eflask.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 tendorequest
,current_app
,url_for
,g
, e outros tais objetos globais ligados ao contexto.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 exemploAmbos os valores estão presentes no
Local
objeto acessível globalmente ao mesmo tempo, mas o acessolocal.first_name
no 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):
A partir do código acima, podemos ver que a mágica se resume a
get_ident()
qual identifica o greenlet ou segmento atual. OLocal
armazenamento usa apenas isso como uma chave para armazenar quaisquer dados contextuais no encadeamento atual.Você pode ter vários
Local
objetos por processo erequest
,g
,current_app
e outros poderiam simplesmente ter sido criado assim. Mas não é assim que é feito no Flask, no qual estes não são tecnicamenteLocal
objetos, masLocalProxy
objetos com mais precisão . O que é umLocalProxy
?LocalProxy
Um LocalProxy é um objeto que consulta a
Local
para encontrar outro objeto de interesse (ou seja, o objeto ao qual ele procura proxy). Vamos dar uma olhada para entender:Agora, para criar proxies acessíveis globalmente, você faria
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
A vantagem de usar
LocalProxy
como objetos acessíveis globalmente, em vez de torná-los elesLocals
mesmos, é que simplifica seu gerenciamento. Você só precisa de um únicoLocal
objeto para criar muitos proxies acessíveis globalmente. No final da solicitação, durante a limpeza, você simplesmente libera umaLocal
(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 diferemLocal
para encontrar o objeto de interesse para solicitações http subsequentes.Para simplificar a criação de a
LocalProxy
quando já temos aLocal
, Werkzeug implementa oLocal.__call__()
método mágico da seguinte maneira:No entanto, se você olhar na fonte Flask (flask.globals) que ainda não é como
request
,g
,current_app
esession
sã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 usaLocalStack
objetos para essa finalidade. Quando concluem seus negócios, expõem o contexto da pilha.LocalStack
É assim que
LocalStack
parece (novamente o código é simplificado para facilitar a compreensão de sua lógica).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
, esession
objetos resolver diretamente a umLocalStack
, que, em vez usaLocalProxy
objetos que envolvem uma função de pesquisa (em vez de umLocal
objeto) que irá encontrar o objeto subjacente doLocalStack
: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 doRequestContext
objeto durante toda a sua posteriorpush()
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 ...
Mais tarde, uma solicitação http é recebida e o servidor WSGI chama o aplicativo com os parâmetros comuns ...
Isso é mais ou menos o que acontece no aplicativo ...
e isso é aproximadamente o que acontece com o RequestContext ...
Digamos que uma solicitação tenha terminado de inicializar, a pesquisa para
request.path
uma de suas funções de visualização seria a seguinte:LocalProxy
objeto acessível globalmenterequest
._find_request()
(a função que ele registrou como seuself.local
).LocalStack
objeto_request_ctx_stack
para o contexto superior na pilha.LocalStack
objeto primeiro consulta seuLocal
atributo interno (self.local
) para ostack
propriedade que foi armazenada anteriormente lá.stack
que obtém o contexto superiortop.request
portanto, é resolvido como o objeto de interesse subjacente.path
atributoEntão, vimos como
Local
,LocalProxy
eLocalStack
trabalhamos, agora pense por um momento nas implicações e nuances na recuperaçãopath
de:request
objeto que seria um simples objeto acessível globalmente.request
objeto que seria um local.request
objeto armazenado como um atributo de um local.request
objeto que é um proxy para um objeto armazenado em um local.request
objeto armazenado em uma pilha, que por sua vez é armazenado em um local.request
objeto que é um proxy para um objeto em uma pilha armazenada em um local. <- é isso que o Flask faz.fonte
Local
,LocalStack
eLocalProxy
trabalho, 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.Pequena adição @Mark Hildreth resposta de .
Aparece a pilha de contexto
{thread.get_ident(): []}
, onde é[]
chamada "pilha" porque usa apenas operaçõesappend
(push
)pop
e[-1]
(__getitem__(-1)
). Portanto, a pilha de contexto manterá os dados reais do thread ou do greenlet.current_app
,g
,request
,session
E etc, éLocalProxy
objecto 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
,request
por exemplo).LocalProxy
necessá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
:request_context
por ambiente (initmap_adapter
, caminho de correspondência)request_context
app_context
se falhou e empurrou para a pilha de contexto do aplicativofonte
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:
definir uma função para recuperar o objeto do usuário dentro do thread ou greenlet atual
Agora defina um LocalProxy
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 ()
fonte