Bug with empty sequences
In the case our pagination returns an empty sequence, the execution will throw:
Traceback (most recent call last):
File "/workspaces/allegro-api/./allegro_api/util/routing.py", line 37, in wrapper
return await func(*args, **kwargs)
File "/workspaces/allegro-api/./allegro_api/routers/content.py", line 157, in get_content_entries
return paginate(
File "/workspaces/allegro-api/.venv/lib/python3.10/site-packages/fastapi_pagination/paginator.py", line 28, in paginate
return create_page(
File "/workspaces/allegro-api/.venv/lib/python3.10/site-packages/fastapi_pagination/api.py", line 180, in create_page
return _page_val.get().create(items, **kwargs)
File "/workspaces/allegro-api/.venv/lib/python3.10/site-packages/fastapi_pagination/default.py", line 65, in create
pages = ceil(total / size) if total is not None else None
ZeroDivisionError: division by zero
This is due to line #65:
pages = ceil(total / size) if total is not None else None
Full method:
class Page(BasePage[T], Generic[T]):
page: Optional[GreaterEqualOne]
size: Optional[GreaterEqualOne]
pages: Optional[GreaterEqualZero] = None
__params_type__ = Params
@classmethod
def create(
cls,
items: Sequence[T],
params: AbstractParams,
*,
total: Optional[int] = None,
**kwargs: Any,
) -> Page[T]:
if not isinstance(params, Params):
raise TypeError("Page should be used with Params")
size = params.size if params.size is not None else total
page = params.page if params.page is not None else 1
pages = ceil(total / size) if total is not None else None
return create_pydantic_model(
cls,
total=total,
items=items,
page=page,
size=size,
pages=pages,
**kwargs,
)
There should be guardrails in place for this.
if size == 0:
pages = 0
else:
pages = ceil(total / size) if total is not None else None
Hi @alexandreczg,
New version 0.12.14 has been released, this issue should be fixed.
Hi @uriyyo , thanks for the quick action. I'm still facing an error with empty sequences.
Traceback (most recent call last):
File "/workspaces/allegro-api/./allegro_api/util/routing.py", line 37, in wrapper
return await func(*args, **kwargs)
File "/workspaces/allegro-api/./allegro_api/routers/v1/packages.py", line 304, in get_package_entries
return paginate(
File "/workspaces/allegro-api/.venv/lib/python3.10/site-packages/fastapi_pagination/paginator.py", line 28, in paginate
return create_page(
File "/workspaces/allegro-api/.venv/lib/python3.10/site-packages/fastapi_pagination/api.py", line 180, in create_page
return _page_val.get().create(items, **kwargs)
File "/workspaces/allegro-api/.venv/lib/python3.10/site-packages/fastapi_pagination/default.py", line 73, in create
return create_pydantic_model(
File "/workspaces/allegro-api/.venv/lib/python3.10/site-packages/fastapi_pagination/utils.py", line 151, in create_pydantic_model
return model_cls(**kwargs)
File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for CustomizedPage[Package]
size
ensure this value is greater than or equal to 1 (type=value_error.number.not_ge; limit_value=1)
I think the size parameter on the customized page model needs to accept sizes of 0.
For what is worth, this is my custom paginate implementation that works:
def paginate(
sequence: Sequence[T],
params: Optional[AbstractParams] = None,
length_function: Callable[[Sequence[T]], int] = len,
*,
transformer: Optional[SyncItemsTransformer] = None,
additional_data: Optional[AdditionalData] = None,
) -> Any:
check_installed_extensions()
params, raw_params = verify_params(params, "limit-offset")
items = sequence[raw_params.as_slice()]
t_items = apply_items_transformer(items, transformer)
length = length_function(sequence)
length = length if length > 0 else None
return create_page(
t_items,
total=length if raw_params.include_total else None,
params=params,
**(additional_data or {}),
)
I get the following schema on empty sequences:
{
"items": [],
"total": null,
"page": 1,
"size": null,
"pages": null
}
I guess you need to remove size constraints from size from your custom page.
Could you please show how CustomizedPage definition looks like?
it's just the default page with optional parameters
from typing import Any, Callable, Optional, Sequence, TypeVar
from fastapi_pagination import Page
from fastapi_pagination.api import apply_items_transformer, create_page
from fastapi_pagination.bases import AbstractParams
from fastapi_pagination.default import OptionalParams
from fastapi_pagination.types import AdditionalData, SyncItemsTransformer
from fastapi_pagination.utils import check_installed_extensions, verify_params
T = TypeVar("T")
PageWithOptionalParams = Page.with_params(
OptionalParams
) # https://github.com/uriyyo/fastapi-pagination/discussions/542
# The paginate method will not work if sequence is empty.
# Below is a rewrite of the paginate method that accepts empty sequences.
# Tracking bug: https://github.com/uriyyo/fastapi-pagination/issues/958
def paginate(
sequence: Sequence[T],
params: Optional[AbstractParams] = None,
length_function: Callable[[Sequence[T]], int] = len,
*,
transformer: Optional[SyncItemsTransformer] = None,
additional_data: Optional[AdditionalData] = None,
) -> Any:
check_installed_extensions()
params, raw_params = verify_params(params, "limit-offset")
items = sequence[raw_params.as_slice()]
t_items = apply_items_transformer(items, transformer)
length = length_function(sequence)
length = length if length > 0 else None
return create_page(
t_items,
total=length if raw_params.include_total else None,
params=params,
**(additional_data or {}),
)
I have the same issue.
It works fine when there are records in database filtered by the query.
sql_paginate(db, query, params=OptionalParams( page=params.page, size=params.size))
But if there is no records, it will fail, the same error as above.
Hi @whatoeat2night @alexandreczg I am planing to release the fix tomorrow.
Hi @whatoeat2night @alexandreczg
New version 0.12.15 has been released, and this issue should be fixed
@whatoeat2night @alexandreczg Any updates? Can I close this issue?
@uriyyo I can confirm it is now working. Thank you for your dedication.
@alexandreczg Thanks for your feedback, I am closing this issue