Python 即时从应用程序获取文件(不将其保存在文件系统中)

Python get file from an app on-the-fly (without saving it in file system)

我想让用户向我的应用程序提交一个 MS Word 文件,用 python-docx 库处理它,然后 return 返回。由于文件可能很大,我不想在处理后将其保存到文件系统中,而是 return 下载。

从流中获取文件 - 这有效

import docx
from docx.document import Document 
from StringIO import StringIO

source_stream = StringIO(request.vars['file'].value)
document = docx.Document(source_stream)
source_stream.close()
process_doc(document)

Return 它作为流 - 这不起作用

应用程序确实让用户下载文件,但是*MS Word 无法打开文件,提示"because some part is missing or invalid" .

def download(document, filename):
    import contenttype as c
    import cStringIO
    out_stream = cStringIO.StringIO()
    document.save(out_stream)  

    response.headers['Content-Type'] = c.contenttype(filename)
    response.headers['Content-Disposition'] = \
            "attachment; filename=%s" %  filename
    return out_stream.getvalue()

我找到了 Upload a StringIO object with send_file() 但这仍然存在于烧瓶框架中。我宁愿使用 web2py 框架

更新 1

有人说在将文件指针发送到输出流之前将文件指针移动到文档数据的开头。但是要怎么做呢?

更新 2

正如@scanny 所建议的,我创建了一个空文件,

document = docx.Document()

并使用 BytesIO 模块从文件对象下载:

document = docx.Document() 
from io import BytesIO
out_stream = BytesIO()
document.save(out_stream)
filename = 'temporal_file.docx'
filepath = os.path.join(request.folder, 'uploads',filename )
try:
    with open(filepath, 'wb') as f:
        f.write(out_stream.getvalue())
    response.flash ='Success to open file for writing'
    response.headers['Content-Disposition'] = "attachment; filename=%s" % filename
    response.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    #response['X-Sendfile'] = filepath
    #response['Content-Length'] = os.stat(filepath).st_size
    return  out_stream.getvalue()

如代码所示,我还将那个空文件写入了文件系统。而且我可以轻松地手动下载它并在 MS word 中打开它:

所以,问题仍然悬而未决为什么下载的 MS Word 文件(通过输出流)损坏并且无法用 MS Word 打开

更新 3

我已经从文件输出到输出流的过程中消除了 python-docx。结果是一样的:文件下载过程完成后,无法在 MS Word 中打开它。代码:

# we load without python-docx library
from io import BytesIO
try:
    filename = 'empty_file.docx'
    filepath = os.path.join(request.folder, 'uploads',filename )
    # read a file from file system (disk)
    with open(filepath, 'rb') as f: 
        out_stream = BytesIO(f.read())
    response.flash ='Success to open file for reading'
    response.headers['Content-Disposition'] = "attachment; filename=%s" % filename
    response.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    return out_stream.getvalue()
except Exception as e:
    response.flash ='Error open file for reading or download it - ' + filename
return

我会先保存到 file-like object,然后将 file-like object 复制到一个文件(本地,无需下载)。这应该平分问题发生的范围。顺便说一下,我会使用 BytesIO 而不是 StringIO。它在 2.7 中可能没有什么不同,但它可以,并且 StringIO 在 Python 3 中无论如何都不起作用:

from io import BytesIO

# ... code that processes `document`
out_stream = BytesIO()
document.save(out_stream)
with open('test.docx', 'wb') as f:
    f.write(out_stream.getvalue())

如果这不起作用(test.docx 无法打开),您已将问题缩小到 "before" document.save() 调用。

如果 确实 有效,您可以再次尝试下载并查看,但要特别注意下载方法中预期的 return 值类型。你在这里得到的是一个字节序列。如果它期望 file-like object 或者可能是一条路径,那也可能是问题所在。

将文件指针移动到开头(使用 out_stream.seek(0))只有在返回 file-like object 时才有意义,例如 return out_stream return outstream.getvalue() 个。后者 returns bytes,当然没有文件指针。 BytesIO(或 StringIO).getvalue() 不需要设置文件游标;它总是 returns object.

的全部内容

此外,我不会依赖 contenttype 来正确处理,而是将 content-type header 拼写为:application/vnd.openxmlformats-officedocument.wordprocessingml.document。如果 contenttype 将文件错误识别为 .doc 格式 (pre-Word 2007) 文件而不是 .docx 格式(Word 2007 及更高版本)文件,这也可能导致问题。