Werkzeug: Hat werkzeug Pläne, ASGI zu unterstützen?

Erstellt am 7. Juni 2018  ·  21Kommentare  ·  Quelle: pallets/werkzeug

Werkzeug bietet viele nützliche Methoden, es wäre viel einfacher, wenn es ASGI unterstützen würde, als wenn wir bei Null anfangen würden.

ASGI

Hilfreichster Kommentar

Ja, Werkzeug und Flask werden irgendwann ASGI unterstützen. Ich habe dafür keinen Zeitplan, obwohl ich gerne helfen würde, eine PR zu überprüfen, wenn jemand eine beginnt.

Allerdings werde ich nicht derjenige sein, der es umsetzt, ich brauche Hilfe von der Community. Siehe das neueste Update unten: https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Alle 21 Kommentare

Ja, Werkzeug und Flask werden irgendwann ASGI unterstützen. Ich habe dafür keinen Zeitplan, obwohl ich gerne helfen würde, eine PR zu überprüfen, wenn jemand eine beginnt.

Allerdings werde ich nicht derjenige sein, der es umsetzt, ich brauche Hilfe von der Community. Siehe das neueste Update unten: https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Ich bin daran interessiert, daran zu arbeiten.

Ich habe einige funktionierende, aber hacky ASGI -Unterstützung am Laufen: werkzeug , Flask . Ich würde gerne mehr über Ihre Pläne erfahren, bevor ich fortfahre.

Dinge, die mir schon einfallen, abgesehen davon, dass alles, was ich dort gemacht habe, generell schöner sein könnte:

  • Ich unterstütze den Formularparser, indem ich einfach seinen synchronen Code in einem Thread ausführe (den ich blockiere, während ich die Daten asynchron lese). Ich glaube nicht, dass das geht. Mein bevorzugter Ansatz wäre , das IO auszuklammern .
  • Die kontextlokale Unterstützung ist ziemlich anfällig. Es scheint einen richtigen Weg zu geben, dies zu tun , aber es beinhaltet Eingriffe in den globalen Zustand, und insbesondere bei ASGI können wir nicht davon ausgehen, dass uns die Welt gehört.
  • Bei Ausführung unter ASGI gibt es keine offensichtliche Möglichkeit, Formulardaten bei Bedarf für eine synchrone Funktion zu parsen. Es könnte sich lohnen, Formulardaten eifrig zu analysieren, es sei denn, wir können feststellen, dass die Ansichtsfunktion asynchron ist. Was auch immer der Standardwert ist, es könnte offensichtlich einen Dekorateur geben, der ihn überschreibt.

Bitte lassen Sie mich Ihre Gedanken wissen.

Ich unterstütze den Formularparser, indem ich einfach seinen synchronen Code in einem Thread ausführe (den ich blockiere, während ich die Daten asynchron lese). Ich glaube nicht, dass das geht. Mein bevorzugter Ansatz wäre, das IO auszuklammern.

Ich denke, der leichte Ansatz wird nur darin bestehen, den vorhandenen Parser neu zu implementieren, aber mit asynchronem IO. Es wäre sauberer, wenn die beiden Parser-Klassen eine gemeinsame Sans-IO-Implementierung unter der Haube teilen könnten, aber es könnte praktischer sein, nur die vorhandene Implementierung zu duplizieren und leicht zu modifizieren.

Die kontextlokale Unterstützung ist ziemlich anfällig.

Kontextlokale (für asyncio) sind in der 3.7 stdlib vorhanden. https://docs.python.org/3.7/library/contextvars.html
Ich würde diese mit einer optionalen Kompatibilitätsbibliothek verwenden, um frühere Versionen von Python zu unterstützen. (Oder nehmen Sie einfach an, dass ASGI auf Flask am Ende eine 3.7+ Sache wäre)

Verwendet werkzeug Thread-Locals? (Ich bin mir bewusst, dass Flask dies tut)

Bei Ausführung unter ASGI gibt es keine offensichtliche Möglichkeit, Formulardaten bei Bedarf für eine synchrone Funktion zu parsen

Ich würde vorschlagen, keine synchrone Schnittstelle zum Analysieren von Formulardaten anzubieten. (Oder für den Zugriff auf den Anfragetext in irgendeiner Weise) und stattdessen einfach asynchrone APIs darauf anbieten. Siehe Starlettes API für einige Beispiele hier... https://github.com/encode/starlette#body

Da Python 3.7 nicht mehr verfügbar ist, hätte ich nichts dagegen, asynchrone Funktionen zu haben, die Python 3.7 erfordern, solange kein anderer Code davon betroffen ist. Aber wenn es einen Backport gibt, der so gut ist wie die native 3.7-Lösung - sogar noch besser!

In Bezug auf das Analysieren von asynchronen Formulardaten ... Ich denke, etwas wie await request.parse() in einer asynchronen Funktion wäre ausreichend und löst dann eine Ausnahme aus, wenn versucht wird, auf nicht analysierte Formulardaten zuzugreifen?

Sicher, oder machen Sie einfach values und seine Freunde selbst asynchron: values = await request.values oder values = await request.values() , je nachdem, was besser aussieht.

würde das nicht ziemlich hässliche Dinge wie (await request.form)['foo'] erfordern, um einen asynchronen Aufruf zu machen, während man direkt ein dict-Element erhält, ohne dazwischen etwas zuzuweisen?

ja :(

Ich bin mir aber nicht sicher, ob das besonders vermeidbar ist. Ich denke nicht

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

ist wirklich mehr oder weniger hässlich als

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

wobei das natürlich geschmackssache ist.

Ich schätze, wir könnten auch ein "asynchrones Diktat" erfinden, dessen _Mitglieder_ stattdessen alle asynchronisiert sind, aber ohne eines gesehen zu haben, würde ich mir vorstellen, dass es ziemlich verwirrend enden würde.

Sicher, oder machen Sie Werte und Freunde einfach selbst asynchron: values ​​= await request.values ​​oder values ​​= await request.values(), je nachdem, was besser aussieht.

Ich würde vorschlagen, Funktionsaufrufe für E/A-Operationen anstelle von Eigenschaften zu verwenden.

Würde das nicht ziemlich hässliche Dinge wie (await request.form)['foo'] erfordern, um einen asynchronen Aufruf durchzuführen, während ein dict-Element direkt ohne Zuweisung dazwischen abgerufen wird?

Achselzucken - Tu das nicht.

asyncio ist notwendigerweise expliziter darüber, welche Teile der Codebasis E/A ausführen, daher würde ich dazu neigen, diese in separate Zeilen aufzuteilen.

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

Ich bin zwar dafür, asynchrone Unterstützung hinzuzufügen, aber wir können Python 2 immer noch nicht beschädigen und Versionen synchronisieren. Ein Ansatz, von dem ich von @njsmith gehört habe, besteht darin, alles als asynchron zu schreiben und dann ein Tool ähnlich wie 2to3 zu verwenden, um die Synchronisierungsversion zu generieren. Anscheinend wird es in urllib3 versucht, aber ich weiß nicht genug darüber.

Ich frage mich, ob wir den Objekten hinter request.form usw. Magie hinzufügen könnten, damit deren Aufruf asynchrone Dinge bewirkt, während die üblichen dict-ähnlichen Methoden synchronisiert werden (und im asynchronen Modus fehlschlagen würden). Oder wir könnten einfach jeden Zugriff auf request.form usw. im asynchronen Modus vereiteln und einen separaten Namen für die asynchronen Versionen verwenden, z. B. request.parse_form() .

Oder... request_class = AsyncRequest falls jemand async haben möchte; Dies könnte tatsächlich ein Standardwert in einer AsyncFlask -Klasse sein.

Oder... request_class = AsyncRequest wenn jemand async haben möchte; Dies könnte tatsächlich ein Standard in einer AsyncFlask-Klasse sein.

Das ist die Art von Ansatz, die für mich Sinn machen würde, ja.

In Bezug auf den Formular-Parser habe ich in #1330 versucht, ihn mit Sansio zu bearbeiten.

Es gibt auch eine Streaming-Formular-Parser-Implementierung unter https://github.com/andrew-d/python-multipart mit 100 % Abdeckung. (Ich habe einen anderen gefunden, aber es war nicht offensichtlich, dass er leicht in einen „Feed-Daten, Ereignisse verarbeiten“-Fluss angepasst werden könnte.)

python-multipart ist die Bibliothek, die ich jetzt für Starlette verwende. Sie können sich die Integration mit dem asynchronen Stream hier ansehen: https://github.com/encode/starlette/blob/master/starlette/formparsers.py#L207

Ich habe auch über die besten Möglichkeiten nachgedacht, eine sync- und async-kompatible Schnittstelle zu präsentieren, da ich das auch für Starlette haben möchte (obwohl ich in die andere Richtung zu Werkzeug gehe, geht es mir um "Ich habe eine vorhandene asynchrone Schnittstelle, wie kann ich jetzt auch ein Sync One präsentieren")

Für Starlette denke ich, dass ich das eigentliche Parsen wahrscheinlich in eine async parse(self) -Methode schieben werde und diese normalerweise irgendwann während des Anforderungsversands aufrufen werde, aber reguläre einfache Eigenschaften form , files usw. verfügbar mache ... für den Zugriff auf die Ergebnisse aus Benutzercode.

Off-Topic, aber im Zusammenhang mit einem beliebten ASGI- und Werkzeug-Fall (ich möchte dieses Problem nicht entgleisen, aber kein doppeltes Problem ohne Substanz zum Hinzufügen erstellen):

Wenn Sie https://github.com/django-extensions/django-extensions mit ./manage.py runserver_plus ausführen und https://github.com/django/channels (das ASGI verwendet) Opcode -1 geben ./manage.py runserver auszuführen, bis ASGI unterstützt wird.

(Werkzeug wird unter der Haube von Django mit Django-Erweiterungen verwendet und ich habe ein paar Stunden damit verbracht, herauszufinden, warum, da ich so daran gewöhnt bin, mit runserver_plus zum Debuggen zu entwickeln.)

Ich verwende runserver_plus, damit ich https in der Entwicklung verwenden kann.
Ich versuche jetzt, Websockets-Unterstützung mithilfe von Django-Kanälen hinzuzufügen, und bin auf das Problem gestoßen, dass die Kanäle runserver verwenden und runserver_plus nicht unterstützen. Also kann ich Kanäle verwenden ODER ich kann https verwenden, nicht beides!

@davidism Bitte sagen Sie mir, ob sich seit Ihrer letzten Antwort in diesem Thema Änderungen bei der Implementierung der ASGI-Unterstützung für Flask ergeben haben und ob sich Ihre Pläne im Zusammenhang mit dieser Aufgabe geändert haben, die Unterstützung für Python 2 zu beenden?

Am 2. Juli 2018 schreibt @davidism :

Anscheinend wird es in urllib3 versucht, aber ich weiß nicht genug darüber.

Ich habe das gerade vor einer Weile gesehen – falls jemand daran interessiert ist, mehr zu erfahren, es sieht so aus, als hätte python-trio/urllib3#1 eine Menge Details. Beachten Sie den Link zu urllib3/urllib3#1323, der Folgendes enthält:

Lösung: Wir pflegen eine Kopie des Codes – die Version mit async/await-Anmerkungen – und dann verwaltet ein kleines Skript die synchrone Kopie, indem es sie automatisch wieder entfernt. Es ist nicht schön, aber soweit ich das beurteilen kann, sind alle Alternativen schlechter ...

(Lesen Sie dort weiter, wenn Sie interessiert sind.)

Schön zu sehen, dass dies anscheinend weiterhin gut funktioniert, basierend auf den stetigen Fortschritten, die unter https://github.com/python-trio/urllib3/commits/bleach-spike gemacht werden.

Kleine Beule, um dieses Problem wieder auf den Radar zu bringen. Da 3.5 dieses Jahr EOL erreicht, ist es meiner Meinung nach ein guter Zeitpunkt, über asynchrone Unterstützung nachzudenken?

In den anderthalb Jahren, seit dies veröffentlicht wurde, gab es nicht viel Aktivität, um es zu implementieren. Ich persönlich habe keine Erfahrung oder Bedarf an Asyncio, und obwohl ich ASGI mag, war es nie etwas, das ich auf mich nehmen wollte.

Inzwischen engagiert sich @pgjones , Autor von Quart, stärker für Werkzeug. Quart verwendet jetzt Werkzeug hinter den Kulissen, wo immer es möglich ist, und wir entwickeln das weiter. Es gab einige Ablehnung von einem Flask-Betreuer, ASGI zu verwenden, also erstellte Phil auch Paletten/Flask#3412, die zumindest das Routing zu async def -Funktionen erlaubten, aber das sitzt jetzt schon eine Weile. An diesem Punkt würde ich lieber zu ASGI gehen, als mich damit zufrieden zu geben. @edk0 hat #1330 erstellt, um die Formularanalyse ohne IO durchzuführen, aber es hat auch gesessen und sollte wahrscheinlich zuerst mehr Design und Überprüfung durchlaufen.

Sie fragen sich vielleicht: „Warum kann Flask nicht das tun, was Django getan hat?“ Ich bin kein Experte für die Interna von Django, aber @andrewgodwin hat mir vor einer Weile erklärt, dass Django eine "leichtere" (sprich: immer noch sehr komplizierte) Zeit hat, da es sich ursprünglich an WSGI angepasst hat, im Gegensatz zu der sehr WSGI-zentrierte API, mit der Werkzeug und Flask begonnen haben. Außerdem erhält Django einfach eine Tonne mehr Vollzeit-Aufmerksamkeit und Ressourcen als Pallets.

Wo bleibt also dieses Problem? Wenn Sie ein Flask-kompatibles Framework wünschen, das Werkzeug verwendet, verwenden Sie Quart. Tragen Sie zu Quart (oder Flask) bei, um sie API-kompatibeler zu machen, wo dies fehlt. Wenn Sie möchten, dass Werkzeug und Flask ASGI unterstützen, müssen Sie etwas tun. Lernen Sie ASGI kennen. Beginnen Sie mit der Identifizierung der WSGI-spezifischen und blockierenden Teile der API von Werkzeug. Denken Sie über Abstraktionen nach, die wir machen können, um Implementierungen sowohl für WSGI als auch für ASGI zu ermöglichen. Bringen Sie diese Forschung dann in diese Diskussion zurück, damit wir mit dem Entwerfen und Schreiben von PRs beginnen können.

Danke für den Quart- Vorschlag, ich würde sehr gerne Beiträge dazu annehmen.

Ich habe versucht zu beantworten, warum Flask meiner Meinung nach nicht das kann, was Django in diesem Artikel getan hat. Letztendlich denke ich, dass Paletten/Kolben Nr. 3412 die beste Lösung für Flask ist.

In Bezug auf Werkzeug denke ich, dass ASGI mit etwas Schmerz möglich ist. Ein bemerkenswertes Beispiel für den Schmerz ist, dass viele Dinge in Werkzeug WSGI-Callables sind (z. B. Ausnahmen). Bei ASGI ist nicht klar, wie diese Funktionalität verwendet werden könnte/sollte, daher würde ich es vorziehen, sie zu entfernen.

Mein Plan ist es, Werkzeug weiterhin in Quart zu integrieren und Werkzeug nach und nach in Richtung ASGI (sans-io) anzupassen (so viel wie möglich) - mein einziges Hindernis ist Zeitmangel.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen