简体   繁体   English

如何将独立的Python脚本集成到Rails应用程序中?

[英]How to integrate a standalone Python script into a Rails application?

I've got a program that has a small file structure going on and is then ran using 我有一个程序,它有一个小的文件结构,然后运行使用

python do_work.py foo bar

I want my Rails users to press a button and have this happen for them, with the result either uploaded somewhere or just thrown to them as a download link or something of the sort - the output of do_work.py (say, it's result.txt ) 我希望我的Rails用户按下按钮并为他们发生这种情况,结果要么上传到某个地方,要么只是作为下载链接或类似的东西抛出 - do_work.py的输出(比如,它是result.txt

I also want to clarify that the script results in the creation on the filesystem of 3 separate files, which are not text files (which shouldn't matter and isn't really the problem here) 我还想澄清一下,脚本会在文件系统上创建3个独立的文件,这些文件不是文本文件(这应该不重要,这里不是问题)

What is the best way to go about it? 最好的方法是什么? Can rake run exec Python? 可以rake运行exec Python吗? More importantly, is this doable on heroku? 更重要的是,这在heroku上是可行的吗?

I have Python installed on my system but the provided answer by sockmonk doesn't seem to work - it returns nil. 我在我的系统上安装了Python,但sockmonk提供的答案似乎不起作用 - 它返回nil。 Mind you, other commands like ls seem to work. 请注意,像ls这样的其他命令似乎有效。

Could it be a permissions problem? 这可能是权限问题吗?

def index
    value = %x( python --version )
    render :text => value
end

Incidentally, trying this in irb : 顺便说一下,在irb尝试这个:

%x(python)

Brings up the Python terminal INSIDE of irb. 打开irb的Python终端INSIDE。 It will not take params for whatever reason however. 然而,无论出于何种原因,它都不会占用。

Your index method does not work because python --version outputs its version to STDERR, not STDOUT. 你的index方法不起作用,因为python --version其版本输出到STDERR,而不是STDOUT。 If you don't need to separate these streams, you may just redirect STDERR to STDOUT: 如果您不需要分离这些流,您可以将STDERR重定向到STDOUT:

value = %x(python --version 2>&1)

This call is synchronous, so after running the script ( python do_work.py foo bar 2>&1 ), you should be able to read the files produced by it. 此调用是同步的,因此在运行脚本( python do_work.py foo bar 2>&1 )之后,您应该能够读取它生成的文件。

If the script is not able to create the files for some reason, you will now see the exception in the value variable because error messages are usually sent to STDERR. 如果脚本由于某种原因无法创建文件,您现在将在value变量中看到异常,因为错误消息通常会发送到STDERR。

If you want to separate STDERR from STDOUT, use the Open3 module. 如果要将STDERR与STDOUT分开,请使用Open3模块。

Beware that the script takes some time to run, so the calls may overlap. 请注意脚本需要一些时间才能运行,因此调用可能会重叠。 I would use a queue here to prevent this. 我会在这里使用一个队列来防止这种情况。

And don't forget to check the data the user enters. 并且不要忘记检查用户输入的数据。 Never pass it directly to the script. 永远不要将它直接传递给脚本。

It partly depends on the format of the data. 它部分取决于数据的格式。 If it's not too long and can be rendered directly in the browser, you can just do something like this in a rails controller: 如果它不是太长并且可以直接在浏览器中呈现,您可以在rails控制器中执行以下操作:

result = `python do_work.py foo bar`
render :text => result

And assuming that result is plain ASCII text, the result will go straight to their browser. 假设结果是纯ASCII文本,结果将直接进入他们的浏览器。 If the params to do_work.py come from the user you MUST validate them first though, so you don't wind up creating a nasty vulnerability for yourself. 如果do_work.py的参数来自用户,你必须首先验证它们,所以你最终不会为自己创建一个讨厌的漏洞。 Using the system() call would probably be safer in that case. 在这种情况下,使用system()调用可能会更安全。

If you want to send the results back as a file, look at ruby's Tempfile class for creating the file (in a way that won't stick around forever), and rails' send_file and send_data commands for some different options to send back the results that way. 如果你想将结果作为文件发回,请查看ruby的Tempfile类以创建文件(以不会永远存在的方式),并使用rails的send_file和send_data命令发送回结果那样。

The answer from utapyngo is cover almost all you need to know. utapyngo的答案几乎涵盖了你需要知道的全部内容。 I'll answer this part: 我会回答这部分:

incidentally, trying this in irb: %x(python) Brings up the python terminal INSIDE of irb. 顺便说一下,在irb中尝试这个:%x(python)打开irb的python终端INSIDE。 It will not take params for whatever reason however. 然而,无论出于何种原因,它都不会占用。

To pass parameters to your python script, simply pass it. 要将参数传递给python脚本,只需传递它即可。 Example: 例:

[fotanus@thing ~]$ python a.py 
args:
['a.py']
[fotanus@thing ~]$ irb
1.8.7 :001 > %x(python a.py foo bar)
 => "args:\n['a.py', 'foo', 'bar']\n" 

This works on ruby 1.8, 1.9 and 2.0. 这适用于ruby 1.8,1.9和2.0。

It depends how deeply you want to integrate the python script. 这取决于你想要整合python脚本的深度。 There are ways to actually call python modules directly from ruby. 有一些方法可以直接从ruby中调用python模块。

http://www.goto.info.waseda.ac.jp/~fukusima/ruby/python-e.html http://www.goto.info.waseda.ac.jp/~fukusima/ruby/python-e.html

This would give you the benefit of getting the output directly from your python script instead of going over the I/O device. 这样可以直接从python脚本获取输出,而不是通过I / O设备。

I would do something like the following. 我会做类似以下的事情。

Asynchronously execute this task in the background. 在后台异步执行此任务。 And once the result is ready report it to the user. 一旦结果准备好,就向用户报告。

One way you can achieve this will by using Open3 and delayed_job gem. 通过使用Open3delayed_job gem,您可以实现此目的的一种方法。

Take a look the popen3 method in Open3 module. 看看Open3模块中的popen3方法。

Open3.popen3([env,] cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
  pid = wait_thr.pid # pid of the started process.
  ...
  exit_status = wait_thr.value # Process::Status object returned.
}

In your case you can change the Open3.popen3 statement to something like the following 在您的情况下,您可以将Open3.popen3语句更改为如下所示

Open3.popen3("python do_work.py foo bar"){
  ...
  # mechanism for reporting like setting a flag in database system
  # or queue system
}

Note: you should give the full path to your python script 注意:您应该提供python脚本的完整路径

And then use delayed_job gem to run this as a background task. 然后使用delayed_job gem将其作为后台任务运行。

You should also have a polling mechanism which will poll the system to see if the flag is set which would mean the result is ready and then serve it to the user. 您还应该有一个轮询机制,它将轮询系统以查看是否设置了标志,这意味着结果已准备就绪,然后将其提供给用户。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM