Werkzeug: werkzeug 是否有计划支持 ASGI?

创建于 2018-06-07  ·  21评论  ·  资料来源: pallets/werkzeug

Werkzeug 提供了许多有用的方法,如果它支持 ASGI 会比我们从头开始容易得多。

最有用的评论

是的,Werkzeug 和 Flask 最终会支持 ASGI。 我没有这方面的时间表,但如果有人开始了 PR,我很乐意帮助审查 PR。

但是,我不会成为实施它的人,我需要社区的帮助。 请参阅下面的最新更新: https ://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

所有21条评论

是的,Werkzeug 和 Flask 最终会支持 ASGI。 我没有这方面的时间表,但如果有人开始了 PR,我很乐意帮助审查 PR。

但是,我不会成为实施它的人,我需要社区的帮助。 请参阅下面的最新更新: https ://github.com/pallets/werkzeug/issues/1322#issuecomment -600926145

我对这方面的工作很感兴趣。

我有一些工作但很老套的 ASGI 支持: werkzeugflask 。 在继续之前,我想更多地了解您可能有的任何计划。

我已经想到的事情,除了我在那里所做的一切通常会更好:

  • 我通过在一个线程中运行它的同步代码来支持表单解析器(当我异步读取数据时我阻塞了它)。 我认为这行不通。 我的首选方法是排除 IO
  • 上下文本地支持非常脆弱。 似乎有一种正确的方法可以做到这一点,但它涉及到干扰全局状态,尤其是对于 ASGI,我们不能假设我们拥有世界。
  • 在 ASGI 下运行时,没有明显的方法可以按需解析表单数据以实现同步功能。 除非我们可以告诉视图函数是异步的,否则急切地解析表单数据可能值得考虑。 无论默认值是什么,显然都有一个装饰器来覆盖它。

请让我知道你的想法。

我通过在一个线程中运行它的同步代码来支持表单解析器(当我异步读取数据时我阻塞了它)。 我认为这行不通。 我首选的方法是排除 IO。

我猜轻触方法只是重新实现现有的解析器,但使用异步 IO。 如果两个解析器类可以在底层共享一个通用的 sans-IO 实现会更简洁,但只是复制和稍微修改现有实现可能更实用。

上下文本地支持非常脆弱。

上下文本地变量(用于 asyncio)存在于 3.7 标准库中。 https://docs.python.org/3.7/library/contextvars.html
我猜想使用这些,并带有一个可选的兼容库来支持早期版本的 python。 (或者只是假设 Flask 上的 ASGI 最终会成为 3.7+ 的东西)

werkzeug 是否使用线程局部变量? (我知道 Flask 确实如此)

在 ASGI 下运行时,没有明显的方法可以按需解析表单数据以实现同步功能

我建议不要为解析表单数据提供同步接口。 (或以任何方式访问请求正文),而只是在其上提供异步 API。 有关示例,请参见 Starlette 的 API ... https://github.com/encode/starlette#body

由于 Python 3.7 已经发布,只要没有其他代码受到它的影响,我不会反对使用需要 Python 3.7 的异步功能。 但是,如果有一个与本机 3.7 解决方案一样好的反向移植——那就更好了!

关于异步表单数据解析......我想像await request.parse()这样的异步函数就足够了,然后在尝试访问未解析的表单数据时引发异常?

当然,或者只是让values和朋友自己异步: values = await request.valuesvalues = await request.values() ,主要取决于哪个看起来更好。

这不需要像(await request.form)['foo']这样丑陋的东西来进行异步调用,同时直接获取 dict 元素而不在两者之间分配吗?

是的 :(

不过,我不确定这是特别可以避免的。 我不认为

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

真的比

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

虽然这显然受制于口味。

我想我们也可以发明一个“异步字典”,它的 _members_ 都是异步化的,但如果没有看到它,我想它最终会相当混乱。

当然,或者只是让值和朋友自己异步:values = await request.values 或 values = await request.values(),主要取决于哪个看起来更好。

我建议将函数调用用于执行 I/O 的操作,而不是属性。

那不需要像 (await request.form)['foo'] 这样丑陋的东西来进行异步调用,同时直接获取 dict 元素而不在两者之间分配吗?

耸肩- 不要那样做。

asyncio 必须更明确地说明代码库的哪些部分执行 I/O,因此我倾向于将它们分成单独的行。

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

虽然我完全赞成添加异步支持,但我们仍然无法破坏 Python 2 和同步版本。 我从@njsmith听到的一种方法是将所有内容编写为异步,然后使用类似于 2to3 的工具生成同步版本。 显然它正在 urllib3 中进行尝试,但我对此知之甚少。

我想知道我们是否可以为request.form等背后的对象添加魔法,因此调用它们将执行异步操作,而通常的类似 dict 的方法将是同步的(并且在异步模式下会失败)。 或者我们可以在异步模式下对request.form等进行任何访问失败,并为异步版本使用单独的名称,例如request.parse_form()

或者... request_class = AsyncRequest如果有人想要异步; 这实际上可能是AsyncFlask类中的默认值。

或者... request_class = AsyncRequest 如果有人想要异步; 这实际上可能是 AsyncFlask 类中的默认值。

这种方法对我来说很有意义,是的。

关于表单解析器,我尝试在 #1330 中对其进行 sansio-ing。

https://github.com/andrew-d/python-multipart上还有一个流式表单解析器实现,覆盖率为 100%。 (我找到了另一个,但它并不明显可以很容易地适应“提要数据,处理事件”流程。)

python-multipart是我现在用于 Starlette 的库。 您可以在这里查看与异步流的集成: https ://github.com/encode/starlette/blob/master/starlette/formparsers.py#L207

我也一直在考虑呈现同步和异步兼容接口的最佳方法,因为我也希望 Starlette现在也呈现一个同步”)

对于 Starlette,我想我可能会将实际解析推送到async parse(self)方法中,并且通常在请求调度期间的某个时间调用它,但要公开常规的普通属性formfiles等...用于访问用户代码的结果。

题外话,但与一个流行的 ASGI 和 werkzeug 案例有关(我不想破坏这个问题,但不想创建一个没有实质内容的重复问题):

如果您使用./manage.py runserver_plushttps://github.com/django/channels (使用 ASGI)运行https://github.com/django-extensions/django-extensions则给出Opcode -1 (操作码减 1)在检查器中,现在尝试运行./manage.py runserver直到支持 ASGI。

(Werkzeug 在 Django 的底层使用了 django-extensions,我花了几个小时弄清楚为什么,因为我已经习惯了使用runserver_plus进行调试)

我使用 runserver_plus 以便可以在开发中使用 https。
我现在正在尝试使用 Django 通道添加 Websockets 支持,并且遇到了通道使用 runserver 并且不支持 runserver_plus 的问题。 所以,我可以使用频道,也可以使用 https,不能同时使用!

@davidism请告诉我,自从您在本主题中的最后一个回答以来,对 Flask 的 ASGI 支持的实现是否有任何变化,以及您的计划是否与此任务相关以终止对 Python 2 的支持?

2018 年 7 月 2 日, @davidism写道:

显然它正在 urllib3 中进行尝试,但我对此知之甚少。

刚刚看到这个——如果有人有兴趣了解更多,看起来 python-trio/urllib3#1 有很多细节。 请注意指向 urllib3/urllib3#1323 的链接,其中包含:

解决方案:我们维护一份代码副本——带有 async/await 注释的版本——然后一个小脚本通过自动剥离它们来维护同步副本。 它并不漂亮,但据我所知,所有的选择都更糟......

(如果有兴趣,请继续阅读。)

很高兴看到这显然继续运作良好,基于https://github.com/python-trio/urllib3/commits/bleach-spike 取得的稳步进展。

小碰撞使这个问题重新引起人们的注意。 随着今年 3.5 达到 EOL,我认为现在是开始考虑异步支持的好时机吗?

自此发布以来的一年半时间里,实施它的活动并不多。 我个人对 asyncio 没有任何经验或需求,虽然我确实喜欢 ASGI,但我从来不会自己承担。

与此同时,Quart 的作者@pgjones更多地参与了 Werkzeug。 Quart 现在尽可能在幕后使用 Werkzeug,我们正在继续开发它。 Flask 维护人员对使用 ASGI 提出了一些反对意见,因此 Phil 还创建了至少允许路由到async def函数的pallets/flask#3412,但现在已经有一段时间了。 在这一点上,我宁愿去 ASGI 而不是满足于此。 @edk0创建了 #1330 来进行表单解析 sans-io,但它也一直在使用,可能应该先进行更多设计和审查。

你可能会问,“为什么 Flask 不能像 Django 那样做?” 我不是 Django 内部的专家,但@andrewgodwin 不久前向我解释说 Django 有一个“更容易”(阅读:仍然非常复杂)的时间,因为它最初是如何适应 WSGI 的,而不是Werkzeug 和 Flask 开始使用的非常以 WSGI 为中心的 API。 此外,与 Pallets 相比,Django 获得了更多的全职关注和资源。

那么这个问题在哪里呢? 如果您想要一个使用 Werkzeug 的 Flask 兼容框架,请使用 Quart。 为 Quart(或 Flask)做出贡献,以使它们在缺少的地方与 API 更兼容。 如果您希望 Werkzeug 和 Flask 支持 ASGI,您将需要加强。 开始学习 ASGI。 开始识别 Werkzeug API 中特定于 WSGI 的部分和阻塞部分。 开始考虑我们可以进行的抽象,以支持 WSGI 和 ASGI 的实现。 然后将这项研究带回到讨论中,这样我们就可以开始设计和编写 PR。

感谢Quart的建议,我很乐意接受对它的贡献。

我试图回答为什么我认为 Flask 不能做 Django 在本文中所做的事情。 最终,我认为pallets/flask#3412 是Flask 的最佳解决方案。

就 Werkzeug 而言,我认为 ASGI 是可能的,但有一些痛苦。 痛苦的一个显着例子是 Werkzeug 中的许多东西都是 WSGI 可调用对象(例如异常)。 对于 ASGI,尚不清楚如何/应该使用此功能,因此我更愿意将其删除。

我的计划是继续将 Werkzeug 集成到 Quart 中,将 Werkzeug 调整为 ASGI(sans-io)(尽可能多地接受)——我唯一的障碍是没有时间。

此页面是否有帮助?
0 / 5 - 0 等级