Kiedy należy używać Flask.g?

173

I saw że gruszy z kontekstu żądanie do aplikacji w kontekście Kolby 0,10, co czyniło mnie mylić o zamierzonym wykorzystaniem g.

W moim rozumieniu (dla Flask 0.9):

  • g żyje w kontekście żądania, tj. jest tworzony od nowa, gdy żądanie zaczyna się i jest dostępny aż do jego zakończenia
  • gma być używany jako "tablica żądań", na której mogę umieścić rzeczy istotne na czas trwania żądania (tj. ustawić flagę na początku żądania i obsłużyć na końcu, prawdopodobnie z before_request/ after_requestpair)
  • oprócz utrzymywania stanu poziomu żądania, gmoże i powinien być używany do zarządzania zasobami, tj. utrzymywania połączeń z bazą danych itp.

Które z tych zdań nie jest już prawdziwe w Flask 0.10? Czy ktoś może wskazać mi źródło omawiające przyczyny zmiany? Czego powinienem używać jako „tablicy żądań” w Flask 0.10 - czy powinienem utworzyć własne lokalne proxy dla wątku specyficzne dla aplikacji / rozszerzenia i wypchnąć je na stos kontekstów before_request? Jaki jest sens zarządzania zasobami w kontekście aplikacji, jeśli moja aplikacja istnieje przez długi czas (nie jak żądanie), a zatem zasoby nigdy nie są zwalniane?

Yaniv Aknin
źródło
Zgadzam się, to dość dziwna zmiana. Miejmy nadzieję, że mitsuhiko zaimplementuje jakiś obiekt kontekstu żądania do zastąpienia gw wersji 0.10, w przeciwnym razie wygląda na to, że w dużej części kodu pojawią się przebiegłe błędy.
Anorov
11
FWIW, Armin Ronacher (autor Flask) wydał sequel „Advanced Flask Patterns”, który pokazuje przykładowy kod, jak używać nowego flask.g. speakerdeck.com/mitsuhiko/advanced-flask-patterns-1
Markus Unterwaditzer
1
również nowy kontekst żądania implikuje nowy kontekst aplikacji, więc powinien po prostu działać dobrze przy normalnym użytkowaniu
Ronny

Odpowiedzi:

119

Advanced Flask Patterns , pod linkiem Markusa , wyjaśnia niektóre zmiany gw wersji 0.10:

  • g teraz żyje w kontekście aplikacji.
  • Każde żądanie wypycha nowy kontekst aplikacji , wymazując stary, więc gnadal można go używać do ustawiania flag na żądanie bez zmiany kodu.
  • Kontekst aplikacji jest otwierany po teardown_request wywołaniu. (Prezentacja Armina wyjaśnia, że ​​dzieje się tak, ponieważ tworzenie połączeń DB to zadania, które konfigurują środowisko dla żądania i nie powinny być obsługiwane wewnątrz before_requesti after_request)
theY4Kman
źródło
Kiedy w kodzie źródłowym, do którego app_ctx is None or app_ctx.app != self.apputworzyłeś łącze, jest wartość False, stary kontekst aplikacji wydaje się być ponownie używany? Wydaje się, że to nie w porządku, ponieważ kontekst aplikacji „nie będzie współdzielony między żądaniami” ...
nalzok
2
Czy masz na myśli popychanieapp.app_context() ? Jeśli tak, należy zauważyć, że przy app_context()każdym wywołaniu tworzy nowy kontekst aplikacji - nigdy nie używa kontekstu ponownie.
theY4Kman
1
Tak to prawda, ale kiedy app_ctx is not None and app_ctx.app == self.appThe app_ctx = self.app.app_context()linia jest nie wykonywane; self._implicit_app_ctx_stack.append(None)jest wykonywany tylko w tym przypadku.
nalzok
1
Och, przepraszam, źle przeczytałem! W środowisku produkcyjnym jest tylko jedno żądanie obsługiwane na wątek (lub greenlet). Tylko jeden RequestContextjest pchany, więc tylko jeden AppContextjest pchany. Ale jeśli tryb debugowania jest włączony i żądanie nie powiedzie się, Flask zapisuje kontekst , więc można go używać z debugerem . Nonejest dołączany do elementu _app_ctx_stack, więc gdy żądanie jest usuwane, wie, że nie ma jeszcze wyskakiwać AppContext. To samo dzieje się z klientem testowym, który zachowuje kontekst, więc można go sprawdzić.
theY4Kman
Zatem zakres g dotyczy żądania (wątku) i nie zachowa wartości w kolejnym żądaniu.
zmienna
83

Jako dodatek do informacji w tym wątku: byłem trochę zdezorientowany zachowaniem flask.g, ale kilka szybkich testów pomogło mi to wyjaśnić. Oto, co wypróbowałem:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in first request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to xyz')
        g.foo = 'xyz'
        print('g.foo should be xyz, is: {0}'.format(g.foo))

    print('in app context, after first request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

    with app.test_request_context():
        print('in second request context')
        print('g.foo should be abc, is: {0}'.format(g.foo))
        print('setting g.foo to pqr')
        g.foo = 'pqr'
        print('g.foo should be pqr, is: {0}'.format(g.foo))

    print('in app context, after second request context')
    print('g.foo should be abc, is: {0}'.format(g.foo))

A oto wynik, który daje:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be abc, is: abc
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in app context, after first request context
g.foo should be abc, is: xyz  

in second request context
g.foo should be abc, is: xyz
setting g.foo to pqr
g.foo should be pqr, is: pqr  

in app context, after second request context
g.foo should be abc, is: pqr

Jak powiedział wcześniej theY4Kman, „Każde żądanie przesyła nowy kontekst aplikacji”. I jak docs kolb powiedzieć , kontekst aplikacji „nie będą dzielone pomiędzy żądaniami”. To, co nie zostało wyraźnie powiedziane (chociaż wydaje mi się, że wynika to z tych stwierdzeń) i co jasno pokazuje moje testy, to fakt, że nigdy nie należy jawnie tworzyć wielu kontekstów żądań zagnieżdżonych w jednym kontekście aplikacji, ponieważ flask.g(i co) tego nie robi. nie ma żadnej magii, dzięki której działa na dwóch różnych „poziomach” kontekstu, z różnymi stanami istniejącymi niezależnie na poziomach aplikacji i żądania.

W rzeczywistości „kontekst aplikacji” jest potencjalnie dość mylącą nazwą, ponieważ app.app_context() jest to kontekst na żądanie , dokładnie taki sam jak „kontekst żądania” . Potraktuj to jako „kontekst żądania lite”, wymagany tylko w przypadku, gdy potrzebujesz niektórych zmiennych, które normalnie wymagają kontekstu żądania, ale nie potrzebujesz dostępu do żadnego obiektu żądania (np. Podczas wykonywania operacji wsadowych baz danych w Skrypt powłoki). Jeśli spróbujesz rozszerzyć kontekst aplikacji, aby obejmował więcej niż jeden kontekst żądania, prosisz o kłopoty. Więc zamiast mojego testu powyżej, powinieneś zamiast tego napisać taki kod z kontekstami Flaska:

from flask import Flask, g
app = Flask(__name__)

with app.app_context():
    print('in app context, before first request context')
    print('setting g.foo to abc')
    g.foo = 'abc'
    print('g.foo should be abc, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in first request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to xyz')
    g.foo = 'xyz'
    print('g.foo should be xyz, is: {0}'.format(g.foo))

with app.test_request_context():
    print('in second request context')
    print('g.foo should be None, is: {0}'.format(g.get('foo')))
    print('setting g.foo to pqr')
    g.foo = 'pqr'
    print('g.foo should be pqr, is: {0}'.format(g.foo))

Co da oczekiwane rezultaty:

in app context, before first request context
setting g.foo to abc
g.foo should be abc, is: abc  

in first request context
g.foo should be None, is: None
setting g.foo to xyz
g.foo should be xyz, is: xyz  

in second request context
g.foo should be None, is: None
setting g.foo to pqr
g.foo should be pqr, is: pqr
Jaza
źródło
7
Głosowano za głosem z powodu ostatniego akapitu, konteksty flask są dość zagmatwane, aby na początku je zrozumieć. Z nazwy można wywnioskować, że kontekst żądania dotyczy żądania i że kontekst aplikacji istnieje nawet po żądaniu lub nie ma na niego wpływu jego czas życia.
simanacci