2.6 KiB
| title | tags | sources | created | updated | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| MongoDB Enum Deserialization to String |
|
|
2026-04-30 | 2026-04-30 |
MongoDB Enum Deserialization to String
MongoDB stores Python Enum fields as their raw string (or int) values. When a document is read back and deserialized into a Pydantic model, the field may arrive as a plain str instead of the Enum member — depending on the Pydantic version, ODM, and field configuration.
The Bug
class UserRole(str, Enum):
ADMIN = "admin"
VIEWER = "viewer"
class User(BaseModel):
role: UserRole
After reading from MongoDB, user.role may be "admin" (a str), not UserRole.ADMIN. Calling .value on it then crashes:
user.role.value # AttributeError: 'str' object has no attribute 'value'
This 500 is often silent in production — FastAPI catches it generically and logs it as an unhandled exception, not obviously linked to the Enum field.
Root Cause
The crash occurred in audit_logger.py:82 calling user.role.value under the assumption that role was a Python Enum. MongoDB had stored it as "admin" and the ODM passed the raw string through without coercion.
Guard Pattern (Short-term)
role_value = user.role.value if hasattr(user.role, "value") else user.role
Works at any call site but is defensive noise. Prefer the boundary fix below.
Boundary Fix (Long-term)
Coerce at deserialization time using a Pydantic validator so all downstream code can safely assume the field is the right type:
from pydantic import field_validator
class User(BaseModel):
role: UserRole
@field_validator("role", mode="before")
@classmethod
def coerce_role(cls, v):
if isinstance(v, UserRole):
return v
return UserRole(v) # raises ValueError if value is unknown
With Pydantic v2 and model_config = ConfigDict(use_enum_values=True) the Enum is stored as its value — reading it back requires the validator to re-wrap it.
When It Happens
- Motor / PyMongo (no ODM) — always returns raw BSON types; no Enum coercion
- Beanie ODM — coerces correctly in most cases; can break with
use_enum_values=True - Pydantic v1
orm_mode— coerces from raw value automatically - Pydantic v2 — requires explicit
mode="before"validator orConfigDict(use_enum_values=False)
Related
- wiki/concepts/pydantic-default-factory-type-alias — another silent Pydantic/MongoDB mismatch
- wiki/tech-patterns/fastapi-patterns — FastAPI error handling