Skip to content

Commit f345b0d

Browse files
authored
Merge pull request #4 from mahenzon/feature/disable-collection-count
Feature/disable collection count
2 parents 4ec2077 + 0e77310 commit f345b0d

File tree

4 files changed

+103
-3
lines changed

4 files changed

+103
-3
lines changed

flask_rest_jsonapi/data_layers/alchemy.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,37 @@ def __init__(self, kwargs):
5050
f"You must provide a model in data_layer_kwargs to use sqlalchemy data layer in {self.resource.__name__}"
5151
)
5252

53+
self.disable_collection_count: bool = False
54+
self.default_collection_count: int = -1
55+
56+
def post_init(self):
57+
"""
58+
Checking some props here
59+
:return:
60+
"""
61+
if self.resource is None:
62+
# if working outside the resource, it's not assigned here
63+
return
64+
65+
if not hasattr(self.resource, "disable_collection_count") or self.resource.disable_collection_count is False:
66+
return
67+
68+
params = self.resource.disable_collection_count
69+
70+
if isinstance(params, (bool, int)):
71+
self.disable_collection_count = bool(params)
72+
73+
if isinstance(params, (tuple, list)):
74+
try:
75+
self.disable_collection_count, self.default_collection_count = params
76+
except ValueError:
77+
raise ValueError(
78+
"Resource's attribute `disable_collection_count` "
79+
"has to be bool or list/tuple with exactly 2 values!\n"
80+
"For example `disable_collection_count = (True, 999)`"
81+
)
82+
# just ignoring other types, we don't know how to process them
83+
5384
def create_object(self, data, view_kwargs):
5485
"""Create an object through sqlalchemy
5586
@@ -125,7 +156,7 @@ def get_object(self, view_kwargs, qs=None):
125156

126157
query = self.retrieve_object_query(view_kwargs, filter_field, filter_value)
127158

128-
if hasattr(self, "resource"):
159+
if self.resource is not None:
129160
for i_plugins in self.resource.plugins:
130161
try:
131162
query = i_plugins.data_layer_get_object_update_query(
@@ -146,6 +177,18 @@ def get_object(self, view_kwargs, qs=None):
146177

147178
return obj
148179

180+
def get_collection_count(self, query, qs, view_kwargs) -> int:
181+
"""
182+
:param query: SQLAlchemy query
183+
:param qs: QueryString
184+
:param view_kwargs: view kwargs
185+
:return:
186+
"""
187+
if self.disable_collection_count is True:
188+
return self.default_collection_count
189+
190+
return query.count()
191+
149192
def get_collection(self, qs, view_kwargs):
150193
"""Retrieve a collection of objects through sqlalchemy
151194
@@ -174,7 +217,7 @@ def get_collection(self, qs, view_kwargs):
174217
if qs.sorting:
175218
query = self.sort_query(query, qs.sorting)
176219

177-
object_count = query.count()
220+
objects_count = self.get_collection_count(query, qs, view_kwargs)
178221

179222
if getattr(self, "eagerload_includes", True):
180223
query = self.eagerload_includes(query, qs)
@@ -185,7 +228,7 @@ def get_collection(self, qs, view_kwargs):
185228

186229
collection = self.after_get_collection(collection, qs, view_kwargs)
187230

188-
return object_count, collection
231+
return objects_count, collection
189232

190233
def update_object(self, obj, data, view_kwargs):
191234
"""Update an object through sqlalchemy

flask_rest_jsonapi/data_layers/base.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"""
66

77
import types
8+
from typing import TYPE_CHECKING, Optional
9+
10+
if TYPE_CHECKING:
11+
from flask_rest_jsonapi.resource import Resource
812

913

1014
class BaseDataLayer:
@@ -38,6 +42,11 @@ def __init__(self, kwargs):
3842
3943
:param dict kwargs: information about data layer instance
4044
"""
45+
46+
# initing this attribute here in the first place
47+
# because it can be easily overridden by kwargs below
48+
self.resource: Optional['Resource'] = None
49+
4150
if kwargs.get("methods") is not None:
4251
self.bound_rewritable_methods(kwargs["methods"])
4352
kwargs.pop("methods")
@@ -47,6 +56,17 @@ def __init__(self, kwargs):
4756
for key, value in kwargs.items():
4857
setattr(self, key, value)
4958

59+
def post_init(self):
60+
"""
61+
Post init stage.
62+
63+
At this moment self.resource is already defined
64+
and the layer can do any post init stuff here
65+
66+
NOTE that the data layer is inited for each request
67+
:return:
68+
"""
69+
5070
def create_object(self, data, view_kwargs):
5171
"""Create an object
5272

flask_rest_jsonapi/resource.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def __new__(cls):
5555
"""Constructor of a resource instance"""
5656
if hasattr(cls, "_data_layer"):
5757
cls._data_layer.resource = cls
58+
cls._data_layer.post_init()
5859

5960
return super().__new__(cls)
6061

tests/test_sqlalchemy_data_layer.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,23 @@ class ComputerList(ResourceList):
437437
yield ComputerList
438438

439439

440+
@pytest.fixture(scope="module")
441+
def fixed_count_for_collection_count():
442+
return 42
443+
444+
445+
@pytest.fixture(scope="module")
446+
def computer_list_resource_with_disable_collection_count(
447+
session, computer_model, computer_schema, fixed_count_for_collection_count
448+
):
449+
class ComputerList(ResourceList):
450+
disable_collection_count = True, fixed_count_for_collection_count
451+
schema = computer_schema
452+
data_layer = {"model": computer_model, "session": session}
453+
454+
yield ComputerList
455+
456+
440457
@pytest.fixture(scope="module")
441458
def computer_detail(session, computer_model, dummy_decorator, computer_schema):
442459
class ComputerDetail(ResourceDetail):
@@ -496,6 +513,7 @@ def register_routes(
496513
person_list_without_schema,
497514
computer_list,
498515
computer_detail,
516+
computer_list_resource_with_disable_collection_count,
499517
computer_owner,
500518
string_json_attribute_person_detail,
501519
string_json_attribute_person_list,
@@ -510,6 +528,12 @@ def register_routes(
510528
api.route(person_list_raise_exception, "person_list_exception", "/persons_exception")
511529
api.route(person_list_response, "person_list_response", "/persons_response")
512530
api.route(person_list_without_schema, "person_list_without_schema", "/persons_without_schema")
531+
api.route(
532+
computer_list_resource_with_disable_collection_count,
533+
"computer_list_with_disabled_count",
534+
"/computers_with_disabled_count",
535+
"/persons/<int:person_id>/computers_with_disabled_count",
536+
)
513537
api.route(computer_list, "computer_list", "/computers", "/persons/<int:person_id>/computers")
514538
api.route(computer_detail, "computer_detail", "/computers/<int:id>")
515539
api.route(computer_owner, "computer_owner", "/computers/<int:id>/relationships/owner")
@@ -1684,3 +1708,15 @@ def test_relationship_containing_hyphens(client, register_routes, person_compute
16841708
f"/persons/{person.person_id}/relationships/computers-owned", content_type="application/vnd.api+json"
16851709
)
16861710
assert response.status_code == 200
1711+
1712+
1713+
def test__sqlalchemy_data_layer__disable_collection_count(client, fixed_count_for_collection_count):
1714+
"""
1715+
:param client:
1716+
:param fixed_count_for_collection_count:
1717+
:return:
1718+
"""
1719+
response = client.get(f"/computers_with_disabled_count", content_type="application/vnd.api+json")
1720+
assert response.status_code == 200
1721+
assert response.json
1722+
assert response.json["meta"]["count"] == fixed_count_for_collection_count

0 commit comments

Comments
 (0)