diff --git a/CHANGES.rst b/CHANGES.rst index 7fd46323..35d8d43a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,15 @@ Unreleased - Pass extra keyword arguments from ``get_or_404`` to ``session.get``. :issue:`1149` +Version 3.0.5 +------------- + +Released 2023-06-21 + +- ``Pagination.next()`` enforces ``max_per_page``. :issue:`1201` +- Improve type hint for ``get_or_404`` return value to be non-optional. :pr:`1226` + + Version 3.0.4 ------------- diff --git a/src/flask_sqlalchemy/extension.py b/src/flask_sqlalchemy/extension.py index 20fd564b..27b0fd21 100644 --- a/src/flask_sqlalchemy/extension.py +++ b/src/flask_sqlalchemy/extension.py @@ -621,7 +621,7 @@ def get_or_404( *, description: str | None = None, **kwargs: t.Any, - ) -> t.Optional[_O]: + ) -> _O: """Like :meth:`session.get() ` but aborts with a ``404 Not Found`` error instead of returning ``None``. diff --git a/src/flask_sqlalchemy/pagination.py b/src/flask_sqlalchemy/pagination.py index 5957de7f..3d49d6e0 100644 --- a/src/flask_sqlalchemy/pagination.py +++ b/src/flask_sqlalchemy/pagination.py @@ -66,6 +66,9 @@ def __init__( self.per_page: int = per_page """The maximum number of items on a page.""" + self.max_per_page: int | None = max_per_page + """The maximum allowed value for ``per_page``.""" + items = self._query_items() if not items and page != 1 and error_out: @@ -249,6 +252,7 @@ def next(self, *, error_out: bool = False) -> Pagination: p = type(self)( page=self.page + 1, per_page=self.per_page, + max_per_page=self.max_per_page, error_out=error_out, count=False, **self._query_args, diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 64258cc9..14e24a9e 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -145,7 +145,7 @@ def __call__( @pytest.fixture def paginate(app: Flask, db: SQLAlchemy, Todo: t.Any) -> _PaginateCallable: with app.app_context(): - for i in range(1, 101): + for i in range(1, 251): db.session.add(Todo(title=f"task {i}")) db.session.commit() @@ -158,8 +158,8 @@ def test_paginate(paginate: _PaginateCallable) -> None: assert p.page == 1 assert p.per_page == 20 assert len(p.items) == 20 - assert p.total == 100 - assert p.pages == 5 + assert p.total == 250 + assert p.pages == 13 def test_paginate_qs(paginate: _PaginateCallable) -> None: @@ -173,6 +173,16 @@ def test_paginate_max(paginate: _PaginateCallable) -> None: assert p.per_page == 50 +@pytest.mark.usefixtures("app_ctx") +def test_next_page_size(paginate: _PaginateCallable) -> None: + p = paginate(per_page=110, max_per_page=250) + assert p.page == 1 + assert p.per_page == 110 + p = p.next() + assert p.page == 2 + assert p.per_page == 110 + + def test_no_count(paginate: _PaginateCallable) -> None: p = paginate(count=False) assert p.total is None diff --git a/tests/test_view_query.py b/tests/test_view_query.py index 7fe6164a..ddbf1bbc 100644 --- a/tests/test_view_query.py +++ b/tests/test_view_query.py @@ -70,10 +70,11 @@ class Quiz(db.Model): db.create_all() - item: Quiz = Quiz() + item: Quiz = Quiz(topic="Python") db.session.add(item) db.session.commit() result = db.get_or_404(Quiz, 1) + assert result.topic == "Python" assert result is item if hasattr(t, "assert_type"): t.assert_type(result, Quiz)