Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/api_for_sqlalchemy/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from examples.api_for_sqlalchemy.models.age_rating import AgeRating
from examples.api_for_sqlalchemy.models.child import Child
from examples.api_for_sqlalchemy.models.computer import Computer
from examples.api_for_sqlalchemy.models.movie import Movie
from examples.api_for_sqlalchemy.models.parent import Parent
from examples.api_for_sqlalchemy.models.parent_to_child_association import ParentToChildAssociation
from examples.api_for_sqlalchemy.models.post import Post
Expand All @@ -9,8 +11,10 @@
from examples.api_for_sqlalchemy.models.workplace import Workplace

__all__ = (
"AgeRating",
"Child",
"Computer",
"Movie",
"Parent",
"ParentToChildAssociation",
"Post",
Expand Down
30 changes: 30 additions & 0 deletions examples/api_for_sqlalchemy/models/age_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import TYPE_CHECKING

from sqlalchemy import String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship

from examples.api_for_sqlalchemy.models.base import BaseMetadata

if TYPE_CHECKING:
from examples.api_for_sqlalchemy.models.movie import Movie


class AgeRating(BaseMetadata):
__tablename__ = "age_rating"

name: Mapped[str] = mapped_column(
String(20),
primary_key=True,
)
description: Mapped[str] = mapped_column(
Text(),
default="",
server_default="",
)

movies: Mapped[list["Movie"]] = relationship(
back_populates="age_rating_obj",
)

def __str__(self) -> str:
return self.name
6 changes: 5 additions & 1 deletion examples/api_for_sqlalchemy/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
class BaseMetadata(DeclarativeBase):
__table_args__: ClassVar[dict[str, Any]] = {
"extend_existing": True,
}


class Base(BaseMetadata):
__abstract__ = True

id: Mapped[int] = mapped_column(primary_key=True)
54 changes: 54 additions & 0 deletions examples/api_for_sqlalchemy/models/movie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import TYPE_CHECKING, Optional

from sqlalchemy import (
ForeignKey,
Identity,
Integer,
String,
Text,
)
from sqlalchemy.orm import (
Mapped,
mapped_column,
relationship,
)

from examples.api_for_sqlalchemy.models.base import Base

if TYPE_CHECKING:
from examples.api_for_sqlalchemy.models.age_rating import AgeRating


class Movie(Base):
__tablename__ = "movie"

id: Mapped[int] = mapped_column(
Integer,
Identity(always=True),
primary_key=True,
autoincrement=True,
)
title: Mapped[str] = mapped_column(
String(120),
index=True,
)
description: Mapped[str] = mapped_column(
Text,
default="",
server_default="",
)
age_rating: Mapped[Optional[str]] = mapped_column(
ForeignKey(
"age_rating.name",
ondelete="SET NULL",
),
)
age_rating_obj: Mapped["AgeRating"] = relationship(
back_populates="movies",
)

def __str__(self) -> str:
return self.title

def __repr__(self) -> str:
return f"Movie(id={self.id}, title={self.title!r})"
24 changes: 24 additions & 0 deletions examples/api_for_sqlalchemy/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
from .age_rating import (
AgeRatingAttributesSchema,
AgeRatingBaseSchema,
AgeRatingCreateSchema,
AgeRatingSchema,
AgeRatingUpdateSchema,
)
from .child import (
ChildAttributesSchema,
ChildInSchema,
Expand All @@ -10,6 +17,13 @@
ComputerPatchSchema,
ComputerSchema,
)
from .movie import (
MovieAttributesSchema,
MovieBaseSchema,
MovieCreateSchema,
MovieSchema,
MovieUpdateSchema,
)
from .parent import (
ParentAttributesSchema,
ParentInSchema,
Expand Down Expand Up @@ -51,6 +65,11 @@
)

__all__ = (
"AgeRatingAttributesSchema",
"AgeRatingBaseSchema",
"AgeRatingCreateSchema",
"AgeRatingSchema",
"AgeRatingUpdateSchema",
"ChildAttributesSchema",
"ChildInSchema",
"ChildPatchSchema",
Expand All @@ -60,6 +79,11 @@
"ComputerPatchSchema",
"ComputerSchema",
"CustomUserAttributesSchema",
"MovieAttributesSchema",
"MovieBaseSchema",
"MovieCreateSchema",
"MovieSchema",
"MovieUpdateSchema",
"ParentAttributesSchema",
"ParentInSchema",
"ParentPatchSchema",
Expand Down
49 changes: 49 additions & 0 deletions examples/api_for_sqlalchemy/schemas/age_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import TYPE_CHECKING, Annotated, Optional

from annotated_types import MaxLen, MinLen
from pydantic import ConfigDict

from fastapi_jsonapi.schema_base import BaseModel
from fastapi_jsonapi.types_metadata import RelationshipInfo

name_constrained = Annotated[
str,
MinLen(1),
MaxLen(20),
]

if TYPE_CHECKING:
from examples.api_for_sqlalchemy.schemas.movie import MovieSchema


class AgeRatingAttributesSchema(BaseModel):
model_config = ConfigDict(
from_attributes=True,
)
name: str
description: str


class AgeRatingBaseSchema(AgeRatingAttributesSchema):
movies: Annotated[
Optional[list["MovieSchema"]],
RelationshipInfo(
resource_type="movie",
many=True,
),
] = None


class AgeRatingCreateSchema(AgeRatingBaseSchema):
name: name_constrained


class AgeRatingUpdateSchema(AgeRatingBaseSchema):
name: Optional[name_constrained] = None
description: Optional[str] = None


class AgeRatingSchema(AgeRatingBaseSchema):
"""
Age Rating
"""
60 changes: 60 additions & 0 deletions examples/api_for_sqlalchemy/schemas/movie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from datetime import date
from typing import (
TYPE_CHECKING,
Annotated,
Optional,
)

from annotated_types import MaxLen, MinLen
from pydantic import ConfigDict

from fastapi_jsonapi.schema_base import BaseModel
from fastapi_jsonapi.types_metadata import RelationshipInfo

if TYPE_CHECKING:
from examples.api_for_sqlalchemy.schemas.age_rating import AgeRatingSchema

title_constrained = Annotated[
str,
MinLen(1),
MaxLen(120),
]


class MovieAttributesSchema(BaseModel):
model_config = ConfigDict(
from_attributes=True,
)
title: str
description: str
age_rating: Optional[str] = None


class MovieBaseSchema(MovieAttributesSchema):
age_rating_obj: Annotated[
Optional["AgeRatingSchema"],
RelationshipInfo(
resource_type="age-rating",
resource_id_example="PG-13",
id_field_name="name",
),
] = None


class MovieCreateSchema(MovieBaseSchema):
"""
Create
"""

title: title_constrained


class MovieUpdateSchema(MovieBaseSchema):
title: Optional[title_constrained] = None
description: Optional[str] = None
release_date: Optional[date] = None
duration: Optional[int] = None


class MovieSchema(MovieBaseSchema):
id: int
29 changes: 29 additions & 0 deletions examples/api_for_sqlalchemy/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

from .api.views_base import ViewBase
from .models import (
AgeRating,
Child,
Computer,
Movie,
Parent,
ParentToChildAssociation,
Post,
Expand All @@ -17,12 +19,18 @@
Workplace,
)
from .schemas import (
AgeRatingCreateSchema,
AgeRatingSchema,
AgeRatingUpdateSchema,
ChildInSchema,
ChildPatchSchema,
ChildSchema,
ComputerInSchema,
ComputerPatchSchema,
ComputerSchema,
MovieCreateSchema,
MovieSchema,
MovieUpdateSchema,
ParentInSchema,
ParentPatchSchema,
ParentSchema,
Expand Down Expand Up @@ -131,6 +139,27 @@ def add_routes(app: FastAPI):
schema_in_patch=WorkplacePatchSchema,
schema_in_post=WorkplaceInSchema,
)
builder.add_resource(
path="/age-ratings",
tags=["Age Ratings"],
resource_type="age-rating",
view=ViewBase,
model=AgeRating,
schema=AgeRatingSchema,
schema_in_post=AgeRatingCreateSchema,
schema_in_patch=AgeRatingUpdateSchema,
model_id_field_name="name",
)
builder.add_resource(
path="/movies",
tags=["Movie"],
resource_type="movie",
view=ViewBase,
model=Movie,
schema=MovieSchema,
schema_in_post=MovieCreateSchema,
schema_in_patch=MovieUpdateSchema,
)
builder.initialize()

atomic = AtomicOperations()
Expand Down
8 changes: 7 additions & 1 deletion fastapi_jsonapi/api/schemas.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from typing import Iterable, Optional, Type, Union

from pydantic import BaseModel
from fastapi import APIRouter
from pydantic import BaseModel, ConfigDict

from fastapi_jsonapi.data_typing import TypeModel, TypeSchema
from fastapi_jsonapi.views import Operation, ViewBase


class ResourceData(BaseModel):
model_config = ConfigDict(
arbitrary_types_allowed=True,
)

path: Union[str, list[str]]
router: Optional[APIRouter]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем?

tags: list[str]
view: Type[ViewBase]
model: Type[TypeModel]
Expand Down
2 changes: 2 additions & 0 deletions fastapi_jsonapi/data_layers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from fastapi_jsonapi.data_typing import TypeModel, TypeSchema
from fastapi_jsonapi.querystring import QueryStringManager
from fastapi_jsonapi.schema import BaseJSONAPIItemInSchema
from fastapi_jsonapi.storages import models_storage
from fastapi_jsonapi.views import RelationshipRequestInfo


Expand Down Expand Up @@ -51,6 +52,7 @@ def __init__(
self.disable_collection_count: bool = disable_collection_count
self.default_collection_count: int = default_collection_count
self.is_atomic = False
self.id_column_name = models_storage.get_model_id_field_name(resource_type)

async def atomic_start(self, previous_dl: Optional["BaseDataLayer"] = None):
self.is_atomic = True
Expand Down
3 changes: 2 additions & 1 deletion fastapi_jsonapi/data_layers/sqla/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ async def count(
cls,
session: AsyncSession,
stmt: Select,
id_field_name: str = "id",
) -> int:
stmt = select(func.count(distinct(column("id")))).select_from(stmt.subquery())
stmt = select(func.count(distinct(column(id_field_name)))).select_from(stmt.subquery())
return (await session.execute(stmt)).scalar_one()

@classmethod
Expand Down
1 change: 1 addition & 0 deletions fastapi_jsonapi/data_layers/sqla/orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ async def get_collection(
objects_count = await self._base_sql.count(
session=self.session,
stmt=query,
id_field_name=self.id_column_name,
)

collection = await self.after_get_collection(collection, qs, view_kwargs)
Expand Down
Loading