第15节,FastAPI 上传文件

1. File对象上传文件

FastAPI 上传文件需要用到File对象,在定义路径操作时声明File类型的参数,可以声明成bytes 或 UploadFile,下面的示例演示声明成bytes类型

from fastapi import FastAPI, File
from starlette.templating import Jinja2Templates
from starlette.requests import Request

app = FastAPI()
template = Jinja2Templates(directory='templates')       # 创建模板对象


@app.get('/upload')
def upload_html(request: Request):
    return template.TemplateResponse('upload.html', {'request': request})


@app.post('/uploadfile')
def upload_file(file: bytes = File(...)):
    with open('./data/up.pdf', 'wb')as f:
        f.write(file)
    return 'ok'

upload.html文件的内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
<form action="uploadfile" method=post enctype=multipart/form-data>
      <input type="file" name="file">
      <input type="submit" value="Upload">
</form>
</body>
</html>

上传一个pdf文件,文件被保存到data目录下,由于file的类型标注是bytes类型,因此,服务端拿不到上传文件的名称,只能得到文件的二进制内容,因此可以说这种上传文件的方式不大可能有太多的应用场景,我们了解一下就好。

2. UploadFile

将文件对象的类型标注设置为UploadFile

import os
from fastapi import FastAPI, File, UploadFile


@app.post('/uploadfile')
async def upload_file(file: UploadFile = File(...)):
    with open(os.path.join('./data', file.filename), 'wb')as f:
        data = await file.read()
        f.write(data)

    return 'ok'

UploadFile 支持的下面几个方法都是异步的

  1. write(data):把 data (str 或 bytes)写入文件
  2. ead(size):按指定数量的字节或字符(size (int))读取文件内容
  3. seek(offset):移动至文件 offset (int)字节处的位置
  4. close():关闭文件

相比于将file对象声明成bytes类型,声明成UploadFile有以下几个好处:

  1. 可以上传很大的文件,如果成名成bytes,所有数据会在内存中,但声明成UploadFile, 如果文件太大了会存储在硬盘上
  2. 可以获得文件的元信息,比如文件的名称,类型

3. 与Form表单一起上传文件

前面演示的只是上传文件,还存在一种情况,在提交表单的同时,也要上传文件,修改upload.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
<form action="uploadfile" method=post enctype=multipart/form-data>
      <input type="text" name="name">
      <input type="file" name="file">
      <input type="submit" value="Upload">
</form>
</body>
</html>

增加一个input控件,修改app.py文件

import os
from fastapi import FastAPI, File, UploadFile, Form
from starlette.templating import Jinja2Templates
from starlette.requests import Request

app = FastAPI()
template = Jinja2Templates(directory='templates')       # 创建模板对象


@app.get('/upload')
def upload_html(request: Request):
    return template.TemplateResponse('upload.html', {'request': request})


@app.post('/uploadfile')
async def upload_file(file: UploadFile = File(...), name: str = Form(...)):
    print(name)
    with open(os.path.join('./data', file.filename), 'wb')as f:
        data = await file.read()
        f.write(data)

    return 'ok'

在upload_file 函数里增加name参数,接收form表单里的数据,通过print语句的输出可以看到form表单里的input标签传上来的数据被正确读取了,文件也得到保存。

4. 上传多个文件

有两种方式上传多个文件,一种是以列表的方法,另一种是精确匹配的方式,先来看列表的方式,修改uplaod.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
<form action="uploadfile" method=post enctype=multipart/form-data>
      <input type="file" name="files" multiple>
      <input type="file" name="files" multiple>
      <input type="submit" value="Upload">
</form>
</body>
</html>

两个file控件的name是相同的,这样在服务端就要使用列表的形式来接收上传的文件

from typing import List


@app.post('/uploadfile')
async def upload_file(files: List[UploadFile] = File(...)):
    for file in files:
        with open(os.path.join('./data', file.filename), 'wb')as f:
            data = await file.read()
            f.write(data)

    return 'ok'

前端name是files的标签有多个,服务端在定义操作路径时,也要对应的定义一个files的变量,同时类型标注为List,如果所上传的多个文件各有自己的用途,不适合统一处理,那么就可以分开来处理,修改upload.html文件

@app.post('/uploadfile')
async def upload_file(pdf_file: UploadFile = File(...), word_file: UploadFile = File(...)):
    print(pdf_file.filename)
    print(word_file.filename)

    return 'ok'

这样也实现了多个文件同时上传,可以通过前后端的变量来唯一的确认文件进行处理。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案