flask-restful使用过程中遇到的问题 - 图1

写在前面

大家好,好久都没有写过相关的文档了,因为最近在开始实习了。正好实习的单位用的技术也是Flask,所以最近就一直在做项目。说实话做实体开发和自己在学校中学习与练手的感觉完全不一样。由于自己的能力问题,在这段时间的工作中,遇到了很多问题,所以趁着周末有空,写个文档记录一下,给那些和我一样遇到这些问题的人提供一个解决问题的思路,也避免自己以后再犯这种错误了。

关于技术

目前用Flask主要是用Flask来写restful风格的API,然后呢用到的就是Flask-restplus这个插件来实现的。在这里面有很多的有趣的需求和问题,我都一一写出来。当然我的问题可能和你的不完全一样,但是呢好歹是个思路对吧。我们用到技术大概如下:

  • Flask
  • Flask-restplus
  • celery
  • SqlAlchemy
  • xlrd
  • Python == 2.7

开始问题

需求:从文件中读入数据并保存到数据库中

问题描述:首先是读取表格文件,读取到表格文件后首先判断文件的后缀首是否是正确的,就是在允许的表格格式之内:“xls、xlsx、csv”这些格式之内,然后是判断文件是否是可以使用xlrd来打开的,因为Excel文件可能存在损坏,如果是一个损坏文件那么就直接报错,然后就是校验文件的MD5值,判断是否有过上传。当通过前面的校验后才是保存到本地,然后读入到数据库中,使用celery来完成这一个目的。大概就是这样一整个流程。这里面涉及到的知识点一个一个的来:

Flask-restplus上传文件

说实话,这个是最没有难度的一个知识点:在Flask-restplus的官方文档中就有教程,大概代码如下:

  1. upload_parser = api.parser()
  2. upload_parser.add_argument('file', location='files',
  3. type=FileStorage, required=True)
  4. @api.expect(upload_parser)
  5. class UploadFile(Resource):
  6. def post(self):
  7. file = request.files["file"]

然后你将项目跑起来后就会在你的页面上看到这个接口:一个文本选择框,这样获取到的是一个FileStorage对象。然后就是关于文件的判定,判定文件后缀是否在允许的范围之内,文件是否损坏、文件内容是否重复和文件保存到本地这些操作,然后有趣的事情就开始了。

Flask-restplus验证文件

这里的验证就是我前面说的那些,包括:

  • 校验文件上传类型是否是我们所允许的类型、
  • 判断文件是否损坏
  • 判断文件内容是否重复

首先是判断文件是否在允许的范围之类,这个很简单没什么说的,直接上代码:

  1. legal_postfix_list = [".xlsx", ".xls", ".csv"]
  2. messages = dict()
  3. messages["message"] = u"上传文件格式错误"
  4. messages["is_upload_status"] = False
  5. if upload_file:
  6. file_name, file_suffix = os.path.splitext(upload_file.filename)
  7. if file_suffix not in legal_postfix_list:
  8. return messages, 415

这个没什么营养,我们继续。然后是判断你这个文件是否是可以打开的,大家可以自己做一个损坏的文件来进行测试:方法很简单,将你的Excel文件使用记事本打开然后随意删除其中的一段字符然后保存就好。然后在Python中只是简单的读表格基本上就是使用xlrd这个第三方库了,但是xlrd在打开损坏的文件会报错,所以呢我们 可以利用这一特点来测试文件是否损坏:

  1. try:
  2. f = xlrd.open_workbook(file_path)
  3. except Exception as e:
  4. return {
  5. "message": u"文件损坏"
  6. }, 400

这个是打开本地文件的方法,关键是现在的需求是不能保存到本地然后就要直接判定。有办法吗?当然有,这一点我要感谢一个大佬,是大佬给我提供的思路和解决办法,大家还记得我们上面获取到的文件对象吧,记得就好,下面开始有趣的操作:

  1. try:
  2. f = xlrd.open_workbook(file_contents=upload_file.read())
  3. except Exception as e:
  4. return return {
  5. "message": u"文件损坏"
  6. }, 400

这样也是可以的,是不是很开心,解决了一个问题。但是这个方法有一个超级大的坑,我先不说,先往后面走。然后是干嘛?校验MD5值,什么是MD5自己去看,然后呢我们写一下根据文件内容获取MD5值的方法:

  1. import hashlib
  2. file_md5 = hashlib.md5(upload_file.read()).hexdigest()

这个也实现了,是不是很开心。但是你会发现你先执行上一步后你不管上传的什么文件你的MD5值都是一样的!这个是为什么呢?这么fk egg?不着急继续向下走,然后就是通过校验的文件我们保存到本地。FileStorage对象给我们提供了一个save方法:

  1. upload_file.save(file_path)

直接使用保存吧,但是你会发现你保存的数据都是0kb,没有错,全都是0。没有想到吧。做到这里发现前面都是无用功,是不是很生气,直接说原因:==FileStorage对象的read()方法只能用一次哦!!==没有去深究原因,我想应该是读到了内存中。解决办法有吗?据说是有的,那就是使用copy.deepcpoy()来复制几份,由于某些原因,我没有试过。后面试过了再来修改,如果有人试过了请告诉我谢谢。

我的思路就很清奇了,这样做不出来我就换一下嘛。先保存,然后通不过校验就再删除也可以。但是要记得处理文件的名称,在互联网的Web开发中有这样一句至理名言:永远不要相信你的用户的上传,这还真不是我编的。可以先使用UUID来生成一个临时文件名,然后保存到本地进行校验。:

  1. temporary_filename = os.path.join("upload", "tmp", str(uuid.uuid1()) + upload_file.filename)
  2. upload_file.save(temporaray_filename)

然后如果通过校验就给文件换个名字,如果没有就删除,是不是很简单吗?

celery的使用

这绝对是我踩过的最大的一个坑,主要还是自己坑。当我们把文件保存下来后就要开始进行读取内容导入数据库,首先是向celery传递参数,我最开始是直接传,然后就报错了。错误的意思是“函数只需要一个参数,但是你给了98个!”我看到这个人都傻了?什么玩意就98个参数,然后怀疑是不是自己的文件路径出错了, 然后发现没错啊,然后实在不行我就通过指明参数来给你传递这总可以了吧,我真是个小机灵鬼呢!然后就是这个错:

我真的是服气,这个说我的文件名有问题,然后发现自己在一开始传递参数的时候就错了,celery的apply_async参数必须是list或者tuple,这个和异步传参一样。所以直接还是上正确的代码吧:

  1. condition = (save_path,)
  2. task = import_area_parameters_task.apply_async(args=condition)

说到底还是自己不熟悉celery造成的,这个参考的是:这个文档