FastAPI-JSONAPI

Navigation

  • Installation
  • A minimal API
  • Filtering API example
  • Quickstart
  • Routing
  • Atomic Operations
  • View Dependencies
  • Filtering
  • Create and include related objects (updated example)
  • Include related objects
  • Include nested and related, Many-to-Many
    • Prepare models and schemas
      • Define SQLAlchemy models
      • Define pydantic schemas
      • Define view classes
    • List Parent objects with Children through an Association object
  • Custom SQL filtering
  • Client generated id
  • Logical data abstraction
  • Resource Manager
  • Data layer
  • Define relationships
  • Sparse fieldsets
  • Pagination
  • Sorting
  • Errors
  • Permission
  • OAuth
  • Configuration
  • Changelog

Related Topics

  • Documentation overview
    • Previous: Include related objects
    • Next: Custom SQL filtering

Quick search

Include nested and related, Many-to-Many¶

The same as usual includes. Here’s an example with an association object.

Example (sources here):

Prepare models and schemas¶

Define SQLAlchemy models¶

Parent model¶

models/parent.py:

from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship

from examples.api_for_sqlalchemy.extensions.sqlalchemy import Base
from examples.api_for_sqlalchemy.utils.sqlalchemy.base_model_mixin import BaseModelMixin


class Parent(Base, BaseModelMixin):
    __tablename__ = "left_table_parents"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String, nullable=False)
    children = relationship(
        "ParentToChildAssociation",
        back_populates="parent",
    )

Child model¶

models/child.py:

from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship

from examples.api_for_sqlalchemy.extensions.sqlalchemy import Base
from examples.api_for_sqlalchemy.utils.sqlalchemy.base_model_mixin import BaseModelMixin


class Child(Base, BaseModelMixin):
    __tablename__ = "right_table_children"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String, nullable=False)
    parents = relationship(
        "ParentToChildAssociation",
        back_populates="child",
    )

Parent to Child Association model¶

models/parent_child_association.py:

from sqlalchemy import Column, ForeignKey, Index, Integer, String
from sqlalchemy.orm import relationship

from examples.api_for_sqlalchemy.extensions.sqlalchemy import Base
from examples.api_for_sqlalchemy.utils.sqlalchemy.base_model_mixin import BaseModelMixin


class ParentToChildAssociation(Base, BaseModelMixin):
    __table_args__ = (
        # JSON:API requires `id` field on any model,
        # so we can't create a composite PK here
        # that's why we need to create this index
        Index(
            "ix_parent_child_association_unique",
            "parent_left_id",
            "child_right_id",
            unique=True,
        ),
    )

    __tablename__ = "parent_to_child_association_table"

    id = Column(Integer, primary_key=True, autoincrement=True)

    parent_left_id = Column(
        ForeignKey("left_table_parents.id"),
        nullable=False,
    )
    child_right_id = Column(
        ForeignKey("right_table_children.id"),
        nullable=False,
    )
    extra_data = Column(String(50))
    parent = relationship(
        "Parent",
        back_populates="children",
        # primaryjoin="ParentToChildAssociation.parent_left_id == Parent.id",
    )
    child = relationship(
        "Child",
        back_populates="parents",
        # primaryjoin="ParentToChildAssociation.child_right_id == Child.id",
    )

Define pydantic schemas¶

Parent Schema¶

schemas/parent.py:

from typing import TYPE_CHECKING, List

from fastapi_jsonapi.schema_base import BaseModel, Field, RelationshipInfo

if TYPE_CHECKING:
    from .parent_child_association import ParentToChildAssociationSchema


class ParentBaseSchema(BaseModel):
    """Parent base schema."""

    class Config:
        orm_mode = True

    name: str

    children: List["ParentToChildAssociationSchema"] = Field(
        default=None,
        relationship=RelationshipInfo(
            resource_type="parent_child_association",
            many=True,
        ),
    )


class ParentPatchSchema(ParentBaseSchema):
    """Parent PATCH schema."""


class ParentInSchema(ParentBaseSchema):
    """Parent input schema."""


class ParentSchema(ParentInSchema):
    """Parent item schema."""

    id: int

Child Schema¶

schemas/child.py:

from typing import TYPE_CHECKING, List

from fastapi_jsonapi.schema_base import BaseModel, Field, RelationshipInfo

if TYPE_CHECKING:
    from .parent_child_association import ParentToChildAssociationSchema


class ChildBaseSchema(BaseModel):
    """Child base schema."""

    class Config:
        orm_mode = True

    name: str

    parents: List["ParentToChildAssociationSchema"] = Field(
        default=None,
        relationship=RelationshipInfo(
            resource_type="parent_child_association",
            many=True,
        ),
    )


class ChildPatchSchema(ChildBaseSchema):
    """Child PATCH schema."""


class ChildInSchema(ChildBaseSchema):
    """Child input schema."""


class ChildSchema(ChildInSchema):
    """Child item schema."""

    id: int

Parent to Child Association Schema¶

schemas/parent_child_association.py:

from typing import TYPE_CHECKING

from fastapi_jsonapi.schema_base import BaseModel, Field, RelationshipInfo

if TYPE_CHECKING:
    from .child import ChildSchema
    from .parent import ParentSchema


class ParentToChildAssociationSchema(BaseModel):
    id: int
    extra_data: str

    parent: "ParentSchema" = Field(
        default=None,
        relationship=RelationshipInfo(
            resource_type="parent",
        ),
    )

    child: "ChildSchema" = Field(
        default=None,
        relationship=RelationshipInfo(
            resource_type="child",
        ),
    )

Define view classes¶

Base Views¶

api/base.py:

Parent Views¶

schemas/child.py:

Child Views¶

schemas/child.py:

List Parent objects with Children through an Association object¶

Request:

GET /parents?include=children%2Cchildren.child HTTP/1.1
Content-Type: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    {
      "attributes": {
        "name": "parent_1"
      },
      "id": "1",
      "relationships": {
        "children": {
          "data": [
            {
              "id": "1",
              "type": "parent_child_association"
            },
            {
              "id": "3",
              "type": "parent_child_association"
            }
          ]
        }
      },
      "type": "parent"
    },
    {
      "attributes": {
        "name": "parent_2"
      },
      "id": "2",
      "relationships": {
        "children": {
          "data": [
            {
              "id": "2",
              "type": "parent_child_association"
            },
            {
              "id": "4",
              "type": "parent_child_association"
            },
            {
              "id": "5",
              "type": "parent_child_association"
            }
          ]
        }
      },
      "type": "parent"
    },
    {
      "attributes": {
        "name": "parent_3"
      },
      "id": "3",
      "relationships": {
        "children": {
          "data": []
        }
      },
      "type": "parent"
    }
  ],
  "included": [
    {
      "attributes": {
        "name": "child_1"
      },
      "id": "1",
      "type": "child"
    },
    {
      "attributes": {
        "name": "child_2"
      },
      "id": "2",
      "type": "child"
    },
    {
      "attributes": {
        "name": "child_3"
      },
      "id": "3",
      "type": "child"
    },
    {
      "attributes": {
        "extra_data": "assoc_p1c1_extra"
      },
      "id": "1",
      "relationships": {
        "child": {
          "data": {
            "id": "1",
            "type": "child"
          }
        }
      },
      "type": "parent_child_association"
    },
    {
      "attributes": {
        "extra_data": "assoc_p2c1_extra"
      },
      "id": "2",
      "relationships": {
        "child": {
          "data": {
            "id": "1",
            "type": "child"
          }
        }
      },
      "type": "parent_child_association"
    },
    {
      "attributes": {
        "extra_data": "assoc_p1c2_extra"
      },
      "id": "3",
      "relationships": {
        "child": {
          "data": {
            "id": "2",
            "type": "child"
          }
        }
      },
      "type": "parent_child_association"
    },
    {
      "attributes": {
        "extra_data": "assoc_p2c2_extra"
      },
      "id": "4",
      "relationships": {
        "child": {
          "data": {
            "id": "2",
            "type": "child"
          }
        }
      },
      "type": "parent_child_association"
    },
    {
      "attributes": {
        "extra_data": "assoc_p2c3_extra"
      },
      "id": "5",
      "relationships": {
        "child": {
          "data": {
            "id": "3",
            "type": "child"
          }
        }
      },
      "type": "parent_child_association"
    }
  ],
  "jsonapi": {
    "version": "1.0"
  },
  "meta": {
    "count": 3,
    "totalPages": 1
  }
}
©2023, MTS AI. | Powered by Sphinx 7.2.5 & Alabaster 0.7.13 | Page source
Fork me on GitHub