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

Le mystérieux Flask Application Context, mes questions et réponses

Le contexte de l'application est initialisé, poussé et explosé, pour une première fois les choses peuvent être confuses pour l'utilisateur. Il est temps de commencer à lire.

30 janvier 2020
Dans Flask
post main image
https://unsplash.com/@donovan_valdivia

Lorsque vous commencez avec Flask , vous lisez un peu sur le Application Context, TL;DR. Je ne sais rien de vous, mais je n'ai certainement pas bien compris. Qu'est-ce que l'application, qu'est-ce que current_app, quelle est la différence, vous commencez juste à programmer votre application. Il y a toujours ce buzz bizarre en arrière-plan : qu'est-ce exactement que le Application Context ... Puis, à un certain moment, en utilisant une classe que vous avez instanciée dans votre usine create_app(), vous avez rencontré cette erreur :

RuntimeError: working outside of application context

WTF ! Puis vous commencez à lire, mais pas trop bien sûr, TL;DR à nouveau. Ok, la solution est juste de mettre un "avec" devant et c'est bon :

with app_context(app):
    ...

Cela devient ennuyeux

Mais peu à peu, cela devient de plus en plus ennuyeux. Par exemple, j'instancie un objet lors de la création d'une application dans create_app(). Cet objet a des méthodes avec des déclarations d'enregistreur comme :

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

Mais si j'appelle cette méthode depuis un Blueprint, je reçois un message d'erreur. Je dois maintenant utiliser l'instruction logger avec current_app :

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

C'est déroutant

Mais c'est peut-être aussi parce que je ne suis pas un programmeur très expérimenté Python Flask . Dans create_app(), j'attache le logger à l'objet app :

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

mais dans le même create_app() nous joignons également des événements comme @before_request. Dans @before_request , nous pouvons utiliser les deux :

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

et

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

Pourquoi ? Je peux comprendre que les deux fonctionnent, mais les deux produiront-ils le même résultat ? Dans @before_request , le current_app est (probablement) le Application Context poussé. Mais est-ce vraiment vrai ? Je cherchais également un moyen de stocker des données dans le Application Context. La documentation le dit :

Stockage des données : Le Application Context est un bon endroit pour stocker des données communes pendant une requête ou une commande CLI. Flask fournit l'objet g à cette fin. Il s'agit d'un simple objet de l'espace de noms qui a la même durée de vie qu'un Application Context".

Notez que c'est écrit : PENDANT une demande. Il est également indiqué d'utiliser l'objet g. Quoi qu'il en soit, il est temps de commencer à lire et de faire quelques tests. J'ai essayé de répondre à un certain nombre de questions que j'avais et j'ai pensé à les écrire pour ne jamais les oublier. J'espère que les réponses sont correctes ... :-( Au fait, dans ma demande, j'utilise le modèle d'usine :

def  create_app(config_name):

    ...

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

    return app

Question : la demande Application Context poussée est-elle détruite après une demande ?

Réponse courte : Oui.

Plus vous lisez à ce sujet, plus cela devient confus. Par exemple, à partir de la documentation Flask :

Le Application Context est créé et détruit si nécessaire. Lorsqu'une demande Flask commence à traiter une requête, elle pousse un Application Context et un contexte de requête. Lorsque la requête se termine, le contexte de la requête apparaît, puis le Application Context. En général, un Application Context aura la même durée de vie qu'une demande".

Regardons cette phrase : "Le Application Context est créé et détruit si nécessaire. Que signifie exactement cette "nécessité" ?

La documentation indique que le Application Context est détruit après une demande, mais ce n'est pas vrai, ou l'est-il ? Pour vérifier cela, j'ai ajouté une variable à la fonction Application Context poussée dans une certaine vue, par exemple :

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

À la première demande, SOME_PARAM_KEY n'est pas dans la configuration, nous l'imprimons et nous le paramétrons. Les demandes suivantes montrent que SOME_PARAM_KEY est dans la configuration et que la configuration contient la valeur correcte pour SOME_PARAM_KEY. Cela signifie que le Application Context n'est pas détruit mais qu'au contraire, il semble que seule une référence au Application Context soit modifiée.

Voici ce qui ne va pas : ce qui précède a été fait avec Flask en mode DEBUG. Si vous effectuez exactement le même test en mode PRODUCTION, vous ne trouverez JAMAIS QUELQUES_CLÉS_PARAMÈTRES dans le fichier current_app.config à la prochaine demande. Soyez donc averti de l'état dans lequel vous utilisez Flask. En mode DEBUG, il apparaît que Flask sauvegarde le Application Context, évidemment pour le débogage.

Question : pouvez-vous joindre un objet de la base de données au moment de la création de l'application et y accéder plus tard dans vos plans ?

Réponse brève : non.

Avec l'accès, je veux dire plus tard : sur des demandes ultérieures. Vous devez faire très attention à ce que vous attachez au Application Context au moment de la création de l'application. Si vous joignez un objet de la base de données SQLAlchemy au moment de la création de l'application, par exemple

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

Ensuite, lors de la première demande, vous pourrez accéder aux langues dans un Blueprint comme :

    for language in  current_app.languages:
        ...

Mais n'oubliez pas que la session est détruite (enlevée) au moment du démontage :

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

Lors d'une prochaine demande, si vous essayez d'accéder à un tel objet, vous obtenez un message d'erreur :

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

C'était prévisible, mais j'ai réussi.

Question : est-ce que "pops the Application Context" signifie que le Application Context est toujours disponible ?

Réponse brève : non.

Du même texte que ci-dessus : "Lorsque la demande se termine, le contexte de la demande apparaît, puis le Application Context. Nous parlons donc ici de pop. Cela peut signifier deux choses :

  1. Le Application Context est plongé dans le ciel bleu
  2. Lorsqu'ils parlent du Application Context, ils signifient en fait un pointeur vers le Application Context

Pourquoi est-ce si important ? Car dans le premier cas, cela signifie que le Application Context est en lecture seule, et dans le second cas, le Application Context entre les demandes n'est pas détruit. Voir également la réponse à une question précédente.

Question : faut-il garder un objet global en dehors de l'objet applicatif ?

Réponse brève : cela dépend.

Objet Logger

Examinons d'abord l'exploitation forestière. Ici, nous attachons le bûcheron à l'objet applicatif.

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

Et puis dans les Blueprints, nous pouvons utiliser le logger comme suit :

from flask import  current_app

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

Le logger est initialisé lors de la création de l'application et attaché à l'objet de l'application. Il fonctionne bien lorsqu'il est utilisé dans les fonctions d'affichage du plan.

Objet de la base de données

Pour Flask-SQLAlchemy et d'autres extensions, une autre méthode est proposée. Ici, nous gardons l'objet SQLAlchemy en dehors de l'objet app. Lors de la création de l'application, nous appelons init_app(app) pour configurer et préparer l'objet SQLAlchemy .

from flask_sqlalchemy import  SQLAlchemy
db =  SQLAlchemy()


def  create_app(config_name):

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

    return app

Nous pouvons ensuite l'utiliser dans une fonction d'affichage blueprint , quelque chose comme ça :

from my_app import db

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

Il y a certainement de la magie dans l'objet SQLAlchemy . Mais ici, contrairement à l'enregistreur, l'objet de la base de données est en dehors de l'objet de l'application.

Question : le Application Context est-il en lecture seule ?

Réponse brève : cela dépend.

Lors d'une demande, vous pouvez la joindre, la lire. Mais la prochaine fois, tout cela aura disparu.

Question : le Application Context est-il unique pour chaque fil ?

Réponse courte : oui.

D'après la documentation :

Lorsqu'une application Flask commence à traiter une demande, elle pousse un contexte de demande, qui pousse également une application Application Context.

Cela signifie-t-il que le fait de jouer avec le Application Context, en utilisant le current_app, peut affecter d'autres fils ? Cela n'est pas encore clair pour moi.

Que faire ?

Je me suis fait une liste de règles concernant le Application Context :

  1. Ne jamais stocker de données / objets dans le Application Context pendant la création de l'application
  2. Ne jamais attacher de données / objets au Application Context lors d'une demande
  3. Maintenir les objets globaux en dehors du Application Context
  4. Utilisez g au lieu de current_app pour joindre les données / objets à utiliser lors d'une demande
  5. Utilisez uniquement l'application dans create_app(), mais dans les gestionnaires d'événements rattachés à create_app(), utilisez toujours current_app
  6. Partout ailleurs, utilisez current_app

Cela signifie par exemple que si vous disposez de données globales pour toutes les requêtes et qu'elles ne figurent pas dans la configuration Flask , vous devez disposer de ces données, par exemple quelque part sur le système de fichiers ou dans un cache externe, puis chaque fois, au début d'une requête, vous devez charger ces données et les joindre à l'objet g. Le premier moment où vous pouvez le faire est dans le gestionnaire @before_request . Ainsi, @before_request est l'endroit où joindre vos données et d'autres choses comme une session de base de données, qui doit être disponible pendant toutes les demandes. Cela signifie que vous n'avez pas accès à la base de données dans le gestionnaire d'événements @app.url_value_preprocessor mais ce n'est pas un gros problème.

Résumé

Entre autres choses, je cherchais un moyen de stocker des données, par exemple la configuration de l'application, dans le Application Context , puis de les modifier quelque part, en rendant les changements disponibles à tous les threads. Je pensais que cela pouvait être fait, mais nous ne pouvons pas accéder à l'initiale Application Context une fois que l'application est lancée. J'ai été piégé par le mode DEBUG. Ce texte a été rédigé par une personne, moi, qui a écrit sa première demande Flask , de sorte que certaines déclarations peuvent ne pas être correctes. Un jour, j'espère mieux comprendre le Application Context . Jusqu'à ce jour, j'utiliserai le Application Context de manière très conservatrice.

Liens / crédits

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

En savoir plus...

Flask

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.