首页 > 解决方案 > python cgi交互找不到graphviz程序twopi

问题描述

我有一个令人抓狂的错误,我的 python 脚本在独立运行时成功执行某些操作,但在作为 jquery 调用的 cgi 脚本运行时失败$.ajax()。欢迎任何见解。

我正在使用本地 apache2 服务器在我的新 MacbookPro(macOS 11.6)上本地开发此 Web 应用程序,我已将其配置为将相关目录中的 .py 文件作为 cgi 程序运行。

相关的工作部分是:

这个应用程序的目的是由一个 python cgi 脚本驱动,使用 python 的requests模块从本地文件系统中检索一些数据,以及从网络上的 AllegroGraph 实例中检索一些 RDF 数据,然后在网络中布局和显示图形可视化页面使用 graphviz 和 python 的 pygraphviz 模块。

javascript发出这样的GET请求:

function graphMe(charter){
    $.ajax({
        type: "get",
        url: "cartametallon.py",
        data: {"graphMe": charter},
        dataType: 'json',
        success: deploySVG,
        error: function(jqXHR, textStatus, errorThrown) {
            console.log(jqXHR.response, textStatus, errorThrown);
        }
    });
}

python cgi 脚本使用 cgi 模块来处理这个请求,如下所示:

import cgi, cgitb
cgitb.enable(format="text")
form = cgi.FieldStorage()

try:
    if 'graphMe' in form:
        charter = form.getvalue('graphMe')
        uri = "<http://chartex.org/graphid/" + charter + ">"
        
        print ("Content-Type: application/json\r\n\r\n")
        print (json.dumps(visualizeDocumentGraph(uri)))
        
except Exception:
    print ("Content-Type: text/plain\n")
    print("Exception in user code:")
    print("~"*20, __file__.split('/')[-1], "~"*20)
    traceback.print_exc(file=sys.stdout)
    print("~"*60)

visualizeDocumentGraph()函数从多个来源组装图形数据和元数据,并将其存储在 adict中,然后应将其作为 json 对象返回到引用页面。twopi该对象中存储的内容之一是由 graphviz 的算法布局的图形的 SVG 字符串。我已经验证了这个 python 函数的每个元素都按预期工作,当在命令行运行时,它返回预期的对象;但是,对jQuery.ajax()请求的响应如下所示:

Content-Type: text/plain

Exception in user code:
~~~~~~~~~~~~~~~~~~~~ cartametallon.py ~~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
  File "/opt/homebrew/lib/python3.9/site-packages/pygraphviz/agraph.py", line 1344, in _get_prog
    runprog = self._which(prog)
  File "/opt/homebrew/lib/python3.9/site-packages/pygraphviz/agraph.py", line 1800, in _which
    raise ValueError(f"No prog {name} in path.")
ValueError: No prog twopi in path.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "~/Sites/cartametallon/cartametallon.py", line 661, in <module>
    print (json.dumps(visualizeDocumentGraph(uri)))
  File "~/Sites/cartametallon/cartametallon.py", line 292, in visualizeDocumentGraph
    dgsvg = makedot(g).draw(format='svg', prog='twopi')
  File "/opt/homebrew/lib/python3.9/site-packages/pygraphviz/agraph.py", line 1596, in draw
    data = self._run_prog(prog, args)
  File "/opt/homebrew/lib/python3.9/site-packages/pygraphviz/agraph.py", line 1360, in _run_prog
    runprog = r'"%s"' % self._get_prog(prog)
  File "/opt/homebrew/lib/python3.9/site-packages/pygraphviz/agraph.py", line 1346, in _get_prog
    raise ValueError(f"Program {prog} not found in path.")
ValueError: Program twopi not found in path.

这就是难题:在cgi交互中“出现”twopi程序无法找到,但自行运行,我的python脚本没有问题。twopi二进制文件安装在,/opt/homebrew/bin/twopi可通过 env 变量轻松访问PATH

% echo $PATH
~/opt/anaconda3/condabin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

很明显,我的 python 脚本也知道它。它不仅在脚本自行运行时成功执行,而且明确知道在哪里可以找到它:

>>> os.get_exec_path()
['~/opt/anaconda3/condabin', '/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin']

我无法让我的 python 脚本返回网页所需的 json,ARGH!更令人抓狂的是,我正在尝试重构和更新在https://neolography.com/chartex/上运行良好的现有应用程序。这个工作程序最近被转移到一个新的网络主机上,需要一点点修补才能让它再次工作,而且图形输出不如我的旧主机上的好,因为 A2 主机坚持安装一个古老版本的 graphviz(不要'任务)。所以,这个工作程序的相关工作部分是:

标签: ajaxcgigraphvizrdflibpygraphviz

解决方案


我花了好几天的时间,但是感谢 Thomas 的评论、这里的一些观察以及python os 模块本身的文档,我终于明白,当我在命令行运行脚本时,“PATH”环境变量是与执行 cgi 程序上下文中的相同变量完全不同。

 % echo $PATH
~/opt/anaconda3/condabin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

>>> os.environ["PATH"]
'~/opt/anaconda3/condabin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'

os.environ["PATH"]运行 cgi 脚本时被 apache 引用的不同。

更糟糕的是,我将 $PATH 变量与sys.path具有完全不同目的的 python 混为一谈。我的 cgi 程序,使用 pygraphviz,试图执行twopi一个二进制文件/opt/homebrew/bin,在一个正在执行的 cgi 程序的上下文中,它可用的 PATH 变量如下所示:

"/usr/bin:/bin:/usr/sbin:/sbin:"

在 cgi 程序中为该变量添加必要的路径很容易,如下所示:

os.environ["PATH"] = f"{os.environ['PATH']}:/opt/homebrew/bin"

这解决了我的问题。但是,我还是很不安。这似乎是一种 hacky 方法。我仍然不完全理解为什么 apache 与 python 解释器可用的PATH不同$PATH,或者在命令行运行的脚本中不同。我收集PATH到 cgi 程序的可用是不同的,因为 apache 以不同的用户 ( _www) 执行它。

很高兴知道是否有更规范的方法来解决这个问题。我们将不胜感激地收到任何有关文档的建议,以澄清我对这个问题的理解。


推荐阅读