Werkzeug: ¿Werkzeug tiene planes para apoyar a ASGI?

Creado en 7 jun. 2018  ·  21Comentarios  ·  Fuente: pallets/werkzeug

Werkzeug ofrece muchos métodos útiles, sería mucho más fácil si fuera compatible con ASGI que si empezáramos desde cero.

ASGI

Comentario más útil

Sí, Werkzeug y Flask finalmente admitirán ASGI. No tengo un cronograma para esto, aunque estaría feliz de ayudar a revisar un PR si alguien comenzó uno.

Sin embargo, no voy a ser yo quien lo implemente, necesito ayuda de la comunidad. Consulte la última actualización a continuación: https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Todos 21 comentarios

Sí, Werkzeug y Flask finalmente admitirán ASGI. No tengo un cronograma para esto, aunque estaría feliz de ayudar a revisar un PR si alguien comenzó uno.

Sin embargo, no voy a ser yo quien lo implemente, necesito ayuda de la comunidad. Consulte la última actualización a continuación: https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Estoy interesado en trabajar en esto.

Tengo algo de soporte ASGI en funcionamiento pero pirateado: werkzeug , matraz . Me gustaría saber más sobre cualquier plan que pueda tener antes de continuar.

Cosas que ya se me ocurren, más allá de que todo lo que he hecho allí podría ser en general mejor:

  • Estoy apoyando el analizador de formularios simplemente ejecutando su código síncrono en un hilo (que bloqueo mientras leo los datos de forma asíncrona). No creo que esto sirva. Mi enfoque preferido sería factorizar el IO .
  • El soporte de contexto local es bastante frágil. Parece que hay una manera correcta de hacer esto , pero implica interferir con el estado global, y especialmente con ASGI, no podemos asumir que somos dueños del mundo.
  • No hay una forma obvia, cuando se ejecuta bajo ASGI, de analizar datos de formularios a pedido para una función síncrona. Podría valer la pena considerar analizar con entusiasmo los datos del formulario a menos que podamos decir que la función de visualización es asíncrona. Cualquiera que sea el valor predeterminado, obviamente podría haber un decorador para anularlo.

Por favor, hágame saber sus pensamientos.

Estoy apoyando el analizador de formularios simplemente ejecutando su código síncrono en un hilo (que bloqueo mientras leo los datos de forma asíncrona). No creo que esto sirva. Mi enfoque preferido sería factorizar el IO.

Supongo que el enfoque de toque ligero será solo para volver a implementar el analizador existente, pero con IO asíncrono. Sería más limpio si las dos clases de analizador pudieran compartir una implementación sans-IO común bajo el capó, pero podría ser más práctico simplemente duplicar y modificar ligeramente la implementación existente.

El soporte de contexto local es bastante frágil.

Los contextos locales (para asyncio) existen en 3.7 stdlib. https://docs.python.org/3.7/library/contextvars.html
Supongo que los usaría, con una biblioteca de compatibilidad opcional para admitir versiones anteriores de python. (O simplemente suponga que ASGI en Flask terminaría siendo una cosa 3.7+)

¿Werkzeug usa subprocesos locales? (Soy consciente de que Flask lo hace)

No hay una forma obvia, cuando se ejecuta bajo ASGI, de analizar los datos del formulario a pedido para una función síncrona

Sugeriría no ofrecer una interfaz síncrona para analizar datos de formularios. (O para acceder al cuerpo de la solicitud de cualquier manera) y, en su lugar, simplemente ofrezca API asíncronas en él. Consulte la API de Starlette para ver algunos ejemplos aquí... https://github.com/encode/starlette#body

Dado que Python 3.7 ya no está disponible, no me opondría a tener funciones asíncronas que requieran Python 3.7, siempre y cuando no afecte a ningún otro código. Pero si hay un backport que es tan bueno como la solución nativa 3.7, ¡aún mejor!

Con respecto al análisis de datos de formularios asíncronos... ¿Supongo que algo como await request.parse() en una función asíncrona sería suficiente y luego generaría una excepción al intentar acceder a datos de formularios no analizados?

Claro, o simplemente hacer que values y sus amigos sean asincrónicos: values = await request.values o values = await request.values() , dependiendo principalmente de cuál se vea mejor.

¿No requeriría eso cosas bastante feas como (await request.form)['foo'] para hacer una llamada asíncrona mientras se obtiene un elemento dict directamente sin asignar en el medio?

sí :(

Sin embargo, no estoy seguro de que eso sea particularmente evitable. no creo

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

es realmente más o menos feo que

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

aunque eso obviamente está sujeto al gusto.

Supongo que también podríamos inventar un "dictado asíncrono" cuyos _miembros_ sean todos asíncronos, pero sin haber visto uno me imagino que terminaría bastante confuso.

Claro, o simplemente hacer que los valores y los amigos sean asincrónicos: valores = solicitud en espera.valores o valores = solicitud en espera.valores(), dependiendo principalmente de cuál se vea mejor.

Sugeriría usar llamadas a funciones para operaciones de E/S, en lugar de propiedades.

¿No requeriría eso cosas bastante feas como (esperar solicitud.formulario) ['foo'] para hacer una llamada asíncrona mientras obtiene un elemento dict directamente sin asignar en el medio?

Encogerse de hombros - No hagas eso.

asyncio es necesariamente más explícito acerca de qué partes del código base realizan E/S, por lo que tendería a dividirlas en líneas separadas.

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

Si bien estoy a favor de agregar soporte asíncrono, todavía no podemos romper Python 2 y sincronizar versiones. Un enfoque que escuché de @njsmith es escribir todo como asíncrono, luego usar una herramienta similar a 2to3 para generar la versión sincronizada. Aparentemente, se está probando en urllib3, pero no sé lo suficiente al respecto.

Me pregunto si podríamos agregar magia a los objetos detrás request.form etc., por lo que llamarlos hará cosas asincrónicas mientras que los métodos habituales similares a dict se sincronizarán (y fallarían en modo asincrónico). O simplemente podríamos fallar cualquier acceso a request.form etc. en modo asíncrono y usar un nombre separado para las versiones asíncronas, por ejemplo, request.parse_form() .

O... request_class = AsyncRequest si alguien quiere async; esto podría ser un valor predeterminado en una clase AsyncFlask .

O... request_class = AsyncRequest si alguien quiere async; esto podría ser un valor predeterminado en una clase AsyncFlask.

Ese es el tipo de enfoque que tendría sentido para mí, sí.

Con respecto al analizador de formularios, he intentado sancionarlo en el #1330.

También hay una implementación de analizador de formularios de transmisión en https://github.com/andrew-d/python-multipart con una cobertura del 100 %. (Encontré otro, pero no era obvio que pudiera adaptarse fácilmente a un flujo de "alimentación de datos, manejo de eventos").

python-multipart es la biblioteca que estoy usando ahora para Starlette. Puede echar un vistazo a la integración con el flujo asíncrono aquí: https://github.com/encode/starlette/blob/master/starlette/formparsers.py#L207

También he estado pensando en las mejores formas de presentar una interfaz compatible con sincronización y asíncronismo, ya que también quiero eso para Starlette (aunque yendo en la otra dirección a Werkzeug, para mí se trata de "Tengo una interfaz asíncrona existente, ¿cómo puedo ahora también presente uno de sincronización")

Para Starlette, creo que probablemente empujaré el análisis real a un método async parse(self) , y normalmente lo llamaré en algún momento durante el envío de la solicitud, pero expondré las propiedades simples regulares form , files etc. ... para acceder a los resultados desde el código de usuario.

Fuera de tema, pero relacionado con un caso popular de ASGI y werkzeug (no quiero descarrilar este problema, pero no quiero crear un problema duplicado sin sustancia para agregar):

Si está ejecutando https://github.com/django-extensions/django-extensions con ./manage.py runserver_plus y https://github.com/django/channels (que usa ASGI) está dando Opcode -1 (código de operación menos 1) en el inspector, intente ejecutar ./manage.py runserver por ahora hasta que ASGI sea compatible.

(Werkzeug se usa debajo del capó en Django con extensiones de django y pasé algunas horas averiguando por qué, ya que estoy tan acostumbrado a desarrollar con runserver_plus para la depuración)

Uso runserver_plus para poder usar https en el desarrollo.
Ahora estoy tratando de agregar compatibilidad con Websockets usando canales de Django, y me he encontrado con el problema de que los canales usan RunServer y no admiten RunServer_Plus. Entonces, puedo usar canales O puedo usar https, ¡no ambos!

@davidism , dígame si hay algún cambio en la implementación de la compatibilidad con ASGI para Flask desde su última respuesta en este tema y si sus planes han cambiado en relación con esta tarea para finalizar la compatibilidad con Python 2.

El 2 de julio de 2018, @davidism escribe:

Aparentemente, se está probando en urllib3, pero no sé lo suficiente al respecto.

Acabo de ver esto hace un tiempo: si alguien está interesado en aprender más, parece que python-trio/urllib3#1 tiene muchos detalles. Tenga en cuenta el enlace a urllib3/urllib3#1323, que contiene:

Solución: mantenemos una copia del código, la versión con anotaciones asíncronas/en espera, y luego un pequeño script mantiene la copia síncrona al eliminarlas automáticamente. No es hermoso, pero que yo sepa, todas las alternativas son peores...

(Sigue leyendo allí si estás interesado).

Es bueno ver que aparentemente esto ha seguido funcionando bien, según el progreso constante que se está logrando en https://github.com/python-trio/urllib3/commits/bleach-spike.

Pequeño golpe para que este problema vuelva a estar en el radar. Con 3.5 llegando a EOL este año, creo que es un buen momento para comenzar a pensar en el soporte asíncrono.

En el año y medio desde que se publicó esto, no ha habido mucha actividad para implementarlo. Personalmente no tengo ninguna experiencia ni necesidad de asyncio, y aunque me gusta ASGI, nunca fue algo que iba a asumir yo mismo.

Mientras tanto, @pgjones , autor de Quart, se ha involucrado más en Werkzeug. Quart ahora usa Werkzeug entre bastidores siempre que sea posible, y continuamos desarrollándolo. Hubo cierto rechazo por parte de un mantenedor de Flask acerca de ir con ASGI, por lo que Phil también creó pallets/flask#3412 que al menos permitían el enrutamiento a las funciones async def , pero eso ha estado sentado por un tiempo. En este punto, prefiero ir a ASGI que conformarme con eso. @ edk0 creó # 1330 para hacer el análisis de formularios sans-io, pero también ha estado sentado, y probablemente debería pasar por un poco más de diseño y revisión primero.

Podría preguntarse: "¿Por qué Flask no puede hacer lo que hizo Django?" No soy un experto en las partes internas de Django, pero @andrewgodwin me explicó hace un tiempo que Django tiene un momento "más fácil" (léase: aún muy complicado) debido a cómo se adaptó originalmente a WSGI, a diferencia del API muy centrada en WSGI con la que comenzaron Werkzeug y Flask. Además, Django recibe mucha más atención y recursos a tiempo completo que Pallets.

Entonces, ¿dónde deja eso este problema? Si desea un marco compatible con Flask que use Werkzeug, use Quart. Contribuya a Quart (o Flask) para hacerlos más compatibles con API donde falta. Si desea que Werkzeug y Flask admitan ASGI, tendrá que dar un paso al frente. Empieza a aprender sobre ASGI. Comience a identificar las partes de bloqueo y específicas de WSGI de la API de Werkzeug. Comience a pensar en las abstracciones que podemos hacer para habilitar implementaciones tanto para WSGI como para ASGI. Luego, vuelva a traer esa investigación a esta discusión para que podamos comenzar a diseñar y escribir relaciones públicas.

Gracias por la sugerencia de Quart , estaría muy feliz de aceptar contribuciones.

He intentado responder por qué creo que Flask no puede hacer lo que ha hecho Django en este artículo . En última instancia, creo que pallets/flask#3412 es la mejor solución para Flask.

En términos de Werkzeug, creo que ASGI es posible, con algo de dolor. Un ejemplo notable del dolor es que muchas cosas en Werkzeug son llamadas WSGI (por ejemplo, excepciones). Con ASGI no está claro cómo podría/debería usarse esta funcionalidad, por lo que preferiría eliminarla.

Mi plan es seguir integrando Werkzeug en Quart ajustando Werkzeug hacia ASGI (sans-io) sobre la marcha (tanto como se pueda aceptar); mi único obstáculo es la falta de tiempo.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

SimonSapin picture SimonSapin  ·  12Comentarios

caiz picture caiz  ·  3Comentarios

androiddrew picture androiddrew  ·  14Comentarios

alexgurrola picture alexgurrola  ·  5Comentarios

golf-player picture golf-player  ·  10Comentarios