J'utilise le contexte de demande / application depuis un certain temps sans comprendre pleinement comment cela fonctionne ou pourquoi il a été conçu tel quel. Quel est le but de la "pile" en ce qui concerne la demande ou le contexte d'application? Ces deux piles sont-elles séparées ou font-elles toutes deux partie d'une pile? Le contexte de requête est-il poussé sur une pile ou s'agit-il d'une pile elle-même? Suis-je capable de pousser / faire apparaître plusieurs contextes les uns sur les autres? Si oui, pourquoi voudrais-je faire ça?
Désolé pour toutes les questions, mais je suis toujours confus après avoir lu la documentation pour le contexte de demande et le contexte d'application.
Réponses:
Applications multiples
Le contexte de l'application (et son objectif) est en effet déroutant jusqu'à ce que vous vous rendiez compte que Flask peut avoir plusieurs applications. Imaginez la situation où vous souhaitez qu'un seul interpréteur WSGI Python exécute plusieurs applications Flask. Nous ne parlons pas de Blueprints ici, nous parlons d'applications Flask entièrement différentes.
Vous pouvez configurer cela de la même manière que dans la section de documentation de Flask sur l' exemple "Distribution d'application" :
Notez que deux applications Flask complètement différentes sont créées "frontend" et "backend". En d'autres termes, le
Flask(...)
constructeur d'application a été appelé deux fois, créant deux instances d'une application Flask.Contextes
Lorsque vous travaillez avec Flask, vous finissez souvent par utiliser des variables globales pour accéder à diverses fonctionnalités. Par exemple, vous avez probablement du code qui lit ...
Ensuite, lors d'une vue, vous pouvez utiliser
request
pour accéder aux informations de la demande en cours. De toute évidence, cerequest
n'est pas une variable globale normale; en réalité, c'est une valeur locale de contexte . En d'autres termes, il y a une certaine magie dans les coulisses qui dit "quand j'appellerequest.path
, récupère l'path
attribut de l'request
objet de la requête CURRENT." Deux demandes différentes auront des résultats différents pourrequest.path
.En fait, même si vous exécutez Flask avec plusieurs threads, Flask est suffisamment intelligent pour garder les objets de requête isolés. Ce faisant, il devient possible pour deux threads, chacun traitant une requête différente, d'appeler
request.path
et d'obtenir simultanément les informations correctes pour leurs requêtes respectives.Mettre ensemble
Nous avons donc déjà vu que Flask peut gérer plusieurs applications dans le même interpréteur, et aussi qu'en raison de la façon dont Flask vous permet d'utiliser les globaux "context local", il doit y avoir un mécanisme pour déterminer ce qu'est la requête "actuelle" ( afin de faire des choses telles que
request.path
).En rassemblant ces idées, il devrait également être logique que Flask ait un moyen de déterminer quelle est l'application "actuelle"!
Vous avez probablement également un code similaire au suivant:
Comme notre
request
exemple, laurl_for
fonction a une logique qui dépend de l'environnement actuel. Dans ce cas, cependant, il est clair que la logique dépend fortement de quelle application est considérée comme l'application "actuelle". Dans l'exemple frontend / backend illustré ci-dessus, les applications "frontend" et "backend" peuvent avoir une route "/ login" eturl_for('/login')
doivent donc renvoyer quelque chose de différent selon que la vue traite la demande de l'application frontend ou backend.Pour répondre à tes questions...
À partir de la documentation sur le contexte de la demande:
En d'autres termes, même si vous avez généralement 0 ou 1 élément sur cette pile de requêtes "actuelles" ou d'applications "actuelles", il est possible que vous en ayez plus.
L'exemple donné est celui où votre requête renvoie les résultats d'une "redirection interne". Disons qu'un utilisateur demande A, mais que vous souhaitez revenir à l'utilisateur B.Dans la plupart des cas, vous émettez une redirection vers l'utilisateur et pointez l'utilisateur vers la ressource B, ce qui signifie que l'utilisateur exécutera une deuxième demande pour récupérer B. A Une manière légèrement différente de gérer cela serait de faire une redirection interne, ce qui signifie que lors du traitement de A, Flask se fera une nouvelle demande pour la ressource B et utilisera les résultats de cette deuxième demande comme résultats de la demande d'origine de l'utilisateur.
Ce sont deux piles distinctes . Cependant, ceci est un détail de mise en œuvre. Le plus important n'est pas tant qu'il existe une pile, mais le fait qu'à tout moment, vous pouvez obtenir l'application ou la requête "actuelle" (en haut de la pile).
Un «contexte de demande» est un élément de la «pile de contexte de demande». De même avec le "contexte d'application" et la "pile de contexte d'application".
Dans une application Flask, vous ne le feriez généralement pas. Un exemple où vous pourriez vouloir est pour une redirection interne (décrite ci-dessus). Même dans ce cas, cependant, vous finiriez probablement par avoir Flask gérer une nouvelle requête, et donc Flask ferait tout le push / popping pour vous.
Cependant, dans certains cas, vous souhaitez manipuler vous-même la pile.
Exécution de code en dehors d'une requête
Un problème typique que les gens ont est qu'ils utilisent l'extension Flask-SQLAlchemy pour configurer une base de données SQL et une définition de modèle en utilisant un code semblable à ce qui est montré ci-dessous ...
Ensuite, ils utilisent les valeurs
app
etdb
dans un script qui doit être exécuté à partir du shell. Par exemple, un script "setup_tables.py" ...Dans ce cas, l'extension Flask-SQLAlchemy connaît l'
app
application, mais au cours decreate_all()
celle-ci, une erreur se plaint de l'absence de contexte d'application. Cette erreur est justifiée; vous n'avez jamais dit à Flask à quelle application il devait traiter lors de l'exécution de lacreate_all
méthode.Vous vous demandez peut-être pourquoi vous n'avez pas besoin de cet
with app.app_context()
appel lorsque vous exécutez des fonctions similaires dans vos vues. La raison en est que Flask gère déjà la gestion du contexte de l'application pour vous lorsqu'il traite des requêtes Web réelles. Le problème ne survient vraiment qu'en dehors de ces fonctions de vue (ou d'autres rappels de ce type), comme lors de l'utilisation de vos modèles dans un script ponctuel.La résolution est de pousser vous-même le contexte de l'application, ce qui peut être fait en faisant ...
Cela poussera un nouveau contexte d'application (en utilisant l'application de
app
, rappelez-vous qu'il peut y avoir plus d'une application).Essai
Un autre cas où vous voudriez manipuler la pile est pour les tests. Vous pouvez créer un test unitaire qui gère une demande et vous vérifiez les résultats:
la source
request = Local()
conception plus simple ne suffirait-elle pas pour global.py? Il y a probablement des cas d'utilisation auxquels je ne pense pas.Les réponses précédentes donnent déjà un bon aperçu de ce qui se passe en arrière-plan de Flask lors d'une requête. Si vous ne l'avez pas encore lu, je recommande la réponse de @ MarkHildreth avant de lire ceci. En bref, un nouveau contexte (thread) est créé pour chaque requête http, c'est pourquoi il est nécessaire d'avoir une fonction de thread
Local
qui autorise des objets tels querequest
etg
pour être accessible globalement à travers les threads, tout en conservant leur contexte spécifique à la demande. De plus, lors du traitement d'une requête http, Flask peut émuler des requêtes supplémentaires de l'intérieur, d'où la nécessité de stocker leur contexte respectif sur une pile. De plus, Flask permet à plusieurs applications wsgi de fonctionner les unes avec les autres dans un même processus, et plusieurs peuvent être appelées à l'action pendant une requête (chaque requête crée un nouveau contexte d'application), d'où la nécessité d'une pile de contexte pour les applications. C'est un résumé de ce qui a été traité dans les réponses précédentes.Mon objectif est maintenant de compléter notre compréhension actuelle en expliquant comment Flask et Werkzeug font ce qu'ils font avec ces locaux contextuels. J'ai simplifié le code pour améliorer la compréhension de sa logique, mais si vous obtenez cela, vous devriez être en mesure de saisir facilement la plupart de ce qui se trouve dans la source réelle (
werkzeug.local
etflask.globals
).Voyons d'abord comment Werkzeug implémente les threads locaux.
Local
Lorsqu'une requête http arrive, elle est traitée dans le contexte d'un seul thread. Comme moyen alternatif de générer un nouveau contexte lors d'une requête http, Werkzeug permet également l'utilisation de greenlets (une sorte de «micro-threads» plus légers) au lieu de threads normaux. Si vous n'avez pas installé de greenlets, il reviendra à l'utilisation des threads à la place. Chacun de ces threads (ou greenlets) est identifiable par un identifiant unique, que vous pouvez récupérer avec la
get_ident()
fonction du module . Cette fonction est le point de départ de la magie derrière ayantrequest
,current_app
,url_for
,g
, et d' autres objets globaux liés au contexte.Maintenant que nous avons notre fonction d'identité, nous pouvons savoir sur quel thread nous sommes à un moment donné et nous pouvons créer ce qu'on appelle un thread
Local
, un objet contextuel accessible globalement, mais lorsque vous accédez à ses attributs, ils se résolvent à leur valeur pour ce fil spécifique. par exempleLes deux valeurs sont présentes sur l'
Local
objet globalement accessible en même temps, mais accéderlocal.first_name
dans le contexte du thread 1 vous le donnera'John'
, alors qu'il reviendra'Debbie'
sur le thread 2.Comment est-ce possible? Regardons du code (simplifié):
À partir du code ci-dessus, nous pouvons voir que la magie se résume à
get_ident()
qui identifie le greenlet ou le thread actuel. LeLocal
stockage l'utilise ensuite comme clé pour stocker toutes les données contextuelles du thread actuel.Vous pouvez avoir plusieurs
Local
objets par processus etrequest
,g
,current_app
et d' autres pourraient simplement avoir été créé comme ça. Mais ce n'est pas ainsi que cela se fait dans Flask dans lequel ce ne sont pas techniquement desLocal
objets, mais plus précisément desLocalProxy
objets. Qu'est-ce qu'unLocalProxy
?LocalProxy
Un LocalProxy est un objet qui interroge a
Local
pour trouver un autre objet d'intérêt (c'est-à-dire l'objet auquel il se rapporte ). Jetons un œil pour comprendre:Maintenant, pour créer des proxys accessibles à l'échelle mondiale, vous
et maintenant quelque temps plus tôt au cours d'une demande, vous stockeriez des objets dans le local auxquels les proxys précédemment créés peuvent accéder, quel que soit le thread sur lequel nous sommes
L'avantage d'utiliser des
LocalProxy
objets globalement accessibles plutôt que de les fabriquer eux-Locals
mêmes est que cela simplifie leur gestion. Vous n'avez besoin que d'un seulLocal
objet pour créer de nombreux proxys globalement accessibles. A la fin de la requête, lors du nettoyage, vous libérez simplement celuiLocal
(c'est-à-dire que vous sortez le context_id de son stockage) et ne vous embêtez pas avec les proxies, ils sont toujours accessibles globalement et se reportent toujours à celuiLocal
pour trouver leur objet d'intérêt pour les requêtes http suivantes.Pour simplifier la création d'un
LocalProxy
lorsque nous avons déjà unLocal
, Werkzeug implémente laLocal.__call__()
méthode magique comme suit:Toutefois, si vous regardez dans la source Flask (de flask.globals) qui est toujours pas comment
request
,g
,current_app
etsession
sont créés. Comme nous l'avons établi, Flask peut générer plusieurs "fausses" requêtes (à partir d'une seule vraie requête http) et, dans le processus, également pousser plusieurs contextes d'application. Ce n'est pas un cas d'utilisation courant, mais c'est une capacité du framework. Étant donné que ces demandes et applications "simultanées" sont encore limitées à s'exécuter avec une seule ayant le "focus" à tout moment, il est logique d'utiliser une pile pour leur contexte respectif. À chaque fois qu'une nouvelle demande est générée ou qu'une des applications est appelée, ils poussent leur contexte en haut de leur pile respective. Flask utilise desLocalStack
objets à cet effet. Quand ils concluent leurs affaires, ils sortent le contexte de la pile.LocalStack
Voici à quoi
LocalStack
ressemble un (encore une fois, le code est simplifié pour faciliter la compréhension de sa logique).Notez à partir de ce qui précède que a
LocalStack
est une pile stockée dans un local, pas un groupe de locaux stockés sur une pile. Cela implique que bien que la pile soit globalement accessible, c'est une pile différente dans chaque thread.Flask ne dispose pas de son
request
,current_app
,g
et projet d'session
objets résoudre directement à unLocalStack
, il utilise plutôt desLocalProxy
objets qui enveloppent une fonction de recherche ( au lieu d'unLocal
objet) qui trouve l'objet sous - jacent de laLocalStack
:Tous ces éléments sont déclarés au démarrage de l'application, mais ne se résolvent réellement en rien tant qu'un contexte de demande ou un contexte d'application n'est pas poussé vers leur pile respective.
Si vous êtes curieux de voir comment un contexte est réellement inséré dans la pile (et ensuite sorti), regardez dans
flask.app.Flask.wsgi_app()
quel est le point d'entrée de l'application wsgi (c'est-à-dire ce que le serveur Web appelle et transmet l'environnement http quand un requête arrive), et suivez la création de l'RequestContext
objet tout au long de sa suitepush()
dans_request_ctx_stack
. Une fois poussé en haut de la pile, il est accessible via_request_ctx_stack.top
. Voici un code abrégé pour illustrer le flux:Vous démarrez donc une application et la mettez à disposition du serveur WSGI ...
Plus tard, une requête http arrive et le serveur WSGI appelle l'application avec les paramètres habituels ...
C'est à peu près ce qui se passe dans l'application ...
et c'est à peu près ce qui se passe avec RequestContext ...
Supposons qu'une requête ait terminé son initialisation, la recherche à
request.path
partir de l'une de vos fonctions d'affichage se déroulerait donc comme suit:LocalProxy
objet globalement accessiblerequest
._find_request()
(la fonction qu'il a enregistrée comme sonself.local
).LocalStack
objet_request_ctx_stack
pour le contexte supérieur sur la pile.LocalStack
objet interroge d'abord sonLocal
attribut interne (self.local
) pour lastack
propriété qui y était précédemment stockée.stack
il obtient le contexte supérieurtop.request
est donc résolu comme l'objet d'intérêt sous-jacent.path
attributNous avons donc vu comment
Local
,LocalProxy
etLocalStack
travailler, réfléchissons maintenant un instant aux implications et aux nuances de la récupérationpath
de:request
objet qui serait un simple objet accessible globalement.request
objet qui serait un local.request
objet stocké comme attribut d'un local.request
objet qui est un proxy vers un objet stocké dans un local.request
objet stocké sur une pile, qui est à son tour stocké dans un local.request
objet qui est un proxy vers un objet sur une pile stockée dans un local. <- c'est ce que fait Flask.la source
Local
,LocalStack
et leLocalProxy
travail, je vous propose de revenir sur ces articles de la doc: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev et flask.pocoo .org / docs / 0.11 / reqcontext . Votre nouvelle prise en main peut vous permettre de les voir sous un nouveau jour et peut fournir plus d'informations.Petit ajout à la réponse de @Mark Hildreth .
La pile de contexte ressemble à
{thread.get_ident(): []}
, là où elle est[]
appelée "pile" car utilisée uniquement pour les opérationsappend
(push
)pop
et[-1]
(__getitem__(-1)
). Ainsi, la pile de contexte conservera les données réelles pour le thread ou le thread greenlet.current_app
,g
,request
,session
Et etc est l'LocalProxy
objet qui vient overrided méthodes spéciales__getattr__
,__getitem__
,__call__
,__eq__
etc et la valeur de retour du haut (contexte de pile[-1]
par nom de l' argument) (current_app
,request
par exemple).LocalProxy
nécessaire pour importer ces objets une fois et ils ne manqueront pas d'actualité. Donc, mieux vaut simplement importerrequest
où que vous soyez dans le code au lieu de jouer avec l'envoi d'argument de requête vers vos fonctions et méthodes. Vous pouvez facilement écrire vos propres extensions avec lui, mais n'oubliez pas qu'une utilisation frivole peut rendre le code plus difficile à comprendre.Passez du temps à comprendre https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .
Alors, comment peuplé les deux piles? Sur demande
Flask
:request_context
par environnement (initmap_adapter
, match path)request_context
app_context
s'il a manqué et poussé vers la pile de contexte d'applicationla source
Prenons un exemple, supposons que vous souhaitiez définir un contexte utilisateur (en utilisant la construction flask de Local et LocalProxy).
Définissez une classe d'utilisateurs:
définir une fonction pour récupérer l'objet utilisateur dans le thread ou le greenlet courant
Maintenant, définissez un LocalProxy
Maintenant, pour obtenir l'ID utilisateur de l'utilisateur dans le thread actuel usercontext.userid
explication:
1.Local a un dict d'identité et d'objet, l'identité est threadid ou greenlet id, dans cet exemple _local.user = User () est équivalent à _local .___ storage __ [id du thread actuel] ["user"] = User ()
la source