首页 > 解决方案 > 在 Django 中使用 Dask 读取上传的 CSV 时出错:“InMemoryUploadedFile”对象没有属性“startswith”

问题描述

我正在构建一个 Django 应用程序,该应用程序使用户能够使用 FormField 通过表单上传 CSV。导入 CSV 后,我使用 Pandas read_csv(filename) 命令读取 CSV,这样我就可以使用 Pandas 对 CSV 进行一些处理。

我最近开始学习真正有用的 Dask 库,因为上传文件的大小可能比内存大。使用 Pandas pd.read_csv(filename) 时一切正常,但是当我尝试使用 Dask dd.read_csv(filename) 时出现错误“'InMemoryUploadedFile' 对象没有属性'startswith'”。

我对 Django、Pandas 和 Dask 很陌生。我在谷歌的任何地方与 Dask 关联时似乎都找不到这个错误。

这是我在下面尝试使用的代码(只是相关位......我希望):

里面forms.py我有:

class ImportFileForm(forms.Form):
    file_name = forms.FileField(label='Select a csv',validators=[validate_file_extension, file_size])

里面views.py

import pandas as pd
import codecs
import dask.array as da
import dask.dataframe as dd

from dask.distributed import Client
client = Client()

def import_csv(request):

    if request.method == 'POST':
        form = ImportFileForm(request.POST, request.FILES)
        if form.is_valid():

             utf8_file = codecs.EncodedFile(request.FILES['file_name'].open(),"utf-8")

             # IF I USE THIS PANDAS LINE IT WORKS AND I CAN THEN USE PANDAS TO PROCESS THE FILE
             #df_in = pd.read_csv(utf8_file)

             # IF I USE THIS DASK LINE IT DOES NOT WORK AND PRODUCES THE ERROR
             df_in = dd.read_csv(utf8_file)

这是我得到的错误输出:

AttributeError at /import_data/import_csv/
'InMemoryUploadedFile' object has no attribute 'startswith'

/home/username/projects/myproject/import_data/services.py in save_imported_doc
    df_in = dd.read_csv(utf8_file) …
▶ Local vars
/home/username/anaconda3/lib/python3.7/site-packages/dask/dataframe/io/csv.py in read
            **kwargs …
▶ Local vars
/home/username/anaconda3/lib/python3.7/site-packages/dask/dataframe/io/csv.py in read_pandas
        **(storage_options or {}) …
▶ Local vars
/home/username/anaconda3/lib/python3.7/site-packages/dask/bytes/core.py in read_bytes
    fs, fs_token, paths = get_fs_token_paths(urlpath, mode="rb", storage_options=kwargs) …
▶ Local vars
/home/username/anaconda3/lib/python3.7/site-packages/fsspec/core.py in get_fs_token_paths
        path = cls._strip_protocol(urlpath) …
▶ Local vars
/home/username/anaconda3/lib/python3.7/site-packages/fsspec/implementations/local.py in _strip_protocol
        if path.startswith("file://"): …
▶ Local vars
/home/username/anaconda3/lib/python3.7/codecs.py in __getattr__
        return getattr(self.stream, name) 

标签: djangopython-3.xpandascsvdask

解决方案


我终于让它工作了。这是一个基于 @mdurant 的答案构建的 Django 特定解决方案,谢天谢地,他为我指明了正确的方向。

默认情况下,Django 将文件存储在 2.5MB 以下的内存中,因此 Dask 无法像 Pandas 那样访问它,因为 Dask 会要求实际存储中的位置。但是,当文件超过 2.5MB 时,Django 将文件存储在一个临时文件夹中,然后可以使用 Django 命令temporary_file_path() 找到该文件夹​​。然后 Dask 可以直接使用此临时文件路径。我在他们的文档中发现了一些关于 Django 如何在后台实际处理文件的非常有用的信息:https ://docs.djangoproject.com/en/3.0/ref/files/uploads/#custom-upload-handlers 。

如果您无法提前预测用户上传的文件大小(如我的情况)并且您的文件小于 2.5MB,您可以在 Django 设置文件中更改 FILE_UPLOAD_HANDLERS 以便它将所有文件写入临时文件存储文件夹,无论大小,因此 Dask 始终可以访问它。

这是我更改代码的方式,以防万一这对处于相同情况的其他人有所帮助。

views.py

def import_csv(request):

    if request.method == 'POST':
        form = ImportFileForm(request.POST, request.FILES)
        if form.is_valid():

             # the temporary_file_path() shows Dask where to find the file
             df_in = dd.read_csv(request.FILES['file_name'].temporary_file_path())

并且在settings.py添加如下设置时,无论文件是否低于 2.5MB,Django 总是将上传的文件写入临时存储,因此 Dask 始终可以访问它

FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHandler',]

推荐阅读