Dlaczego poleca się kolbę CLI zamiast Flask.run?

13

W Flask 0.11 wprowadzono flaskCLI. Zarówno dokumentacja, jak i dziennik zmian są zalecane.

Dokumentacja serwera deweloperskiego :

Począwszy od wersji Flask 0.11 istnieje wiele wbudowanych sposobów uruchamiania serwera programistycznego. Najlepszym z nich jest narzędzie wiersza polecenia flask , ale można również kontynuować korzystanie z tej Flask.run()metody.

Wiersz poleceń

Kolba skrypt z linii poleceń (Command Line Interface) jest zalecane dla rozwoju, ponieważ zapewnia doskonałe wrażenia przeładowania ze względu na to, jak ładuje aplikację. Podstawowe użycie wygląda następująco:

$ export FLASK_APP=my_application
$ export FLASK_DEBUG=1
$ flask run

Dziennik zmian :

  • Dodano flaski flask.climoduł do uruchamiania lokalnego serwera debugowania poprzez system CLI kliknięcia. Jest to zalecane w porównaniu ze starą flask.run()metodą, ponieważ działa szybciej i bardziej niezawodnie z powodu innej konstrukcji, a także zastępuje Flask-Script.

Do tej pory nie zauważyłem tego „lepszego przeładowania”. Nie widzę sensu używania CLI w skrypcie niestandardowym.

Jeśli używasz Flask.run, po prostu napisałbym plik python:

#!/usr/bin/env python3
from my_app import app


if __name__ == '__main__':
    app.run(debug=True)

Jeśli używasz CLI, musisz określić zmienne środowiskowe. W dokumentach CLI stwierdzono, że można to zintegrować ze activateskryptem virtualenvwrapper. Osobiście uważam, że jest to część aplikacji i uważam, że powinna być pod kontrolą wersji. Niestety, potrzebny jest skrypt powłoki:

#!/usr/bin/env bash
export FLASK_APP=my_app:app
export FLASK_DEBUG=1

flask run

Oczywiście będzie mu towarzyszyć dodatkowy skrypt nietoperzy, gdy tylko użytkownicy systemu Windows zaczną współpracować.

Również pierwsza opcja pozwala na instalację napisaną w Pythonie przed uruchomieniem właściwej aplikacji.

To pozwala na przykład

  • parsować argumenty wiersza poleceń w Pythonie
  • skonfigurować rejestrowanie przed uruchomieniem aplikacji

Wydają się promować, że można dodawać własne polecenia. Nie rozumiem, dlaczego jest to lepsze niż pisanie prostych skryptów w języku Python, opcjonalnie udostępnianych przez punkty wejścia.

Przykładowe dane wyjściowe rejestrowania przy korzystaniu ze skonfigurowanego rejestratora przy użyciu skryptu uruchamiania Python:

$ ./run.py 
   DEBUG 21:51:22 main.py:95) Configured logging
    INFO 21:51:22 _internal.py:87)  * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    INFO 21:51:22 _internal.py:87)  * Restarting with inotify reloader
   DEBUG 21:51:22 main.py:95) Configured logging
 WARNING 21:51:22 _internal.py:87)  * Debugger is active!
    INFO 21:51:22 _internal.py:87)  * Debugger pin code: 263-225-431
   DEBUG 21:51:25 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
   DEBUG 21:51:25 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
    INFO 21:51:25 _internal.py:87)  * Detected change in 'my_app/main.py', reloading
    INFO 21:51:26 _internal.py:87)  * Restarting with inotify reloader
   DEBUG 21:51:26 main.py:95) Configured logging
 WARNING 21:51:26 _internal.py:87)  * Debugger is active!
    INFO 21:51:26 _internal.py:87)  * Debugger pin code: 263-225-431

Przykładowe dane wyjściowe rejestrowania przy korzystaniu ze skonfigurowanego rejestratora za pomocą interfejsu CLI: zauważ, że rejestrator root nie mógł zostać skonfigurowany wystarczająco wcześnie.

$ ./run.sh 
 * Serving Flask app "appsemble.api.main:app"
 * Forcing debug mode on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with inotify reloader
   DEBUG 21:51:33 main.py:95) Configured logging
 * Debugger is active!
 * Debugger pin code: 187-758-498
   DEBUG 21:51:34 main.py:95) Configured logging
   DEBUG 21:51:37 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
   DEBUG 21:51:37 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
 * Detected change in 'my_app/main.py', reloading
    INFO 21:51:37 _internal.py:87)  * Detected change in 'my_app/main.py', reloading
 * Restarting with inotify reloader
    INFO 21:51:38 _internal.py:87)  * Restarting with inotify reloader
 * Debugger is active!
 * Debugger pin code: 187-758-498
   DEBUG 21:51:38 main.py:95) Configured logging

Moje aktualne pytanie brzmi:

Dlaczego zalecana jest kolba CLI Flask.run?

Remco Haszing
źródło

Odpowiedzi:

11

Dokumenty serwera deweloperskiego wskazują, że występują problemy z wywołaniem run () i automatycznym przeładowaniem kodu:

Działa to dobrze w typowym przypadku, ale nie działa dobrze w programowaniu, dlatego od Flask 0.11 zaleca się metodę kolby. Powodem tego jest to, że z powodu działania mechanizmu przeładowania występują dziwne efekty uboczne (takie jak dwukrotne wykonanie określonego kodu, czasami zawieszanie się bez komunikatu lub zginięcie, gdy wystąpi błąd składni lub importu).

Twierdzą, że CLI nie cierpi z powodu tego problemu.

Pierwsze zatwierdzenie, które wydaje się dotyczyć tego problemu, to: https://github.com/pallets/flask/commit/3bdb90f06b9d3167320180d4a5055dcd949bf72f

I tam Armin Ronacher napisał:

Nie zaleca się używania tej funkcji do programowania z automatycznym przeładowaniem, ponieważ jest ona źle obsługiwana. Zamiast tego powinieneś korzystać ze wsparcia flaskskryptu wiersza poleceń runserver.

Jak wspomniał Aaron Hall, wydaje się, że użycie run () może być problematyczne ze względu na fakt, że wszystkie obiekty, które są instancjami klas zdefiniowanych w wymienianych modułach, nie zostaną przywrócone, a po każdym przeładowaniu modułu moduły, które importuje, również nie są ładowane ponownie.

Szczegółowe informacje na ten temat można znaleźć w Pythonie 3 na stronie : https://docs.python.org/3/library/importlib.html?highlight=importlib#module-importlib

W Stanach:

Podobnie jak w przypadku wszystkich innych obiektów w Pythonie, stare obiekty są odzyskiwane dopiero po tym, jak ich liczba referencji spadnie do zera.

Inne odniesienia do starych obiektów (takie jak nazwy zewnętrzne modułu) nie są powiązane w odniesieniu do nowych obiektów i muszą być aktualizowane w każdej przestrzeni nazw, w której występują, jeśli jest to pożądane.

Po ponownym załadowaniu modułu jego słownik (zawierający zmienne globalne modułu) zostaje zachowany. Przedefiniowanie nazw zastąpi stare definicje, więc generalnie nie stanowi to problemu. Jeśli nowa wersja modułu nie definiuje nazwy zdefiniowanej przez starą wersję, stara definicja pozostaje.

Tak więc, tworząc nowy proces i zabijając stary, naturalnie eliminujesz wszystkie przestarzałe referencje.

Ponadto interfejs CLI firmy Flask korzysta z modułu „kliknięcia”, co bardzo ułatwia dodawanie niestandardowych poleceń, ale co najważniejsze, oprócz naprawienia błędu przeładowywania, interfejs CLI oferuje znormalizowany sposób uruchamiania aplikacji i dodawania niestandardowych poleceń. To brzmi jak bardzo dobra rzecz, ponieważ sprawia, że ​​znajomość Flask jest łatwiejsza do przenoszenia między różnymi zespołami i aplikacjami, niż na wiele sposobów, aby zrobić to samo.

Wydaje się, że to prawdziwy sposób, aby uczynić Flask bardziej zgodnym z Zen Python:

Powinien być jeden - a najlepiej tylko jeden - oczywisty sposób na zrobienie tego.

Martin Jungblut Schreiner
źródło
2
Oto, co myślę, że Armin rozumie przez „źle obsługiwany”: w Pythonie przeładowanie modułu nie przeładowuje modułów, które ten moduł importuje, ani też nie łączy nazw w innych modułach od wskazywania starych obiektów na nowe z nowego modułu - więc zamiana nowego modułu na gorąco w tym samym procesie jest problematyczna. O wiele lepiej jest rozpocząć nowy proces, gdy chcesz dokonać zmiany kodu.
Aaron Hall
Teraz, kiedy o tym wspominasz, przypominam sobie opisane zachowanie, dziękuję za wyjaśnienie! Odpowiednio zmienię odpowiedź.
Martin Jungblut Schreiner
ok, plus 1 za zacytowanie mnie. :)
Aaron Hall
Dodatek od Aarona Hall wyjaśnił mi to. Dzięki. :)
Remco Haszing
7
Dlaczego musimy używać zmiennej środowiskowej FLASK_APP? Czy to jest nieodłączne od tego, jak to działa? Jestem ciekawy, dlaczego flask runnie akceptuje tego samego jako argumentu, co ułatwiłoby dołączanie nowych użytkowników. Dziękuję Ci.
John Wheeler,