Skip to content

Commit 9e79a53

Browse files
authored
Merge pull request #172 from arunsureshkumar/support-graphene-v3
Support graphene v3
2 parents 659ea56 + de158a3 commit 9e79a53

File tree

9 files changed

+100
-56
lines changed

9 files changed

+100
-56
lines changed

graphene_mongo/advanced_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def resolve_data(self, info):
3030
v = getattr(self.instance, self.key)
3131
data = v.read()
3232
if data is not None:
33-
return base64.b64encode(data)
33+
return base64.b64encode(data).decode("utf-8")
3434
return None
3535

3636

graphene_mongo/fields.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import graphene
77
import mongoengine
8-
from bson import DBRef
8+
from bson import DBRef, ObjectId
99
from graphene import Context
1010
from graphene.types.utils import get_type
1111
from graphene.utils.str_converters import to_snake_case
12-
from graphql import ResolveInfo
12+
from graphql import GraphQLResolveInfo
1313
from mongoengine.base import get_document
1414
from promise import Promise
1515
from graphql_relay import from_global_id
@@ -168,7 +168,7 @@ def filter_args(self):
168168
}
169169
filter_type = advanced_filter_types.get(each, filter_type)
170170
filter_args[field + "__" + each] = graphene.Argument(
171-
type=filter_type
171+
type_=filter_type
172172
)
173173
return filter_args
174174

@@ -215,7 +215,10 @@ def fields(self):
215215
self._type = get_type(self._type)
216216
return self._type._meta.fields
217217

218-
def get_queryset(self, model, info, required_fields=list(), skip=None, limit=None, reversed=False, **args):
218+
def get_queryset(self, model, info, required_fields=None, skip=None, limit=None, reversed=False, **args):
219+
if required_fields is None:
220+
required_fields = list()
221+
219222
if args:
220223
reference_fields = get_model_reference_fields(self.model)
221224
hydrated_references = {}
@@ -276,8 +279,13 @@ def get_queryset(self, model, info, required_fields=list(), skip=None, limit=Non
276279
skip)
277280
return model.objects(**args).no_dereference().only(*required_fields).order_by(self.order_by)
278281

279-
def default_resolver(self, _root, info, required_fields=list(), **args):
282+
def default_resolver(self, _root, info, required_fields=None, **args):
283+
if required_fields is None:
284+
required_fields = list()
280285
args = args or {}
286+
for key, value in dict(args).items():
287+
if value is None:
288+
del args[key]
281289
if _root is not None:
282290
field_name = to_snake_case(info.field_name)
283291
if not hasattr(_root, "_fields_ordered"):
@@ -301,9 +309,13 @@ def default_resolver(self, _root, info, required_fields=list(), **args):
301309
limit = None
302310
reverse = False
303311
first = args.pop("first", None)
304-
after = cursor_to_offset(args.pop("after", None))
312+
after = args.pop("after", None)
313+
if after:
314+
after = cursor_to_offset(after)
305315
last = args.pop("last", None)
306-
before = cursor_to_offset(args.pop("before", None))
316+
before = args.pop("before", None)
317+
if before:
318+
before = cursor_to_offset(before)
307319
if callable(getattr(self.model, "objects", None)):
308320
if "pk__in" in args and args["pk__in"]:
309321
count = len(args["pk__in"])
@@ -318,20 +330,32 @@ def default_resolver(self, _root, info, required_fields=list(), **args):
318330
args["pk__in"] = args["pk__in"][skip:]
319331
iterables = self.get_queryset(self.model, info, required_fields, **args)
320332
list_length = len(iterables)
321-
if isinstance(info, ResolveInfo):
333+
if isinstance(info, GraphQLResolveInfo):
322334
if not info.context:
323-
info.context = Context()
335+
info = info._replace(context=Context())
324336
info.context.queryset = self.get_queryset(self.model, info, required_fields, **args)
325337
elif _root is None or args:
326-
count = self.get_queryset(self.model, info, required_fields, **args).count()
338+
args_copy = args.copy()
339+
for key in args.copy():
340+
if key not in self.model._fields_ordered:
341+
args_copy.pop(key)
342+
elif isinstance(getattr(self.model, key),
343+
mongoengine.fields.ReferenceField) or isinstance(getattr(self.model, key),
344+
mongoengine.fields.GenericReferenceField) or isinstance(
345+
getattr(self.model, key),
346+
mongoengine.fields.LazyReferenceField) or isinstance(getattr(self.model, key),
347+
mongoengine.fields.CachedReferenceField):
348+
if not isinstance(args_copy[key], ObjectId):
349+
args_copy[key] = from_global_id(args_copy[key])[1]
350+
count = mongoengine.get_db()[self.model._get_collection_name()].find(args_copy).count()
327351
if count != 0:
328352
skip, limit, reverse = find_skip_and_limit(first=first, after=after, last=last, before=before,
329353
count=count)
330354
iterables = self.get_queryset(self.model, info, required_fields, skip, limit, reverse, **args)
331355
list_length = len(iterables)
332-
if isinstance(info, ResolveInfo):
356+
if isinstance(info, GraphQLResolveInfo):
333357
if not info.context:
334-
info.context = Context()
358+
info = info._replace(context=Context())
335359
info.context.queryset = self.get_queryset(self.model, info, required_fields, **args)
336360

337361
elif _root is not None:
@@ -367,6 +391,9 @@ def default_resolver(self, _root, info, required_fields=list(), **args):
367391
return connection
368392

369393
def chained_resolver(self, resolver, is_partial, root, info, **args):
394+
for key, value in dict(args).items():
395+
if value is None:
396+
del args[key]
370397
required_fields = list()
371398
for field in self.required_fields:
372399
if field in self.model._fields_ordered:
@@ -378,13 +405,15 @@ def chained_resolver(self, resolver, is_partial, root, info, **args):
378405
if not bool(args) or not is_partial:
379406
if isinstance(self.model, mongoengine.Document) or isinstance(self.model,
380407
mongoengine.base.metaclasses.TopLevelDocumentMetaclass):
408+
381409
for arg_name, arg in args.copy().items():
382410
if arg_name not in self.model._fields_ordered + tuple(self.filter_args.keys()):
383411
args_copy.pop(arg_name)
384-
if isinstance(info, ResolveInfo):
412+
if isinstance(info, GraphQLResolveInfo):
385413
if not info.context:
386-
info.context = Context()
387-
info.context.queryset = self.get_queryset(self.model, info, required_fields, **args_copy)
414+
info = info._replace(context=Context())
415+
info.context.queryset = self.get_queryset(self.model, info, required_fields, **args)
416+
388417
# XXX: Filter nested args
389418
resolved = resolver(root, info, **args)
390419
if resolved is not None:
@@ -405,7 +434,7 @@ def chained_resolver(self, resolver, is_partial, root, info, **args):
405434
if arg_name == '_id' and isinstance(arg, dict):
406435
operation = list(arg.keys())[0]
407436
args_copy['pk' + operation.replace('$', '__')] = arg[operation]
408-
if '.' in arg_name:
437+
if not isinstance(arg, ObjectId) and '.' in arg_name:
409438
operation = list(arg.keys())[0]
410439
args_copy[arg_name.replace('.', '__') + operation.replace('$', '__')] = arg[operation]
411440
else:
@@ -415,6 +444,8 @@ def chained_resolver(self, resolver, is_partial, root, info, **args):
415444
args_copy[arg_name + operation.replace('$', '__')] = arg[operation]
416445
del args_copy[arg_name]
417446
return self.default_resolver(root, info, required_fields, **args_copy)
447+
elif isinstance(resolved, Promise):
448+
return resolved.value
418449
else:
419450
return resolved
420451
return self.default_resolver(root, info, required_fields, **args)

graphene_mongo/tests/test_inputs.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ def mutate(self, info, article):
2121
return CreateArticle(article=article)
2222

2323
class Query(graphene.ObjectType):
24-
2524
node = Node.Field()
2625

2726
class Mutation(graphene.ObjectType):
28-
2927
create_article = CreateArticle.Field()
3028

3129
query = """
@@ -57,7 +55,8 @@ class Arguments:
5755
def mutate(self, info, id, editor):
5856
editor_to_update = Editor.objects.get(id=id)
5957
for key, value in editor.items():
60-
setattr(editor_to_update, key, value)
58+
if value:
59+
setattr(editor_to_update, key, value)
6160
editor_to_update.save()
6261
return UpdateEditor(editor=editor_to_update)
6362

graphene_mongo/tests/test_query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def resolve_editors(self, *args, **kwargs):
5555
"chunkSize": 261120,
5656
"length": 46928,
5757
"md5": "f3c657fd472fdc4bc2ca9056a1ae6106",
58-
"data": str(data),
58+
"data": data.decode("utf-8"),
5959
},
6060
},
6161
"editors": [

graphene_mongo/tests/test_relay_query.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class Query(graphene.ObjectType):
173173
"avatar": {
174174
"contentType": "image/jpeg",
175175
"length": 46928,
176-
"data": str(data),
176+
"data": data.decode("utf-8"),
177177
},
178178
}
179179
},
@@ -489,7 +489,6 @@ class Query(graphene.ObjectType):
489489

490490
def test_should_first_n(fixtures):
491491
class Query(graphene.ObjectType):
492-
493492
editors = MongoengineConnectionField(nodes.EditorNode)
494493

495494
query = """
@@ -533,7 +532,6 @@ class Query(graphene.ObjectType):
533532

534533
def test_should_after(fixtures):
535534
class Query(graphene.ObjectType):
536-
537535
players = MongoengineConnectionField(nodes.PlayerNode)
538536

539537
query = """
@@ -566,7 +564,6 @@ class Query(graphene.ObjectType):
566564

567565
def test_should_before(fixtures):
568566
class Query(graphene.ObjectType):
569-
570567
players = MongoengineConnectionField(nodes.PlayerNode)
571568

572569
query = """
@@ -632,7 +629,6 @@ class Query(graphene.ObjectType):
632629

633630
def test_should_self_reference(fixtures):
634631
class Query(graphene.ObjectType):
635-
636632
players = MongoengineConnectionField(nodes.PlayerNode)
637633

638634
query = """
@@ -767,7 +763,6 @@ class Query(graphene.ObjectType):
767763

768764
def test_should_query_with_embedded_document(fixtures):
769765
class Query(graphene.ObjectType):
770-
771766
professors = MongoengineConnectionField(nodes.ProfessorVectorNode)
772767

773768
query = """
@@ -1026,7 +1021,6 @@ class Query(graphene.ObjectType):
10261021

10271022

10281023
def test_should_filter_mongoengine_queryset_by_id_and_other_fields(fixtures):
1029-
10301024
class Query(graphene.ObjectType):
10311025
players = MongoengineConnectionField(nodes.PlayerNode)
10321026

graphene_mongo/utils.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import mongoengine
77
from graphene import Node
88
from graphene.utils.trim_docstring import trim_docstring
9-
from graphql.utils.ast_to_dict import ast_to_dict
9+
# from graphql.utils.ast_to_dict import ast_to_dict
10+
from graphql import FieldNode
1011
from graphql_relay.connection.arrayconnection import offset_to_cursor
1112

1213

@@ -126,21 +127,24 @@ def collect_query_fields(node, fragments):
126127
"""
127128

128129
field = {}
129-
130-
if node.get('selection_set'):
131-
for leaf in node['selection_set']['selections']:
132-
if leaf['kind'] == 'Field':
130+
selection_set = None
131+
if type(node) == dict:
132+
selection_set = node.get('selection_set')
133+
else:
134+
selection_set = node.selection_set
135+
if selection_set:
136+
for leaf in selection_set.selections:
137+
if leaf.kind == 'field':
133138
field.update({
134-
leaf['name']['value']: collect_query_fields(leaf, fragments)
139+
leaf.name.value: collect_query_fields(leaf, fragments)
135140
})
136-
elif leaf['kind'] == 'FragmentSpread':
141+
elif leaf.kind == 'fragment_spread':
137142
field.update(collect_query_fields(fragments[leaf['name']['value']],
138143
fragments))
139-
elif leaf['kind'] == 'InlineFragment':
144+
elif leaf.kind == 'inline_fragment':
140145
field.update({
141-
leaf["type_condition"]["name"]['value']: collect_query_fields(leaf, fragments)
146+
leaf.type_condition.name.value: collect_query_fields(leaf, fragments)
142147
})
143-
pass
144148

145149
return field
146150

@@ -156,7 +160,7 @@ def get_query_fields(info):
156160
"""
157161

158162
fragments = {}
159-
node = ast_to_dict(info.field_asts[0])
163+
node = ast_to_dict(info.field_nodes[0])
160164

161165
for name, value in info.fragments.items():
162166
fragments[name] = ast_to_dict(value)
@@ -167,6 +171,24 @@ def get_query_fields(info):
167171
return query
168172

169173

174+
def ast_to_dict(node, include_loc=False):
175+
if isinstance(node, FieldNode):
176+
d = {"kind": node.__class__.__name__}
177+
if hasattr(node, "keys"):
178+
for field in node.keys:
179+
d[field] = ast_to_dict(getattr(node, field), include_loc)
180+
181+
if include_loc and hasattr(node, "loc") and node.loc:
182+
d["loc"] = {"start": node.loc.start, "end": node.loc.end}
183+
184+
return d
185+
186+
elif isinstance(node, list):
187+
return [ast_to_dict(item, include_loc) for item in node]
188+
189+
return node
190+
191+
170192
def find_skip_and_limit(first, last, after, before, count):
171193
reverse = False
172194
skip = 0

requirements.txt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
coveralls==1.11.1; python_version <= '3.5'
2-
coveralls==2.1.2; python_version > '3.5'
2+
coveralls==3.0.1; python_version > '3.5'
33
flake8==3.7.9
44
flake8-per-file-ignores==0.6
55
future==0.18.2
6-
graphene>=2.1.8,<3
7-
iso8601==0.1.13
6+
graphene==3.0b7
7+
promise==2.3
88
mongoengine==0.19.1; python_version <= '3.5'
9-
mongoengine==0.22.1; python_version > '3.5'
10-
mongomock==3.22.0
11-
pymongo==3.11.2
9+
mongoengine==0.23.0; python_version > '3.5'
10+
mongomock==3.22.1
1211
pytest==4.6.11; python_version <= '3.5'
13-
pytest==6.2.1; python_version > '3.5'
12+
pytest==6.2.2; python_version > '3.5'
1413
pytest-cov==2.8.1; python_version == '3.5' or python_version == '3.4'
15-
pytest-cov==2.10.1; python_version < '3.4' or python_version > '3.5'
16-
singledispatch==3.4.0.3
14+
pytest-cov==2.11.1; python_version < '3.4' or python_version > '3.5'
15+
singledispatch==3.6.1
1716
# https://stackoverflow.com/a/58189684/9041712
1817
attrs==20.2.0
1918
futures; python_version < '3.0'

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[metadata]
2-
description-file = README.md
2+
description_file = README.md
33

44
[flake8]
55
exclude = setup.py,docs/*,examples/*

setup.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name="graphene-mongo",
5-
version="0.2.13",
5+
version="0.3.1",
66
description="Graphene Mongoengine integration",
77
long_description=open("README.rst").read(),
88
url="https://github.com/graphql-python/graphene-mongo",
@@ -13,24 +13,23 @@
1313
"Development Status :: 4 - Beta",
1414
"Intended Audience :: Developers",
1515
"Topic :: Software Development :: Libraries",
16-
"Programming Language :: Python :: 2.7",
17-
"Programming Language :: Python :: 3.4",
18-
"Programming Language :: Python :: 3.5",
1916
"Programming Language :: Python :: 3.6",
2017
"Programming Language :: Python :: 3.8",
18+
"Programming Language :: Python :: 3.9",
2119
"Programming Language :: Python :: Implementation :: PyPy",
2220
"License :: OSI Approved :: MIT License",
2321
],
2422
keywords="api graphql protocol rest relay graphene mongo mongoengine",
2523
packages=find_packages(exclude=["tests"]),
2624
install_requires=[
27-
"graphene>=2.1.3,<3",
28-
"mongoengine>=0.15.0",
25+
"graphene==3.0b7",
26+
"promise==2.3",
27+
"mongoengine>=0.23.0",
2928
"singledispatch>=3.4.0.3",
3029
"iso8601>=0.1.12",
3130
'futures; python_version < "3.0"'
3231
],
33-
python_requires=">=2.7",
32+
python_requires=">=3.6",
3433
zip_safe=True,
3534
tests_require=["pytest>=3.3.2", "mongomock", "mock"],
3635
)

0 commit comments

Comments
 (0)