Search code examples
pythonfastapipydantic

why is Pydantic saying email is missing when I'm declaring it


In the code below, I'm creating an api that would accept a notification that should send an email. There are fields I want to add to the notification like an ID and TS after its been submitted. I have modeled it like below.

class NotificationPriority(Enum):
    high = "high"
    medium = "medium"
    low = "low"


class Notification(BaseModel):
    notification: str
    priority: NotificationPriority
    notification_from: str


class EmailNotification(Notification):
    email_to: str
    email_from: str | None = None


class EmailNotificationSystem(EmailNotification):
    id: uuid.UUID = uuid.uuid4()
    ts: datetime.datetime = datetime.datetime.now(datetime.UTC).isoformat()
    email: EmailNotification



@app.post("/notifications/email")
async def create_notification(email_notification: EmailNotification):
    print(email_notification.model_dump())
    system = EmailNotificationSystem(
        email=email_notification,
    )
    return system

Pydantic fails saying all the fields inside the email_notification that I'm setting with email=email_notification are missing.

Field required [type=missing, input_value={'email': EmailNotificati...rom='[email protected]')}, input_type=dict] For further information visit https://errors.pydantic.dev/2.7/v/missing priority Field required [type=missing, input_value={'email': EmailNotificati...rom='[email protected]')}, input_type=dict] For further information visit https://errors.pydantic.dev/2.7/v/missing notification_from Field required [type=missing, input_value={'email': EmailNotificati...rom='[email protected]')}, input_type=dict] For further information visit https://errors.pydantic.dev/2.7/v/missing email_to Field required [type=missing, input_value={'email': EmailNotificati...rom='[email protected]')}, input_type=dict] For further information visit https://errors.pydantic.dev/2.7/v/missing

when I print(email_notification.model_dump()) I can see the fields are in there and satisfied. The request body looks like this:

 {
     "notification": "new notification",
     "priority": "low",
     "notification_from": "sampleapp",
     "email_to":"[email protected];[email protected]",
     "email_from":"[email protected]"
 } 

Primary Q: Why is it saying I'm missing fields?

Secondary Q: Is there a better practice to doing what I'm doing?


Solution

  • As masklin pointed out in the comment, EmailNotificationSystem extends EmailNotification, but also has it as a member. This probably isn't what you want here.

    Another thing that's bad is the way you set the id and ts members. If you don't pass these in your JSON, they will always use the same default value, regardless of the time the notification was received. You should use Field with the default_factory parameter for this.

    Here's a fixed version, based on the model dump you provided:

    from datetime import datetime, UTC
    from enum import Enum
    from uuid import uuid4, UUID
    
    from pydantic import BaseModel, Field
    
    
    class NotificationPriority(Enum):
        high = "high"
        medium = "medium"
        low = "low"
    
    
    class Notification(BaseModel):
        notification: str
        priority: NotificationPriority
        notification_from: str
    
    
    class EmailNotification(Notification):
        email_to: str
        email_from: str | None = None
    
    
    class EmailNotificationSystem(EmailNotification):
        id: UUID = Field(default_factory=uuid4)
        ts: datetime = Field(default_factory=lambda: datetime.now(UTC))
    
    
    if __name__ == '__main__':
        json_data = """
        {
            "notification": "new notification",
            "priority": "low",
            "notification_from": "sampleapp",
            "email_to": "[email protected];[email protected]",
            "email_from": "[email protected]"
        }
        """
    
        system = EmailNotificationSystem.model_validate_json(json_data)
        print(system)