Tworzenie stron internetowych jest jednym z najpopularniejszych przypadków użycia programowania. Python jest jednym z najpopularniejszych języków programowania na świecie. Dlaczego więc nie możemy tworzyć aplikacji internetowych w Pythonie?
Tworzenie interfejsu użytkownika powinno być proste, ale nawet jeśli masz świetnych inżynierów w swoim zespole, narzut związany z nauką nowego języka i narzędzi był ogromną barierą. Często tworzenie interfejsu użytkownika może być trudniejsze niż rzeczywista praca, którą się wykonuje! Zbudowaliśmy Reflex, open source'owy framework internetowy Python, aby rozwiązać ten konkretny problem.
Pod maską aplikacje Reflex kompilują się do aplikacji front-end React i aplikacji back-end FastAPI . Tylko interfejs użytkownika jest kompilowany do Javascript; cała logika aplikacji i zarządzanie stanem pozostają w Pythonie i są uruchamiane na serwerze. Reflex używa WebSockets do wysyłania zdarzeń z front-endu do back-endu i do wysyłania aktualizacji stanu z back-endu do front-endu.
Istniało już kilka sposobów tworzenia aplikacji w Pythonie, ale żaden z nich nie odpowiadał naszym potrzebom.
Z jednej strony istnieją frameworki takie jak Django i Flask , które świetnie nadają się do tworzenia aplikacji internetowych klasy produkcyjnej. Ale obsługują one tylko zaplecze — nadal musisz używać JavaScript i frameworka frontendu, a także pisać mnóstwo szablonowego kodu, aby połączyć frontend i zaplecze.
Z drugiej strony biblioteki czystego Pythona, takie jak Dash i Streamlit , mogą być świetne dla małych projektów, ale są ograniczone do konkretnego przypadku użycia i nie mają funkcji i wydajności, aby zbudować pełną aplikację internetową. Wraz ze wzrostem funkcji i złożoności aplikacji możesz dojść do granic możliwości frameworka, w którym to momencie musisz albo ograniczyć swój pomysł, aby pasował do frameworka, albo porzucić projekt i przebudować go przy użyciu „prawdziwego frameworka internetowego”.
Chcemy zniwelować tę lukę, tworząc strukturę, która będzie łatwa i intuicyjna w obsłudze, a jednocześnie elastyczna i wydajna, by obsługiwać dowolną aplikację.
Przyjrzyjmy się teraz, jak stworzyliśmy Reflex, aby osiągnąć te cele.
Aplikacje internetowe full-stack składają się z front-endu i back-endu. Front-end to interfejs użytkownika, który jest obsługiwany jako strona internetowa, która działa w przeglądarce użytkownika. Back-end obsługuje logikę i zarządzanie stanem (takie jak bazy danych i API) i jest uruchamiany na serwerze. W tradycyjnym rozwoju sieci są to zwykle dwie oddzielne aplikacje, często napisane w różnych frameworkach lub językach. Na przykład możesz połączyć back-end Flask z front-endem React. Przy takim podejściu musisz utrzymywać dwie oddzielne aplikacje i kończysz na pisaniu mnóstwa szablonowego kodu, aby połączyć front-end i back-end.
Chcemy uprościć ten proces w Reflex, definiując zarówno front-end, jak i back-end w jednej bazie kodu, używając Pythona do wszystkiego. Deweloperzy powinni martwić się tylko logiką swojej aplikacji, a nie szczegółami implementacji niskiego poziomu.
Chcemy, aby aplikacje Reflex wyglądały i sprawiały wrażenie tradycyjnych aplikacji internetowych dla użytkownika końcowego, a jednocześnie były łatwe do zbudowania i utrzymania dla dewelopera. Aby to zrobić, zbudowaliśmy je na dojrzałych i popularnych technologiach internetowych.
Po reflex run
aplikacji Reflex kompiluje front-end do jednostronicowej aplikacji Next.js i udostępnia ją na porcie (domyślnie 3000
), do którego masz dostęp w przeglądarce.
Zadaniem frontendu jest odzwierciedlanie stanu aplikacji i wysyłanie zdarzeń do backendu, gdy użytkownik wchodzi w interakcję z UI. Żadna rzeczywista logika nie jest uruchamiana na frontendzie.
Frontendy Reflex są budowane przy użyciu komponentów, które można ze sobą łączyć, aby tworzyć złożone interfejsy użytkownika. Zamiast używać języka szablonów, który łączy HTML i Python, używamy po prostu funkcji Pythona do definiowania interfejsu użytkownika.
Pod maską komponenty kompilują się do komponentów React. Wiele naszych głównych komponentów jest opartych na Radix , popularnej bibliotece komponentów React. Mamy również wiele innych komponentów do tworzenia wykresów, tabel danych i innych. Wybraliśmy React, ponieważ jest to popularna biblioteka z ogromnym ekosystemem. Naszym celem nie jest odtworzenie ekosystemu internetowego, ale udostępnienie go programistom Pythona.
Umożliwia to również naszym użytkownikom przynoszenie własnych komponentów, jeśli nie mamy komponentu, którego potrzebują. Użytkownicy mogą opakowywać własne komponenty React, a następnie publikować je, aby inni mogli z nich korzystać. Z czasem zbudujemy nasz ekosystem komponentów stron trzecich, aby użytkownicy mogli łatwo znajdować i używać komponentów, które inni zbudowali.
Chcieliśmy mieć pewność, że aplikacje Reflex będą wyglądać dobrze od razu po wyjęciu z pudełka, a jednocześnie dadzą deweloperom pełną kontrolę nad wyglądem ich aplikacji.
Dysponujemy podstawowym systemem motywów, który umożliwia ustawienie zaawansowanych opcji stylizacji, takich jak tryb ciemny i kolor akcentujący w całej aplikacji, aby nadać jej spójny wygląd.
Poza tym komponenty Reflex można stylizować, korzystając z pełnej mocy CSS. Wykorzystujemy bibliotekę Emotion , aby umożliwić stylizację „CSS-in-Python”, dzięki czemu możesz przekazać dowolny atrybut CSS jako argument słowa kluczowego do komponentu. Obejmuje to responsywne atrybuty poprzez przekazanie listy wartości.
W Reflex tylko frontend kompiluje się do Javascript i działa w przeglądarce użytkownika, podczas gdy cały stan i logika pozostają w Pythonie i są uruchamiane na serwerze. Gdy reflex run
, uruchamiamy serwer FastAPI (domyślnie na porcie 8000
), z którym frontend łączy się przez websocket.
Cały stan i logika są zdefiniowane w klasie State
. Stan składa się ze zmiennych i obsługi zdarzeń . Zmienne to dowolne wartości w aplikacji, które mogą się zmieniać w czasie. Są zdefiniowane jako atrybuty klasy w klasie State
i mogą być dowolnym typem Pythona, który można serializować do JSON.
Obsługujące zdarzenia to metody w klasie State
, które są wywoływane, gdy użytkownik wchodzi w interakcję z interfejsem użytkownika. Są jedynym sposobem, w jaki możemy modyfikować zmienne w Reflex, i mogą być wywoływane w odpowiedzi na działania użytkownika, takie jak kliknięcie przycisku lub wpisanie w polu tekstowym.
Ponieważ procedury obsługi zdarzeń są uruchamiane w zapleczu, można w nich używać dowolnej biblioteki Pythona.
Zwykle podczas pisania aplikacji internetowych musisz napisać mnóstwo szablonowego kodu, aby połączyć front-end i back-end. Dzięki Reflex nie musisz się o to martwić — my zajmujemy się komunikacją między front-endem i back-endem za Ciebie. Programiści muszą tylko napisać logikę obsługi zdarzeń, a gdy zmienne są aktualizowane, interfejs użytkownika jest automatycznie aktualizowany.
Użytkownik może wchodzić w interakcję z UI na wiele sposobów, np. klikając przycisk, wpisując tekst w polu tekstowym lub najeżdżając kursorem na element. W Reflex nazywamy to wyzwalaczami zdarzeń .
Na froncie utrzymujemy kolejkę zdarzeń wszystkich oczekujących zdarzeń. Zdarzenie składa się z trzech głównych części danych:
Gdy zdarzenie jest wyzwalane, jest dodawane do kolejki. Mamy flagę processing
, aby upewnić się, że przetwarzane jest tylko jedno zdarzenie na raz. Zapewnia to, że stan jest zawsze spójny i nie ma żadnych warunków wyścigu z dwoma programami obsługi zdarzeń modyfikującymi stan w tym samym czasie. Istnieją wyjątki od tej reguły, takie jak zdarzenia w tle, które umożliwiają uruchamianie zdarzeń w tle bez blokowania interfejsu użytkownika.
Gdy zdarzenie jest gotowe do przetworzenia, jest ono wysyłane do zaplecza poprzez połączenie WebSocket.
Po otrzymaniu zdarzenia jest ono przetwarzane w zapleczu. Reflex używa menedżera stanu , który utrzymuje mapowanie między tokenami klienta a ich stanem. Domyślnie menedżer stanu jest po prostu słownikiem w pamięci, ale można go rozszerzyć o użycie bazy danych lub pamięci podręcznej. W środowisku produkcyjnym używamy Redis jako naszego menedżera stanu.
Gdy znamy już stan użytkownika, następnym krokiem jest uruchomienie obsługi zdarzeń z argumentami.
Za każdym razem, gdy obsługa zdarzeń zwraca (lub yielduje się), zapisujemy stan w menedżerze stanu i wysyłamy aktualizacje stanu do front-endu, aby zaktualizować interfejs użytkownika. Aby utrzymać wydajność w miarę rozwoju stanu, wewnętrznie Reflex śledzi zmienne, które zostały zaktualizowane podczas obsługi zdarzeń ( dirty vars ).
Gdy program obsługi zdarzeń zakończy przetwarzanie, wyszukujemy wszystkie brudne zmienne i tworzymy aktualizację stanu, która zostanie wysłana do front-endu.
Przechowujemy nowy stan w naszym menedżerze stanu, a następnie wysyłamy aktualizację stanu do front-endu. Następnie front-end aktualizuje interfejs użytkownika, aby odzwierciedlić nowy stan.
Mam nadzieję, że to daje dobry przegląd tego, jak Reflex działa pod maską. Będziemy publikować więcej postów, aby podzielić się tym, jak uczyniliśmy Reflex skalowalnym i wydajnym dzięki takim funkcjom, jak partycjonowanie stanu i optymalizacje kompilatora.