How to Show Additional Description for Enum Field in OpenAPI Document With Litestar

# Introduction

# Litestar Framework

Litestar is a powerful, flexible, highly performant, and opinionated ASGI framework.

The Litestar framework supports Plugins, ships with dependency injection, security primitives, OpenAPI schema generation, MessagePack, middlewares, a great CLI experience, and much more.

# Scalar

Scalar is the modern open-source developer experience platform for your APIs.

It support add custom specification extensions (starting with a x-) through its plugin API.

The official custom extensions supported by Scalar are:

  • x-scalar-environments
  • x-scalar-active-environment
  • x-codeSamples
  • x-displayName
  • x-tagGroups
  • x-scalar-ignore
  • x-additionalPropertiesName
  • x-scalar-stability
  • x-badges
  • x-enum-descriptions
  • x-enum-varnames
  • x-scalar-sdk-installation

# Minimal Example

# app.py
import enum

import pydantic
from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig
from litestar.openapi.plugins import ScalarRenderPlugin


class Weather(enum.IntEnum):
    SUNNY = 1
    RAINY = 2
    CLOUDY = 3


class WeatherPredict(pydantic.BaseModel):
    city: str
    weather: Weather = pydantic.Field(description="The weather condition")


@get("/city/{name:str}")
async def get_weather(name: str) -> WeatherPredict:
    return WeatherPredict(city=name, weather=Weather.SUNNY)

plugins = []

app = Litestar(
    route_handlers=[get_weather],
    openapi_config=OpenAPIConfig(
        title="Litestar Example",
        description="Example of Litestar with Scalar OpenAPI docs",
        version="0.0.1",
        render_plugins=[ScalarRenderPlugin()],
    ),
    plugins=plugins,
)

# Open Scalar OpenAPI Document

$ litestar run

It will display a great CLI interface like below.

Litestar CLI Interface

Visit http://127.0.0.1:8000/schema/scalar, you will see the enum field rendered like below.

OpenAPI Enum Field Default View

# Support using The x-enum-descriptions Extension

Below is the screenshot of the OpenAPI document rendered by ScalarRenderPlugin with enum field showing additional description using the x-enum-descriptions extension.

OpenAPI Enum Field With x-enum-descriptions View

The highlighted lines is all you need to add to your code. The EnumSchemaPlugin modifies the enum field schema to add x-enumDescriptions property. It is then used by the x-enum-descriptions extension.

# app.py
import dataclasses
import enum
from typing import Optional

import pydantic
from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig
from litestar.openapi.plugins import ScalarRenderPlugin
from litestar.openapi.spec import Reference, Schema
from litestar.plugins import OpenAPISchemaPlugin


class Weather(enum.IntEnum):
    SUNNY = 1
    RAINY = 2
    CLOUDY = 3

    @classmethod
    def get_openapi_descriptions(cls):
        mapper = {
            cls.SUNNY: "Sunny weather",
            cls.RAINY: "Rainy weather",
            cls.CLOUDY: "Cloudy weather",
        }
        return [mapper[i] for i in cls]


class WeatherPredict(pydantic.BaseModel):
    city: str
    weather: Weather = pydantic.Field(description="The weather condition")


@dataclasses.dataclass
class CustomSchema(Schema):
    x_enum_descriptions: Optional[list[str]] = None

    @property
    def _exclude_fields(self) -> set[str]:
        return {"x_enum_descriptions"}

    @classmethod
    def copy_from(cls, schema: Schema):
        return CustomSchema(**schema.__dict__)

    def to_schema(self) -> dict:
        schema = super().to_schema()
        schema |= {"x-enumDescriptions": self.x_enum_descriptions}
        return schema


class EnumSchemaPlugin(OpenAPISchemaPlugin):
    @staticmethod
    def is_plugin_supported_type(value):
        return False

    def is_plugin_supported_field(self, field_definition) -> bool:
        return type(field_definition.annotation) is enum.EnumType

    def to_openapi_schema(self, field_definition, schema_creator):
        schema = schema_creator.for_enum_field(field_definition)
        if isinstance(schema, Reference):
            if getattr(field_definition.annotation, "get_openapi_descriptions", None):
                registered_schema = schema_creator.schema_registry.from_reference(schema)
                new_schema = CustomSchema.copy_from(registered_schema.schema)
                new_schema.x_enum_descriptions = field_definition.annotation.get_openapi_descriptions()
                registered_schema.schema = new_schema

            return schema

        raise RuntimeError("Never execute this line, it is unreachable code.")



@get("/city/{name:str}")
async def get_weather(name: str) -> WeatherPredict:
    return WeatherPredict(city=name, weather=Weather.SUNNY)

plugins = [EnumSchemaPlugin()]

app = Litestar(
    route_handlers=[get_weather],
    openapi_config=OpenAPIConfig(
        title="Litestar Example",
        description="Example of Litestar with Scalar OpenAPI docs",
        version="0.0.1",
        render_plugins=[ScalarRenderPlugin()],
    ),
    plugins=plugins,
)