Od jakiegoś czasu korzystam z kontekstu żądania / aplikacji, nie w pełni rozumiejąc, jak to działa i dlaczego zostało zaprojektowane tak, jak było. Jaki jest cel „stosu”, jeśli chodzi o żądanie lub kontekst aplikacji? Czy są to dwa oddzielne stosy, czy też oba są częścią jednego stosu? Czy kontekst żądania jest wypychany na stos, czy też sam jest stosem? Czy mogę przesuwać / przesuwać wiele kontekstów jeden na drugim? Jeśli tak, dlaczego miałbym to robić?
Przepraszam za wszystkie pytania, ale nadal jestem zdezorientowany po przeczytaniu dokumentacji dotyczącej kontekstu żądania i kontekstu aplikacji.
Odpowiedzi:
Wiele aplikacji
Kontekst aplikacji (i jej cel) jest rzeczywiście mylący, dopóki nie zdasz sobie sprawy, że Flask może mieć wiele aplikacji. Wyobraź sobie sytuację, w której chcesz, aby jeden interpreter WSGI Python uruchamiał wiele aplikacji Flask. Nie mówimy tutaj o schematach, mówimy o zupełnie różnych aplikacjach Flask.
Możesz ustawić to podobnie do sekcji dokumentacji Flask w przykładzie „Wysyłanie aplikacji” :
Zauważ, że są tworzone dwie zupełnie różne aplikacje Flask „frontend” i „backend”. Innymi słowy,
Flask(...)
konstruktor aplikacji został wywołany dwukrotnie, tworząc dwie instancje aplikacji Flask.Konteksty
Kiedy pracujesz z Flaskiem, często używasz zmiennych globalnych, aby uzyskać dostęp do różnych funkcji. Na przykład prawdopodobnie masz kod, który czyta ...
Następnie podczas przeglądania możesz użyć,
request
aby uzyskać dostęp do informacji o bieżącym żądaniu. Oczywiścierequest
nie jest to normalna zmienna globalna; w rzeczywistości jest to kontekstowa wartość lokalna . Innymi słowy, za kulisami kryje się jakaś magia, która mówi: „kiedy dzwonięrequest.path
, pobierzpath
atrybut zrequest
obiektu żądania CURRENT”. Dwa różne żądania będą miały różne wyniki dlarequest.path
.W rzeczywistości, nawet jeśli uruchomisz Flask z wieloma wątkami, Flask jest wystarczająco inteligentny, aby izolować obiekty żądań. W ten sposób staje się możliwe, że dwa wątki, każdy obsługujący inne żądanie, jednocześnie wywołują
request.path
i uzyskują prawidłowe informacje dla swoich żądań.Łącząc to razem
Widzieliśmy już, że Flask może obsługiwać wiele aplikacji w tym samym interprecie, a także że ze względu na sposób, w jaki Flask pozwala na używanie globalnych "kontekstowo lokalnych", musi istnieć jakiś mechanizm określający, jakie jest "bieżące" żądanie ( w celu robienia rzeczy takich jak
request.path
).Łącząc te pomysły razem, powinno również mieć sens, że Flask musi mieć jakiś sposób na określenie, jaka jest „bieżąca” aplikacja!
Prawdopodobnie masz również kod podobny do następującego:
Podobnie jak w naszym
request
przykładzie,url_for
funkcja ma logikę zależną od bieżącego środowiska. W tym przypadku jednak widać wyraźnie, że logika w dużym stopniu zależy od tego, która aplikacja jest uważana za „bieżącą” aplikację. W przykładzie frontend / backend pokazanym powyżej, zarówno aplikacje „frontend”, jak i „backend” mogą mieć trasę „/ login”, a więcurl_for('/login')
powinny zwracać coś innego w zależności od tego, czy widok obsługuje żądanie aplikacji frontend czy backend.Aby odpowiedzieć na Twoje pytania ...
Z dokumentów kontekstu żądania:
Innymi słowy, nawet jeśli na stosie „bieżących” żądań lub „bieżących” aplikacji będzie zazwyczaj 0 lub 1 elementów, możliwe, że będzie ich więcej.
Podany przykład dotyczy sytuacji, w której żądanie zwraca wyniki „wewnętrznego przekierowania”. Powiedzmy, że użytkownik żąda A, ale chcesz wrócić do użytkownika B. W większości przypadków wydajesz przekierowanie do użytkownika i kierujesz go do zasobu B, co oznacza, że użytkownik uruchomi drugie żądanie pobrania B. A Nieco innym sposobem obsługi tego byłoby wykonanie wewnętrznego przekierowania, co oznacza, że podczas przetwarzania A Flask wyśle do siebie nowe żądanie dotyczące zasobu B i użyje wyników tego drugiego żądania jako wyników pierwotnego żądania użytkownika.
Są to dwa oddzielne stosy . Jest to jednak szczegół implementacji. Co ważniejsze, nie tyle istnieje stos, ale fakt, że w każdej chwili możesz pobrać „bieżącą” aplikację lub żądanie (szczyt stosu).
„Kontekst żądania” jest jednym z elementów „stosu kontekstów żądania”. Podobnie jest z „kontekstem aplikacji” i „stosem kontekstów aplikacji”.
W aplikacji Flask zazwyczaj tego nie robisz. Przykładem tego, gdzie możesz chcieć, jest wewnętrzne przekierowanie (opisane powyżej). Jednak nawet w takim przypadku prawdopodobnie skończysz, gdy Flask zajmie się nowym żądaniem, więc Flask wykona za Ciebie wszystkie popychanie / popping.
Istnieją jednak przypadki, w których chcesz samodzielnie manipulować stosem.
Uruchamianie kodu poza żądaniem
Jednym z typowych problemów jest to, że używają rozszerzenia Flask-SQLAlchemy do konfigurowania bazy danych SQL i definicji modelu przy użyciu kodu podobnego do tego, co pokazano poniżej ...
Następnie używają wartości
app
idb
w skrypcie, który powinien zostać uruchomiony z powłoki. Na przykład skrypt „setup_tables.py” ...W takim przypadku rozszerzenie Flask-SQLAlchemy wie o
app
aplikacji, ale w trakciecreate_all()
tego wyrzuci błąd informujący o braku kontekstu aplikacji. Ten błąd jest uzasadniony; nigdy nie powiedziałeś Flaskowi, z jaką aplikacją powinien mieć do czynienia podczas uruchamianiacreate_all
metody.Możesz się zastanawiać, dlaczego nie potrzebujesz tego
with app.app_context()
wywołania, gdy uruchamiasz podobne funkcje w swoich widokach. Powodem jest to, że Flask już obsługuje zarządzanie kontekstem aplikacji za Ciebie, gdy obsługuje rzeczywiste żądania sieciowe. Problem pojawia się naprawdę tylko poza tymi funkcjami widoku (lub innymi takimi wywołaniami zwrotnymi), na przykład podczas używania modeli w jednorazowym skrypcie.Rozwiązaniem jest samodzielne wypchnięcie kontekstu aplikacji, co można zrobić, wykonując ...
Spowoduje to wypchnięcie nowego kontekstu aplikacji (używając aplikacji
app
pamiętaj, że może istnieć więcej niż jedna aplikacja).Testowanie
Innym przypadkiem, w którym chciałbyś manipulować stosem, jest testowanie. Możesz utworzyć test jednostkowy, który obsługuje żądanie i sprawdzić wyniki:
źródło
request = Local()
projekt nie byłby wystarczający dla global.py? Prawdopodobnie są przypadki użycia, o których nie myślę.Poprzednie odpowiedzi już dają ładny przegląd tego, co dzieje się w tle Flaska podczas żądania. Jeśli jeszcze tego nie czytałeś, polecam odpowiedź @ MarkHildreth przed przeczytaniem tego. Krótko mówiąc, dla każdego żądania http tworzony jest nowy kontekst (wątek), dlatego konieczne jest posiadanie funkcji wątku
Local
, która zezwala na obiekty takie jakrequest
ig
aby były dostępne globalnie we wszystkich wątkach, przy jednoczesnym zachowaniu określonego kontekstu żądań. Co więcej, podczas przetwarzania żądania http Flask może emulować dodatkowe żądania od wewnątrz, stąd konieczność przechowywania ich kontekstu na stosie. Ponadto Flask umożliwia uruchamianie wielu aplikacji wsgi w ramach jednego procesu, a więcej niż jedną można wywołać podczas żądania (każde żądanie tworzy nowy kontekst aplikacji), stąd potrzeba stosu kontekstów dla aplikacji. To podsumowanie tego, co zostało omówione w poprzednich odpowiedziach.Moim celem jest teraz uzupełnienie naszej obecnej wiedzy, wyjaśniając, w jaki sposób Flask i Werkzeug robią to, co robią z lokalnymi kontekstami. Uprościłem kod, aby lepiej zrozumieć jego logikę, ale jeśli to zrozumiesz, powinieneś być w stanie łatwo uchwycić większość tego, co jest w rzeczywistym źródle (
werkzeug.local
iflask.globals
).Najpierw zrozumiemy, jak Werkzeug implementuje Thread Locals.
Lokalny
Kiedy przychodzi żądanie http, jest przetwarzane w kontekście pojedynczego wątku. Jako alternatywny sposób tworzenia nowego kontekstu podczas żądania http, Werkzeug pozwala również na użycie zielonych wątków (rodzaj lżejszych „mikro-wątków”) zamiast zwykłych wątków. Jeśli nie masz zainstalowanych greenlets, powróci do używania wątków. Każdy z tych wątków (lub zielonych ulotek) można zidentyfikować za pomocą unikalnego identyfikatora, który można pobrać za pomocą
get_ident()
funkcji modułu . Funkcja ta jest punktem wyjścia do magii za posiadającychrequest
,current_app
,url_for
,g
, i innych tego typu obiektów globalnych kontekst związany.Teraz, gdy mamy naszą funkcję tożsamości, możemy wiedzieć, w którym wątku jesteśmy w danym momencie i możemy stworzyć tak zwany wątek
Local
, obiekt kontekstowy, do którego można uzyskać dostęp globalnie, ale kiedy uzyskujesz dostęp do jego atrybutów, rozwiązują one ich wartość dla ten konkretny wątek. na przykładObie wartości są obecne w globalnie dostępnym
Local
obiekcie w tym samym czasie, ale dostęplocal.first_name
w kontekście wątku 1 da ci'John'
, podczas gdy powróci'Debbie'
w wątku 2.Jak to możliwe? Spójrzmy na jakiś (uproszczony) kod:
Z powyższego kodu możemy zobaczyć, że magia sprowadza się do
get_ident()
tego, że identyfikuje aktualną greenlet lub wątek.Local
Przechowywania wtedy właśnie używa tego jako klucz do przechowywać żadnych danych kontekstowych do bieżącego wątku.Można mieć wiele
Local
obiektów na proces irequest
,g
,current_app
a inni mogą po prostu zostały stworzone w taki sposób. Ale nie tak to się robi w Flasku, w którym nie są to technicznieLocal
obiekty, ale dokładniejLocalProxy
obiekty. Co to jestLocalProxy
?LocalProxy
LocalProxy to obiekt, który pyta a,
Local
aby znaleźć inny interesujący obiekt (tj. Obiekt, do którego pośredniczy). Spójrzmy, aby zrozumieć:Teraz, aby utworzyć globalnie dostępne serwery proxy, należy zrobić
a teraz jakiś czas wcześniej w trakcie żądania możesz przechowywać niektóre obiekty wewnątrz lokalnego, do którego mają dostęp wcześniej utworzone proxy, bez względu na to, w którym wątku jesteśmy
Zaletą używania
LocalProxy
obiektów globalnie dostępnych, zamiast tworzenia ichLocals
samodzielnie, jest to, że upraszcza to zarządzanie nimi. Wystarczy jedenLocal
obiekt, aby utworzyć wiele globalnie dostępnych serwerów proxy. Na końcu żądania, podczas czyszczenia, po prostu zwalniasz tenLocal
(tj. Usuwasz context_id z jego pamięci) i nie przejmujesz się serwerami proxy, są one nadal dostępne globalnie i nadal odkładają na ten, ktoLocal
znajdzie ich obiekt zainteresowania dla kolejnych żądań http.Aby uprościć tworzenie,
LocalProxy
gdy już mamyLocal
, Werkzeug implementujeLocal.__call__()
magiczną metodę w następujący sposób:Jednak, jeśli spojrzeć w źródle kolbę (flask.globals), które nie jest jeszcze jak
request
,g
,current_app
isession
są tworzone. Jak ustaliliśmy, Flask może generować wiele „fałszywych” żądań (z jednego prawdziwego żądania http), a w trakcie tego procesu także przekazywać wiele kontekstów aplikacji. To nie jest typowy przypadek użycia, ale jest to zdolność platformy. Ponieważ te „współbieżne” żądania i aplikacje są nadal ograniczone do działania, a tylko jedno ma „fokus” w dowolnym momencie, sensowne jest użycie stosu dla ich odpowiedniego kontekstu. Za każdym razem, gdy pojawia się nowe żądanie lub wywoływana jest jedna z aplikacji, umieszczają kontekst na górze odpowiedniego stosu. Flask używaLocalStack
do tego obiektów. Kiedy kończą swoją działalność, wyrywają kontekst ze stosu.LocalStack
Tak
LocalStack
wygląda a (ponownie kod jest uproszczony, aby ułatwić zrozumienie jego logiki).Zauważ z powyższego, że a
LocalStack
to stos przechowywany w lokalnym, a nie zbiór lokalnych przechowywanych na stosie. Oznacza to, że chociaż stos jest dostępny globalnie, w każdym wątku jest inny stos.Kolba nie posiada
request
,current_app
,g
, isession
obiektów bezpośrednio do rozstrzyganiaLocalStack
, to raczej wykorzystujeLocalProxy
obiekty, które zawinąć funkcja wyszukiwania (zamiastLocal
obiektu), która znajdzie bazowego obiektu zLocalStack
:Wszystkie te są deklarowane podczas uruchamiania aplikacji, ale w rzeczywistości nie są rozwiązywane do niczego, dopóki kontekst żądania lub kontekst aplikacji nie zostanie przesłany do odpowiedniego stosu.
Jeśli jesteś ciekawy, w jaki sposób kontekst jest faktycznie wstawiany do stosu (a następnie wyskakiwany), spójrz, w
flask.app.Flask.wsgi_app()
którym miejscu znajduje się punkt wejścia aplikacji wsgi (tj. Co wywołuje serwer sieciowy i przekazuje środowisko http do request przychodzi) i śledź tworzenieRequestContext
obiektu przez wszystkie jego kolejnepush()
etapy_request_ctx_stack
. Po umieszczeniu na szczycie stosu jest dostępny za pośrednictwem_request_ctx_stack.top
. Oto skrócony kod, aby zademonstrować przepływ:Więc uruchamiasz aplikację i udostępniasz ją na serwerze WSGI ...
Później przychodzi żądanie http i serwer WSGI wywołuje aplikację ze zwykłymi parametrami ...
Tak z grubsza dzieje się w aplikacji ...
i tak z grubsza dzieje się z RequestContext ...
Powiedzmy, że żądanie zostało zainicjowane, wyszukiwanie
request.path
z jednej z funkcji widoku wyglądałoby zatem następująco:LocalProxy
obiektu dostępnego globalnierequest
._find_request()
(funkcję, którą zarejestrował jako swojąself.local
).LocalStack
obiektu_request_ctx_stack
o najwyższy kontekst na stosie.LocalStack
obiekt najpierw wysyła zapytanie do swojego wewnętrznegoLocal
atrybutu (self.local
) ostack
właściwość, która była tam wcześniej przechowywana.stack
tego uzyskuje główny konteksttop.request
tym samym jest rozpatrywany jako podstawowy przedmiot zainteresowania.path
atrybutTak jak widzieliśmy
Local
,LocalProxy
iLocalStack
pracę, teraz pomyśleć przez chwilę o konsekwencjach i niuansów przy pobieraniupath
z:request
obiekt, który byłby prosty globalnie dostępny obiekt.request
obiekt, który byłby lokalny.request
przedmiot przechowywany jako atrybut miejscowy.request
obiekt, który jest pełnomocnikiem do obiektu przechowywane w lokalnej.request
obiekt przechowywane na stosie, który z kolei jest przechowywany w lokalnym.request
obiekt, który jest pełnomocnikiem do obiektu na stosie przechowywane w lokalnej. <- to właśnie robi Flask.źródło
Local
,LocalStack
iLocalProxy
praca, sugeruję, aby ponownie Te artykuły Doc: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev i flask.pocoo .org / docs / 0.11 / reqcontext . Twój świeży chwyt może pozwolić ci zobaczyć je w nowym świetle i może zapewnić lepszy wgląd.Mały dodatek @Mark Hildreth 's odpowiedź.
Kontekst stosu wyglądać
{thread.get_ident(): []}
, gdzie[]
nazywa się „stos”, ponieważ wykorzystywane tylkoappend
(push
)pop
i[-1]
(__getitem__(-1)
) operacje. Zatem stos kontekstowy zachowa aktualne dane dla wątku lub wątku Greenlet.current_app
,g
,request
,session
I itp jestLocalProxy
obiekt, który właśnie overrided specjalnych metod__getattr__
,__getitem__
,__call__
,__eq__
itp i wartość powrotna z kontekstu stosie górnej ([-1]
) o nazwą zmiennej (current_app
,request
na przykład).LocalProxy
trzeba raz zaimportować te obiekty i nie przegapią one aktualności. Więc lepiej po prostu importujrequest
gdziekolwiek jesteś w kodzie, zamiast bawić się wysyłaniem argumentu żądania do funkcji i metod. Możesz z łatwością napisać własne rozszerzenia, ale nie zapominaj, że niepoważne użycie może utrudnić zrozumienie kodu.Poświęć czas na zrozumienie https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .
Jak więc zapełniono oba stosy? Na zapytanie
Flask
:request_context
według środowiska (initmap_adapter
, match path)request_context
app_context
jeśli pominięto go i przekazano do stosu kontekstów aplikacjiźródło
Weźmy jeden przykład, załóżmy, że chcesz ustawić kontekst użytkownika (używając konstrukcji kolbowej Local i LocalProxy).
Zdefiniuj jedną klasę użytkownika:
zdefiniuj funkcję do pobierania obiektu użytkownika w bieżącym wątku lub greenletu
Teraz zdefiniuj LocalProxy
Teraz, aby uzyskać identyfikator użytkownika w bieżącym wątku usercontext.userid
wyjaśnienie:
1. Lokalny ma dykt tożsamości i objet, tożsamość to identyfikator wątku lub zielony, w tym przykładzie _local.user = User () jest równoważne z _local .___ storage __ [identyfikator bieżącego wątku] ["user"] = User ()
źródło