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

Paginate inner model field #361

Closed
davidbrochart opened this issue Sep 6, 2022 · 12 comments
Closed

Paginate inner model field #361

davidbrochart opened this issue Sep 6, 2022 · 12 comments
Assignees
Labels
question Further information is requested

Comments

@davidbrochart
Copy link

Great library, thanks!

How would you go about using it on an endpoint which doesn't directly return a sequence, but a model in which a field is the sequence to paginate, e.g.:

class ResponseType(BaseModel):
    other_data: int
    data_to_paginate: Sequence[str]
@uriyyo uriyyo self-assigned this Sep 6, 2022
@uriyyo uriyyo added the question Further information is requested label Sep 6, 2022
@uriyyo
Copy link
Owner

uriyyo commented Sep 6, 2022

@davidbrochart How JSON response should look like?

smth like this?

{
    "other_data": 1000000,
    "data_to_paginate": ["a", "b", "c"]
}

@davidbrochart
Copy link
Author

Yes, exactly.

@uriyyo
Copy link
Owner

uriyyo commented Sep 6, 2022

@davidbrochart It looks ugly now(

I will try to provide a better API for such purposes.

from string import ascii_lowercase
from typing import Sequence, Type, TypeVar, Optional, Generic, cast

from fastapi import FastAPI

from fastapi_pagination.bases import AbstractPage, AbstractParams
from fastapi_pagination import add_pagination, paginate, Params

app = FastAPI()

T = TypeVar("T")
P = TypeVar("P", bound=AbstractPage)


class CustomResponse(AbstractPage[T], Generic[T]):
    other_data: Optional[int] = None
    data_to_paginate: Sequence[T]

    __params_type__ = Params

    @classmethod
    def create(
        cls: Type[P],
        items: Sequence[T],
        total: int,
        params: AbstractParams,
    ) -> P:
        return cls(data_to_paginate=items)


DATA = [*ascii_lowercase]


@app.get("/", response_model=CustomResponse[str])
def route():
    response = cast(CustomResponse[str], paginate(DATA))

    response.other_data = 42  # Add additional data to custom response

    return response


add_pagination(app)


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app)

image

@uriyyo
Copy link
Owner

uriyyo commented Sep 6, 2022

@davidbrochart As for me ideal API should look like this:

from string import ascii_lowercase
from typing import Sequence, Type, TypeVar, Optional, Generic, Dict, Any

from fastapi import FastAPI

from fastapi_pagination.bases import AbstractPage, AbstractParams
from fastapi_pagination import add_pagination, paginate, Params

app = FastAPI()

T = TypeVar("T")
P = TypeVar("P", bound=AbstractPage)


class CustomResponse(AbstractPage[T], Generic[T]):
    other_data: Optional[int] = None
    data_to_paginate: Sequence[T]

    __params_type__ = Params

    @classmethod
    def create(
        cls: Type[P],
        items: Sequence[T],
        total: int,
        params: AbstractParams,
        additional_data: Optional[Dict[str, Any]] = None,
    ) -> P:
        return cls(
            data_to_paginate=items,
            **(additional_data or {}),
        )


DATA = [*ascii_lowercase]


@app.get("/", response_model=CustomResponse[str])
def route():
    return paginate(
        DATA,
        additional_data={
            "other_data": 42,
        },
    )


add_pagination(app)

if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app)

So basically you can add additional fields that will be passed when the response model will be instantiated:

    return paginate(
        DATA,
        additional_data={
            "other_data": 42,
        },
    )

What do you think about such API? Maybe you have other ideas?

@davidbrochart
Copy link
Author

My original ResponseType example was intentionally simple:

class ResponseType(BaseModel):
    other_data: int
    data_to_paginate: Sequence[str]

But actually my real use-case is more complex. Ideally this model could be as big as we want, and the data to paginate could even be nested deep inside, so we don't want to add back all the other data around the data to paginate "by hand".
It would be great if paginate() could return the same data structure, with the data to paginate replaced by the paginated data. Do you think it is possible?

@uriyyo
Copy link
Owner

uriyyo commented Sep 6, 2022

I think yes.

Could you please show how API should look like in your opinion?

@davidbrochart
Copy link
Author

I'm not sure, maybe something like this:

class CustomResponse(AbstractPage[T], Generic[T]):
    __root__: ResponseType

    __params_type__ = Params

    @classmethod
    def create(
        cls: Type[P],
        items: Sequence[T],
        total: int,
        params: AbstractParams,
    ) -> P:
        data = get_response()
        data.data_to_paginate = items
        return cls(__root__=data)

But I'm probably missing something.

@uriyyo
Copy link
Owner

uriyyo commented Sep 6, 2022

Thanks for your suggestion. I will try to think about a good API for such feature.

Currently, as a workaround you can access paginated items and pass them to your custom schema:

from string import ascii_lowercase
from typing import Sequence, cast

from fastapi import FastAPI, Depends
from pydantic import BaseModel

from fastapi_pagination import add_pagination, paginate, Params, Page

app = FastAPI()


class ResponseType(BaseModel):
    other_data: int
    data_to_paginate: Sequence[str]


DATA = [*ascii_lowercase]


@app.get("/", response_model=ResponseType)
def route(params: Params = Depends()):
    data_to_paginate = cast(Page[str], paginate(DATA, params)).items

    return ResponseType(
        other_data=42,
        data_to_paginate=data_to_paginate,
    )


add_pagination(app)

if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app)

@davidbrochart
Copy link
Author

Thanks, but I'm not sure I understand. This doesn't return a Page response, right? For instance, there is no link header.

@uriyyo
Copy link
Owner

uriyyo commented Nov 27, 2022

Hi @davidbrochart,

Now it should be easier to implement this feature. Example:

from typing import Any, TypeVar, Generic, Sequence, Type

from fastapi import FastAPI
from pydantic import BaseModel, Field

from fastapi_pagination import paginate, Params
from fastapi_pagination.api import pagination_items, add_pagination
from fastapi_pagination.bases import AbstractPage, AbstractParams

app = FastAPI()
add_pagination(app)

T = TypeVar("T")
C = TypeVar("C")


class PageWithInnerItems(AbstractPage[T], Generic[T]):
    __root__: T
    __params_type__ = Params

    @classmethod
    def create(
            cls: Type[C],
            items: Sequence[T],
            params: AbstractParams,
            **kwargs: Any,
    ) -> C:
        return cls(__root__=kwargs)


class ResponseType(BaseModel):
    other_data: int
    data_to_paginate: Sequence[str] = Field(default_factory=pagination_items)


@app.get(
    "/",
    response_model=PageWithInnerItems[ResponseType],
)
async def route() -> Any:
    data = [*range(100)]

    return paginate(data, additional_data={"other_data": 1000000})


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app)

@uriyyo
Copy link
Owner

uriyyo commented Dec 7, 2022

Hi @davidbrochart,
I am closing this issue, please reopen it if you have more questions.

@uriyyo uriyyo closed this as completed Dec 7, 2022
@davidbrochart
Copy link
Author

Thanks a lot for the work, I had to switch to something else but I will probably need these new features in the future.

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