Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use pydantic model on UseAdditionalFields ? #1452

Open
Kavan72 opened this issue Jan 29, 2025 · 6 comments
Open

How to use pydantic model on UseAdditionalFields ? #1452

Kavan72 opened this issue Jan 29, 2025 · 6 comments
Assignees
Labels
question Further information is requested

Comments

@Kavan72
Copy link

Kavan72 commented Jan 29, 2025

Discussed in #1451

Originally posted by Kavan72 January 29, 2025
I've my pydantic model and i want to use on UseAdditionalFields, like this

CustomPage = CustomizedPage[
    Page[T],
    UseAdditionalFields(
        extra=MyCustomModel
    ),
]

My code for custom page

from collections.abc import Sequence
from math import ceil
from typing import Any, TypeVar

from fastapi_pagination import paginate
from fastapi_pagination.bases import AbstractParams, BasePage, RawParams
from pydantic import BaseModel, Field

T = TypeVar("T")


class CustomParams(BaseModel, AbstractParams):
    page: int = Field(1, ge=1, description="Page number")
    page_size: int = Field(10, ge=1, le=1000, alias="pageSize", description="Page size")

    def to_raw_params(self) -> RawParams:
        return RawParams(
            limit=self.page_size if self.page_size is not None else None,
            offset=self.page_size * (self.page - 1)
            if self.page is not None and self.page_size is not None
            else None,
        )

    @classmethod
    def get_alias(cls, field_name: str):
        field = cls.__pydantic_fields__.get(field_name)

        if field is None:
            raise AttributeError(f"Field {field_name} is not defined")

        return field.alias or field_name


class CustomPage(BasePage):
    __qualname__ = "Page"

    page: int = Field(strict=True, ge=1, alias=CustomParams.get_alias("page"))
    page_size: int = Field(strict=True, ge=1, alias=CustomParams.get_alias("page_size"))
    pages: int = Field(strict=True, ge=0)

    @classmethod
    def create(
        cls,
        items: Sequence[T],
        params: CustomParams,
        *,
        total: int | None = None,
        **kwargs: Any,
    ):
        page_size = params.page_size if params.page_size is not None else (total or None)
        page = params.page if params.page is not None else 1

        if page_size in {0, None}:
            pages = 0
        elif total is not None:
            pages = ceil(total / page_size)
        else:
            pages = None

        return cls.model_validate(
            {
                "total": total,
                "items": items,
                "page": page,
                "page_size": page_size,
                "pages": pages,
                **kwargs,
            },
            from_attributes=True,
        )

class UserCount(BaseModel):
    active: int
    invited: int
    banned: int
    rejected: int

class Custom(CustomPage):
    __qualname__ = "Page"
    extra: UserCount

@app.get("/nums")
async def get_nums(is_admin: bool = False) -> Custom[int]:
    return paginate(
        range(1_000),
        params=CustomParams(page=1, page_size=10),
        additional_data={
            "extra": UserCount(active=1, banned=2, rejected=3, invited=4),
        }
    )

Since I’m not using add_pagination(app), FastAPI Pagination can’t find my custom page class. Is there any way to pass the class into the paginate method so it can use my custom page? Sorry, but I don’t prefer using add_pagination or set_page.

@Kavan72
Copy link
Author

Kavan72 commented Jan 30, 2025

@uriyyo, any suggestions? I’m happy to contribute to this.

@uriyyo
Copy link
Owner

uriyyo commented Jan 30, 2025

Sorry, but I don’t prefer using add_pagination or set_page.

@Kavan72 But it's kinda required to use, otherwise you will use default Page class.

Is there any reason why you don't want to use add_pagination or set_page?

There is also pagination_ctx function, you can take a look at it here - https://uriyyo-fastapi-pagination.netlify.app/learn/tutorial_advanced/low_level_api/#pagination_ctx

@Kavan72
Copy link
Author

Kavan72 commented Jan 31, 2025

Sorry, but I don’t prefer using add_pagination or set_page.

@Kavan72 But it's kinda required to use, otherwise you will use default Page class.

Is there any reason why you don't want to use add_pagination or set_page?

There is also pagination_ctx function, you can take a look at it here - https://uriyyo-fastapi-pagination.netlify.app/learn/tutorial_advanced/low_level_api/#pagination_ctx

Actually, I don’t like monkey patching, which is why I’m avoiding this. I also have my own OpenAPI build function, and I don’t want any library modifying my OpenAPI schema. While pagination_ctx is fine, it’s not an ideal solution. You mentioned that I’m Depends on this, but that’s not accurate. A custom page is a response class, not something that relies on a dependency.

@uriyyo uriyyo self-assigned this Jan 31, 2025
@uriyyo uriyyo added the question Further information is requested label Jan 31, 2025
@uriyyo
Copy link
Owner

uriyyo commented Feb 2, 2025

You mentioned that I’m Depends on this, but that’s not accurate. A custom page is a response class, not something that relies on a dependency.

Sorry, I guess my message was not clear.

When you call paginate function it depends on a value of contextvar that holds page class that should be used.
In order to use correct page class you need to set it, it can be done using set_page, pagination_ctx or add_pagination functions.

add_pagination does some monkey-patching but both set_page and pagination_ctx don't, they just change value of context-var.

You can see it here:

def set_page(page: type[AbstractPage[Any]]) -> AbstractContextManager[None]:
return _ctx_var_with_reset(_page_val, page)

@Kavan72
Copy link
Author

Kavan72 commented Feb 8, 2025

You mentioned that I’m Depends on this, but that’s not accurate. A custom page is a response class, not something that relies on a dependency.

Sorry, I guess my message was not clear.

When you call paginate function it depends on a value of contextvar that holds page class that should be used. In order to use correct page class you need to set it, it can be done using set_page, pagination_ctx or add_pagination functions.

add_pagination does some monkey-patching but both set_page and pagination_ctx don't, they just change value of context-var.

You can see it here:

fastapi-pagination/fastapi_pagination/api.py

Lines 135 to 136 in f867c15

def set_page(page: type[AbstractPage[Any]]) -> AbstractContextManager[None]:
return _ctx_var_with_reset(_page_val, page)

Hey @uriyyo,

I understand what you’re trying to say. My idea is that if someone comes from a different framework or language, using pagination_ctx with Depends feels like a dirty solution. Honestly, I have no objections to your suggestion, and since you’re the owner of this library, you have full authority to decide what to do. My suggestion is to allow users to pass a CustomPage to the pagination function, which will use that class if provided; otherwise, it will fall back to the default. 🙂

@Kavan72
Copy link
Author

Kavan72 commented Feb 12, 2025

@uriyyo hey any update 😅, if you are okay then I’m happy to contribute to this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants