5

Python 3.9 - I have the following module:

from __future__ import annotations
from typing import TYPE_CHECKING
from pydantic import BaseModel

if TYPE_CHECKING:
    from typing import Optional

class A(BaseModel):
    id: int
    class Config:
        orm_mode = True

class B(A):
    foo: C

class C(A):
    bar: Optional[str]

C.update_forward_refs()

c = C(id=1, bar='bar')
b = B(id=2, foo=c)

When I import this module it raises NameError: name 'Optional' is not defined. I can remove the if TYPE_CHECKING part, but I understand that this is the best practice (to prevent circular imports if I use my own types for example). When I remove the B.update_forward_refs() call it raises pydantic.errors.ConfigError: field "foo" not yet prepared so type is still a ForwardRef, you might need to call B.update_forward_refs().

Any idea how to overcome this?

ishefi
  • 480
  • 4
  • 12
  • When I import the code as you've got it without `B.update_forward_refs()` things work just fine. I'm using Pydantic 1.9.0 on Python 3.9.6. Which version of Pydantic are you using? – LondonRob Mar 17 '22 at 12:20
  • I'm using Pydantic 1.9.0 on Python 3.9.10. You need `update_forward_refs()` when you actually create instances of Pydantic classes - I updated the code. Thanks. – ishefi Mar 20 '22 at 12:39

1 Answers1

4

As you stated, the TYPE_CHECKING is not needed in your example but for circular imports, you might need that one. I'll give a bit simpler example to illustrate how to solve the circular import problem.

We have the following modules:

foo.py

from typing import TYPE_CHECKING
from pydantic import BaseModel

if TYPE_CHECKING:
    from bar import Bar

class Foo(BaseModel):
    reference: 'Bar'

bar.py

from typing import TYPE_CHECKING, Optional
from pydantic import BaseModel

if TYPE_CHECKING:
    from foo import Foo

class Bar(BaseModel):
    reference: Optional['Foo']

main.py

from bar import Bar
from foo import Foo

Bar.update_forward_refs(Foo=Foo)
Foo.update_forward_refs(Bar=Bar)

# Put Bar into Foo and Foo into Bar
Bar(reference=Foo(reference=Bar()))

This solution works (if we run the main.py file) and does not have the circular import problem. See how we passed Foo and Bar as keyword arguments to the update_forward_refs. This is because the method defines the types using the locals of the models' modules by default and those module spaces do not have those as locals (as we used the if TYPE_CHECKING:). Therefore we passed these ourselves.

If you are building a package, you might want to update_forward_refs in the __init__.py files.

miksus
  • 2,426
  • 1
  • 18
  • 34
  • 1
    Could I get feedback on why I was downvoted? It seems fixing the name error without introducing circular import was the issue here and I showed a working solution to solve the name error without introducing circular import. Was the answer unclear or should it be edited? – miksus Jun 29 '22 at 17:14