Ich verwende den Anforderungs- / Anwendungskontext seit einiger Zeit, ohne vollständig zu verstehen, wie er funktioniert oder warum er so entworfen wurde, wie er war. Was ist der Zweck des "Stacks", wenn es um den Anforderungs- oder Anwendungskontext geht? Sind diese beiden getrennten Stapel oder sind sie beide Teil eines Stapels? Wird der Anforderungskontext auf einen Stapel verschoben oder handelt es sich um einen Stapel selbst? Kann ich mehrere Kontexte übereinander schieben / platzieren? Wenn ja, warum sollte ich das tun wollen?
Entschuldigung für alle Fragen, aber ich bin immer noch verwirrt, nachdem ich die Dokumentation für Anforderungskontext und Anwendungskontext gelesen habe.
Antworten:
Mehrere Apps
Der Anwendungskontext (und sein Zweck) ist in der Tat verwirrend, bis Sie erkennen, dass Flask mehrere Apps haben kann. Stellen Sie sich die Situation vor, in der ein einzelner WSGI Python-Interpreter mehrere Flask-Anwendungen ausführen soll. Wir sprechen hier nicht von Blaupausen, sondern von ganz anderen Flask-Anwendungen.
Sie können dies ähnlich wie im Abschnitt zur Kolbendokumentation im Beispiel "Application Dispatching" einrichten :
Beachten Sie, dass zwei völlig unterschiedliche Flask-Anwendungen "Frontend" und "Backend" erstellt werden. Mit anderen Worten, der
Flask(...)
Anwendungskonstruktor wurde zweimal aufgerufen, wodurch zwei Instanzen einer Flask-Anwendung erstellt wurden.Kontexte
Wenn Sie mit Flask arbeiten, verwenden Sie häufig globale Variablen, um auf verschiedene Funktionen zuzugreifen. Zum Beispiel haben Sie wahrscheinlich Code, der lautet ...
Während einer Ansicht können Sie
request
dann auf die Informationen der aktuellen Anforderung zugreifen. Offensichtlichrequest
ist keine normale globale Variable; In Wirklichkeit ist es ein lokaler Kontextwert . Mit anderen Worten, hinter den Kulissen steckt etwas Magisches, das besagt: "Wenn ich anruferequest.path
, erhalte ich daspath
Attribut vomrequest
Objekt der CURRENT-Anforderung." Zwei verschiedene Anfragen haben unterschiedliche Ergebnisse fürrequest.path
.Selbst wenn Sie Flask mit mehreren Threads ausführen, ist Flask intelligent genug, um die Anforderungsobjekte isoliert zu halten. Auf diese Weise können zwei Threads, die jeweils eine andere Anforderung bearbeiten, gleichzeitig
request.path
die richtigen Informationen für ihre jeweiligen Anforderungen aufrufen und abrufen .Etwas zusammensetzen
Wir haben also bereits gesehen, dass Flask mehrere Anwendungen in demselben Interpreter verarbeiten kann und dass aufgrund der Art und Weise, wie Flask die Verwendung von "kontextlokalen" Globals ermöglicht, ein Mechanismus vorhanden sein muss, um zu bestimmen, wie die "aktuelle" Anforderung lautet ( um Dinge zu tun wie
request.path
).Wenn man diese Ideen zusammenfasst, sollte es auch Sinn machen, dass Flask eine Möglichkeit haben muss, um zu bestimmen, was die "aktuelle" Anwendung ist!
Sie haben wahrscheinlich auch Code ähnlich dem folgenden:
Wie in unserem
request
Beispiel verfügt dieurl_for
Funktion über eine Logik, die von der aktuellen Umgebung abhängt. In diesem Fall ist jedoch klar, dass die Logik stark davon abhängt, welche App als "aktuelle" App betrachtet wird. In dem oben gezeigten Frontend / Backend-Beispiel können sowohl die "Frontend" - als auch die "Backend" -App eine "/ login" -Route haben undurl_for('/login')
sollten daher etwas anderes zurückgeben, je nachdem, ob die Ansicht die Anforderung für die Frontend- oder Backend-App verarbeitet.Um Ihre Fragen zu beantworten ...
Aus den Anforderungskontextdokumenten:
Mit anderen Worten, obwohl Sie normalerweise 0 oder 1 Elemente auf diesem Stapel von "aktuellen" Anforderungen oder "aktuellen" Anwendungen haben, ist es möglich, dass Sie mehr haben.
In dem angegebenen Beispiel würde Ihre Anfrage die Ergebnisse einer "internen Umleitung" zurückgeben. Angenommen, ein Benutzer fordert A an, aber Sie möchten zu Benutzer B zurückkehren. In den meisten Fällen geben Sie eine Umleitung an den Benutzer aus und verweisen den Benutzer auf Ressource B, was bedeutet, dass der Benutzer eine zweite Anforderung zum Abrufen von B ausführt. A. Eine etwas andere Art, dies zu handhaben, wäre eine interne Umleitung. Dies bedeutet, dass Flask während der Verarbeitung von A eine neue Anforderung für Ressource B an sich selbst stellt und die Ergebnisse dieser zweiten Anforderung als Ergebnisse der ursprünglichen Anforderung des Benutzers verwendet.
Sie sind zwei separate Stapel . Dies ist jedoch ein Implementierungsdetail. Was wichtiger ist, ist nicht so sehr, dass es einen Stapel gibt, sondern die Tatsache, dass Sie jederzeit die "aktuelle" App oder Anfrage (oben auf dem Stapel) erhalten können.
Ein "Anforderungskontext" ist ein Element des "Anforderungskontextstapels". Ähnliches gilt für den "App-Kontext" und den "App-Kontext-Stack".
In einer Flask-Anwendung würden Sie dies normalerweise nicht tun. Ein Beispiel dafür, wo Sie möchten, ist eine interne Umleitung (siehe oben). Selbst in diesem Fall würde Flask wahrscheinlich eine neue Anfrage bearbeiten, und Flask würde das ganze Pushing / Popping für Sie erledigen.
Es gibt jedoch einige Fälle, in denen Sie den Stapel selbst bearbeiten möchten.
Ausführen von Code außerhalb einer Anforderung
Ein typisches Problem besteht darin, dass sie die Flask-SQLAlchemy-Erweiterung verwenden, um eine SQL-Datenbank und eine Modelldefinition mithilfe von Code wie dem unten gezeigten einzurichten.
Dann verwenden sie die Werte
app
unddb
in einem Skript, das von der Shell ausgeführt werden soll. Zum Beispiel ein "setup_tables.py" -Skript ...In diesem Fall kennt die Flask-SQLAlchemy-Erweiterung die
app
Anwendung, gibt jedochcreate_all()
einen Fehler aus, der sich darüber beschwert, dass kein Anwendungskontext vorhanden ist. Dieser Fehler ist gerechtfertigt; Sie haben Flask nie gesagt, mit welcher Anwendung es sich beim Ausführen dercreate_all
Methode befassen soll .Sie fragen sich möglicherweise, warum Sie diesen
with app.app_context()
Aufruf nicht benötigen , wenn Sie ähnliche Funktionen in Ihren Ansichten ausführen. Der Grund dafür ist, dass Flask die Verwaltung des Anwendungskontexts bereits für Sie übernimmt, wenn es um tatsächliche Webanforderungen geht. Das Problem tritt wirklich nur außerhalb dieser Ansichtsfunktionen (oder anderer solcher Rückrufe) auf, z. B. wenn Sie Ihre Modelle in einem einmaligen Skript verwenden.Die Lösung besteht darin, den Anwendungskontext selbst zu verschieben, was durch ...
Dadurch wird ein neuer Anwendungskontext erstellt (bei Verwendung der Anwendung von
app
denken Sie daran, dass möglicherweise mehr als eine Anwendung vorhanden ist).Testen
Ein anderer Fall, in dem Sie den Stapel manipulieren möchten, ist das Testen. Sie können einen Komponententest erstellen, der eine Anforderung verarbeitet, und die Ergebnisse überprüfen:
quelle
request = Local()
Design für global.py nicht ausreichen? Es gibt wahrscheinlich Anwendungsfälle, an die ich nicht denke.Frühere Antworten geben bereits einen schönen Überblick darüber, was während einer Anfrage im Hintergrund von Flask vor sich geht. Wenn Sie es noch nicht gelesen haben, empfehle ich die Antwort von @ MarkHildreth, bevor Sie dies lesen. Kurz gesagt, für jede http-Anforderung wird ein neuer Kontext (Thread) erstellt, weshalb eine Thread-Funktion erforderlich ist
Local
, die Objekte wierequest
und zulässtg
globaler Zugriff über Threads hinweg unter Beibehaltung des anforderungsspezifischen Kontexts. Darüber hinaus kann Flask während der Verarbeitung einer http-Anfrage zusätzliche Anfragen von innen emulieren, weshalb der jeweilige Kontext auf einem Stapel gespeichert werden muss. Außerdem ermöglicht Flask, dass mehrere wsgi-Anwendungen innerhalb eines einzelnen Prozesses nebeneinander ausgeführt werden, und während einer Anforderung können mehrere aufgerufen werden (jede Anforderung erstellt einen neuen Anwendungskontext), sodass ein Kontextstapel für Anwendungen erforderlich ist. Das ist eine Zusammenfassung dessen, was in früheren Antworten behandelt wurde.Mein Ziel ist es nun, unser derzeitiges Verständnis zu ergänzen, indem ich erkläre, wie Flask und Werkzeug das tun, was sie mit diesen Kontext-Einheimischen tun. Ich habe den Code vereinfacht, um das Verständnis seiner Logik zu verbessern, aber wenn Sie dies erhalten, sollten Sie in der Lage sein, die meisten Inhalte der tatsächlichen Quelle (
werkzeug.local
undflask.globals
) leicht zu erfassen .Lassen Sie uns zunächst verstehen, wie Werkzeug Thread-Locals implementiert.
Lokal
Wenn eine http-Anfrage eingeht, wird sie im Kontext eines einzelnen Threads verarbeitet. Als alternatives Mittel, um während einer http-Anfrage einen neuen Kontext zu erzeugen, erlaubt Werkzeug auch die Verwendung von Greenlets (eine Art leichterer "Mikro-Threads") anstelle von normalen Threads. Wenn Sie keine Greenlets installiert haben, werden stattdessen wieder Threads verwendet. Jeder dieser Threads (oder Greenlets) ist durch eine eindeutige ID erkennbar, die Sie mit der
get_ident()
Funktion des Moduls abrufen können. Diese Funktion ist der Ausgangspunkt , um die Magie hinter mitrequest
,current_app
,url_for
,g
und andere solcher kontextgebundene globalen Objekten.Nachdem wir nun unsere Identitätsfunktion haben, können wir wissen, in welchem Thread wir uns zu einem bestimmten Zeitpunkt befinden, und wir können einen sogenannten Thread erstellen
Local
, ein Kontextobjekt, auf das global zugegriffen werden kann. Wenn Sie jedoch auf seine Attribute zugreifen, werden sie in ihren Wert für aufgelöst dieser spezifische Thread. z.BBeide Werte sind gleichzeitig auf dem global zugänglichen
Local
Objekt vorhanden, aber der Zugrifflocal.first_name
im Kontext von Thread 1 gibt Ihnen die Möglichkeit'John'
, während er'Debbie'
auf Thread 2 zurückkehrt.Wie ist das möglich? Schauen wir uns einen (vereinfachten) Code an:
Aus dem obigen Code können wir erkennen, dass die Magie darauf
get_ident()
hinausläuft, das aktuelle Greenlet oder den aktuellen Thread zu identifizieren. DerLocal
Speicher verwendet dies dann nur als Schlüssel zum Speichern von Daten, die für den aktuellen Thread kontextbezogen sind.Sie können mehrere
Local
Objekte pro Prozess undrequest
habeng
,current_app
und andere könnten einfach so erstellt worden sein. Aber so wird es in Flask nicht gemacht , wo es sich nicht um technischeLocal
Objekte handelt, sondern um genauereLocalProxy
Objekte. Was ist einLocalProxy
?LocalProxy
Ein LocalProxy ist ein Objekt, das a abfragt
Local
, um ein anderes interessierendes Objekt zu finden (dh das Objekt, für das es einen Proxy erstellt). Lassen Sie uns einen Blick darauf werfen, um zu verstehen:Nun würden Sie global zugängliche Proxys erstellen
und jetzt, einige Zeit früher im Verlauf einer Anfrage, würden Sie einige Objekte in der lokalen speichern, auf die die zuvor erstellten Proxys zugreifen können, unabhängig davon, auf welchem Thread wir uns befinden
Der Vorteil der Verwendung
LocalProxy
als global zugängliche Objekte, anstatt sieLocals
selbst zu erstellen, besteht darin, dass ihre Verwaltung vereinfacht wird. Sie benötigen nur ein einzigesLocal
Objekt, um viele global zugängliche Proxys zu erstellen. Am Ende der Anforderung geben Sie während der Bereinigung einfach die Freigabe freiLocal
(dh Sie löschen die context_id aus ihrem Speicher) und kümmern sich nicht um die Proxys. Sie sind weiterhin global zugänglich und verschieben sich immer noch auf diejenigeLocal
, um ihr Objekt zu finden von Interesse für nachfolgende http-Anfragen.Um die Erstellung eines zu vereinfachen,
LocalProxy
wenn wir bereits ein habenLocal
, implementiert Werkzeug dieLocal.__call__()
magische Methode wie folgt:Doch wenn man sich in der Flasche Quelle (flask.globals) , das ist immer noch nicht , wie
request
,g
,current_app
undsession
erstellt werden. Wie wir festgestellt haben, kann Flask mehrere "gefälschte" Anforderungen (aus einer einzigen echten http-Anforderung) erzeugen und dabei auch mehrere Anwendungskontexte übertragen. Dies ist kein allgemeiner Anwendungsfall, aber es ist eine Fähigkeit des Frameworks. Da diese "gleichzeitigen" Anforderungen und Apps immer noch nur mit einem "Fokus" ausgeführt werden können, ist es sinnvoll, einen Stapel für den jeweiligen Kontext zu verwenden. Immer wenn eine neue Anforderung erzeugt oder eine der Anwendungen aufgerufen wird, verschieben sie ihren Kontext an die Spitze ihres jeweiligen Stapels. Flask verwendetLocalStack
zu diesem Zweck Objekte. Wenn sie ihr Geschäft abschließen, entfernen sie den Kontext aus dem Stapel.LocalStack
So
LocalStack
sieht ein aus (wieder wird der Code vereinfacht, um das Verständnis seiner Logik zu erleichtern).Beachten Sie, dass a
LocalStack
ein Stapel ist, der in einem lokalen Speicher gespeichert ist, nicht eine Gruppe von Einheimischen, die auf einem Stapel gespeichert sind. Dies bedeutet, dass der Stapel zwar global zugänglich ist, in jedem Thread jedoch ein anderer Stapel vorhanden ist.Kolben haben nicht seine
request
,current_app
,g
, undsession
direkt an einen Auflösungs ObjekteLocalStack
, es eher verwendetLocalProxy
Objekte , welche eine Lookup - Funktion (anstelle einemLocal
Objekt) , die das darunter liegende Objekt aus dem finden wirdLocalStack
:Alle diese werden beim Start der Anwendung deklariert, werden jedoch erst dann aufgelöst, wenn ein Anforderungs- oder Anwendungskontext auf den jeweiligen Stapel verschoben wird.
Wenn Sie neugierig sind, wie ein Kontext tatsächlich in den Stapel eingefügt wird (und anschließend herausspringt), schauen Sie, an
flask.app.Flask.wsgi_app()
welcher Stelle die wsgi-App eingegeben wird (dh was der Webserver aufruft, und übergeben Sie die http-Umgebung an wann a Anfrage kommt herein), und folgen Sie der Erstellung desRequestContext
Objekts durch seine anschließendepush()
in_request_ctx_stack
. Sobald es oben auf den Stapel geschoben wurde, ist es über zugänglich_request_ctx_stack.top
. Hier ist ein abgekürzter Code, um den Ablauf zu demonstrieren:Sie starten also eine App und stellen sie dem WSGI-Server zur Verfügung ...
Später kommt eine http-Anfrage und der WSGI-Server ruft die App mit den üblichen Parametern auf ...
Dies ist ungefähr das, was in der App passiert ...
und genau das passiert mit RequestContext ...
Angenommen, eine Anfrage wurde initialisiert. Die Suche nach
request.path
einer Ihrer Ansichtsfunktionen würde daher wie folgt aussehen:LocalProxy
Objektrequest
._find_request()
(die Funktion, die es als seine registriert hatself.local
).LocalStack
Objekt_request_ctx_stack
nach dem obersten Kontext auf dem Stapel ab.LocalStack
fragt das Objekt zuerst sein inneresLocal
Attribut (self.local
) nach derstack
Eigenschaft ab, die zuvor dort gespeichert wurde.stack
bekommt es den obersten Kontexttop.request
wird somit als das zugrunde liegende interessierende Objekt aufgelöst.path
AttributSo wir , wie gesehen haben
Local
,LocalProxy
undLocalStack
Arbeit, denkt jetzt für einen Moment der Auswirkungen und Nuancen in dem Abrufen derpath
von:request
Objekt, das ein einfaches global zugängliches Objekt wäre.request
Objekt, das ein lokales wäre.request
Objekt, das als Attribut eines lokalen Objekts gespeichert ist.request
Objekt, das ein Proxy für ein Objekt ist, das in einem lokalen Objekt gespeichert ist.request
Objekt, das auf einem Stapel gespeichert ist, der wiederum in einem lokalen Objekt gespeichert ist.request
Objekt, das ein Proxy für ein Objekt auf einem Stapel ist, der in einem lokalen Speicher gespeichert ist. <- das macht Flask.quelle
Local
,LocalStack
undLocalProxy
Arbeit, schlage ich vor , diese Artikel des Dokuments zu überdenken: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev und flask.pocoo .org / docs / 0.11 / reqcontext . Ihr neuer Griff kann Sie sie mit einem neuen Licht sehen lassen und mehr Einsicht bieten.Kleine Ergänzung @ Mark Hildreths Antwort.
Der Kontextstapel sieht so aus
{thread.get_ident(): []}
,[]
als würde er "Stapel" genannt, da nur die Operationenappend
(push
)pop
und[-1]
(__getitem__(-1)
) verwendet werden. Der Kontextstapel speichert also die tatsächlichen Daten für den Thread oder den Greenlet-Thread.current_app
,g
,request
,session
Und ist etcLocalProxy
Objekt , das nur spezielle Methoden overrided__getattr__
,__getitem__
,__call__
,__eq__
und etc. und Rückgabewert von Kontextstapel nach oben ([-1]
) durch das Argument name (current_app
,request
zum Beispiel).LocalProxy
müssen diese Objekte einmal importieren und sie werden die Aktualität nicht verfehlen. Importierenrequest
Sie also besser, wo immer Sie sich im Code befinden, und spielen Sie stattdessen mit dem Senden des Anforderungsarguments an Ihre Funktionen und Methoden. Sie können damit problemlos eigene Erweiterungen schreiben, aber vergessen Sie nicht, dass eine leichtfertige Verwendung das Verständnis von Code erschweren kann.Nehmen Sie sich Zeit, um https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py zu verstehen .
Wie bevölkert sind beide Stapel? Auf Anfrage
Flask
:request_context
Umgebung erstellen (Initmap_adapter
, Übereinstimmungspfad)request_context
app_context
wenn es fehlte und in den Anwendungskontextstapel verschoben wurdequelle
Nehmen wir ein Beispiel: Angenommen, Sie möchten einen Benutzerkontext festlegen (mithilfe des Flask-Konstrukts von Local und LocalProxy).
Definieren Sie eine Benutzerklasse:
Definieren Sie eine Funktion zum Abrufen des Benutzerobjekts innerhalb des aktuellen Threads oder Greenlets
Definieren Sie nun einen LocalProxy
Nun, um die Benutzer-ID des Benutzers im aktuellen Thread usercontext.userid abzurufen
Erklärung:
1.Local hat ein Diktat der Identität und des Objekts, Identität ist Thread-ID oder Greenlet-ID. In diesem Beispiel entspricht _local.user = User () _local .___ Speicher __ [ID des aktuellen Threads] ["Benutzer"] = Benutzer ()
quelle