Cloudflare Workers, ahora se puede escribir en Python
(blog.cloudflare.com)- Cloudflare Workers admite escribir en Python en beta abierta, integrando directamente Pyodide en workerd, el runtime open source de Workers, para ampliar su modelo centrado en JavaScript
- Python Workers admite desde el primer día los bindings existentes, como Vectorize, Workers AI, R2 y Durable Objects, y puede importar algunos paquetes de Python como FastAPI, Langchain y Numpy
- La base de la implementación es Pyodide, el port de WebAssembly de CPython, y mediante su FFI entre JavaScript y Python maneja Request, Response, la Fetch API y los bindings de recursos de Cloudflare desde código Python
- Cloudflare ejecuta los imports al momento del despliegue y crea un snapshot de memoria lineal de WebAssembly para reducir el costo de inicialización de Pyodide y de los paquetes, bajando el cold start de un Python Worker básico a menos de 1 segundo
- Las versiones de Python y las actualizaciones de Pyodide y de paquetes se administran con Compatibility Dates y Compatibility Flags, y los Workers que permanezcan en una versión de Python cuyo período de soporte de 5 años haya terminado pasarán automáticamente a la siguiente versión de Python más antigua
Python Workers en beta abierta
- Python Workers ya se puede usar en beta abierta en Cloudflare Workers
- A diferencia del soporte previo para lenguajes distintos de JavaScript, integra directamente la implementación de Python en el runtime workerd
- Los bindings de Cloudflare compatibles desde el primer día incluyen:
- Python Workers puede importar algunos paquetes populares de Python como FastAPI, Langchain y Numpy
- No requiere una etapa de build separada ni un toolchain externo
Por qué no bastaba con compilar solo a WebAssembly
- Workers es compatible con WebAssembly desde 2018, y cada Worker se ejecuta en un isolate de V8, el mismo motor de JavaScript que usa Chrome
- En principio, varios lenguajes, incluido Python, podían ejecutarse en Workers si primero se compilaban a WebAssembly o JavaScript
- Pero las aplicaciones reales necesitan más que ejecutar “hello world”, y es clave contar con soporte para el ecosistema de paquetes que los desarrolladores usan habitualmente
- Python Workers es una primera forma de dar soporte de primera clase a lenguajes distintos de JavaScript en Workers
Flujo de ejecución de un Python Worker
- Al quedar integrado Pyodide en workerd, un Python Worker puede procesar solicitudes con un handler
on_fetch - En
wrangler.toml, se especifica un archivo.pycomomainy se configuracompatibility_flags = ["python_workers"] - Al ejecutar
npx wrangler@latest dev, el runtime de Workers hace lo siguiente:- determina la versión necesaria de Pyodide según la compatibility date
- crea un isolate para el Worker e inyecta Pyodide automáticamente
- entrega el código Python a Pyodide
- El entorno de ejecución de Python se maneja internamente y la plataforma lo ofrece de forma similar a JavaScript Workers
Por qué Pyodide encaja en Workers
- Pyodide es una implementación de CPython portada a WebAssembly, e interpreta el código Python sin precompilarlo previamente a otro formato
- Proporciona la mayor parte de la biblioteca estándar de Python y una interfaz de función externa (FFI) que permite llamar APIs de JavaScript desde Python
- Está diseñado para compilar el intérprete principal y cada módulo nativo de Python como módulos de WebAssembly separados, con capacidad de enlace dinámico en tiempo de ejecución
- Varios Workers que corren en la misma máquina pueden compartir la huella de código de esos módulos, algo importante en el entorno de Cloudflare, que ejecuta miles de Workers por máquina
- La mayoría de los lenguajes dirigidos a WebAssembly todavía no admiten enlace dinámico, por lo que muchas aplicaciones terminan incluyendo su propia copia del runtime del lenguaje
Pyodide y el enlace dinámico en WebAssembly
- Como WebAssembly es un entorno sandbox aislado del runtime anfitrión, las tareas que van más allá del cálculo puro, como leer archivos, deben ser provistas por el entorno de ejecución e importadas por el módulo
- El target WebAssembly de LLVM se divide en tres variantes:
wasm32-unknown-unknown: no proporciona biblioteca estándar de C ni interfaz de llamadas al sistemawasm32-wasi: usa una interfaz estándar de sistema implementada por un runtime WASIwasm32-unknown-emscripten: genera junto con el binario una biblioteca de JavaScript que define e implementa los imports necesarios
- Pyodide usa Emscripten para ofrecer el intérprete de CPython, el FFI entre Python y JavaScript, y paquetes de Python de terceros compilados a WebAssembly
- De esos targets, hoy solo Emscripten admite enlace dinámico
- WASI todavía no soporta la abstracción de enlace dinámico
dlopen/dlsymque usa CPython
El FFI que une Python y JavaScript
- En el ejemplo de Python Worker,
from js import ResponsetraeResponsede JavaScript - El FFI de Pyodide permite acceder desde Python a toda la funcionalidad de JavaScript, reduciendo la desventaja funcional que Python Workers podría tener frente a JavaScript Workers
- Los tipos inmutables como strings y números se convierten de forma transparente entre ambos lenguajes, mientras que los objetos mutables se envuelven en proxies adecuados
- Cuando un objeto JavaScript se pasa a Python, Pyodide verifica qué protocolos de JavaScript soporta ese objeto y construye dinámicamente una clase que implementa los protocolos equivalentes en Python
- Si soporta el protocolo de iteración de JavaScript, también soporta el protocolo de iteración de Python
- Si es un Promise o un thenable, en Python se convierte en un objeto awaitable
- También en el flujo de manejo de solicitudes, el objeto
Requestde JavaScript que entra se envuelve en unJsProxyaccesible desde código Python, y el valor de retorno del handler de Python se convierte en un objetoResponsede JavaScript
Enlace dinámico y paquetes de Python
- Muchos paquetes de Python cargan bibliotecas nativas mediante el FFI de C, y para funcionar en el runtime de Workers esas bibliotecas deben compilarse a WebAssembly
- Pyodide está construido con Emscripten y sobrescribe el FFI de C de Python para que, cuando un paquete intente cargar una biblioteca nativa, se cargue un módulo de WebAssembly provisto por el runtime de Workers
- El enlace dinámico permite que Pyodide sea compatible con varios paquetes de Python que dependen de bibliotecas nativas
- El enlace estático obliga a cargar todo el código necesario antes de ejecutar el binario, mientras que el enlace dinámico paga ese costo solo cuando hace falta
- Workers crea para cada Worker un sistema de archivos que parece una distribución de Python en tiempo de ejecución, pero los archivos base se comparten entre Workers
- Por ahora los archivos se comparten entre Workers, pero se copian en cada isolate nuevo; a futuro, Cloudflare cree que podrá compartir más recursos subyacentes con técnicas de copy-on-write
Soporte para bibliotecas cliente y servidor HTTP
- Python tiene muchas bibliotecas cliente HTTP como httpx, urllib3 y requests, pero no funcionan de forma predeterminada en Pyodide
- Estas bibliotecas usan sockets sin procesar, y como el modelo de seguridad del navegador y CORS no lo permiten, en el runtime de Workers se necesita otro enfoque
-
Clientes asíncronos
- Las bibliotecas que permiten solicitudes asíncronas, como aiohttp y httpx, pueden usar la Fetch API de Workers
- Cloudflare parchea las bibliotecas con Pyodide FFI para que usen la Fetch API de JavaScript
- El parche de httpx está compuesto por menos de 100 líneas de código
-
Clientes síncronos y limitaciones
- Muchas APIs de Python son síncronas, y en ese caso no pueden usar directamente la Fetch API asíncrona
- urllib3 recibió contribuciones para el soporte de navegador en Pyodide usando
Atomics.wait()y un fetch worker thread, o XMLHttpRequest síncrono - Cloudflare Workers actualmente no soporta worker threads ni
XMLHttpRequestsíncrono, por lo que estos dos enfoques no funcionan en Python Workers - Actualmente no se admiten solicitudes síncronas
-
Stack switching en WebAssembly
- En WebAssembly hay una propuesta en stage 3 para agregar cambio de pila, y V8 ya tiene una implementación
- Los contribuidores de Pyodide han estado agregando soporte para stack switching desde septiembre de 2022 y ya casi está listo
- Cuando este soporte llegue,
run_syncde Pyodide podrá bloquearse mientras espera que se complete un awaitable, lo que abrirá una ruta para soportar solicitudes síncronas
FastAPI y ASGI
- FastAPI es una biblioteca muy usada para definir servidores en Python y utiliza el protocolo ASGI
- Las aplicaciones FastAPI no leen ni escriben sockets directamente; normalmente un servidor ASGI como uvicorn se encarga del manejo de sockets sin procesar
- Gracias a esta estructura, FastAPI puede funcionar en Cloudflare Workers sin necesidad de parcharlo ni modificarlo
- Solo hace falta reemplazar uvicorn por un servidor ASGI que pueda ejecutarse dentro de Workers
- La implementación inicial está en asgi.py de workerd y está incluida en el fork de Pyodide mantenido por Cloudflare
- Cloudflare planea añadir más funciones y cobertura de pruebas antes de enviar estos cambios upstream a Pyodide
Importación de paquetes de Python
- Python Workers soporta algunos paquetes de Python provistos directamente por Pyodide
- Entre los paquetes soportados están numpy, httpx, FastAPI y Langchain
- Para importar paquetes, se agrega el nombre del paquete sin número de versión en
requirements.txt - Pyodide provee directamente versiones específicas de los paquetes
- Por ahora los paquetes pueden usarse en desarrollo local, y en unas semanas también será posible desplegar Workers con dependencias definidas en
requirements.txt - Cloudflare dice que mantiene su propio fork de Pyodide para ofrecer parches específicos del runtime de Workers y que reflejará esos cambios en el upstream de Pyodide
Cold start y snapshots de memoria
- Pyodide por sí solo pesa 6.4 MB, y los paquetes de Python también pueden ser grandes
- Si se mete Pyodide tal cual dentro de un Worker y se sube a Cloudflare, el costo de cargar un isolate nuevo aumenta y el cold start se vuelve más lento
- En una computadora rápida y con buena red, Pyodide tarda unos 2 segundos en inicializarse en el navegador: 1 segundo de red y 1 segundo de CPU
- Al ejecutar
npx wrangler@latest deploy, el proceso de despliegue funciona así- Wrangler sube el código Python y
requirements.txta la Workers API - El runtime de Workers valida el código Python y las dependencias
- Crea un isolate nuevo e inyecta automáticamente Pyodide y los paquetes especificados
- Escanea y ejecuta las sentencias import del código del Worker, y luego genera un snapshot de la memoria lineal de WebAssembly del Worker
- Despliega ese snapshot y el código Python en la red de Cloudflare
- Igual que con los Workers de JavaScript, ejecuta el scope de nivel superior
- Wrangler sube el código Python y
- Cuando llega una solicitud, se carga ese snapshot para hacer bootstrap del Worker en el isolate, evitando así el costoso costo de inicialización
- El cold start de un Python Worker básico baja a menos de 1 segundo
-
Reutilización de snapshots
- Actualmente, el snapshot de memoria que se genera al subir un Python Worker es exclusivo de ese Worker y no puede compartirse con otros Python Workers, aunque gran parte sea igual
- Cloudflare cree que puede crear por adelantado un único snapshot compartido y precargarlo en un pool de isolates pre-warmed donde el runtime de Pyodide ya esté cargado
- Con este enfoque, Python Workers se acercaría a un modelo en el que el runtime se ofrece on-demand, como ocurre con los Workers de JavaScript
- Cloudflare considera la reutilización de snapshots como la mayor vía para seguir reduciendo el cold start durante lo que queda de 2024
Gestión de versiones de Python con Compatibility Dates
- Cloudflare Workers tiene un modelo en el que se espera que un Worker desplegado una vez siga ejecutándose sin cambios aunque no se actualice
- Esta estabilidad se ofrece mediante Compatibility Dates y Compatibility Flags
- En Python, tanto Pyodide como CPython tienen sus propias versiones, y una versión nueva puede incluir breaking changes
- Las nuevas versiones de Python se lanzan cada agosto, y una nueva versión de Pyodide se publica 6 meses después
- Cuando una nueva versión de Pyodide se agrega a Workers, se coloca detrás de un Compatibility Flag y solo se activa después de la Compatibility Date especificada
- Las versiones de Python tienen un periodo de soporte de 5 años, y las versiones cuyo soporte termina dejan de recibir parches de seguridad
- Incluso después de 5 años, un Python Worker que siga en una versión antigua de Python pasará automáticamente a la siguiente versión más antigua de Python
- Cloudflare espera que, en la mayoría de los casos, los Python Workers sigan funcionando sin problemas, pero recomienda actualizar regularmente la compatibility date para mantenerse dentro del periodo de soporte
- Entre una versión de Python y otra, los paquetes también se actualizarán y agregarán con el mismo esquema opt-in; un ejemplo de flag es
python_3.17_packages_2025_03_01
Bindings de Python Workers
- Gracias a Pyodide FFI, Python puede acceder directamente a objetos, métodos y funciones de JavaScript
- Con esta estructura, todas las binding API para los recursos de Cloudflare están soportadas en Python Workers desde el primer día
- El objeto
envdel handler de Python es un objeto de JavaScript, y Pyodide proporciona una API proxy que maneja la conversión de tipos entre lenguajes - En un namespace de KV, se puede hacer
awaitaenv.FOO.put()yenv.FOO.get()desde Python para escribir y leer valores - Las Web API también pueden usarse de la misma forma, y se pueden importar globales de JavaScript como
Responsedesde el módulojs
Plan para una API más idiomática de Python
- Cloudflare reconoce que una forma como
from js import Responseno es muy propia de Python, y planea ofrecer una API más idiomática para Python Workers - workers-rs, lanzado en 2021, ofrecía bindings al estilo de Rust para cada API de JavaScript de Workers
- En Python Workers planean seguir la misma dirección, comenzando primero con los bindings de Workers AI y Vectorize
- El workers-rs de Rust usa dependencias externas y necesita actualizaciones, pero la API de Python Workers estará integrada directamente en el runtime de Workers
- Al actualizar la compatibility date, se podrá usar la API idiomática de Python más reciente
- También existe la posibilidad de ofrecer parte de la raw socket API de la biblioteca estándar de Python basándose en la API
connect()de JavaScript de Workers - Cloudflare espera comenzar un esfuerzo para crear una API serverless estandarizada que ofrezca las mismas capacidades que JavaScript y que al mismo tiempo sea fácil de usar para desarrolladores de Python
Próximos pasos
- Para dar soporte adecuado a un nuevo lenguaje de programación, hace falta una inversión mucho mayor que un simple “hello world”
- Según la encuesta de Stack Overflow de 2023, Python aparece como el lenguaje más usado después de JavaScript
- Cloudflare afirma que seguirá mejorando el rendimiento de Python Workers y ampliando el alcance del soporte para paquetes de Python
- Los canales de retroalimentación son el canal de Python Workers en Cloudflare Developers Discord y workerd GitHub discussion
1 comentarios
Opiniones de Hacker News
Me alegra que Cloudflare le esté prestando más atención a ejecutar Python en WebAssembly en el Edge
Resumiendo desde la perspectiva de quienes en Wasmer hemos trabajado en la ejecución de Python basado en WebAssembly en el Edge: Cloudflare Workers habilita Python en el Edge usando Pyodide, que es Python compilado a WebAssembly con Emscripten
La arquitectura consiste en integrar Pyodide con Workerd y reducir el tiempo de arranque con snapshots de V8; en el mejor de los casos, un cold start de Python ronda 1 segundo
Sin embargo, el enfoque actual queda atado a la versión de Python/Pyodide incorporada en Workerd, y la resolución de paquetes también está fuertemente acoplada a Workerd, por lo que es muy probable que en runtime solo se permitan paquetes nativos precompilados. Por ejemplo, usar una versión específica de numpy podría ser complicado
Al estar arquitectónicamente atado al mundo JS/V8, con la arquitectura actual parece difícil lograr tiempos de arranque por debajo de 100 ms
Aun así, esta versión es bienvenida, y espero que la gente cree apps geniales
https://pyodide.org/
https://github.com/cloudflare/workerd/blob/main/docs/pyodide...
https://github.com/cloudflare/workerd/pull/1875
Editado: cambié “prueba de concepto” por “versión” para reflejar la explicación del equipo de Cloudflare
Planeamos actualizar con bastante frecuencia paquetes como langchain o el numpy mencionado
Sería bueno que explicaras más por qué ves a V8 como un factor limitante. V8 es un runtime de WebAssembly potente, y la mayoría de las optimizaciones planeadas no dependen demasiado del motor subyacente
Además, esto no es una prueba de concepto, sino una beta que seguiremos mejorando hasta llegar a GA
https://wasmer.io/templates?language=python
Cloudflare tiene muchas cosas buenas en hosting y bases de datos, pero como plataforma para desarrolladores parece que no se ha sabido vender bien, y Vercel o Netlify se han llevado bastante cuota de reconocimiento
Por otro lado, me pregunto si Cloudflare ofrece un servicio de hosting de contenedores independiente del lenguaje, como Google Cloud Run
El precio y el producto son excelentes
Algunas funciones directamente no funcionaban, y otras solo estaban soportadas parcialmente
Si uno tiene que gastar valioso tiempo de desarrollo resolviendo esos problemas, plataformas alternativas como Vercel, Netlify o Deno Deploy resultan más fluidas para las necesidades del equipo y hacen más fácil enfocarse en el desarrollo en lugar de en problemas de infraestructura
Los costos de ancho de banda en Vercel y Netlify son entre 40 y 50 veces más caros que en la mayoría de los proveedores cloud, y en Cloudflare el ancho de banda casi no representa un costo
Las invocaciones de funciones Edge también son 6 veces más caras en Vercel y Netlify que en Cloudflare. Esa cifra excluye el costo del tiempo de cómputo, que en Cloudflare es gratuito
Casi la única razón por la que Vercel es popular es porque es el mejor lugar para alojar NextJS, y quizá por eso hacen que sea difícil desplegar NextJS en otros lugares
Llevo unos 4 años usando Workers en producción y me gustan, pero la mayoría de mis apps siguen corriendo en contenedores
Probé JS Workers en un sitio puesto delante de Cloudflare, y fue fácil de usar y muy rápido. Me gustaría migrar toda la app Django detrás del sitio, incluso usando la base de datos D1
Como el tráfico es bajo sigo en el tier gratuito, pero es tan fácil crear con eso que estaría dispuesto a pagar
Sería útil tener una comparación de rendimiento con un Worker en JS. Es interesante, pero como hay varias capas involucradas, potencialmente podría ser más lento
No espero un rendimiento equivalente, pero estaría bueno conocer los trade-offs aproximados
Actualmente el cold start de Python es más lento que el de un Worker JavaScript del mismo tamaño. Un Worker básico de “Hello World” escrito en JavaScript tiene un tiempo de cold start casi cercano a 0, pero un Worker en Python está por debajo de 1 segundo
Esto se debe a que, cuando llega una solicitud, hay que cargar Pyodide en el Worker bajo demanda; en el post del blog se explica el trabajo para reducirlo haciendo que Pyodide esté disponible de antemano
Sin embargo, una vez que un Worker en Python termina el cold start, la diferencia se vuelve casi marginal y puede ser de unos pocos milisegundos, según lo que ocurra durante la solicitud
Al cruzar el “puente” entre JavaScript y WebAssembly, por ejemplo al realizar operaciones de entrada/salida o tareas asíncronas, hay un pequeño costo. Pero hay que pensarlo en términos de microsegundos, no milisegundos, y en general es muy pequeño
Quienes usan Workers sensibles al rendimiento ya a veces los escriben en Rust: https://github.com/cloudflare/workers-rs Esto también depende del puente entre JavaScript y WebAssembly
El intérprete de Python basado en WebAssembly que ofrece Pyodide no es tan rápido como las optimizaciones acumuladas durante años para hacer rápido a JavaScript en V8. Aun así, Pyodide todavía está en una etapa temprana en comparación con el motor JS de V8, y hay áreas donde parecen posibles grandes mejoras de rendimiento. Quieren llevar mejoras de rendimiento upstream, y también hay propuestas de WebAssembly que ayudarían
Me pregunto si eligieron lzma para mostrar el aislamiento de forma intencional, o si fue una coincidencia por las noticias tecnológicas de la semana pasada
https://news.ycombinator.com/item?id=39865810
Parece que será un verdadero game changer para ejecutar trabajos relacionados con IA en Cloudflare. Lo venía esperando desde hace bastante
Si todavía no los viste, también vale la pena revisar los otros dos anuncios de hoy
“Leveling up Workers AI: General Availability and more new capabilities”
https://blog.cloudflare.com/workers-ai-ga-huggingface-loras-...
“Running fine-tuned models on Workers AI with LoRAs”
https://blog.cloudflare.com/fine-tuned-inference-with-loras
Lo probé hoy, me gustó y pude llegar a ejecutarlo muy rápido
Pero me pregunto cómo hacer que el entorno de desarrollo local entienda las bibliotecas integradas en la implementación de Python de CFW
Por ejemplo, existe la biblioteca
asgi, y quisiera que el linter no la marque como desconocida. Pero esa biblioteca solo existe en runtime para el handleron_fetchy no está realmente en la máquina local de desarrollo, así que no encontré una soluciónHe tenido buenos resultados usando CF Pages para sitios estáticos, y también me parecen interesantes los productos de Cloudflare que ofrecen LLM open source como servicio
La razón principal que me frenaba para construir más en Cloudflare era la falta de soporte para Python, y tengo ganas de probar esta función
Es fácil poner algo en marcha rápido y no tener que preocuparse demasiado por la infraestructura
La incorporación de Python es muy bienvenida, y ojalá Go también llegue con soporte de primera clase
Me pregunto cómo funcionará la limitación de usar solo paquetes de Pyodide en builds que no sean triviales
Hay mucho código que no es Python puro, así que para soportar apps reales de producción parece que habría muchas cosas que habría que recompilar manualmente
La adopción por parte de Cloudflare podría ayudar a atraer más paquetes, y si aquí aplica la regla 80/20, podría ser suficientemente bueno
Como también se menciona en el post del blog, el mayor problema es el soporte para paquetes relacionados con servidores y requests. Claramente serían útiles en Cloudflare Workers, pero son difíciles de portar porque suelen usar sockets raw y alguna forma de concurrencia
Me gustaría que CloudFlare implementara Workers con WASM como objetivo de primera clase, junto con una API de uso general que no esté atada a JS Workers
Hoy ya se puede desplegar código WASM, así que en la práctica se puede usar casi cualquier lenguaje, pero no es nativo: se ejecuta dentro de un contexto JS
Hay algo de overhead y torpeza en el despliegue
Creo que, con el tiempo, todos los servicios se desplegarán directamente en runtimes WASM securizados, no en contenedores. Algo parecido al movimiento de imágenes a contenedores
Actualmente no hay una gran ventaja en usar algo como Rust en Cloudflare Edge. Buena parte de la ventaja de rendimiento se ve compensada por el overhead y los tiempos de inicio
Ejemplo: https://github.com/WasmEdge/WasmEdge