À compter d'aujourd'hui, la version bêta ouverte vous permet d'écrire vos instances Cloudflare Workers en Python.
Cette nouvelle prise en charge de Python diffère de la manière dont les instances Workers ont, jusqu'à présent, pris en charge les langages autres que JavaScript. Dans ce cas, nous avons directement intégré une implémentation de Python dans workerd, le runtime open source de Workers. Toutes les liaisons (y compris les liaisons avec Vectorize, Workers AI, R2, Durable Objects et bien d'autres) sont prises en charge dès le lancement. Python Workers permet d'importer un sous-ensemble de packages Python populaires, parmi lesquels FastAPI, Langchain, Numpy et d'autres. Aucune étape de développement ou chaîne d'outils externes supplémentaire n'est nécessaire.
Pour parvenir à ce résultat, nous avons dû repousser les limites de tous nos systèmes, du runtime lui-même jusqu'à notre système de déploiement, et même au contenu du bundle Worker publié sur notre réseau. Vous pouvez consulter la documentation et commencer à utiliser la solution dès aujourd'hui.
Dans cet article, nous souhaitons dévoiler le cycle de vie interne d'une instance Python Workers, partager ce que nous avons appris durant le processus de développement et présenter nos projets pour l'avenir.
Au-delà de la « simple compilation dans WebAssembly »
Cloudflare Workers prend en charge WebAssembly depuis 2018 ; chaque instance Workers est un isolat V8 qui utilise le même moteur JavaScript que le navigateur web Chrome. En principe, depuis des années, il est possible d'écrire des instances Workers dans n'importe quel langage (parmi lesquels Python), dès lors que la compilation du code se déroule d'abord dans WebAssembly ou JavaScript.
En pratique, toutefois, ce n'est pas parce qu'une chose est possible qu'elle est simple, et ce n'est pas parce que « hello world » fonctionne que l'on peut créer une application de manière fiable. Le développement d'applications complètes nécessite la prise en charge d'un écosystème de packages avec lequel les développeurs ont l'habitude de créer des solutions. Pour qu'une plateforme prenne réellement en charge un langage de programmation, il est nécessaire de faire beaucoup plus que simplement montrer comment compiler du code à l'aide de chaînes d'outils externes.
Les instances Python Workers sont différentes de ce que nous avons fait jusqu'à présent. Nous n'en sommes encore qu'au commencement, et la solution se trouve encore dans sa version bêta, mais nous pensons qu'elle démontre ce que peut offrir une excellente prise en charge de langages de programmation autres que JavaScript dans Workers.
Le cycle de vie d'une instance Python Workers
Maintenant que Pyodide est intégré à workerd, vous pouvez écrire une instance Workers comme ceci :
…avec un fichier Wrangler.toml qui renvoie à un fichier .py :
from js import Response
async def on_fetch(request, env):
return Response.new("Hello world!")
…et lorsque vous exécutez la commande npx wrangler@latest dev, le runtime de Workers effectue les actions suivantes :
name = "hello-world-python-worker"
main = "src/entry.py"
compatibility_date = "2024-03-18"
compatibility_flags = ["python_workers"]
Il détermine quelle version de Pyodide est nécessaire, en fonction de la date de compatibilité définie
Il crée un isolat pour l'instance Workers, puis injecte automatiquement Pyodide
Il utilise Pyodide pour servir le code Python
Tout se déroule en coulisses, sans nécessiter de chaîne d'outils ni d'étapes de précompilation supplémentaires. L'environnement d'exécution Python est fourni et reflète le fonctionnement existant des instances Workers écrites en JavaScript.
Un interpréteur Python intégré dans le runtime Workers
Tout comme JavaScript propose de nombreux moteurs, Python propose de nombreuses implémentations permettant d'exécuter du code Python. CPython est l’implémentation de référence de Python ; si vous avez déjà utilisé Python, il s'agit presque certainement de celle que vous avez utilisée, communément appelée « Python ».
Pyodide est un port de CPython pour WebAssembly. Il interprète le code Python sans qu'il soit nécessaire de précompiler le code Python dans un autre format. Il s'exécute dans un navigateur web ; jetez un coup d'œil à ce REPL. Il est fidèle à la version de CPython que les développeurs Python connaissent et attendent et fournit la majeure partie de la bibliothèque standard Python. Il propose une interface de fonction étrangère (FFI, « Foreign Function Interface ») avec JavaScript, vous permettant d'appeler une API JavaScript directement depuis Python (vous trouverez plus d'informations à ce sujet ci-dessous). Il propose des packages open source populaires et permet d'importer des packages en langage Python pur directement depuis PyPI.
Pyodide nous a semblé être la solution idéale pour Workers. Il est conçu pour permettre le développement de l'interpréteur principal et de chaque module Python natif sous la forme de modules WebAssembly distincts, liés de manière dynamique lors de l'exécution. Cette approche permet de partager la consommation de ressources liées au code de ces modules parmi l'ensemble des instances Workers en cours d'exécution sur la même machine, au lieu d'exiger que chaque instance Workers fournisse sa propre copie. Cet aspect est essentiel pour assurer le fonctionnement correct de WebAssembly dans l'environnement Workers, dans lequel nous exécutons fréquemment des milliers d'instances Workers par machine. Toutes les instances Workers doivent utiliser le même langage de programmation pour permettre le partage des ressources liées à l'exécution du code de leur runtime. L'exécution de milliers d'instances Workers sur chaque machine nous permet de déployer chaque application sur chaque site à un tarif raisonnable.
À l'instar de JavaScript Workers, avec Python Workers, nous fournissons le runtime :
Pyodide constitue actuellement l'exception : la plupart des langages qui ciblent WebAssembly n'offrent pas encore de prise en charge des liaisons dynamiques, et chaque application finit par apporter sa propre copie du runtime de son langage. Nous espérons constater, à l'avenir, une augmentation du nombre de langages prenant en charge les liaisons dynamiques, afin de les intégrer plus efficacement à Workers.
Comment fonctionne Pyodide
Pyodide exécute le code Python dans WebAssembly, qui est un environnement déployé dans un sandbox, séparé du runtime hôte. Contrairement à l'exécution de code natif, toutes les opérations extérieures au traitement pur (par exemple, les lectures de fichiers) doivent être fournies par un environnement d'exécution, puis être importées par le module WebAssembly.
LLVM fournit trois triples cibles pour WebAssembly :
wasm32-unknown-unknown – Ce backend ne fournit pas de bibliothèque standard C, ni d'interface d'appel système. Pour offrir une prise en charge de ce backend, nous devrions réécrire manuellement chaque appel de système ou de bibliothèque, afin d'utiliser les importations que nous définirions nous-mêmes dans le runtime.
wasm32-wasi – WASI est une interface système normalisée qui définit un ensemble standard d'importations mises en œuvre dans les runtimes WASI tels que wasmtime.
wasm32-unknown-emscripten – Comme WASI, Emscripten définit les importations que doit exécuter un programme WebAssembly, mais génère également une bibliothèque JavaScript qui met en œuvre ces fonctions importées.
Pyodide utilise Emscripten et fournit trois éléments :
Une distribution de l'interpréteur CPython, compilée avec Emscripten
Une interface de fonction étrangère (FFI) entre Python et JavaScript
Un ensemble de packages Python tiers, compilés à l'aide du compilateur d'Emscripten pour WebAssembly.
Parmi ces cibles, seul Emscripten prend actuellement en charge les liaisons dynamiques qui, comme nous l'avons souligné ci-dessus, sont essentielles pour fournir un runtime de langage partagé pour Python, commun aux différents isolats. Pour cela, Emscripten fournit des implémentations de dlopen et dlsyn qui utilisent la bibliothèque JavaScript associée pour modifier la table du programme WebAssembly, afin de lier des modules supplémentaires compilés dans WebAssembly lors de l'exécution. WASI ne prend pas encore en charge les abstractions de liaisons dynamiques dlopen/dlsyn utilisées par CPython.
Pyodide et la magie des interfaces de fonction étrangère (FFI)
Peut-être avez-vous remarqué que dans notre instance Python Workers « Hello World », nous importons Response depuis le module js :
Pourquoi cela ?
from js import Response
async def on_fetch(request, env):
return Response.new("Hello world!")
La plupart des instances Workers sont écrites en JavaScript, et la majeure partie de nos travaux d'ingénierie concernant le runtime Workers sont consacrés à l'amélioration de JavaScript Workers. Il y a un risque à ajouter un second langage : celui-ci pourrait ne jamais offrir des fonctionnalités équivalentes à celles du premier langage, et restera toujours un « citoyen de seconde classe ». L'interface de fonctions étrangères (FFI) de Pyodide joue un rôle essentiel pour éviter cette situation en permettant l'accès à toutes les fonctionnalités JavaScript depuis Python. Cette fonctionnalité peut être utilisée directement par l'auteur de l'instance Workers, et elle est également utilisée pour permettre l'exécution immédiate de packages tels que FastAPI et Langchain, comme nous le démontrerons plus tard dans cet article.
Une interface de fonctions étrangères (FFI, « Foreign Function Interface ») est un système permettant d'appeler, dans un langage, des fonctions mises en œuvre dans un autre langage. Dans la plupart des cas, une FFI est définie par un langage de « niveau supérieur », permettant d'appeler des fonctions mises en œuvre dans un langage système, souvent C ; le module ctypes de Python en est un exemple. Ces sortes d'interfaces de fonctions étrangères sont souvent difficiles à utiliser, en raison de la nature des API C.
L'interface de fonctions étrangères de Pyodide est une interface entre Python et JavaScript, qui sont deux langages orientés objet de haut niveau, présentant de nombreuses similitudes de conception. Lorsqu'ils sont transmis d'un langage à un autre, les types immuables tels que les chaînes et les nombres bénéficient d'une traduction transparente. Tous les objets évolutifs sont enveloppés dans un proxy approprié.
Lorsqu'un objet JavaScript est transmis à Python, Pyodide détermine le protocole JavaScript pris en charge par l'objet et construit dynamiquement une classe Python appropriée, qui assure l'implémentation du protocole Python correspondant. Par exemple, si l'objet JavaScript prend en charge le protocole d'itération JavaScript, le proxy prendra en charge le protocole d'itération Python. Si l'objet JavaScript est de type Promise ou d'un autre type unthenable, l'objet Python sera de type awaitable.
Le cycle de vie d'une requête adressée à une instance Python Workers utilise la FFI de Pyodide, qui enveloppe l'objet JavaScript Request entrant dans un objet JsProxy accessible dans votre code Python. Il convertit ensuite la valeur renvoyée par le handler de l'instance Python Workers en objet JavaScript Response pouvant être renvoyé au client :
from js import JSON
js_array = JSON.parse("[1,2,3]")
for entry in js_array:
print(entry)
Pourquoi les liaisons dynamiques sont essentielles et les liaisons statiques sont insuffisantes
Python est fourni avec une FFI C, et de nombreux packages Python utilisent cette FFI pour importer des bibliothèques natives. Ces bibliothèques sont généralement écrites en C et doivent donc d'abord être compilées dans WebAssembly pour s'exécuter dans le runtime de Workers. Comme nous l'avons remarqué plus haut, Pyodide est développé avec Emscripten, qui remplace la FFI C de Python ; chaque fois qu'un package essaie de charger une bibliothèque native, celle-ci est chargée depuis un module WebAssembly fourni par le runtime de Workers. Les liaisons dynamiques nous permettent ainsi de contourner la FFI C de Python, et permettent par conséquent à Pyodide de prendre en charge une multitude de packages Python dotés de dépendances de bibliothèques natives.
Les liaisons dynamiques sont « facturées en fonction de l'utilisation », tandis que les liaisons statiques sont « facturées à l'avance ». Si votre fichier binaire comporte du code lié de manière statique, même si ce code n'est jamais utilisé, il doit être chargé dès le début pour permettre l'exécution du fichier binaire.
Les liaisons dynamiques permettent au runtime de Workers de partager les modules WebAssembly sous-jacents des packages entre différentes instances Workers en cours d'exécution sur une même machine.
Nous n'examinerons pas ici le fonctionnement détaillé des liaisons dynamiques dans Emscripten ; cependant, la principale leçon à retenir est que le runtime Emscripten extrait les modules WebAssembly d'une abstraction du système de fichiers fournie en JavaScript. Pour chaque instance Workers, lors de l'exécution, nous générons un système de fichiers dont la structure imite une distribution Python pour laquelle les dépendances de l'instance Workers sont installées, mais dont les fichiers sous-jacents sont partagés entre les instances Workers. Ceci permet de partager des fichiers Python et WebAssembly entre différentes instances Workers important la même dépendance. Aujourd'hui, nous sommes en mesure de partager ces fichiers entre les instances Workers, mais nous les copions dans chaque nouvel isolat. Nous pensons pouvoir aller encore plus loin en employant des techniques de copie sur écriture pour partager la ressource sous-jacente entre une multitude d'instances Workers.
Prise en charge des bibliothèques serveur et client
Python dispose d'un grand nombre de bibliothèques client HTTP populaires, parmi lesquelles httpx, urllib3, requests et bien d'autres. Malheureusement, aucune d'elles ne fonctionne immédiatement dans Pyodide. L'ajout de la prise en charge de ces bibliothèques figure parmi les plus anciennes demandes des utilisateurs concernant le projet Pyodide. Les bibliothèques client HTTP Python fonctionnent toutes avec des sockets en mode caractère (« raw sockets »), et le modèle de sécurité du navigateur et CORS ne le permettent pas ; nous avions donc besoin d'élaborer une autre approche pour permettre leur exécution dans le runtime Workers.
Bibliothèques client asynchrones
Pour les bibliothèques qui peuvent transmettre des requêtes de manière asynchrone, notamment aiohttp et httpx, nous pouvons utiliser l'API Fetch pour exécuter ces requêtes. Pour cela, nous appliquons à la bibliothèque un correctif qui lui indique d'exécuter l'API Fetch depuis JavaScript, en tirant parti de la FFI de Pyodide. Le correctif httpx est finalement assez simple : moins de 100 lignes de code. Si nous le simplifions encore davantage, il se présente comme ceci :
Bibliothèques client synchrones
from js import Headers, Request, fetch
def py_request_to_js_request(py_request):
js_headers = Headers.new(py_request.headers)
return Request.new(py_request.url, method=py_request.method, headers=js_headers)
def js_response_to_py_response(js_response):
... # omitted
async def do_request(py_request):
js_request = py_request_to_js_request(py_request)
js_response = await fetch(js_request)
py_response = js_response_to_py_response(js_response)
return py_response
Une autre difficulté liée à la prise en charge des bibliothèques client HTTP Python tient au fait que de nombreuses API Python sont synchrones. Pour ces bibliothèques, nous ne pouvons pas utiliser directement l'API Fetch, car elle est asynchrone.
Heureusement, Joe Marshall a récemment proposé une contribution à urllib3, qui ajoute la prise en support de Pyodide dans les navigateurs web, en effectuant les opérations suivantes :
Vérifier si le blocage avec `
Atomics.wait()
` est possiblea. Si c'est le cas, lancer un thread fetch Workersb. Déléguer l'opération fetch au thread Workers, puis sérialiser la réponse dans un objet SharedArrayBufferc. Dans le thread Python, utiliser Atomics.wait pour bloquer la réponse dans l'objet SharedArrayBufferSi `
Atomics.wait()
` ne fonctionne pas, exécuter une requête XMLHttpRequest synchrone à la place
Malgré cela, Cloudflare Workers ne prend actuellement pas en charge les threads Workers ou les requêtes XMLHttpRequest synchrones ; par conséquent, aucune de ces deux approches ne fonctionne dans Python Workers. Nous ne prenons pas en charge les requêtes synchrones à l'heure actuelle, mais cette situation n'est pas irrémédiable…
Commutation de pile WebAssembly
Il existe une approche qui nous permettra de prendre en charge les requêtes synchrones. Il existe, pour WebAssembly, une proposition en phase 3 d'ajout de la prise en charge de la commutation de pile, dont V8 propose une implémentation. Les contributeurs de Pyodide travaillent à l'ajout de la prise en charge de la commutation de pile vers Pyodide depuis septembre 2022, et celle-ci est presque prête.
Avec cette prise en charge, Pyodide expose une fonction appelée `run_sync
`, qui permet de bloquer l'exécution d'un objet awaitable :
FastAPI et la spécification Asynchronous Server Gateway Interface de Python
from pyodide.ffi import run_sync
def sync_fetch(py_request):
js_request = py_request_to_js_request(py_request)
js_response = run_sync(fetch(js_request))
return js_response_to_py_response(js_response)
FastAPI est l'une des bibliothèques les plus populaires pour définir des serveurs Python. Les applications FastAPI utilisent un protocole appelé Asynchronous Server Gateway Interface (ASGI). Cela signifie que FastAPI ne lit et n'écrit jamais dans un socket lui-même. Une application ASGI s'attend à être connectée à un serveur ASGI, généralement uvicorn. Le serveur ASGI gère tous les raw sockets pour le compte de l'application.
Pour nous, cela signifie que FastAPI s'exécute dans Cloudflare Workers sans correctif ni modification de la bibliothèque FastAPI elle-même. Nous devons simplement remplacer uvicorn par un serveur ASGI approprié, capable de s'exécuter dans une instance Workers. Notre implémentation initiale réside ici, dans le fork de Pyodide que nous gérons. Nous espérons ajouter un ensemble de fonctionnalités plus exhaustif, ajouter la prise en charge de tests, puis répercuter cette implémentation en amont dans Pyodide.
Vous pouvez essayer vous-même en clonant cloudflare/python-workers-examples, puis en exécutant `npx wrangler@latest dev
` dans le répertoire de l'exemple FastAPI.
Importation de packages Python
Les instances Python Workers prennent en charge un sous-ensemble de packages Python fournis directement par Pyodide, parmi lesquels numpy, httpx, FastAPI, Langchain et bien d'autres. Ceci garantit la compatibilité avec le runtime de Pyodide, en associant des versions des packages aux versions de Pyodide, et permet à Pyodide d'appliquer des correctifs aux implémentations internes, comme nous l'avons expliqué ci-dessus, dans le cas de httpx.
Pour importer un package, ajoutez-le simplement à votre fichier requirements.txt
, sans ajouter de numéro de version. Une version spécifique du package est fournie directement par Pyodide. Aujourd'hui, vous pouvez utiliser des packages dans le développement local et, dans les semaines à venir, vous pourrez déployer des instances Workers définissant les dépendances dans un fichier requirements.txt
. Plus loin dans cet article, nous vous expliquerons comment nous envisageons la gestion des nouvelles versions de Pyodide et des packages.
Nous gérons notre propre fork de Pyodide, ce qui nous permet de proposer des correctifs spécifiques au runtime de Workers et d'étendre rapidement notre prise en charge des packages dans Python Workers, tout en nous engageant à répercuter nos modifications en amont dans Pyodide, afin que l'ensemble de l'écosystème de développeurs puisse en bénéficier.
Cependant, les packages Python sont souvent volumineux, consomment une grande quantité de mémoire et peuvent en outre exécuter d'importants calculs lors de l'importation. Comment pouvions-nous permettre l'importation des packages nécessaires, tout en atténuant les longs temps de démarrage à froid ?
Accélérer les démarrages à froid grâce aux instantanés mémoire
Dans l'exemple présenté au début de cet article, dans le développement local, nous avons mentionné l'injection de Pyodide dans une instance Workers. Pyodide lui-même pèse 6,4 Mo, et les packages Python peuvent également être assez volumineux.
Si nous pouvions simplement déplacer Pyodide dans l'instance Workers, puis le transférer vers Cloudflare, l'instance Workers chargée dans un nouvel isolat serait assez volumineuse et les démarrages à froid seraient lents. Sur un ordinateur performant, doté d'une connexion réseau rapide, l'initialisation de Pyodide dans un navigateur demande environ deux secondes – une seconde de temps réseau et une seconde de temps processeur. Il ne serait pas acceptable d'initialiser Pyodide chaque fois à chaque actualisation du code pour chaque isolat dans lequel s'exécute l'instance Workers sur le réseau de Cloudflare.
Au lieu de cela, lors de l'exécution de la commande npx wrangler@latest deploy, voici ce qui se passe :
Wrangler transfère le code Python et le fichier
requirements.txt
vers l'API WorkerNous transmettons le code Python et le fichier
requirements.txt
au runtime de Workers, afin qu'ils soient validésNous créons un nouvel isolat pour l'instance Workers et injectons automatiquement Pyodide, ainsi que tous les packages indiqués dans le fichier
requirements.txt
.Nous analysons le code de l'instance Workers à la recherche d'instructions d'importation, nous les exécutons, puis nous réalisons un instantané de la mémoire linéaire WebAssembly de l'instance Workers. Concrètement, nous exécutons la tâche exigeante consistant à importer les packages au moment du déploiement, plutôt que pendant l'exécution.
Nous déployons cet instantané avec le code Python de l'instance Workers sur le réseau de Cloudflare.
À l'instar d'une instance JavaScript Workers, nous exécutons le champ d'application de premier niveau de l'instance Workers.
Lorsqu'une requête est transmise à l'instance Workers, nous chargeons cet instantané et l'utilisons pour amorcer l'instance Workers dans un isolat, évitant ainsi un temps d'initialisation coûteux :
Ceci permet de réduire à moins d'une seconde les délais de démarrage à froid d'une instance Python Workers basique. Cependant, cela ne nous convient pas encore, et nous sommes convaincus de pouvoir réduire encore considérablement ce délai. Comment cela ? En réutilisant les instantanés de mémoire.
Réutilisation des instantanés mémoire
Lorsque vous transférez une instance Python Workers, nous générons un instantané mémoire unique des importations de premier niveau de l'instance Workers, incluant à la fois Pyodide et les dépendances éventuelles. Cet instantané est spécifique à votre instance Workers ; elle ne peut pas être partagée, même si la plupart de son contenu est identique à celui d'autres instances Python Workers.
À la place, nous pouvons créer à l'avance un instantané partagé unique, puis le précharger dans un pool d'isolats « préchauffés ». Dans ces isolats, le runtime de Pyodide serait déjà chargé et prêt, ce qui permettrait à une instance Python Workers de fonctionner de la même manière qu'une instance JavaScript Workers. Dans les deux cas, l'interpréteur sous-jacent et l'environnement d'exécution sont fournis par le runtime de Workers et sont disponibles sur demande et sans délai. La seule différence est qu'avec Python, l'interpréteur s'exécute dans WebAssembly, au sein de l'instance Workers.
Les instantanés sont un modèle courant dans les runtimes et les environnements d'exécution. Node.js utilise des instantanés V8 pour réduire les temps de démarrage. Vous pouvez réaliser des instantanés des microVM Firecracker et en reprendre l'exécution dans un processus différent. Ici, nous pouvons encore en faire beaucoup plus – pas uniquement pour les instances Python Workers, mais également pour les instances Workers écrites en JavaScript, en mettant en cache des instantanés de code compilé depuis le champ d'application de premier niveau, ainsi que l'état de l'isolat lui-même. Les instances Workers sont tellement rapides et efficaces que, jusqu'à présent, nous n'avons pas eu besoin d'effectuer des instantanés de cette manière ; toutefois, nous pensons qu'il est possible d'améliorer encore considérablement les performances.
Il s'agit de notre principal levier pour réduire les temps de démarrage à froid durant le reste de l'année 2024.
Pérennisation de la compatibilité avec les versions de Pyodide et dates de compatibilité
Lorsque vous déployez une instance Workers sur Cloudflare, vous attendez à ce qu'elle continue à fonctionner indéfiniment, même si vous ne la mettez plus jamais à jour. Certaines instances Workers déployées en 2018 fonctionnent toujours parfaitement en production.
Nous parvenons à ce résultat en utilisant les dates de compatibilité et les indicateurs de compatibilité, qui fournissent des mécanismes de souscription explicites aux nouveaux comportements ainsi que des modifications potentiellement rétrocompatibles, sans affecter les instances Workers existantes.
Cette approche fonctionne, en partie, parce qu’elle reflète le fonctionnement d’Internet et des navigateurs web. Lorsque vous publiez une page web avec du code JavaScript, vous vous attendez à juste titre à ce qu'elle fonctionne indéfiniment. Les navigateurs web et Cloudflare Workers présentent le même type d'engagement de stabilité envers les développeurs.
Python comporte toutefois un défi : Pyodide et CPython comportent tous deux des versions. Des versions mises à jour sont publiées régulièrement et peuvent contenir d'importantes modifications, et Pyodide fournit un ensemble de packages intégrés, comportant chacun un numéro de version attitré. Cette problématique soulève une question : comment pouvons-nous permettre aux utilisateurs de mettre à jour leur instance Workers avec une version plus récente de Pyodide ?
La réponse est grâce aux dates de compatibilité et aux indicateurs de compatibilité.
Une nouvelle version de Python est publiée chaque année au mois d'août, et une nouvelle version de Pyodide est publiée six (6) mois plus tard. Lorsque cette nouvelle version de Pyodide sera publiée, nous l'ajouterons à Workers avec un indicateur de compatibilité, qui ne sera activé qu'après une date de compatibilité spécifiée. Cette approche nous permet de fournir des mises à jour en continu, sans risque de modifications perturbatrices, et ainsi, d'étendre à Python l'engagement que nous avons pris pour JavaScript.
Chaque version de Python offre une période de support de cinq (5) ans. Lorsque cette période de support est dépassée pour une version donnée de Python, les correctifs de sécurité ne sont plus appliqués, rendant cette version peu sûre. Pour atténuer ce risque, tout en essayant de rester aussi fidèles que possible à notre engagement de stabilité et de support à long terme, après cinq ans, toute instance Python Workers utilisant encore une version de Python extérieure à la période de support sera automatiquement remplacée par la plus ancienne version de Python suivante. Python est un langage mature et stable ; aussi, nous nous attendons donc à ce que, dans la plupart des cas, votre instance Python Workers continue à fonctionner sans problème. Cependant, nous vous recommandons de régulièrement mettre à jour la date de compatibilité de votre instance Workers, afin qu'elle reste incluse dans la période de support.
Entre les lancements de différentes versions de Python, nous prévoyons également de mettre à jour et d'ajouter des packages Python supplémentaires, en utilisant le même mécanisme de souscription. Un indicateur de compatibilité sera constitué de l'association de la version de Python et de la date de publication d'un ensemble de packages, par exemple : python_3.17_packages_2025_03_01.
Fonctionnement des liaisons dans Python Workers
Nous avons mentionné plus haut que Pyodide fournit une interface de fonction étrangère (FFI, « Foreign Function Interface ») avec JavaScript, ce qui signifie que vous pouvez utiliser directement des objets, des méthodes et des fonctions JavaScript, et bien davantage, directement depuis Python.
Cela signifie que dès le premier jour, toutes les API de liaison vers d'autres ressources de Cloudflare sont prises en charge dans Cloudflare Workers. L'objet env fourni par les handlers dans Python est un objet JavaScript auquel Pyodide fournit une API proxy, permettant de gérer automatiquement des traductions de types dans différents langages.
Par exemple, pour écrire et lire depuis un espace de noms KV depuis une instance Python Workers, vous devriez écrire le code suivant :
Cela fonctionne également pour les API web ; voyez de quelle manière l'objet Response est importé depuis le module js. Vous pouvez importer n'importe quel objet global depuis JavaScript de cette façon.
from js import Response
async def on_fetch(request, env):
await env.FOO.put("bar", "baz")
bar = await env.FOO.get("bar")
return Response.new(bar) # returns "baz"
Hors de mon Python, JavaScript !
Si vous lisez cet article, c'est probablement parce que vous avez l'intention d'écrire du code Python plutôt que du code JavaScript. from js import Response
n'est simplement pas conforme aux prescriptions du langage Python : nous le savons, et nous avons déjà relevé ce défi auparavant pour un autre langage (Rust). Et nous pensons pouvoir faire encore mieux pour Python.
Nous avons lancé workers-rs en 2021 afin de permettre la création d'instances Workers en langage Rust. Pour chaque API JavaScript dans Workers, aux côtés de contributeurs open source, nous avons écrit des liaisons qui exposent une API Rust plus idiomatique.
Nous avons l'intention de faire de même pour Python Workers, en commençant par les liaisons avec Workers AI et Vectorize. Cependant, alors que workers-rs nécessite l'utilisation et la mise à jour d'une dépendance externe, l'API que nous fournissons avec Python Workers sera intégrée directement dans le runtime Workers. Il vous suffira de mettre à jour la date de compatibilité et d'obtenir l'API la plus récente et la plus conforme aux prescriptions du langage Python.
Toutefois, il ne s'agit pas simplement de rendre les liaisons aux ressources sur Cloudflare plus conformes aux prescriptions du langage Python : il s'agit de compatibilité avec l'écosystème.
Nous avons récemment converti workers-rs afin d'utiliser les types issus du crate http, ce qui facilite l'utilisation du crate axum pour le routage, et nous avons l'intention de faire de même pour Python Workers. Par exemple, la bibliothèque standard Python fournit une API raw socket dont dépendent de nombreux packages Python. Workers propose déjà connect(), une API JavaScript permettant d'utiliser les raw sockets. Nous voyons des moyens de fournir au moins un sous-ensemble de l'API socket de la bibliothèque standard Python dans Workers, ce qui permettra à un ensemble plus vaste de packages Python de fonctionner dans Workers, en nécessitant moins de correctifs.
À terme, toutefois, nous espérons néanmoins donner le coup d'envoi d'une initiative visant à créer une API serverless normalisée pour Python – une API facile à utiliser pour tout développeur Python, et qui offre les mêmes capacités que JavaScript.
Nous n'en sommes encore qu'au commencement avec Python Workers
La prise en support d'un nouveau langage de programmation constitue un investissement majeur, qui va bien au-delà de la simple exécution de « hello world ». Nous avons choisi Python de manière très intentionnelle, puisqu'il s'agit du deuxième langage de programmation le plus populaire après JavaScript, et nous engageons à continuer à améliorer les performances et étendre notre prise en charge des packages Python.
Nous sommes reconnaissants aux personnes impliquées dans la maintenance de Pyodide et à la communauté Python Workers dans son ensemble, et nous serions ravis de faire votre connaissance. Rejoignez le canal Python Workers du Discord pour développeurs de Cloudflare ou lancez une discussion sur Github pour parler des nouveautés que vous aimeriez voir prochainement mises en œuvre et des packages Python que vous aimeriez nous demander de prendre en charge.