Literals, Enums, and Callables
This guide explains how to work with fixed sets of values and functional validation using LiteralSchema, EnumSchema, and CallableSchema in the pydantic-core framework. These schemas allow you to define strict constraints on allowed values and ensure that inputs are executable objects.
Literal Validation
The LiteralSchema is used to validate that an input matches one of a specific set of allowed values. It is defined in pydantic_core/core_schema.py and is typically created using the literal_schema helper function.
Core Implementation
A LiteralSchema requires an expected list containing the allowed values. During validation, pydantic-core checks if the input is exactly equal to one of these values.
from pydantic_core import SchemaValidator, core_schema
# Define a schema for specific string values
schema = core_schema.literal_schema(['hello', 'world'])
v = SchemaValidator(schema)
assert v.validate_python('hello') == 'hello'
# v.validate_python('foo') # Raises ValidationError
Behavior with Enums
When using Python Enum members within a LiteralSchema, the behavior depends on whether the Enum is a "plain" Enum or a mixin like IntEnum or StrEnum:
- Plain Enums: Validation requires the actual Enum member. Passing the raw value (e.g., the string
'foo_value') will fail unless the input is the Enum member itself. - Mixin Enums (StrEnum/IntEnum): Validation allows both the Enum member and the raw value, as these Enums compare equal to their underlying types.
from enum import Enum
from pydantic_core import SchemaValidator, core_schema
class Color(Enum):
RED = 'red'
# Plain Enum requires the member
schema = core_schema.literal_schema([Color.RED])
v = SchemaValidator(schema)
assert v.validate_python(Color.RED) == Color.RED
# v.validate_python('red') # Raises ValidationError: Input should be <Color.RED: 'red'>
Enum Validation
The EnumSchema provides a more robust way to validate Python Enum classes. Unlike LiteralSchema, which just checks for equality against a list, EnumSchema is designed to return instances of a specific Enum class.
Schema Structure
The EnumSchema requires the Enum class (cls) and its members. It also supports a sub_type to enable coercion from raw values.
from enum import Enum
from pydantic_core import SchemaValidator, core_schema
class Status(int, Enum):
ACTIVE = 1
INACTIVE = 2
# Create an Enum schema with integer coercion
schema = core_schema.enum_schema(
Status,
list(Status.__members__.values()),
sub_type='int'
)
v = SchemaValidator(schema)
# Coercion from various types
assert v.validate_python(1) is Status.ACTIVE
assert v.validate_python('2') is Status.INACTIVE
assert v.validate_json('1') is Status.ACTIVE
Strict vs. Lax Mode
The EnumSchema behaves differently based on the strict setting:
- Lax Mode (default): Allows validation from raw values (like
1or'1') and performs coercion if asub_typeis provided. - Strict Mode: In Python validation, it requires an actual instance of the Enum class. However, in JSON validation, it still allows raw values because JSON has no native Enum type.
Custom Lookup with missing
You can provide a missing callable to the EnumSchema. This function is called when a value is not found in the members list, allowing for custom fallback logic or dynamic member resolution (similar to the _missing_ method in Python Enums).
Callable Validation
The CallableSchema ensures that an input is a Python callable object. This includes functions, classes, and instances of classes that implement the __call__ method.
Usage
The callable_schema helper creates a simple validator that performs a check equivalent to Python's built-in callable() function.
from pydantic_core import SchemaValidator, core_schema
def my_function():
return 42
class MyClass:
def __call__(self):
return 42
schema = core_schema.callable_schema()
v = SchemaValidator(schema)
assert v.validate_python(my_function) == my_function
assert v.validate_python(list) == list # Classes are callable
assert v.validate_python(MyClass()) == MyClass() # Instance with __call__
# v.validate_python(42) # Raises ValidationError: Input should be callable
Implementation Details and Constraints
- Minimum Elements: Both
LiteralSchemaandEnumSchemarequire at least one element inexpectedormembers. Attempting to create a validator with an empty list will raise aSchemaError(e.g.,SchemaError: expected should have length > 0). - Discriminated Unions:
LiteralSchemais frequently used as a "tag" withinUnionSchemato implement discriminated unions. This allowspydantic-coreto quickly identify which branch of a union to validate against based on a specific field's literal value. - Metadata and Serialization: All three schemas support optional
metadata(for storing extra information) andserialization(for customizing how these values are converted back to JSON or Python objects).