FastAPI-JSONAPI
2.3.1
  • 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
        • Parent model
        • Child model
        • Parent to Child Association model
      • Define pydantic schemas
        • Parent Schema
        • Child Schema
        • Parent to Child Association Schema
      • Define view classes
        • Base Views
    • List Parent objects with Children through an Association object
  • Custom SQL filtering
  • Client generated id
  • Logical data abstraction
  • Resource Manager
  • Data layer
  • Define relationships
  • Configuration
  • Sparse fieldsets
  • Pagination
  • Sorting
  • Errors
  • Permission
  • OAuth
  • Package fastapi_jsonapi index
  • Changelog
FastAPI-JSONAPI
  • Include nested and related, Many-to-Many
  • Edit on GitHub

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:

from typing import Dict

from fastapi import Depends
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession

from examples.api_for_sqlalchemy.extensions.sqlalchemy import Connector
from fastapi_jsonapi.data_layers.sqla_orm import SqlalchemyDataLayer
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
from fastapi_jsonapi.views.utils import HTTPMethod, HTTPMethodConfig
from fastapi_jsonapi.views.view_base import ViewBase


class SessionDependency(BaseModel):
    session: AsyncSession = Depends(Connector.get_session)

    class Config:
        arbitrary_types_allowed = True


def handler(view: ViewBase, dto: SessionDependency) -> Dict:
    return {"session": dto.session}


class DetailViewBase(DetailViewBaseGeneric):
    """
    Generic view base (detail)
    """

    data_layer_cls = SqlalchemyDataLayer

    method_dependencies = {
        HTTPMethod.ALL: HTTPMethodConfig(
            dependencies=SessionDependency,
            prepare_data_layer_kwargs=handler,
        ),
    }


class ListViewBase(ListViewBaseGeneric):
    """
    Generic view base (list)
    """

    data_layer_cls = SqlalchemyDataLayer

    method_dependencies = {
        HTTPMethod.ALL: HTTPMethodConfig(
            dependencies=SessionDependency,
            prepare_data_layer_kwargs=handler,
        ),
    }

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
  }
}
Previous Next

© Copyright 2023, MTS AI. Revision 550aa4e4.

Built with Sphinx using a theme provided by Read the Docs.