Skip to content

Commit 4af31ee

Browse files
committed
Add a ODATA_SERVER_MONGO_COUNT_MAX_TIME_MS setting
1 parent aedf7b8 commit 4af31ee

File tree

4 files changed

+46
-14
lines changed

4 files changed

+46
-14
lines changed

odata_server/settings.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# Copyright (c) 2024 Future Internet Consulting and Development Solutions S.L.
2+
13
import os
24

3-
MONGO_SEARCH_MAX_TIME_MS = int(os.getenv("MONGO_SEARCH_MAX_TIME_MS", "30000"))
5+
MONGO_COUNT_MAX_TIME_MS = int(
6+
os.getenv(
7+
"ODATA_SERVER_MONGO_COUNT_MAX_TIME_MS",
8+
os.getenv("ODATA_SERVER_MONGO_SEARCH_MAX_TIME_MS", "30000"),
9+
)
10+
)
11+
MONGO_SEARCH_MAX_TIME_MS = int(
12+
os.getenv("ODATA_SERVER_MONGO_SEARCH_MAX_TIME_MS", "30000")
13+
)

odata_server/utils/__init__.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import re
44

55
import abnf
6+
import pymongo.database
67
from bson.son import SON
78
from flask import abort, request, url_for
89

9-
from odata_server import edm
10+
from odata_server import edm, settings
1011

1112
from .common import crop_result, format_key_predicate
1213
from .flask import add_odata_annotations
@@ -148,9 +149,9 @@ def process_common_expr(tree, filters, entity_type, prefix, joinop="andExpr"):
148149
regex_literal = re.escape(parse_primitive_literal(args[1].children[0]))
149150
if methodExpr.name == "containsMethodCallExpr":
150151
filters[-1][field] = {
151-
"$regex": "(?!{})".format(regex_literal)
152-
if negation
153-
else regex_literal
152+
"$regex": (
153+
"(?!{})".format(regex_literal) if negation else regex_literal
154+
)
154155
}
155156
elif methodExpr.name == "startsWithMethodCallExpr":
156157
filters[-1][field] = {
@@ -398,7 +399,14 @@ def prepare_anonymous_result(result, RootEntitySet, expand_details, prefix):
398399
return expand_result(RootEntitySet, expand_details, croped_result, prefix=prefix)
399400

400401

401-
def get_collection(mongo, RootEntitySet, subject, prefers, filters=None, count=False):
402+
def get_collection(
403+
db: pymongo.database.Database,
404+
RootEntitySet,
405+
subject,
406+
prefers,
407+
filters=None,
408+
count=False,
409+
):
402410
qs = parse_qs(request.query_string)
403411
anonymous = not isinstance(subject, edm.EntitySet)
404412

@@ -440,7 +448,7 @@ def get_collection(mongo, RootEntitySet, subject, prefers, filters=None, count=F
440448
orderby = parse_orderby(qs.get("$orderby", ""))
441449

442450
# Get the results
443-
mongo_collection = mongo.get_collection(RootEntitySet.mongo_collection)
451+
mongo_collection = db.get_collection(RootEntitySet.mongo_collection)
444452
if prefix:
445453
seq_filter = {"Seq": filters.pop("Seq")} if "Seq" in filters else None
446454
pipeline = [
@@ -464,19 +472,29 @@ def get_collection(mongo, RootEntitySet, subject, prefers, filters=None, count=F
464472
pipeline.append({"$project": projection})
465473
pipeline.append({"$skip": offset})
466474
pipeline.append({"$limit": limit})
467-
results = mongo_collection.aggregate(pipeline)
475+
results = mongo_collection.aggregate(
476+
pipeline, maxTimeMS=settings.MONGO_SEARCH_MAX_TIME_MS
477+
)
468478
else:
469-
cursor = mongo_collection.find(filters, projection)
479+
cursor = mongo_collection.find(filters, projection).max_time_ms(
480+
settings.MONGO_SEARCH_MAX_TIME_MS
481+
)
470482
if len(orderby) > 0:
471483
cursor = cursor.sort(orderby)
472484
results = cursor.skip(offset).limit(limit)
473485

474486
if count:
475487
if prefix == "":
476-
count = mongo_collection.count_documents(filters)
488+
count = mongo_collection.count_documents(
489+
filters, maxTimeMS=settings.MONGO_COUNT_MAX_TIME_MS
490+
)
477491
else:
478492
basepipeline.append({"$count": "count"})
479-
result = tuple(mongo_collection.aggregate(basepipeline))
493+
result = tuple(
494+
mongo_collection.aggregate(
495+
basepipeline, maxTimeMS=settings.MONGO_COUNT_MAX_TIME_MS
496+
)
497+
)
480498
count = 0 if len(result) == 0 else result[0]["count"]
481499
odata_count = count
482500
else:

odata_server/utils/json.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def generate_collection_response(
4343
page_limit,
4444
prepare,
4545
odata_context,
46-
odata_count=None,
46+
odata_count: int | None = None,
4747
prepare_kwargs={},
4848
):
4949

tests/test_flask.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ def test_get_entity_collection_api_orderby(self):
257257
)
258258
mongo.reset_mock()
259259
response = self.app.get("/Products?$orderby={}".format(orderby_expr))
260-
mongo.get_collection().find().sort.assert_called_once_with(expected)
260+
mongo.get_collection().find().max_time_ms().sort.assert_called_once_with(
261+
expected
262+
)
261263
self.assertEqual(response.status_code, 200)
262264

263265
def test_get_entity_collection_api_empty_filter(self):
@@ -277,7 +279,9 @@ def test_get_entity_collection_api_basic_filters(self):
277279
)
278280
for label, filter_expr in test_data:
279281
with self.subTest(msg=label):
280-
mongo.get_collection().find().skip().limit.return_value = iter(())
282+
mongo.get_collection().find().max_time_ms().skip().limit.return_value = iter(
283+
()
284+
)
281285
response = self.app.get("/Products?$filter={}".format(filter_expr))
282286
self.assertEqual(response.status_code, 200)
283287
# force processing of the body generator

0 commit comments

Comments
 (0)