El desarrollo web es uno de los casos de uso más populares de la programación. Python es uno de los lenguajes de programación más populares del mundo. Entonces, ¿por qué no podemos crear aplicaciones web en Python?
Crear una interfaz de usuario debería ser sencillo, pero incluso si tienes grandes ingenieros en tu equipo, el costo de aprender un nuevo lenguaje y herramientas era una gran barrera. A menudo, crear una interfaz de usuario puede ser más difícil que el trabajo real que uno está haciendo. Creamos Reflex, un marco web Python de código abierto para resolver este problema exacto.
En esencia, las aplicaciones Reflex se compilan en una aplicación frontend de React y una aplicación backend de FastAPI . Solo la interfaz de usuario se compila en Javascript; toda la lógica de la aplicación y la gestión de estado se quedan en Python y se ejecutan en el servidor. Reflex utiliza WebSockets para enviar eventos desde el frontend al backend y para enviar actualizaciones de estado desde el backend al frontend.
Ya existían algunas formas de crear aplicaciones en Python, pero ninguna de ellas se adaptaba a nuestras necesidades.
Por un lado, existen frameworks como Django y Flask que son excelentes para crear aplicaciones web de nivel de producción, pero solo se ocupan del backend: todavía es necesario usar JavaScript y un framework frontend, además de escribir una gran cantidad de código repetitivo para conectar el frontend y el backend.
Por otro lado, las bibliotecas de Python puro como Dash y Streamlit pueden ser excelentes para proyectos pequeños, pero están limitadas a un caso de uso específico y no tienen las características ni el rendimiento necesarios para crear una aplicación web completa. A medida que tu aplicación aumenta en características y complejidad, es posible que llegues a los límites del marco, en cuyo caso tendrás que limitar tu idea para que se ajuste al marco o descartar tu proyecto y reconstruirlo utilizando un "marco web real".
Queremos superar esta brecha creando un marco que sea fácil e intuitivo para comenzar, pero que al mismo tiempo sea flexible y potente para admitir cualquier aplicación.
Ahora veamos cómo construimos Reflex para alcanzar estos objetivos.
Las aplicaciones web full-stack se componen de un frontend y un backend. El frontend es la interfaz de usuario y se presenta como una página web que se ejecuta en el navegador del usuario. El backend maneja la lógica y la gestión del estado (como bases de datos y API) y se ejecuta en un servidor. En el desarrollo web tradicional, estas suelen ser dos aplicaciones independientes y, a menudo, se escriben en diferentes marcos o lenguajes. Por ejemplo, puedes combinar un backend de Flask con un frontend de React. Con este enfoque, tienes que mantener dos aplicaciones independientes y terminar escribiendo una gran cantidad de código repetitivo para conectar el frontend y el backend.
Queremos simplificar este proceso en Reflex definiendo tanto el frontend como el backend en una única base de código, mientras usamos Python para todo. Los desarrolladores solo deben preocuparse por la lógica de su aplicación y no por los detalles de implementación de bajo nivel.
Queremos que las aplicaciones Reflex tengan el mismo aspecto y la misma funcionalidad que una aplicación web tradicional para el usuario final, pero que sean fáciles de crear y mantener para el desarrollador. Para lograrlo, nos basamos en tecnologías web maduras y populares.
Cuando reflex run
, Reflex compila el frontend en una aplicación Next.js de una sola página y la sirve en un puerto (por defecto 3000
) al que puede acceder en su navegador.
La función del frontend es reflejar el estado de la aplicación y enviar eventos al backend cuando el usuario interactúa con la interfaz de usuario. No se ejecuta ninguna lógica real en el frontend.
Las interfaces de usuario de Reflex se crean utilizando componentes que se pueden combinar para crear interfaces de usuario complejas. En lugar de utilizar un lenguaje de plantillas que combina HTML y Python, solo utilizamos funciones de Python para definir la interfaz de usuario.
En segundo plano, los componentes se compilan para formar componentes React. Muchos de nuestros componentes principales se basan en Radix , una biblioteca de componentes React muy popular. También tenemos muchos otros componentes para gráficos, tablas de datos y más. Elegimos React porque es una biblioteca popular con un ecosistema enorme. Nuestro objetivo no es recrear el ecosistema web, sino hacerlo accesible para los desarrolladores de Python.
Esto también permite que nuestros usuarios traigan sus propios componentes si no tenemos un componente que necesitan. Los usuarios pueden empaquetar sus propios componentes de React y luego publicarlos para que otros los usen. Con el tiempo, desarrollaremos nuestro ecosistema de componentes de terceros para que los usuarios puedan encontrar y usar fácilmente los componentes que otros han creado.
Queríamos asegurarnos de que las aplicaciones Reflex se vieran bien desde el primer momento, pero al mismo tiempo dándoles a los desarrolladores control total sobre la apariencia de su aplicación.
Contamos con un sistema de temas central que le permite establecer opciones de estilo de alto nivel, como el modo oscuro y el color de acento, en toda su aplicación para darle una apariencia unificada.
Además de esto, los componentes Reflex pueden diseñarse utilizando todo el poder de CSS. Aprovechamos la biblioteca Emotion para permitir el diseño "CSS en Python", de modo que pueda pasar cualquier propiedad CSS como argumento de palabra clave a un componente. Esto incluye propiedades responsivas al pasar una lista de valores.
En Reflex, solo el frontend compila en Javascript y se ejecuta en el navegador del usuario, mientras que todo el estado y la lógica permanecen en Python y se ejecutan en el servidor. Cuando reflex run
, iniciamos un servidor FastAPI (por defecto en el puerto 8000
) al que se conecta el frontend a través de un websocket.
Todo el estado y la lógica se definen dentro de una clase State
. El estado se compone de variables y controladores de eventos . Las variables son valores de la aplicación que pueden cambiar con el tiempo. Se definen como atributos de clase en la clase State
y pueden ser cualquier tipo de Python que se pueda serializar en JSON.
Los controladores de eventos son métodos de la clase State
que se invocan cuando el usuario interactúa con la interfaz de usuario. Son la única forma en que podemos modificar las variables en Reflex y se pueden invocar en respuesta a las acciones del usuario, como hacer clic en un botón o escribir en un cuadro de texto.
Dado que los controladores de eventos se ejecutan en el backend, puedes usar cualquier biblioteca de Python dentro de ellos.
Normalmente, cuando se escriben aplicaciones web, hay que escribir una gran cantidad de código repetitivo para conectar el frontend y el backend. Con Reflex, no tiene que preocuparse por eso: nosotros nos encargamos de la comunicación entre el frontend y el backend por usted. Los desarrolladores solo tienen que escribir la lógica de su controlador de eventos y, cuando se actualizan las variables, la interfaz de usuario se actualiza automáticamente.
El usuario puede interactuar con la interfaz de usuario de muchas maneras, como hacer clic en un botón, escribir en un cuadro de texto o pasar el cursor sobre un elemento. En Reflex, a estos eventos los llamamos activadores de eventos .
En el frontend, mantenemos una cola de eventos con todos los eventos pendientes. Un evento consta de tres datos principales:
Cuando se activa un evento, se agrega a la cola. Tenemos un indicador processing
para asegurarnos de que solo se procese un evento a la vez. Esto garantiza que el estado siempre sea consistente y que no haya condiciones de carrera con dos controladores de eventos que modifiquen el estado al mismo tiempo. Existen excepciones a esto, como los eventos en segundo plano que le permiten ejecutar eventos en segundo plano sin bloquear la interfaz de usuario.
Una vez que el evento está listo para ser procesado, se envía al backend a través de una conexión WebSocket.
Una vez que se recibe el evento, se procesa en el backend. Reflex utiliza un administrador de estado que mantiene una relación entre los tokens del cliente y su estado. De forma predeterminada, el administrador de estado es solo un diccionario en memoria, pero se puede ampliar para utilizar una base de datos o caché. En producción, utilizamos Redis como nuestro administrador de estado.
Una vez que tenemos el estado del usuario, el siguiente paso es ejecutar el controlador de eventos con los argumentos.
Cada vez que un controlador de eventos retorna (o cede), guardamos el estado en el administrador de estados y enviamos las actualizaciones de estado al frontend para actualizar la IU. Para mantener el rendimiento a medida que su estado crece, internamente Reflex realiza un seguimiento de las variables que se actualizaron durante el controlador de eventos ( variables sucias ).
Cuando el controlador de eventos termina de procesarse, encontramos todas las variables sucias y creamos una actualización de estado para enviar al frontend.
Almacenamos el nuevo estado en nuestro administrador de estados y luego enviamos la actualización de estado al frontend. Luego, el frontend actualiza la interfaz de usuario para reflejar el nuevo estado.
Espero que esto proporcione una buena descripción general de cómo funciona Reflex. Publicaremos más publicaciones para compartir cómo logramos que Reflex sea escalable y eficiente a través de funciones como la fragmentación de estados y las optimizaciones del compilador.