Fastapi: [QUESTION] How to manage relationships in (pydantic) models

Created on 12 Apr 2019  ·  13Comments  ·  Source: tiangolo/fastapi

EDIT: add proper greetings 🙄

Hi guys,

Many thanks for this fantastic repo. It rocks. Not to mention https://dockerswarm.rocks/, I am now in the process to review all my projects from this base 🥇

Here is my question :

How can I declare a one-to-many relationship in the pydantic models?

Context

I have a first object 'Rule', that is attached to a second 'City'.

I have tried the following without success :

rules.py

from app.models.cities import City

class RuleBase(BaseModel):
    mode: Optional[RuleMode] = None
    value: Optional[float] = None
    city: Optional[City] = None

cities.py

class City(BaseModel):
    id: int
    name: str
    rules: Optional[List['Rule']]

Error in tests:

ImportError while loading conftest '/app/app/tests/conftest.py'.
app/app/tests/conftest.py:4: in <module>
    from app.models.cities import City, Rectangle, Point
app/app/models/cities.py:50: in <module>
    class City(BaseModel):
usr/local/lib/python3.6/site-packages/pydantic/main.py:179: in __new__
    config=config,
usr/local/lib/python3.6/site-packages/pydantic/fields.py:118: in infer
    schema=schema,
usr/local/lib/python3.6/site-packages/pydantic/fields.py:87: in __init__
    self.prepare()
usr/local/lib/python3.6/site-packages/pydantic/fields.py:152: in prepare
    self._populate_sub_fields()
usr/local/lib/python3.6/site-packages/pydantic/fields.py:177: in _populate_sub_fields
    self.sub_fields = [self._create_sub_type(t, f'{self.name}_{display_as_type(t)}') for t in types_]
usr/local/lib/python3.6/site-packages/pydantic/fields.py:177: in <listcomp>
    self.sub_fields = [self._create_sub_type(t, f'{self.name}_{display_as_type(t)}') for t in types_]
usr/local/lib/python3.6/site-packages/pydantic/fields.py:210: in _create_sub_type
    model_config=self.model_config,
usr/local/lib/python3.6/site-packages/pydantic/fields.py:87: in __init__
    self.prepare()
usr/local/lib/python3.6/site-packages/pydantic/fields.py:153: in prepare
    self._populate_validators()
usr/local/lib/python3.6/site-packages/pydantic/fields.py:228: in _populate_validators
    else find_validators(self.type_, self.model_config.arbitrary_types_allowed)
usr/local/lib/python3.6/site-packages/pydantic/validators.py:326: in find_validators
    raise RuntimeError(f'error checking inheritance of {type_!r} (type: {display_as_type(type_)})') from e
E   RuntimeError: error checking inheritance of _ForwardRef('Rule') (type: _ForwardRef('Rule'))

Cheers,
Emmanuel

question

Most helpful comment

Hi, I'm sorry for resurrecting an old issue but I just ran into @ebreton's problem and I'd like to check if this is still the best practice. Considering an example similar to the docs:

item.py:

class Item(ItemBase):
    id: int
    owner: User    # <------ Relationship with User

    class Config:
        orm_mode = True

user.py:

class User(UserBase):
    id: int
    is_active: bool
    items: List[Item] = [] # <------ Relationship with Item

    class Config:
        orm_mode = True

I'd have to import both item from user.py and user from item.py, which would cause a circular import. So I should do something like


ListItem = ForwardRef("List[Item]")
class User(UserBase):
    id: int
    is_active: bool
    items: ListItem = [] # <------ Relationship with Item

    class Config:
        orm_mode = True

User.update_forward_refs()

As my FastAPI application grows, I'd like to break the CRUD into a submodule and the schemas as well. Having back populated relationships like those make things difficult


Update: actually, I couldn't make it work...
Update 2: I think my problem is actually this one samuelcolvin/pydantic#659

All 13 comments

It won't answer your question exactly but I feel it's the same pattern where you have recursivity and declare an attribute of a model while that depends on another not yet created

my model looks like this :

<LoggerModel name='root' level=20 children=[<LoggerModel name='__main__' level=10 children=[]>, <LoggerModel name='__mp_ma…>
from __future__ import annotations

from typing import ForwardRef, List, Optional

from pydantic import BaseModel

ListLoggerModel = ForwardRef("List[LoggerModel]")

class LoggerModel(BaseModel):
    name: str
    level: Optional[int]
    children: ListLoggerModel = None


LoggerModel.update_forward_refs()

Thanks for the hint!

I got an ImportError with python 3.6.8

>>> from typing import ForwardRef
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'ForwardRef'

Therefore I installed python 3.7.3 and that solved the issue.

Probably something to report to https://github.com/tiangolo/full-stack-fastapi-postgresql, from which I started from :)

Forwardref is a python 3 thing indeed, maybe there s a way to achieve the
same without, @tiangolo will know for sure !

Le ven. 12 avr. 2019 à 7:59 PM, Manu notifications@github.com a écrit :

Thanks for the hint!

I got an ImportError with python 3.6.8

from typing import ForwardRef
Traceback (most recent call last):
File "", line 1, in
ImportError: cannot import name 'ForwardRef'

Therefore I installed python 3.7.3 and that solved the issue.

Probably something to report to
https://github.com/tiangolo/full-stack-fastapi-postgresql, from which I
started from :)


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/tiangolo/fastapi/issues/153#issuecomment-482667182,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABDZPnowf6UmatNDVrS7YpgPPMgkXMCnks5vgMlkgaJpZM4csUX5
.

Many thanks for this fantastic repo. It rocks. Not to mention https://dockerswarm.rocks/, I am now in the process to review all my projects from this base 🥇

That's cool! Thanks!

As @euri10 says, for that case, ForwardRef is the way to go.

The caveat is that ForwardRef is Python 3.7 only. And Python 3.7 is not compatible yet with tools like Celery and TensorFlow :/

Hi @tiangolo ,

Thanks for the confirmation 👍

Fair enough for tensorflow. Celery seems to have catch up with python 3.7 and I have made a PR on the postgresql project Generator https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/10 to test 3.7

Could you have a look at it ? Would it effectively allow the ForwardRef and ease the definition of relationships on a pydantic levels, that would be a great added value for a postgresql project 😇

Cheers,
Emmanuel

Awesome! I'll check it.

Just merged your PR. Now Celery and TensorFlow both support Python 3.7! :tada:

Hi, I'm sorry for resurrecting an old issue but I just ran into @ebreton's problem and I'd like to check if this is still the best practice. Considering an example similar to the docs:

item.py:

class Item(ItemBase):
    id: int
    owner: User    # <------ Relationship with User

    class Config:
        orm_mode = True

user.py:

class User(UserBase):
    id: int
    is_active: bool
    items: List[Item] = [] # <------ Relationship with Item

    class Config:
        orm_mode = True

I'd have to import both item from user.py and user from item.py, which would cause a circular import. So I should do something like


ListItem = ForwardRef("List[Item]")
class User(UserBase):
    id: int
    is_active: bool
    items: ListItem = [] # <------ Relationship with Item

    class Config:
        orm_mode = True

User.update_forward_refs()

As my FastAPI application grows, I'd like to break the CRUD into a submodule and the schemas as well. Having back populated relationships like those make things difficult


Update: actually, I couldn't make it work...
Update 2: I think my problem is actually this one samuelcolvin/pydantic#659

My problem is the same as @fbidu .

Or @tiangolo are you against splitting schema into multiple files?

Same issue here. Can't manage to have 2 schemas referencing each others. What is the correct way to achieve this?

I have the same problem. Has anyone found a solution? @tiangolo

There is no correct way - two schemas referencing each other will cause an infinite loop, which you must break

What would be the best practice in this situation?

Was this page helpful?
0 / 5 - 0 ratings