全部--
好的......我已经尝试过各种方式,并且一路上都接受了体面的教育。底线是 可以从远程服务器上运行的 Jupyter Notebook(又名 IPython)调用基于 localhost 的服务,并且还可以返回结果以便它可以在 Python 中使用(尽管有限制)。这很重要,因为它使生物工作流程能够调用 Cytoscape 桌面应用程序的基于 REST 的自动化接口。
我写这篇文章是为了让我的继任者看到这一点并从中获利,或者放弃。 tons 的其他 StackOverlow 和独立文章帮助了我……太多了,最突出的是:
ipython-notebook-javascript-python-communication
Execute Discussion
workaround-for-wrapping-a-js-function-in-python-in-jupyter-notebook
这个问题分为三个部分:
- 执行 HTTP 操作(...假设 GET,但 POST、PUT 等工作,
也)
- 获取 HTTP 状态和结果
- 在 Python 中使用结果
有多重挑战:
- 将 URL 和有效负载(用于 PUT/POST)传递到 HTTP 调用程序
- 仅在 HTTP 结果可用时(而不是之前)返回给调用者
- 通过 Python 变量使 HTTP 结果可用
存在一个主要问题,因为 Python 是一种在基于浏览器 (IPython) 的环境中(部分)执行的传统同步语言。通常,这不是问题,因为远程 Jupyter 服务器先执行一个完整的 Python 单元,然后再执行另一个单元。出现问题是因为 HTTP 调用必须在 Javascript 中(即,在 %%js 或 %%javascript 单元格中或作为 IPython.display.HTML() 调用的参数),它在本地浏览器中执行(而不是远程服务器)。基于浏览器的 Javascript 的基本原则是长(例如,HTTP)操作应该异步完成(例如,使用 Promises 的 Fetch(),使用 Yield() 的生成器,或正常的 XMLHttpRequest 调用)以保持高度交互的浏览器性能。对于等待答案的工作流,异步执行会使一个糟糕的生物信息学 Python 程序员因回调地狱而震惊。此外,此类工作流预计会占用大量计算资源,并且预计不会产生交互性......必须保留因果关系。
在 Javascript 中,解决方案的要点是使用 同步 XMLHttpRequest 调用。这得到了一个同步的答案。接下来,使用未记录的 IPython.notebook.kernel.execute() 将值存储到远程服务器上的 Python 变量中。这是一个异步操作,必须最后完成。 (只能在Chrome浏览器的开发模式下查看execute()的源码:F12->static/notebook/js/services/kernels/kernel.js,找到kernel.js/Kernel.prototype.execute) .如果这是在单元格末尾完成的,IPython 将在准备好下一个单元格或等待用户输入时暂停。在这两种情况下,Python 内核似乎为 execute() 创建的语句队列提供服务。如果确实发生了这种情况,那么可以安全地预期包含 HTTP 结果的 Python 变量将准备好在后续单元格中读取。如果在同一个单元格中执行 execute() 和 Python 变量获取,那么它不会准备好。
当您将“false”传递给 XMLHttpRequest.open() 函数时,会创建一个同步 XMLHttpRequest 调用。一些浏览器可能会抱怨这一点,因为他们认为同步调用是邪恶的并且已被弃用。最好使用不会报错的浏览器(例如 Chrome)。
奇怪:如果调用 HTML() 来执行 Javascript,它应该是单元格中的最后一次调用;否则,HTML() 调用将失败。如果您将 HTML() 调用包装在 IPython.display.display() 调用中,则可以在同一单元格中将其他 Python 语句放在它之后……只是不要寻找在 IPython 之前设置的 HTTP 结果变量过渡到下一个单元格。请注意,Timeout() 之类的变通方法在这里无效,因为它似乎不会强制处理 Execute() 语句队列,因此您确实确实必须等到下一个单元格才能访问 HTTP结果。
此 Javascript 单元创建将完成工作的 Javascript 函数。请注意 Cy* 命名约定,它不太可能与 IPython 浏览器代码发生冲突。
%%js
function CyHttpGet(url) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url, false)
xhr.send(null);
if (xhr.status === 200) {
return xhr.responseText;
}
else {
return "bad news:" + xhr.status
}
}
function CySetKernelVar(varValue) {
varValue = varValue.replace(/'/g, "/'"); // Double any single quotes
var command = "httpResult = '"+ varValue + "'";
var kernel = IPython.notebook.kernel;
kernel.execute(command);
}
这个 Python 单元进行实际的 HTTP 调用:
from IPython.display import HTML
javascript = """
<script type="text/Javascript">
// This is what actually executes the HTTP and sets the result as a Python variable
CySetKernelVar(CyHttpGet('http://localhost:1234'));
</script>
"""
HTML(javascript)
这个 Python 单元使用 HTTP 结果(并且必须不是前一个单元的一部分):
print(httpResult)
httpResult='xxx' # clear variable so we're not fooled if the Javascript code doesn't do what we thought it would
请注意,通过 Execute() 将值从 Javascript 传递到 Python 的能力确实取决于 Python 内核清除单元之间的执行队列。如果这不是真的发生,我们可能会不时在 print(httpResult) 中看到“xxx” - 非常糟糕的消息。
请注意,一些网络帖子假设可以通过让 Javascript 将值存储为 DIV 语句的 innerHTML,然后让 HTML() 或 Javascript() 函数读取 innerHTML 和把它返还。我还没有让它工作,但如果它可以工作,那么 Execute() 可能就没有必要了。