🐛 Fix `get_model_fields` to support Annotated types by ilovemesomeramen · Pull Request #14805 · fastapi/fastapi

@YuriiMotov Yes exactly, i know the test looks a little wonky but it makes sense in my opinion.

I think it is logical that the plain annotated type does not create a new component but the root model creates one, as defining a RootModel implicates a new "Model" (component) while the plain Type Annotation does not (in my opinion).

So this should be fine.

Regarding the additional test, would something like this work for you?

Details
from typing import Annotated, Literal, Union

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel, Field, RootModel


class Base(BaseModel):
    type: Literal["BASE"] = "BASE"
    value: str


class MyModelRoot(RootModel[Annotated[Union[Base], Field(discriminator="type")]]):
    pass


class MyModelBase(RootModel[Annotated[Base, Field(discriminator="type")]]):
    pass


app = FastAPI()


@app.get("/root")
def get_root() -> MyModelRoot:
    return MyModelRoot.model_validate(Base(value="test"))


@app.get("/base")
def get_base() -> MyModelBase:
    return MyModelBase.model_validate(Base(value="test"))


client = TestClient(app)


@pytest.mark.parametrize("path", ["/root", "/base"])
def test_get(path: str):
    response = client.get(path)
    assert response.json() == {"value": "test", "type": "BASE"}


def test_openapi_schema():
    response = client.get("openapi.json")
    assert response.json() == snapshot(
        {
            "openapi": "3.1.0",
            "info": {"title": "FastAPI", "version": "0.1.0"},
            "paths": {
                "/root": {
                    "get": {
                        "summary": "Get Root",
                        "operationId": "get_root_root_get",
                        "responses": {
                            "200": {
                                "description": "Successful Response",
                                "content": {
                                    "application/json": {
                                        "schema": {
                                            "$ref": "#/components/schemas/MyModelRoot"
                                        }
                                    }
                                },
                            }
                        },
                    }
                },
                "/base": {
                    "get": {
                        "summary": "Get Base",
                        "operationId": "get_base_base_get",
                        "responses": {
                            "200": {
                                "description": "Successful Response",
                                "content": {
                                    "application/json": {
                                        "schema": {
                                            "$ref": "#/components/schemas/MyModelBase"
                                        }
                                    }
                                },
                            }
                        },
                    }
                },
            },
            "components": {
                "schemas": {
                    "Base": {
                        "properties": {
                            "type": {
                                "type": "string",
                                "const": "BASE",
                                "title": "Type",
                                "default": "BASE",
                            },
                            "value": {"type": "string", "title": "Value"},
                        },
                        "type": "object",
                        "required": ["value"],
                        "title": "Base",
                    },
                    "MyModelBase": {
                        "oneOf": [{"$ref": "#/components/schemas/Base"}],
                        "title": "MyModelBase",
                        "discriminator": {
                            "propertyName": "type",
                            "mapping": {"BASE": "#/components/schemas/Base"},
                        },
                    },
                    "MyModelRoot": {
                        "oneOf": [{"$ref": "#/components/schemas/Base"}],
                        "title": "MyModelRoot",
                        "discriminator": {
                            "propertyName": "type",
                            "mapping": {"BASE": "#/components/schemas/Base"},
                        },
                    },
                }
            },
        }