Cloudflare Workers peut désormais s’écrire en Python
(blog.cloudflare.com)- Cloudflare Workers prend en charge l’écriture en Python en bêta ouverte, en intégrant directement Pyodide dans workerd, le runtime Workers open source, afin d’étendre son modèle centré sur JavaScript
- Les Python Workers prennent en charge dès le premier jour les bindings existants comme Vectorize, Workers AI, R2 et Durable Objects, et permettent d’importer certains packages Python tels que FastAPI, Langchain et Numpy
- L’implémentation repose sur Pyodide, un port WebAssembly de CPython, et utilise une FFI entre JavaScript et Python pour manipuler les API Request, Response et Fetch, ainsi que les bindings de ressources Cloudflare depuis du code Python
- Lors du déploiement, Cloudflare exécute les imports et crée un snapshot de la mémoire linéaire WebAssembly afin de réduire le coût d’initialisation de Pyodide et des packages, abaissant le cold start d’un Python Worker de base à moins d’une seconde
- Les versions de Python ainsi que les mises à jour de Pyodide et des packages sont gérées via les Compatibility Dates et les Compatibility Flags ; les Workers restant sur une version de Python arrivée au terme de ses 5 ans de support sont automatiquement déplacés vers la version de Python la plus ancienne suivante
Bêta ouverte des Python Workers
- Les Python Workers sont disponibles en bêta ouverte dans Cloudflare Workers
- Contrairement à la prise en charge existante de langages autres que JavaScript, l’implémentation de Python est intégrée directement au runtime workerd
- Les bindings Cloudflare pris en charge dès le premier jour incluent notamment :
- Les Python Workers peuvent importer certains packages Python populaires, comme FastAPI, Langchain ou Numpy
- Aucune étape de build séparée ni toolchain externe n’est nécessaire
Pourquoi la compilation en WebAssembly seule ne suffisait pas
- Workers prend en charge WebAssembly depuis 2018, et chaque Worker s’exécute dans un isolate V8, le même moteur JavaScript que Chrome
- En principe, plusieurs langages, dont Python, pouvaient déjà s’exécuter sur Workers s’ils étaient d’abord compilés en WebAssembly ou en JavaScript
- Les applications réelles exigent davantage qu’un simple « hello world » : la prise en charge de l’écosystème de packages que les développeurs utilisent au quotidien est essentielle
- Les Python Workers constituent une première forme de prise en charge de premier rang des langages autres que JavaScript dans Workers
Flux d’exécution d’un Python Worker
- Avec l’intégration de Pyodide dans workerd, un Python Worker peut traiter les requêtes via un handler
on_fetch - Dans
wrangler.toml, un fichier.pyest défini commemain, etcompatibility_flags = ["python_workers"]est configuré - Lors de l’exécution de
npx wrangler@latest dev, le runtime Workers se charge de :- déterminer la version de Pyodide nécessaire à partir de la compatibility date
- créer l’isolate du Worker et y injecter automatiquement Pyodide
- fournir le code Python à Pyodide
- L’environnement d’exécution Python est géré en interne et fourni par la plateforme, d’une manière similaire aux JavaScript Workers
Pourquoi Pyodide convient aux Workers
- Pyodide est une implémentation qui porte CPython vers WebAssembly et interprète le code Python sans le précompiler dans un autre format
- Il fournit la majeure partie de la bibliothèque standard Python, ainsi qu’une interface de fonctions étrangères (FFI) permettant d’appeler des API JavaScript depuis Python
- Il est conçu pour compiler l’interpréteur principal et chaque module Python natif en modules WebAssembly séparés, puis permettre leur liaison dynamique au runtime
- Plusieurs Workers s’exécutant sur la même machine peuvent partager le footprint de code des modules, un point important dans l’environnement Cloudflare, qui exécute des milliers de Workers par machine
- La plupart des langages ciblant WebAssembly ne prennent pas encore en charge la liaison dynamique, si bien que chaque application inclut souvent sa propre copie du runtime du langage
Pyodide et la liaison dynamique WebAssembly
- WebAssembly étant un environnement sandboxé séparé du runtime hôte, les opérations autres que le calcul pur, comme la lecture de fichiers, doivent être fournies par l’environnement d’exécution et importées par le module
- La cible WebAssembly de LLVM se divise en trois catégories :
wasm32-unknown-unknown: ne fournit pas de bibliothèque standard C ni d’interface d’appels systèmewasm32-wasi: utilise les interfaces système standard implémentées par le runtime WASIwasm32-unknown-emscripten: définit les imports nécessaires et génère en parallèle une bibliothèque JavaScript qui les implémente
- Pyodide utilise Emscripten pour fournir l’interpréteur CPython, la FFI Python-JavaScript et des packages Python tiers compilés en WebAssembly
- Parmi ces cibles, seul Emscripten prend actuellement en charge la liaison dynamique
- WASI ne prend pas encore en charge les abstractions de liaison dynamique
dlopen/dlsymutilisées par CPython
La FFI qui relie Python et JavaScript
- L’exemple de Python Worker importe l’objet JavaScript
Responseavecfrom js import Response - La FFI de Pyodide permet d’accéder à toutes les fonctionnalités JavaScript depuis Python, ce qui réduit le risque que les Python Workers soient moins capables que les JavaScript Workers sur le plan fonctionnel
- Les types immuables comme les chaînes et les nombres sont convertis de manière transparente entre les deux langages, tandis que les objets mutables sont enveloppés dans des proxys appropriés
- Lorsqu’un objet JavaScript est transmis à Python, Pyodide vérifie les protocoles JavaScript pris en charge par cet objet et construit dynamiquement une classe implémentant les protocoles Python correspondants
- S’il prend en charge le protocole d’itération JavaScript, il prend aussi en charge le protocole d’itération Python
- S’il s’agit d’une Promise ou d’un thenable, il devient en Python un objet awaitable
- Dans le flux de traitement des requêtes, l’objet JavaScript
Requestentrant est enveloppé dans unJsProxyaccessible depuis le code Python, et la valeur de retour du handler Python est convertie en objet JavaScriptResponse
Liaison dynamique et packages Python
- De nombreux packages Python chargent des bibliothèques natives via la FFI C ; pour fonctionner dans le runtime Workers, ces bibliothèques doivent être compilées en WebAssembly
- Pyodide est construit avec Emscripten afin de remplacer la FFI C de Python et, lorsqu’un package tente de charger une bibliothèque native, le runtime Workers charge le module WebAssembly fourni
- La liaison dynamique permet à Pyodide de prendre en charge plusieurs packages Python ayant des dépendances sur des bibliothèques natives
- La liaison statique impose de charger tout le code nécessaire avant l’exécution du binaire, tandis que la liaison dynamique permet de ne payer ce coût qu’au moment où il est nécessaire
- Workers crée pour chaque Worker un système de fichiers ressemblant à une distribution Python au runtime, tout en partageant les fichiers de base entre Workers
- Actuellement, les fichiers sont partagés entre Workers mais copiés pour chaque nouvel isolate ; à l’avenir, Cloudflare estime pouvoir partager davantage de ressources sous-jacentes grâce à des techniques de copy-on-write
Prise en charge des bibliothèques client et serveur HTTP
- Python compte de nombreuses bibliothèques clientes HTTP comme httpx, urllib3 ou requests, mais elles ne fonctionnent pas par défaut dans Pyodide
- Ces bibliothèques utilisent des sockets bruts, et le modèle de sécurité du navigateur ainsi que CORS ne les autorisent pas ; il faut donc une autre approche dans le runtime Workers
-
Clients asynchrones
- Les bibliothèques capables d’effectuer des requêtes asynchrones, comme aiohttp et httpx, peuvent utiliser la Fetch API de Workers
- Cloudflare patche les bibliothèques via la FFI de Pyodide afin qu’elles utilisent la Fetch API de JavaScript
- Le patch de httpx tient en moins de 100 lignes de code
-
Clients synchrones et limites
- Beaucoup d’API Python sont synchrones et ne peuvent donc pas utiliser directement l’API Fetch asynchrone
- urllib3 a reçu une contribution pour la prise en charge de Pyodide dans le navigateur, utilisant
Atomics.wait()avec un fetch worker thread, ou bien un XMLHttpRequest synchrone - Cloudflare Workers ne prend actuellement pas en charge les worker threads ni
XMLHttpRequestsynchrone ; ces deux approches ne fonctionnent donc pas dans Python Workers - Les requêtes synchrones ne sont pas prises en charge pour le moment
-
WebAssembly Stack Switching
- WebAssembly dispose d’une proposition de stage 3 ajoutant le changement de pile, et V8 en possède une implémentation
- Les contributeurs de Pyodide ajoutent la prise en charge du stack switching depuis septembre 2022, et elle est presque prête
- Une fois ce support intégré,
run_syncde Pyodide pourra bloquer en attendant la fin d’un awaitable, ouvrant ainsi une voie vers la prise en charge des requêtes synchrones
FastAPI et ASGI
- FastAPI est une bibliothèque très utilisée pour définir des serveurs Python et s’appuie sur le protocole ASGI
- Les applications FastAPI ne lisent ni n’écrivent directement dans les sockets ; en général, un serveur ASGI comme uvicorn se charge du traitement des sockets bruts
- Grâce à cette architecture, FastAPI peut fonctionner sur Cloudflare Workers sans être patché ni remplacé
- Il suffit de remplacer uvicorn par un serveur ASGI exécutable dans Workers
- Une première implémentation se trouve dans asgi.py de workerd et est incluse dans le fork de Pyodide maintenu par Cloudflare
- Cloudflare prévoit de l’envoyer upstream vers Pyodide après l’avoir enrichie et avoir ajouté une meilleure couverture de tests
Importer des packages Python
- Python Workers prend en charge certains packages Python fournis directement par Pyodide
- Les packages pris en charge incluent notamment numpy, httpx, FastAPI et Langchain
- Pour importer un package, ajoutez son nom dans
requirements.txt, sans numéro de version - La version précise de chaque package est fournie directement par Pyodide
- Pour l’instant, les packages peuvent être utilisés en développement local, et le déploiement de Workers avec des dépendances définies dans
requirements.txtdevrait devenir possible dans les prochaines semaines - Cloudflare indique maintenir son propre fork de Pyodide afin de fournir des patches propres au runtime Workers, tout en réintégrant les changements upstream dans Pyodide
Démarrages à froid et snapshots mémoire
- Pyodide lui-même pèse 6,4 Mo, et les packages Python peuvent eux aussi être volumineux
- Si Pyodide est simplement intégré tel quel dans un Worker puis uploadé vers Cloudflare, le coût de chargement d’un nouvel isolate augmente, ce qui ralentit les démarrages à froid
- Sur un ordinateur rapide avec une bonne connexion, Pyodide met environ 2 secondes à s’initialiser dans le navigateur, dont 1 seconde de réseau et 1 seconde de CPU
- Lors de
npx wrangler@latest deploy, le processus de déploiement fonctionne comme suit- Wrangler uploade le code Python et
requirements.txtvers l’API Workers - Le runtime Workers valide le code Python et ses dépendances
- Il crée un nouvel isolate et y injecte automatiquement Pyodide ainsi que les packages spécifiés
- Il scanne et exécute les instructions import du code Worker, puis génère un snapshot de la mémoire linéaire WebAssembly du Worker
- Il déploie ce snapshot et le code Python sur le réseau Cloudflare
- Comme pour un Worker JavaScript, le scope top-level est exécuté
- Wrangler uploade le code Python et
- Lorsqu’une requête arrive, ce snapshot est chargé pour amorcer le Worker dans l’isolate, ce qui évite le coûteux coût d’initialisation
- Le démarrage à froid d’un Worker Python de base descend à moins d’une seconde
-
Réutilisation des snapshots
- Le snapshot mémoire généré actuellement lors de l’upload d’un Worker Python est propre à ce Worker ; il ne peut pas être partagé avec d’autres Python Workers, même s’ils sont en grande partie identiques
- Cloudflare estime pouvoir créer à l’avance un snapshot partagé unique et le précharger dans un pool d’isolates préchauffés où le runtime Pyodide est déjà chargé
- Avec cette approche, les Python Workers se rapprocheraient eux aussi du modèle dans lequel le runtime est fourni à la demande, comme pour les Workers JavaScript
- Cloudflare considère la réutilisation des snapshots comme le principal levier pour réduire encore les démarrages à froid sur le reste de 2024
Gestion des versions de Python avec Compatibility Dates
- Cloudflare Workers repose sur un modèle dans lequel un Worker déployé une fois est censé continuer à s’exécuter sans mise à jour
- Cette stabilité est assurée par les Compatibility Dates et les Compatibility Flags
- En Python, Pyodide et CPython ont chacun leurs propres versions, et les nouvelles versions peuvent inclure des breaking changes
- Une nouvelle version de Python sort chaque année en août, et une nouvelle version de Pyodide est publiée six mois plus tard
- Lorsqu’une nouvelle version de Pyodide est ajoutée à Workers, elle est placée derrière un Compatibility Flag et n’est activée qu’après la Compatibility Date spécifiée
- Les versions de Python bénéficient d’une période de support de 5 ans, et les versions de Python arrivées en fin de support ne reçoivent plus de correctifs de sécurité
- Au bout de 5 ans, les Python Workers qui restent sur une ancienne version de Python sont automatiquement migrés vers la version de Python la plus ancienne encore prise en charge
- Cloudflare s’attend à ce que, dans la plupart des cas, les Python Workers continuent de fonctionner sans problème, mais recommande de mettre régulièrement à jour la compatibility date afin de rester dans la période de support
- Entre deux versions de Python, les packages seront aussi mis à jour ou ajoutés selon le même modèle opt-in ; un exemple de flag serait de la forme
python_3.17_packages_2025_03_01
Bindings des Python Workers
- Grâce à la FFI de Pyodide, il est possible d’accéder directement depuis Python aux objets, méthodes et fonctions JavaScript
- Cette architecture permet à toutes les API de binding des ressources Cloudflare d’être prises en charge dans Python Workers dès le premier jour
- L’objet
envd’un handler Python est un objet JavaScript, et Pyodide fournit une API de proxy qui gère la conversion de types entre les langages - Pour un namespace KV, on peut faire
awaitsurenv.FOO.put()etenv.FOO.get()en Python pour écrire et lire des valeurs - Les Web API peuvent s’utiliser de la même manière, et les globals JavaScript comme
Responsepeuvent être importés depuis le modulejs
Des API plus idiomatiques pour Python en projet
- Cloudflare reconnaît qu’une forme comme
from js import Responsen’est pas très « Pythonique », et prévoit de fournir des API plus idiomatiques pour Python Workers - workers-rs, lancé en 2021, proposait des bindings idiomatiques Rust pour chaque API JavaScript de Workers
- Cloudflare prévoit la même approche pour Python Workers, en commençant par les bindings Workers AI et Vectorize
- Alors que le workers-rs de Rust utilise des dépendances externes qui doivent être mises à jour, les API de Python Workers devraient être intégrées directement au runtime Workers
- En mettant à jour la compatibility date, il sera possible d’utiliser les dernières API idiomatiques pour Python
- Il est aussi possible qu’une partie de l’API de raw sockets de la bibliothèque standard Python soit fournie sur la base de l’API JavaScript
connect()de Workers - Cloudflare espère lancer un effort pour créer des API serverless standardisées, offrant les mêmes fonctionnalités qu’en JavaScript tout en étant faciles à utiliser pour les développeurs Python
Perspectives
- Prendre correctement en charge un nouveau langage de programmation demande un investissement bien supérieur à un simple “hello world”
- D’après l’enquête Stack Overflow 2023, Python apparaît comme le langage le plus utilisé après JavaScript
- Cloudflare indique vouloir continuer à améliorer les performances de Python Workers et élargir la prise en charge des packages Python
- Les canaux de feedback sont le canal Python Workers du Discord Cloudflare Developers et la discussion GitHub de workerd
1 commentaires
Avis sur Hacker News
C’est une bonne chose que Cloudflare s’intéresse davantage à l’exécution de Python en WebAssembly à l’Edge
Du point de vue de quelqu’un qui a travaillé chez Wasmer sur l’exécution de Python basée sur WebAssembly à l’Edge, Cloudflare Workers active Python à l’Edge en utilisant Pyodide, c’est-à-dire Python compilé en WebAssembly avec Emscripten
L’architecture consiste à intégrer Pyodide à Workerd et à réduire le temps de démarrage avec des snapshots V8 ; dans le meilleur des cas, un cold start Python prend environ 1 seconde
Toutefois, l’approche actuelle lie l’application à la version de Python/Pyodide intégrée à Workerd, et la résolution des packages est elle aussi fortement couplée à Workerd, si bien qu’au runtime seuls des packages natifs précompilés seront probablement autorisés. Par exemple, utiliser une version précise de numpy pourrait être délicat
Comme l’architecture est structurellement liée au monde JS/V8, il semble difficile d’atteindre un temps de démarrage inférieur à 100 ms avec l’architecture actuelle
Cela dit, cette release est bienvenue, et j’ai hâte de voir les applications intéressantes que les gens vont construire
https://pyodide.org/
https://github.com/cloudflare/workerd/blob/main/docs/pyodide...
https://github.com/cloudflare/workerd/pull/1875
Modification : « preuve de concept » a été remplacé par « release » pour refléter les explications de l’équipe Cloudflare
Nous prévoyons de mettre à jour assez fréquemment des packages comme langchain ou le numpy mentionné
J’aimerais en savoir plus sur les raisons pour lesquelles vous considérez V8 comme un facteur limitant. V8 est un runtime WebAssembly puissant, et la plupart des optimisations prévues ne dépendent pas fortement du moteur sous-jacent
De plus, ce n’est pas une preuve de concept, mais une bêta qui continuera à être améliorée jusqu’à la GA
https://wasmer.io/templates?language=python
Cloudflare a beaucoup de très bons produits côté hébergement et bases de données, mais en tant que plateforme développeur, son marketing n’est pas très efficace, si bien que Vercel et Netlify semblent avoir capté une part importante de notoriété
À part ça, je me demande si Cloudflare propose un service d’hébergement de conteneurs agnostique au langage, comme Google Cloud Run
Les prix et les produits sont excellents
Certaines fonctionnalités ne fonctionnaient pas du tout, d’autres n’étaient que partiellement prises en charge
Si vous devez consacrer un temps de développement précieux à résoudre ce genre de problèmes, des plateformes alternatives comme Vercel, Netlify ou Deno Deploy étaient plus fluides pour les besoins de l’équipe, et permettaient de se concentrer plus facilement sur le développement plutôt que sur l’infrastructure
Les coûts de bande passante sont 40 à 50 fois plus élevés chez Vercel et Netlify que chez la plupart des fournisseurs cloud, alors que chez Cloudflare la bande passante ne coûte presque rien
Les appels de fonctions Edge sont aussi 6 fois plus chers chez Vercel et Netlify que chez Cloudflare. Et cela n’inclut pas le coût du temps de calcul, qui est gratuit chez Cloudflare
La seule vraie raison de la popularité de Vercel est probablement que c’est le meilleur endroit pour héberger NextJS, et c’est peut-être pour cela qu’ils rendent le déploiement de NextJS ailleurs difficile
J’utilise Workers en production depuis environ 4 ans et j’aime beaucoup, mais la plupart de mes apps tournent encore dans des conteneurs
J’ai utilisé des JS Workers sur un site placé derrière Cloudflare : c’était facile à utiliser et très rapide. J’aimerais déplacer toute l’application Django derrière le site, en utilisant aussi une base de données D1
Le trafic est faible, donc je reste dans le niveau gratuit, mais c’est tellement facile à construire que je serais aussi prêt à payer
Ce serait utile d’avoir une comparaison des performances avec les Workers JS. C’est intéressant, mais avec plusieurs couches imbriquées, ça semble potentiellement plus lent
Je ne m’attends pas à des performances équivalentes, mais ce serait bien de connaître les compromis à grands traits
Aujourd’hui, le démarrage à froid de Python est plus lent que celui d’un Worker JavaScript de même taille. Un Worker « Hello World » de base écrit en JavaScript a un temps de démarrage à froid presque nul, tandis qu’un Worker Python est sous la seconde
C’est parce qu’il faut charger Pyodide à la demande dans le Worker lorsqu’une requête arrive ; l’article de blog explique le travail réalisé pour rendre Pyodide disponible à l’avance afin de réduire cela
Cela dit, une fois qu’un Worker Python a terminé son démarrage à froid, l’écart devient marginal, et peut n’être que de quelques millisecondes selon ce qui se passe pendant la requête
Lorsqu’on traverse le « pont » entre JavaScript et WebAssembly, par exemple pour effectuer des entrées/sorties ou des opérations asynchrones, il y a un léger coût. Mais il faut plutôt le voir à l’échelle des microsecondes, pas des millisecondes, et il est généralement très faible
Les personnes qui utilisent des Workers sensibles aux performances les écrivent déjà parfois en Rust : https://github.com/cloudflare/workers-rs Cela aussi repose sur le pont entre JavaScript et WebAssembly
L’interpréteur Python basé sur WebAssembly fourni par Pyodide n’est pas aussi rapide que les années d’optimisations accumulées pour rendre JavaScript rapide dans V8. Mais Pyodide en est encore à ses débuts par rapport au moteur JS de V8, et certains axes semblent permettre de gros gains de performances. Nous aimerions reverser les améliorations de performance en upstream, et certaines propositions WebAssembly pourraient aussi aider
Je me demande si le choix de lzma pour montrer l’isolation était intentionnel, ou si c’est juste une coïncidence liée à l’actualité tech de la semaine dernière
https://news.ycombinator.com/item?id=39865810
Ça pourrait vraiment changer la donne pour exécuter des charges liées à l’IA chez Cloudflare. J’attendais ça depuis assez longtemps
Si tu ne les as pas encore vues, les deux autres annonces publiées aujourd’hui valent aussi le détour
« 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
Je l’ai essayé aujourd’hui, c’était bien, et j’ai pu arriver à quelque chose qui s’exécute très rapidement
En revanche, je me demande comment faire pour que l’environnement de développement local comprenne les bibliothèques intégrées à l’implémentation Python de CFW
Par exemple, il y a la bibliothèque
asgi, et j’aimerais que le linter ne la signale pas comme inconnue. Mais cette bibliothèque n’existe qu’au runtime du handleron_fetch, et pas réellement sur ma machine de développement locale, donc je n’ai pas trouvé de solutionJ’ai eu de bons résultats avec CF Pages pour des sites statiques, et les produits Cloudflare qui proposent des LLM open source comme un service sont aussi intéressants
La principale chose qui m’empêchait de construire davantage sur Cloudflare était le manque de support de Python, et j’ai hâte d’essayer cette fonctionnalité
Il est facile de lancer quelque chose rapidement, sans trop se soucier de l’infrastructure
L’ajout de Python est très bienvenu, et j’aimerais que Go bénéficie aussi d’un support de premier ordre
Je me demande comment la contrainte de n’utiliser que les paquets Pyodide fonctionnera pour des builds non triviaux
Il y a beaucoup de code qui n’est pas du Python pur, et pour prendre en charge des applications de production réalistes, il semble qu’il y aura beaucoup de choses à reconstruire manuellement
L’adoption par Cloudflare pourrait aider à attirer davantage de paquets, et si la règle des 80/20 s’applique ici, cela pourrait être largement suffisant
Comme mentionné dans l’article de blog, le plus gros problème est le support des paquets liés aux serveurs et aux requêtes. Ils sont clairement utiles sur Cloudflare Workers, mais ils utilisent souvent des sockets brutes et une certaine forme de concurrence, ce qui les rend difficiles à porter
J’aimerais que CloudFlare implémente des Workers dont WASM serait une cible de premier ordre, avec une API générale qui ne soit pas liée aux JS Workers
Aujourd’hui, on peut déjà déployer du code WASM, donc utiliser pratiquement n’importe quel langage, mais ce n’est pas natif : il s’exécute dans un contexte JS
Il y a un peu d’overhead et de maladresse au déploiement
Je pense qu’à terme, tous les services seront déployés directement sur des runtimes WASM sécurisés, plutôt que dans des conteneurs. Un peu comme le passage des images aux conteneurs
À l’heure actuelle, l’intérêt d’utiliser quelque chose comme Rust sur Cloudflare Edge n’est pas énorme. Une grande partie du gain de performance est annulée par l’overhead et le temps de démarrage
Exemple : https://github.com/WasmEdge/WasmEdge