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

De mysterieuze Flask Application Context, mijn vragen en antwoorden

De applicatiecontext wordt geïnitialiseerd, geduwd en geknald, voor een eerste keer kunnen gebruikers dingen verwarrend zijn. Tijd om te gaan lezen.

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

Als je begint met Flask lees je wat over de Application Context, TL;DR. Ik weet niet hoe het met u zit, maar ik heb het zeker niet helemaal begrepen. Wat is app, wat is current_app, wat is het verschil, je begint gewoon met het programmeren van je applicatie. De hele tijd is er op de achtergrond deze rare buzz: wat is de Application Context precies ... Op een bepaald moment wanneer u een klasse gebruikt die u in uw create_app() fabriek hebt geïnstalleerd, komt u op deze fout terecht:

RuntimeError: working outside of application context

WTF! Dan begin je te lezen, maar niet te veel natuurlijk, TL;DR weer. Oké, de oplossing is om er een 'met' voor te zetten en je bent goed:

with app_context(app):
    ...

Het wordt vervelend

Maar langzaam wordt het steeds vervelender. Ik installeer bijvoorbeeld een object tijdens het maken van een app in create_app(). Dit object heeft methoden met loggerafschriften zoals:

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

Maar als je deze methode van een Blueprint afroept, krijg ik een foutmelding. Nu moet ik de loggerverklaring gebruiken met current_app:

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

Het is verwarrend

Maar dit kan ook komen omdat ik niet zo'n ervaren Python programmeur ben. In create_app() bevestig ik de logger aan het app-object:

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

maar in dezelfde create_app() voegen we ook gebeurtenissen als @before_request toe. In @before_request kunnen we beide gebruiken:

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

en

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

Waarom? Ik kan begrijpen dat beide werken, maar zullen beide hetzelfde resultaat opleveren? In @before_request is de current_app (waarschijnlijk) de geduwde Application Context. Maar is dit echt waar? Ik was ook op zoek naar een manier om gegevens op te slaan in de Application Context. De documentatie zegt:

'Opslag van gegevens: De Application Context is een goede plaats om veelvoorkomende gegevens op te slaan tijdens een verzoek of CLI-opdracht. Flask levert hiervoor het g-object. Het is een eenvoudig naamruimte-object dat dezelfde levensduur heeft als een Application Context.

Merk op dat er staat: DURENDE een verzoek. Er staat ook dat je het g-object moet gebruiken. Hoe dan ook, tijd om te beginnen met lezen en wat tests te doen. Ik probeerde een aantal vragen te beantwoorden die ik had en dacht ze op te schrijven zodat ik ze nooit kan vergeten. Hopelijk zijn de antwoorden juist ... :-( Trouwens, in mijn applicatie gebruik ik het fabriekspatroon:

def  create_app(config_name):

    ...

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

    return app

Vraag: wordt het geduwde Application Context verzoek na een verzoek vernietigd?

Kort antwoord: Ja.

Hoe meer je erover leest, hoe verwarrender het wordt. Bijvoorbeeld uit de Flask documentatie:

De Application Context wordt aangemaakt en indien nodig vernietigd. Wanneer een Flask applicatie een aanvraag begint te behandelen, duwt deze een Application Context en een aanvraagcontext. Wanneer het verzoek eindigt, verschijnt de aanvraagcontext en vervolgens de Application Context. Normaal gesproken heeft een Application Context dezelfde levensduur als een aanvraag.

Laten we eens kijken naar deze zin: 'De Application Context wordt gemaakt en vernietigd als dat nodig is'. Wat betekent dit 'noodzakelijk' precies?

In de documentatie staat dat de Application Context na een verzoek wordt vernietigd, maar dit is niet waar, of wel? Om dit te controleren heb ik een variabele toegevoegd aan de geduwde Application Context in een of andere view-functie, bijvoorbeeld:

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

Op het eerste verzoek staat SOME_PARAM_KEY niet in de configuratie, we printen dit en stellen het vervolgens in. De volgende requests laten zien dat SOME_PARAM_KEY in de config zit en de config heeft de juiste waarde voor SOME_PARAM_KEY. Dit betekent dat de Application Context niet wordt vernietigd, maar dat in plaats daarvan alleen een verwijzing naar de Application Context wordt gewijzigd.

Hier is wat er mis is: het bovenstaande is gedaan met Flask in de DEBUG-modus. Als u precies dezelfde test doet in de PRODUCTIEmodus, vindt u NOOIT SOME_PARAM_KEY in current_app.config bij de volgende aanvraag. Wees dus gewaarschuwd in welke staat u Flask gebruikt. In de DEBUG-modus is het aan dat Flask de Application Context opslaat, uiteraard voor het debuggen.

Vraag: kunt u een database-object op het moment van aanmaken van de app toevoegen en het later in uw Blueprints openen?

Kort antwoord: nee.

Met toegang tot het later ik bedoel: op latere verzoeken. Je moet heel voorzichtig zijn met wat je aan de Application Context vastmaakt bij het aanmaken van de app. Als u een SQLAlchemy database-object toevoegt bij het aanmaken van de app, bijv.

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

Vervolgens kunt u tijdens de eerste aanvraag toegang krijgen tot de talen in een Blueprint zoals:

    for language in  current_app.languages:
        ...

Maar vergeet dan niet dat de sessie wordt vernietigd (verwijderen) op het moment van de tranen:

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

Bij een volgende aanvraag krijgt u een foutmelding als u probeert toegang te krijgen tot zo'n object:

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

Te verwachten, maar ik raakte het.

Vraag: betekent 'knalt de Application Context' dat de Application Context nog steeds beschikbaar is?

Kort antwoord: nee.

Uit dezelfde tekst als hierboven: 'Als het verzoek eindigt, verschijnt de context van het verzoek en vervolgens de Application Context'. Dus hier hebben we het over pop. Dit kan twee dingen betekenen:

  1. De Application Context wordt in de blauwe lucht geduwd.
  2. Als ze het over de Application Context hebben, bedoelen ze in feite een verwijzing naar de Application Context

Waarom is dit zo belangrijk? Want in het eerste geval betekent het dat de Application Context alleen-lezen is, en in het tweede geval wordt de Application Context tussen de verzoeken door niet vernietigd. Zie ook het antwoord op een vorige vraag.

Vraag: moet je een globaal object buiten het app-object houden?

Kort antwoord: hangt af.

Loggerobject

Laten we eerst eens kijken naar de houtkap. Hier bevestigen we de logger aan het app-object.

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

En dan kunnen we in de Blueprints de logger als volgt gebruiken:

from flask import  current_app

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

De logger wordt tijdens het maken van de app geïnitialiseerd en aan het app-object bevestigd en werkt prima bij gebruik in de Blueprint-weergavefuncties.

Databaseobject

Voor Flask-SQLAlchemy en andere uitbreidingen wordt een andere methode voorgesteld. Hier houden we het SQLAlchemy object buiten het app-object. Tijdens het maken van een app roepen we init_app(app) aan om het SQLAlchemy object te configureren en voor te bereiden.

from flask_sqlalchemy import  SQLAlchemy
db =  SQLAlchemy()


def  create_app(config_name):

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

    return app

Dan kunnen we dit gebruiken in een blueprint weergavefunctie, zoiets als:

from my_app import db

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

Er is zeker wat magie in het SQLAlchemy object. Maar hier, in tegenstelling tot de logger, staat het database-object buiten het app-object.

Vraag: is de Application Context alleen-lezen?

Kort antwoord: hangt af.

Tijdens een aanvraag kunt u deze bijvoegen, lees deze. Maar de volgende keer is het allemaal weg.

Vraag: is de Application Context uniek voor elke draad?

Kort antwoord: ja.

Uit de documentatie:

Wanneer een Flask applicatie een aanvraag begint te behandelen, duwt deze een aanvraagcontext, die ook een De Application Context duwt.

Betekent dit dat het knoeien met de Application Context, met behulp van current_app, andere draden kan beïnvloeden? Dit is mij nog steeds niet duidelijk.

Wat moet ik doen?

Ik heb voor mezelf een lijst met regels gemaakt over de Application Context:

  1. Sla nooit gegevens / objecten op in de Application Context tijdens de aanmaaktijd van de app.
  2. Bevestig nooit gegevens / objecten aan de Application Context tijdens een aanvraag.
  3. Globale objecten buiten de Application Context houden
  4. Gebruik g in plaats van current_app om gegevens / objecten aan te sluiten die tijdens een aanvraag moeten worden gebruikt.
  5. Gebruik alleen app in create_app(), maar in event handlers in create_app() altijd current_app gebruiken.
  6. Overal anders gebruikt u current_app

Dit betekent bijvoorbeeld dat als u gegevens heeft die globaal zijn voor alle aanvragen en deze niet in de Flask configuratie staan, u deze gegevens bijv. ergens op het bestandssysteem of in een externe cache moet hebben, en dat u dan elke keer, bij het begin van een aanvraag, deze gegevens moet laden en aan het g-object moet hechten. Het eerste moment waarop dit kan is in de @before_request handler. Dus @before_request is de plaats waar u uw gegevens en andere zaken zoals een databasesessie, die bij alle aanvragen beschikbaar moeten zijn, kunt toevoegen. Dit betekent dat u geen toegang heeft tot de database in de event handler @app.url_value_preprocessor, maar dit is geen groot probleem.

Samenvatting

Ik was onder andere op zoek naar een manier om gegevens op te slaan, bijvoorbeeld applicatieconfiguratie, in de Application Context en deze vervolgens ergens te wijzigen, zodat de wijzigingen beschikbaar zijn voor alle threads. Ik dacht dat dit kon worden gedaan, maar we kunnen geen toegang krijgen tot de inital Application Context als de app eenmaal draait. Ik werd misleid door de DEBUG-modus. Deze tekst is geschreven door een persoon, ik, die zijn eerste Flask applicatie schrijft, dus sommige verklaringen kunnen niet correct zijn. Op een dag hoop ik de Application Context beter te begrijpen. Tot die dag zal ik de Application Context op een zeer conservatieve manier gebruiken.

Links / credits

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

Lees meer

Flask

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.