Skip to content

Commit beb8415

Browse files
abhinand-cmak626
andauthored
Fix Performance Issues (#228)
* fix: union type naming for mongo_fields having inner mongo_field eg: field_1 = ListField(GenericLazyReferenceField(choices=[X1,X2])) generates type ModelNoneUnionType This fix will make it ModelField1UnionType * fix: union type naming convention AsyncUnionType -> UnionType * fix: wrong sync to async wrapper being used * fix: await not called on len * fix: Blocking code removed, and reduce has_next_page DB calls # TODO: cleanup logics * fix: lint issues * fix: Bump Minor Version Bump from released version number * fix: Lint issue * fix: handle all cases, handle invalid info --------- Co-authored-by: M Aswin Kishore <[email protected]>
1 parent 2a094fc commit beb8415

File tree

8 files changed

+171
-129
lines changed

8 files changed

+171
-129
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ To create a GraphQL schema and async executor; for it you simply have to write t
7474
import graphene
7575

7676
from graphene_mongo import AsyncMongoengineObjectType
77-
from asgiref.sync import sync_to_async
77+
from graphene_mongo.utils import sync_to_async
7878
from concurrent.futures import ThreadPoolExecutor
7979

8080
from .models import User as UserModel

graphene_mongo/converter.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,13 @@ async def reference_resolver_async(root, *args, **kwargs):
285285
)
286286
tasks.append(task)
287287
result = await asyncio.gather(*tasks)
288-
result = [each[0] for each in result]
289-
result_object_ids = list()
290-
for each in result:
291-
result_object_ids.append(each.id)
288+
result_object = {}
289+
for items in result:
290+
for item in items:
291+
result_object[item.id] = item
292292
ordered_result = list()
293293
for each in to_resolve_object_ids:
294-
ordered_result.append(result[result_object_ids.index(each)])
294+
ordered_result.append(result_object[each])
295295
return ordered_result
296296
return None
297297

@@ -350,10 +350,19 @@ def convert_field_to_union(field, registry=None, executor: ExecutorEnum = Execut
350350
if len(_types) == 0:
351351
return None
352352

353-
name = (
354-
to_camel_case("{}_{}".format(field._owner_document.__name__, field.db_field)) + "UnionType"
355-
if ExecutorEnum.SYNC
356-
else "AsyncUnionType"
353+
field_name = field.db_field
354+
if field_name is None:
355+
# Get db_field name from parent mongo_field
356+
for db_field_name, _mongo_parent_field in field.owner_document._fields.items():
357+
if hasattr(_mongo_parent_field, "field") and _mongo_parent_field.field == field:
358+
field_name = db_field_name
359+
break
360+
361+
name = to_camel_case(
362+
"{}_{}_union_type".format(
363+
field._owner_document.__name__,
364+
field_name,
365+
)
357366
)
358367
Meta = type("Meta", (object,), {"types": tuple(_types)})
359368
_union = type(name, (graphene.Union,), {"Meta": Meta})

graphene_mongo/fields_async.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
find_skip_and_limit,
2727
get_query_fields,
2828
sync_to_async,
29+
has_page_info,
2930
)
3031

3132
PYMONGO_VERSION = tuple(pymongo.version_tuple[:2])
@@ -99,6 +100,7 @@ async def default_resolver(self, _root, info, required_fields=None, resolved=Non
99100
before = args.pop("before", None)
100101
if before:
101102
before = cursor_to_offset(before)
103+
requires_page_info = has_page_info(info)
102104
has_next_page = False
103105

104106
if resolved is not None:
@@ -111,7 +113,7 @@ async def default_resolver(self, _root, info, required_fields=None, resolved=Non
111113
else:
112114
count = None
113115
except OperationFailure:
114-
count = len(items)
116+
count = await sync_to_async(len)(items)
115117
else:
116118
count = len(items)
117119

@@ -128,7 +130,14 @@ async def default_resolver(self, _root, info, required_fields=None, resolved=Non
128130
)
129131
items = await sync_to_async(_base_query.limit)(limit)
130132
has_next_page = (
131-
len(await sync_to_async(_base_query.skip(limit).only("id").limit)(1)) != 0
133+
(
134+
await sync_to_async(len)(
135+
await sync_to_async(_base_query.skip(limit).only("id").limit)(1)
136+
)
137+
!= 0
138+
)
139+
if requires_page_info
140+
else False
132141
)
133142
elif skip:
134143
items = await sync_to_async(items.skip)(skip)
@@ -137,11 +146,12 @@ async def default_resolver(self, _root, info, required_fields=None, resolved=Non
137146
if reverse:
138147
_base_query = items[::-1]
139148
items = _base_query[skip : skip + limit]
140-
has_next_page = (skip + limit) < len(_base_query)
141149
else:
142150
_base_query = items
143151
items = items[skip : skip + limit]
144-
has_next_page = (skip + limit) < len(_base_query)
152+
has_next_page = (
153+
(skip + limit) < len(_base_query) if requires_page_info else False
154+
)
145155
elif skip:
146156
items = items[skip:]
147157
iterables = await sync_to_async(list)(items)
@@ -234,24 +244,23 @@ async def default_resolver(self, _root, info, required_fields=None, resolved=Non
234244
if reverse:
235245
_base_query = items[::-1]
236246
items = _base_query[skip : skip + limit]
237-
has_next_page = (skip + limit) < len(_base_query)
238247
else:
239248
_base_query = items
240249
items = items[skip : skip + limit]
241-
has_next_page = (skip + limit) < len(_base_query)
250+
has_next_page = (skip + limit) < len(_base_query) if requires_page_info else False
242251
elif skip:
243252
items = items[skip:]
244253
iterables = items
245254
iterables = await sync_to_async(list)(iterables)
246255
list_length = len(iterables)
247256

248-
if count:
257+
if requires_page_info and count:
249258
has_next_page = (
250259
True
251260
if (0 if limit is None else limit) + (0 if skip is None else skip) < count
252261
else False
253262
)
254-
has_previous_page = True if skip else False
263+
has_previous_page = True if requires_page_info and skip else False
255264

256265
if reverse:
257266
iterables = await sync_to_async(list)(iterables)

graphene_mongo/types.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
from collections import OrderedDict
2-
from concurrent.futures import ThreadPoolExecutor
32

43
import graphene
54
import mongoengine
6-
from asgiref.sync import sync_to_async
75
from graphene.relay import Connection, Node
8-
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
96
from graphene.types.inputobjecttype import InputObjectType, InputObjectTypeOptions
107
from graphene.types.interface import Interface, InterfaceOptions
8+
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
119
from graphene.types.utils import yank_fields_from_attrs
1210
from graphene.utils.str_converters import to_snake_case
13-
from graphene_mongo import MongoengineConnectionField
1411

12+
from graphene_mongo import MongoengineConnectionField
1513
from .converter import convert_mongoengine_field
1614
from .registry import Registry, get_global_registry, get_inputs_registry
17-
from .utils import get_model_fields, is_valid_mongoengine_model, get_query_fields, ExecutorEnum
15+
from .utils import (
16+
ExecutorEnum,
17+
get_model_fields,
18+
get_query_fields,
19+
is_valid_mongoengine_model,
20+
sync_to_async,
21+
)
1822

1923

2024
def construct_fields(
@@ -241,8 +245,6 @@ async def get_node(cls, info, id):
241245
required_fields = list(set(required_fields))
242246
return await sync_to_async(
243247
cls._meta.model.objects.no_dereference().only(*required_fields).get,
244-
thread_sensitive=False,
245-
executor=ThreadPoolExecutor(),
246248
)(pk=id)
247249

248250
def resolve_id(self, info):

graphene_mongo/types_async.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import graphene
22
import mongoengine
3-
from asgiref.sync import sync_to_async
43
from graphene import InputObjectType
54
from graphene.relay import Connection, Node
65
from graphene.types.interface import Interface, InterfaceOptions
@@ -11,7 +10,7 @@
1110
from graphene_mongo import AsyncMongoengineConnectionField
1211
from .registry import Registry, get_global_async_registry, get_inputs_async_registry
1312
from .types import construct_fields, construct_self_referenced_fields
14-
from .utils import ExecutorEnum, get_query_fields, is_valid_mongoengine_model
13+
from .utils import ExecutorEnum, get_query_fields, is_valid_mongoengine_model, sync_to_async
1514

1615

1716
def create_graphene_generic_class_async(object_type, option_type):

graphene_mongo/utils.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from typing import Any, Callable, Union
88

99
import mongoengine
10-
from asgiref.sync import SyncToAsync, sync_to_async as asgiref_sync_to_async
10+
from asgiref.sync import sync_to_async as asgiref_sync_to_async
11+
from asgiref.sync import SyncToAsync
1112
from graphene import Node
1213
from graphene.utils.trim_docstring import trim_docstring
1314
from graphql import FieldNode
@@ -173,6 +174,28 @@ def get_query_fields(info):
173174
return query
174175

175176

177+
def has_page_info(info):
178+
"""A convenience function to call collect_query_fields with info
179+
for retrieving if page_info details are required
180+
181+
Args:
182+
info (ResolveInfo)
183+
184+
Returns:
185+
bool: True if it received pageinfo
186+
"""
187+
188+
fragments = {}
189+
if not info:
190+
return True # Returning True if invalid info is provided
191+
node = ast_to_dict(info.field_nodes[0])
192+
for name, value in info.fragments.items():
193+
fragments[name] = ast_to_dict(value)
194+
195+
query = collect_query_fields(node, fragments)
196+
return next((True for x in query.keys() if x.lower() == "pageinfo"), False)
197+
198+
176199
def ast_to_dict(node, include_loc=False):
177200
if isinstance(node, FieldNode):
178201
d = {"kind": node.__class__.__name__}

0 commit comments

Comments
 (0)