4

Check out these Pydantic models, AuthorSchema and BookSchema

from typing import List, Optional
from pydantic import BaseModel


class AuthorSchema(BaseModel):
    id: int
    name: str
    blurb: Optional[str]

    class Config:
        orm_mode = True

class BookSchema(BaseModel):
    id: int
    title: str
    authors: List[AuthorSchema]

    class Config:
        orm_mode = True

A Book can contain multiple Authors. Furthermore, an Author can have a blurb. (A blurb is a note about an author, relative to his work on a specific book.)

I have three corresponding classes for Book, Author, and BookAuthor.

class Author:
    def __init__(self, id: int, name: str):
        self.id = id
        self.name = name

class Book:
    def __init__(self, id: int, title: str):
        self.id = id
        self.title = title

class BookAuthor:
    def __init__(self, book: Book, author: Author, blurb: str):
        self.book = book
        self.author = author
        self.blurb = blurb

My goal is to populate a BookSchema instance with multiple related Authors. Here's a demo of my attempt

Demo (Attempt)

bob = Author(id=1, name='Bob')
sue = Author(id=2, name='Sue')

book1 = Book(id=1, title="Foo")
book1.authors = [
    BookAuthor(book=book1, author=bob, blurb='Bob is a scientist who wrote chapters 1-3'),
    BookAuthor(book=book1, author=sue, blurb='Sue is an economist who wrote chapter 4')
]

print(book1.title)                   # Foo
print(book1.authors[0].blurb)        # Bob is a scientist who wrote chapters 1-3
print(book1.authors[0].author.name)  # Bob


AuthorSchema.from_orm(bob)  # works
BookSchema.from_orm(book1)  # errors

BookSchema.from_orm(book1) errors because it expects book1.authors[0] to have a .id attribute. Obviously, it doesn't. To access the id attribute one must do book1.authors[0].author.id.

Unfortunately I cannot alter the shape of my incoming data. I also don't want to change the structure of my Pydantic models. So, how can I populate my Pydantic model given my input data, considering they are misaligned in their structure?

Ben
  • 20,038
  • 30
  • 112
  • 189

1 Answers1

1

tl;dr: it seems the route to victory lies in setting a getter_dict config field, to a subclass of GetterDict:

from pydantic.utils import GetterDict


class BookAuthorGetter(GetterDict):
    def get(self, key: str, default: Any) -> Any:
        if key in {"authors"}:
            return [author.author for author in self._obj.authors]

        return super().get(key, default)


# updated BookSchema model
class BookSchema(BaseModel):
    id: int
    title: str
    authors: List[AuthorSchema]

    class Config:
        orm_mode = True
        getter_dict = BookAuthorGetter

Test:

>>> print(AuthorSchema.from_orm(bob))
id=1 name='Bob' blurb=None
>>> print(BookSchema.from_orm(book1))
id=1 title='Foo' authors=[AuthorSchema(id=1, name='Bob', blurb=None), AuthorSchema(id=2, name='Sue', blurb=None)]

References:

tutuDajuju
  • 10,307
  • 6
  • 65
  • 88