Werkzeug: Werkzeug envisage-t-il de soutenir ASGI ?

Créé le 7 juin 2018  ·  21Commentaires  ·  Source: pallets/werkzeug

Werkzeug propose de nombreuses méthodes utiles, ce serait beaucoup plus facile s'il supportait ASGI que si nous partions de zéro.

ASGI

Commentaire le plus utile

Oui, Werkzeug et Flask prendront éventuellement en charge ASGI. Je n'ai pas de calendrier pour cela, bien que je serais heureux d'aider à réviser un PR si quelqu'un en commençait un.

Cependant, je ne vais pas être celui qui l'implémente, j'ai besoin de l'aide de la communauté. Voir la dernière mise à jour ci-dessous : https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Tous les 21 commentaires

Oui, Werkzeug et Flask prendront éventuellement en charge ASGI. Je n'ai pas de calendrier pour cela, bien que je serais heureux d'aider à réviser un PR si quelqu'un en commençait un.

Cependant, je ne vais pas être celui qui l'implémente, j'ai besoin de l'aide de la communauté. Voir la dernière mise à jour ci-dessous : https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Je suis intéressé à travailler là-dessus.

J'ai un support ASGI fonctionnel mais hacky en cours: werkzeug , flask . J'aimerais en savoir plus sur les plans que vous pourriez avoir avant d'aller plus loin.

Des choses qui me viennent déjà à l'esprit, au-delà du fait que tout ce que j'y ai fait pourrait être globalement plus sympa :

  • Je prends en charge l'analyseur de formulaire en exécutant simplement son code synchrone dans un thread (que je bloque pendant que je lis les données de manière asynchrone). Je ne pense pas que cela suffira. Mon approche préférée serait de factoriser le IO .
  • Le support contextuel local est assez fragile. Il semble y avoir une bonne façon de faire cela , mais cela implique d'interférer avec l'état global, et en particulier avec ASGI, nous ne pouvons pas supposer que nous possédons le monde.
  • Il n'y a aucun moyen évident, lors de l'exécution sous ASGI, d'analyser les données de formulaire à la demande pour une fonction synchrone. Il peut être utile d'envisager d'analyser avec impatience les données du formulaire, à moins que nous ne puissions dire que la fonction d'affichage est asynchrone. Quelle que soit la valeur par défaut, il pourrait évidemment y avoir un décorateur pour la remplacer.

S'il vous plaît laissez-moi savoir vos pensées.

Je prends en charge l'analyseur de formulaire en exécutant simplement son code synchrone dans un thread (que je bloque pendant que je lis les données de manière asynchrone). Je ne pense pas que cela suffira. Mon approche préférée serait de factoriser l'IO.

Je suppose que l'approche tactile légère consistera simplement à réimplémenter l'analyseur existant, mais avec des E/S asynchrones. Ce serait plus propre si les deux classes d'analyseur pouvaient partager une implémentation commune sans-IO sous le capot, mais il pourrait être plus pratique de dupliquer et de modifier légèrement l'implémentation existante.

Le support contextuel local est assez fragile.

Les paramètres locaux de contexte (pour asyncio) existent dans la 3.7 stdlib. https://docs.python.org/3.7/library/contextvars.html
Je suppose que je les utiliserais, avec une bibliothèque compat facultative pour prendre en charge les versions antérieures de python. (Ou bien supposez simplement que ASGI sur Flask finirait par être une chose 3.7+)

Werkzeug utilise-t-il des threads locaux ? (Je sais que Flask le fait)

Il n'y a aucun moyen évident, lors de l'exécution sous ASGI, d'analyser les données de formulaire à la demande pour une fonction synchrone

Je suggérerais de ne pas offrir d'interface synchrone pour l'analyse des données de formulaire. (Ou pour accéder au corps de la requête de quelque manière que ce soit), et à la place, proposez simplement des API asynchrones dessus. Voir l'API de Starlette pour quelques exemples ici... https://github.com/encode/starlette#body

Depuis que Python 3.7 est sorti, je ne serais pas opposé à avoir des fonctionnalités asynchrones nécessitant Python 3.7 tant qu'aucun autre code n'en est affecté. Mais s'il y a un backport qui est aussi bon que la solution native 3.7 - encore mieux !

En ce qui concerne l'analyse des données de formulaire asynchrone... Je suppose que quelque chose comme await request.parse() dans une fonction asynchrone serait suffisant, puis déclencherait une exception lors de la tentative d'accès aux données de formulaire non analysées ?

Bien sûr, ou faites en sorte que values et vos amis soient eux-mêmes asynchrones : values = await request.values ou values = await request.values() , en fonction principalement de ce qui semble le plus agréable.

cela ne nécessiterait-il pas des choses plutôt laides comme (await request.form)['foo'] pour faire un appel asynchrone tout en obtenant directement un élément dict sans assigner entre les deux?

Oui :(

Je ne suis pas sûr que ce soit particulièrement évitable, cependant. je ne pense pas

form = await request.form
form['foo']

est vraiment plus ou moins moche que

await request.parse()
request.form['foo']

bien que cela soit évidemment soumis au goût.

Je suppose que nous pourrions également inventer un "dict asynchrone" dont les _members_ sont tous asynchrones à la place, mais sans en avoir vu un, j'imagine que cela finirait par être plutôt déroutant.

Bien sûr, ou rendez simplement les valeurs et les amis eux-mêmes asynchrones : values ​​= await request.values ​​ou values ​​= await request.values(), en fonction principalement de ce qui semble le plus agréable.

Je suggérerais d'utiliser des appels de fonction pour les opérations d'E/S, plutôt que des propriétés.

cela ne nécessiterait-il pas des choses plutôt laides comme (wait request.form) ['foo'] pour faire un appel asynchrone tout en obtenant directement un élément dict sans assigner entre les deux?

Haussement d'épaules - Ne fais pas ça.

asyncio est nécessairement plus explicite sur les parties de la base de code qui effectuent des E/S, donc j'aurais tendance à les diviser en lignes distinctes.

form = await request.form()
form['foo']

Bien que je sois tout à fait favorable à l'ajout de la prise en charge asynchrone, nous ne pouvons toujours pas casser Python 2 et synchroniser les versions. Une approche dont j'ai entendu parler de @njsmith consiste à tout écrire en asynchrone, puis à utiliser un outil similaire à 2to3 pour générer la version de synchronisation. Apparemment, il est essayé dans urllib3, mais je n'en sais pas assez.

Je me demande si nous pourrions ajouter de la magie aux objets derrière request.form etc., donc les appeler fera des choses asynchrones tandis que les méthodes habituelles de type dict seront synchronisées (et échoueraient en mode asynchrone). Ou nous pourrions simplement échouer à tout accès à request.form etc. en mode asynchrone et utiliser un nom distinct pour les versions asynchrones, par exemple request.parse_form() .

Ou... request_class = AsyncRequest si quelqu'un veut asynchrone ; cela pourrait en fait être une valeur par défaut dans une classe AsyncFlask .

Ou... request_class = AsyncRequest si quelqu'un veut asynchrone ; cela pourrait en fait être une valeur par défaut dans une classe AsyncFlask.

C'est le genre d'approche qui aurait du sens pour moi, oui.

En ce qui concerne l'analyseur de formulaire, j'ai tenté de le supprimer dans #1330.

Il existe également une implémentation d'analyseur de formulaire de streaming sur https://github.com/andrew-d/python-multipart avec une couverture à 100 %. (J'en ai trouvé un autre mais il n'était pas évident qu'il puisse être facilement adapté à un flux "données d'alimentation, gestion des événements".)

python-multipart est la bibliothèque que j'utilise maintenant pour Starlette. Vous pouvez jeter un œil à l'intégration avec le flux asynchrone ici : https://github.com/encode/starlette/blob/master/starlette/formparsers.py#L207

J'ai également réfléchi aux meilleurs moyens de présenter une interface compatible sync et async, puisque je le veux aussi pour Starlette (bien que dans l'autre sens que Werkzeug, pour moi il s'agit de "j'ai une interface async existante, comment puis-je maintenant également présenter un sync")

Pour Starlette, je pense que je vais probablement pousser l'analyse réelle dans une méthode async parse(self) , et l'appeler généralement pendant l'envoi de la demande, mais exposer les propriétés simples normales form , files etc. ... pour accéder aux résultats à partir du code utilisateur.

Hors sujet, mais lié à un cas ASGI et werkzeug populaire (je ne veux pas faire dérailler ce problème, mais je ne veux pas créer un problème en double sans substance à ajouter):

Si vous exécutez https://github.com/django-extensions/django-extensions avec ./manage.py runserver_plus et https://github.com/django/channels (qui utilise ASGI) donne Opcode -1 (opcode moins 1) dans l'inspecteur, essayez d'exécuter ./manage.py runserver pour l'instant jusqu'à ce que l'ASGI soit pris en charge.

(Werkzeug est utilisé sous le capot sur Django avec des extensions django et j'ai passé quelques heures à comprendre pourquoi puisque je suis tellement habitué à développer avec runserver_plus pour le débogage)

J'utilise runserver_plus pour pouvoir utiliser https en développement.
J'essaie maintenant d'ajouter la prise en charge de Websockets à l'aide des canaux Django et j'ai rencontré le problème selon lequel les canaux utilisent runserver et ne prennent pas en charge runserver_plus. Donc, je peux utiliser des canaux OU je peux utiliser https, pas les deux !

@davidism s'il vous plaît dites-moi s'il y a des changements dans la mise en œuvre du support ASGI pour Flask depuis votre dernière réponse dans ce sujet et si vos plans ont changé en relation avec cette tâche pour mettre fin au support de Python 2 ?

Le 2 juillet 2018, @davidism écrit :

Apparemment, il est essayé dans urllib3, mais je n'en sais pas assez.

Je viens de voir cela il y a quelque temps - si quelqu'un souhaite en savoir plus, il semble que python-trio/urllib3 # 1 ait un tas de détails. Notez le lien vers urllib3/urllib3#1323, qui contient :

Solution : nous maintenons une copie du code – la version avec les annotations async/wait – puis un petit script maintient la copie synchrone en les retirant automatiquement. Ce n'est pas beau, mais pour autant que je sache, toutes les alternatives sont pires...

(Continuez à lire si vous êtes intéressé.)

Ravi de voir que cela a apparemment continué à bien fonctionner, sur la base des progrès constants réalisés sur https://github.com/python-trio/urllib3/commits/bleach-spike.

Petite bosse pour faire revenir ce problème sur le radar. Avec 3.5 atteignant EOL cette année, je pense que c'est le bon moment pour commencer à penser au support asynchrone ?

Au cours de l'année et demie qui s'est écoulée depuis sa publication, il n'y a pas eu beaucoup d'activités pour le mettre en œuvre. Je n'ai personnellement aucune expérience ou besoin d'asyncio, et bien que j'aime ASGI, ce n'était jamais quelque chose que j'allais prendre sur moi.

Entre-temps, @pgjones , auteur de Quart, s'est davantage impliqué dans Werkzeug. Quart utilise désormais Werkzeug dans les coulisses lorsque cela est possible, et nous continuons à développer cela. Il y a eu quelques réticences de la part d'un mainteneur de Flask à propos de l'utilisation d'ASGI, alors Phil a également créé des palettes/flask#3412 qui permettaient au moins le routage vers les fonctions async def , mais cela fait un moment maintenant. À ce stade, je préfère aller à ASGI plutôt que de me contenter de cela. @ edk0 a créé # 1330 pour effectuer une analyse de formulaire sans-io, mais il est également resté assis et devrait probablement passer par un peu plus de conception et de révision en premier.

Vous pourriez demander : « Pourquoi Flask ne peut-il pas faire ce que Django a fait ? Je ne suis pas un expert des composants internes de Django, mais @andrewgodwin m'a expliqué il y a quelque temps que Django a un temps "plus facile" (lire: toujours très compliqué) en raison de la façon dont il s'est initialement adapté à WSGI, par opposition au API très centrée sur WSGI avec laquelle Werkzeug et Flask ont ​​commencé. De plus, Django reçoit juste une tonne plus d'attention et de ressources à plein temps que Pallets.

Alors, où cela laisse-t-il ce problème? Si vous voulez un framework compatible Flask qui utilise Werkzeug, utilisez Quart. Contribuez à Quart (ou Flask) pour les rendre plus compatibles avec l'API là où cela manque. Si vous voulez que Werkzeug et Flask prennent en charge ASGI, vous devrez intensifier. Commencez à vous renseigner sur l'ASGI. Commencez à identifier les parties spécifiques à WSGI et bloquantes de l'API de Werkzeug. Commencez à penser aux abstractions que nous pouvons faire pour permettre des implémentations pour WSGI et ASGI. Ensuite, ramenez cette recherche à cette discussion afin que nous puissions commencer à concevoir et à rédiger des relations publiques.

Merci pour la suggestion de Quart , je serais très heureux d'accepter des contributions.

J'ai essayé de répondre pourquoi je pense que Flask ne peut pas faire ce que Django a fait dans cet article . En fin de compte, je pense que pallets/flask#3412 est la meilleure solution pour Flask.

En termes de Werkzeug, je pense que l'ASGI est possible, avec un peu de douleur. Un exemple notable de la douleur est que beaucoup de choses dans Werkzeug sont des callables WSGI (par exemple des exceptions). Avec ASGI, il n'est pas clair comment cette fonctionnalité pourrait/devrait être utilisée, donc je préférerais la supprimer.

Mon plan est de continuer à intégrer Werkzeug dans Quart en ajustant Werkzeug vers ASGI (sans-io) au fur et à mesure (autant que possible) - mon seul obstacle est le manque de temps.

Cette page vous a été utile?
0 / 5 - 0 notes