mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-25 07:08:11 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac2b18bf40 | ||
|
|
b89a24448b | ||
|
|
e76216dd26 | ||
|
|
123d778a0c | ||
|
|
829ad209a6 | ||
|
|
b15a65c37e | ||
|
|
0eed798aac | ||
|
|
2caca42b9e | ||
|
|
7658d0af16 | ||
|
|
c14ec50f73 | ||
|
|
6b6ea0da2e | ||
|
|
0b9fe62a10 |
@@ -15,3 +15,9 @@ script:
|
||||
|
||||
after_script:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: bash scripts/trigger-docker.sh
|
||||
on:
|
||||
branch: master
|
||||
|
||||
@@ -344,7 +344,7 @@ For a more complete example including more features, see the <a href="https://fa
|
||||
|
||||
## Performance
|
||||
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=a979de55-980d-4721-a46f-77298b3f3923&hw=ph&test=fortune&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
|
||||
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" target="_blank">Benchmarks</a>.
|
||||
|
||||
|
||||
@@ -377,6 +377,10 @@ All that is what powers FastAPI (through Starlette) and what makes it have such
|
||||
|
||||
When you declare a *path operation function* with normal `def` instead of `async def`, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).
|
||||
|
||||
If you are coming from another async framework that does not work in the way described above and you are used to define trivial compute-only *path operation functions* with plain `def` for a tiny performance gain (about 100 nanoseconds), please note that in **FastAPI** the effect would be quite opposite. In these cases, it's better to use `async def` unless your *path operation functions* use code that performs blocking <abbr title="Input/Output: disk reading or writing, network communications.">IO</abbr>.
|
||||
|
||||
Still, in both situations, chances are that **FastAPI** will <a href="https://fastapi.tiangolo.com/#performance" target="_blank">still be faster</a> than (or at least comparable to) your previous framework.
|
||||
|
||||
### Dependencies
|
||||
|
||||
The same applies for dependencies. If a dependency is a standard `def` function instead of `async def`, it is run in the external threadpool.
|
||||
|
||||
@@ -26,7 +26,7 @@ But you can still change and update all the configurations with environment vari
|
||||
To see all the configurations and options, go to the Docker image page: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
|
||||
|
||||
### Build your Image
|
||||
### Create a `Dockerfile`
|
||||
|
||||
* Go to your project directory.
|
||||
* Create a `Dockerfile` with:
|
||||
@@ -37,6 +37,37 @@ FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
|
||||
COPY ./app /app
|
||||
```
|
||||
|
||||
#### Bigger Applications
|
||||
|
||||
If you followed the section about creating <a href="" target="_blank">Bigger Applications with Multiple Files
|
||||
</a>, your `Dockerfile` might instead look like:
|
||||
|
||||
```Dockerfile
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
|
||||
|
||||
COPY ./app /app/app
|
||||
```
|
||||
|
||||
#### Raspberry Pi and other architectures
|
||||
|
||||
If you are running Docker in a Raspberry Pi (that has an ARM processor) or any other architecture, you can create a `Dockerfile` from scratch, based on a Python base image (that is multi-architecture) and use Uvicorn alone.
|
||||
|
||||
In this case, your `Dockerfile` could look like:
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.7
|
||||
|
||||
RUN pip install fastapi uvicorn
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
COPY ./app /app
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
### Create the **FastAPI** Code
|
||||
|
||||
* Create an `app` directory and enter in it.
|
||||
* Create a `main.py` file with:
|
||||
|
||||
@@ -65,6 +96,8 @@ def read_item(item_id: int, q: str = None):
|
||||
└── Dockerfile
|
||||
```
|
||||
|
||||
### Build the Docker image
|
||||
|
||||
* Go to the project directory (in where your `Dockerfile` is, containing your `app` directory).
|
||||
* Build your FastAPI image:
|
||||
|
||||
@@ -72,6 +105,8 @@ def read_item(item_id: int, q: str = None):
|
||||
docker build -t myimage .
|
||||
```
|
||||
|
||||
### Start the Docker container
|
||||
|
||||
* Run a container based on your image:
|
||||
|
||||
```bash
|
||||
@@ -81,18 +116,6 @@ docker run -d --name mycontainer -p 80:80 myimage
|
||||
Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores).
|
||||
|
||||
|
||||
#### Bigger Applications
|
||||
|
||||
If you followed the section about creating <a href="" target="_blank">Bigger Applications with Multiple Files
|
||||
</a>, your `Dockerfile` might instead look like:
|
||||
|
||||
```Dockerfile
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
|
||||
|
||||
COPY ./app /app/app
|
||||
```
|
||||
|
||||
|
||||
### Check it
|
||||
|
||||
You should be able to check it in your Docker container's URL, for example: <a href="http://192.168.99.100/items/5?q=somequery" target="_blank">http://192.168.99.100/items/5?q=somequery</a> or <a href="http://127.0.0.1/items/5?q=somequery" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (or equivalent, using your Docker host).
|
||||
|
||||
@@ -344,7 +344,7 @@ For a more complete example including more features, see the <a href="https://fa
|
||||
|
||||
## Performance
|
||||
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=a979de55-980d-4721-a46f-77298b3f3923&hw=ph&test=fortune&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
|
||||
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" target="_blank">Benchmarks</a>.
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
## Next
|
||||
|
||||
## 0.7.1
|
||||
|
||||
* Update <a href="https://fastapi.tiangolo.com/async/#path-operation-functions" target="_blank">technical details about `async def` handling</a> with respect to previous frameworks. PR <a href="https://github.com/tiangolo/fastapi/pull/64" target="_blank">#64</a> by <a href="https://github.com/haizaar" target="_blank">@haizaar</a>.
|
||||
|
||||
* Add <a href="https://fastapi.tiangolo.com/deployment/#raspberry-pi-and-other-architectures" target="_blank">deployment documentation for Docker in Raspberry Pi</a> and other architectures.
|
||||
|
||||
* Trigger Docker images build on Travis CI automatically. PR <a href="https://github.com/tiangolo/fastapi/pull/65" target="_blank">#65</a>.
|
||||
|
||||
## 0.7.0
|
||||
|
||||
* Add support for `UploadFile` in `File` parameter annotations.
|
||||
* This includes a file-like interface.
|
||||
* Here's the updated documentation for declaring <a href="https://fastapi.tiangolo.com/tutorial/request-files/#file-parameters-with-uploadfile" target="_blank"> `File` parameters with `UploadFile`</a>.
|
||||
* And here's the updated documentation for using <a href="https://fastapi.tiangolo.com/tutorial/request-forms-and-files/" target="_blank">`Form` parameters mixed with `File` parameters, supporting `bytes` and `UploadFile`</a> at the same time.
|
||||
* PR <a href="https://github.com/tiangolo/fastapi/pull/63" target="_blank">#63</a>.
|
||||
|
||||
## 0.6.4
|
||||
|
||||
* Add <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">technical details about `async def` handling to docs</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/61" target="_blank">#61</a>.
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
from fastapi import FastAPI, File
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/files/")
|
||||
async def create_file(*, file: bytes = File(...)):
|
||||
async def create_file(file: bytes = File(...)):
|
||||
return {"file_size": len(file)}
|
||||
|
||||
|
||||
@app.post("/uploadfile/")
|
||||
async def create_upload_file(file: UploadFile = File(...)):
|
||||
return {"filename": file.filename}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
from fastapi import FastAPI, File, Form
|
||||
from fastapi import FastAPI, File, Form, UploadFile
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/files/")
|
||||
async def create_file(*, file: bytes = File(...), token: str = Form(...)):
|
||||
return {"file_size": len(file), "token": token}
|
||||
async def create_file(
|
||||
file: bytes = File(...), fileb: UploadFile = File(...), token: str = Form(...)
|
||||
):
|
||||
return {
|
||||
"file_size": len(file),
|
||||
"token": token,
|
||||
"fileb_content_type": fileb.content_type,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ You can define files to be uploaded by the client using `File`.
|
||||
|
||||
## Import `File`
|
||||
|
||||
Import `File` from `fastapi`:
|
||||
Import `File` and `UploadFile` from `fastapi`:
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!./src/request_files/tutorial001.py!}
|
||||
@@ -16,14 +16,78 @@ Create file parameters the same way you would for `Body` or `Form`:
|
||||
{!./src/request_files/tutorial001.py!}
|
||||
```
|
||||
|
||||
The files will be uploaded as form data and you will receive the contents as `bytes`.
|
||||
|
||||
!!! info
|
||||
`File` is a class that inherits directly from `Form`.
|
||||
|
||||
!!! info
|
||||
To declare File bodies, you need to use `File`, because otherwise the parameters would be interpreted as query parameters or body (JSON) parameters.
|
||||
|
||||
The files will be uploaded as "form data".
|
||||
|
||||
If you declare the type of your *path operation function* parameter as `bytes`, **FastAPI** will read the file for you and you will receive the contents as `bytes`.
|
||||
|
||||
Have in mind that this means that the whole contents will be stored in memory. This will work well for small files.
|
||||
|
||||
But there are several cases in where you might benefit from using `UploadFile`.
|
||||
|
||||
|
||||
## `File` parameters with `UploadFile`
|
||||
|
||||
Define a `File` parameter with a type of `UploadFile`:
|
||||
|
||||
```Python hl_lines="12"
|
||||
{!./src/request_files/tutorial001.py!}
|
||||
```
|
||||
|
||||
Using `UploadFile` has several advantages over `bytes`:
|
||||
|
||||
* It uses a "spooled" file:
|
||||
* A file stored in memory up to a maximum size limit, and after passing this limit it will be stored in disk.
|
||||
* This means that it will work well for large files like images, videos, large binaries, etc. All without consuming all the memory.
|
||||
* You can get metadata from the uploaded file.
|
||||
* It has a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> `async` interface.
|
||||
* It exposes an actual Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> object that you can pass directly to other libraries that expect a file-like object.
|
||||
|
||||
|
||||
### `UploadFile`
|
||||
|
||||
`UploadFile` has the following attributes:
|
||||
|
||||
* `filename`: A `str` with the original file name that was uploaded (e.g. `myimage.jpg`).
|
||||
* `content_type`: A `str` with the content type (MIME type / media type) (e.g. `image/jpeg`).
|
||||
* `file`: A <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
|
||||
|
||||
|
||||
`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).
|
||||
|
||||
* `write(data)`: Writes `data` (`str` or `bytes`) to the file.
|
||||
* `read(size)`: Reads `size` (`int`) bytes/characters of the file.
|
||||
* `seek(offset)`: Goes to the byte position `offset` (`int`) in the file.
|
||||
* E.g., `await myfile.seek(0)` would go to the start of the file.
|
||||
* This is especially useful if you run `await myfile.read()` once and then need to read the contents again.
|
||||
* `close()`: Closes the file.
|
||||
|
||||
As all these methods are `async` methods, you need to "await" them.
|
||||
|
||||
For example, inside of an `async` *path operation function* you can get the contents with:
|
||||
|
||||
```Python
|
||||
contents = await myfile.read()
|
||||
```
|
||||
|
||||
If you are inside of a normal `def` *path operation function*, you can access the `UploadFile.file` directly, for example:
|
||||
|
||||
```Python
|
||||
contents = myfile.file.read()
|
||||
```
|
||||
|
||||
!!! note "`async` Technical Details"
|
||||
When you use the `async` methods, **FastAPI** runs the file methods in a threadpool and awaits for them.
|
||||
|
||||
|
||||
!!! note "Starlette Technical Details"
|
||||
**FastAPI**'s `UploadFile` inherits directly from **Starlette**'s `UploadFile`, but adds some necessary parts to make it compatible with **Pydantic** and the other parts of FastAPI.
|
||||
|
||||
## "Form Data"?
|
||||
|
||||
The way HTML forms (`<form></form>`) sends the data to the server normally uses a "special" encoding for that data, it's different from JSON.
|
||||
|
||||
@@ -10,12 +10,14 @@ You can define files and form fields at the same time using `File` and `Form`.
|
||||
|
||||
Create file and form parameters the same way you would for `Body` or `Query`:
|
||||
|
||||
```Python hl_lines="7"
|
||||
```Python hl_lines="8"
|
||||
{!./src/request_forms_and_files/tutorial001.py!}
|
||||
```
|
||||
|
||||
The files and form fields will be uploaded as form data and you will receive the files and form fields.
|
||||
|
||||
And you can declare some of the files as `bytes` and some as `UploadFile`.
|
||||
|
||||
!!! warning
|
||||
You can declare multiple `File` and `Form` parameters in a path operation, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `multipart/form-data` instead of `application/json`.
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.6.4"
|
||||
__version__ = "0.7.1"
|
||||
|
||||
from .applications import FastAPI
|
||||
from .routing import APIRouter
|
||||
from .params import Body, Path, Query, Header, Cookie, Form, File, Security, Depends
|
||||
from .exceptions import HTTPException
|
||||
from .datastructures import UploadFile
|
||||
|
||||
15
fastapi/datastructures.py
Normal file
15
fastapi/datastructures.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from typing import Any, Callable, Iterable, Type
|
||||
|
||||
from starlette.datastructures import UploadFile as StarletteUploadFile
|
||||
|
||||
|
||||
class UploadFile(StarletteUploadFile):
|
||||
@classmethod
|
||||
def __get_validators__(cls: Type["UploadFile"]) -> Iterable[Callable]:
|
||||
yield cls.validate
|
||||
|
||||
@classmethod
|
||||
def validate(cls: Type["UploadFile"], v: Any) -> Any:
|
||||
if not isinstance(v, StarletteUploadFile):
|
||||
raise ValueError(f"Expected UploadFile, received: {type(v)}")
|
||||
return v
|
||||
@@ -17,6 +17,7 @@ from pydantic.fields import Field, Required, Shape
|
||||
from pydantic.schema import get_annotation_from_schema
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.datastructures import UploadFile
|
||||
from starlette.requests import Headers, QueryParams, Request
|
||||
|
||||
param_supported_types = (
|
||||
@@ -323,6 +324,12 @@ async def request_body_to_args(
|
||||
else:
|
||||
values[field.name] = deepcopy(field.default)
|
||||
continue
|
||||
if (
|
||||
isinstance(field.schema, params.File)
|
||||
and lenient_issubclass(field.type_, bytes)
|
||||
and isinstance(value, UploadFile)
|
||||
):
|
||||
value = await value.read()
|
||||
v_, errors_ = field.validate(value, values, loc=("body", field.alias))
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
@@ -333,6 +340,21 @@ async def request_body_to_args(
|
||||
return values, errors
|
||||
|
||||
|
||||
def get_schema_compatible_field(*, field: Field) -> Field:
|
||||
if lenient_issubclass(field.type_, UploadFile):
|
||||
return Field(
|
||||
name=field.name,
|
||||
type_=bytes,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
schema=field.schema,
|
||||
)
|
||||
return field
|
||||
|
||||
|
||||
def get_body_field(*, dependant: Dependant, name: str) -> Field:
|
||||
flat_dependant = get_flat_dependant(dependant)
|
||||
if not flat_dependant.body_params:
|
||||
@@ -340,11 +362,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Field:
|
||||
first_param = flat_dependant.body_params[0]
|
||||
embed = getattr(first_param.schema, "embed", None)
|
||||
if len(flat_dependant.body_params) == 1 and not embed:
|
||||
return first_param
|
||||
return get_schema_compatible_field(field=first_param)
|
||||
model_name = "Body_" + name
|
||||
BodyModel = create_model(model_name)
|
||||
for f in flat_dependant.body_params:
|
||||
BodyModel.__fields__[f.name] = f
|
||||
BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
|
||||
required = any(True for f in flat_dependant.body_params if f.required)
|
||||
if any(isinstance(f.schema, params.File) for f in flat_dependant.body_params):
|
||||
BodySchema: Type[params.Body] = params.File
|
||||
|
||||
@@ -15,7 +15,6 @@ from pydantic.utils import lenient_issubclass
|
||||
from starlette import routing
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.formparsers import UploadFile
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.routing import compile_path, get_name, request_response
|
||||
@@ -57,10 +56,7 @@ def get_app(
|
||||
raw_body = await request.form()
|
||||
form_fields = {}
|
||||
for field, value in raw_body.items():
|
||||
if isinstance(value, UploadFile):
|
||||
form_fields[field] = await value.read()
|
||||
else:
|
||||
form_fields[field] = value
|
||||
form_fields[field] = value
|
||||
if form_fields:
|
||||
body = form_fields
|
||||
else:
|
||||
|
||||
17
scripts/trigger-docker.sh
Executable file
17
scripts/trigger-docker.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
body='{
|
||||
"request": {
|
||||
"branch":"master"
|
||||
}}'
|
||||
|
||||
curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Travis-API-Version: 3" \
|
||||
-H "Authorization: token $TRAVIS_TOKEN" \
|
||||
-d "$body" \
|
||||
https://api.travis-ci.org/repo/tiangolo%2Fuvicorn-gunicorn-fastapi-docker/requests
|
||||
7
tests/test_datastructures.py
Normal file
7
tests/test_datastructures.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import pytest
|
||||
from fastapi import UploadFile
|
||||
|
||||
|
||||
def test_upload_file_invalid():
|
||||
with pytest.raises(ValueError):
|
||||
UploadFile.validate("not a Starlette UploadFile")
|
||||
@@ -39,7 +39,39 @@ openapi_schema = {
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"/uploadfile/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Upload File Post",
|
||||
"operationId": "create_upload_file_uploadfile__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_create_upload_file"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
@@ -51,6 +83,14 @@ openapi_schema = {
|
||||
"file": {"title": "File", "type": "string", "format": "binary"}
|
||||
},
|
||||
},
|
||||
"Body_create_upload_file": {
|
||||
"title": "Body_create_upload_file",
|
||||
"required": ["file"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {"title": "File", "type": "string", "format": "binary"}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
@@ -131,3 +171,14 @@ def test_post_large_file(tmpdir):
|
||||
response = client.post("/files/", files={"file": open(path, "rb")})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"file_size": default_pydantic_max_size + 1}
|
||||
|
||||
|
||||
def test_post_upload_file(tmpdir):
|
||||
path = os.path.join(tmpdir, "test.txt")
|
||||
with open(path, "wb") as file:
|
||||
file.write(b"<file content>")
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.post("/uploadfile/", files={"file": open(path, "rb")})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"filename": "test.txt"}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
@@ -45,10 +46,11 @@ openapi_schema = {
|
||||
"schemas": {
|
||||
"Body_create_file": {
|
||||
"title": "Body_create_file",
|
||||
"required": ["file", "token"],
|
||||
"required": ["file", "fileb", "token"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {"title": "File", "type": "string", "format": "binary"},
|
||||
"fileb": {"title": "Fileb", "type": "string", "format": "binary"},
|
||||
"token": {"title": "Token", "type": "string"},
|
||||
},
|
||||
},
|
||||
@@ -94,20 +96,32 @@ file_required = {
|
||||
"loc": ["body", "file"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
token_required = {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
# {'detail': [, {'loc': ['body', 'token'], 'msg': 'field required', 'type': 'value_error.missing'}]}
|
||||
|
||||
file_and_token_required = {
|
||||
"detail": [
|
||||
{
|
||||
@@ -115,6 +129,11 @@ file_and_token_required = {
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
@@ -153,14 +172,24 @@ def test_post_file_no_token(tmpdir):
|
||||
assert response.json() == token_required
|
||||
|
||||
|
||||
def test_post_file_and_token(tmpdir):
|
||||
path = os.path.join(tmpdir, "test.txt")
|
||||
with open(path, "wb") as file:
|
||||
file.write(b"<file content>")
|
||||
def test_post_files_and_token(tmpdir):
|
||||
patha = Path(tmpdir) / "test.txt"
|
||||
pathb = Path(tmpdir) / "testb.txt"
|
||||
patha.write_text("<file content>")
|
||||
pathb.write_text("<file b content>")
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.post(
|
||||
"/files/", data={"token": "foo"}, files={"file": open(path, "rb")}
|
||||
"/files/",
|
||||
data={"token": "foo"},
|
||||
files={
|
||||
"file": patha.open("rb"),
|
||||
"fileb": ("testb.txt", pathb.open("rb"), "text/plain"),
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"file_size": 14, "token": "foo"}
|
||||
assert response.json() == {
|
||||
"file_size": 14,
|
||||
"token": "foo",
|
||||
"fileb_content_type": "text/plain",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user