angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Die geheimnisvolle Flask Application Context, meine Fragen und Antworten

Der Anwendungskontext wird initialisiert, gepusht und gepoppt, beim ersten Mal können die Dinge für den Benutzer verwirrend sein. Zeit, mit dem Lesen zu beginnen.

30 Januar 2020
In Flask
post main image
https://unsplash.com/@donovan_valdivia

Wenn Sie mit Flask beginnen, lesen Sie ein wenig über die Application Context, TL;DR. Ich weiß nicht, wie es Ihnen geht, aber ich habe es sicher nicht ganz verstanden. Was ist app, was ist current_app, was ist der Unterschied, Sie beginnen einfach mit der Programmierung Ihrer Anwendung. Die ganze Zeit im Hintergrund gibt es dieses seltsame Summen: Was genau ist die Application Context ... Dann trafen Sie zu einem bestimmten Zeitpunkt bei der Verwendung einer Klasse, die Sie in Ihrer create_app()-Fabrik instanziiert haben, auf diesen Fehler:

RuntimeError: working outside of application context

WTF! Dann beginnt man zu lesen, aber natürlich nicht zu viel, TL;DR wieder. Ok, die Lösung ist, einfach ein "mit" davor zu setzen, und schon ist man gut:

with app_context(app):
    ...

Es wird lästig

Aber langsam wird es immer ärgerlicher. Beispielsweise instanziiere ich ein Objekt während der Erstellung einer Anwendung in create_app(). Dieses Objekt hat Methoden mit Logger-Anweisungen wie

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

Aber wenn ich diese Methode von einem Blueprint aus aufrufe, bekomme ich eine Fehlermeldung. Jetzt muss ich die Logger-Anweisung mit current_app verwenden:

     current_app.logger.debug(fname  +  ': message, var = {}'.format(var))

Es ist verwirrend

Aber das mag auch daran liegen, dass ich kein sehr erfahrener Python Flask -Programmierer bin. In create_app() hänge ich den Logger an das Anwendungsobjekt an:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

aber in der gleichen create_app() hängen wir auch Ereignisse wie @before_request an. In @before_request können wir beides verwenden:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

und

     current_app.logger.debug(fname  +  ': message, var = {}'.format(var))

Warum? Ich kann verstehen, dass beide funktionieren, aber werden beide zum gleichen Ergebnis führen? In @before_request ist das current_app (wahrscheinlich) das geschobene Application Context. Aber ist das wirklich wahr? Ich habe auch nach einer Möglichkeit gesucht, Daten im Application Context zu speichern. In der Dokumentation steht:

Daten speichern: Der Application Context ist ein guter Ort, um allgemeine Daten während einer Anfrage oder eines CLI-Befehls zu speichern. Flask liefert das g-Objekt für diesen Zweck. Es handelt sich um ein einfaches Namensraumobjekt, das die gleiche Lebensdauer hat wie ein Application Context".

Beachten Sie, dass es heißt: WÄHREND einer Anfrage. Es besagt auch, dass das g-Objekt verwendet werden soll. Wie auch immer, Zeit, mit dem Lesen zu beginnen und einige Tests durchzuführen. Ich habe versucht, eine Reihe von Fragen zu beantworten, die ich hatte, und dachte, ich könnte sie aufschreiben, damit ich sie nie vergessen kann. Hoffentlich sind die Antworten richtig ... :-( Übrigens benutze ich in meiner Anwendung das Fabrikmuster:

def  create_app(config_name):

    ...

    @app.before_request
    def  before_request():
        ...

    return app

Frage: Wird die gepush-te Application Context -Anfrage nach einer Anfrage zerstört?

Kurze Antwort: Ja.

Je mehr man darüber liest, desto verwirrender wird es. Zum Beispiel aus der Dokumentation Flask :

Der Application Context wird erstellt und bei Bedarf zerstört. Wenn eine Flask -Anwendung mit der Bearbeitung einer Anforderung beginnt, schiebt sie eine Application Context und einen Anforderungskontext. Wenn die Anfrage beendet wird, wird der Anfragekontext und dann Application Context angezeigt. In der Regel hat ein Application Context die gleiche Lebensdauer wie eine Anfrage".

Schauen wir uns diesen Satz an: 'Der Application Context wird erstellt und bei Bedarf zerstört. Was bedeutet dieses "notwendig" genau?

Die Dokumentation besagt, dass die Application Context nach einer Anfrage vernichtet wird, aber das stimmt nicht, oder ist es so? Um dies zu überprüfen, fügte ich z.B. in einer Ansichtsfunktion eine Variable zu der gepushten Application Context hinzu:

def home():
    fname = 'home'
    ...
    # test to check if config can be accessed and is maintained between requests
    if 'SOME_PARAM_KEY' not in  current_app.config:
         current_app.logger.debug(fname  +  ': SOME_PARAM_KEY not in  current_app.config')
        # add it now
         current_app.config['SOME_PARAM_KEY'] = 'some_param_value'
    else:		
         current_app.logger.debug(fname  +  ': SOME_PARAM_KEY in  current_app.config,  current_app.config[SOME_PARAM] = {}'.format(current_app.config['SOME_PARAM_KEY']))

Bei der ersten Anfrage ist SOME_PARAM_KEY nicht in der Konfiguration, wir drucken dies aus und setzen es dann. Die nächsten Anfragen zeigen, dass SOME_PARAM_KEY in der Konfiguration enthalten ist und die Konfiguration den richtigen Wert für SOME_PARAM_KEY enthält. Das bedeutet, dass die Application Context nicht zerstört wird, sondern dass nur ein Verweis auf die Application Context geändert wird.

Folgendes ist falsch: Das oben genannte wurde mit Flask im DEBUG-Modus gemacht. Wenn Sie im Modus PRODUCTION genau denselben Test durchführen, werden Sie bei der nächsten Anfrage NIEMALS SOME_PARAM_KEY in current_app.config finden. Seien Sie also gewarnt, in welchem Zustand Sie Flask verwenden. Wenn der DEBUG-Modus eingeschaltet ist, scheint es, dass Flask die Application Context speichert, offensichtlich zur Fehlersuche.

Frage: Können Sie ein Datenbankobjekt bei der Erstellung einer App anhängen und später in Ihren Blueprints darauf zugreifen?

Kurze Antwort: nein.

Mit späterem Zugriff meine ich: auf spätere Anfragen. Sie müssen sehr vorsichtig sein mit dem, was Sie an die Application Context bei der Erstellung der Anwendung anhängen. Wenn Sie ein SQLAlchemy -Datenbankobjekt zur Zeit der Erstellung einer Anwendung anhängen, z.B.

    app.languages = db.session.query(Language).all()

Bei der ersten Anfrage können Sie dann auf die Sprachen in einem Blueprint wie z.B:

    for language in  current_app.languages:
        ...

Aber denken Sie dann daran, dass die Sitzung zum Zeitpunkt des Abreißens zerstört (entfernt) wird:

    @app.teardown_appcontext
    def teardown_db(resp_or_exc):
         current_app.db.session.remove()

Wenn Sie bei einer nächsten Anfrage versuchen, auf ein solches Objekt zuzugreifen, erhalten Sie eine Fehlermeldung:

(DetachedInstanceError('Instance <Setting at 0x7f4466739d90> is not bound to a Session; attribute refresh operation cannot proceed'),)

Das war zu erwarten, aber ich habe es getroffen.

Frage: Bedeutet 'knallt der Application Context', dass der Application Context noch verfügbar ist?

Kurze Antwort: nein.

Aus dem gleichen Text wie oben: "Wenn die Anfrage beendet wird, erscheint der Anfragekontext und dann der Application Context. Wir sprechen hier also über Pop. Dies kann zwei Dinge bedeuten:

  1. Die Application Context wird in den blauen Himmel gehoben
  2. Wenn sie über die Application Context sprechen, meinen sie in Wirklichkeit einen Zeiger auf die Application Context

Warum ist dies so wichtig? Denn im ersten Fall bedeutet dies, dass die Application Context schreibgeschützt ist, und im zweiten Fall wird die Application Context zwischen den Anfragen nicht zerstört. Siehe auch die Antwort in einer früheren Frage.

Frage: Sollten Sie ein globales Objekt außerhalb des App-Objekts halten?

Kurze Antwort: abhängig.

Logger-Objekt

Zuerst sehen wir uns die Protokollierung an. Hier fügen wir den Logger an das App-Objekt an.

def  create_app(config_name):

    ...
    # set app log handler level
    app_log_handler.setLevel(app_log_level)
    # set app logger
    app.logger.setLevel(app_log_level)
    app.logger.addHandler(app_log_handler)
    ...


    return app

Und dann können wir in den Blueprints den Logger wie folgt verwenden:

from flask import  current_app

def user_list(page_number):
    fname = 'user_list'
     current_app.logger.debug(fname  +  '()')
    ...

Der Logger wird während der Erstellung der Anwendung initialisiert und an das Anwendungsobjekt angehängt und funktioniert einwandfrei, wenn er in den Funktionen der Blueprint-Ansicht verwendet wird.

Datenbank-Objekt

Für Flask-SQLAlchemy und andere Erweiterungen wird eine andere Methode vorgeschlagen. Hier behalten wir das Objekt SQLAlchemy außerhalb des App-Objekts. Während der Erstellung der Anwendung rufen wir init_app(app) auf, um das SQLAlchemy -Objekt zu konfigurieren und vorzubereiten.

from flask_sqlalchemy import  SQLAlchemy
db =  SQLAlchemy()


def  create_app(config_name):

    app =  Flask()
    ...
    # set up
    db.init_app(app)
    ...

    return app

Dann können wir dies in einer blueprint Ansichtsfunktion verwenden, etwa so:

from my_app import db

def list_users:
    users = db.session.query(User).all()

Im Objekt SQLAlchemy ist sicherlich etwas Magie im Spiel. Aber im Gegensatz zum Logger befindet sich hier das Datenbankobjekt außerhalb des App-Objekts.

Frage: Ist das Application Context schreibgeschützt?

Kurze Antwort: abhängig.

Während einer Anfrage können Sie diese anhängen und lesen. Aber beim nächsten Mal ist alles weg.

Frage: Ist der Application Context für jeden Thread eindeutig?

Kurze Antwort: Ja.

Aus der Dokumentation:

Wenn eine Flask -Anwendung mit der Bearbeitung einer Anforderung beginnt, schiebt sie einen Anforderungskontext, der auch einen Die Application Context schiebt.

Bedeutet dies, dass sich eine Verwechslung mit Application Context unter Verwendung von current_app auf andere Threads auswirken kann? Das ist mir immer noch nicht klar.

Was ist zu tun?

Ich habe mir eine Liste von Regeln über die Application Context gemacht:

  1. Speichern Sie niemals Daten/Objekte in Application Context während der Zeit der Anwendungserstellung.
  2. Hängen Sie während einer Anfrage niemals Daten/Objekte an die Application Context an.
  3. Halten Sie globale Objekte außerhalb des Application Context
  4. Verwenden Sie g anstelle von current_app , um Daten/Objekte anzuhängen, die während einer Anfrage verwendet werden sollen.
  5. Verwenden Sie nur die Anwendung in create_app(), aber in den in create_app() angehängten Ereignisbehandlern immer current_app
  6. Überall sonst verwenden Sie current_app

Das bedeutet z.B., wenn Sie Daten haben, die für alle Anfragen global sind und nicht in der Flask -Konfiguration enthalten sind, sollten Sie diese Daten z.B. irgendwo im Dateisystem oder in einem externen Cache haben, und dann müssen Sie diese Daten jedes Mal, zu Beginn einer Anfrage, laden und an das g-Objekt anhängen. Der erste Moment, in dem Sie dies tun können, ist im @before_request -Handler. So ist @before_request der Ort, an dem Sie Ihre Daten und andere Dinge wie eine Datenbanksitzung anhängen können, die bei allen Anfragen verfügbar sein müssen. Das bedeutet, dass Sie im Ereignisbehandler @app.url_value_preprocessor keinen Zugriff auf die Datenbank haben, aber das ist keine große Sache.

Zusammenfassung

Unter anderem suchte ich nach einer Möglichkeit, Daten, z.B. die Anwendungskonfiguration, im Application Context zu speichern und diese dann irgendwo zu modifizieren, so dass die Änderungen für alle Threads verfügbar sind. Ich dachte, dass dies möglich wäre, aber wir können nicht auf die inital Application Context zugreifen, sobald die Anwendung läuft. Ich wurde durch den DEBUG-Modus ausgetrickst. Dieser Text wurde von einer Person, mir, geschrieben, die ihren ersten Flask -Antrag geschrieben hat, so dass einige Aussagen möglicherweise nicht korrekt sind. Ich hoffe, dass ich eines Tages die Application Context besser verstehen werde. Bis zu diesem Tag werde ich den Application Context sehr konservativ verwenden.

Links / Impressum

Demystifying Flask’s Application Context
https://hackingandslacking.com/demystifying-flasks-application-context-c7bd31a53817

Documention for "The Application Context" is confusing #1151
https://github.com/pallets/flask/issues/1151

Making an object persist between requests and available to the whole app
https://www.reddit.com/r/flask/comments/9kenzp/making_an_object_persist_between_requests_and/

number of threads in flask
https://stackoverflow.com/questions/59320537/number-of-threads-in-flask

The Application Context
https://flask.palletsprojects.com/en/1.1.x/appcontext/

Understand Flask (---)
http://songroger.win/understand-flask-s1/

What is the purpose of Flask's context stacks?
https://stackoverflow.com/questions/20036520/what-is-the-purpose-of-flasks-context-stacks

Mehr erfahren

Flask

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.