Skip to content

Commit

Permalink
Fix pagination in Flask-SQLAlchemy
Browse files Browse the repository at this point in the history
As reported in #37, flask-restless-ng does not work with flask-sql-alchemy 3.0+
because it no longer supports keyword arguments passed as positional arguments.
Fixed and added tox tests for backward compatibility
  • Loading branch information
mrevutskyi committed Oct 23, 2022
1 parent 4cd0a05 commit 1cbb88b
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 6 deletions.
3 changes: 2 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ Changelog
Changes in Flask-Restless-NG
============================

Upcoming version:
Version 2.3.1:
-------------
- Fix for an incorrect error message
- Returns 500 instead 400 response code in case of serialization errors in POST requests
- Fix for pagination Flask-SQLAlchemy (now works also with 3.0+) (#37)

Version 2.3.0
------------
Expand Down
2 changes: 1 addition & 1 deletion flask_restless/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""
#: The current version of this extension.
__version__ = '2.3.0'
__version__ = '2.3.1'


# The following names are available as part of the public API for Flask-Restless-NG.
Expand Down
3 changes: 1 addition & 2 deletions flask_restless/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1497,8 +1497,7 @@ def _paginated(self, items, filters=None, sort=None):
# its built-in pagination. Otherwise, we need to manually
# compute the page numbers, the number of results, etc.
if hasattr(items, 'paginate'):
pagination = items.paginate(page_number, page_size,
error_out=False)
pagination = items.paginate(page=page_number, per_page=page_size, error_out=False)
num_results = pagination.total
first = 1
last = pagination.pages
Expand Down
1 change: 1 addition & 0 deletions requirements/test-cpython.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ psycopg2-binary

# mypy requires types-ast and it does not build in pypy
mypy
types-python-dateutil # for mypy
sqlalchemy2-stubs
2 changes: 2 additions & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
jsonschema
savalidation

PyMySQL # required after Flask_SQLAlchemy 3.0

# For testing PostgreSQL specific operations...
testing.postgresql

Expand Down
8 changes: 7 additions & 1 deletion tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# License version 3 and under the 3-clause BSD license. For more
# information, see LICENSE.AGPL and LICENSE.BSD.
"""Helper functions for unit tests."""
import json
import unittest
import uuid
from datetime import date
Expand All @@ -21,7 +22,6 @@

import jsonschema
from flask import Flask
from flask import json
from sqlalchemy import create_engine
from sqlalchemy import event
from sqlalchemy.dialects.postgresql import UUID
Expand Down Expand Up @@ -280,13 +280,19 @@ def setUp(self):
# read more like the tests for plain old SQLAlchemy.
self.db = SQLAlchemy(self.flaskapp)
self.session = self.db.session
self.client = self.flaskapp.test_client()

# 'Flask-SQLAlchemy > 3.0 requires app context for everything
self.app_context = self.flaskapp.app_context()
self.app_context.push()

def tearDown(self):
"""Drops all tables and unregisters Flask-SQLAlchemy session
signals.
"""
self.db.drop_all()
self.app_context.pop()
unregister_fsa_session_signals()


Expand Down
20 changes: 19 additions & 1 deletion tests/test_flask_sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ def setUp(self):

class Person(self.db.Model):
id = self.db.Column(self.db.Integer, primary_key=True)
team_id = self.db.Column(self.db.Integer, self.db.ForeignKey('team.id'))

class Team(self.db.Model):
id = self.db.Column(self.db.Integer, primary_key=True)

members = self.db.relationship('Person')

self.Person = Person
self.Team = Team
self.db.create_all()
self.manager = APIManager(self.flaskapp, session=self.db.session)
self.manager.create_api(self.Person, methods=['POST', 'DELETE'])
self.manager.create_api(self.Person, methods=['GET', 'POST', 'DELETE'])
self.manager.create_api(self.Team, methods=['GET'])

def test_create(self):
"""Tests for creating a resource."""
Expand All @@ -38,3 +46,13 @@ def test_delete(self):
response = self.app.delete('/api/person/1')
assert response.status_code == 204
assert self.Person.query.count() == 0

def test_get_many(self):
"""Tests pagination in Flask-SQLAlchemy"""
self.session.add(self.Team(id=1))
self.session.bulk_save_objects([
self.Person(id=i, team_id=1) for i in range(50)
])
self.session.commit()
response = self.app.get('/api/team/1/members')
assert response.status_code == 200
13 changes: 13 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ envlist =
py38
py38-lowest
py39
py39-fsqla3

[testenv]
deps =
-r requirements/test-cpython.txt
lowest: sqlalchemy==1.3.6
lowest: flask==1.0.1

# Flask 1.0.1 does not work with the newest versions
lowest: jinja2==2.11.3
lowest: itsdangerous==2.0.1
lowest: Werkzeug==1.0.1

lowest: flask-sqlalchemy==2.3.2

# Jinja needs `from markupsafe import soft_unicode`
lowest: MarkupSafe==2.0.1

fsqla3: flask-sqlalchemy>=3.0.0
commands =
pytest

0 comments on commit 1cbb88b

Please sign in to comment.