我正在尝试创建一个芹菜任务,用于在将图像存储到Amazon S3之前上传和调整图像大小。但它没有按预期工作。没有任务,一切都运转正常。这是到目前为止的代码:
栈跟踪
Traceback (most recent call last):
File "../myVE/lib/python2.7/site-packages/kombu/messaging.py", line 579, in _receive_callback
decoded = None if on_m else message.decode()
File "../myVE/lib/python2.7/site-packages/kombu/transport/base.py", line 147, in decode
self.content_encoding, accept=self.accept)
File "../myVE/lib/python2.7/site-packages/kombu/serialization.py", line 187, in decode
return decode(data)
File "../myVE/lib/python2.7/site-packages/kombu/serialization.py", line 74, in pickle_loads
return load(BytesIO(s))
File "../myVE/lib/python2.7/site-packages/werkzeug/datastructures.py", line 2595, in __getattr__
return getattr(self.stream, name)
File "../myVE/lib/python2.7/site-packages/werkzeug/datastructures.py", line 2595, in __getattr__
return getattr(self.stream, name)
...
RuntimeError: maximum recursion depth exceeded while calling a Python object
views.py
from PIL import Image
from flask import Blueprint, redirect, render_template, request, url_for
from myapplication.forms import UploadForm
from myapplication.tasks import upload_task
main = Blueprint('main', __name__)
@main.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
if form.validate_on_submit():
upload_task.delay(form.title.data, form.description.data,
Image.open(request.files['image']))
return redirect(url_for('main.index'))
return render_template('upload.html', form=form)
tasks.py
from StringIO import StringIO
from flask import current_app
from myapplication.extensions import celery, db
from myapplication.helpers import resize, s3_upload
from myapplication.models import MyObject
@celery.task(name='tasks.upload_task')
def upload_task(title, description, source):
stream = StringIO()
target = resize(source, current_app.config['SIZE'])
target.save(stream, 'JPEG', quality=95)
stream.seek(0)
obj = MyObject(title=title, description=description, url=s3_upload(stream))
db.session.add(obj)
db.session.commit()
答案 0 :(得分:6)
我知道这是一个非常古老的问题,但我正在努力将文件的内容传递给芹菜任务。我会不断尝试跟随其他人做的错误。所以我写了这篇文章,希望将来能有所帮助。
io.BytesIO
作为流我对将图像保存到磁盘并再次读取不感兴趣,所以我想传递所需的数据以在后台重建文件。
尝试按照其他人的建议,我不断收到编码错误。 一些错误是:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
TypeError: initial_value must be str or None, not bytes
TypeError
抛出了io.StringIO
。试图解码数据以摆脱UnicodeDecodeError
没有多大意义。由于数据首先是二进制的,我尝试使用io.BytesIO
实例,这非常有效。我唯一需要做的就是使用base64对文件的流进行编码,然后我就可以将内容传递给芹菜任务了。
images.py
import base64
file_.stream.seek(0) # start from beginning of file
# some of the data may not be defined
data = {
'stream': base64.b64encode(file_.read()),
'name': file_.name,
'filename': file_.filename,
'content_type': file_.content_type,
'content_length': file_.content_length,
'headers': {header[0]: header[1] for header in file_.headers}
}
###
# add logic to sanitize required fields
###
# define the params for the upload (here I am using AWS S3)
bucket, s3_image_path = AWS_S3_BUCKET, AWS_S3_IMAGE_PATH
# import and call the background task
from async_tasks import upload_async_photo
upload_async_photo.delay(
data=data,
image_path=s3_image_path,
bucket=bucket)
async_tasks
import base64, io
from werkzeug.datastructures import FileStorage
@celery.task
def upload_async_photo(data, image_path, bucket):
bucket = get_s3_bucket(bucket) # get bucket instance
try:
# decode the stream
data['stream'] = base64.b64decode(data['stream'])
# create a BytesIO instance
# https://docs.python.org/3/library/io.html#binary-i-o
data['stream'] = io.BytesIO(data['stream'])
# create the file structure
file_ = FileStorage(**data)
# upload image
bucket.put_object(
Body=file_,
Key=image_path,
ContentType=data['content_type'])
except Exception as e:
print(str(e))
我还改变了芹菜接受的内容以及它如何序列化数据。为了避免在将Bytes实例传递给celery任务时遇到问题,我必须将以下内容添加到我的配置中:
CELERY_ACCEPT_CONTENT = ['pickle']
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
答案 1 :(得分:5)
您似乎试图将整个上传的文件作为Celery消息的一部分传递。我想这会给你带来一些麻烦。我建议您查看是否可以将文件作为视图的一部分保存到Web服务器,然后让消息(“delay”参数)包含文件名而不是整个文件的数据。然后,任务可以从硬盘驱动器读取文件,上传到s3,然后在本地删除它。
答案 2 :(得分:0)
老问题,但我遇到了同样的问题。接受的答案对我不起作用(我使用的是Docker实例,因此Celery无法访问生产者文件系统。此外,它首先将文件保存到本地文件系统的速度很慢)。
我的解决方案将文件保存在RAM中。因此它要快得多。 唯一的缺点是如果你需要处理大文件(> 1GB),那么你需要一个拥有大量RAM的服务器。
doc_file的类型为werkzeug.datastructure.FileStorage
(see docs here)
将文件发送给芹菜工人:
entry.delay(doc_file.read(), doc_file.filename, doc_file.name, doc_file.content_length, doc_file.content_type, doc_file.headers)
收到文件:
from werkzeug.datastructures import FileStorage
from StringIO import StringIO
@celery.task()
def entry(stream, filename, name, content_length, content_type, headers):
doc = FileStorage(stream=StringIO(stream), filename=filename, name=name, content_type=content_type, content_length=content_length)
# Do something with the file (e.g save to Amazon S3)
答案 3 :(得分:0)
我知道这是一篇非常古老的文章,但以防万一,它可以帮助某人-在这种情况下,最好的解决方法是从外部源下载图像,然后执行异步操作。
按@Obeyed的建议修复序列化问题后,我可能会遇到类似的异步问题(尽管不需要更改celery配置),但是由于文件内容可能是。
如果您要将异步任务委派给工作机,则@Mark Hildreth的方法不会很有帮助。
在这种情况下,也许一种更好的方法是同步上传原始图像,然后异步下载,调整大小并重新上传图像以替换原始图像。