Werkzeug: Werkzeug tem planos para apoiar ASGI?

Criado em 7 jun. 2018  ·  21Comentários  ·  Fonte: pallets/werkzeug

Werkzeug oferece muitos métodos úteis, seria muito mais fácil se suportasse ASGI do que se começássemos do zero.

ASGI

Comentários muito úteis

Sim, Werkzeug e Flask eventualmente suportarão ASGI. Eu não tenho um cronograma para isso, embora eu ficaria feliz em ajudar a revisar um PR se alguém começar um.

No entanto, não sou eu que vou implementar, preciso da ajuda da comunidade. Veja a atualização mais recente abaixo: https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Todos 21 comentários

Sim, Werkzeug e Flask eventualmente suportarão ASGI. Eu não tenho um cronograma para isso, embora eu ficaria feliz em ajudar a revisar um PR se alguém começar um.

No entanto, não sou eu que vou implementar, preciso da ajuda da comunidade. Veja a atualização mais recente abaixo: https://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

Estou interessado em trabalhar nisso.

Eu tenho algum suporte ASGI funcionando, mas hacky: werkzeug , flask . Eu gostaria de entender mais sobre quaisquer planos que você possa ter antes de prosseguir.

Coisas que já me ocorrem, além do fato de que tudo o que fiz lá poderia ser geralmente mais legal:

  • Estou dando suporte ao analisador de formulário apenas executando seu código síncrono em um thread (que eu bloqueio enquanto leio os dados de forma assíncrona). Eu não acho que isso vai fazer. Minha abordagem preferida seria fatorar o IO .
  • O suporte local de contexto é bastante frágil. Parece haver uma maneira certa de fazer isso , mas envolve interferir no estado global e, especialmente com ASGI, não podemos assumir que somos donos do mundo.
  • Não há uma maneira óbvia, ao executar em ASGI, de analisar dados de formulário sob demanda para uma função síncrona. Pode valer a pena considerar analisar ansiosamente os dados do formulário, a menos que possamos dizer que a função de exibição é assíncrona. Qualquer que seja o padrão, obviamente pode haver um decorador para substituí-lo.

Por favor, deixe-me saber seus pensamentos.

Estou dando suporte ao analisador de formulário apenas executando seu código síncrono em um thread (que eu bloqueio enquanto leio os dados de forma assíncrona). Eu não acho que isso vai fazer. Minha abordagem preferida seria fatorar o IO.

Eu acho que a abordagem de toque leve será apenas para reimplementar o analisador existente, mas com E/S assíncrona. Seria mais limpo se as duas classes de analisador pudessem compartilhar uma implementação sans-IO comum sob o capô, mas pode ser mais prático apenas duplicar e modificar ligeiramente a implementação existente.

O suporte local de contexto é bastante frágil.

Locais de contexto (para assíncrono) existem no 3.7 stdlib. https://docs.python.org/3.7/library/contextvars.html
Eu acho que usá-los, com uma biblioteca de compatibilidade opcional para suportar versões anteriores do python. (Ou então apenas assuma que ASGI no Flask acabaria sendo uma coisa 3.7+)

O werkzeug usa thread-locals? (Estou ciente de que o Flask faz)

Não há uma maneira óbvia, ao executar em ASGI, de analisar dados de formulário sob demanda para uma função síncrona

Eu sugiro não oferecer uma interface síncrona para analisar dados de formulário. (Ou para acessar o corpo da solicitação de qualquer maneira) e, em vez disso, apenas ofereça APIs assíncronas para ele. Veja a API do Starlette para alguns exemplos aqui... https://github.com/encode/starlette#body

Como o Python 3.7 foi lançado, eu não me oporia a ter recursos assíncronos exigindo o Python 3.7, desde que nenhum outro código seja afetado por ele. Mas se houver um backport que seja tão bom quanto a solução nativa 3.7 - melhor ainda!

Em relação à análise de dados de formulário assíncrono... Acho que algo como await request.parse() em uma função assíncrona seria suficiente e, em seguida, geraria uma exceção ao tentar acessar dados de formulário não analisados?

Claro, ou apenas faça values e amigos assíncronos: values = await request.values ou values = await request.values() , dependendo principalmente do que parece melhor.

isso não exigiria coisas feias como (await request.form)['foo'] para fazer uma chamada assíncrona enquanto obtinha um elemento dict diretamente sem atribuir no meio?

sim :(

Não tenho certeza de que isso seja particularmente evitável, no entanto. eu não acho

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

é realmente mais ou menos feio do que

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

embora isso esteja obviamente sujeito ao gosto.

Acho que também poderíamos inventar um "dict assíncrono" cujos _membros_ são todos assíncronos, mas sem ter visto um, imagino que acabaria bastante confuso.

Claro, ou apenas torne os valores e amigos assíncronos: values ​​= await request.values ​​ou values ​​= await request.values(), dependendo principalmente do que parece melhor.

Eu sugiro usar chamadas de função para operações de E/S, em vez de propriedades.

isso não exigiria coisas bastante feias como (await request.form)['foo'] para fazer uma chamada assíncrona enquanto obtém um elemento dict diretamente sem atribuir no meio?

Dar de ombros - Não faça isso.

asyncio é necessariamente mais explícito sobre quais partes da base de código executam E/S, então eu tenderia a dividi-las em linhas separadas.

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

Embora eu seja a favor de adicionar suporte assíncrono, ainda não podemos quebrar o Python 2 e sincronizar as versões. Uma abordagem que ouvi do @njsmith é escrever tudo como assíncrono e usar uma ferramenta semelhante a 2to3 para gerar a versão de sincronização. Aparentemente está sendo tentado em urllib3, mas eu não sei o suficiente sobre isso.

Gostaria de saber se poderíamos adicionar magia aos objetos por trás de request.form etc., então chamá-los fará coisas assíncronas enquanto os métodos usuais do tipo dict serão sincronizados (e falharão no modo assíncrono). Ou podemos simplesmente falhar em qualquer acesso a request.form etc. no modo assíncrono e usar um nome separado para as versões assíncronas, por exemplo request.parse_form() .

Ou... request_class = AsyncRequest se alguém quiser assíncrono; isso pode realmente ser um padrão em uma classe AsyncFlask .

Ou... request_class = AsyncRequest se alguém quiser assíncrono; isso pode realmente ser um padrão em uma classe AsyncFlask.

Esse é o tipo de abordagem que faria sentido para mim, sim.

Em relação ao analisador de formulários, tentei saná-lo em #1330.

Há também uma implementação de analisador de formulário de streaming em https://github.com/andrew-d/python-multipart com 100% de cobertura. (Encontrei um outro, mas não era óbvio que pudesse ser facilmente adaptado em um fluxo de "feed data, handle events".)

python-multipart é a biblioteca que estou usando agora para Starlette. Você pode dar uma olhada na integração com o fluxo assíncrono aqui: https://github.com/encode/starlette/blob/master/starlette/formparsers.py#L207

Também tenho pensado nas melhores maneiras de apresentar uma interface compatível com sincronização e assíncrona, já que também quero isso para Starlette (embora indo na outra direção para Werkzeug, para mim trata-se de "Eu tenho uma interface assíncrona existente, como faço para agora também apresentar uma sincronização")

Para Starlette, acho que provavelmente enviarei a análise real para um método async parse(self) e normalmente chamarei isso em algum momento durante o envio da solicitação, mas exporei propriedades simples form , files etc ... para acessar os resultados do código do usuário.

Fora do tópico, mas relacionado a um caso popular de ASGI e werkzeug (não quero inviabilizar esse problema, mas não quero criar um problema duplicado sem substância a ser adicionada):

Se você está executando https://github.com/django-extensions/django-extensions com ./manage.py runserver_plus e https://github.com/django/channels (que usa ASGI) está dando Opcode -1 (opcode menos 1) no inspetor, tente executar ./manage.py runserver por enquanto até que ASGI seja suportado.

(Werkzeug é usado por baixo do capô no Django com django-extensions e passei algumas horas descobrindo o porquê, já que estou tão acostumado a desenvolver com runserver_plus para depuração)

Eu uso o runserver_plus para poder usar https no desenvolvimento.
Agora estou tentando adicionar suporte a Websockets usando canais Django e me deparei com o problema de que os canais usam runserver e não suportam runserver_plus. Então, posso usar canais OU posso usar https, não ambos!

@davidism por favor me diga se há alguma mudança na implementação do suporte ASGI para Flask desde sua última resposta neste tópico e se seus planos mudaram em conexão com esta tarefa para encerrar o suporte para Python 2?

Em 2 de julho de 2018 @davidism escreve:

Aparentemente está sendo tentado em urllib3, mas eu não sei o suficiente sobre isso.

Acabei de ver isso há algum tempo - se alguém estiver interessado em aprender mais, parece que python-trio/urllib3#1 tem muitos detalhes. Observe o link para urllib3/urllib3#1323, que contém:

Solução: mantemos uma cópia do código – a versão com anotações async/await – e então um pequeno script mantém a cópia síncrona removendo-as automaticamente novamente. Não é bonito, mas pelo que sei todas as alternativas são piores...

(Continue lendo lá se estiver interessado.)

É bom ver que isso aparentemente continua funcionando bem, com base no progresso constante sendo feito em https://github.com/python-trio/urllib3/commits/bleach-spike.

Pequeno solavanco para trazer esse problema de volta ao radar. Com o 3.5 chegando à EOL este ano, acho que é um bom momento para começar a pensar em suporte assíncrono?

No ano e meio desde que isso foi publicado, não houve muita atividade para implementá-lo. Eu pessoalmente não tenho nenhuma experiência ou necessidade de assíncrono e, embora goste de ASGI, nunca foi algo que eu iria assumir.

Nesse meio tempo, @pgjones , autor de Quart, tornou-se mais envolvido em Werkzeug. Quart agora usa Werkzeug nos bastidores sempre que possível, e continuamos desenvolvendo isso. Houve alguma resistência de um mantenedor do Flask sobre usar ASGI, então Phil também criou pallets/flask#3412 que pelo menos permitia o roteamento para funções async def , mas isso está parado há algum tempo. Neste ponto, prefiro ir ASGI do que me contentar com isso. @edk0 criou o #1330 para fazer a análise de formulários sans-io, mas também está parado e provavelmente deve passar por mais design e revisão primeiro.

Você pode perguntar: "Por que o Flask não pode fazer o que o Django fez?" Eu não sou um especialista nas partes internas do Django, mas @andrewgodwin me explicou um tempo atrás que o Django tem um tempo "mais fácil" (leia-se: ainda muito complicado) devido a como ele se adaptou originalmente ao WSGI, em oposição ao API muito centrada em WSGI com a qual Werkzeug e Flask começaram. Além disso, o Django recebe muito mais atenção e recursos em tempo integral do que o Pallets.

Então, onde isso deixa esse problema? Se você deseja uma estrutura compatível com Flask que usa Werkzeug, use Quart. Contribua com o Quart (ou Flask) para torná-los mais compatíveis com API onde isso estiver faltando. Se você deseja que Werkzeug e Flask suportem ASGI, você precisará intensificar. Comece a aprender sobre ASGI. Comece a identificar as partes específicas do WSGI e de bloqueio da API do Werkzeug. Comece a pensar em abstrações que podemos fazer para permitir implementações para WSGI e ASGI. Em seguida, traga essa pesquisa de volta para esta discussão para que possamos começar a projetar e escrever PRs.

Obrigado pela sugestão do Quart , ficarei muito feliz em aceitar contribuições.

Eu tentei responder porque eu acho que o Flask não pode fazer o que o Django fez neste artigo . Em última análise, acho que pallets/flask#3412 é a melhor solução para o Flask.

Em termos de Werkzeug, acho que ASGI é possível, com alguma dor. Um exemplo notável da dor é que muitas coisas em Werkzeug são callables WSGI (por exemplo, exceções). Com ASGI não está claro como essa funcionalidade pode/deve ser usada, então eu prefiro removê-la.

Meu plano é continuar integrando Werkzeug em Quart ajustando Werkzeug para ASGI (sans-io) à medida que eu for (tanto quanto puder ser aceito) - meu único obstáculo é a falta de tempo.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

mrx23dot picture mrx23dot  ·  6Comentários

alexgurrola picture alexgurrola  ·  5Comentários

taion picture taion  ·  7Comentários

asottile picture asottile  ·  11Comentários

androiddrew picture androiddrew  ·  14Comentários