|
| 1 | +import functools |
| 2 | +import inspect |
1 | 3 | from collections import OrderedDict
|
2 | 4 | from functools import singledispatch, wraps
|
3 | 5 |
|
|
25 | 27 | )
|
26 | 28 | from graphene.types.json import JSONString
|
27 | 29 | from graphene.types.scalars import BigInt
|
| 30 | +from graphene.types.resolver import get_default_resolver |
28 | 31 | from graphene.utils.str_converters import to_camel_case
|
29 | 32 | from graphql import GraphQLError
|
30 | 33 |
|
@@ -324,13 +327,54 @@ def wrap_resolve(self, parent_resolver):
|
324 | 327 | resolver = super().wrap_resolve(parent_resolver)
|
325 | 328 |
|
326 | 329 | def custom_resolver(root, info, **args):
|
327 |
| - fk_obj = resolver(root, info, **args) |
328 |
| - if not isinstance(fk_obj, model): |
329 |
| - # In case the resolver is a custom one that overwrites |
| 330 | + # Note: this function is used to resolve FK or 1:1 fields |
| 331 | + # it does not differentiate between custom-resolved fields |
| 332 | + # and default resolved fields. |
| 333 | + |
| 334 | + # because this is a django foreign key or one-to-one field, the primary-key for |
| 335 | + # this node can be accessed from the root node. |
| 336 | + # ex: article.reporter_id |
| 337 | + from graphene.utils.str_converters import to_snake_case |
| 338 | + |
| 339 | + # get the name of the id field from the root's model |
| 340 | + field_name = to_snake_case(info.field_name) |
| 341 | + db_field_key = root.__class__._meta.get_field(field_name).attname |
| 342 | + if hasattr(root, db_field_key): |
| 343 | + # get the object's primary-key from root |
| 344 | + object_pk = getattr(root, db_field_key) |
| 345 | + else: |
| 346 | + return None |
| 347 | + |
| 348 | + is_resolver_awaitable = inspect.iscoroutinefunction(resolver) |
| 349 | + |
| 350 | + if is_resolver_awaitable: |
| 351 | + fk_obj = resolver(root, info, **args) |
| 352 | + # In case the resolver is a custom awaitable resolver overwrites |
330 | 353 | # the default Django resolver
|
331 |
| - # This happens, for example, when using custom awaitable resolvers. |
332 | 354 | return fk_obj
|
333 |
| - return _type.get_node(info, fk_obj.pk) |
| 355 | + |
| 356 | + instance_from_get_node = _type.get_node(info, object_pk) |
| 357 | + |
| 358 | + if instance_from_get_node is None: |
| 359 | + # no instance to return |
| 360 | + return |
| 361 | + elif isinstance(resolver, functools.partial) and resolver.func is get_default_resolver(): |
| 362 | + return instance_from_get_node |
| 363 | + elif resolver is not get_default_resolver(): |
| 364 | + # Default resolver is overriden |
| 365 | + # For optimization, add the instance to the resolver |
| 366 | + setattr(root, field_name, instance_from_get_node) |
| 367 | + # Explanation: |
| 368 | + # previously, _type.get_node` is called which results in at least one hit to the database. |
| 369 | + # But, if we did not pass the instance to the root, calling the resolver will result in |
| 370 | + # another call to get the instance which results in at least two database queries in total |
| 371 | + # to resolve this node only. |
| 372 | + # That's why the value of the object is set in the root so when the object is accessed |
| 373 | + # in the resolver (root.field_name) it does not access the database unless queried explicitly. |
| 374 | + fk_obj = resolver(root, info, **args) |
| 375 | + return fk_obj |
| 376 | + else: |
| 377 | + return instance_from_get_node |
334 | 378 |
|
335 | 379 | return custom_resolver
|
336 | 380 |
|
|
0 commit comments