-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Initial Checks
- I have searched Google & GitHub for similar requests and couldn't find anything
- I have read and followed the docs and still think this feature is missing
Description
🚀 Feature Request: Support NotRequired
Field Semantics in Pydantic Models
Background
In many real-world API contracts, some request fields are:
- Optional to include (i.e., the client may omit them entirely),
- But if provided, must be a valid non-null value,
- And
null
is not a valid input (and often not semantically meaningful), - Meanwhile, default values might exist on either client or server side,
- And sometimes you want to keep Pydantic’s default values for internal logic, while only omitting them from serialized outputs under certain rules.
Currently, Pydantic does not provide a clean, expressive way to represent this intent.
🔍 Motivation
Consider the following server-side API contract:
POST /predict
{
"model": "gpt-4",
"temperature": 0.7 // optional, but if provided must be a float between 0 and 1
}
The rules are:
- The
temperature
field is optional — if omitted, the server uses its own default. - If provided, it must be a float, and must not be
null
. - The client should be able to omit it without sending a dummy value like
0.0
.
🧪 Existing Workarounds (and Their Issues)
1. temperature: float | None = None
class Request(BaseModel):
model: str
temperature: float | None = None
Problem:
- This accepts
"temperature": null
, which violates the API contract. null
is not a valid temperature — but this model would allow it.
2. Use .model_dump(exclude_none=True)
Problem:
- Some fields may legitimately use
None
as a value (e.g.description: str | None
). exclude_none=True
will strip allNone
s, even if some are semantically meaningful.- No per-field control over
None
exclusion.
3. Use temperature: float = 0.0
and .model_dump(exclude_defaults=True)
class Request(BaseModel):
model: str
temperature: float = 0.0
Problem:
- 0.0 might be a valid and meaningful temperature.
- Can't distinguish between "use server default" vs. "I want zero".
exclude_defaults=True
affects all fields, not justtemperature
.
4. Use .model_dump(exclude_unset=True)
class Request(BaseModel):
model: str
temperature: float = 0.7
Problem:
- Works for
temperature
, but fails for cases where you want the defaulted field to be included.
Example:
class FunctionTool(BaseModel):
type: Literal["function"] = "function"
function: dict
type
should always be included in the output (required by server).- But
exclude_unset=True
will remove it unless explicitly set by the user. - Again, too broad — no per-field control.
5. Use TypedDict + TypeAdapter
(which supports NotRequired
)
from typing import TypedDict, NotRequired
from pydantic import TypeAdapter
class RequestDict(TypedDict, total=False):
model: str
temperature: NotRequired[float]
TypeAdapter(RequestDict).validate_python({"model": "gpt-4"})
Problem:
-
TypedDict
does not support default values. -
For example:
class FunctionToolDict(TypedDict): type: Literal["function"] # must be manually set every time function: dict
- There's no way to pre-fill
type = "function"
as a default. - Developer must remember to manually set it every time — error-prone and inconvenient.
- There's no way to pre-fill
-
TypedDict
also lacks many useful features fromBaseModel
:- Validation logic,
- Serialization methods (
model_dump
), - Field validators, default factories, etc.
In short: TypedDict
gives you NotRequired
, but takes away everything else that makes Pydantic useful.
🧩 What's Missing: NotRequired
We need a way to express this common API pattern:
“This field is optional to include, but if it is included, it must be non-null and type-valid.”
Something like:
from pydantic import BaseModel, NotRequired
class Request(BaseModel):
model: str
temperature: NotRequired[float]
This would:
- Validate properly (disallow
None
), - Exclude the field from
.model_dump()
if unset, - Allow defaulted fields like
type = "function"
to still work, - Avoid global exclusions like
exclude_none
orexclude_defaults
, - Make intent explicit and models more maintainable.
✅ Benefits
- Expressivity: Cleanly encodes a common contract pattern.
- Precision: Avoids accepting
None
when it isn't semantically valid. - Field-level control: Unlike global flags like
exclude_none
. - Developer ergonomics: No need for boilerplate logic or manual defaults.
- Type-safe and declarative: Self-documenting and easy to understand.
🧪 Realistic Example Use Case
class JobConfig(BaseModel):
id: str
retry: NotRequired[int] # optional override; must be non-null if set
timeout: int = 60 # always included
description: str | None = None # null is meaningful here
retry
: Optional override — server has a default, but client can set.timeout
: Required by server; included by default.description
: Can be null — unlikeretry
.
This pattern is very hard to model cleanly with current Pydantic tools.
🙏 Feature Request
Would the Pydantic team consider supporting a NotRequired[T]
type hint?
It would fill the semantic gap between Optional[T]
, defaulted fields, and serialization control — and would greatly simplify many real-world use cases involving API request modeling.
Thanks for the amazing work you do 💙
Related Issue
Affected Components
- Compatibility between releases
- Data validation/parsing
- Data serialization -
.model_dump()
and.model_dump_json()
- JSON Schema
- Dataclasses
- Model Config
- Field Types - adding or changing a particular data type
- Function validation decorator
- Generic Models
- Other Model behaviour -
model_construct()
, pickling, private attributes, ORM mode - Plugins and integration with other tools - mypy, FastAPI, python-devtools, Hypothesis, VS Code, PyCharm, etc.