From 4e8bc36e7f74370b2d84221fe2fd8a169f58ee80 Mon Sep 17 00:00:00 2001 From: abawchen Date: Tue, 23 Apr 2019 18:08:16 +0800 Subject: [PATCH 01/40] test: Add test_should_generic_reference_convert_union back --- graphene_mongo/tests/test_converter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/graphene_mongo/tests/test_converter.py b/graphene_mongo/tests/test_converter.py index d5b9e05a..a5e73f3e 100644 --- a/graphene_mongo/tests/test_converter.py +++ b/graphene_mongo/tests/test_converter.py @@ -297,8 +297,6 @@ class Meta: def test_should_generic_reference_convert_union(): - pass - """ class A(MongoengineObjectType): class Meta: @@ -318,4 +316,4 @@ class Meta: Reporter._fields['generic_reference'], registry.get_global_registry()) assert isinstance(generic_reference_field, graphene.Field) assert isinstance(generic_reference_field.type(), graphene.Union) - """ + assert generic_reference_field.type()._meta.types == (A, E) From 60c0a68f820aaf3b5350151c1cd3db77eddea0c2 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Tue, 23 Apr 2019 23:50:56 +0530 Subject: [PATCH 02/40] Support List of GenericReferenceField --- graphene_mongo/converter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index a357ac71..9306856e 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -96,6 +96,8 @@ def convert_field_to_datetime(field, registry=None): @convert_mongoengine_field.register(mongoengine.EmbeddedDocumentListField) def convert_field_to_list(field, registry=None): base_type = convert_mongoengine_field(field.field, registry=registry) + if isinstance(base_type, Field): + return List(base_type._type, description=get_field_description(field, registry), required=field.required) if isinstance(base_type, (Dynamic)): base_type = base_type.get_type() if base_type is None: @@ -116,7 +118,6 @@ def convert_field_to_list(field, registry=None): @convert_mongoengine_field.register(mongoengine.GenericReferenceField) def convert_field_to_union(field, registry=None): - _types = [] for choice in field.choices: _field = mongoengine.ReferenceField(get_document(choice)) @@ -137,8 +138,8 @@ def convert_field_to_union(field, registry=None): field.db_field, str(uuid.uuid1()).replace('-', '') ) - Meta = type('Meta', (object, ), {'types': tuple(_types)}) - _union = type(name, (Union, ), {'Meta': Meta}) + Meta = type('Meta', (object,), {'types': tuple(_types)}) + _union = type(name, (Union,), {'Meta': Meta}) return Field(_union) From 2174cd1ab3484615c35ab1af9bc9f86b77f999cf Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 24 Apr 2019 15:54:46 +0530 Subject: [PATCH 03/40] Support List of GenericReferenceField for MongoEngineConnectionField --- graphene_mongo/fields.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index bd7121aa..bbee52a2 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -8,7 +8,7 @@ from graphene.relay import ConnectionField from graphene.types.argument import to_arguments from graphene.types.dynamic import Dynamic -from graphene.types.structures import Structure +from graphene.types.structures import Structure, List from graphql_relay.connection.arrayconnection import connection_from_list_slice from .advanced_types import PointFieldType, MultiPolygonFieldType @@ -80,12 +80,12 @@ def is_filterable(k): converted = convert_mongoengine_field(getattr(self.model, k), self.registry) except MongoEngineConversionError: return False - if isinstance(converted, (ConnectionField, Dynamic)): + if isinstance(converted, (ConnectionField, Dynamic, List)): return False if callable(getattr(converted, 'type', None)) \ and isinstance( - converted.type(), - (PointFieldType, MultiPolygonFieldType, graphene.Union)): + converted.type(), + (PointFieldType, MultiPolygonFieldType, graphene.Union)): return False return True From 61515e387d3bbba44673810d8570e86d3a799add Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 00:49:49 +0530 Subject: [PATCH 04/40] Relative module imported --- examples/flask_mongoengine/app.py | 10 ++++------ examples/flask_mongoengine/database.py | 2 +- examples/flask_mongoengine/schema.py | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/examples/flask_mongoengine/app.py b/examples/flask_mongoengine/app.py index 659becc9..65a71609 100644 --- a/examples/flask_mongoengine/app.py +++ b/examples/flask_mongoengine/app.py @@ -1,7 +1,7 @@ -from database import init_db +from .database import init_db from flask import Flask from flask_graphql import GraphQLView -from schema import schema +from .schema import schema app = Flask(__name__) app.debug = True @@ -44,7 +44,5 @@ app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True)) -if __name__ == '__main__': - init_db() - app.run() - +init_db() +app.run() diff --git a/examples/flask_mongoengine/database.py b/examples/flask_mongoengine/database.py index 496c436d..24f17ab1 100644 --- a/examples/flask_mongoengine/database.py +++ b/examples/flask_mongoengine/database.py @@ -1,6 +1,6 @@ from mongoengine import connect -from models import Department, Employee, Role, Task +from .models import Department, Employee, Role, Task connect('graphene-mongo-example', host='mongomock://localhost', alias='default') diff --git a/examples/flask_mongoengine/schema.py b/examples/flask_mongoengine/schema.py index 9715751e..e43cc787 100644 --- a/examples/flask_mongoengine/schema.py +++ b/examples/flask_mongoengine/schema.py @@ -1,10 +1,10 @@ import graphene from graphene.relay import Node from graphene_mongo import MongoengineConnectionField, MongoengineObjectType -from models import Department as DepartmentModel -from models import Employee as EmployeeModel -from models import Role as RoleModel -from models import Task as TaskModel +from .models import Department as DepartmentModel +from .models import Employee as EmployeeModel +from .models import Role as RoleModel +from .models import Task as TaskModel class Department(MongoengineObjectType): From 27bc3a1ccd965947ffda2789f702f1852424279c Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 11:25:38 +0530 Subject: [PATCH 05/40] Support Mongoengine queryset filters using argument Example: class Employee(MongoengineObjectType): class Meta: model = EmployeeModel interfaces = (Node,) filter_fields = { 'name': ['exact', 'icontains', 'istartswith'] } --- examples/flask_mongoengine/schema.py | 16 +++++++++++----- graphene_mongo/fields.py | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/examples/flask_mongoengine/schema.py b/examples/flask_mongoengine/schema.py index e43cc787..2205c67b 100644 --- a/examples/flask_mongoengine/schema.py +++ b/examples/flask_mongoengine/schema.py @@ -1,36 +1,42 @@ import graphene from graphene.relay import Node +from graphene_mongo.tests.nodes import PlayerNode, ReporterNode + from graphene_mongo import MongoengineConnectionField, MongoengineObjectType from .models import Department as DepartmentModel from .models import Employee as EmployeeModel from .models import Role as RoleModel from .models import Task as TaskModel -class Department(MongoengineObjectType): +class Department(MongoengineObjectType): class Meta: model = DepartmentModel interfaces = (Node,) class Role(MongoengineObjectType): - class Meta: model = RoleModel interfaces = (Node,) + filter_fields = { + 'name': ['exact', 'icontains', 'istartswith'] + } class Task(MongoengineObjectType): - class Meta: model = TaskModel interfaces = (Node,) -class Employee(MongoengineObjectType): +class Employee(MongoengineObjectType): class Meta: model = EmployeeModel interfaces = (Node,) + filter_fields = { + 'name': ['exact', 'icontains', 'istartswith'] + } class Query(graphene.ObjectType): @@ -39,5 +45,5 @@ class Query(graphene.ObjectType): all_roles = MongoengineConnectionField(Role) role = graphene.Field(Role) -schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) +schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index bbee52a2..2a093a4d 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -56,7 +56,7 @@ def registry(self): def args(self): return to_arguments( self._base_args or OrderedDict(), - dict(self.field_args, **self.reference_args) + dict(self.field_args, **self.reference_args, **self.filter_args) ) @args.setter @@ -80,7 +80,7 @@ def is_filterable(k): converted = convert_mongoengine_field(getattr(self.model, k), self.registry) except MongoEngineConversionError: return False - if isinstance(converted, (ConnectionField, Dynamic, List)): + if isinstance(converted, (ConnectionField, Dynamic)): return False if callable(getattr(converted, 'type', None)) \ and isinstance( @@ -100,6 +100,17 @@ def get_type(v): def field_args(self): return self._field_args(self.fields.items()) + @property + def filter_args(self): + filter_args = dict() + if self._type._meta.filter_fields: + for field, filter_collection in self._type._meta.filter_fields.items(): + for each in filter_collection: + filter_args[field + "__" + each] = graphene.Argument( + type=getattr(graphene, str(self._type._meta.fields[field].type).replace("!", ""))) + + return filter_args + @property def reference_args(self): def get_reference_field(r, kv): From 98ff0baa508d0a652afbaea501a34bc88b86b585 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 13:10:52 +0530 Subject: [PATCH 06/40] Update fields.py --- graphene_mongo/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 66a103be..b1a9d3c5 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -8,7 +8,7 @@ from graphene.relay import ConnectionField from graphene.types.argument import to_arguments from graphene.types.dynamic import Dynamic -from graphene.types.structures import Structure, List +from graphene.types.structures import Structure from graphql_relay.connection.arrayconnection import connection_from_list_slice from .advanced_types import ( From f035772ce40c01db63395a6b0a3db5034253dc2e Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 13:14:59 +0530 Subject: [PATCH 07/40] Support Mongoengine queryset filters using argument Example: class Employee(MongoengineObjectType): class Meta: model = EmployeeModel interfaces = (Node,) filter_fields = { 'name': ['exact', 'icontains', 'istartswith'] } --- graphene_mongo/fields.py | 6 +- graphene_mongo/tests/models.py | 2 +- graphene_mongo/tests/nodes.py | 39 ++++------ graphene_mongo/tests/test_relay_query.py | 99 ++++++++++++++---------- 4 files changed, 78 insertions(+), 68 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 2a093a4d..dff92f3f 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -8,7 +8,7 @@ from graphene.relay import ConnectionField from graphene.types.argument import to_arguments from graphene.types.dynamic import Dynamic -from graphene.types.structures import Structure, List +from graphene.types.structures import Structure from graphql_relay.connection.arrayconnection import connection_from_list_slice from .advanced_types import PointFieldType, MultiPolygonFieldType @@ -83,9 +83,7 @@ def is_filterable(k): if isinstance(converted, (ConnectionField, Dynamic)): return False if callable(getattr(converted, 'type', None)) \ - and isinstance( - converted.type(), - (PointFieldType, MultiPolygonFieldType, graphene.Union)): + and isinstance(converted.type(), (PointFieldType, MultiPolygonFieldType, graphene.Union)): return False return True diff --git a/graphene_mongo/tests/models.py b/graphene_mongo/tests/models.py index 400a1e5d..585e7fe1 100644 --- a/graphene_mongo/tests/models.py +++ b/graphene_mongo/tests/models.py @@ -1,7 +1,7 @@ import mongoengine from datetime import datetime -mongoengine.connect('graphene-mongo-test', host='mongomock://localhost', alias='default') +mongoengine.connect('graphene-mongo-test', host='localhost', alias='default') class Publisher(mongoengine.Document): diff --git a/graphene_mongo/tests/nodes.py b/graphene_mongo/tests/nodes.py index 76eed2b6..3eadef0a 100644 --- a/graphene_mongo/tests/nodes.py +++ b/graphene_mongo/tests/nodes.py @@ -13,87 +13,78 @@ class PublisherNode(MongoengineObjectType): class Meta: model = models.Publisher only_fields = ('id', 'name') - interfaces = (Node, ) + interfaces = (Node,) class ArticleNode(MongoengineObjectType): - class Meta: model = models.Article - interfaces = (Node, ) + interfaces = (Node,) class EditorNode(MongoengineObjectType): - class Meta: model = models.Editor - interfaces = (Node, ) + interfaces = (Node,) class EmbeddedArticleNode(MongoengineObjectType): - class Meta: model = models.EmbeddedArticle - interfaces = (Node, ) + interfaces = (Node,) class PlayerNode(MongoengineObjectType): - class Meta: model = models.Player - interfaces = (Node, ) + interfaces = (Node,) + filter_fields = { + 'first_name': ['istartswith']} class ReporterNode(MongoengineObjectType): - class Meta: model = models.Reporter - interfaces = (Node, ) + interfaces = (Node,) class ParentNode(MongoengineObjectType): - class Meta: model = models.Parent - interfaces = (Node, ) + interfaces = (Node,) class ChildNode(MongoengineObjectType): - class Meta: model = models.Child - interfaces = (Node, ) + interfaces = (Node,) class ChildRegisteredBeforeNode(MongoengineObjectType): - class Meta: model = models.ChildRegisteredBefore - interfaces = (Node, ) + interfaces = (Node,) class ParentWithRelationshipNode(MongoengineObjectType): - class Meta: model = models.ParentWithRelationship - interfaces = (Node, ) + interfaces = (Node,) class ChildRegisteredAfterNode(MongoengineObjectType): - class Meta: model = models.ChildRegisteredAfter - interfaces = (Node, ) + interfaces = (Node,) class ProfessorVectorNode(MongoengineObjectType): - class Meta: model = models.ProfessorVector - interfaces = (Node, ) + interfaces = (Node,) class ErroneousModelNode(MongoengineObjectType): class Meta: model = models.ErroneousModel - interfaces = (Node, ) + interfaces = (Node,) diff --git a/graphene_mongo/tests/test_relay_query.py b/graphene_mongo/tests/test_relay_query.py index de210d79..93a08ff0 100644 --- a/graphene_mongo/tests/test_relay_query.py +++ b/graphene_mongo/tests/test_relay_query.py @@ -13,7 +13,7 @@ ReporterNode, ChildNode, ParentWithRelationshipNode, - ProfessorVectorNode,) + ProfessorVectorNode, ) from ..fields import MongoengineConnectionField @@ -22,7 +22,6 @@ def get_nodes(data, key): def test_should_query_reporter(fixtures): - class Query(graphene.ObjectType): node = Node.Field() reporter = graphene.Field(ReporterNode) @@ -129,7 +128,6 @@ def resolve_reporter(self, *args, **kwargs): def test_should_query_all_editors(fixtures): - class Query(graphene.ObjectType): node = Node.Field() all_editors = MongoengineConnectionField(EditorNode) @@ -182,7 +180,6 @@ class Query(graphene.ObjectType): def test_should_filter_editors_by_id(fixtures): - class Query(graphene.ObjectType): node = Node.Field() all_editors = MongoengineConnectionField(EditorNode) @@ -221,7 +218,6 @@ class Query(graphene.ObjectType): def test_should_filter(fixtures): - class Query(graphene.ObjectType): node = Node.Field() articles = MongoengineConnectionField(ArticleNode) @@ -263,8 +259,46 @@ class Query(graphene.ObjectType): expected, sort_keys=True) -def test_should_filter_by_reference_field(fixtures): +def test_should_filter_mongoengine_queryset(fixtures): + class Query(graphene.ObjectType): + players = MongoengineConnectionField(PlayerNode) + query = ''' + query players { + players(firstName_Istartswith: "M") { + edges { + node { + firstName + } + } + } + } + ''' + expected = { + 'players': { + 'edges': [ + { + 'node': { + 'firstName': 'Michael', + } + }, + { + 'node': { + 'firstName': 'Magic' + } + } + ] + } + } + schema = graphene.Schema(query=Query) + result = schema.execute(query) + + assert not result.errors + assert json.dumps(result.data, sort_keys=True) == json.dumps( + expected, sort_keys=True) + + +def test_should_filter_by_reference_field(fixtures): class Query(graphene.ObjectType): node = Node.Field() articles = MongoengineConnectionField(ArticleNode) @@ -305,7 +339,6 @@ class Query(graphene.ObjectType): def test_should_filter_through_inheritance(fixtures): - class Query(graphene.ObjectType): node = Node.Field() children = MongoengineConnectionField(ChildNode) @@ -334,8 +367,8 @@ class Query(graphene.ObjectType): 'bar': 'bar', 'baz': 'baz', 'loc': { - 'type': 'Point', - 'coordinates': [10.0, 20.0] + 'type': 'Point', + 'coordinates': [10.0, 20.0] } } } @@ -414,9 +447,7 @@ class Query(graphene.ObjectType): def test_should_first_n(fixtures): - class Query(graphene.ObjectType): - editors = MongoengineConnectionField(EditorNode) query = ''' @@ -471,7 +502,6 @@ class Query(graphene.ObjectType): def test_should_after(fixtures): class Query(graphene.ObjectType): - players = MongoengineConnectionField(PlayerNode) query = ''' @@ -502,10 +532,10 @@ class Query(graphene.ObjectType): } }, { - 'cursor': 'YXJyYXljb25uZWN0aW9uOjM=', - 'node': { + 'cursor': 'YXJyYXljb25uZWN0aW9uOjM=', + 'node': { 'firstName': 'Chris' - } + } } ] } @@ -520,7 +550,6 @@ class Query(graphene.ObjectType): def test_should_before(fixtures): class Query(graphene.ObjectType): - players = MongoengineConnectionField(PlayerNode) query = ''' @@ -587,10 +616,10 @@ class Query(graphene.ObjectType): } }, { - 'cursor': 'YXJyYXljb25uZWN0aW9uOjM=', - 'node': { - 'firstName': 'Chris' - } + 'cursor': 'YXJyYXljb25uZWN0aW9uOjM=', + 'node': { + 'firstName': 'Chris' + } } ] } @@ -599,14 +628,11 @@ class Query(graphene.ObjectType): result = schema.execute(query) assert not result.errors - assert json.dumps(result.data, sort_keys=True) == \ - json.dumps(expected, sort_keys=True) + assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) def test_should_self_reference(fixtures): - class Query(graphene.ObjectType): - all_players = MongoengineConnectionField(PlayerNode) query = ''' @@ -695,15 +721,15 @@ class Query(graphene.ObjectType): } }, { - 'node': { - 'firstName': 'Chris', - 'players': { - 'edges': [] - }, - 'embeddedListArticles': { - 'edges': [] - } - } + 'node': { + 'firstName': 'Chris', + 'players': { + 'edges': [] + }, + 'embeddedListArticles': { + 'edges': [] + } + } } ] } @@ -716,7 +742,6 @@ class Query(graphene.ObjectType): def test_should_lazy_reference(fixtures): - class Query(graphene.ObjectType): node = Node.Field() parents = MongoengineConnectionField(ParentWithRelationshipNode) @@ -782,9 +807,7 @@ class Query(graphene.ObjectType): def test_should_query_with_embedded_document(fixtures): - class Query(graphene.ObjectType): - all_professors = MongoengineConnectionField(ProfessorVectorNode) query = ''' @@ -808,7 +831,7 @@ class Query(graphene.ObjectType): 'node': { 'vec': [1.0, 2.3], 'metadata': { - 'firstName': 'Steven' + 'firstName': 'Steven' } } @@ -823,7 +846,6 @@ class Query(graphene.ObjectType): def test_should_get_queryset_returns_dict_filters(fixtures): - class Query(graphene.ObjectType): node = Node.Field() articles = MongoengineConnectionField(ArticleNode, get_queryset=lambda *_, **__: {"headline": "World"}) @@ -866,7 +888,6 @@ class Query(graphene.ObjectType): def test_should_get_queryset_returns_qs_filters(fixtures): - def get_queryset(model, info, **args): return model.objects(headline="World") From b8765c6b9dfd22e44e9cc327052ec155ae384fc8 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 13:25:19 +0530 Subject: [PATCH 08/40] Merge remote-tracking branch 'remotes/origin/feat-mongoengine-connection-field-filters' into feat-mongoengine-connection-field-filters # Conflicts: # graphene_mongo/fields.py # graphene_mongo/tests/models.py # graphene_mongo/tests/test_relay_query.py --- graphene_mongo/tests/test_relay_query.py | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/graphene_mongo/tests/test_relay_query.py b/graphene_mongo/tests/test_relay_query.py index ea41d8af..502e54bf 100644 --- a/graphene_mongo/tests/test_relay_query.py +++ b/graphene_mongo/tests/test_relay_query.py @@ -1,4 +1,5 @@ import base64 +import json import os import pytest @@ -925,3 +926,41 @@ class Query(graphene.ObjectType): result = schema.execute(query) assert not result.errors assert result.data == expected + + +def test_should_filter_mongoengine_queryset(fixtures): + class Query(graphene.ObjectType): + players = MongoengineConnectionField(PlayerNode) + + query = ''' + query players { + players(firstName_Istartswith: "M") { + edges { + node { + firstName + } + } + } + } + ''' + expected = { + 'players': { + 'edges': [ + { + 'node': { + 'firstName': 'Michael', + } + }, + { + 'node': { + 'firstName': 'Magic' + } + } + ] + } + } + schema = graphene.Schema(query=Query) + result = schema.execute(query) + + assert not result.errors + assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) From 0aedb9aa73bb8f1fef094199bd3ac01e26700430 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 13:30:58 +0530 Subject: [PATCH 09/40] Merge remote-tracking branch 'remotes/origin/feat-mongoengine-connection-field-filters' into feat-mongoengine-connection-field-filters # Conflicts: # graphene_mongo/fields.py # graphene_mongo/tests/models.py # graphene_mongo/tests/test_relay_query.py --- graphene_mongo/fields.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index b1a9d3c5..9b1dbd90 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -56,10 +56,8 @@ def registry(self): @property def args(self): - return to_arguments( - self._base_args or OrderedDict(), - dict(self.field_args, **self.reference_args, **self.filter_args) - ) + return to_arguments(self._base_args or OrderedDict(), + dict(self.field_args, **self.reference_args, **self.filter_args)) @args.setter def args(self, args): @@ -85,10 +83,8 @@ def is_filterable(k): return False if isinstance(converted, (ConnectionField, Dynamic)): return False - if callable(getattr(converted, 'type', None)) \ - and isinstance( - converted.type(), - (FileFieldType, PointFieldType, MultiPolygonFieldType, graphene.Union)): + if callable(getattr(converted, 'type', None)) and \ + isinstance(converted.type(), (FileFieldType, PointFieldType, MultiPolygonFieldType, graphene.Union)): return False return True From 324b936181a3aa9bbd52557362ab43d146cf62ff Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 13:39:54 +0530 Subject: [PATCH 10/40] Update fields.py --- graphene_mongo/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 9b1dbd90..90afee20 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -57,7 +57,7 @@ def registry(self): @property def args(self): return to_arguments(self._base_args or OrderedDict(), - dict(self.field_args, **self.reference_args, **self.filter_args)) + dict(dict(self.field_args, **self.reference_args), **self.filter_args)) @args.setter def args(self, args): From c0eb7bf33bf576804d48e35bacd29b706bd16a2f Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Wed, 8 May 2019 14:08:48 +0530 Subject: [PATCH 11/40] Merge remote-tracking branch 'remotes/origin/feat-mongoengine-connection-field-filters' into feat-mongoengine-connection-field-filters # Conflicts: # graphene_mongo/fields.py # graphene_mongo/tests/models.py # graphene_mongo/tests/test_relay_query.py --- graphene_mongo/fields.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 90afee20..21cd9fae 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -8,7 +8,7 @@ from graphene.relay import ConnectionField from graphene.types.argument import to_arguments from graphene.types.dynamic import Dynamic -from graphene.types.structures import Structure +from graphene.types.structures import Structure, List from graphql_relay.connection.arrayconnection import connection_from_list_slice from .advanced_types import ( @@ -81,10 +81,11 @@ def is_filterable(k): converted = convert_mongoengine_field(getattr(self.model, k), self.registry) except MongoEngineConversionError: return False - if isinstance(converted, (ConnectionField, Dynamic)): + if isinstance(converted, (ConnectionField, Dynamic, List)): return False if callable(getattr(converted, 'type', None)) and \ - isinstance(converted.type(), (FileFieldType, PointFieldType, MultiPolygonFieldType, graphene.Union)): + isinstance(converted.type(), + (FileFieldType, PointFieldType, MultiPolygonFieldType, graphene.Union)): return False return True From 63b4f93cba80982405959a77627b97a1145ce7a5 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Thu, 16 May 2019 13:52:40 +0530 Subject: [PATCH 12/40] Support GenericEmbeddedDocumentField and ListField(GenericEmbeddedDocumentField) --- graphene_mongo/converter.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index f29c980c..ced7f36a 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -122,11 +122,16 @@ def convert_field_to_list(field, registry=None): @convert_mongoengine_field.register(mongoengine.GenericReferenceField) +@convert_mongoengine_field.register(mongoengine.GenericEmbeddedDocumentField) def convert_field_to_union(field, registry=None): _types = [] for choice in field.choices: - _field = mongoengine.ReferenceField(get_document(choice)) - _field = convert_mongoengine_field(_field, registry) + try: + _field = mongoengine.ReferenceField(get_document(choice)) + _field = convert_mongoengine_field(_field, registry) + except Exception: + _field = mongoengine.EmbeddedDocumentField(get_document(choice._class_name)) + _field = convert_mongoengine_field(_field, registry) _type = _field.get_type() if _type: _types.append(_type.type) From c64c759b38c6111f2a183b1d85c87942a6efacc2 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 01:28:10 +0530 Subject: [PATCH 13/40] Query efficiency and performance - Retrieving only the queried fields from database --- graphene_mongo/converter.py | 6 ++-- graphene_mongo/fields.py | 50 ++++++++++++++++----------- graphene_mongo/utils.py | 67 ++++++++++++++++++++++++++++++++++--- 3 files changed, 97 insertions(+), 26 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 74b66be1..dfc47666 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -6,7 +6,7 @@ from mongoengine.base import get_document from . import advanced_types -from .utils import import_single_dispatch, get_field_description +from .utils import import_single_dispatch, get_field_description, get_query_fields singledispatch = import_single_dispatch() @@ -186,7 +186,9 @@ def convert_lazy_field_to_dynamic(field, registry=None): def lazy_resolver(root, *args, **kwargs): if getattr(root, field.name or field.db_name): - return getattr(root, field.name or field.db_name).fetch() + only_fields = get_query_fields(args[0]).keys() + document = getattr(root, field.name or field.db_name) + return document.document_type.objects().only(*only_fields).get(pk=document.pk) def dynamic_type(): _type = registry.get_type_for_model(model) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index a6f9650d..47be36d1 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -5,6 +5,7 @@ import graphene import mongoengine +from graphql.utils.ast_to_dict import ast_to_dict from promise import Promise from graphql_relay import from_global_id from graphene.relay import ConnectionField @@ -21,7 +22,7 @@ ) from .converter import convert_mongoengine_field, MongoEngineConversionError from .registry import get_global_registry -from .utils import get_model_reference_fields, get_node_from_global_id +from .utils import get_model_reference_fields, get_node_from_global_id, get_query_fields, camel_to_snake class MongoengineConnectionField(ConnectionField): @@ -98,18 +99,18 @@ def is_filterable(k): if isinstance(converted, (ConnectionField, Dynamic)): return False if callable(getattr(converted, "type", None)) and isinstance( - converted.type(), - ( - FileFieldType, - PointFieldType, - MultiPolygonFieldType, - graphene.Union, - PolygonFieldType, - ), + converted.type(), + ( + FileFieldType, + PointFieldType, + MultiPolygonFieldType, + graphene.Union, + PolygonFieldType, + ), ): return False if isinstance(converted, (graphene.List)) and issubclass( - getattr(converted, "_of_type", None), graphene.Union + getattr(converted, "_of_type", None), graphene.Union ): return False @@ -160,8 +161,8 @@ def get_reference_field(r, kv): field = kv[1] mongo_field = getattr(self.model, kv[0], None) if isinstance( - mongo_field, - (mongoengine.LazyReferenceField, mongoengine.ReferenceField), + mongo_field, + (mongoengine.LazyReferenceField, mongoengine.ReferenceField), ): field = convert_mongoengine_field(mongo_field, self.registry) if callable(getattr(field, "get_type", None)): @@ -169,7 +170,7 @@ def get_reference_field(r, kv): if _type: node = _type._type._meta if "id" in node.fields and not issubclass( - node.model, (mongoengine.EmbeddedDocument,) + node.model, (mongoengine.EmbeddedDocument,) ): r.update({kv[0]: node.fields["id"]._type.of_type()}) return r @@ -180,7 +181,7 @@ def get_reference_field(r, kv): def fields(self): return self._type._meta.fields - def get_queryset(self, model, info, **args): + def get_queryset(self, model, info, only_fields=list(), **args): if args: reference_fields = get_model_reference_fields(self.model) hydrated_references = {} @@ -198,12 +199,13 @@ def get_queryset(self, model, info, **args): return queryset_or_filters else: args.update(queryset_or_filters) + return model.objects(**args).order_by(self.order_by) - def default_resolver(self, _root, info, **args): + def default_resolver(self, _root, info, only_fields=list(), **args): args = args or {} - if _root is not None: + if _root is not None and getattr(_root, info.field_name, []) is not None: args["pk__in"] = [r.pk for r in getattr(_root, info.field_name, [])] connection_args = { @@ -219,7 +221,7 @@ def default_resolver(self, _root, info, **args): args['pk'] = from_global_id(_id)[-1] if callable(getattr(self.model, "objects", None)): - iterables = self.get_queryset(self.model, info, **args) + iterables = self.get_queryset(self.model, info, only_fields, **args) list_length = iterables.count() else: iterables = [] @@ -239,23 +241,31 @@ def default_resolver(self, _root, info, **args): return connection def chained_resolver(self, resolver, is_partial, root, info, **args): + only_fields = list() + for field in get_query_fields(info): + if camel_to_snake(field) in self.model._fields_ordered: + only_fields.append(camel_to_snake(field)) if not bool(args) or not is_partial: + if isinstance(self.model, mongoengine.Document) or isinstance(self.model, + mongoengine.base.metaclasses.TopLevelDocumentMetaclass): + args_copy = args.copy() + for arg_name, arg in args.copy().items(): + if arg_name not in self.model._fields_ordered: + args_copy.pop(arg_name) # XXX: Filter nested args resolved = resolver(root, info, **args) if resolved is not None: return resolved - return self.default_resolver(root, info, **args) + return self.default_resolver(root, info, only_fields, **args) @classmethod def connection_resolver(cls, resolver, connection_type, root, info, **args): iterable = resolver(root, info, **args) if isinstance(connection_type, graphene.NonNull): connection_type = connection_type.of_type - on_resolve = partial(cls.resolve_connection, connection_type, args) if Promise.is_thenable(iterable): return Promise.resolve(iterable).then(on_resolve) - return on_resolve(iterable) def get_resolver(self, parent_resolver): diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 7e3fc66b..27fb18b3 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -6,6 +6,7 @@ import mongoengine from graphene import Node from graphene.utils.trim_docstring import trim_docstring +from graphql.utils.ast_to_dict import ast_to_dict def get_model_fields(model, excluding=None): @@ -23,8 +24,8 @@ def get_model_reference_fields(model, excluding=None): attributes = dict() for attr_name, attr in model._fields.items(): if attr_name in excluding or not isinstance( - attr, - (mongoengine.fields.ReferenceField, mongoengine.fields.LazyReferenceField), + attr, + (mongoengine.fields.ReferenceField, mongoengine.fields.LazyReferenceField), ): continue attributes[attr_name] = attr @@ -33,8 +34,8 @@ def get_model_reference_fields(model, excluding=None): def is_valid_mongoengine_model(model): return inspect.isclass(model) and ( - issubclass(model, mongoengine.Document) - or issubclass(model, mongoengine.EmbeddedDocument) + issubclass(model, mongoengine.Document) + or issubclass(model, mongoengine.EmbeddedDocument) ) @@ -101,3 +102,61 @@ def get_node_from_global_id(node, info, global_id): return interface.get_node_from_global_id(info, global_id) except AttributeError: return Node.get_node_from_global_id(info, global_id) + + +def collect_query_fields(node, fragments): + """Recursively collects fields from the AST + + Args: + node (dict): A node in the AST + fragments (dict): Fragment definitions + + Returns: + A dict mapping each field found, along with their sub fields. + + {'name': {}, + 'image': {'id': {}, + 'name': {}, + 'description': {}}, + 'slug': {}} + """ + + field = {} + + if node.get('selection_set'): + for leaf in node['selection_set']['selections']: + if leaf['kind'] == 'Field': + field.update({ + leaf['name']['value']: collect_query_fields(leaf, fragments) + }) + elif leaf['kind'] == 'FragmentSpread': + field.update(collect_query_fields(fragments[leaf['name']['value']], + fragments)) + + return field + + +def get_query_fields(info): + """A convenience function to call collect_query_fields with info + + Args: + info (ResolveInfo) + + Returns: + dict: Returned from collect_query_fields + """ + + fragments = {} + node = ast_to_dict(info.field_asts[0]) + + for name, value in info.fragments.items(): + fragments[name] = ast_to_dict(value) + + query = collect_query_fields(node, fragments) + if "edges" in query: + return query["edges"]["node"].keys() + return query + + +def camel_to_snake(field): + return ''.join(['_' + c.lower() if c.isupper() else c for c in field]).lstrip('_') From 0b37013f8bca4b8c2840041a75467a4d8a8e7f0b Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 02:53:29 +0530 Subject: [PATCH 14/40] Query efficiency and performance - Retrieving only the queried fields from the database by implementing deafult resolvers for both ReferenceField & CachedReferenceField. --- graphene_mongo/converter.py | 22 +++++++++++++++++++--- graphene_mongo/fields.py | 1 - 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index dfc47666..f5b6c444 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -121,7 +121,7 @@ def convert_field_to_list(field, registry=None): # Non-relationship field relations = (mongoengine.ReferenceField, mongoengine.EmbeddedDocumentField) if not isinstance(base_type, (graphene.List, graphene.NonNull)) and not isinstance( - field.field, relations + field.field, relations ): base_type = type(base_type) @@ -135,7 +135,6 @@ def convert_field_to_list(field, registry=None): @convert_mongoengine_field.register(mongoengine.GenericEmbeddedDocumentField) @convert_mongoengine_field.register(mongoengine.GenericReferenceField) def convert_field_to_union(field, registry=None): - _types = [] for choice in field.choices: if isinstance(field, mongoengine.GenericReferenceField): @@ -171,11 +170,28 @@ def convert_field_to_union(field, registry=None): def convert_field_to_dynamic(field, registry=None): model = field.document_type + def reference_resolver(root, *args, **kwargs): + document = getattr(root, field.name or field.db_name) + only_fields = get_query_fields(args[0]).keys() + return field.document_type.objects().only(*only_fields).get(pk=document.id) + + def cached_reference_resolver(root, *args, **kwargs): + document = getattr(root, field.name or field.db_name) + only_fields = get_query_fields(args[0]).keys() + return field.document_type.objects().only(*only_fields).get(pk=document) + def dynamic_type(): _type = registry.get_type_for_model(model) if not _type: return None - return graphene.Field(_type, description=get_field_description(field, registry)) + elif isinstance(field, mongoengine.ReferenceField): + graphene.Field(_type, resolver=reference_resolver, + description=get_field_description(field, registry)) + elif isinstance(field, mongoengine.CachedReferenceField): + return graphene.Field(_type, resolver=cached_reference_resolver, + description=get_field_description(field, registry)) + return graphene.Field(_type, + description=get_field_description(field, registry)) return graphene.Dynamic(dynamic_type) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 47be36d1..c72c56ca 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -5,7 +5,6 @@ import graphene import mongoengine -from graphql.utils.ast_to_dict import ast_to_dict from promise import Promise from graphql_relay import from_global_id from graphene.relay import ConnectionField From b72dcf4239cebf9b23906685ed353eef30e43484 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 03:27:22 +0530 Subject: [PATCH 15/40] Query efficiency and performance - Retrieving only the queried fields from the database by implementing deafult resolvers for both ReferenceField & CachedReferenceField. --- graphene_mongo/converter.py | 19 +++++++++---------- graphene_mongo/fields.py | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index f5b6c444..c6cc75f8 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -6,7 +6,7 @@ from mongoengine.base import get_document from . import advanced_types -from .utils import import_single_dispatch, get_field_description, get_query_fields +from .utils import import_single_dispatch, get_field_description, get_query_fields, camel_to_snake singledispatch = import_single_dispatch() @@ -172,13 +172,13 @@ def convert_field_to_dynamic(field, registry=None): def reference_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) - only_fields = get_query_fields(args[0]).keys() - return field.document_type.objects().only(*only_fields).get(pk=document.id) + only_fields = [camel_to_snake(i) for i in get_query_fields(args[0]).keys()] + return field.document_type.objects().no_dereference().only(*only_fields).get(pk=document.id) def cached_reference_resolver(root, *args, **kwargs): - document = getattr(root, field.name or field.db_name) - only_fields = get_query_fields(args[0]).keys() - return field.document_type.objects().only(*only_fields).get(pk=document) + only_fields = [camel_to_snake(i) for i in get_query_fields(args[0]).keys()] + return field.document_type.objects().no_dereference().only(*only_fields).get( + pk=getattr(root, field.name or field.db_name)) def dynamic_type(): _type = registry.get_type_for_model(model) @@ -201,10 +201,9 @@ def convert_lazy_field_to_dynamic(field, registry=None): model = field.document_type def lazy_resolver(root, *args, **kwargs): - if getattr(root, field.name or field.db_name): - only_fields = get_query_fields(args[0]).keys() - document = getattr(root, field.name or field.db_name) - return document.document_type.objects().only(*only_fields).get(pk=document.pk) + document = getattr(root, field.name or field.db_name) + only_fields = [camel_to_snake(i) for i in get_query_fields(args[0]).keys()] + return document.document_type.objects().no_dereference().only(*only_fields).get(pk=document.pk) def dynamic_type(): _type = registry.get_type_for_model(model) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index c72c56ca..35547e59 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -199,7 +199,7 @@ def get_queryset(self, model, info, only_fields=list(), **args): else: args.update(queryset_or_filters) - return model.objects(**args).order_by(self.order_by) + return model.objects(**args).only(*only_fields).order_by(self.order_by) def default_resolver(self, _root, info, only_fields=list(), **args): args = args or {} From 5dfabd06a8a3ef286bcd5d4c38f79bbc66f03d26 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 03:32:10 +0530 Subject: [PATCH 16/40] Query efficiency and performance - Retrieving only the queried fields from the database by implementing deafult resolvers for both ReferenceField & CachedReferenceField. --- graphene_mongo/fields.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 35547e59..39b58d6f 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -245,12 +245,6 @@ def chained_resolver(self, resolver, is_partial, root, info, **args): if camel_to_snake(field) in self.model._fields_ordered: only_fields.append(camel_to_snake(field)) if not bool(args) or not is_partial: - if isinstance(self.model, mongoengine.Document) or isinstance(self.model, - mongoengine.base.metaclasses.TopLevelDocumentMetaclass): - args_copy = args.copy() - for arg_name, arg in args.copy().items(): - if arg_name not in self.model._fields_ordered: - args_copy.pop(arg_name) # XXX: Filter nested args resolved = resolver(root, info, **args) if resolved is not None: From 3b443754e66d39cbd55ddadd90afc2c36110f294 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 10:18:40 +0530 Subject: [PATCH 17/40] removed camel_to_snake custom function and used to_snake_case from graphene.utils.str_converters re-indented documentation. --- graphene_mongo/converter.py | 10 +++++----- graphene_mongo/fields.py | 7 ++++--- graphene_mongo/utils.py | 19 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index c6cc75f8..45b5981d 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -3,10 +3,10 @@ import uuid from graphene.types.json import JSONString +from graphene.utils.str_converters import to_snake_case from mongoengine.base import get_document - from . import advanced_types -from .utils import import_single_dispatch, get_field_description, get_query_fields, camel_to_snake +from .utils import import_single_dispatch, get_field_description, get_query_fields singledispatch = import_single_dispatch() @@ -172,11 +172,11 @@ def convert_field_to_dynamic(field, registry=None): def reference_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) - only_fields = [camel_to_snake(i) for i in get_query_fields(args[0]).keys()] + only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] return field.document_type.objects().no_dereference().only(*only_fields).get(pk=document.id) def cached_reference_resolver(root, *args, **kwargs): - only_fields = [camel_to_snake(i) for i in get_query_fields(args[0]).keys()] + only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] return field.document_type.objects().no_dereference().only(*only_fields).get( pk=getattr(root, field.name or field.db_name)) @@ -202,7 +202,7 @@ def convert_lazy_field_to_dynamic(field, registry=None): def lazy_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) - only_fields = [camel_to_snake(i) for i in get_query_fields(args[0]).keys()] + only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] return document.document_type.objects().no_dereference().only(*only_fields).get(pk=document.pk) def dynamic_type(): diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 39b58d6f..d01886f8 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -5,6 +5,7 @@ import graphene import mongoengine +from graphene.utils.str_converters import to_snake_case from promise import Promise from graphql_relay import from_global_id from graphene.relay import ConnectionField @@ -21,7 +22,7 @@ ) from .converter import convert_mongoengine_field, MongoEngineConversionError from .registry import get_global_registry -from .utils import get_model_reference_fields, get_node_from_global_id, get_query_fields, camel_to_snake +from .utils import get_model_reference_fields, get_node_from_global_id, get_query_fields class MongoengineConnectionField(ConnectionField): @@ -242,8 +243,8 @@ def default_resolver(self, _root, info, only_fields=list(), **args): def chained_resolver(self, resolver, is_partial, root, info, **args): only_fields = list() for field in get_query_fields(info): - if camel_to_snake(field) in self.model._fields_ordered: - only_fields.append(camel_to_snake(field)) + if to_snake_case(field) in self.model._fields_ordered: + only_fields.append(to_snake_case(field)) if not bool(args) or not is_partial: # XXX: Filter nested args resolved = resolver(root, info, **args) diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 27fb18b3..8d60c111 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -113,12 +113,15 @@ def collect_query_fields(node, fragments): Returns: A dict mapping each field found, along with their sub fields. - - {'name': {}, - 'image': {'id': {}, - 'name': {}, - 'description': {}}, - 'slug': {}} + { + 'name': {}, + 'image': { + 'id': {}, + 'name': {}, + 'description': {} + }, + 'slug': {} + } """ field = {} @@ -156,7 +159,3 @@ def get_query_fields(info): if "edges" in query: return query["edges"]["node"].keys() return query - - -def camel_to_snake(field): - return ''.join(['_' + c.lower() if c.isupper() else c for c in field]).lstrip('_') From 61c85920cf178dad569db914fa52cc80b2a8e3d3 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 15:22:19 +0530 Subject: [PATCH 18/40] Update converter.py convert_field_to_dynamic - return added for mongoengine.ReferenceField --- graphene_mongo/converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 45b5981d..ec8524f1 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -185,7 +185,7 @@ def dynamic_type(): if not _type: return None elif isinstance(field, mongoengine.ReferenceField): - graphene.Field(_type, resolver=reference_resolver, + return graphene.Field(_type, resolver=reference_resolver, description=get_field_description(field, registry)) elif isinstance(field, mongoengine.CachedReferenceField): return graphene.Field(_type, resolver=cached_reference_resolver, From 0845b406bbef7c5919340b78af3df8a524378192 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 18:58:20 +0530 Subject: [PATCH 19/40] Support Added for GenericReferenceField --- graphene_mongo/converter.py | 21 ++++++++++++++++++--- graphene_mongo/fields.py | 4 ++-- graphene_mongo/utils.py | 5 +++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index ec8524f1..8c286d33 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -161,6 +161,19 @@ def convert_field_to_union(field, registry=None): ) Meta = type("Meta", (object,), {"types": tuple(_types)}) _union = type(name, (graphene.Union,), {"Meta": Meta}) + + def reference_resolver(root, *args, **kwargs): + dereferenced = getattr(root, field.name or field.db_name) + document = get_document(dereferenced["_cls"]) + document_field= mongoengine.ReferenceField(document) + document_field = convert_mongoengine_field(document_field, registry) + document_field_type = document_field.get_type().type._meta.name + only_fields = [to_snake_case(i) for i in get_query_fields(args[0])[document_field_type].keys()] + return document.objects().no_dereference().only(*only_fields).get(pk=dereferenced["_ref"].id) + + if isinstance(field, mongoengine.GenericReferenceField): + return graphene.Field(_union, resolver=reference_resolver) + return graphene.Field(_union) @@ -172,8 +185,10 @@ def convert_field_to_dynamic(field, registry=None): def reference_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) - only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] - return field.document_type.objects().no_dereference().only(*only_fields).get(pk=document.id) + if document: + only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] + return field.document_type.objects().no_dereference().only(*only_fields).get(pk=document.id) + return None def cached_reference_resolver(root, *args, **kwargs): only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] @@ -186,7 +201,7 @@ def dynamic_type(): return None elif isinstance(field, mongoengine.ReferenceField): return graphene.Field(_type, resolver=reference_resolver, - description=get_field_description(field, registry)) + description=get_field_description(field, registry)) elif isinstance(field, mongoengine.CachedReferenceField): return graphene.Field(_type, resolver=cached_reference_resolver, description=get_field_description(field, registry)) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index d01886f8..15b9a773 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -200,13 +200,13 @@ def get_queryset(self, model, info, only_fields=list(), **args): else: args.update(queryset_or_filters) - return model.objects(**args).only(*only_fields).order_by(self.order_by) + return model.objects(**args).no_dereference().only(*only_fields).order_by(self.order_by) def default_resolver(self, _root, info, only_fields=list(), **args): args = args or {} if _root is not None and getattr(_root, info.field_name, []) is not None: - args["pk__in"] = [r.pk for r in getattr(_root, info.field_name, [])] + args["pk__in"] = [r.id for r in getattr(_root, info.field_name, [])] connection_args = { "first": args.pop("first", None), diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 8d60c111..66dc93e4 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -135,6 +135,11 @@ def collect_query_fields(node, fragments): elif leaf['kind'] == 'FragmentSpread': field.update(collect_query_fields(fragments[leaf['name']['value']], fragments)) + elif leaf['kind'] == 'InlineFragment': + field.update({ + leaf["type_condition"]["name"]['value']: collect_query_fields(leaf, fragments) + }) + pass return field From b91d2a2d65d2249e8c3bed6e3318c0c67e9d62d0 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Fri, 30 Oct 2020 21:00:29 +0530 Subject: [PATCH 20/40] Support Added for GenericReferenceField --- graphene_mongo/converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 8c286d33..82a8c94c 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -165,7 +165,7 @@ def convert_field_to_union(field, registry=None): def reference_resolver(root, *args, **kwargs): dereferenced = getattr(root, field.name or field.db_name) document = get_document(dereferenced["_cls"]) - document_field= mongoengine.ReferenceField(document) + document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field(document_field, registry) document_field_type = document_field.get_type().type._meta.name only_fields = [to_snake_case(i) for i in get_query_fields(args[0])[document_field_type].keys()] From 1553e71a45fa86203e40624c42d8fe041820b05b Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sat, 31 Oct 2020 04:59:59 +0530 Subject: [PATCH 21/40] Bugs fixed --- graphene_mongo/converter.py | 2 +- graphene_mongo/fields.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 82a8c94c..95268e33 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -128,7 +128,7 @@ def convert_field_to_list(field, registry=None): return graphene.List( base_type, description=get_field_description(field, registry), - required=field.required, + required=field.required ) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 15b9a773..7be0e13a 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -5,6 +5,7 @@ import graphene import mongoengine +from graphene import Context from graphene.utils.str_converters import to_snake_case from promise import Promise from graphql_relay import from_global_id @@ -245,7 +246,16 @@ def chained_resolver(self, resolver, is_partial, root, info, **args): for field in get_query_fields(info): if to_snake_case(field) in self.model._fields_ordered: only_fields.append(to_snake_case(field)) - if not bool(args) or not is_partial: + if root is None and (not bool(args) or not is_partial): + if isinstance(self.model, mongoengine.Document) or isinstance(self.model, + mongoengine.base.metaclasses.TopLevelDocumentMetaclass): + args_copy = args.copy() + for arg_name, arg in args.copy().items(): + if arg_name not in self.model._fields_ordered: + args_copy.pop(arg_name) + if not info.context: + info.context = Context() + info.context.queryset = self.get_queryset(self.model, info, only_fields, **args_copy) # XXX: Filter nested args resolved = resolver(root, info, **args) if resolved is not None: From 263a74ed48255fb587ece1b69f7a6a5e4ed4fd8f Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sat, 31 Oct 2020 12:53:43 +0530 Subject: [PATCH 22/40] Query efficiency and performance - By retrieving only the queried fields from the database - Test Passed --- graphene_mongo/converter.py | 46 ++++++++++++++++++++++-- graphene_mongo/fields.py | 26 +++++++++++--- graphene_mongo/tests/test_relay_query.py | 2 +- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 95268e33..5c9e95ac 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -7,6 +7,7 @@ from mongoengine.base import get_document from . import advanced_types from .utils import import_single_dispatch, get_field_description, get_query_fields +from concurrent.futures import ThreadPoolExecutor, wait, as_completed singledispatch = import_single_dispatch() @@ -104,6 +105,46 @@ def convert_file_to_field(field, registry=None): def convert_field_to_list(field, registry=None): base_type = convert_mongoengine_field(field.field, registry=registry) if isinstance(base_type, graphene.Field): + if isinstance(field.field, mongoengine.GenericReferenceField): + def get_reference_objects(*args, **kwargs): + if args[0][1]: + document = get_document(args[0][0]) + document_field = mongoengine.ReferenceField(document) + document_field = convert_mongoengine_field(document_field, registry) + document_field_type = document_field.get_type().type._meta.name + only_fields = [to_snake_case(i) for i in get_query_fields(args[0][3])[document_field_type].keys()] + return document.objects().no_dereference().only(*only_fields).filter(pk__in=args[0][1]) + else: + return [] + + def reference_resolver(root, *args, **kwargs): + choice_to_resolve = dict() + to_resolve = getattr(root, field.name or field.db_name) + for each in to_resolve: + if each['_cls'] not in choice_to_resolve: + choice_to_resolve[each['_cls']] = list() + choice_to_resolve[each['_cls']].append(each["_ref"].id) + + pool = ThreadPoolExecutor(5) + futures = list() + for model, object_id_list in choice_to_resolve.items(): + futures.append(pool.submit(get_reference_objects, (model, object_id_list, registry, *args))) + result = list() + for x in as_completed(futures): + result += x.result() + to_resolve_object_ids = [each["_ref"].id for each in to_resolve] + result_to_resolve_object_ids = [each.id for each in result] + ordered_result = list() + for each in to_resolve_object_ids: + ordered_result.append(result[result_to_resolve_object_ids.index(each)]) + return ordered_result + + return graphene.List( + base_type._type, + description=get_field_description(field, registry), + required=field.required, + resolver=reference_resolver + ) return graphene.List( base_type._type, description=get_field_description(field, registry), @@ -128,7 +169,7 @@ def convert_field_to_list(field, registry=None): return graphene.List( base_type, description=get_field_description(field, registry), - required=field.required + required=field.required, ) @@ -172,7 +213,8 @@ def reference_resolver(root, *args, **kwargs): return document.objects().no_dereference().only(*only_fields).get(pk=dereferenced["_ref"].id) if isinstance(field, mongoengine.GenericReferenceField): - return graphene.Field(_union, resolver=reference_resolver) + return graphene.Field(_union, resolver=reference_resolver, + description=get_field_description(field, registry)) return graphene.Field(_union) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 7be0e13a..73b0d61a 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -5,6 +5,7 @@ import graphene import mongoengine +from bson import DBRef from graphene import Context from graphene.utils.str_converters import to_snake_case from promise import Promise @@ -61,6 +62,12 @@ def model(self): def order_by(self): return self.node_type._meta.order_by + @property + def only_fields(self): + if isinstance(self.node_type._meta.only_fields, str): + return self.node_type._meta.only_fields.split(",") + return list() + @property def registry(self): return getattr(self.node_type._meta, "registry", get_global_registry()) @@ -206,8 +213,10 @@ def get_queryset(self, model, info, only_fields=list(), **args): def default_resolver(self, _root, info, only_fields=list(), **args): args = args or {} - if _root is not None and getattr(_root, info.field_name, []) is not None: - args["pk__in"] = [r.id for r in getattr(_root, info.field_name, [])] + if _root is not None: + field_name = to_snake_case(info.field_name) + if getattr(_root, field_name, []) is not None: + args["pk__in"] = [r.id for r in getattr(_root, field_name, [])] connection_args = { "first": args.pop("first", None), @@ -243,10 +252,13 @@ def default_resolver(self, _root, info, only_fields=list(), **args): def chained_resolver(self, resolver, is_partial, root, info, **args): only_fields = list() + for field in self.only_fields: + if field in self.model._fields_ordered: + only_fields.append(field) for field in get_query_fields(info): if to_snake_case(field) in self.model._fields_ordered: only_fields.append(to_snake_case(field)) - if root is None and (not bool(args) or not is_partial): + if not bool(args) or not is_partial: if isinstance(self.model, mongoengine.Document) or isinstance(self.model, mongoengine.base.metaclasses.TopLevelDocumentMetaclass): args_copy = args.copy() @@ -259,7 +271,13 @@ def chained_resolver(self, resolver, is_partial, root, info, **args): # XXX: Filter nested args resolved = resolver(root, info, **args) if resolved is not None: - return resolved + if isinstance(resolved, list): + if resolved == list(): + return resolved + elif not isinstance(resolved[0], DBRef): + return resolved + else: + return resolved return self.default_resolver(root, info, only_fields, **args) @classmethod diff --git a/graphene_mongo/tests/test_relay_query.py b/graphene_mongo/tests/test_relay_query.py index f2128af9..0439b2a0 100644 --- a/graphene_mongo/tests/test_relay_query.py +++ b/graphene_mongo/tests/test_relay_query.py @@ -17,7 +17,7 @@ class Query(graphene.ObjectType): reporter = graphene.Field(nodes.ReporterNode) def resolve_reporter(self, *args, **kwargs): - return models.Reporter.objects.first() + return models.Reporter.objects.no_dereference().first() query = """ query ReporterQuery { From 827fbecd588542ac807305168257f40fb2d5f826 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sat, 31 Oct 2020 13:15:07 +0530 Subject: [PATCH 23/40] concurrent.futures standard library module to Python 2 added --- graphene_mongo/converter.py | 6 +++--- requirements.txt | 1 + setup.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 5c9e95ac..1233ab6a 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -7,7 +7,7 @@ from mongoengine.base import get_document from . import advanced_types from .utils import import_single_dispatch, get_field_description, get_query_fields -from concurrent.futures import ThreadPoolExecutor, wait, as_completed +from concurrent.futures import ThreadPoolExecutor, as_completed singledispatch = import_single_dispatch() @@ -112,7 +112,7 @@ def get_reference_objects(*args, **kwargs): document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field(document_field, registry) document_field_type = document_field.get_type().type._meta.name - only_fields = [to_snake_case(i) for i in get_query_fields(args[0][3])[document_field_type].keys()] + only_fields = [to_snake_case(i) for i in get_query_fields(args[0][3][0])[document_field_type].keys()] return document.objects().no_dereference().only(*only_fields).filter(pk__in=args[0][1]) else: return [] @@ -128,7 +128,7 @@ def reference_resolver(root, *args, **kwargs): pool = ThreadPoolExecutor(5) futures = list() for model, object_id_list in choice_to_resolve.items(): - futures.append(pool.submit(get_reference_objects, (model, object_id_list, registry, *args))) + futures.append(pool.submit(get_reference_objects, (model, object_id_list, registry, args))) result = list() for x in as_completed(futures): result += x.result() diff --git a/requirements.txt b/requirements.txt index 1c9d02e7..93b5e6c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ pytest-cov==2.5.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 attrs==19.1.0 +futures; python_version < '3.0' diff --git a/setup.py b/setup.py index b07ee06a..7f878467 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ "mongoengine>=0.15.0", "singledispatch>=3.4.0.3", "iso8601>=0.1.12", + 'futures; python_version == "2.7"' ], python_requires=">=2.7", zip_safe=True, From ff387fec3a233e8bd6b43d79fd18c4f6f33c0d48 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 07:29:41 +0530 Subject: [PATCH 24/40] requirements updated --- requirements.txt | 27 +++++++++++++++------------ setup.py | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/requirements.txt b/requirements.txt index 93b5e6c4..020117ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,18 @@ -coveralls==1.2.0 -flake8==3.7.9 -flake8-per-file-ignores==0.6 -future==0.17.1 -graphene>=2.1.3,<3 -iso8601==0.1.12 -mongoengine==0.16.3 -mongomock==3.14.0 -pymongo==3.6.1 -pytest==3.3.2 -pytest-cov==2.5.1 +coveralls==1.11.1; python_version < '3.0' +coveralls==2.1.2; python_version > '3.0' +flake8<3.7,>=3; +flake8-per-file-ignores==0.8.1 +future==0.18.2 +graphene>=2.1.8,<3 +iso8601==0.1.13 +mongoengine==0.19.1; python_version < '3.0' +mongoengine==0.20.0; python_version > '3.0' +mongomock==3.21.0 +pymongo==3.11.0 +pytest==4.6.11; python_version < '3.0' +pytest==6.1.2; python_version > '3.0' +pytest-cov==2.10.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 -attrs==19.1.0 +attrs==20.2.0 futures; python_version < '3.0' diff --git a/setup.py b/setup.py index 7f878467..e1a9e71d 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ "mongoengine>=0.15.0", "singledispatch>=3.4.0.3", "iso8601>=0.1.12", - 'futures; python_version == "2.7"' + 'futures; python_version < "3.0"' ], python_requires=">=2.7", zip_safe=True, From 4c17e869576d8b6b267ae8c1686e6d5252696b6e Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 11:06:29 +0530 Subject: [PATCH 25/40] dereference added to node resolver --- graphene_mongo/types.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/graphene_mongo/types.py b/graphene_mongo/types.py index ad68a1c5..007c7bba 100644 --- a/graphene_mongo/types.py +++ b/graphene_mongo/types.py @@ -5,11 +5,12 @@ from graphene.relay import Connection, Node from graphene.types.objecttype import ObjectType, ObjectTypeOptions from graphene.types.utils import yank_fields_from_attrs +from graphene.utils.str_converters import to_snake_case from graphene_mongo import MongoengineConnectionField from .converter import convert_mongoengine_field from .registry import Registry, get_global_registry -from .utils import get_model_fields, is_valid_mongoengine_model +from .utils import get_model_fields, is_valid_mongoengine_model, get_query_fields def construct_fields(model, registry, only_fields, exclude_fields): @@ -210,7 +211,15 @@ def is_type_of(cls, root, info): @classmethod def get_node(cls, info, id): - return cls._meta.model.objects.get(pk=id) + only_fields = list() + for field in cls._meta.only_fields: + if field in cls._meta.model._fields_ordered: + only_fields.append(field) + for field in get_query_fields(info): + if to_snake_case(field) in cls._meta.model._fields_ordered: + only_fields.append(to_snake_case(field)) + only_fields = list(set(only_fields)) + return cls._meta.model.objects.no_dereference().only(*only_fields).get(pk=id) def resolve_id(self, info): return str(self.id) From afa284d2a38f3ab8a337cee1ec348e216d614cc5 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 11:22:43 +0530 Subject: [PATCH 26/40] Bugs fixed --- graphene_mongo/converter.py | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 1233ab6a..c08e2056 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -112,7 +112,8 @@ def get_reference_objects(*args, **kwargs): document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field(document_field, registry) document_field_type = document_field.get_type().type._meta.name - only_fields = [to_snake_case(i) for i in get_query_fields(args[0][3][0])[document_field_type].keys()] + only_fields = [to_snake_case(i) for i in + get_query_fields(args[0][3][0])[document_field_type].keys()] return document.objects().no_dereference().only(*only_fields).filter(pk__in=args[0][1]) else: return [] @@ -208,9 +209,12 @@ def reference_resolver(root, *args, **kwargs): document = get_document(dereferenced["_cls"]) document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field(document_field, registry) - document_field_type = document_field.get_type().type._meta.name - only_fields = [to_snake_case(i) for i in get_query_fields(args[0])[document_field_type].keys()] - return document.objects().no_dereference().only(*only_fields).get(pk=dereferenced["_ref"].id) + _type = document_field.get_type().type + only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, + str) else list() + return document.objects().no_dereference().only(*list( + set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0])[_type._meta.name].keys()]))).get( + pk=dereferenced["_ref"].id) if isinstance(field, mongoengine.GenericReferenceField): return graphene.Field(_union, resolver=reference_resolver, @@ -228,25 +232,21 @@ def convert_field_to_dynamic(field, registry=None): def reference_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) if document: - only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] - return field.document_type.objects().no_dereference().only(*only_fields).get(pk=document.id) + _type = registry.get_type_for_model(field.document_type) + only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, + str) else list() + return field.document_type.objects().no_dereference().only( + *((list(set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()]))) + )).get(pk=document.id) return None - def cached_reference_resolver(root, *args, **kwargs): - only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] - return field.document_type.objects().no_dereference().only(*only_fields).get( - pk=getattr(root, field.name or field.db_name)) - def dynamic_type(): _type = registry.get_type_for_model(model) if not _type: return None - elif isinstance(field, mongoengine.ReferenceField): + elif isinstance(field, mongoengine.ReferenceField) or isinstance(field, mongoengine.CachedReferenceField): return graphene.Field(_type, resolver=reference_resolver, description=get_field_description(field, registry)) - elif isinstance(field, mongoengine.CachedReferenceField): - return graphene.Field(_type, resolver=cached_reference_resolver, - description=get_field_description(field, registry)) return graphene.Field(_type, description=get_field_description(field, registry)) @@ -259,11 +259,16 @@ def convert_lazy_field_to_dynamic(field, registry=None): def lazy_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) - only_fields = [to_snake_case(i) for i in get_query_fields(args[0]).keys()] - return document.document_type.objects().no_dereference().only(*only_fields).get(pk=document.pk) + _type = registry.get_type_for_model(document.document_type) + only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, + str) else list() + return document.document_type.objects().no_dereference().only( + *(list(set((only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()]))))).get( + pk=document.pk) def dynamic_type(): _type = registry.get_type_for_model(model) + if not _type: return None return graphene.Field( From e85a612f11574dc509c4a3ae7abf221704790d3f Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 11:29:09 +0530 Subject: [PATCH 27/40] [lint] Error fixed --- graphene_mongo/converter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index c08e2056..a847c749 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -236,8 +236,8 @@ def reference_resolver(root, *args, **kwargs): only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, str) else list() return field.document_type.objects().no_dereference().only( - *((list(set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()]))) - )).get(pk=document.id) + *((list(set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()]))))).get( + pk=document.id) return None def dynamic_type(): From 4a68f963f3944315db83bc4506d2b0e2a5fb039f Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 11:55:05 +0530 Subject: [PATCH 28/40] requirements.txt updated --- requirements.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 020117ee..f4ee6da2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -coveralls==1.11.1; python_version < '3.0' -coveralls==2.1.2; python_version > '3.0' -flake8<3.7,>=3; -flake8-per-file-ignores==0.8.1 +coveralls==1.11.1; python_version < '3.4' +coveralls==2.1.2; python_version > '3.4' +flake8==3.7.9 +flake8-per-file-ignores==0.6 future==0.18.2 graphene>=2.1.8,<3 iso8601==0.1.13 @@ -11,7 +11,6 @@ mongomock==3.21.0 pymongo==3.11.0 pytest==4.6.11; python_version < '3.0' pytest==6.1.2; python_version > '3.0' -pytest-cov==2.10.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 attrs==20.2.0 From 1c9df706f314c9392ca263e78efc0e0efccfb232 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:08:58 +0530 Subject: [PATCH 29/40] requirements.txt updated --- requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f4ee6da2..fbcca067 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,12 +5,13 @@ flake8-per-file-ignores==0.6 future==0.18.2 graphene>=2.1.8,<3 iso8601==0.1.13 -mongoengine==0.19.1; python_version < '3.0' -mongoengine==0.20.0; python_version > '3.0' +mongoengine==0.19.1; python_version < '3.4' +mongoengine==0.20.0; python_version > '3.4' mongomock==3.21.0 pymongo==3.11.0 pytest==4.6.11; python_version < '3.0' pytest==6.1.2; python_version > '3.0' +pytest-cov==2.10.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 attrs==20.2.0 From f477b90b805f48bf91e64b82f64e529b3476e2bd Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:12:59 +0530 Subject: [PATCH 30/40] requirements.txt updated for python 3.4 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index fbcca067..c10d60c3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,8 @@ mongoengine==0.19.1; python_version < '3.4' mongoengine==0.20.0; python_version > '3.4' mongomock==3.21.0 pymongo==3.11.0 -pytest==4.6.11; python_version < '3.0' -pytest==6.1.2; python_version > '3.0' +pytest==4.6.11; python_version < '3.4' +pytest==6.1.2; python_version > '3.4' pytest-cov==2.10.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 From 597421bb6ae2dd1ca07492534f82658fbe17deb5 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:19:30 +0530 Subject: [PATCH 31/40] requirements.txt updated for python 3.4 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c10d60c3..65c376f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,8 @@ mongoengine==0.19.1; python_version < '3.4' mongoengine==0.20.0; python_version > '3.4' mongomock==3.21.0 pymongo==3.11.0 -pytest==4.6.11; python_version < '3.4' -pytest==6.1.2; python_version > '3.4' +pytest==4.6.11; python_version < '3.5' +pytest==6.1.2; python_version > '3.5' pytest-cov==2.10.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 From c138693a6993bb13432268dc586439aa2a974cf3 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:20:35 +0530 Subject: [PATCH 32/40] requirements.txt updated for all versions --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 65c376f4..4a3f296d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ coveralls==1.11.1; python_version < '3.4' -coveralls==2.1.2; python_version > '3.4' +coveralls==2.1.2; python_version >= '3.4' flake8==3.7.9 flake8-per-file-ignores==0.6 future==0.18.2 graphene>=2.1.8,<3 iso8601==0.1.13 mongoengine==0.19.1; python_version < '3.4' -mongoengine==0.20.0; python_version > '3.4' +mongoengine==0.20.0; python_version >= '3.4' mongomock==3.21.0 pymongo==3.11.0 -pytest==4.6.11; python_version < '3.5' -pytest==6.1.2; python_version > '3.5' +pytest==4.6.11; python_version <= '3.4' +pytest==6.1.2; python_version >= '3.4' pytest-cov==2.10.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 From dcd2422915f6cc57f275f52f3e841f28c142d2a1 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:27:07 +0530 Subject: [PATCH 33/40] requirements.txt updated for python3.4 --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4a3f296d..7b9f8d9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ coveralls==1.11.1; python_version < '3.4' -coveralls==2.1.2; python_version >= '3.4' +coveralls==2.1.2; python_version > '3.4' flake8==3.7.9 flake8-per-file-ignores==0.6 future==0.18.2 graphene>=2.1.8,<3 iso8601==0.1.13 mongoengine==0.19.1; python_version < '3.4' -mongoengine==0.20.0; python_version >= '3.4' +mongoengine==0.20.0; python_version > '3.4' mongomock==3.21.0 pymongo==3.11.0 pytest==4.6.11; python_version <= '3.4' -pytest==6.1.2; python_version >= '3.4' +pytest==6.1.2; python_version > '3.4' pytest-cov==2.10.1 singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 From a73e3fa5d38604dae23d0ca7501d8f079be1d72f Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:31:44 +0530 Subject: [PATCH 34/40] requirements.txt updated for python3.4 --- requirements.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7b9f8d9a..aa07450a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,20 @@ coveralls==1.11.1; python_version < '3.4' -coveralls==2.1.2; python_version > '3.4' +coveralls==2.1.2; python_version >= '3.4' flake8==3.7.9 flake8-per-file-ignores==0.6 future==0.18.2 graphene>=2.1.8,<3 iso8601==0.1.13 mongoengine==0.19.1; python_version < '3.4' -mongoengine==0.20.0; python_version > '3.4' +mongoengine==0.20.0; python_version >= '3.4' mongomock==3.21.0 pymongo==3.11.0 -pytest==4.6.11; python_version <= '3.4' -pytest==6.1.2; python_version > '3.4' -pytest-cov==2.10.1 +pytest==4.6.11; python_version < '3.4' +pytest==6.1.2; python_version >= '3.4' +pytest-cov==2.8.1; python_version == '3.7' +pytest-cov==2.10.1; python_version < '3.7' +pytest-cov==2.10.1; python_version > '3.4' + singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 attrs==20.2.0 From b5717d891d274b5c5d0df49e7b96f806b05da116 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:36:01 +0530 Subject: [PATCH 35/40] requirements.txt updated for python3.4 --- requirements.txt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index aa07450a..77f36064 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,18 @@ -coveralls==1.11.1; python_version < '3.4' -coveralls==2.1.2; python_version >= '3.4' +coveralls==1.11.1; python_version < '3.0' +coveralls==2.1.2; python_version >= '3.0' flake8==3.7.9 flake8-per-file-ignores==0.6 future==0.18.2 graphene>=2.1.8,<3 iso8601==0.1.13 -mongoengine==0.19.1; python_version < '3.4' -mongoengine==0.20.0; python_version >= '3.4' +mongoengine==0.19.1; python_version < '3.0' +mongoengine==0.20.0; python_version >= '3.0' mongomock==3.21.0 pymongo==3.11.0 -pytest==4.6.11; python_version < '3.4' -pytest==6.1.2; python_version >= '3.4' -pytest-cov==2.8.1; python_version == '3.7' -pytest-cov==2.10.1; python_version < '3.7' -pytest-cov==2.10.1; python_version > '3.4' - +pytest==4.6.11; python_version < '3.0' +pytest==6.1.2; python_version >= '3.0' +pytest-cov==2.8.1; python_version == '3.4' +pytest-cov==2.10.1; python_version < '3.4' or python_version > '3.4' singledispatch==3.4.0.3 # https://stackoverflow.com/a/58189684/9041712 attrs==20.2.0 From ddf2d561870b0d491354900e2dba5ae8ddc6af1f Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:48:12 +0530 Subject: [PATCH 36/40] requirements.txt updated for python3.4 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 77f36064..d5c84bb7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -coveralls==1.11.1; python_version < '3.0' -coveralls==2.1.2; python_version >= '3.0' +coveralls==1.11.1; python_version <= '3.4' +coveralls==2.1.2; python_version > '3.4' flake8==3.7.9 flake8-per-file-ignores==0.6 future==0.18.2 From 16f97a30074341cfe9ee956210f2cedf1b022e17 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 12:52:13 +0530 Subject: [PATCH 37/40] requirements.txt updated for python3.4 --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index d5c84bb7..6b4d43e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,12 +5,12 @@ flake8-per-file-ignores==0.6 future==0.18.2 graphene>=2.1.8,<3 iso8601==0.1.13 -mongoengine==0.19.1; python_version < '3.0' -mongoengine==0.20.0; python_version >= '3.0' +mongoengine==0.19.1; python_version <= '3.4' +mongoengine==0.20.0; python_version > '3.4' mongomock==3.21.0 pymongo==3.11.0 -pytest==4.6.11; python_version < '3.0' -pytest==6.1.2; python_version >= '3.0' +pytest==4.6.11; python_version <= '3.4' +pytest==6.1.2; python_version > '3.4' pytest-cov==2.8.1; python_version == '3.4' pytest-cov==2.10.1; python_version < '3.4' or python_version > '3.4' singledispatch==3.4.0.3 From d42a224d46ae8f0d8500f05ec8b9c48c039ed1a8 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Sun, 1 Nov 2020 13:22:19 +0530 Subject: [PATCH 38/40] CachedReferenceField - bug fixed --- graphene_mongo/converter.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index a847c749..6d3573d2 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -240,13 +240,25 @@ def reference_resolver(root, *args, **kwargs): pk=document.id) return None + def cached_reference_resolver(root, *args, **kwargs): + _type = registry.get_type_for_model(field.document_type) + only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, + str) else list() + return field.document_type.objects().no_dereference().only( + *(list(set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()])) + )).get( + pk=getattr(root, field.name or field.db_name)) + def dynamic_type(): _type = registry.get_type_for_model(model) if not _type: return None - elif isinstance(field, mongoengine.ReferenceField) or isinstance(field, mongoengine.CachedReferenceField): + elif isinstance(field, mongoengine.ReferenceField): return graphene.Field(_type, resolver=reference_resolver, description=get_field_description(field, registry)) + elif isinstance(field, mongoengine.CachedReferenceField): + return graphene.Field(_type, resolver=cached_reference_resolver, + description=get_field_description(field, registry)) return graphene.Field(_type, description=get_field_description(field, registry)) From 6981bfeac9417fe11fd05e7f1a3da60a7752a7be Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Mon, 2 Nov 2020 09:21:17 +0530 Subject: [PATCH 39/40] Implemented None Check for all type of reference and list of reference field. --- graphene_mongo/converter.py | 88 ++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 6d3573d2..fa51b216 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -121,24 +121,26 @@ def get_reference_objects(*args, **kwargs): def reference_resolver(root, *args, **kwargs): choice_to_resolve = dict() to_resolve = getattr(root, field.name or field.db_name) - for each in to_resolve: - if each['_cls'] not in choice_to_resolve: - choice_to_resolve[each['_cls']] = list() - choice_to_resolve[each['_cls']].append(each["_ref"].id) - - pool = ThreadPoolExecutor(5) - futures = list() - for model, object_id_list in choice_to_resolve.items(): - futures.append(pool.submit(get_reference_objects, (model, object_id_list, registry, args))) - result = list() - for x in as_completed(futures): - result += x.result() - to_resolve_object_ids = [each["_ref"].id for each in to_resolve] - result_to_resolve_object_ids = [each.id for each in result] - ordered_result = list() - for each in to_resolve_object_ids: - ordered_result.append(result[result_to_resolve_object_ids.index(each)]) - return ordered_result + if to_resolve: + for each in to_resolve: + if each['_cls'] not in choice_to_resolve: + choice_to_resolve[each['_cls']] = list() + choice_to_resolve[each['_cls']].append(each["_ref"].id) + + pool = ThreadPoolExecutor(5) + futures = list() + for model, object_id_list in choice_to_resolve.items(): + futures.append(pool.submit(get_reference_objects, (model, object_id_list, registry, args))) + result = list() + for x in as_completed(futures): + result += x.result() + to_resolve_object_ids = [each["_ref"].id for each in to_resolve] + result_to_resolve_object_ids = [each.id for each in result] + ordered_result = list() + for each in to_resolve_object_ids: + ordered_result.append(result[result_to_resolve_object_ids.index(each)]) + return ordered_result + return [] return graphene.List( base_type._type, @@ -206,15 +208,17 @@ def convert_field_to_union(field, registry=None): def reference_resolver(root, *args, **kwargs): dereferenced = getattr(root, field.name or field.db_name) - document = get_document(dereferenced["_cls"]) - document_field = mongoengine.ReferenceField(document) - document_field = convert_mongoengine_field(document_field, registry) - _type = document_field.get_type().type - only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, - str) else list() - return document.objects().no_dereference().only(*list( - set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0])[_type._meta.name].keys()]))).get( - pk=dereferenced["_ref"].id) + if dereferenced: + document = get_document(dereferenced["_cls"]) + document_field = mongoengine.ReferenceField(document) + document_field = convert_mongoengine_field(document_field, registry) + _type = document_field.get_type().type + only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, + str) else list() + return document.objects().no_dereference().only(*list( + set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0])[_type._meta.name].keys()]))).get( + pk=dereferenced["_ref"].id) + return None if isinstance(field, mongoengine.GenericReferenceField): return graphene.Field(_union, resolver=reference_resolver, @@ -241,13 +245,15 @@ def reference_resolver(root, *args, **kwargs): return None def cached_reference_resolver(root, *args, **kwargs): - _type = registry.get_type_for_model(field.document_type) - only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, - str) else list() - return field.document_type.objects().no_dereference().only( - *(list(set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()])) - )).get( - pk=getattr(root, field.name or field.db_name)) + if field: + _type = registry.get_type_for_model(field.document_type) + only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, + str) else list() + return field.document_type.objects().no_dereference().only( + *(list(set(only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()])) + )).get( + pk=getattr(root, field.name or field.db_name)) + return None def dynamic_type(): _type = registry.get_type_for_model(model) @@ -271,12 +277,14 @@ def convert_lazy_field_to_dynamic(field, registry=None): def lazy_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) - _type = registry.get_type_for_model(document.document_type) - only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, - str) else list() - return document.document_type.objects().no_dereference().only( - *(list(set((only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()]))))).get( - pk=document.pk) + if document: + _type = registry.get_type_for_model(document.document_type) + only_fields = _type._meta.only_fields.split(",") if isinstance(_type._meta.only_fields, + str) else list() + return document.document_type.objects().no_dereference().only( + *(list(set((only_fields + [to_snake_case(i) for i in get_query_fields(args[0]).keys()]))))).get( + pk=document.pk) + return None def dynamic_type(): _type = registry.get_type_for_model(model) From 74e60944cc456c22d48f8ff21f9556b728021789 Mon Sep 17 00:00:00 2001 From: Arun S Kumar Date: Mon, 2 Nov 2020 10:24:41 +0530 Subject: [PATCH 40/40] Queryset added to info.context --- graphene_mongo/fields.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 73b0d61a..d5ee71a1 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -8,6 +8,7 @@ from bson import DBRef from graphene import Context from graphene.utils.str_converters import to_snake_case +from graphql import ResolveInfo from promise import Promise from graphql_relay import from_global_id from graphene.relay import ConnectionField @@ -232,6 +233,10 @@ def default_resolver(self, _root, info, only_fields=list(), **args): if callable(getattr(self.model, "objects", None)): iterables = self.get_queryset(self.model, info, only_fields, **args) + if isinstance(info, ResolveInfo): + if not info.context: + info.context = Context() + info.context.queryset = iterables list_length = iterables.count() else: iterables = [] @@ -265,9 +270,10 @@ def chained_resolver(self, resolver, is_partial, root, info, **args): for arg_name, arg in args.copy().items(): if arg_name not in self.model._fields_ordered: args_copy.pop(arg_name) - if not info.context: - info.context = Context() - info.context.queryset = self.get_queryset(self.model, info, only_fields, **args_copy) + if isinstance(info, ResolveInfo): + if not info.context: + info.context = Context() + info.context.queryset = self.get_queryset(self.model, info, only_fields, **args_copy) # XXX: Filter nested args resolved = resolver(root, info, **args) if resolved is not None: