mirror of
https://github.com/fastapi/fastapi.git
synced 2026-05-29 10:46:02 -04:00
Compare commits
5 Commits
fix-respon
...
fix-multip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5dbca92d5 | ||
|
|
be926b473c | ||
|
|
6aa2f98504 | ||
|
|
3405306469 | ||
|
|
506d278db7 |
@@ -58,14 +58,25 @@ class GenerateJsonSchema(_GenerateJsonSchema):
|
||||
# TODO: remove when this is merged (or equivalent): https://github.com/pydantic/pydantic/pull/12841
|
||||
# and dropping support for any version of Pydantic before that one (so, in a very long time)
|
||||
def bytes_schema(self, schema: CoreSchema) -> JsonSchemaValue:
|
||||
json_schema = {"type": "string", "contentMediaType": "application/octet-stream"}
|
||||
bytes_mode = (
|
||||
self._config.ser_json_bytes
|
||||
if self.mode == "serialization"
|
||||
else self._config.val_json_bytes
|
||||
)
|
||||
if bytes_mode == "base64":
|
||||
json_schema["contentEncoding"] = "base64"
|
||||
is_file_upload = schema.get("metadata", {}).get("fastapi_file_upload", False)
|
||||
if is_file_upload:
|
||||
json_schema: JsonSchemaValue = {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
}
|
||||
else:
|
||||
json_schema = {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
}
|
||||
bytes_mode = (
|
||||
self._config.ser_json_bytes
|
||||
if self.mode == "serialization"
|
||||
else self._config.val_json_bytes
|
||||
)
|
||||
if bytes_mode == "base64":
|
||||
json_schema["contentEncoding"] = "base64"
|
||||
self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes)
|
||||
return json_schema
|
||||
|
||||
|
||||
@@ -139,7 +139,11 @@ class UploadFile(StarletteUploadFile):
|
||||
def __get_pydantic_json_schema__(
|
||||
cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
|
||||
) -> dict[str, Any]:
|
||||
return {"type": "string", "contentMediaType": "application/octet-stream"}
|
||||
return {
|
||||
"type": "string",
|
||||
"format": "binary", # For compatibility with OAS 3.0
|
||||
"contentMediaType": "application/octet-stream",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
|
||||
@@ -2,7 +2,7 @@ import warnings
|
||||
from collections.abc import Callable, Sequence
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Annotated, Any, Literal
|
||||
from typing import Annotated, Any, Literal, cast
|
||||
|
||||
from fastapi.exceptions import FastAPIDeprecationWarning
|
||||
from fastapi.openapi.models import Example
|
||||
@@ -660,6 +660,32 @@ class Form(Body): # type: ignore[misc]
|
||||
)
|
||||
|
||||
|
||||
class _FileUploadMarker:
|
||||
"Pydantic metadata marker to tag bytes CoreSchemas as file uploads."
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
cls, source: type[Any], handler: Any
|
||||
) -> dict[str, Any]:
|
||||
schema = cast(dict[str, Any], handler(source))
|
||||
|
||||
# Find the inner type schema (if nullable or list)
|
||||
inner_type_schema = schema
|
||||
if inner_type_schema.get("type") != "bytes":
|
||||
if inner_type_schema.get("type") == "list":
|
||||
inner_type_schema = inner_type_schema["items_schema"]
|
||||
elif "schema" in inner_type_schema:
|
||||
inner_type_schema = inner_type_schema["schema"]
|
||||
if inner_type_schema.get("type") == "list":
|
||||
inner_type_schema = inner_type_schema["items_schema"]
|
||||
|
||||
# If the inner type is bytes, add the file upload marker metadata
|
||||
if inner_type_schema.get("type") == "bytes":
|
||||
metadata: dict[str, Any] = inner_type_schema.setdefault("metadata", {})
|
||||
metadata["fastapi_file_upload"] = True
|
||||
return schema
|
||||
|
||||
|
||||
class File(Form): # type: ignore[misc]
|
||||
def __init__(
|
||||
self,
|
||||
@@ -740,6 +766,7 @@ class File(Form): # type: ignore[misc]
|
||||
json_schema_extra=json_schema_extra,
|
||||
**extra,
|
||||
)
|
||||
self.metadata.append(_FileUploadMarker())
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -844,6 +844,28 @@ class APIRoute(routing.Route):
|
||||
self.path = path
|
||||
self.endpoint = endpoint
|
||||
self.stream_item_type: Any | None = None
|
||||
if isinstance(response_model, DefaultPlaceholder):
|
||||
return_annotation = get_typed_return_annotation(endpoint)
|
||||
if lenient_issubclass(return_annotation, Response):
|
||||
response_model = None
|
||||
else:
|
||||
stream_item = get_stream_item_type(return_annotation)
|
||||
if stream_item is not None:
|
||||
# Extract item type for JSONL or SSE streaming when
|
||||
# response_class is DefaultPlaceholder (JSONL) or
|
||||
# EventSourceResponse (SSE).
|
||||
# ServerSentEvent is excluded: it's a transport
|
||||
# wrapper, not a data model, so it shouldn't feed
|
||||
# into validation or OpenAPI schema generation.
|
||||
if (
|
||||
isinstance(response_class, DefaultPlaceholder)
|
||||
or lenient_issubclass(response_class, EventSourceResponse)
|
||||
) and not lenient_issubclass(stream_item, ServerSentEvent):
|
||||
self.stream_item_type = stream_item
|
||||
response_model = None
|
||||
else:
|
||||
response_model = return_annotation
|
||||
self.response_model = response_model
|
||||
self.summary = summary
|
||||
self.response_description = response_description
|
||||
self.deprecated = deprecated
|
||||
@@ -879,6 +901,27 @@ class APIRoute(routing.Route):
|
||||
if isinstance(status_code, IntEnum):
|
||||
status_code = int(status_code)
|
||||
self.status_code = status_code
|
||||
if self.response_model:
|
||||
assert is_body_allowed_for_status_code(status_code), (
|
||||
f"Status code {status_code} must not have a response body"
|
||||
)
|
||||
response_name = "Response_" + self.unique_id
|
||||
self.response_field = create_model_field(
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
mode="serialization",
|
||||
)
|
||||
else:
|
||||
self.response_field = None # type: ignore[assignment]
|
||||
if self.stream_item_type:
|
||||
stream_item_name = "StreamItem_" + self.unique_id
|
||||
self.stream_item_field: ModelField | None = create_model_field(
|
||||
name=stream_item_name,
|
||||
type_=self.stream_item_type,
|
||||
mode="serialization",
|
||||
)
|
||||
else:
|
||||
self.stream_item_field = None
|
||||
self.dependencies = list(dependencies or [])
|
||||
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
||||
# if a "form feed" character (page break) is found in the description text,
|
||||
@@ -930,50 +973,6 @@ class APIRoute(routing.Route):
|
||||
self.is_json_stream = is_generator and isinstance(
|
||||
response_class, DefaultPlaceholder
|
||||
)
|
||||
if isinstance(response_model, DefaultPlaceholder):
|
||||
return_annotation = get_typed_return_annotation(endpoint)
|
||||
if lenient_issubclass(return_annotation, Response):
|
||||
response_model = None
|
||||
else:
|
||||
stream_item = get_stream_item_type(return_annotation)
|
||||
if stream_item is not None and is_generator:
|
||||
# Extract item type for JSONL or SSE streaming for
|
||||
# generator endpoints when response_class is
|
||||
# DefaultPlaceholder (JSONL) or EventSourceResponse
|
||||
# (SSE).
|
||||
# ServerSentEvent is excluded: it's a transport
|
||||
# wrapper, not a data model, so it shouldn't feed
|
||||
# into validation or OpenAPI schema generation.
|
||||
if (
|
||||
isinstance(response_class, DefaultPlaceholder)
|
||||
or lenient_issubclass(response_class, EventSourceResponse)
|
||||
) and not lenient_issubclass(stream_item, ServerSentEvent):
|
||||
self.stream_item_type = stream_item
|
||||
response_model = None
|
||||
else:
|
||||
response_model = return_annotation
|
||||
self.response_model = response_model
|
||||
if self.response_model:
|
||||
assert is_body_allowed_for_status_code(status_code), (
|
||||
f"Status code {status_code} must not have a response body"
|
||||
)
|
||||
response_name = "Response_" + self.unique_id
|
||||
self.response_field = create_model_field(
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
mode="serialization",
|
||||
)
|
||||
else:
|
||||
self.response_field = None # type: ignore[assignment]
|
||||
if self.stream_item_type:
|
||||
stream_item_name = "StreamItem_" + self.unique_id
|
||||
self.stream_item_field: ModelField | None = create_model_field(
|
||||
name=stream_item_name,
|
||||
type_=self.stream_item_type,
|
||||
mode="serialization",
|
||||
)
|
||||
else:
|
||||
self.stream_item_field = None
|
||||
self.app = request_response(self.get_route_handler())
|
||||
|
||||
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Annotated
|
||||
import pytest
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from fastapi.testclient import TestClient
|
||||
from inline_snapshot import Is, snapshot
|
||||
|
||||
from .utils import get_body_model_name
|
||||
|
||||
@@ -33,21 +34,24 @@ def test_list_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
"title": "P",
|
||||
},
|
||||
"title": "P",
|
||||
},
|
||||
},
|
||||
"required": ["p"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -114,21 +118,24 @@ def test_list_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
"title": "P Alias",
|
||||
},
|
||||
"title": "P Alias",
|
||||
},
|
||||
},
|
||||
"required": ["p_alias"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p_alias"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -223,21 +230,24 @@ def test_list_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
"title": "P Val Alias",
|
||||
},
|
||||
"title": "P Val Alias",
|
||||
},
|
||||
},
|
||||
"required": ["p_val_alias"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p_val_alias"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -343,21 +353,24 @@ def test_list_alias_and_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
"title": "P Val Alias",
|
||||
},
|
||||
"title": "P Val Alias",
|
||||
},
|
||||
},
|
||||
"required": ["p_val_alias"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p_val_alias"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Annotated
|
||||
import pytest
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from fastapi.testclient import TestClient
|
||||
from inline_snapshot import Is, snapshot
|
||||
|
||||
from .utils import get_body_model_name
|
||||
|
||||
@@ -33,19 +34,25 @@ def test_optional_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p": {
|
||||
"anyOf": [
|
||||
{"type": "string", "contentMediaType": "application/octet-stream"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -105,19 +112,25 @@ def test_optional_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"anyOf": [
|
||||
{"type": "string", "contentMediaType": "application/octet-stream"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Alias",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Alias",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -196,19 +209,25 @@ def test_optional_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{"type": "string", "contentMediaType": "application/octet-stream"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -292,19 +311,25 @@ def test_optional_alias_and_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{"type": "string", "contentMediaType": "application/octet-stream"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Annotated
|
||||
import pytest
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from fastapi.testclient import TestClient
|
||||
from inline_snapshot import Is, snapshot
|
||||
|
||||
from .utils import get_body_model_name
|
||||
|
||||
@@ -35,25 +36,28 @@ def test_optional_list_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -113,25 +117,28 @@ def test_optional_list_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Alias",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Alias",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -205,25 +212,28 @@ def test_optional_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -304,25 +314,28 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "P Val Alias",
|
||||
}
|
||||
},
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Annotated
|
||||
import pytest
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from fastapi.testclient import TestClient
|
||||
from inline_snapshot import Is, snapshot
|
||||
|
||||
from .utils import get_body_model_name
|
||||
|
||||
@@ -33,18 +34,21 @@ def test_required_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p": {
|
||||
"title": "P",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p": {
|
||||
"title": "P",
|
||||
"format": "binary",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["p"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -111,18 +115,21 @@ def test_required_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"title": "P Alias",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_alias": {
|
||||
"title": "P Alias",
|
||||
"format": "binary",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["p_alias"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p_alias"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -219,18 +226,21 @@ def test_required_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"title": "P Val Alias",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"title": "P Val Alias",
|
||||
"format": "binary",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["p_val_alias"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p_val_alias"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -332,18 +342,21 @@ def test_required_alias_and_validation_alias_schema(path: str):
|
||||
openapi = app.openapi()
|
||||
body_model_name = get_body_model_name(openapi, path)
|
||||
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == {
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"title": "P Val Alias",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
|
||||
{
|
||||
"properties": {
|
||||
"p_val_alias": {
|
||||
"title": "P Val Alias",
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["p_val_alias"],
|
||||
"title": body_model_name,
|
||||
"type": "object",
|
||||
}
|
||||
"required": ["p_val_alias"],
|
||||
"title": Is(body_model_name),
|
||||
"type": "object",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from collections.abc import Iterable
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
@@ -67,21 +65,6 @@ def get_exclude_unset_none() -> ModelDefaults:
|
||||
return ModelDefaults(x=None, y="y")
|
||||
|
||||
|
||||
@app.get("/iterable_exclude_unset", response_model_exclude_unset=True)
|
||||
def get_iterable_exclude_unset() -> Iterable[ModelDefaults]:
|
||||
return [ModelDefaults(x=None, y="y")]
|
||||
|
||||
|
||||
@app.get("/iterable_exclude_defaults", response_model_exclude_defaults=True)
|
||||
def get_iterable_exclude_defaults() -> Iterable[ModelDefaults]:
|
||||
return [ModelDefaults(x=None, y="y")]
|
||||
|
||||
|
||||
@app.get("/iterable_exclude_none", response_model_exclude_none=True)
|
||||
def get_iterable_exclude_none() -> Iterable[ModelDefaults]:
|
||||
return [ModelDefaults(x=None, y="y")]
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@@ -108,18 +91,3 @@ def test_return_exclude_none():
|
||||
def test_return_exclude_unset_none():
|
||||
response = client.get("/exclude_unset_none")
|
||||
assert response.json() == {"y": "y"}
|
||||
|
||||
|
||||
def test_return_iterable_exclude_unset():
|
||||
response = client.get("/iterable_exclude_unset")
|
||||
assert response.json() == [{"x": None, "y": "y"}]
|
||||
|
||||
|
||||
def test_return_iterable_exclude_defaults():
|
||||
response = client.get("/iterable_exclude_defaults")
|
||||
assert response.json() == [{}]
|
||||
|
||||
|
||||
def test_return_iterable_exclude_none():
|
||||
response = client.get("/iterable_exclude_none")
|
||||
assert response.json() == [{"y": "y", "z": "z"}]
|
||||
|
||||
@@ -162,6 +162,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"properties": {
|
||||
"file": {
|
||||
"title": "File",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"type": "string",
|
||||
}
|
||||
@@ -174,6 +175,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"properties": {
|
||||
"file": {
|
||||
"title": "File",
|
||||
"format": "binary",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
{"type": "null"},
|
||||
@@ -152,6 +153,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
{"type": "null"},
|
||||
|
||||
@@ -121,6 +121,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"properties": {
|
||||
"file": {
|
||||
"title": "File",
|
||||
"format": "binary",
|
||||
"type": "string",
|
||||
"description": "A file read as bytes",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
@@ -134,6 +135,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"properties": {
|
||||
"file": {
|
||||
"title": "File",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"type": "string",
|
||||
"description": "A file read as UploadFile",
|
||||
|
||||
@@ -197,6 +197,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
}
|
||||
@@ -212,6 +213,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
"description": "Multiple files as bytes",
|
||||
@@ -183,6 +184,7 @@ def test_openapi_schema(client: TestClient):
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
"description": "Multiple files as UploadFile",
|
||||
|
||||
@@ -197,11 +197,13 @@ def test_openapi_schema(client: TestClient):
|
||||
"properties": {
|
||||
"file": {
|
||||
"title": "File",
|
||||
"format": "binary",
|
||||
"type": "string",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
},
|
||||
"fileb": {
|
||||
"title": "Fileb",
|
||||
"format": "binary",
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"type": "string",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user