Defining recursive models in Pydantic?
Asked Answered
M

4

21

How can I define a recursive Pydantic model?

Here's an example of what I mean:

from typing import List
from pydantic import BaseModel

class Task(BaseModel):
    name: str
    subtasks: List[Task] = []

but when I run that I get the following error:

NameError                                 Traceback (most recent call last)
<ipython-input-1-c6dca1d390fe> in <module>
      2 from pydantic import BaseModel
      3
----> 4 class Task(BaseModel):
      5     name: str
      6     subtasks: List[Task] = []

<ipython-input-1-c6dca1d390fe> in Task()
      4 class Task(BaseModel):
      5     name: str
----> 6     subtasks: List[Task] = []
      7

NameError: name 'Task' is not defined

I looked through the documentation but couldn't find anything. For example, at the page on "Recursive Models", but it seems to be about nesting subtypes of BaseModel not about a recursive type definition.

Thanks for your help!

Malo answered 22/6, 2021 at 22:43 Comment(1)
Alex Hall's answer is correct — you need from __future__ import annotations at the top of your code. This blog post goes into a little more detail about what's going on there: dev.to/tiangolo/…Karb
M
18

Either put from __future__ import annotations at the top of the file, or change your annotation to List['Task'].

The relevant pydantic documentation is here: https://pydantic-docs.helpmanual.io/usage/postponed_annotations/

But the error in your question is not pydantic specific, that's just how type annotations work.

Mitchelmitchell answered 22/6, 2021 at 22:47 Comment(0)
E
6

Expanding on the accepted answer from Alex Hall:

From the Pydantic docs, it appears the call to update_forward_refs() is still required whether or not annotations is imported.

from typing import List
from pydantic import BaseModel

class Task(BaseModel):
    name: str
    subtasks: List['Task'] = []


Task.update_forward_refs()

or

# python3.7+
from __future__ import annotations
from typing import List
from pydantic import BaseModel

class Task(BaseModel):
    name: str
    subtasks: List[Task] = []


Task.update_forward_refs()

https://pydantic-docs.helpmanual.io/usage/postponed_annotations/#self-referencing-models

Electroballistics answered 19/11, 2021 at 16:45 Comment(1)
If you are using pydantic V2 onwards update_forward_refs is deprecated and you'd want to use model_rebuild instead.Wayzgoose
M
0

I found myself in a situation in which I want to run a root validator on the class. What happens is that, although the above solution seem to work find in some cases, the value of the recursive field subtasks doesn't show up in the values dictionary parameter of the @root_validator.

I ended up with declaring

subtasks: List[Dict]

and recursively build the subtasks objects in the @root_validator:

subtasks = values.get('subtasks')
if subtasks:
    values['subtasks']=[Task(**subtask) for subtask in subtasks]
Mussman answered 12/11, 2021 at 13:36 Comment(0)
J
0

This worked for me

from typing import Optional
from pydantic import BaseModel

class Task(BaseModel):
    name: str
    subtasks: Optional["Task"]
Judges answered 12/7 at 9:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.