【问题标题】:Django asyncio call in views doesn't work视图中的 Django asyncio 调用不起作用
【发布时间】:2018-04-04 09:45:00
【问题描述】:

我在这个问题上挣扎了很长时间。 我正在尝试使用pyppeteer 以pdf 格式导出视图。这是我的功能:

  async def export_pdf(url):                                                                             
      browser = await launch()                                                                           
      page = await browser.newPage() 
      await page.goto(url) 
      pdf = await page.pdf(                                                                              
          {   
              'printBackground': True                                                                    
          }   
      )       
      await browser.close()

      return pdf

在我看来这样称呼它:

   response.content = asyncio.get_event_loop().run_until_complete(
      export_pdf(self.request.get_full_path())
  )

但我得到了这个错误

/export-pdf/1/2018/1/1/ 处的运行时错误 线程 'Thread-1' 中没有当前的事件循环。

经过一番研究,我认为有人解决了我的问题,我就这样称呼它(不太明白,但这是关于 django 的事情,我的函数没有在主线程中调用):

  loop = asyncio.new_event_loop()
  asyncio.set_event_loop(loop)
  response.content = loop.run_until_complete(
      export_pdf(
          asyncio.wait(
              export_pdf(self.request.get_full_path())
          )
      )
  )
  loop.close()

但现在我有这个错误:

/export-pdf/1/2018/1/1/ 处的类型错误 期望一个期货列表,而不是协程

我对 python 中的异步非常陌生,问题是,当我将完全相同的代码复制并粘贴到我的 ipython shell 中时,一切正常。

任何解释/灯光将不胜感激!

提前致谢。

编辑: 经过一些研究,我设法遇到了另一个错误,即

信号只在主线程中起作用

奇怪的是错误来自/usr/lib/python3.6/signal.py,甚至不是来自我的virtualenv。

【问题讨论】:

    标签: python django python-3.x python-asyncio pypputeer


    【解决方案1】:

    你的方法是正确的,除了有一个额外的asyncio.wait 电话。 只需这样做:

    coroutine = export_pdf(self.request.get_full_path())
    
    # nothing is done yet, we need to give this coroutine to an event loop which will run it
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
       data = loop.run_until_complete(coroutine)
    finally:
        loop.close()
    
    response.content = data
    

    【讨论】:

    • 是的,我已经尝试删除该 asyncio.wait() 但我得到了完全相同的错误。它来自信号中的/usr/lib/python3.6/signal.py,这很奇怪,因为它不是来自我的venv。我已经更新了我的问题
    • 信号错误是不言自明的:您的export_pdf 函数正在尝试设置新的信号处理程序,而这不能从主线程外部完成。更具体地说,pyppeteer 正在尝试设置三个信号处理程序:github.com/miyakogi/pyppeteer/…
    • 这意味着pyppeteer库不能真正在主线程之外使用,使用主线程在django中不是一个选项。所以,你可以让主线程运行这个export_pdf调用(非常糟糕的主意,你会在运行export_pdf时锁定你的http堆栈,如果另一个请求进来,它将无法正确处理),
    • 或者您可以在启动 pyppeteer 时使用三个handleSIGINThandleSIGTERMhandleSIGHUB 选项,以确保它不会尝试添加信号处理程序(这可能 i> 工作,但由于 pyppeteer 绝对不是从线程中使用的,它最终可能会中断)
    • 或者,迄今为止最简洁的选项:您可以使用 puppeteer(官方 nodeJS 库)创建一个新的内部 HTTP 服务,该服务将公开一个 GET pdf?url=... 端点,该服务将在 nodeJS 中运行,并进行实际的 PDF 渲染,然后您将能够在您的 django 应用程序中执行简单的requests.get('http://my-pdf-renderer/pdf?url=...')
    【解决方案2】:

    也许将其作为 celery 任务执行,并在调用 celery 任务时使用 apply_async。这可能会有所帮助:https://www.youtube.com/watch?v=XjzyOyLbvN8

    【讨论】:

      【解决方案3】:

      我试图做和你一样的事情。 但我没有用线程来搞乱我的 Django 服务器,而是听从 Arthur 的建议,而是决定使用我自己的 Puppeteer 微服务。

      但是在这个过程中,我发现有人有同样的想法,并为此创建了一个准备部署的工具:https://github.com/alvarcarto/url-to-pdf-api

      基本上,您只需部署此代码(如果您使用 Heroku,则单击按钮),然后使用您的应用程序的 url 开始生成 pdf。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-04-02
        • 1970-01-01
        • 2021-06-15
        • 2017-06-02
        • 2015-03-24
        • 1970-01-01
        • 2020-12-25
        • 1970-01-01
        相关资源
        最近更新 更多